firebase-tools 9.16.6 → 9.20.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 (82) hide show
  1. package/CHANGELOG.md +1 -3
  2. package/lib/api.js +1 -0
  3. package/lib/apiv2.js +1 -1
  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 +8 -6
  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-init.js +18 -9
  15. package/lib/commands/ext-dev-publish.js +11 -4
  16. package/lib/commands/ext-dev-unpublish.js +2 -1
  17. package/lib/commands/ext-install.js +115 -48
  18. package/lib/commands/ext-uninstall.js +6 -0
  19. package/lib/commands/ext-update.js +67 -43
  20. package/lib/commands/functions-config-export.js +115 -0
  21. package/lib/commands/functions-delete.js +44 -35
  22. package/lib/commands/functions-list.js +54 -0
  23. package/lib/commands/functions-log.js +5 -22
  24. package/lib/commands/hosting-channel-deploy.js +6 -4
  25. package/lib/commands/index.js +12 -0
  26. package/lib/deploy/functions/backend.js +47 -12
  27. package/lib/deploy/functions/containerCleaner.js +5 -1
  28. package/lib/deploy/functions/deploy.js +7 -5
  29. package/lib/deploy/functions/prepare.js +9 -7
  30. package/lib/deploy/functions/prompts.js +3 -21
  31. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
  32. package/lib/deploy/functions/runtimes/index.js +2 -1
  33. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +4 -3
  34. package/lib/deploy/functions/runtimes/node/parseTriggers.js +14 -9
  35. package/lib/deploy/functions/triggerRegionHelper.js +32 -0
  36. package/lib/downloadUtils.js +37 -0
  37. package/lib/emulator/auth/apiSpec.js +1758 -404
  38. package/lib/emulator/auth/handlers.js +6 -5
  39. package/lib/emulator/auth/operations.js +429 -40
  40. package/lib/emulator/auth/server.js +18 -11
  41. package/lib/emulator/auth/state.js +186 -5
  42. package/lib/emulator/auth/widget_ui.js +2 -2
  43. package/lib/emulator/download.js +2 -31
  44. package/lib/emulator/downloadableEmulators.js +7 -7
  45. package/lib/emulator/emulatorLogger.js +0 -3
  46. package/lib/emulator/events/types.js +16 -0
  47. package/lib/emulator/functionsEmulator.js +102 -17
  48. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  49. package/lib/emulator/functionsEmulatorShared.js +51 -7
  50. package/lib/emulator/functionsEmulatorShell.js +1 -1
  51. package/lib/emulator/pubsubEmulator.js +61 -40
  52. package/lib/extensions/askUserForConsent.js +16 -13
  53. package/lib/extensions/askUserForParam.js +72 -3
  54. package/lib/extensions/billingMigrationHelper.js +1 -11
  55. package/lib/extensions/changelog.js +93 -0
  56. package/lib/extensions/displayExtensionInfo.js +38 -38
  57. package/lib/extensions/emulator/optionsHelper.js +3 -3
  58. package/lib/extensions/emulator/triggerHelper.js +2 -32
  59. package/lib/extensions/extensionsApi.js +69 -95
  60. package/lib/extensions/extensionsHelper.js +75 -50
  61. package/lib/extensions/paramHelper.js +79 -36
  62. package/lib/extensions/refs.js +59 -0
  63. package/lib/extensions/resolveSource.js +2 -20
  64. package/lib/extensions/secretsUtils.js +58 -0
  65. package/lib/extensions/updateHelper.js +39 -105
  66. package/lib/extensions/warnings.js +1 -7
  67. package/lib/functional.js +64 -0
  68. package/lib/functions/env.js +26 -13
  69. package/lib/functions/functionslog.js +40 -0
  70. package/lib/functions/listFunctions.js +10 -0
  71. package/lib/functions/runtimeConfigExport.js +137 -0
  72. package/lib/gcp/cloudfunctions.js +84 -9
  73. package/lib/gcp/cloudfunctionsv2.js +99 -7
  74. package/lib/gcp/cloudlogging.js +27 -21
  75. package/lib/gcp/secretManager.js +111 -0
  76. package/lib/gcp/storage.js +16 -0
  77. package/lib/previews.js +1 -1
  78. package/lib/requireInteractive.js +12 -0
  79. package/package.json +5 -4
  80. package/schema/firebase-config.json +2 -1
  81. package/templates/extensions/CHANGELOG.md +7 -0
  82. package/templates/init/hosting/index.html +10 -10
