firebase-tools 11.24.0 → 11.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/lib/commands/database-import.js +74 -0
  2. package/lib/commands/ext-dev-init.js +1 -1
  3. package/lib/commands/firestore-delete.js +12 -7
  4. package/lib/commands/firestore-indexes-list.js +5 -2
  5. package/lib/commands/index.js +1 -0
  6. package/lib/commands/init.js +5 -3
  7. package/lib/commands/logout.js +15 -15
  8. package/lib/database/import.js +125 -0
  9. package/lib/deploy/extensions/prepare.js +0 -14
  10. package/lib/deploy/firestore/deploy.js +22 -16
  11. package/lib/deploy/firestore/prepare.js +34 -24
  12. package/lib/deploy/firestore/release.js +13 -9
  13. package/lib/deploy/functions/prepare.js +1 -1
  14. package/lib/deploy/functions/release/fabricator.js +2 -24
  15. package/lib/deploy/functions/release/index.js +5 -5
  16. package/lib/deploy/functions/services/firestore.js +17 -0
  17. package/lib/deploy/functions/services/index.js +14 -0
  18. package/lib/emulator/controller.js +14 -3
  19. package/lib/extensions/emulator/optionsHelper.js +1 -1
  20. package/lib/extensions/emulator/triggerHelper.js +19 -1
  21. package/lib/extensions/paramHelper.js +7 -49
  22. package/lib/extensions/warnings.js +1 -7
  23. package/lib/firestore/delete.js +6 -1
  24. package/lib/firestore/fsConfig.js +67 -0
  25. package/lib/firestore/indexes.js +13 -13
  26. package/lib/firestore/options.js +2 -0
  27. package/lib/firestore/util.js +9 -7
  28. package/lib/functions/events/v2.js +7 -1
  29. package/lib/gcp/cloudfunctions.js +4 -1
  30. package/lib/gcp/cloudfunctionsv2.js +6 -1
  31. package/lib/gcp/firestore.js +14 -1
  32. package/lib/gcp/run.js +6 -0
  33. package/lib/init/features/hosting/github.js +1 -1
  34. package/lib/rulesDeploy.js +1 -3
  35. package/npm-shrinkwrap.json +2 -2
  36. package/package.json +1 -1
  37. package/schema/firebase-config.json +137 -29
  38. package/templates/extensions/CL-template.md +2 -0
  39. package/templates/init/functions/python/main.py +4 -4
  40. package/templates/init/functions/typescript/_eslintrc +1 -1
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const clc = require("colorette");
5
+ const fs = require("fs");
6
+ const utils = require("../utils");
7
+ const command_1 = require("../command");
8
+ const import_1 = require("../database/import");
9
+ const types_1 = require("../emulator/types");
10
+ const error_1 = require("../error");
11
+ const logger_1 = require("../logger");
12
+ const projectUtils_1 = require("../projectUtils");
13
+ const commandUtils_1 = require("../emulator/commandUtils");
14
+ const prompt_1 = require("../prompt");
15
+ const database_1 = require("../management/database");
16
+ const api_1 = require("../database/api");
17
+ const requireDatabaseInstance_1 = require("../requireDatabaseInstance");
18
+ const requirePermissions_1 = require("../requirePermissions");
19
+ exports.command = new command_1.Command("database:import <path> [infile]")
20
+ .description("non-atomically import the contents of a JSON file to the specified path in Realtime Database")
21
+ .withForce()
22
+ .option("--instance <instance>", "use the database <instance>.firebaseio.com (if omitted, use default database instance)")
23
+ .option("--disable-triggers", "suppress any Cloud functions triggered by this operation, default to true", true)
24
+ .option("--filter <dataPath>", "import only data at this path in the JSON file (if omitted, import entire file)")
25
+ .before(requirePermissions_1.requirePermissions, ["firebasedatabase.instances.update"])
26
+ .before(requireDatabaseInstance_1.requireDatabaseInstance)
27
+ .before(database_1.populateInstanceDetails)
28
+ .before(commandUtils_1.printNoticeIfEmulated, types_1.Emulators.DATABASE)
29
+ .action(async (path, infile, options) => {
30
+ if (!path.startsWith("/")) {
31
+ throw new error_1.FirebaseError("Path must begin with /");
32
+ }
33
+ if (!infile) {
34
+ throw new error_1.FirebaseError("No file supplied");
35
+ }
36
+ const projectId = (0, projectUtils_1.needProjectId)(options);
37
+ const origin = (0, api_1.realtimeOriginOrEmulatorOrCustomUrl)(options.instanceDetails.databaseUrl);
38
+ const dbPath = utils.getDatabaseUrl(origin, options.instance, path);
39
+ const dbUrl = new URL(dbPath);
40
+ if (options.disableTriggers) {
41
+ dbUrl.searchParams.set("disableTriggers", "true");
42
+ }
43
+ const confirm = await (0, prompt_1.promptOnce)({
44
+ type: "confirm",
45
+ name: "force",
46
+ default: false,
47
+ message: "You are about to import data to " + clc.cyan(dbPath) + ". Are you sure?",
48
+ }, options);
49
+ if (!confirm) {
50
+ throw new error_1.FirebaseError("Command aborted.");
51
+ }
52
+ const inStream = fs.createReadStream(infile);
53
+ const dataPath = options.filter || "";
54
+ const importer = new import_1.default(dbUrl, inStream, dataPath);
55
+ let responses;
56
+ try {
57
+ responses = await importer.execute();
58
+ }
59
+ catch (err) {
60
+ if (err instanceof error_1.FirebaseError) {
61
+ throw err;
62
+ }
63
+ logger_1.logger.debug(err);
64
+ throw new error_1.FirebaseError(`Unexpected error while importing data: ${err}`, { exit: 2 });
65
+ }
66
+ if (responses.length) {
67
+ utils.logSuccess("Data persisted successfully");
68
+ }
69
+ else {
70
+ utils.logWarning("No data was persisted. Check the data path supplied.");
71
+ }
72
+ logger_1.logger.info();
73
+ logger_1.logger.info(clc.bold("View data at:"), utils.getDatabaseViewDataUrl(origin, projectId, options.instance, path));
74
+ });
@@ -24,7 +24,7 @@ function readCommonTemplates() {
24
24
  extSpecTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "extension.yaml"), "utf8"),
