firebase-tools 9.17.0 → 9.21.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 (95) hide show
  1. package/CHANGELOG.md +3 -7
  2. package/lib/api.js +1 -0
  3. package/lib/apiv2.js +5 -3
  4. package/lib/appdistribution/client.js +84 -72
  5. package/lib/appdistribution/distribution.js +8 -26
  6. package/lib/appdistribution/options-parser-util.js +51 -0
  7. package/lib/command.js +1 -1
  8. package/lib/commands/appdistribution-distribute.js +74 -91
  9. package/lib/commands/appdistribution-testers-add.js +18 -0
  10. package/lib/commands/appdistribution-testers-remove.js +32 -0
  11. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  12. package/lib/commands/ext-configure.js +9 -1
  13. package/lib/commands/ext-dev-extension-delete.js +2 -1
  14. package/lib/commands/ext-dev-publish.js +11 -4
  15. package/lib/commands/ext-dev-unpublish.js +12 -4
  16. package/lib/commands/ext-install.js +115 -48
  17. package/lib/commands/ext-uninstall.js +6 -0
  18. package/lib/commands/ext-update.js +61 -18
  19. package/lib/commands/functions-config-export.js +115 -0
  20. package/lib/commands/functions-delete.js +45 -25
  21. package/lib/commands/functions-list.js +54 -0
  22. package/lib/commands/functions-log.js +5 -22
  23. package/lib/commands/hosting-channel-deploy.js +6 -4
  24. package/lib/commands/index.js +12 -0
  25. package/lib/deploy/functions/backend.js +118 -121
  26. package/lib/deploy/functions/checkIam.js +8 -8
  27. package/lib/deploy/functions/containerCleaner.js +5 -1
  28. package/lib/deploy/functions/deploy.js +11 -15
  29. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  30. package/lib/deploy/functions/prepare.js +67 -33
  31. package/lib/deploy/functions/pricing.js +17 -17
  32. package/lib/deploy/functions/prompts.js +24 -41
  33. package/lib/deploy/functions/release/executor.js +39 -0
  34. package/lib/deploy/functions/release/fabricator.js +362 -0
  35. package/lib/deploy/functions/release/index.js +69 -0
  36. package/lib/deploy/functions/release/planner.js +159 -0
  37. package/lib/deploy/functions/release/reporter.js +162 -0
  38. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  39. package/lib/deploy/functions/release/timer.js +14 -0
  40. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +102 -126
  41. package/lib/deploy/functions/runtimes/node/parseTriggers.js +34 -50
  42. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  43. package/lib/deploy/functions/validate.js +1 -24
  44. package/lib/downloadUtils.js +37 -0
  45. package/lib/emulator/auth/apiSpec.js +1788 -403
  46. package/lib/emulator/auth/handlers.js +6 -5
  47. package/lib/emulator/auth/operations.js +439 -40
  48. package/lib/emulator/auth/server.js +32 -11
  49. package/lib/emulator/auth/state.js +205 -5
  50. package/lib/emulator/auth/widget_ui.js +2 -2
  51. package/lib/emulator/download.js +2 -31
  52. package/lib/emulator/downloadableEmulators.js +7 -7
  53. package/lib/emulator/emulatorLogger.js +0 -3
  54. package/lib/emulator/events/types.js +16 -0
  55. package/lib/emulator/functionsEmulator.js +117 -20
  56. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  57. package/lib/emulator/functionsEmulatorShared.js +51 -7
  58. package/lib/emulator/functionsEmulatorShell.js +1 -1
  59. package/lib/emulator/pubsubEmulator.js +61 -40
  60. package/lib/emulator/storage/cloudFunctions.js +37 -7
  61. package/lib/extensions/askUserForConsent.js +16 -13
  62. package/lib/extensions/askUserForParam.js +72 -3
  63. package/lib/extensions/billingMigrationHelper.js +1 -11
  64. package/lib/extensions/changelog.js +2 -1
  65. package/lib/extensions/displayExtensionInfo.js +35 -33
  66. package/lib/extensions/emulator/optionsHelper.js +3 -3
  67. package/lib/extensions/emulator/triggerHelper.js +2 -32
  68. package/lib/extensions/extensionsApi.js +67 -94
  69. package/lib/extensions/extensionsHelper.js +49 -35
  70. package/lib/extensions/paramHelper.js +79 -36
  71. package/lib/extensions/refs.js +59 -0
  72. package/lib/extensions/secretsUtils.js +58 -0
  73. package/lib/extensions/updateHelper.js +21 -45
  74. package/lib/extensions/warnings.js +1 -7
  75. package/lib/functional.js +64 -0
  76. package/lib/functions/env.js +26 -13
  77. package/lib/functions/functionslog.js +40 -0
  78. package/lib/functions/runtimeConfigExport.js +137 -0
  79. package/lib/gcp/cloudfunctions.js +46 -38
  80. package/lib/gcp/cloudfunctionsv2.js +47 -47
  81. package/lib/gcp/cloudlogging.js +27 -21
  82. package/lib/gcp/cloudscheduler.js +22 -16
  83. package/lib/gcp/pubsub.js +1 -9
  84. package/lib/gcp/secretManager.js +111 -0
  85. package/lib/gcp/storage.js +16 -0
  86. package/lib/previews.js +1 -1
  87. package/lib/requireInteractive.js +12 -0
  88. package/lib/utils.js +30 -1
  89. package/package.json +5 -4
  90. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  91. package/lib/deploy/functions/deploymentTimer.js +0 -23
  92. package/lib/deploy/functions/errorHandler.js +0 -75
  93. package/lib/deploy/functions/release.js +0 -116
  94. package/lib/deploy/functions/tasks.js +0 -324
  95. package/lib/functionsDelete.js +0 -60
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs = require("fs-extra");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const spawn = require("cross-spawn");
7
+ const uuid = require("uuid");
8
+ const command_1 = require("../command");
9
+ const downloadUtils = require("../downloadUtils");
10
+ const error_1 = require("../error");
11
+ const logger_1 = require("../logger");
12
+ const rimraf = require("rimraf");
13
+ const utils = require("../utils");
14
+ var SymbolGenerator;
15
+ (function (SymbolGenerator) {
16
+ SymbolGenerator["breakpad"] = "breakpad";
17
+ SymbolGenerator["csym"] = "csym";
18
+ })(SymbolGenerator || (SymbolGenerator = {}));
19
+ const SYMBOL_CACHE_ROOT_DIR = process.env.FIREBASE_CRASHLYTICS_CACHE_PATH || os.tmpdir();
20
+ const JAR_CACHE_DIR = process.env.FIREBASE_CRASHLYTICS_BUILDTOOLS_PATH ||
21
+ path.join(os.homedir(), ".cache", "firebase", "crashlytics", "buildtools");
22
+ const JAR_VERSION = "2.8.0";
23
+ const JAR_URL = `https://storage.googleapis.com/firebase-preview-drop/android/crashlytics-eap/crashlytics-buildtools/firebase-crashlytics-buildtools-${JAR_VERSION}-alpha-all.jar`;
24
+ exports.default = new command_1.Command("crashlytics:symbols:upload <symbolFiles...>")
25
+ .description("Upload symbols for native code, to symbolicate stack traces.")
26
+ .option("--app <appID>", "the app id of your Firebase app")
27
+ .option("--generator [breakpad|csym]", "the symbol generator being used, defaults to breakpad.")
28
+ .option("--dry-run", "generate symbols without uploading them")
29
+ .option("--debug", "print debug output and logging from the underlying uploader tool")
30
+ .action(async (symbolFiles, options) => {
31
+ const app = getGoogleAppID(options) || "";
32
+ const generator = getSymbolGenerator(options);
33
+ const dryRun = !!options.dryRun;
34
+ const debug = !!options.debug;
35
+ let jarFile = await downloadBuiltoolsJar();
36
+ if (process.env.LOCAL_JAR) {
37
+ jarFile = process.env.LOCAL_JAR;
38
+ }
39
+ const jarOptions = {
40
+ jarFile,
41
+ app,
42
+ generator,
43
+ cachePath: path.join(SYMBOL_CACHE_ROOT_DIR, `crashlytics-${uuid.v4()}`, "nativeSymbols", app.replace(/:/g, "-"), generator),
44
+ symbolFile: "",
45
+ generate: true,
46
+ };
47
+ for (const symbolFile of symbolFiles) {
48
+ utils.logBullet(`Generating symbols for ${symbolFile}`);
49
+ const generateArgs = buildArgs(Object.assign(Object.assign({}, jarOptions), { symbolFile }));
50
+ const output = runJar(generateArgs, debug);
51
+ if (output.length > 0) {
52
+ utils.logBullet(output);
53
+ }
54
+ else {
55
+ utils.logBullet(`Generated symbols for ${symbolFile}`);
56
+ utils.logBullet(`Output Path: ${jarOptions.cachePath}`);
57
+ }
58
+ }
59
+ if (dryRun) {
60
+ utils.logBullet("Skipping upload because --dry-run was passed");
61
+ return;
62
+ }
63
+ utils.logBullet(`Uploading all generated symbols`);
64
+ const uploadArgs = buildArgs(Object.assign(Object.assign({}, jarOptions), { generate: false }));
65
+ const output = runJar(uploadArgs, debug);
66
+ if (output.length > 0) {
67
+ utils.logBullet(output);
68
+ }
69
+ else {
70
+ utils.logBullet("Successfully uploaded all symbols");
71
+ }
72
+ });
73
+ function getGoogleAppID(options) {
74
+ if (!options.app) {
75
+ throw new error_1.FirebaseError("set the --app option to a valid Firebase app id and try again");
76
+ }
77
+ return options.app;
78
+ }
79
+ function getSymbolGenerator(options) {
80
+ if (!options.generator) {
81
+ return SymbolGenerator.breakpad;
82
+ }
83
+ if (!Object.values(SymbolGenerator).includes(options.generator)) {
84
+ throw new error_1.FirebaseError('--symbol-generator should be set to either "breakpad" or "csym"');
85
+ }
86
+ return options.generator;
87
+ }
88
+ async function downloadBuiltoolsJar() {
89
+ const jarPath = path.join(JAR_CACHE_DIR, `crashlytics-buildtools-${JAR_VERSION}.jar`);
90
+ if (fs.existsSync(jarPath)) {
91
+ logger_1.logger.debug(`Buildtools Jar already downloaded at ${jarPath}`);
92
+ return jarPath;
93
+ }
94
+ if (fs.existsSync(JAR_CACHE_DIR)) {
95
+ logger_1.logger.debug(`Deleting Jar cache at ${JAR_CACHE_DIR} because the CLI was run with a newer Jar version`);
96
+ rimraf.sync(JAR_CACHE_DIR);
97
+ }
98
+ utils.logBullet("Downloading buildtools.jar to " + jarPath);
99
+ utils.logBullet("For open source licenses used by this command, look in the META-INF directory in the buildtools.jar file");
100
+ const tmpfile = await downloadUtils.downloadToTmp(JAR_URL);
101
+ fs.mkdirSync(JAR_CACHE_DIR, { recursive: true });
102
+ fs.copySync(tmpfile, jarPath);
103
+ return jarPath;
104
+ }
105
+ function buildArgs(options) {
106
+ const baseArgs = [
107
+ "-jar",
108
+ options.jarFile,
109
+ `-symbolGenerator=${options.generator}`,
110
+ `-symbolFileCacheDir=${options.cachePath}`,
111
+ "-verbose",
112
+ ];
113
+ if (options.generate) {
114
+ return baseArgs.concat(["-generateNativeSymbols", `-unstrippedLibrary=${options.symbolFile}`]);
115
+ }
116
+ return baseArgs.concat([
117
+ "-uploadNativeSymbols",
118
+ `-googleAppId=${options.app}`,
119
+ ]);
120
+ }
121
+ function runJar(args, debug) {
122
+ var _a, _b, _c;
123
+ const outputs = spawn.sync("java", args, {
124
+ stdio: debug ? "inherit" : "pipe",
125
+ });
126
+ if (outputs.status || 0 > 0) {
127
+ if (!debug) {
128
+ utils.logWarning(((_a = outputs.stdout) === null || _a === void 0 ? void 0 : _a.toString()) || "An unknown error occurred");
129
+ }
130
+ throw new error_1.FirebaseError("Failed to upload symbols");
131
+ }
132
+ if (!debug) {
133
+ let logRegex = /(Generated symbol file.*$)/m;
134
+ let matched = (((_b = outputs.stdout) === null || _b === void 0 ? void 0 : _b.toString()) || "").match(logRegex);
135
+ if (matched) {
136
+ return matched[1];
137
+ }
138
+ logRegex = /(Crashlytics symbol file uploaded successfully.*$)/m;
139
+ matched = (((_c = outputs.stdout) === null || _c === void 0 ? void 0 : _c.toString()) || "").match(logRegex);
140
+ if (matched) {
141
+ return matched[1];
142
+ }
143
+ return "";
144
+ }
145
+ return "";
146
+ }
@@ -20,6 +20,7 @@ marked.setOptions({
20
20
  });