@@ -1,11 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.confirmInstallInstance = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.confirmExtensionVersion = exports.getExtensionSourceFromName = exports.createSourceFromLocation = exports.publishExtensionVersionFromLocalSource = exports.ensureExtensionsApiEnabled = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
3
+ exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getExtensionSourceFromName = exports.createSourceFromLocation = exports.publishExtensionVersionFromLocalSource = exports.ensureExtensionsApiEnabled = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
4
4
  const _ = require("lodash");
5
5
  const clc = require("cli-color");
6
6
  const ora = require("ora");
7
7
  const semver = require("semver");
8
8
  const marked = require("marked");
9
+ const TerminalRenderer = require("marked-terminal");
10
+ marked.setOptions({
11
+ renderer: new TerminalRenderer(),
12
+ });
9
13
  const api_1 = require("../api");
10
14
  const archiveDirectory_1 = require("../archiveDirectory");
11
15
  const utils_1 = require("./utils");
@@ -17,16 +21,19 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
17
21
  const storage_1 = require("../gcp/storage");
18
22
  const projectUtils_1 = require("../projectUtils");
19
23
  const extensionsApi_1 = require("./extensionsApi");
24
+ const refs = require("./refs");
20
25
  const localHelper_1 = require("./localHelper");
21
26
  const prompt_1 = require("../prompt");
22
27
  const logger_1 = require("../logger");
23
28
  const utils_2 = require("../utils");
29
+ const changelog_1 = require("./changelog");
24
30
  var SpecParamType;
25
31
  (function (SpecParamType) {
26
32
  SpecParamType["SELECT"] = "select";
27
33
  SpecParamType["MULTISELECT"] = "multiSelect";
28
34
  SpecParamType["STRING"] = "string";
29
35
  SpecParamType["SELECTRESOURCE"] = "selectResource";
36
+ SpecParamType["SECRET"] = "secret";
30
37
  })(SpecParamType = exports.SpecParamType || (exports.SpecParamType = {}));
31
38
  var SourceOrigin;