25
25
  preinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "PREINSTALL.md"), "utf8"),
26
26
  postinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "POSTINSTALL.md"), "utf8"),
27
- changelogTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "CHANGELOG.md"), "utf8"),
27
+ changelogTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "CL-template.md"), "utf8"),
28
28
  };
29
29
  }
30
30
  exports.command = new command_1.Command("ext:dev:init")
@@ -9,12 +9,12 @@ const delete_1 = require("../firestore/delete");
9
9
  const prompt_1 = require("../prompt");
10
10
  const requirePermissions_1 = require("../requirePermissions");
11
11
  const utils = require("../utils");
12
- function getConfirmationMessage(deleteOp, options) {
12
+ function confirmationMessage(deleteOp, options) {
13
13
  if (options.allCollections) {
14
14
  return ("You are about to delete " +
15
15
  clc.bold(clc.yellow(clc.underline("THE ENTIRE DATABASE"))) +
16
16
  " for " +
17
- clc.cyan(options.project) +
17
+ clc.cyan(deleteOp.getRoot()) +
18
18
  ". Are you sure?");
19
19
  }
20
20
  if (deleteOp.isDocumentPath) {
@@ -23,13 +23,13 @@ function getConfirmationMessage(deleteOp, options) {
23
23
  clc.cyan(deleteOp.path) +
24
24
  " and all of its subcollections " +
25
25
  " for " +
26
- clc.cyan(options.project) +
26
+ clc.cyan(deleteOp.getRoot()) +
27
27
  ". Are you sure?");
28
28
  }
29
29
  return ("You are about to delete the document at " +
30
30
  clc.cyan(deleteOp.path) +
31
31
  " for " +
32
- clc.cyan(options.project) +
32
+ clc.cyan(deleteOp.getRoot()) +
33
33
  ". Are you sure?");
34
34
  }
35
35
  if (options.recursive) {
@@ -37,13 +37,13 @@ function getConfirmationMessage(deleteOp, options) {
37
37
  clc.cyan(deleteOp.path) +
38
38
  " and all of their subcollections " +
39
39
  " for " +
40
- clc.cyan(options.project) +
40
+ clc.cyan(deleteOp.getRoot()) +
41
41
  ". Are you sure?");
42
42
  }
43
43
  return ("You are about to delete all documents in the collection at " +
44
44
  clc.cyan(deleteOp.path) +
45
45
  " for " +
46
- clc.cyan(options.project) +
46
+ clc.cyan(deleteOp.getRoot()) +
47
47
  ". Are you sure?");
48
48
  }