21
21
  exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
22
22
  .description("configure an existing extension instance")
23
+ .withForce()
23
24
  .option("--params <paramsFile>", "path of params file with .env format.")
24
25
  .before(requirePermissions_1.requirePermissions, [
25
26
  "firebaseextensions.instances.update",
@@ -46,7 +47,14 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
46
47
  const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => {
47
48
  return param.immutable || param.param === "LOCATION";
48
49
  });
49
- const params = await paramHelper.getParams(projectId, paramSpecWithNewDefaults, options.params);
50
+ const params = await paramHelper.getParams({
51
+ projectId,
52
+ paramSpecs: paramSpecWithNewDefaults,
53
+ nonInteractive: options.nonInteractive,
54
+ paramsEnvPath: options.params,
55
+ instanceId,
56
+ reconfiguring: true,
57
+ });
50
58
  if (immutableParams.length) {
51
59
  const plural = immutableParams.length > 1;
52
60
  logger_1.logger.info(`The following param${plural ? "s are" : " is"} immutable:`);
@@ -5,6 +5,7 @@ const clc = require("cli-color");
5
5
  const command_1 = require("../command");
6
6
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
7
7
  const extensionsApi_1 = require("../extensions/extensionsApi");
8
+ const refs = require("../extensions/refs");
8
9
  const prompt_1 = require("../prompt");
9
10
  const requireAuth_1 = require("../requireAuth");
10
11
  const error_1 = require("../error");
@@ -16,7 +17,7 @@ module.exports = new command_1.Command("ext:dev:delete <extensionRef>")
16
17
  .before(requireAuth_1.requireAuth)
17
18
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
18
19
  .action(async (extensionRef) => {
19
- const { publisherId, extensionId, version } = extensionsApi_1.parseRef(extensionRef);
20
+ const { publisherId, extensionId, version } = refs.parse(extensionRef);
20
21
  if (version) {
21
22
  throw new error_1.FirebaseError(`Deleting a single version is not currently supported. You can only delete ${clc.bold("ALL versions")} of an extension. To delete all versions, please remove the version from the reference.`);
22
23
  }
@@ -5,7 +5,7 @@ const marked = require("marked");
5
5
  const TerminalRenderer = require("marked-terminal");
6
6
  const command_1 = require("../command");
7
7
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
8
- const extensionsApi_1 = require("../extensions/extensionsApi");
8
+ const refs = require("../extensions/refs");
9
9
  const localHelper_1 = require("../extensions/localHelper");
10
10
  const publishHelpers_1 = require("../extensions/publishHelpers");
11
11
  const requireAuth_1 = require("../requireAuth");
@@ -16,12 +16,13 @@ marked.setOptions({
16
16
  });
17
17
  exports.default = new command_1.Command("ext:dev:publish <extensionRef>")
18
18
  .description(`publish a new version of an extension`)
19
+ .withForce()
19
20
  .help("if you have not previously published a version of this extension, this will " +
20
21
  "create the extension. If you have previously published a version of this extension, this version must " +
21
22
  "be greater than previous versions.")
22
23
  .before(requireAuth_1.requireAuth)
23
- .action(async (extensionRef) => {
24
- const { publisherId, extensionId, version } = extensionsApi_1.parseRef(extensionRef);
24
+ .action(async (extensionRef, options) => {
25
+ const { publisherId, extensionId, version } = refs.parse(extensionRef);
25
26
  if (version) {
26
27
  throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should not be supplied and will be inferred directly from extension.yaml. Please increment the version in extension.yaml if you would like to bump/specify a version.`);
27
28
  }
@@ -29,7 +30,13 @@ exports.default = new command_1.Command("ext:dev:publish <extensionRef>")
29
30
  throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
30
31
  }
31
32
  const extensionYamlDirectory = localHelper_1.findExtensionYaml(process.cwd());
32
- const res = await extensionsHelper_1.publishExtensionVersionFromLocalSource(publisherId, extensionId, extensionYamlDirectory);
33
+ const res = await extensionsHelper_1.publishExtensionVersionFromLocalSource({
34
+ publisherId,
35
+ extensionId,
36
+ rootDirectory: extensionYamlDirectory,
37
+ nonInteractive: options.nonInteractive,
38
+ force: options.force,
39
+ });
33
40
  if (res) {
34
41
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`[Install Link](${publishHelpers_1.consoleInstallLink(res.ref)})`));
35
42
  }
@@ -4,6 +4,7 @@ const command_1 = require("../command");
4
4
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
5
5
  const extensionsApi_1 = require("../extensions/extensionsApi");
6
6
  const utils = require("../utils");
7
+ const refs = require("../extensions/refs");
7
8
  const prompt_1 = require("../prompt");
8
9
  const clc = require("cli-color");
9
10
  const requireAuth_1 = require("../requireAuth");
@@ -11,26 +12,33 @@ const error_1 = require("../error");
11
12
  const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
12
13
  module.exports = new command_1.Command("ext:dev:unpublish <extensionRef>")
13
14
  .description("unpublish an extension")
15
+ .withForce()
14
16
  .help("use this command to unpublish an extension, and make it unavailable for developers to install or reconfigure. " +
15
17
  "Specify the extension you want to unpublish using the format '<publisherId>/<extensionId>.")
16
18
  .before(requireAuth_1.requireAuth)
17
19
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
18
- .action(async (extensionRef) => {
19
- const { publisherId, extensionId, version } = extensionsApi_1.parseRef(extensionRef);
20
+ .action(async (extensionRef, options) => {
21
+ const { publisherId, extensionId, version } = refs.parse(extensionRef);
20
22
  utils.logLabeledWarning(extensionsHelper_1.logPrefix, "If you unpublish this extension, developers won't be able to install it. For developers who currently have this extension installed, it will continue to run and will appear as unpublished when listed in the Firebase console or Firebase CLI.");
21
23
  utils.logLabeledWarning("This is a permanent action", `Once unpublished, you may never use the extension name '${clc.bold(extensionId)}' again.`);
22
24
  if (version) {
23
25
  throw new error_1.FirebaseError(`Unpublishing a single version is not currently supported. You can only unpublish ${clc.bold("ALL versions")} of an extension. To unpublish all versions, please remove the version from the reference.`);
24
26
  }
25
27
  await extensionsApi_1.getExtension(extensionRef);
26
- const consent = await comfirmUnpublish(publisherId, extensionId);
28
+ const consent = await comfirmUnpublish(publisherId, extensionId, options);
27
29
  if (!consent) {
28
30
  throw new error_1.FirebaseError("unpublishing cancelled.");
29
31
  }
30
32
  await extensionsApi_1.unpublishExtension(extensionRef);
31
33
  utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully unpublished all versions of this extension.");
32
34
  });
33
- async function comfirmUnpublish(publisherId, extensionId) {
35
+ async function comfirmUnpublish(publisherId, extensionId, options) {
36
+ if (options.nonInteractive && !options.force) {
37
+ throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
38
+ }
39
+ if (options.nonInteractive && options.force) {
40
+ return true;
41
+ }
34
42
  const message = `You are about to unpublish ALL versions of ${clc.green(`${publisherId}/${extensionId}`)}.\nDo you wish to continue? `;
35
43
  return prompt_1.promptOnce({
36
44
  type: "confirm",
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const _ = require("lodash");
4
3
  const clc = require("cli-color");
5
4
  const marked = require("marked");
6
5
  const ora = require("ora");
@@ -15,10 +14,13 @@ const command_1 = require("../command");
15
14
  const error_1 = require("../error");
16
15
  const projectUtils_1 = require("../projectUtils");
17
16
  const extensionsApi = require("../extensions/extensionsApi");
17
+ const secretsUtils = require("../extensions/secretsUtils");
18
18
  const provisioningHelper = require("../extensions/provisioningHelper");
19
+ const refs = require("../extensions/refs");
19
20
  const warnings_1 = require("../extensions/warnings");
20
21
  const paramHelper = require("../extensions/paramHelper");
21
22
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
23
+ const updateHelper_1 = require("../extensions/updateHelper");
22
24
  const utils_1 = require("../extensions/utils");
23
25
  const requirePermissions_1 = require("../requirePermissions");
24
26
  const utils = require("../utils");
@@ -28,50 +30,120 @@ marked.setOptions({
28
30
  renderer: new TerminalRenderer(),
29
31
  });
30
32
  async function installExtension(options) {
31
- const { projectId, extensionName, source, extVersion, paramFilePath } = options;
33
+ const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force, } = options;
32
34
  const spec = (source === null || source === void 0 ? void 0 : source.spec) || (extVersion === null || extVersion === void 0 ? void 0 : extVersion.spec);
33
35
  if (!spec) {
34
36
  throw new error_1.FirebaseError(`Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.`);
35
37
  }
36
- const spinner = ora.default("Installing your extension instance. This usually takes 3 to 5 minutes...");
38
+ const spinner = ora.default();
37
39
  try {
38
40
  await provisioningHelper.checkProductsProvisioned(projectId, spec);
39
- if (spec.billingRequired) {
41
+ const usesSecrets = secretsUtils.usesSecrets(spec);
42
+ if (spec.billingRequired || usesSecrets) {
40
43
  const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
41
- if (!enabled) {
44
+ if (!enabled && nonInteractive) {
45
+ throw new error_1.FirebaseError(`This extension requires the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
46
+ marked("Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project."));
47
+ }
48
+ else if (!enabled) {
42
49
  await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, false);
43
50
  await checkProjectBilling_1.enableBilling(projectId, spec.displayName || spec.name);
44
51
  }
45
52
  else {
46
- await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, true);
53
+ await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, !nonInteractive);
54
+ }
55
+ }
56
+ const apis = spec.apis || [];
57
+ if (usesSecrets) {
58
+ apis.push({
59
+ apiName: "secretmanager.googleapis.com",
60
+ reason: `To access and manage secrets which are used by this extension. By using this product you agree to the terms and conditions of the following license: https://console.cloud.google.com/tos?id=cloud&project=${projectId}`,
61
+ });
62
+ }
63
+ if (apis.length) {
64
+ askUserForConsent.displayApis(spec.displayName || spec.name, projectId, apis);
65
+ const consented = await extensionsHelper_1.confirm({ nonInteractive, force, default: true });
66
+ if (!consented) {
67
+ throw new error_1.FirebaseError("Without explicit consent for the APIs listed, we cannot deploy this extension.");
47
68
  }
48
69
  }
70
+ if (usesSecrets) {
71
+ await secretsUtils.ensureSecretManagerApiEnabled(options);
72
+ }
49
73
  const roles = spec.roles ? spec.roles.map((role) => role.role) : [];
50
- await askUserForConsent.prompt(spec.displayName || spec.name, projectId, roles);
74
+ if (roles.length) {
75
+ await askUserForConsent.displayRoles(spec.displayName || spec.name, projectId, roles);
76
+ const consented = await extensionsHelper_1.confirm({ nonInteractive, force, default: true });
77
+ if (!consented) {
78
+ throw new error_1.FirebaseError("Without explicit consent for the roles listed, we cannot deploy this extension.");
79
+ }
80
+ }
51
81
  let instanceId = spec.name;
82
+ let choice;
52
83
  const anotherInstanceExists = await extensionsHelper_1.instanceIdExists(projectId, instanceId);
53
84
  if (anotherInstanceExists) {
54
- const consent = await extensionsHelper_1.promptForRepeatInstance(projectId, spec.name);
55
- if (!consent) {
56
- logger_1.logger.info(marked("Installation cancelled. For a list of all available Firebase Extensions commands, run `firebase ext`."));
57
- return;
85
+ if (!nonInteractive) {
86
+ choice = await extensionsHelper_1.promptForRepeatInstance(projectId, spec.name);
87
+ }
88
+ else if (nonInteractive && force) {
89
+ choice = "updateExisting";
90
+ }
91
+ else {
92
+ throw new error_1.FirebaseError(`An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectId)}'.` +
93
+ ` To update or reconfigure this instance instead, rerun this command with the --force flag.`);
58
94
  }
59
- instanceId = await extensionsHelper_1.promptForValidInstanceId(`${instanceId}-${utils_1.getRandomString(4)}`);
60
- }
61
- const params = await paramHelper.getParams(projectId, _.get(spec, "params", []), paramFilePath);
62
- spinner.start();
63
- if (!source && extVersion) {
64
- await extensionsApi.createInstanceFromExtensionVersion(projectId, instanceId, extVersion, params);
65
- }
66
- else if (source) {
67
- await extensionsApi.createInstanceFromSource(projectId, instanceId, source, params);
68
95
  }
69
96
  else {
70
- throw new error_1.FirebaseError(`Neither a extension source nor an extension version was supplied for ${extensionName}. Please make sure this is a valid extension and try again.`);
97
+ choice = "installNew";
98
+ }
99
+ let params;
100
+ switch (choice) {
101
+ case "installNew":
102
+ instanceId = await extensionsHelper_1.promptForValidInstanceId(`${instanceId}-${utils_1.getRandomString(4)}`);
103
+ params = await paramHelper.getParams({
104
+ projectId,
105
+ paramSpecs: spec.params,
106
+ nonInteractive,
107
+ paramsEnvPath,
108
+ instanceId,
109
+ });
110
+ spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes...";
111
+ spinner.start();
112
+ await extensionsApi.createInstance({
113
+ projectId,
114
+ instanceId,
115
+ extensionSource: source,
116
+ extensionVersionRef: extVersion === null || extVersion === void 0 ? void 0 : extVersion.ref,
117
+ params,
118
+ });
119
+ spinner.stop();
120
+ utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
121
+ `Its Instance ID is ${clc.bold(instanceId)}.`);
122
+ break;
123
+ case "updateExisting":
124
+ params = await paramHelper.getParams({
125
+ projectId,
126
+ paramSpecs: spec.params,
127
+ nonInteractive,
128
+ paramsEnvPath,
129
+ instanceId,
130
+ });
131
+ spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes...";
132
+ spinner.start();
133
+ await updateHelper_1.update({
134
+ projectId,
135
+ instanceId,
136
+ source,
137
+ extRef: extVersion === null || extVersion === void 0 ? void 0 : extVersion.ref,
138
+ params,
139
+ });
140
+ spinner.stop();
141
+ utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully updated your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
142
+ `Its Instance ID is ${clc.bold(instanceId)}.`);
143
+ break;
144
+ case "cancel":
145
+ return;
71
146
  }
72
- spinner.stop();
73
- utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
74
- `Its Instance ID is ${clc.bold(instanceId)}.`);
75
147
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked("Go to the Firebase console to view instructions for using your extension, " +
76
148
  `which may include some required post-installation tasks: ${utils.consoleUrl(projectId, `/extensions/instances/${instanceId}?tab=usage`)}`));