32
39
  (function (SourceOrigin) {
@@ -255,41 +262,60 @@ async function archiveAndUploadSource(extPath, bucketName) {
255
262
  const res = await storage_1.uploadObject(zippedSource, bucketName);
256
263
  return `/${res.bucket}/${res.object}`;
257
264
  }
258
- async function publishExtensionVersionFromLocalSource(publisherId, extensionId, rootDirectory) {
259
- const extensionSpec = await localHelper_1.getLocalExtensionSpec(rootDirectory);
260
- if (extensionSpec.name != extensionId) {
261
- throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
265
+ async function publishExtensionVersionFromLocalSource(args) {
266
+ const extensionSpec = await localHelper_1.getLocalExtensionSpec(args.rootDirectory);
267
+ if (extensionSpec.name != args.extensionId) {
268
+ throw new error_1.FirebaseError(`Extension ID '${clc.bold(args.extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
262
269
  }
263
270
  const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
264
271
  subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
265
272
  validateSpec(subbedSpec);
266
- const consent = await confirmExtensionVersion(publisherId, extensionId, extensionSpec.version);
267
- if (!consent) {
268
- return;
269
- }
270
273
  let extension;
271
274
  try {
272
- extension = await extensionsApi_1.getExtension(`${publisherId}/${extensionId}`);
275
+ extension = await extensionsApi_1.getExtension(`${args.publisherId}/${args.extensionId}`);
273
276
  }
274
277
  catch (err) {
275
278
  }
279
+ let notes;
280
+ try {
281
+ const changes = changelog_1.getLocalChangelog(args.rootDirectory);
282
+ notes = changes[extensionSpec.version];
283
+ }
284
+ catch (err) {
285
+ throw new error_1.FirebaseError("No CHANGELOG.md file found. " +
286
+ "Please create one and add an entry for this version. " +
287
+ marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
288
+ }
289
+ if (!notes && extension) {
290
+ throw new error_1.FirebaseError(`No entry for version ${extensionSpec.version} found in CHANGELOG.md. ` +
291
+ "Please add one so users know what has changed in this version. " +
292
+ marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
293
+ }
294
+ displayReleaseNotes(args.publisherId, args.extensionId, extensionSpec.version, notes);
295
+ if (!(await confirm({
296
+ nonInteractive: args.nonInteractive,
297
+ force: args.force,
298
+ default: false,
299
+ }))) {
300
+ return;
301
+ }
276
302
  if (extension &&
277
303
  extension.latestVersion &&
278
304
  semver.lt(extensionSpec.version, extension.latestVersion)) {
279
- throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(extension.latestVersion)}) for the extension '${clc.bold(`${publisherId}/${extensionId}`)}'. Please make sure this version is greater than the current version (${clc.bold(extension.latestVersion)}) inside of extension.yaml.\n`);
305
+ throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(extension.latestVersion)}) for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please make sure this version is greater than the current version (${clc.bold(extension.latestVersion)}) inside of extension.yaml.\n`);
280
306
  }
281
307
  else if (extension &&
282
308
  extension.latestVersion &&
283
309
  semver.eq(extensionSpec.version, extension.latestVersion)) {
284
- throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${publisherId}/${extensionId}`)}'. Please increment the version inside of extension.yaml.\n`);
310
+ throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please increment the version inside of extension.yaml.\n`);
285
311
  }
286
- const ref = `${publisherId}/${extensionId}@${extensionSpec.version}`;
312
+ const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
287
313
  let packageUri;
288
314
  let objectPath = "";
289
315
  const uploadSpinner = ora.default(" Archiving and uploading extension source code");
290
316
  try {
291
317
  uploadSpinner.start();
292
- objectPath = await archiveAndUploadSource(rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
318
+ objectPath = await archiveAndUploadSource(args.rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
293
319
  uploadSpinner.succeed(" Uploaded extension source code");
294
320
  packageUri = api_1.storageOrigin + objectPath + "?alt=media";
295
321
  }
@@ -307,7 +333,7 @@ async function publishExtensionVersionFromLocalSource(publisherId, extensionId,
307
333
  catch (err) {
308
334
  publishSpinner.fail();
309
335
  if (err.status == 404) {
310
- throw new error_1.FirebaseError(marked(`Couldn't find publisher ID '${clc.bold(publisherId)}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.`));
336
+ throw new error_1.FirebaseError(marked(`Couldn't find publisher ID '${clc.bold(args.publisherId)}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.`));
311
337
  }
312
338
  throw err;
313
339
  }
@@ -369,17 +395,15 @@ async function getExtensionSourceFromName(extensionName) {
369
395
  throw new error_1.FirebaseError(`Could not find an extension named '${extensionName}'. `);
370
396
  }
371
397
  exports.getExtensionSourceFromName = getExtensionSourceFromName;
372
- async function confirmExtensionVersion(publisherId, extensionId, versionId) {
373
- const message = `You are about to publish version ${clc.green(versionId)} of ${clc.green(`${publisherId}/${extensionId}`)} to Firebase's registry of extensions.\n\n` +
374
- "Once an extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n\n" +
375
- "Do you wish to continue?";
376
- return await prompt_1.promptOnce({
377
- type: "confirm",
378
- message,
379
- default: false,
380
- });
398
+ function displayReleaseNotes(publisherId, extensionId, versionId, releaseNotes) {
399
+ const releaseNotesMessage = releaseNotes
400
+ ? ` Release notes for this version:\n${marked(releaseNotes)}\n`
401
+ : "\n";
402
+ const message = `You are about to publish version ${clc.green(versionId)} of ${clc.green(`${publisherId}/${extensionId}`)} to Firebase's registry of extensions.${releaseNotesMessage}` +
403
+ "Once an extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n\n";
404
+ logger_1.logger.info(message);
381
405
  }
382
- exports.confirmExtensionVersion = confirmExtensionVersion;
406
+ exports.displayReleaseNotes = displayReleaseNotes;
383
407
  async function promptForOfficialExtension(message) {
384
408
  const officialExts = await resolveSource_1.getExtensionRegistry(true);
385
409
  return await prompt_1.promptOnce({
@@ -392,11 +416,16 @@ async function promptForOfficialExtension(message) {
392
416
  }
393
417
  exports.promptForOfficialExtension = promptForOfficialExtension;
394
418
  async function promptForRepeatInstance(projectName, extensionName) {
395
- const message = `An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectName)}'.\n` +
396
- `Do you want to proceed with installing another instance of extension '${clc.bold(extensionName)}' in this project?`;
419
+ const message = `An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectName)}'. What would you like to do?`;
420
+ const choices = [
421
+ { name: "Update or reconfigure the existing instance", value: "updateExisting" },
422
+ { name: "Install a new instance with a different ID", value: "installNew" },
423
+ { name: "Cancel extension installation", value: "cancel" },
424
+ ];
397
425
  return await prompt_1.promptOnce({
398
- type: "confirm",
426
+ type: "list",
399
427
  message,
428
+ choices,
400
429
  });
401
430
  }
402
431
  exports.promptForRepeatInstance = promptForRepeatInstance;
@@ -434,29 +463,17 @@ function isLocalOrURLPath(extInstallPath) {
434
463
  return isLocalPath(extInstallPath) || isUrlPath(extInstallPath);
435
464
  }
436
465
  exports.isLocalOrURLPath = isLocalOrURLPath;
437
- async function getSourceOrigin(sourceOrVersion) {
438
- if (!sourceOrVersion) {
439
- return SourceOrigin.OFFICIAL_EXTENSION;
440
- }
441
- if (semver.valid(sourceOrVersion)) {
442
- return SourceOrigin.OFFICIAL_EXTENSION_VERSION;
443
- }
466
+ function getSourceOrigin(sourceOrVersion) {
444
467
  if (isLocalPath(sourceOrVersion)) {
445
468
  return SourceOrigin.LOCAL;
446
469
  }
447
470
  if (isUrlPath(sourceOrVersion)) {
448
471
  return SourceOrigin.URL;
449
472
  }
450
- try {
451
- await resolveSource_1.resolveRegistryEntry(sourceOrVersion);
452
- return SourceOrigin.OFFICIAL_EXTENSION;
453
- }
454
- catch (_a) {
455
- }
456
473
  if (sourceOrVersion.includes("/")) {
457
474
  let ref;
458
475
  try {
459
- ref = extensionsApi_1.parseRef(sourceOrVersion);
476
+ ref = refs.parse(sourceOrVersion);
460
477
  }
461
478
  catch (err) {
462
479
  }
@@ -470,12 +487,20 @@ async function getSourceOrigin(sourceOrVersion) {
470
487
  throw new error_1.FirebaseError(`Could not find source '${clc.bold(sourceOrVersion)}'. Check to make sure the source is correct, and then please try again.`);
471
488
  }
472
489
  exports.getSourceOrigin = getSourceOrigin;
473
- async function confirmInstallInstance(defaultOption) {
474
- const message = `Would you like to continue installing this extension?`;
475
- return await prompt_1.promptOnce({
476
- type: "confirm",
477
- message,
478
- default: defaultOption,
479
- });
490
+ async function confirm(args) {
491
+ if (!args.nonInteractive && !args.force) {
492
+ const message = `Do you wish to continue?`;
493
+ return await prompt_1.promptOnce({
494
+ type: "confirm",
495
+ message,
496
+ default: args.default,
497
+ });
498
+ }
499
+ else if (args.nonInteractive && !args.force) {
500
+ throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
501
+ }
502
+ else {
503
+ return true;
504
+ }
480
505
  }
481
- exports.confirmInstallInstance = confirmInstallInstance;
506
+ exports.confirm = confirm;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readParamsFile = exports.promptForNewParams = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = void 0;
3
+ exports.readEnvFile = exports.getParamsFromFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = void 0;
4
4
  const _ = require("lodash");
5
5
  const path = require("path");
6
6
  const clc = require("cli-color");
@@ -25,70 +25,113 @@ function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
25
25
  return setNewDefaults(specParams, currentParams);
26
26
  }
27
27
  exports.getParamsWithCurrentValuesAsDefaults = getParamsWithCurrentValuesAsDefaults;
28
- async function getParams(projectId, paramSpecs, envFilePath) {
29
- let commandLineParams;
30
- if (envFilePath) {
31
- try {
32
- const buf = fs.readFileSync(path.resolve(envFilePath), "utf8");
33
- commandLineParams = dotenv.parse(buf.toString().trim(), { debug: true });
34
- track("Extension Env File", "Present");
35
- }
36
- catch (err) {
37
- track("Extension Env File", "Invalid");
38
- throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
39
- }
28
+ async function getParams(args) {
29
+ let params;
30
+ if (args.nonInteractive && !args.paramsEnvPath) {
31
+ const paramsMessage = args.paramSpecs
32
+ .map((p) => {
33
+ return `\t${p.param}${p.required ? "" : " (Optional)"}`;
34
+ })
35
+ .join("\n");
36
+ throw new error_1.FirebaseError("In non-interactive mode but no `--params` flag found. " +
37
+ "To install this extension in non-interactive mode, set `--params` to a path to an .env file" +
38
+ " containing values for this extension's params:\n" +
39
+ paramsMessage);
40
+ }
41
+ else if (args.paramsEnvPath) {
42
+ params = getParamsFromFile({
43
+ projectId: args.projectId,
44
+ paramSpecs: args.paramSpecs,
45
+ noninteractive: args.nonInteractive,
46
+ paramsEnvPath: args.paramsEnvPath,
47
+ });
40
48
  }
41
49
  else {
42
- track("Extension Env File", "Not Present");
50
+ const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
51
+ params = await askUserForParam.ask(args.projectId, args.instanceId, args.paramSpecs, firebaseProjectParams, !!args.reconfiguring);
43
52
  }
44
- const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(projectId);
53
+ track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
54
+ return params;
55
+ }
56
+ exports.getParams = getParams;
57
+ async function getParamsForUpdate(args) {
45
58
  let params;
46
- if (commandLineParams) {
47
- params = extensionsHelper_1.populateDefaultParams(commandLineParams, paramSpecs);
48
- extensionsHelper_1.validateCommandLineParams(params, paramSpecs);
59
+ if (args.nonInteractive && !args.paramsEnvPath) {
60
+ const paramsMessage = args.newSpec.params
61
+ .map((p) => {
62
+ return `\t${p.param}${p.required ? "" : " (Optional)"}`;
63
+ })
64
+ .join("\n");
65
+ throw new error_1.FirebaseError("In non-interactive mode but no `--params` flag found. " +
66
+ "To update this extension in non-interactive mode, set `--params` to a path to an .env file" +
67
+ " containing values for this extension's params:\n" +
68
+ paramsMessage);
69
+ }
70
+ else if (args.paramsEnvPath) {
71
+ params = getParamsFromFile({
72
+ projectId: args.projectId,
73
+ paramSpecs: args.newSpec.params,
74
+ noninteractive: args.nonInteractive,
75
+ paramsEnvPath: args.paramsEnvPath,
76
+ });
49
77
  }
50
78
  else {
51
- params = await askUserForParam.ask(paramSpecs, firebaseProjectParams);
79
+ params = await promptForNewParams({
80
+ spec: args.spec,
81
+ newSpec: args.newSpec,
82
+ currentParams: args.currentParams,
83
+ projectId: args.projectId,
84
+ instanceId: args.instanceId,
85
+ });
52
86
  }
53
87
  track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
54
88
  return params;
55
89
  }
56
- exports.getParams = getParams;
57
- async function promptForNewParams(spec, newSpec, currentParams, projectId) {
58
- const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(projectId);
90
+ exports.getParamsForUpdate = getParamsForUpdate;
91
+ async function promptForNewParams(args) {
92
+ const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
59
93
  const comparer = (param1, param2) => {
60
94
  return param1.type === param2.type && param1.param === param2.param;
61
95
  };
62
- let paramsDiffDeletions = _.differenceWith(spec.params, _.get(newSpec, "params", []), comparer);
96
+ let paramsDiffDeletions = _.differenceWith(args.spec.params, _.get(args.newSpec, "params", []), comparer);
63
97
  paramsDiffDeletions = extensionsHelper_1.substituteParams(paramsDiffDeletions, firebaseProjectParams);
64
- let paramsDiffAdditions = _.differenceWith(newSpec.params, _.get(spec, "params", []), comparer);
98
+ let paramsDiffAdditions = _.differenceWith(args.newSpec.params, _.get(args.spec, "params", []), comparer);
65
99
  paramsDiffAdditions = extensionsHelper_1.substituteParams(paramsDiffAdditions, firebaseProjectParams);
66
100
  if (paramsDiffDeletions.length) {
67
101
  logger_1.logger.info("The following params will no longer be used:");
68
102
  paramsDiffDeletions.forEach((param) => {
69
- logger_1.logger.info(clc.red(`- ${param.param}: ${currentParams[param.param.toUpperCase()]}`));
70
- delete currentParams[param.param.toUpperCase()];
103
+ logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
104
+ delete args.currentParams[param.param.toUpperCase()];
71
105
  });
72
106
  }
73
107
  if (paramsDiffAdditions.length) {
74
108
  logger_1.logger.info("To update this instance, configure the following new parameters:");
75
109
  for (const param of paramsDiffAdditions) {
76
- const chosenValue = await askUserForParam.askForParam(param);
77
- currentParams[param.param] = chosenValue;
110
+ const chosenValue = await askUserForParam.askForParam(args.projectId, args.instanceId, param, false);
111
+ args.currentParams[param.param] = chosenValue;
78
112
  }
79
113
  }
80
- return currentParams;
114
+ return args.currentParams;
81
115
  }
82
116
  exports.promptForNewParams = promptForNewParams;
83
- function readParamsFile(envFilePath) {
117
+ function getParamsFromFile(args) {
118
+ let envParams;
84
119
  try {
85
- const buf = fs.readFileSync(path.resolve(envFilePath), "utf8");
86
- return dotenv.parse(buf.toString().trim(), { debug: true });
120
+ envParams = readEnvFile(args.paramsEnvPath);
121
+ track("Extension Env File", "Present");
87
122
  }
88
123
  catch (err) {
89
- throw new error_1.FirebaseError(`Error reading --test-params file: ${err.message}\n`, {
90
- original: err,
91
- });
124
+ track("Extension Env File", "Invalid");
125
+ throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
92
126
  }
127
+ const params = extensionsHelper_1.populateDefaultParams(envParams, args.paramSpecs);
128
+ extensionsHelper_1.validateCommandLineParams(params, args.paramSpecs);
129
+ logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
130
+ return params;
131
+ }
132
+ exports.getParamsFromFile = getParamsFromFile;
133
+ function readEnvFile(envPath) {
134
+ const buf = fs.readFileSync(path.resolve(envPath), "utf8");
135
+ return dotenv.parse(buf.toString().trim(), { debug: true });
93
136
  }
94
- exports.readParamsFile = readParamsFile;
137
+ exports.readEnvFile = readEnvFile;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toExtensionVersionName = exports.toExtensionName = exports.toExtensionVersionRef = exports.toExtensionRef = exports.parse = void 0;
4
+ const semver = require("semver");
5
+ const error_1 = require("../error");
6
+ const refRegex = new RegExp(/^([^/@\n]+)\/{1}([^/@\n]+)(@{1}([^\n]+)|)$/);
7
+ function parse(refOrName) {
8
+ const ret = parseRef(refOrName) || parseName(refOrName);
9
+ if (!ret || !ret.publisherId || !ret.extensionId) {
10
+ throw new error_1.FirebaseError(`Unable to parse ${refOrName} as an extension ref`);
11
+ }
12
+ if (ret.version &&
13
+ !semver.valid(ret.version) &&
14
+ !semver.validRange(ret.version) &&
15
+ ret.version !== "latest") {
16
+ throw new error_1.FirebaseError(`Extension reference ${ret} contains an invalid version ${ret.version}.`);
17
+ }
18
+ return ret;
19
+ }
20
+ exports.parse = parse;
21
+ function parseRef(ref) {
22
+ const parts = refRegex.exec(ref);
23
+ if (parts && (parts.length == 5 || parts.length == 7)) {
24
+ const publisherId = parts[1];
25
+ const extensionId = parts[2];
26
+ const version = parts[4];
27
+ return { publisherId, extensionId, version };
28
+ }
29
+ }
30
+ function parseName(name) {
31
+ const parts = name.split("/");
32
+ return {
33
+ publisherId: parts[1],
34
+ extensionId: parts[3],
35
+ version: parts[5],
36
+ };
37
+ }
38
+ function toExtensionRef(ref) {
39
+ return `${ref.publisherId}/${ref.extensionId}`;
40
+ }
41
+ exports.toExtensionRef = toExtensionRef;
42
+ function toExtensionVersionRef(ref) {
43
+ if (!ref.version) {
44
+ throw new error_1.FirebaseError(`Ref does not have a version`);
45
+ }
46
+ return `${ref.publisherId}/${ref.extensionId}@${ref.version}`;
47
+ }
48
+ exports.toExtensionVersionRef = toExtensionVersionRef;
49
+ function toExtensionName(ref) {
50
+ return `publishers/${ref.publisherId}/extensions/${ref.extensionId}`;
51
+ }
52
+ exports.toExtensionName = toExtensionName;
53
+ function toExtensionVersionName(ref) {
54
+ if (!ref.version) {
55
+ throw new error_1.FirebaseError(`Ref does not have a version`);
56
+ }
57
+ return `publishers/${ref.publisherId}/extensions/${ref.extensionId}/versions/${ref.version}`;
58
+ }
59
+ exports.toExtensionVersionName = toExtensionVersionName;
@@ -1,10 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTrustedPublishers = exports.getExtensionRegistry = exports.promptForUpdateWarnings = exports.getMinRequiredVersion = exports.getTargetVersion = exports.resolveRegistryEntry = exports.isOfficialSource = exports.resolveSourceUrl = exports.confirmUpdateWarning = void 0;
3
+ exports.getTrustedPublishers = exports.getExtensionRegistry = exports.getMinRequiredVersion = exports.getTargetVersion = exports.resolveRegistryEntry = exports.isOfficialSource = exports.resolveSourceUrl = exports.confirmUpdateWarning = void 0;
4
4
  const _ = require("lodash");
5
5
  const clc = require("cli-color");
6
6
  const marked = require("marked");
7
- const semver = require("semver");
8
7
  const api = require("../api");
9
8
  const error_1 = require("../error");
10
9
  const logger_1 = require("../logger");
@@ -58,23 +57,6 @@ function getMinRequiredVersion(registryEntry) {
58
57
  return _.get(registryEntry, ["labels", "minRequired"]);
59
58
  }
60
59
  exports.getMinRequiredVersion = getMinRequiredVersion;
61
- async function promptForUpdateWarnings(registryEntry, startVersion, endVersion) {
62
- if (registryEntry.updateWarnings) {
63
- for (const targetRange in registryEntry.updateWarnings) {
64
- if (semver.satisfies(endVersion, targetRange)) {
65
- const updateWarnings = registryEntry.updateWarnings[targetRange];
66
- for (const updateWarning of updateWarnings) {
67
- if (semver.satisfies(startVersion, updateWarning.from)) {
68
- await module.exports.confirmUpdateWarning(updateWarning);
69
- break;
70
- }
71
- }
72
- break;
73
- }
74
- }
75
- }
76
- }
77
- exports.promptForUpdateWarnings = promptForUpdateWarnings;
78
60
  async function getExtensionRegistry(onlyFeatured) {
79
61
  const res = await api.request("GET", EXTENSIONS_REGISTRY_ENDPOINT, {
80
62
  origin: api.firebaseExtensionsRegistryOrigin,
@@ -96,7 +78,7 @@ async function getTrustedPublishers() {
96
78
  }
97
79
  catch (err) {
98
80
  logger_1.logger.debug("Couldn't get extensions registry, assuming no trusted publishers except Firebase.");
99
- return ["firebase "];
81
+ return ["firebase"];
100
82
  }
101
83
  const publisherIds = new Set();
102
84
  for (const entry in registry) {
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prettySecretName = exports.getSecretLabels = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = void 0;
4
+ const getProjectNumber_1 = require("../getProjectNumber");
5
+ const utils = require("../utils");
6
+ const ensureApiEnabled_1 = require("../ensureApiEnabled");
7
+ const projectUtils_1 = require("../projectUtils");
8
+ const extensionsApi = require("./extensionsApi");
9
+ const secretManagerApi = require("../gcp/secretManager");
10
+ const logger_1 = require("../logger");
11
+ const SECRET_LABEL = "firebase-extensions-managed";
12
+ async function ensureSecretManagerApiEnabled(options) {
13
+ const projectId = projectUtils_1.needProjectId(options);
14
+ return await ensureApiEnabled_1.ensure(projectId, "secretmanager.googleapis.com", "extensions", options.markdown);
15
+ }
16
+ exports.ensureSecretManagerApiEnabled = ensureSecretManagerApiEnabled;
17
+ function usesSecrets(spec) {
18
+ return spec.params && !!spec.params.find((p) => p.type == extensionsApi.ParamType.SECRET);
19
+ }
20
+ exports.usesSecrets = usesSecrets;
21
+ async function grantFirexServiceAgentSecretAdminRole(secret) {
22
+ const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId: secret.projectId });
23
+ const firexSaProjectId = utils.envOverride("FIREBASE_EXTENSIONS_SA_PROJECT_ID", "gcp-sa-firebasemods");
24
+ const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`;
25
+ return secretManagerApi.grantServiceAgentRole(secret, saEmail, "roles/secretmanager.admin");
26
+ }
27
+ exports.grantFirexServiceAgentSecretAdminRole = grantFirexServiceAgentSecretAdminRole;
28
+ async function getManagedSecrets(instance) {
29
+ return (await Promise.all(getActiveSecrets(instance).map(async (secretResourceName) => {
30
+ const secret = secretManagerApi.parseSecretResourceName(secretResourceName);
31
+ const labels = await secretManagerApi.getSecretLabels(secret.projectId, secret.name);
32
+ if (labels && labels[SECRET_LABEL]) {
33
+ return secretResourceName;
34
+ }
35
+ return Promise.resolve("");
36
+ }))).filter((secretId) => !!secretId);
37
+ }
38
+ exports.getManagedSecrets = getManagedSecrets;
39
+ function getActiveSecrets(instance) {
40
+ return instance.config.source.spec.params
41
+ .map((p) => p.type == extensionsApi.ParamType.SECRET && instance.config.params[p.param])
42
+ .filter((pv) => !!pv);
43
+ }
44
+ function getSecretLabels(instanceId) {
45
+ const labels = {};
46
+ labels[SECRET_LABEL] = instanceId;
47
+ return labels;
48
+ }
49
+ exports.getSecretLabels = getSecretLabels;
50
+ function prettySecretName(secretResourceName) {
51
+ const nameTokens = secretResourceName.split("/");
52
+ if (nameTokens.length != 4 && nameTokens.length != 6) {
53
+ logger_1.logger.debug(`unable to parse secret secretResourceName: ${secretResourceName}`);
54
+ return secretResourceName;
55
+ }
56
+ return nameTokens.slice(0, 4).join("/");
57
+ }
58
+ exports.prettySecretName = prettySecretName;