49
49
  exports.command = new command_1.Command("firestore:delete [path]")
@@ -56,22 +56,27 @@ exports.command = new command_1.Command("firestore:delete [path]")
56
56
  .option("--all-collections", "Delete all. Deletes the entire Firestore database, " +
57
57
  "including all collections and documents. Any other flags or arguments will be ignored.")
58
58
  .option("-f, --force", "No confirmation. Otherwise, a confirmation prompt will appear.")
59
+ .option("--database <databaseId>", 'Database ID for database to delete from. "(default)" if none is provided.')
59
60
  .before(commandUtils_1.printNoticeIfEmulated, types_1.Emulators.FIRESTORE)
60
61
  .before(requirePermissions_1.requirePermissions, ["datastore.entities.list", "datastore.entities.delete"])
61
62
  .action(async (path, options) => {
62
63
  if (!path && !options.allCollections) {
63
64
  return utils.reject("Must specify a path.", { exit: 1 });
64
65
  }
66
+ if (!options.database) {
67
+ options.database = "(default)";
68
+ }
65
69
  const deleteOp = new delete_1.FirestoreDelete(options.project, path, {
66
70
  recursive: options.recursive,
67
71
  shallow: options.shallow,
68
72
  allCollections: options.allCollections,
73
+ databaseId: options.database,
69
74
  });
70
75
  const confirm = await (0, prompt_1.promptOnce)({
71
76
  type: "confirm",
72
77
  name: "force",
73
78
  default: false,
74
- message: getConfirmationMessage(deleteOp, options),
79
+ message: confirmationMessage(deleteOp, options),
75
80
  }, options);
76
81
  if (!confirm) {
77
82
  return utils.reject("Command aborted.", { exit: 1 });
@@ -12,12 +12,15 @@ exports.command = new command_1.Command("firestore:indexes")
12
12
  .description("List indexes in your project's Cloud Firestore database.")
13
13
  .option("--pretty", "Pretty print. When not specified the indexes are printed in the " +
14
14
  "JSON specification format.")
15
+ .option("--database <databaseId>", "Database ID of the firestore database from which to list indexes. (default) if none provided.")
15
16
  .before(requirePermissions_1.requirePermissions, ["datastore.indexes.list"])
16
17
  .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
17
18
  .action(async (options) => {
19
+ var _a;
18
20
  const indexApi = new fsi.FirestoreIndexes();
19
- const indexes = await indexApi.listIndexes(options.project);
20
- const fieldOverrides = await indexApi.listFieldOverrides(options.project);
21
+ const databaseId = (_a = options.database) !== null && _a !== void 0 ? _a : "(default)";
22
+ const indexes = await indexApi.listIndexes(options.project, databaseId);
23
+ const fieldOverrides = await indexApi.listFieldOverrides(options.project, databaseId);
21
24
  const indexSpec = indexApi.makeIndexSpec(indexes, fieldOverrides);
22
25
  if (options.pretty) {
23
26
  logger_1.logger.info(clc.bold(clc.white("Compound Indexes")));
@@ -39,6 +39,7 @@ function load(client) {
39
39
  client.crashlytics.mappingfile.upload = loadCommand("crashlytics-mappingfile-upload");
40
40
  client.database = {};
41
41
  client.database.get = loadCommand("database-get");
42
+ client.database.import = loadCommand("database-import");
42
43
  client.database.instances = {};
43
44
  client.database.instances.create = loadCommand("database-instances-create");
44
45
  client.database.instances.list = loadCommand("database-instances-list");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
3
+ exports.initAction = exports.command = void 0;
4
4
  const clc = require("colorette");
5
5
  const fs = require("fs");
6
6
  const os = require("os");
@@ -76,7 +76,8 @@ ${[...featureNames]
76
76
  exports.command = new command_1.Command("init [feature]")
77
77
  .description(DESCRIPTION)
78
78
  .before(requireAuth_1.requireAuth)
79
- .action((feature, options) => {
79
+ .action(initAction);
80
+ function initAction(feature, options) {
80
81
  if (feature && !featureNames.includes(feature)) {
81
82
  return utils.reject(clc.bold(feature) +
82
83
  " is not a supported feature; must be one of " +
@@ -177,4 +178,5 @@ exports.command = new command_1.Command("init [feature]")
177
178
  logger_1.logger.info();
178
179
  utils.logSuccess("Firebase initialization complete!");
179
180
  });
180
- });
181
+ }
182
+ exports.initAction = initAction;
@@ -1,27 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
3
+ exports.logoutAction = exports.command = void 0;
4
4
  const command_1 = require("../command");
5
5
  const logger_1 = require("../logger");
6
6
  const clc = require("colorette");
7
7
  const utils = require("../utils");
8
- const auth = require("../auth");
9
8
  const prompt_1 = require("../prompt");
9
+ const auth_1 = require("../auth");
10
10
  exports.command = new command_1.Command("logout [email]")
11
11
  .description("log the CLI out of Firebase")
12
- .action(async (email, options) => {
12
+ .action(logoutAction);
13
+ async function logoutAction(email, options) {
13
14
  const globalToken = utils.getInheritedOption(options, "token");
14
15
  utils.assertIsStringOrUndefined(globalToken);
15
- const allAccounts = auth.getAllAccounts();
16
+ const allAccounts = (0, auth_1.getAllAccounts)();
16
17
  if (allAccounts.length === 0 && !globalToken) {
17
18
  logger_1.logger.info("No need to logout, not logged in");
18
19
  return;
19
20
  }
20
- const defaultAccount = auth.getGlobalDefaultAccount();
21
- const additionalAccounts = auth.getAdditionalAccounts();
22
- const accountsToLogOut = email
23
- ? allAccounts.filter((a) => a.user.email === email)
24
- : allAccounts;
21
+ const defaultAccount = (0, auth_1.getGlobalDefaultAccount)();
22
+ const additionalAccounts = (0, auth_1.getAdditionalAccounts)();
23
+ const accountsToLogOut = email ? allAccounts.filter((a) => a.user.email === email) : allAccounts;
25
24
  if (email && accountsToLogOut.length === 0) {
26
25
  utils.logWarning(`No account matches ${email}, can't log out.`);
27
26
  return;
@@ -49,9 +48,9 @@ exports.command = new command_1.Command("logout [email]")
49
48
  for (const account of accountsToLogOut) {
50
49
  const token = account.tokens.refresh_token;
51
50
  if (token) {
52
- auth.setRefreshToken(token);
51
+ (0, auth_1.setRefreshToken)(token);
53
52
  try {
54
- await auth.logout(token);
53
+ await (0, auth_1.logout)(token);
55
54
  }
56
55
  catch (e) {
57
56
  utils.logWarning(`Invalid refresh token for ${account.user.email}, did not need to deauthorize`);
@@ -60,9 +59,9 @@ exports.command = new command_1.Command("logout [email]")
60
59
  }
61
60
  }
62
61
  if (globalToken) {
63
- auth.setRefreshToken(globalToken);
62
+ (0, auth_1.setRefreshToken)(globalToken);
64
63
  try {
65
- await auth.logout(globalToken);
64
+ await (0, auth_1.logout)(globalToken);
66
65
  }
67
66
  catch (e) {
68
67
  utils.logWarning("Invalid refresh token, did not need to deauthorize");
@@ -71,6 +70,7 @@ exports.command = new command_1.Command("logout [email]")
71
70
  }
72
71
  if (newDefaultAccount) {
73
72
  utils.logSuccess(`Setting default account to "${newDefaultAccount.user.email}"`);
74
- auth.setGlobalDefaultAccount(newDefaultAccount);
73
+ (0, auth_1.setGlobalDefaultAccount)(newDefaultAccount);
75
74
  }
76
- });
75
+ }
76
+ exports.logoutAction = logoutAction;
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const Chain = require("stream-chain");
4
+ const clc = require("colorette");
5
+ const Filter = require("stream-json/filters/Filter");
6
+ const stream = require("stream");
7
+ const StreamObject = require("stream-json/streamers/StreamObject");
8
+ const p_limit_1 = require("p-limit");
9
+ const apiv2_1 = require("../apiv2");
10
+ const error_1 = require("../error");
11
+ const MAX_CHUNK_SIZE = 1024 * 1024 * 10;
12
+ const CONCURRENCY_LIMIT = 5;
13
+ class DatabaseImporter {
14
+ constructor(dbUrl, inStream, dataPath, chunkSize = MAX_CHUNK_SIZE) {
15
+ this.dbUrl = dbUrl;
16
+ this.inStream = inStream;
17
+ this.dataPath = dataPath;
18
+ this.chunkSize = chunkSize;
19
+ this.limit = (0, p_limit_1.default)(CONCURRENCY_LIMIT);
20
+ this.client = new apiv2_1.Client({ urlPrefix: dbUrl.origin, auth: true });
21
+ }
22
+ async execute() {
23
+ await this.checkLocationIsEmpty();
24
+ return this.readAndWriteChunks();
25
+ }
26
+ async checkLocationIsEmpty() {
27
+ const response = await this.client.request({
28
+ method: "GET",
29
+ path: this.dbUrl.pathname + ".json",
30
+ queryParams: { shallow: "true" },
31
+ });
32
+ if (response.body) {
33
+ throw new error_1.FirebaseError("Importing is only allowed for an empty location. Delete all data by running " +
34
+ clc.bold(`firebase database:remove ${this.dbUrl.pathname} --disable-triggers`) +
35
+ ", then rerun this command.", { exit: 2 });
36
+ }
37
+ }
38
+ readAndWriteChunks() {
39
+ const { dbUrl } = this;
40
+ const chunkData = this.chunkData.bind(this);
41
+ const writeChunk = this.writeChunk.bind(this);
42
+ const getJoinedPath = this.joinPath.bind(this);
43
+ const readChunks = new stream.Transform({ objectMode: true });
44
+ readChunks._transform = function (chunk, _, done) {
45
+ const data = { json: chunk.value, pathname: getJoinedPath(dbUrl.pathname, chunk.key) };
46
+ const chunkedData = chunkData(data);
47
+ const chunks = chunkedData.chunks || [data];
48
+ for (const chunk of chunks) {
49
+ this.push(chunk);
50
+ }
51
+ done();
52
+ };
53
+ const writeChunks = new stream.Transform({ objectMode: true });
54
+ writeChunks._transform = async function (chunk, _, done) {
55
+ const res = await writeChunk(chunk);
56
+ this.push(res);
57
+ done();
58
+ };
59
+ return new Promise((resolve, reject) => {
60
+ const responses = [];
61
+ const pipeline = new Chain([
62
+ this.inStream,
63
+ Filter.withParser({
64
+ filter: this.computeFilterString(this.dataPath) || (() => true),
65
+ pathSeparator: "/",
66
+ }),
67
+ StreamObject.streamObject(),
68
+ ]);
69
+ pipeline
70
+ .on("error", (err) => reject(new error_1.FirebaseError(`Invalid data; couldn't parse JSON object, array, or value. ${err.message}`, {
71
+ original: err,
72
+ exit: 2,
73
+ })))
74
+ .pipe(readChunks)
75
+ .pipe(writeChunks)
76
+ .on("data", (res) => responses.push(res))
77
+ .on("error", reject)
78
+ .once("end", () => resolve(responses));
79
+ });
80
+ }
81
+ writeChunk(chunk) {
82
+ return this.limit(() => this.client.request({
83
+ method: "PUT",
84
+ path: chunk.pathname + ".json",
85
+ body: JSON.stringify(chunk.json),
86
+ queryParams: this.dbUrl.searchParams,
87
+ }));
88
+ }
89
+ chunkData({ json, pathname }) {
90
+ if (typeof json === "string" || typeof json === "number" || typeof json === "boolean") {
91
+ return { chunks: null, size: JSON.stringify(json).length };
92
+ }
93
+ else {
94
+ let size = 2;
95
+ const chunks = [];
96
+ let hasChunkedChild = false;
97
+ for (const [key, val] of Object.entries(json)) {
98
+ size += key.length + 3;
99
+ const child = { json: val, pathname: this.joinPath(pathname, key) };
100
+ const childChunks = this.chunkData(child);
101
+ size += childChunks.size;
102
+ if (childChunks.chunks) {
103
+ hasChunkedChild = true;
104
+ chunks.push(...childChunks.chunks);
105
+ }
106
+ else {
107
+ chunks.push(child);
108
+ }
109
+ }
110
+ if (hasChunkedChild || size >= this.chunkSize) {
111
+ return { chunks, size };
112
+ }
113
+ else {
114
+ return { chunks: null, size };
115
+ }
116
+ }
117
+ }
118
+ computeFilterString(dataPath) {
119
+ return dataPath.split("/").filter(Boolean).join("/");
120
+ }
121
+ joinPath(root, key) {
122
+ return [root, key].join("/").replace("//", "/");
123
+ }
124
+ }
125
+ exports.default = DatabaseImporter;
@@ -72,20 +72,6 @@ async function prepare(context, options, payload) {
72
72
  throw new error_1.FirebaseError("Deployment cancelled");
73
73
  }
74
74
  }
75
- if (await (0, warnings_1.displayWarningsForDeploy)(payload.instancesToCreate)) {
76
- if (!options.force && options.nonInteractive) {
77
- throw new error_1.FirebaseError("Pass the --force flag to acknowledge these terms in non-interactive mode");
78
- }
79
- else if (!options.force &&
80
- !options.nonInteractive &&
81
- !(await prompt.promptOnce({
82
- type: "confirm",
83
- message: `Do you wish to continue deploying these extensions?`,
84
- default: true,
85
- }))) {
86
- throw new error_1.FirebaseError("Deployment cancelled");
87
- }
88
- }
89
75
  const permissionsNeeded = [];
90
76
  if (payload.instancesToCreate.length) {
91
77
  permissionsNeeded.push("firebaseextensions.instances.create");
@@ -1,36 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const _ = require("lodash");
4
3
  const clc = require("colorette");
5
- const error_1 = require("../../error");
6
4
  const indexes_1 = require("../../firestore/indexes");
7
5
  const logger_1 = require("../../logger");
8
6
  const utils = require("../../utils");
9
7
  const rulesDeploy_1 = require("../../rulesDeploy");
10
8
  async function deployRules(context) {
11
- const rulesDeploy = _.get(context, "firestore.rulesDeploy");
9
+ var _a;
10
+ const rulesDeploy = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rulesDeploy;
12
11
  if (!context.firestoreRules || !rulesDeploy) {
13
12
  return;
14
13
  }
15
14
  await rulesDeploy.createRulesets(rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE);
16
15
  }
17
16
  async function deployIndexes(context, options) {
17
+ var _a;
18
18
  if (!context.firestoreIndexes) {
19
19
  return;
20
20
  }
21
- const indexesFileName = _.get(context, "firestore.indexes.name");
22
- const indexesSrc = _.get(context, "firestore.indexes.content");
23
- if (!indexesSrc) {
24
- logger_1.logger.debug("No Firestore indexes present.");
25
- return;
26
- }
27
- const indexes = indexesSrc.indexes;
28
- if (!indexes) {
29
- throw new error_1.FirebaseError(`Index file must contain an "indexes" property.`);
30
- }
31
- const fieldOverrides = indexesSrc.fieldOverrides || [];
32
- await new indexes_1.FirestoreIndexes().deploy(options, indexes, fieldOverrides);
33
- utils.logSuccess(`${clc.bold(clc.green("firestore:"))} deployed indexes in ${clc.bold(indexesFileName)} successfully`);
21
+ const indexesContext = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.indexes;
22
+ utils.logBullet(clc.bold(clc.cyan("firestore: ")) + "deploying indexes...");
23
+ const firestoreIndexes = new indexes_1.FirestoreIndexes();
24
+ await Promise.all(indexesContext.map(async (indexContext) => {
25
+ const { databaseId, indexesFileName, indexesRawSpec } = indexContext;
26
+ if (!indexesRawSpec) {
27
+ logger_1.logger.debug(`No Firestore indexes present for ${databaseId} database.`);
28
+ return;
29
+ }
30
+ const indexes = indexesRawSpec.indexes;
31
+ if (!indexes) {
32
+ logger_1.logger.error(`${databaseId} database index file must contain "indexes" property.`);
33
+ return;
34
+ }
35
+ const fieldOverrides = indexesRawSpec.fieldOverrides || [];
36
+ await firestoreIndexes.deploy(options, indexes, fieldOverrides, databaseId).then(() => {
37
+ utils.logSuccess(`${clc.bold(clc.green("firestore:"))} deployed indexes in ${clc.bold(indexesFileName)} successfully for ${databaseId} database`);
38
+ });
39
+ }));
34
40
  }
35
41
  async function default_1(context, options) {
36
42
  await Promise.all([deployRules(context), deployIndexes(context, options)]);
@@ -1,34 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const _ = require("lodash");
4
3
  const clc = require("colorette");
5
4
  const loadCJSON_1 = require("../../loadCJSON");
6
5
  const rulesDeploy_1 = require("../../rulesDeploy");
7
6
  const utils = require("../../utils");
8
- async function prepareRules(context, options) {
9
- var _a;
10
- const rulesFile = (_a = options.config.src.firestore) === null || _a === void 0 ? void 0 : _a.rules;
11
- if (context.firestoreRules && rulesFile) {
12
- const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE);
13
- _.set(context, "firestore.rulesDeploy", rulesDeploy);
14
- rulesDeploy.addFile(rulesFile);
15
- await rulesDeploy.compile();
16
- }
7
+ const fsConfig = require("../../firestore/fsConfig");
8
+ function prepareRules(context, rulesDeploy, databaseId, rulesFile) {
9
+ rulesDeploy.addFile(rulesFile);
10
+ context.firestore.rules.push({
11
+ databaseId,
12
+ rulesFile,
13
+ });
17
14
  }
18
- function prepareIndexes(context, options) {
19
- var _a;
20
- if (!context.firestoreIndexes || !((_a = options.config.src.firestore) === null || _a === void 0 ? void 0 : _a.indexes)) {
21
- return;
22
- }
23
- const indexesFileName = options.config.src.firestore.indexes;
15
+ function prepareIndexes(context, options, databaseId, indexesFileName) {
24
16
  const indexesPath = options.config.path(indexesFileName);
25
- const parsedSrc = (0, loadCJSON_1.loadCJSON)(indexesPath);
17
+ const indexesRawSpec = (0, loadCJSON_1.loadCJSON)(indexesPath);
26
18
  utils.logBullet(`${clc.bold(clc.cyan("firestore:"))} reading indexes from ${clc.bold(indexesFileName)}...`);
27
- context.firestore = context.firestore || {};
28
- context.firestore.indexes = {
29
- name: indexesFileName,
30
- content: parsedSrc,
31
- };
19
+ context.firestore.indexes.push({
20
+ databaseId,
21
+ indexesFileName,
22
+ indexesRawSpec,
23
+ });
32
24
  }
33
25
  async function default_1(context, options) {
34
26
  if (options.only) {
@@ -43,7 +35,25 @@ async function default_1(context, options) {
43
35
  context.firestoreIndexes = true;
44
36
  context.firestoreRules = true;
45
37
  }
46
- prepareIndexes(context, options);
47
- await prepareRules(context, options);
38
+ const firestoreConfigs = fsConfig.getFirestoreConfig(context.projectId, options);
39
+ if (!firestoreConfigs || firestoreConfigs.length === 0) {
40
+ return;
41
+ }
42
+ context.firestore = context.firestore || {};
43
+ context.firestore.indexes = [];
44
+ context.firestore.rules = [];
45
+ const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE);
46
+ context.firestore.rulesDeploy = rulesDeploy;
47
+ for (const firestoreConfig of firestoreConfigs) {
48
+ if (firestoreConfig.indexes) {
49
+ prepareIndexes(context, options, firestoreConfig.database, firestoreConfig.indexes);
50
+ }
51
+ if (firestoreConfig.rules) {
52
+ prepareRules(context, rulesDeploy, firestoreConfig.database, firestoreConfig.rules);
53
+ }
54
+ }
55
+ if (context.firestore.rules.length > 0) {
56
+ await rulesDeploy.compile();
57
+ }
48
58
  }
49
59
  exports.default = default_1;
@@ -1,18 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const _ = require("lodash");
4
3
  const rulesDeploy_1 = require("../../rulesDeploy");
5
- const error_1 = require("../../error");
4
+ const logger_1 = require("../../logger");
6
5
  async function default_1(context, options) {
7
- var _a;
8
- const rulesDeploy = _.get(context, "firestore.rulesDeploy");
6
+ var _a, _b;
7
+ const rulesDeploy = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rulesDeploy;
9
8
  if (!context.firestoreRules || !rulesDeploy) {
10
9
  return;
11
10
  }
12
- const rulesFile = (_a = options.config.src.firestore) === null || _a === void 0 ? void 0 : _a.rules;
13
- if (!rulesFile) {
14
- throw new error_1.FirebaseError(`Invalid firestore config: ${JSON.stringify(options.config.src.firestore)}`);
15
- }
16
- await rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE);
11
+ const rulesContext = (_b = context === null || context === void 0 ? void 0 : context.firestore) === null || _b === void 0 ? void 0 : _b.rules;
12
+ await Promise.all(rulesContext.map(async (ruleContext) => {
13
+ const databaseId = ruleContext.databaseId;
14
+ const rulesFile = ruleContext.rulesFile;
15
+ if (!rulesFile) {
16
+ logger_1.logger.error(`Invalid firestore config for ${databaseId} database: ${JSON.stringify(options.config.src.firestore)}`);
17
+ return;
18
+ }
19
+ return rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE, databaseId);
20
+ }));
17
21
  }
18
22
  exports.default = default_1;
@@ -82,7 +82,7 @@ async function prepare(context, options, payload) {
82
82
  }
83
83
  }
84
84
  for (const endpoint of backend.allEndpoints(wantBackend)) {
85
- endpoint.environmentVariables = wantBackend.environmentVariables || {};
85
+ endpoint.environmentVariables = Object.assign({}, wantBackend.environmentVariables) || {};
86
86
  let resource;
87
87
  if (endpoint.platform === "gcfv1") {
88
88
  resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`;