firebase-tools 14.20.0 → 14.22.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 (93) hide show
  1. package/lib/appUtils.js +2 -1
  2. package/lib/command.js +5 -9
  3. package/lib/commands/dataconnect-sdk-generate.js +66 -11
  4. package/lib/commands/deploy.js +6 -4
  5. package/lib/commands/firestore-databases-clone.js +99 -0
  6. package/lib/commands/functions-secrets-set.js +19 -1
  7. package/lib/commands/hosting-sites-create.js +4 -3
  8. package/lib/commands/index.js +1 -0
  9. package/lib/commands/init.js +12 -8
  10. package/lib/commands/internaltesting-functions-discover.js +1 -3
  11. package/lib/dataconnect/provisionCloudSql.js +3 -2
  12. package/lib/deploy/extensions/prepare.js +3 -1
  13. package/lib/deploy/functions/checkIam.js +1 -1
  14. package/lib/deploy/functions/functionsDeployHelper.js +8 -7
  15. package/lib/deploy/functions/params.js +15 -5
  16. package/lib/deploy/functions/prepare.js +9 -6
  17. package/lib/detectProjectRoot.js +1 -1
  18. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  19. package/lib/emulator/hubExport.js +5 -0
  20. package/lib/experiments.js +0 -7
  21. package/lib/firestore/api.js +15 -0
  22. package/lib/firestore/util.js +22 -1
  23. package/lib/frameworks/angular/index.js +1 -1
  24. package/lib/frameworks/flutter/index.js +1 -1
  25. package/lib/frameworks/next/index.js +1 -1
  26. package/lib/frameworks/nuxt/index.js +1 -1
  27. package/lib/frameworks/vite/index.js +5 -2
  28. package/lib/functions/projectConfig.js +5 -1
  29. package/lib/functions/secrets.js +14 -1
  30. package/lib/hosting/interactive.js +14 -19
  31. package/lib/init/features/dataconnect/index.js +21 -20
  32. package/lib/init/features/dataconnect/sdk.js +44 -21
  33. package/lib/init/features/functions/index.js +1 -0
  34. package/lib/init/features/hosting/index.js +96 -93
  35. package/lib/init/features/index.js +3 -2
  36. package/lib/init/index.js +7 -3
  37. package/lib/mcp/index.js +46 -18
  38. package/lib/mcp/prompt.js +4 -1
  39. package/lib/mcp/prompts/core/consult.js +1 -1
  40. package/lib/mcp/prompts/core/deploy.js +1 -1
  41. package/lib/mcp/prompts/core/init.js +1 -1
  42. package/lib/mcp/prompts/crashlytics/connect.js +1 -1
  43. package/lib/mcp/prompts/dataconnect/schema.js +1 -1
  44. package/lib/mcp/prompts/index.js +20 -10
  45. package/lib/mcp/resources/guides/init_backend.js +3 -26
  46. package/lib/mcp/resources/guides/init_hosting.js +15 -10
  47. package/lib/mcp/tool.js +17 -2
  48. package/lib/mcp/tools/apphosting/fetch_logs.js +1 -1
  49. package/lib/mcp/tools/apphosting/list_backends.js +1 -1
  50. package/lib/mcp/tools/auth/get_users.js +1 -1
  51. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  52. package/lib/mcp/tools/auth/update_user.js +1 -1
  53. package/lib/mcp/tools/core/create_android_sha.js +1 -1
  54. package/lib/mcp/tools/core/create_app.js +1 -1
  55. package/lib/mcp/tools/core/create_project.js +1 -1
  56. package/lib/mcp/tools/core/get_environment.js +1 -1
  57. package/lib/mcp/tools/core/get_project.js +1 -1
  58. package/lib/mcp/tools/core/get_sdk_config.js +1 -1
  59. package/lib/mcp/tools/core/get_security_rules.js +1 -1
  60. package/lib/mcp/tools/core/init.js +30 -2
  61. package/lib/mcp/tools/core/list_apps.js +1 -1
  62. package/lib/mcp/tools/core/list_projects.js +1 -1
  63. package/lib/mcp/tools/core/login.js +1 -1
  64. package/lib/mcp/tools/core/logout.js +1 -1
  65. package/lib/mcp/tools/core/read_resources.js +1 -1
  66. package/lib/mcp/tools/core/update_environment.js +1 -1
  67. package/lib/mcp/tools/core/validate_security_rules.js +15 -1
  68. package/lib/mcp/tools/crashlytics/events.js +3 -3
  69. package/lib/mcp/tools/crashlytics/issues.js +3 -3
  70. package/lib/mcp/tools/crashlytics/notes.js +4 -4
  71. package/lib/mcp/tools/crashlytics/reports.js +6 -6
  72. package/lib/mcp/tools/dataconnect/compile.js +1 -1
  73. package/lib/mcp/tools/dataconnect/execute.js +1 -1
  74. package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
  75. package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
  76. package/lib/mcp/tools/dataconnect/list_services.js +1 -1
  77. package/lib/mcp/tools/firestore/delete_document.js +1 -1
  78. package/lib/mcp/tools/firestore/get_documents.js +1 -1
  79. package/lib/mcp/tools/firestore/list_collections.js +1 -1
  80. package/lib/mcp/tools/firestore/query_collection.js +1 -1
  81. package/lib/mcp/tools/functions/get_logs.js +1 -1
  82. package/lib/mcp/tools/index.js +14 -4
  83. package/lib/mcp/tools/messaging/send_message.js +1 -1
  84. package/lib/mcp/tools/realtime_database/get_data.js +1 -1
  85. package/lib/mcp/tools/realtime_database/set_data.js +1 -1
  86. package/lib/mcp/tools/remoteconfig/get_template.js +1 -1
  87. package/lib/mcp/tools/remoteconfig/update_template.js +1 -1
  88. package/lib/mcp/tools/storage/get_download_url.js +1 -1
  89. package/lib/mcp/util/availability.js +22 -0
  90. package/lib/mcp/util/crashlytics/availability.js +81 -0
  91. package/lib/mcp/util.js +26 -6
  92. package/package.json +1 -1
  93. package/schema/firebase-config.json +3 -0