77
149
  logger_1.logger.info(marked("You can run `firebase ext` to view available Firebase Extensions commands, " +
@@ -89,7 +161,7 @@ async function installExtension(options) {
89
161
  });
90
162
  }
91
163
  }
92
- async function confirmInstallBySource(projectId, extensionName) {
164
+ async function infoInstallBySource(projectId, extensionName) {
93
165
  let source;
94
166
  try {
95
167
  source = await extensionsHelper_1.createSourceFromLocation(projectId, extensionName);
@@ -99,32 +171,21 @@ async function confirmInstallBySource(projectId, extensionName) {
99
171
  `and encountered the following error when trying to create an instance of extension '${clc.bold(extensionName)}':\n ${err.message}`);
100
172
  }
101
173
  displayExtensionInfo_1.displayExtInfo(extensionName, "", source.spec);
102
- const confirm = await extensionsHelper_1.confirmInstallInstance();
103
- if (!confirm) {
104
- throw new error_1.FirebaseError("Install cancelled.");
105
- }
106
174
  return source;
107
175
  }
108
- async function confirmInstallByReference(extensionName) {
176
+ async function infoInstallByReference(extensionName) {
109
177
  if (extensionName.split("/").length < 2) {
110
178
  const [extensionID, version] = extensionName.split("@");
111
179
  extensionName = `firebase/${extensionID}@${version || "latest"}`;
112
180
  }
113
- const ref = extensionsApi.parseRef(extensionName);
114
- const extension = await extensionsApi.getExtension(`${ref.publisherId}/${ref.extensionId}`);
181
+ const ref = refs.parse(extensionName);
182
+ const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref));
115
183
  if (!ref.version) {
116
184
  extensionName = `${extensionName}@latest`;
117
185
  }
118
186
  const extVersion = await extensionsApi.getExtensionVersion(extensionName);
119
187
  displayExtensionInfo_1.displayExtInfo(extensionName, ref.publisherId, extVersion.spec, true);
120
- const confirm = await extensionsHelper_1.confirmInstallInstance();
121
- if (!confirm) {
122
- throw new error_1.FirebaseError("Install cancelled.");
123
- }
124
- const warningConsent = await warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
125
- if (!warningConsent) {
126
- throw new error_1.FirebaseError("Install cancelled.");
127
- }
188
+ await warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
128
189
  return extVersion;
129
190
  }
