diff --git a/changelog.txt b/changelog.txt index e69de29b..e38baab6 100644 --- a/changelog.txt +++ b/changelog.txt @@ -0,0 +1,9 @@ +fixed - Fixed Firestore emulator bug related to https://github.com/firebase/firebase-tools/issues/1073 +fixed - Fixed Firestore emulator bug regarding array ordering during writing/reading +fixed - Fixed Firestore emulator handling of query cursors using document names +fixed - Fixed a race condition when deploying Firestore indexes (issues #1080 and #1081) +fixed - Fixed an error that occurs when a Firestore field override removes all indexes +feature - Firestore emulator now has the ability to produce rule-coverage reports +changed - Firestore emulator now exposes the v1 service definition +changed - Firestore emulator has various runtime improvements +changed - Clearer empty state when pretty-printing Firestore indexes \ No newline at end of file diff --git a/src/deploy/lifecycleHooks.js b/src/deploy/lifecycleHooks.js index 5ad213fe..899b8f34 100644 --- a/src/deploy/lifecycleHooks.js +++ b/src/deploy/lifecycleHooks.js @@ -11,13 +11,15 @@ var logger = require("../logger"); var path = require("path"); function runCommand(command, childOptions) { + var escapedCommand = command.replace(/\"/g, '\\"'); var translatedCommand = '"' + process.execPath + '" "' + - path.resolve(require.resolve("cross-env"), "..", "bin", "cross-env.js") + - '" ' + - command; + path.resolve(require.resolve("cross-env"), "..", "bin", "cross-env-shell.js") + + '" "' + + escapedCommand + + '"'; return new Promise(function(resolve, reject) { logger.info("Running command: " + command); diff --git a/src/emulator/constants.js b/src/emulator/constants.js index 7996e59a..67e324db 100644 --- a/src/emulator/constants.js +++ b/src/emulator/constants.js @@ -24,8 +24,8 @@ const _emulators = { stdout: null, cacheDir: CACHE_DIR, remoteUrl: - "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.2.2.jar", - localPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.2.2.jar"), + "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.2.3.jar", + localPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.2.3.jar"), }, }; diff --git a/src/firestore/indexes-api.ts b/src/firestore/indexes-api.ts index 1b5e8568..39f35561 100644 --- a/src/firestore/indexes-api.ts +++ b/src/firestore/indexes-api.ts @@ -65,5 +65,5 @@ export interface Field { */ export interface IndexConfig { ancestorField?: string; - indexes: Index[]; + indexes?: Index[]; } diff --git a/src/firestore/indexes.ts b/src/firestore/indexes.ts index 9c88a775..4631a5c7 100644 --- a/src/firestore/indexes.ts +++ b/src/firestore/indexes.ts @@ -59,16 +59,17 @@ export class FirestoreIndexes { ); } - indexesToDeploy.forEach(async (index) => { + const indexPromises: Array> = []; + indexesToDeploy.forEach((index) => { const exists = existingIndexes.some((x) => this.indexMatchesSpec(x, index)); if (exists) { logger.debug(`Skipping existing index: ${JSON.stringify(index)}`); - return; + } else { + logger.debug(`Creating new index: ${JSON.stringify(index)}`); + indexPromises.push(this.createIndex(project, index)); } - - logger.debug(`Creating new index: ${JSON.stringify(index)}`); - await this.createIndex(project, index); }); + await Promise.all(indexPromises); if (existingFieldOverrides.length > fieldOverridesToDeploy.length) { utils.logBullet( @@ -78,16 +79,17 @@ export class FirestoreIndexes { ); } - fieldOverridesToDeploy.forEach(async (field) => { + const fieldPromises: Array> = []; + fieldOverridesToDeploy.forEach((field) => { const exists = existingFieldOverrides.some((x) => this.fieldMatchesSpec(x, field)); if (exists) { logger.debug(`Skipping existing field override: ${JSON.stringify(field)}`); - return; + } else { + logger.debug(`Updating field override: ${JSON.stringify(field)}`); + fieldPromises.push(this.patchField(project, field)); } - - logger.debug(`Updating field override: ${JSON.stringify(field)}`); - await this.patchField(project, field); }); + await Promise.all(fieldPromises); } /** @@ -172,11 +174,12 @@ export class FirestoreIndexes { const fieldsJson = fields.map((field) => { const parsedName = this.parseFieldName(field.name); + const fieldIndexes = field.indexConfig.indexes || []; return { collectionGroup: parsedName.collectionGroupId, fieldPath: parsedName.fieldPath, - indexes: field.indexConfig.indexes.map((index) => { + indexes: fieldIndexes.map((index) => { const firstField = index.fields[0]; return { order: firstField.order, @@ -198,6 +201,11 @@ export class FirestoreIndexes { * @param indexes the array of indexes. */ prettyPrintIndexes(indexes: API.Index[]): void { + if (indexes.length === 0) { + logger.info("None"); + return; + } + indexes.forEach((index) => { logger.info(this.prettyIndexString(index)); }); @@ -208,6 +216,11 @@ export class FirestoreIndexes { * @param fields the array of field overrides. */ printFieldOverrides(fields: API.Field[]): void { + if (fields.length === 0) { + logger.info("None"); + return; + } + fields.forEach((field) => { logger.info(this.prettyFieldString(field)); }); @@ -390,11 +403,12 @@ export class FirestoreIndexes { return false; } - if (field.indexConfig.indexes.length !== spec.indexes.length) { + const fieldIndexes = field.indexConfig.indexes || []; + if (fieldIndexes.length !== spec.indexes.length) { return false; } - const fieldModes = field.indexConfig.indexes.map((index) => { + const fieldModes = fieldIndexes.map((index) => { const firstField = index.fields[0]; return firstField.order || firstField.arrayConfig; }); @@ -563,11 +577,16 @@ export class FirestoreIndexes { clc.yellow(parsedName.fieldPath) + "] --"; - field.indexConfig.indexes.forEach((index) => { - const firstField = index.fields[0]; - const mode = firstField.order || firstField.arrayConfig; - result += " (" + mode + ")"; - }); + const fieldIndexes = field.indexConfig.indexes || []; + if (fieldIndexes.length > 0) { + fieldIndexes.forEach((index) => { + const firstField = index.fields[0]; + const mode = firstField.order || firstField.arrayConfig; + result += ` (${mode})`; + }); + } else { + result += " (no indexes)"; + } return result; } diff --git a/src/init/features/functions/typescript.js b/src/init/features/functions/typescript.js index 0e4ea60f..70e5c47b 100644 --- a/src/init/features/functions/typescript.js +++ b/src/init/features/functions/typescript.js @@ -39,7 +39,7 @@ module.exports = function(setup, config) { return config .askWriteProjectFile("functions/package.json", PACKAGE_LINTING_TEMPLATE) .then(function() { - config.askWriteProjectFile("functions/tslint.json", TSLINT_TEMPLATE); + return config.askWriteProjectFile("functions/tslint.json", TSLINT_TEMPLATE); }); } _.set(setup, "config.functions.predeploy", 'npm --prefix "$RESOURCE_DIR" run build'); diff --git a/src/test/firestore/indexes.spec.ts b/src/test/firestore/indexes.spec.ts index 0c32d538..b32fe31a 100644 --- a/src/test/firestore/indexes.spec.ts +++ b/src/test/firestore/indexes.spec.ts @@ -198,6 +198,21 @@ describe("IndexSpecMatching", () => { expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(true); }); + it("should match a field spec with all indexes excluded", () => { + const apiField = { + name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123", + indexConfig: {}, + } as API.Field; + + const specField = { + collectionGroup: "collection", + fieldPath: "abc123", + indexes: [], + } as Spec.FieldOverride; + + expect(idx.fieldMatchesSpec(apiField, specField)).to.eql(true); + }); + it("should identify a negative field spec match", () => { const apiField = { name: "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123",