package/lib/appUtils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractAppIdentifiersAndroid = exports.extractAppIdentifierIos = exports.extractAppIdentifiersFlutter = exports.detectApps = exports.getPlatformsFromFolder = exports.appDescription = exports.Framework = exports.Platform = void 0;
3
+ exports.detectFiles = exports.extractAppIdentifiersAndroid = exports.extractAppIdentifierIos = exports.extractAppIdentifiersFlutter = exports.detectApps = exports.getPlatformsFromFolder = exports.appDescription = exports.Framework = exports.Platform = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const path = require("path");
6
6
  const glob_1 = require("glob");
@@ -228,3 +228,4 @@ async function detectFiles(dirPath, filePattern) {
228
228
  };
229
229
  return (0, glob_1.glob)(`**/${filePattern}`, options);
230
230
  }
231
+ exports.detectFiles = detectFiles;
package/lib/command.js CHANGED
@@ -106,9 +106,8 @@ class Command {
106
106
  const trackSuccess = (0, track_1.trackGA4)("command_execution", {
107
107
  command_name: this.name,
108
108
  result: "success",
109
- duration,
110
109
  interactive: (0, utils_1.getInheritedOption)(options, "nonInteractive") ? "false" : "true",
111
- });
110
+ }, duration);
112
111
  if (!isEmulator) {
113
112
  await (0, utils_1.withTimeout)(5000, trackSuccess);
114
113
  }