130
191
  exports.default = new command_1.Command("ext:install [extensionName]")
@@ -133,13 +194,14 @@ exports.default = new command_1.Command("ext:install [extensionName]")
133
194
  ? "install a local extension if [localPathOrUrl] or [url#root] is provided; install a published extension (not authored by Firebase) if [publisherId/extensionId] is provided "
134
195
  : "") +
135
196
  "or run with `-i` to see all available extensions.")
197
+ .withForce()
136
198
  .option("--params <paramsFile>", "name of params variables file with .env format.")
137
199
  .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.create"])
138
200
  .before(extensionsHelper_1.ensureExtensionsApiEnabled)
139
201
  .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
140
202
  .action(async (extensionName, options) => {
141
203
  const projectId = projectUtils_1.needProjectId(options);
142
- const paramFilePath = options.params;
204
+ const paramsEnvPath = options.params;
143
205
  let learnMore = false;
144
206
  if (!extensionName) {
145
207
  if (options.interactive) {
@@ -155,10 +217,17 @@ exports.default = new command_1.Command("ext:install [extensionName]")
155
217
  let source;
156
218
  let extVersion;
157
219
  if (extensionsHelper_1.isLocalOrURLPath(extensionName)) {
158
- source = await confirmInstallBySource(projectId, extensionName);
220
+ source = await infoInstallBySource(projectId, extensionName);
159
221
  }
160
222
  else {
161
- extVersion = await confirmInstallByReference(extensionName);
223
+ extVersion = await infoInstallByReference(extensionName);
224
+ }
225
+ if (!(await extensionsHelper_1.confirm({
226
+ nonInteractive: options.nonInteractive,
227
+ force: options.force,
228
+ default: true,
229
+ }))) {
230
+ return;
162
231
  }
163
232
  if (!source && !extVersion) {
164
233
  throw new error_1.FirebaseError("Could not find a source. Please specify a valid source to continue.");
@@ -171,18 +240,16 @@ exports.default = new command_1.Command("ext:install [extensionName]")
171
240
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `You selected: ${clc.bold(spec.displayName)}.\n` +
172
241
  `${spec.description}\n` +
173
242
  `View details: https://firebase.google.com/products/extensions/${spec.name}\n`);
174
- const confirm = await extensionsHelper_1.confirmInstallInstance();
175
- if (!confirm) {
176
- return;
177
- }
178
243
  }
179
244
  try {
180
245
  return installExtension({
181
- paramFilePath,
246
+ paramsEnvPath,
182
247
  projectId,
183
248
  extensionName,
184
249
  source,
185
250
  extVersion,
251
+ nonInteractive: options.nonInteractive,
252
+ force: options.force,
186
253
  });
187
254
  }
188
255
  catch (err) {
@@ -10,6 +10,7 @@ const command_1 = require("../command");
10
10
  const error_1 = require("../error");
11
11
  const projectUtils_1 = require("../projectUtils");
12
12
  const extensionsApi = require("../extensions/extensionsApi");
13
+ const secretsUtils = require("../extensions/secretsUtils");
13
14
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
14
15
  const prompt_1 = require("../prompt");
15
16
  const requirePermissions_1 = require("../requirePermissions");
@@ -51,11 +52,16 @@ exports.default = new command_1.Command("ext:uninstall <extensionInstanceId>")
51
52
  }
52
53
  if (!options.force) {
53
54
  const serviceAccountMessage = `Uninstalling deletes the service account used by this extension instance:\n${clc.bold(instance.serviceAccountEmail)}\n\n`;
55
+ const managedSecrets = await secretsUtils.getManagedSecrets(instance);
54
56
  const resourcesMessage = _.get(instance, "config.source.spec.resources", []).length
55
57
  ? "Uninstalling deletes all extension resources created for this extension instance:\n" +
56
58
  instance.config.source.spec.resources
57
59
  .map((resource) => clc.bold(`- ${extensionsHelper_1.resourceTypeToNiceName[resource.type] || resource.type}: ${resource.name} \n`))
58
60
  .join("") +
61
+ managedSecrets
62
+ .map(secretsUtils.prettySecretName)
63
+ .map((s) => clc.bold(`- Secret: ${s}\n`))
64
+ .join("") +
59
65
  "\n"
60
66
  : "";
61
67
  const artifactsMessage = `The following ${clc.bold("will not")} be deleted:\n` +