@@ -154,21 +153,18 @@ class Command {
154
153
  async prepare(options) {
155
154
  options = options || {};
156
155
  options.project = (0, utils_1.getInheritedOption)(options, "project");
157
- if (!process.stdin.isTTY || (0, utils_1.getInheritedOption)(options, "nonInteractive")) {
156
+ if (!process.stdin.isTTY ||
157
+ (0, utils_1.getInheritedOption)(options, "nonInteractive") ||
158
+ (0, utils_1.getInheritedOption)(options, "json")) {
158
159
  options.nonInteractive = true;
159
160
  }
160
161
  if ((0, utils_1.getInheritedOption)(options, "interactive")) {
161
- options.interactive = true;
162
162
  options.nonInteractive = false;
163
163
  }
164
164
  if ((0, utils_1.getInheritedOption)(options, "debug")) {
165
165
  options.debug = true;
166
166
  }
167
- if ((0, utils_1.getInheritedOption)(options, "json")) {
168
- options.interactive = false;
169
- options.nonInteractive = true;
170
- }
171
- else if (!options.isMCP) {
167
+ if (!(0, utils_1.getInheritedOption)(options, "json") && !options.isMCP) {
172
168
  (0, logger_1.useConsoleLoggers)();
173
169
  }
174
170
  if ((0, utils_1.getInheritedOption)(options, "config")) {
@@ -6,28 +6,80 @@ const command_1 = require("../command");
6
6
  const dataconnectEmulator_1 = require("../emulator/dataconnectEmulator");
7
7
  const projectUtils_1 = require("../projectUtils");
8
8
  const load_1 = require("../dataconnect/load");
9
- const logger_1 = require("../logger");
10
9
  const auth_1 = require("../auth");
11
10
  const utils_1 = require("../utils");
11
+ const config_1 = require("../config");
12
+ const dataconnectInit = require("../init/features/dataconnect");
13
+ const dataconnectSdkInit = require("../init/features/dataconnect/sdk");
14
+ const error_1 = require("../error");
15
+ const init_1 = require("./init");
16
+ const hub_1 = require("../emulator/hub");
12
17
  exports.command = new command_1.Command("dataconnect:sdk:generate")
13
18
  .description("generate typed SDKs for your Data Connect connectors")
14
19
  .option("--watch", "watch for changes to your connector GQL files and regenerate your SDKs when updates occur")
15
20
  .action(async (options) => {
16
- const projectId = (0, projectUtils_1.needProjectId)(options);
17
- const serviceInfos = await (0, load_1.loadAll)(projectId, options.config);
18
- const serviceInfosWithSDKs = serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => {
21
+ const projectId = (0, projectUtils_1.getProjectId)(options);
22
+ let justRanInit = false;
23
+ let config = options.config;
24
+ if (!config || !config.has("dataconnect")) {
25
+ if (options.nonInteractive) {
26
+ throw new error_1.FirebaseError(`No dataconnect project directory found. Please run ${clc.bold("firebase init dataconnect")} to set it up first.`);
27
+ }
28
+ (0, utils_1.logWarning)("No dataconnect project directory found.");
29
+ (0, utils_1.logBullet)(`Running ${clc.bold("firebase init dataconnect")} to setup a dataconnect project directory.`);
30
+ if (!config) {
31
+ const cwd = options.cwd || process.cwd();
32
+ config = new config_1.Config({}, { projectDir: cwd, cwd: cwd });
33
+ }
34
+ const setup = {
35
+ config: config.src,
36
+ projectId: projectId,
37
+ rcfile: options.rc.data,
38
+ featureInfo: {
39
+ dataconnectSource: "gen_sdk_init",
40
+ },
41
+ instructions: [],
42
+ };
43
+ await dataconnectInit.askQuestions(setup);
44
+ await dataconnectInit.actuate(setup, config, options);
45
+ await (0, init_1.postInitSaves)(setup, config);
46
+ justRanInit = true;
47
+ options.config = config;
48
+ }
49
+ let serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config);
50
+ if (!serviceInfosWithSDKs.length) {
51
+ if (justRanInit || options.nonInteractive) {
52
+ throw new error_1.FirebaseError(`No generated SDKs are configured during init. Please run ${clc.bold("firebase init dataconnect:sdk")} to configure a generated SDK.`);
53
+ }
54
+ (0, utils_1.logWarning)("No generated SDKs have been configured.");
55
+ (0, utils_1.logBullet)(`Running ${clc.bold("firebase init dataconnect:sdk")} to configure a generated SDK.`);
56
+ const setup = {
57
+ config: config.src,
58
+ projectId: projectId,
59
+ rcfile: options.rc.data,
60
+ featureInfo: {
61
+ dataconnectSource: "gen_sdk_init_sdk",
62
+ },
63
+ instructions: [],
64
+ };
65
+ await dataconnectSdkInit.askQuestions(setup);
66
+ await dataconnectSdkInit.actuate(setup, config);
67
+ justRanInit = true;
68
+ serviceInfosWithSDKs = await loadAllWithSDKs(projectId, config);
69
+ }
70
+ await generateSDKsInAll(options, serviceInfosWithSDKs, justRanInit);
71
+ });
72
+ async function loadAllWithSDKs(projectId, config) {
73
+ const serviceInfos = await (0, load_1.loadAll)(projectId || hub_1.EmulatorHub.MISSING_PROJECT_PLACEHOLDER, config);
74
+ return serviceInfos.filter((serviceInfo) => serviceInfo.connectorInfo.some((c) => {
19
75
  var _a, _b, _c, _d;
20
76
  return (((_a = c.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk) ||
21
77
  ((_b = c.connectorYaml.generate) === null || _b === void 0 ? void 0 : _b.kotlinSdk) ||
22
78
  ((_c = c.connectorYaml.generate) === null || _c === void 0 ? void 0 : _c.swiftSdk) ||
23
79
  ((_d = c.connectorYaml.generate) === null || _d === void 0 ? void 0 : _d.dartSdk));
24
80
  }));
25
- if (!serviceInfosWithSDKs.length) {
26
- logger_1.logger.warn("No generated SDKs have been declared in connector.yaml files.");
27
- logger_1.logger.warn(`Run ${clc.bold("firebase init dataconnect:sdk")} to configure a generated SDK.`);
28
- logger_1.logger.warn(`See https://firebase.google.com/docs/data-connect/web-sdk for more details of how to configure generated SDKs.`);
29
- return;
30
- }
81
+ }
82
+ async function generateSDKsInAll(options, serviceInfosWithSDKs, justRanInit) {
31
83
  async function generateSDK(serviceInfo) {
32
84
  return dataconnectEmulator_1.DataConnectEmulator.generate({
33
85
  configDir: serviceInfo.sourceDirectory,
@@ -39,10 +91,13 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
39
91
  await Promise.race(serviceInfosWithSDKs.map(generateSDK));
40
92
  }
41
93
  else {
94
+ if (justRanInit) {
95
+ return;
96
+ }
42
97
  for (const s of serviceInfosWithSDKs) {
43
98
  await generateSDK(s);
44
99
  }
45
100
  const services = serviceInfosWithSDKs.map((s) => s.dataConnectYaml.serviceId).join(", ");
46
101
  (0, utils_1.logLabeledSuccess)("dataconnect", `Successfully Generated SDKs for services: ${clc.bold(services)}`);
47
102
  }
48
- });
103
+ }
@@ -15,6 +15,7 @@ const error_1 = require("../error");
15
15
  const colorette_1 = require("colorette");
16
16
  const interactive_1 = require("../hosting/interactive");
17
17
  const utils_1 = require("../utils");
18
+ const api_1 = require("../hosting/api");
18
19
  exports.VALID_DEPLOY_TARGETS = [
19
20
  "database",
20
21
  "storage",
@@ -107,7 +108,7 @@ exports.command = new command_1.Command("deploy")
107
108
  await (0, requireDatabaseInstance_1.requireDatabaseInstance)(options);
108
109
  }
109
110
  if (options.filteredTargets.includes("hosting")) {
110
- let createSite = false;
111
+ let shouldCreateSite = false;
111
112
  try {
112
113
  await (0, requireHostingSite_1.requireHostingSite)(options);
113
114
  }
@@ -119,17 +120,18 @@ exports.command = new command_1.Command("deploy")
119
120
  throw err;
120
121
  }
121
122
  else if (err === getDefaultHostingSite_1.errNoDefaultSite) {
122
- createSite = true;
123
+ shouldCreateSite = true;
123
124
  }
124
125
  }
125
- if (!createSite) {
126
+ if (!shouldCreateSite) {
126
127
  return;
127
128
  }
128
129
  if (options.nonInteractive) {
129
130
  throw new error_1.FirebaseError(`Unable to deploy to Hosting as there is no Hosting site. Use ${(0, colorette_1.bold)("firebase hosting:sites:create")} to create a site.`);
130
131
  }
131
132
  (0, utils_1.logBullet)("No Hosting site detected.");
132
- await (0, interactive_1.interactiveCreateHostingSite)("", "", options);
133
+ const siteId = await (0, interactive_1.pickHostingSiteName)("", options);
134
+ await (0, api_1.createSite)(options.project, siteId);
133
135
  }
134
136
  })
135
137
  .before(checkValidTargetFilters_1.checkValidTargetFilters)
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const clc = require("colorette");
5
+ const command_1 = require("../command");
6
+ const fsi = require("../firestore/api");
7
+ const util_1 = require("../firestore/util");
8
+ const logger_1 = require("../logger");
9
+ const requirePermissions_1 = require("../requirePermissions");
10
+ const types_1 = require("../emulator/types");
11
+ const commandUtils_1 = require("../emulator/commandUtils");
12
+ const options_1 = require("../firestore/options");
13
+ const pretty_print_1 = require("../firestore/pretty-print");
14
+ const error_1 = require("../error");
15
+ exports.command = new command_1.Command("firestore:databases:clone <sourceDatabase> <targetDatabase>")
16
+ .description("clone one Firestore database to another")
17
+ .option("-e, --encryption-type <encryptionType>", `encryption method of the cloned database; one of ${options_1.EncryptionType.USE_SOURCE_ENCRYPTION} (default), ` +
18
+ `${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}, ${options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION}`)
19
+ .option("-k, --kms-key-name <kmsKeyName>", "resource ID of the Cloud KMS key to encrypt the cloned database. This " +
20
+ "feature is allowlist only in initial launch")
21
+ .option("-s, --snapshot-time <snapshotTime>", "snapshot time of the source database to use, in ISO 8601 format. Can be any minutely snapshot after the database's earliest version time. If unspecified, takes the most recent available snapshot")
22
+ .before(requirePermissions_1.requirePermissions, ["datastore.databases.clone"])
23
+ .before(commandUtils_1.warnEmulatorNotSupported, types_1.Emulators.FIRESTORE)
24
+ .action(async (sourceDatabase, targetDatabase, options) => {
25
+ const api = new fsi.FirestoreApi();
26
+ const printer = new pretty_print_1.PrettyPrint();
27
+ const helpCommandText = "See firebase firestore:databases:clone --help for more info.";
28
+ if (options.database) {
29
+ throw new error_1.FirebaseError(`--database is not a supported flag for 'firestoree:databases:clone'. ${helpCommandText}`);
30
+ }
31
+ let snapshotTime;
32
+ if (options.snapshotTime) {
33
+ snapshotTime = options.snapshotTime;
34
+ }
35
+ else {
36
+ snapshotTime = (0, util_1.getCurrentMinuteAsIsoString)();
37
+ }
38
+ let encryptionConfig = undefined;
39
+ switch (options.encryptionType) {
40
+ case options_1.EncryptionType.GOOGLE_DEFAULT_ENCRYPTION:
41
+ throwIfKmsKeyNameIsSet(options.kmsKeyName);
42
+ encryptionConfig = { googleDefaultEncryption: {} };
43
+ break;
44
+ case options_1.EncryptionType.USE_SOURCE_ENCRYPTION:
45
+ throwIfKmsKeyNameIsSet(options.kmsKeyName);
46
+ encryptionConfig = { useSourceEncryption: {} };
47
+ break;
48
+ case options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION:
49
+ encryptionConfig = {
50
+ customerManagedEncryption: { kmsKeyName: getKmsKeyOrThrow(options.kmsKeyName) },
51
+ };
52
+ break;
53
+ case undefined:
54
+ throwIfKmsKeyNameIsSet(options.kmsKeyName);
55
+ break;
56
+ default:
57
+ throw new error_1.FirebaseError(`Invalid value for flag --encryption-type. ${helpCommandText}`);
58
+ }
59
+ const targetDatabaseName = (0, util_1.parseDatabaseName)(targetDatabase);
60
+ const parentProject = targetDatabaseName.projectId;
61
+ const targetDatabaseId = targetDatabaseName.databaseId;
62
+ const sourceProject = (0, util_1.parseDatabaseName)(sourceDatabase).projectId;
63
+ if (parentProject !== sourceProject) {
64
+ throw new error_1.FirebaseError(`Cloning across projects is not supported.`);
65
+ }
66
+ const lro = await api.cloneDatabase(sourceProject, {
67
+ database: sourceDatabase,
68
+ snapshotTime,
69
+ }, targetDatabaseId, encryptionConfig);
70
+ if (lro.error) {
71
+ logger_1.logger.error(clc.bold(`Clone to ${printer.prettyDatabaseString(targetDatabase)} failed. See below for details.`));
72
+ printer.prettyPrintOperation(lro);
73
+ }
74
+ else {
75
+ logger_1.logger.info(clc.bold(`Successfully initiated clone to ${printer.prettyDatabaseString(targetDatabase)}`));
76
+ logger_1.logger.info("Please be sure to configure Firebase rules in your Firebase config file for\n" +
77
+ "the new database. By default, created databases will have closed rules that\n" +
78
+ "block any incoming third-party traffic.");
79
+ logger_1.logger.info();
80
+ logger_1.logger.info(`You can monitor the progress of this clone by executing this command:`);
81
+ logger_1.logger.info();
82
+ logger_1.logger.info(`firebase firestore:operations:describe --database="${targetDatabaseId}" ${lro.name}`);
83
+ logger_1.logger.info();
84
+ logger_1.logger.info(`Once the clone is complete, your database may be viewed at ${printer.firebaseConsoleDatabaseUrl(options.project, targetDatabaseId)}`);
85
+ }
86
+ return lro;
87
+ function throwIfKmsKeyNameIsSet(kmsKeyName) {
88
+ if (kmsKeyName) {
89
+ throw new error_1.FirebaseError("--kms-key-name can only be set when specifying an --encryption-type " +
90
+ `of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
91
+ }
92
+ }
93
+ function getKmsKeyOrThrow(kmsKeyName) {
94
+ if (kmsKeyName)
95
+ return kmsKeyName;
96
+ throw new error_1.FirebaseError("--kms-key-name must be provided when specifying an --encryption-type " +
97
+ `of ${options_1.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION}.`);
98
+ }
99
+ });
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const clc = require("colorette");
5
+ const tty = require("tty");
5
6
  const logger_1 = require("../logger");
7
+ const error_1 = require("../error");
6
8
  const secrets_1 = require("../functions/secrets");
7
9
  const command_1 = require("../command");
8
10
  const requirePermissions_1 = require("../requirePermissions");
@@ -26,12 +28,28 @@ exports.command = new command_1.Command("functions:secrets:set <KEY>")
26
28
  "secretmanager.versions.add",
27
29
  ])
28
30
  .option("--data-file <dataFile>", 'file path from which to read secret data. Set to "-" to read the secret data from stdin')
31
+ .option("--format <format>", "format of the secret value. 'string' (default) or 'json'")
29
32
  .action(async (unvalidatedKey, options) => {
30
33
  const projectId = (0, projectUtils_1.needProjectId)(options);
31
34
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
32
35
  const key = await (0, secrets_1.ensureValidKey)(unvalidatedKey, options);
33
36
  const secret = await (0, secrets_1.ensureSecret)(projectId, key, options);
34
- const secretValue = await (0, utils_1.readSecretValue)(`Enter a value for ${key}`, options.dataFile);
37
+ let format = options.format;
38
+ const dataFile = options.dataFile;
39
+ if (!format && dataFile && dataFile !== "-") {
40
+ if (dataFile.endsWith(".json")) {
41
+ format = "json";
42
+ }
43
+ }
44
+ if (!dataFile && tty.isatty(0) && options.nonInteractive) {
45
+ throw new error_1.FirebaseError(`Cannot prompt for secret value in non-interactive mode.\n` +
46
+ `Use --data-file to provide the secret value from a file.`);
47
+ }
48
+ const promptSuffix = format === "json" ? " (JSON format)" : "";
49
+ const secretValue = await (0, utils_1.readSecretValue)(`Enter a value for ${key}${promptSuffix}:`, dataFile);
50
+ if (format === "json") {
51
+ (0, secrets_1.validateJsonSecret)(key, secretValue);
52
+ }
35
53
  const secretVersion = await (0, secretManager_1.addVersion)(projectId, key, secretValue);
36
54
  (0, utils_1.logSuccess)(`Created a new secret version ${(0, secretManager_1.toSecretVersionResourceName)(secretVersion)}`);
37
55
  if (!(0, secretManager_1.isFunctionsManaged)(secret)) {
@@ -8,6 +8,7 @@ const utils_1 = require("../utils");
8
8
  const logger_1 = require("../logger");
9
9
  const projectUtils_1 = require("../projectUtils");
10
10
  const requirePermissions_1 = require("../requirePermissions");
11
+ const api_1 = require("../hosting/api");
11
12
  const error_1 = require("../error");
12
13
  const LOG_TAG = "hosting:sites";
13
14
  exports.command = new command_1.Command("hosting:sites:create [siteId]")
@@ -18,10 +19,10 @@ exports.command = new command_1.Command("hosting:sites:create [siteId]")
18
19
  const projectId = (0, projectUtils_1.needProjectId)(options);
19
20
  const appId = options.app;
20
21
  if (options.nonInteractive && !siteId) {
21
- throw new error_1.FirebaseError(`${(0, colorette_1.bold)(siteId)} is required in a non-interactive environment`);
22
+ throw new error_1.FirebaseError(`${(0, colorette_1.bold)("siteId")} is required in a non-interactive environment`);
22
23
  }
23
- const site = await (0, interactive_1.interactiveCreateHostingSite)(siteId, appId, options);
24
- siteId = (0, utils_1.last)(site.name.split("/"));
24
+ siteId = await (0, interactive_1.pickHostingSiteName)(siteId !== null && siteId !== void 0 ? siteId : "", options);
25
+ const site = await (0, api_1.createSite)(projectId, siteId, appId);
25
26
  logger_1.logger.info();
26
27
  (0, utils_1.logLabeledSuccess)(LOG_TAG, `Site ${(0, colorette_1.bold)(siteId)} has been created in project ${(0, colorette_1.bold)(projectId)}.`);
27
28
  if (appId) {
@@ -110,6 +110,7 @@ function load(client) {
110
110
  client.firestore.databases.update = loadCommand("firestore-databases-update");
111
111
  client.firestore.databases.delete = loadCommand("firestore-databases-delete");
112
112
  client.firestore.databases.restore = loadCommand("firestore-databases-restore");
113
+ client.firestore.databases.clone = loadCommand("firestore-databases-clone");
113
114
  client.firestore.backups = {};
114
115
  client.firestore.backups.schedules = {};
115
116
  client.firestore.backups.list = loadCommand("firestore-backups-list");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.initAction = exports.command = void 0;
3
+ exports.postInitSaves = exports.initAction = exports.command = void 0;
4
4
  const clc = require("colorette");
5
5
  const os = require("os");
6
6
  const path = require("path");
@@ -217,6 +217,16 @@ async function initAction(feature, options) {
217
217
  setup.features = setup.features.filter((f) => f !== "dataconnect:sdk");
218
218
  }
219
219
  await (0, init_1.init)(setup, config, options);
220
+ await postInitSaves(setup, config);
221
+ if (setup.instructions.length) {
222
+ logger_1.logger.info(`\n${clc.bold("To get started:")}\n`);
223
+ for (const i of setup.instructions) {
224
+ (0, utils_1.logBullet)(i + "\n");
225
+ }
226
+ }
227
+ }
228
+ exports.initAction = initAction;
229
+ async function postInitSaves(setup, config) {
220
230
  logger_1.logger.info();
221
231
  config.writeProjectFile("firebase.json", setup.config);
222
232
  config.writeProjectFile(".firebaserc", setup.rcfile);
@@ -225,11 +235,5 @@ async function initAction(feature, options) {
225
235
  }
226
236
  logger_1.logger.info();
227
237
  utils.logSuccess("Firebase initialization complete!");
228
- if (setup.instructions.length) {
229
- logger_1.logger.info(`\n${clc.bold("To get started:")}\n`);
230
- for (const i of setup.instructions) {
231
- (0, utils_1.logBullet)(i + "\n");
232
- }
233
- }
234
238
  }
235
- exports.initAction = initAction;
239
+ exports.postInitSaves = postInitSaves;
@@ -10,7 +10,6 @@ const projectUtils_1 = require("../projectUtils");
10
10
  const error_1 = require("../error");
11
11
  const ensureApiEnabled = require("../ensureApiEnabled");
12
12
  const api_1 = require("../api");
13
- const experiments = require("../experiments");
14
13
  const prepareFunctionsUpload_1 = require("../deploy/functions/prepareFunctionsUpload");
15
14
  exports.command = new command_1.Command("internaltesting:functions:discover")
16
15
  .description("discover function triggers defined in the current project directory")
@@ -22,8 +21,7 @@ exports.command = new command_1.Command("internaltesting:functions:discover")
22
21
  throw new error_1.FirebaseError("Admin SDK config unexpectedly undefined - have you run firebase init?");
23
22
  }
24
23
  let runtimeConfig = { firebase: firebaseConfig };
25
- const allowFunctionsConfig = experiments.isEnabled("dangerouslyAllowFunctionsConfig");
26
- if (allowFunctionsConfig) {
24
+ if (fnConfig.some(projectConfig_1.shouldUseRuntimeConfig)) {
27
25
  try {
28
26
  const runtimeConfigApiEnabled = await ensureApiEnabled.check(projectId, (0, api_1.runtimeconfigOrigin)(), "runtimeconfig", true);
29
27
  if (runtimeConfigApiEnabled) {
@@ -22,7 +22,7 @@ async function setupCloudSql(args) {
22
22
  }
23
23
  finally {
24
24
  if (!dryRun) {
25
- await (0, track_1.trackGA4)("dataconnect_cloud_sql", {
25
+ void (0, track_1.trackGA4)("dataconnect_cloud_sql", {
26
26
  source: args.source,
27
27
  action: success ? stats.action : `${stats.action}_error`,
28
28
  location: args.location,
@@ -44,7 +44,8 @@ async function upsertInstance(stats, args) {
44
44
  const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
45
45
  utils.logLabeledBullet("dataconnect", `Found existing Cloud SQL instance ${clc.bold(instanceId)}.`);
46
46
  stats.databaseVersion = existingInstance.databaseVersion;
47
- stats.dataconnectLabel = (_b = (_a = existingInstance.settings) === null || _a === void 0 ? void 0 : _a.userLabels) === null || _b === void 0 ? void 0 : _b["firebase-data-connect"];
47
+ stats.dataconnectLabel =
48
+ ((_b = (_a = existingInstance.settings) === null || _a === void 0 ? void 0 : _a.userLabels) === null || _b === void 0 ? void 0 : _b["firebase-data-connect"]) || "absent";
48
49
  const why = getUpdateReason(existingInstance, requireGoogleMlIntegration);
49
50
  if (why) {
50
51
  if (dryRun) {
@@ -18,6 +18,7 @@ const v2FunctionHelper_1 = require("./v2FunctionHelper");
18
18
  const tos_1 = require("../../extensions/tos");
19
19
  const common_1 = require("../../extensions/runtimes/common");
20
20
  const functionsDeployHelper_1 = require("../functions/functionsDeployHelper");
21
+ const projectConfig_1 = require("../../functions/projectConfig");
21
22
  const matchesInstanceId = (dep) => (test) => {
22
23
  return dep.instanceId === test.instanceId;
23
24
  };
@@ -117,7 +118,8 @@ async function prepareHelper(context, options, payload, wantExtensions, haveExte
117
118
  }
118
119
  }
119
120
  async function prepareDynamicExtensions(context, options, payload, builds) {
120
- const filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
121
+ const functionsConfig = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
122
+ const filters = (0, functionsDeployHelper_1.getEndpointFilters)(options, functionsConfig);
121
123
  const extensions = (0, common_1.extractExtensionsFromBuilds)(builds, filters);
122
124
  const projectId = (0, projectUtils_1.needProjectId)(options);
123
125
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
@@ -44,7 +44,7 @@ async function checkHttpIam(context, options, payload) {
44
44
  if (!payload.functions) {
45
45
  return;
46
46
  }
47
- const filters = context.filters || (0, functionsDeployHelper_1.getEndpointFilters)(options);
47
+ const filters = context.filters || (0, functionsDeployHelper_1.getEndpointFilters)(options, context.config);
48
48
  const wantBackends = Object.values(payload.functions).map(({ wantBackend }) => wantBackend);
49
49
  const httpEndpoints = [...(0, functional_1.flattenArray)(wantBackends.map((b) => backend.allEndpoints(b)))]
50
50
  .filter(backend.isHttpsTriggered)
@@ -32,13 +32,14 @@ function endpointMatchesFilter(endpoint, filter) {
32
32
  return true;
33
33
  }
34
34
  exports.endpointMatchesFilter = endpointMatchesFilter;
35
- function parseFunctionSelector(selector) {
35
+ function parseFunctionSelector(selector, config) {
36
36
  const fragments = selector.split(":");
37
37
  if (fragments.length < 2) {
38
- return [
39
- { codebase: fragments[0] },
40
- { codebase: projectConfig_1.DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) },
41
- ];
38
+ const codebaseNames = config.map((c) => c.codebase);
39
+ if (codebaseNames.includes(fragments[0])) {
40
+ return [{ codebase: fragments[0] }];
41
+ }
42
+ return [{ codebase: projectConfig_1.DEFAULT_CODEBASE, idChunks: fragments[0].split(/[-.]/) }];
42
43
  }
43
44
  return [
44
45
  {
@@ -48,7 +49,7 @@ function parseFunctionSelector(selector) {
48
49
  ];
49
50
  }
50
51
  exports.parseFunctionSelector = parseFunctionSelector;
51
- function getEndpointFilters(options) {
52
+ function getEndpointFilters(options, config) {
52
53
  if (!options.only) {
53
54
  return undefined;
54
55
  }
@@ -58,7 +59,7 @@ function getEndpointFilters(options) {
58
59
  if (selector.startsWith("functions:")) {
59
60
  selector = selector.replace("functions:", "");
60
61
  if (selector.length > 0) {
61
- filters.push(...parseFunctionSelector(selector));
62
+ filters.push(...parseFunctionSelector(selector, config));
62
63
  }
63
64
  }
64
65
  }
@@ -4,6 +4,7 @@ exports.resolveParams = exports.ParamValue = exports.isMultiSelectInput = export
4
4
  const logger_1 = require("../../logger");
5
5
  const error_1 = require("../../error");
6
6
  const prompt_1 = require("../../prompt");
7
+ const secrets_1 = require("../../functions/secrets");
7
8
  const functional_1 = require("../../functional");
8
9
  const secretManager = require("../../gcp/secretManager");
9
10
  const storage_1 = require("../../gcp/storage");
@@ -169,11 +170,11 @@ async function resolveParams(params, firebaseConfig, userEnvs, nonInteractive, i
169
170
  const [needSecret, needPrompt] = (0, functional_1.partition)(outstanding, (param) => param.type === "secret");
170
171
  if (!isEmulator) {
171
172
  for (const param of needSecret) {
172
- await handleSecret(param, firebaseConfig.projectId);
173
+ await handleSecret(param, firebaseConfig.projectId, nonInteractive);
173
174
  }
174
175
  }
175
176
  if (nonInteractive && needPrompt.length > 0) {
176
- const envNames = outstanding.map((p) => p.name).join(", ");
177
+ const envNames = needPrompt.map((p) => p.name).join(", ");
177
178
  throw new error_1.FirebaseError(`In non-interactive mode but have no value for the following environment variables: ${envNames}\n` +
178
179
  "To continue, either run `firebase deploy` with an interactive terminal, or add values to a dotenv file. " +
179
180
  "For information regarding how to use dotenv files, see https://firebase.google.com/docs/functions/config-env");
@@ -220,15 +221,24 @@ function populateDefaultParams(config) {
220
221
  }
221
222
  return defaultParams;
222
223
  }
223
- async function handleSecret(secretParam, projectId) {
224
+ async function handleSecret(secretParam, projectId, nonInteractive) {
224
225
  const metadata = await secretManager.getSecretMetadata(projectId, secretParam.name, "latest");
225
226
  if (!metadata.secret) {
227
+ if (nonInteractive) {
228
+ throw new error_1.FirebaseError(`In non-interactive mode but have no value for the secret: ${secretParam.name}\n\n` +
229
+ "Set this secret before deploying:\n" +
230
+ `\tfirebase functions:secrets:set ${secretParam.name}${secretParam.format === "json" ? " --format=json --data-file <file.json>" : ""}`);
231
+ }
232
+ const promptMessage = `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${secretParam.name}. Enter ${secretParam.format === "json" ? "a JSON value" : "a value"} for ${secretParam.label || secretParam.name}:`;
226
233
  const secretValue = await (0, prompt_1.password)({
227
- message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${secretParam.name}. Enter a value for ${secretParam.label || secretParam.name}:`,
234
+ message: promptMessage,
228
235
  });
236
+ if (secretParam.format === "json") {
237
+ (0, secrets_1.validateJsonSecret)(secretParam.name, secretValue);
238
+ }
229
239
  await secretManager.createSecret(projectId, secretParam.name, (0, secretManager_1.labels)());
230
240
  await secretManager.addVersion(projectId, secretParam.name, secretValue);
231
- return secretValue;
241
+ return;
232
242
  }
233
243
  else if (!metadata.secretVersion) {
234
244
  throw new error_1.FirebaseError(`Cloud Secret Manager has no latest version of the secret defined by param ${secretParam.label || secretParam.name}`);
@@ -12,7 +12,6 @@ const runtimes = require("./runtimes");
12
12
  const supported = require("./runtimes/supported");
13
13
  const validate = require("./validate");
14
14
  const ensure = require("./ensure");
15
- const experiments = require("../../experiments");
16
15
  const api_1 = require("../../api");
17
16
  const functionsDeployHelper_1 = require("./functionsDeployHelper");
18
17
  const utils_1 = require("../../utils");
@@ -37,7 +36,7 @@ async function prepare(context, options, payload) {
37
36
  const projectId = (0, projectUtils_1.needProjectId)(options);
38
37
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
39
38
  context.config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
40
- context.filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
39
+ context.filters = (0, functionsDeployHelper_1.getEndpointFilters)(options, context.config);
41
40
  const codebases = (0, functionsDeployHelper_1.targetCodebases)(context.config, context.filters);
42
41
  if (codebases.length === 0) {
43
42
  throw new error_1.FirebaseError("No function matches given --only filters. Aborting deployment.");
@@ -55,8 +54,8 @@ async function prepare(context, options, payload) {
55
54
  context.firebaseConfig = firebaseConfig;
56
55
  context.codebaseDeployEvents = {};
57
56
  let runtimeConfig = { firebase: firebaseConfig };
58
- const allowFunctionsConfig = experiments.isEnabled("dangerouslyAllowFunctionsConfig");
59
- if (allowFunctionsConfig && checkAPIsEnabled[1]) {
57
+ const targetedCodebaseConfigs = context.config.filter((cfg) => codebases.includes(cfg.codebase));
58
+ if (checkAPIsEnabled[1] && targetedCodebaseConfigs.some(projectConfig_1.shouldUseRuntimeConfig)) {
60
59
  runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), (await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)));
61
60
  }
62
61
  context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase");
@@ -157,7 +156,8 @@ async function prepare(context, options, payload) {
157
156
  source.functionsSourceV2Hash = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.hash;
158
157
  }
159
158
  if (backend.someEndpoint(wantBackend, (e) => e.platform === "gcfv1")) {
160
- const packagedSource = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, localCfg, runtimeConfig);
159
+ const configForUpload = (0, projectConfig_1.shouldUseRuntimeConfig)(localCfg) ? runtimeConfig : undefined;
160
+ const packagedSource = await (0, prepareFunctionsUpload_1.prepareFunctionsUpload)(sourceDir, localCfg, configForUpload);
161
161
  source.functionsSourceV1 = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.pathToSource;
162
162
  source.functionsSourceV1Hash = packagedSource === null || packagedSource === void 0 ? void 0 : packagedSource.hash;
163
163
  }
@@ -314,7 +314,10 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
314
314
  await runtimeDelegate.build();
315
315
  const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
316
316
  (0, utils_1.logLabeledBullet)("functions", `Loading and analyzing source code for codebase ${codebase} to determine what to deploy`);
317
- const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
317
+ const codebaseRuntimeConfig = (0, projectConfig_1.shouldUseRuntimeConfig)(codebaseConfig)
318
+ ? runtimeConfig
319
+ : { firebase: firebaseConfig };
320
+ const discoveredBuild = await runtimeDelegate.discoverBuild(codebaseRuntimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
318
321
  discoveredBuild.runtime = codebaseConfig.runtime;
319
322
  build.applyPrefix(discoveredBuild, codebaseConfig.prefix || "");
320
323
  wantBuilds[codebase] = discoveredBuild;
@@ -19,7 +19,7 @@ function detectProjectRoot(options) {
19
19
  while (!(0, fsutils_1.fileExistsSync)((0, path_1.resolve)(projectRootDir, "./firebase.json"))) {
20
20
  const parentDir = (0, path_1.dirname)(projectRootDir);
21
21
  if (parentDir === projectRootDir) {
22
- return null;
22
+ return undefined;
23
23
  }
24
24
  projectRootDir = parentDir;
25
25
  }