firebase-tools 9.18.0 → 9.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 (114) hide show
  1. package/CHANGELOG.md +3 -6
  2. package/lib/api.js +3 -0
  3. package/lib/apiv2.js +8 -5
  4. package/lib/command.js +1 -1
  5. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  6. package/lib/commands/deploy.js +9 -1
  7. package/lib/commands/ext-configure.js +9 -2
  8. package/lib/commands/ext-dev-deprecate.js +63 -0
  9. package/lib/commands/ext-dev-extension-delete.js +2 -1
  10. package/lib/commands/ext-dev-publish.js +10 -4
  11. package/lib/commands/ext-dev-undeprecate.js +56 -0
  12. package/lib/commands/ext-dev-unpublish.js +12 -4
  13. package/lib/commands/ext-export.js +44 -0
  14. package/lib/commands/ext-install.js +50 -13
  15. package/lib/commands/ext-uninstall.js +6 -0
  16. package/lib/commands/ext-update.js +60 -18
  17. package/lib/commands/functions-config-export.js +115 -0
  18. package/lib/commands/functions-delete.js +47 -25
  19. package/lib/commands/functions-list.js +12 -12
  20. package/lib/commands/index.js +9 -0
  21. package/lib/commands/init.js +3 -0
  22. package/lib/config.js +3 -2
  23. package/lib/deploy/extensions/args.js +2 -0
  24. package/lib/deploy/extensions/deploy.js +49 -0
  25. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  26. package/lib/deploy/extensions/errors.js +31 -0
  27. package/lib/deploy/extensions/index.js +8 -0
  28. package/lib/deploy/extensions/planner.js +95 -0
  29. package/lib/deploy/extensions/prepare.js +103 -0
  30. package/lib/deploy/extensions/release.js +43 -0
  31. package/lib/deploy/extensions/secrets.js +150 -0
  32. package/lib/deploy/extensions/tasks.js +98 -0
  33. package/lib/deploy/extensions/validate.js +17 -0
  34. package/lib/deploy/functions/backend.js +93 -115
  35. package/lib/deploy/functions/checkIam.js +8 -8
  36. package/lib/deploy/functions/containerCleaner.js +82 -22
  37. package/lib/deploy/functions/deploy.js +4 -10
  38. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  39. package/lib/deploy/functions/prepare.js +62 -27
  40. package/lib/deploy/functions/pricing.js +17 -17
  41. package/lib/deploy/functions/prompts.js +22 -21
  42. package/lib/deploy/functions/release/executor.js +39 -0
  43. package/lib/deploy/functions/release/fabricator.js +422 -0
  44. package/lib/deploy/functions/release/index.js +73 -0
  45. package/lib/deploy/functions/release/planner.js +162 -0
  46. package/lib/deploy/functions/release/reporter.js +165 -0
  47. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  48. package/lib/deploy/functions/release/timer.js +14 -0
  49. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
  50. package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
  51. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  52. package/lib/deploy/functions/validate.js +1 -24
  53. package/lib/deploy/index.js +1 -0
  54. package/lib/downloadUtils.js +37 -0
  55. package/lib/emulator/auth/apiSpec.js +1788 -403
  56. package/lib/emulator/auth/handlers.js +6 -5
  57. package/lib/emulator/auth/operations.js +439 -40
  58. package/lib/emulator/auth/server.js +32 -11
  59. package/lib/emulator/auth/state.js +205 -5
  60. package/lib/emulator/auth/widget_ui.js +2 -2
  61. package/lib/emulator/download.js +2 -31
  62. package/lib/emulator/downloadableEmulators.js +7 -7
  63. package/lib/emulator/emulatorLogger.js +0 -3
  64. package/lib/emulator/events/types.js +16 -0
  65. package/lib/emulator/functionsEmulator.js +120 -21
  66. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  67. package/lib/emulator/functionsEmulatorShared.js +51 -7
  68. package/lib/emulator/functionsEmulatorShell.js +1 -1
  69. package/lib/emulator/pubsubEmulator.js +61 -40
  70. package/lib/emulator/storage/cloudFunctions.js +37 -7
  71. package/lib/extensions/askUserForConsent.js +14 -1
  72. package/lib/extensions/askUserForParam.js +81 -4
  73. package/lib/extensions/billingMigrationHelper.js +1 -11
  74. package/lib/extensions/changelog.js +2 -1
  75. package/lib/extensions/checkProjectBilling.js +7 -7
  76. package/lib/extensions/displayExtensionInfo.js +35 -33
  77. package/lib/extensions/emulator/optionsHelper.js +3 -3
  78. package/lib/extensions/emulator/triggerHelper.js +2 -32
  79. package/lib/extensions/export.js +107 -0
  80. package/lib/extensions/extensionsApi.js +149 -97
  81. package/lib/extensions/extensionsHelper.js +36 -32
  82. package/lib/extensions/listExtensions.js +16 -11
  83. package/lib/extensions/paramHelper.js +73 -40
  84. package/lib/extensions/provisioningHelper.js +16 -3
  85. package/lib/extensions/refs.js +67 -0
  86. package/lib/extensions/secretsUtils.js +59 -0
  87. package/lib/extensions/updateHelper.js +33 -47
  88. package/lib/extensions/versionHelper.js +14 -0
  89. package/lib/extensions/warnings.js +33 -1
  90. package/lib/functional.js +64 -0
  91. package/lib/functions/env.js +26 -13
  92. package/lib/functions/runtimeConfigExport.js +137 -0
  93. package/lib/gcp/artifactregistry.js +16 -0
  94. package/lib/gcp/cloudfunctions.js +65 -35
  95. package/lib/gcp/cloudfunctionsv2.js +56 -43
  96. package/lib/gcp/cloudscheduler.js +22 -16
  97. package/lib/gcp/cloudtasks.js +143 -0
  98. package/lib/gcp/docker.js +7 -1
  99. package/lib/gcp/proto.js +2 -2
  100. package/lib/gcp/pubsub.js +1 -9
  101. package/lib/gcp/secretManager.js +132 -0
  102. package/lib/gcp/storage.js +16 -0
  103. package/lib/previews.js +1 -1
  104. package/lib/requireInteractive.js +12 -0
  105. package/lib/utils.js +30 -1
  106. package/package.json +6 -4
  107. package/schema/firebase-config.json +9 -0
  108. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  109. package/lib/deploy/functions/deploymentTimer.js +0 -23
  110. package/lib/deploy/functions/errorHandler.js +0 -75
  111. package/lib/deploy/functions/release.js +0 -116
  112. package/lib/deploy/functions/tasks.js +0 -324
  113. package/lib/functions/listFunctions.js +0 -10
  114. package/lib/functionsDelete.js +0 -60
@@ -1,6 +1,6 @@
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");
@@ -21,17 +21,20 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
21
21
  const storage_1 = require("../gcp/storage");
22
22
  const projectUtils_1 = require("../projectUtils");
23
23
  const extensionsApi_1 = require("./extensionsApi");
24
+ const refs = require("./refs");
24
25
  const localHelper_1 = require("./localHelper");
25
26
  const prompt_1 = require("../prompt");
26
27
  const logger_1 = require("../logger");
27
28
  const utils_2 = require("../utils");
28
29
  const changelog_1 = require("./changelog");
30
+ const getProjectNumber_1 = require("../getProjectNumber");
29
31
  var SpecParamType;
30
32
  (function (SpecParamType) {
31
33
  SpecParamType["SELECT"] = "select";
32
34
  SpecParamType["MULTISELECT"] = "multiSelect";
33
35
  SpecParamType["STRING"] = "string";
34
36
  SpecParamType["SELECTRESOURCE"] = "selectResource";
37
+ SpecParamType["SECRET"] = "secret";
35
38
  })(SpecParamType = exports.SpecParamType || (exports.SpecParamType = {}));
36
39
  var SourceOrigin;
37
40
  (function (SourceOrigin) {
@@ -74,6 +77,7 @@ function getDBInstanceFromURL(databaseUrl = "") {
74
77
  exports.getDBInstanceFromURL = getDBInstanceFromURL;
75
78
  async function getFirebaseProjectParams(projectId) {
76
79
  const body = await functionsConfig_1.getFirebaseConfig({ project: projectId });
80
+ const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId });
77
81
  const FIREBASE_CONFIG = JSON.stringify({
78
82
  projectId: body.projectId,
79
83
  databaseURL: body.databaseURL,
@@ -81,6 +85,7 @@ async function getFirebaseProjectParams(projectId) {
81
85
  });
82
86
  return {
83
87
  PROJECT_ID: body.projectId,
88
+ PROJECT_NUMBER: projectNumber,
84
89
  DATABASE_URL: body.databaseURL,
85
90
  STORAGE_BUCKET: body.storageBucket,
86
91
  FIREBASE_CONFIG,
@@ -106,7 +111,7 @@ function populateDefaultParams(paramVars, paramSpecs) {
106
111
  const newParams = paramVars;
107
112
  for (const param of paramSpecs) {
108
113
  if (!paramVars[param.param]) {
109
- if (param.default != undefined) {
114
+ if (param.default != undefined && param.required) {
110
115
  newParams[param.param] = param.default;
111
116
  }
112
117
  else if (param.required) {
@@ -260,23 +265,23 @@ async function archiveAndUploadSource(extPath, bucketName) {
260
265
  const res = await storage_1.uploadObject(zippedSource, bucketName);
261
266
  return `/${res.bucket}/${res.object}`;
262
267
  }
263
- async function publishExtensionVersionFromLocalSource(publisherId, extensionId, rootDirectory) {
264
- const extensionSpec = await localHelper_1.getLocalExtensionSpec(rootDirectory);
265
- if (extensionSpec.name != extensionId) {
266
- throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
268
+ async function publishExtensionVersionFromLocalSource(args) {
269
+ const extensionSpec = await localHelper_1.getLocalExtensionSpec(args.rootDirectory);
270
+ if (extensionSpec.name != args.extensionId) {
271
+ throw new error_1.FirebaseError(`Extension ID '${clc.bold(args.extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
267
272
  }
268
273
  const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
269
274
  subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
270
275
  validateSpec(subbedSpec);
271
276
  let extension;
272
277
  try {
273
- extension = await extensionsApi_1.getExtension(`${publisherId}/${extensionId}`);
278
+ extension = await extensionsApi_1.getExtension(`${args.publisherId}/${args.extensionId}`);
274
279
  }
275
280
  catch (err) {
276
281
  }
277
282
  let notes;
278
283
  try {
279
- const changes = changelog_1.getLocalChangelog(rootDirectory);
284
+ const changes = changelog_1.getLocalChangelog(args.rootDirectory);
280
285
  notes = changes[extensionSpec.version];
281
286
  }
282
287
  catch (err) {
@@ -289,27 +294,31 @@ async function publishExtensionVersionFromLocalSource(publisherId, extensionId,
289
294
  "Please add one so users know what has changed in this version. " +
290
295
  marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
291
296
  }
292
- const consent = await confirmExtensionVersion(publisherId, extensionId, extensionSpec.version, notes);
293
- if (!consent) {
297
+ displayReleaseNotes(args.publisherId, args.extensionId, extensionSpec.version, notes);
298
+ if (!(await confirm({
299
+ nonInteractive: args.nonInteractive,
300
+ force: args.force,
301
+ default: false,
302
+ }))) {
294
303
  return;
295
304
  }
296
305
  if (extension &&
297
306
  extension.latestVersion &&
298
307
  semver.lt(extensionSpec.version, extension.latestVersion)) {
299
- 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`);
308
+ 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`);
300
309
  }
301
310
  else if (extension &&
302
311
  extension.latestVersion &&
303
312
  semver.eq(extensionSpec.version, extension.latestVersion)) {
304
- 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`);
313
+ 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`, { exit: 103 });
305
314
  }
306
- const ref = `${publisherId}/${extensionId}@${extensionSpec.version}`;
315
+ const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
307
316
  let packageUri;
308
317
  let objectPath = "";
309
318
  const uploadSpinner = ora.default(" Archiving and uploading extension source code");
310
319
  try {
311
320
  uploadSpinner.start();
312
- objectPath = await archiveAndUploadSource(rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
321
+ objectPath = await archiveAndUploadSource(args.rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
313
322
  uploadSpinner.succeed(" Uploaded extension source code");
314
323
  packageUri = api_1.storageOrigin + objectPath + "?alt=media";
315
324
  }
@@ -327,7 +336,7 @@ async function publishExtensionVersionFromLocalSource(publisherId, extensionId,
327
336
  catch (err) {
328
337
  publishSpinner.fail();
329
338
  if (err.status == 404) {
330
- 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.`));
339
+ 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.`));
331
340
  }
332
341
  throw err;
333
342
  }
@@ -389,20 +398,15 @@ async function getExtensionSourceFromName(extensionName) {
389
398
  throw new error_1.FirebaseError(`Could not find an extension named '${extensionName}'. `);
390
399
  }
391
400
  exports.getExtensionSourceFromName = getExtensionSourceFromName;
392
- async function confirmExtensionVersion(publisherId, extensionId, versionId, releaseNotes) {
401
+ function displayReleaseNotes(publisherId, extensionId, versionId, releaseNotes) {
393
402
  const releaseNotesMessage = releaseNotes
394
403
  ? ` Release notes for this version:\n${marked(releaseNotes)}\n`
395
404
  : "\n";
396
405
  const message = `You are about to publish version ${clc.green(versionId)} of ${clc.green(`${publisherId}/${extensionId}`)} to Firebase's registry of extensions.${releaseNotesMessage}` +
397
- "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" +
398
- "Do you wish to continue?";
399
- return await prompt_1.promptOnce({
400
- type: "confirm",
401
- message,
402
- default: false,
403
- });
406
+ "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";
407
+ logger_1.logger.info(message);
404
408
  }
405
- exports.confirmExtensionVersion = confirmExtensionVersion;
409
+ exports.displayReleaseNotes = displayReleaseNotes;
406
410
  async function promptForOfficialExtension(message) {
407
411
  const officialExts = await resolveSource_1.getExtensionRegistry(true);
408
412
  return await prompt_1.promptOnce({
@@ -472,7 +476,7 @@ function getSourceOrigin(sourceOrVersion) {
472
476
  if (sourceOrVersion.includes("/")) {
473
477
  let ref;
474
478
  try {
475
- ref = extensionsApi_1.parseRef(sourceOrVersion);
479
+ ref = refs.parse(sourceOrVersion);
476
480
  }
477
481
  catch (err) {
478
482
  }
@@ -486,20 +490,20 @@ function getSourceOrigin(sourceOrVersion) {
486
490
  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.`);
487
491
  }
488
492
  exports.getSourceOrigin = getSourceOrigin;
489
- async function confirmInstallInstance(nonInteractive, force) {
490
- if (!nonInteractive && !force) {
491
- const message = `Would you like to continue installing this extension?`;
493
+ async function confirm(args) {
494
+ if (!args.nonInteractive && !args.force) {
495
+ const message = `Do you wish to continue?`;
492
496
  return await prompt_1.promptOnce({
493
497
  type: "confirm",
494
498
  message,
495
- default: true,
499
+ default: args.default,
496
500
  });
497
501
  }
498
- else if (nonInteractive && !force) {
499
- throw new error_1.FirebaseError("Pass the --force flag to install in non-interactive mode");
502
+ else if (args.nonInteractive && !args.force) {
503
+ throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
500
504
  }
501
505
  else {
502
506
  return true;
503
507
  }
504
508
  }
505
- exports.confirmInstallInstance = confirmInstallInstance;
509
+ exports.confirm = confirm;
@@ -8,19 +8,20 @@ const extensionsApi_1 = require("./extensionsApi");
8
8
  const extensionsHelper_1 = require("./extensionsHelper");
9
9
  const utils = require("../utils");
10
10
  const extensionsUtils = require("./utils");
11
- const logger_1 = require("../logger");
12
11
  async function listExtensions(projectId) {
13
12
  const instances = await extensionsApi_1.listInstances(projectId);
14
13
  if (instances.length < 1) {
15
14
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `there are no extensions installed on project ${clc.bold(projectId)}.`);
16
- return { instances: [] };
15
+ return [];
17
16
  }
18
17
  const table = new Table({
19
18
  head: ["Extension", "Publisher", "Instance ID", "State", "Version", "Your last update"],
20
19
  style: { head: ["yellow"] },
21
20
  });
22
21
  const sorted = _.sortBy(instances, "createTime", "asc").reverse();
22
+ const formatted = [];
23
23
  sorted.forEach((instance) => {
24
+ var _a, _b, _c, _d;
24
25
  let extension = _.get(instance, "config.extensionRef", "");
25
26
  let publisher;
26
27
  if (extension === "") {
@@ -30,18 +31,22 @@ async function listExtensions(projectId) {
30
31
  else {
31
32
  publisher = extension.split("/")[0];
32
33
  }
33
- table.push([
34
+ const instanceId = (_a = _.last(instance.name.split("/"))) !== null && _a !== void 0 ? _a : "";
35
+ const state = instance.state +
36
+ (_.get(instance, "config.source.state", "ACTIVE") === "DELETED" ? " (UNPUBLISHED)" : "");
37
+ const version = (_d = (_c = (_b = instance === null || instance === void 0 ? void 0 : instance.config) === null || _b === void 0 ? void 0 : _b.source) === null || _c === void 0 ? void 0 : _c.spec) === null || _d === void 0 ? void 0 : _d.version;
38
+ const updateTime = extensionsUtils.formatTimestamp(instance.updateTime);
39
+ table.push([extension, publisher, instanceId, state, version, updateTime]);
40
+ formatted.push({
34
41
  extension,
35
42
  publisher,
36
- _.last(instance.name.split("/")),
37
- instance.state +
38
- (_.get(instance, "config.source.state", "ACTIVE") === "DELETED" ? " (UNPUBLISHED)" : ""),
39
- _.get(instance, "config.source.spec.version", ""),
40
- extensionsUtils.formatTimestamp(instance.updateTime),
41
- ]);
43
+ instanceId,
44
+ state,
45
+ version,
46
+ updateTime,
47
+ });
42
48
  });
43
49
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`);
44
- logger_1.logger.info(table.toString());
45
- return { instances: sorted };
50
+ return formatted;
46
51
  }
47
52
  exports.listExtensions = listExtensions;
@@ -1,16 +1,16 @@
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");
7
- const dotenv = require("dotenv");
8
7
  const fs = require("fs-extra");
9
8
  const error_1 = require("../error");
10
9
  const logger_1 = require("../logger");
11
10
  const extensionsHelper_1 = require("./extensionsHelper");
12
11
  const askUserForParam = require("./askUserForParam");
13
12
  const track = require("../track");
13
+ const env = require("../functions/env");
14
14
  function setNewDefaults(params, newDefaults) {
15
15
  params.forEach((param) => {
16
16
  if (newDefaults[param.param.toUpperCase()]) {
@@ -25,9 +25,10 @@ function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
25
25
  return setNewDefaults(specParams, currentParams);
26
26
  }
27
27
  exports.getParamsWithCurrentValuesAsDefaults = getParamsWithCurrentValuesAsDefaults;
28
- async function getParams(projectId, paramSpecs, noninteractive = false, envFilePath) {
29
- if (noninteractive && !envFilePath) {
30
- const paramsMessage = paramSpecs
28
+ async function getParams(args) {
29
+ let params;
30
+ if (args.nonInteractive && !args.paramsEnvPath) {
31
+ const paramsMessage = args.paramSpecs
31
32
  .map((p) => {
32
33
  return `\t${p.param}${p.required ? "" : " (Optional)"}`;
33
34
  })
@@ -37,70 +38,102 @@ async function getParams(projectId, paramSpecs, noninteractive = false, envFileP
37
38
  " containing values for this extension's params:\n" +
38
39
  paramsMessage);
39
40
  }
40
- let commandLineParams;
41
- if (envFilePath) {
42
- try {
43
- const buf = fs.readFileSync(path.resolve(envFilePath), "utf8");
44
- commandLineParams = dotenv.parse(buf.toString().trim(), { debug: true });
45
- track("Extension Env File", "Present");
46
- }
47
- catch (err) {
48
- track("Extension Env File", "Invalid");
49
- throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
50
- }
41
+ else if (args.paramsEnvPath) {
42
+ params = getParamsFromFile({
43
+ projectId: args.projectId,
44
+ paramSpecs: args.paramSpecs,
45
+ paramsEnvPath: args.paramsEnvPath,
46
+ });
51
47
  }
52
48
  else {
53
- track("Extension Env File", "Not Present");
49
+ const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
50
+ params = await askUserForParam.ask(args.projectId, args.instanceId, args.paramSpecs, firebaseProjectParams, !!args.reconfiguring);
54
51
  }
55
- const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(projectId);
52
+ track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
53
+ return params;
54
+ }
55
+ exports.getParams = getParams;
56
+ async function getParamsForUpdate(args) {
56
57
  let params;
57
- if (commandLineParams) {
58
- params = extensionsHelper_1.populateDefaultParams(commandLineParams, paramSpecs);
59
- extensionsHelper_1.validateCommandLineParams(params, paramSpecs);
60
- logger_1.logger.info(`Using param values from ${envFilePath}`);
58
+ if (args.nonInteractive && !args.paramsEnvPath) {
59
+ const paramsMessage = args.newSpec.params
60
+ .map((p) => {
61
+ return `\t${p.param}${p.required ? "" : " (Optional)"}`;
62
+ })
63
+ .join("\n");
64
+ throw new error_1.FirebaseError("In non-interactive mode but no `--params` flag found. " +
65
+ "To update this extension in non-interactive mode, set `--params` to a path to an .env file" +
66
+ " containing values for this extension's params:\n" +
67
+ paramsMessage);
68
+ }
69
+ else if (args.paramsEnvPath) {
70
+ params = getParamsFromFile({
71
+ projectId: args.projectId,
72
+ paramSpecs: args.newSpec.params,
73
+ paramsEnvPath: args.paramsEnvPath,
74
+ });
61
75
  }
62
76
  else {
63
- params = await askUserForParam.ask(paramSpecs, firebaseProjectParams);
77
+ params = await promptForNewParams({
78
+ spec: args.spec,
79
+ newSpec: args.newSpec,
80
+ currentParams: args.currentParams,
81
+ projectId: args.projectId,
82
+ instanceId: args.instanceId,
83
+ });
64
84
  }
65
85
  track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
66
86
  return params;
67
87
  }
68
- exports.getParams = getParams;
69
- async function promptForNewParams(spec, newSpec, currentParams, projectId) {
70
- const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(projectId);
88
+ exports.getParamsForUpdate = getParamsForUpdate;
89
+ async function promptForNewParams(args) {
90
+ const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
71
91
  const comparer = (param1, param2) => {
72
92
  return param1.type === param2.type && param1.param === param2.param;
73
93
  };
74
- let paramsDiffDeletions = _.differenceWith(spec.params, _.get(newSpec, "params", []), comparer);
94
+ let paramsDiffDeletions = _.differenceWith(args.spec.params, _.get(args.newSpec, "params", []), comparer);
75
95
  paramsDiffDeletions = extensionsHelper_1.substituteParams(paramsDiffDeletions, firebaseProjectParams);
76
- let paramsDiffAdditions = _.differenceWith(newSpec.params, _.get(spec, "params", []), comparer);
96
+ let paramsDiffAdditions = _.differenceWith(args.newSpec.params, _.get(args.spec, "params", []), comparer);
77
97
  paramsDiffAdditions = extensionsHelper_1.substituteParams(paramsDiffAdditions, firebaseProjectParams);
78
98
  if (paramsDiffDeletions.length) {
79
99
  logger_1.logger.info("The following params will no longer be used:");
80
100
  paramsDiffDeletions.forEach((param) => {
81
- logger_1.logger.info(clc.red(`- ${param.param}: ${currentParams[param.param.toUpperCase()]}`));
82
- delete currentParams[param.param.toUpperCase()];
101
+ logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
102
+ delete args.currentParams[param.param.toUpperCase()];
83
103
  });
84
104
  }
85
105
  if (paramsDiffAdditions.length) {
86
106
  logger_1.logger.info("To update this instance, configure the following new parameters:");
87
107
  for (const param of paramsDiffAdditions) {
88
- const chosenValue = await askUserForParam.askForParam(param);
89
- currentParams[param.param] = chosenValue;
108
+ const chosenValue = await askUserForParam.askForParam(args.projectId, args.instanceId, param, false);
109
+ args.currentParams[param.param] = chosenValue;
90
110
  }
91
111
  }
92
- return currentParams;
112
+ return args.currentParams;
93
113
  }
94
114
  exports.promptForNewParams = promptForNewParams;
95
- function readParamsFile(envFilePath) {
115
+ function getParamsFromFile(args) {
116
+ let envParams;
96
117
  try {
97
- const buf = fs.readFileSync(path.resolve(envFilePath), "utf8");
98
- return dotenv.parse(buf.toString().trim(), { debug: true });
118
+ envParams = readEnvFile(args.paramsEnvPath);
119
+ track("Extension Env File", "Present");
99
120
  }
100
121
  catch (err) {
101
- throw new error_1.FirebaseError(`Error reading --test-params file: ${err.message}\n`, {
102
- original: err,
103
- });
122
+ track("Extension Env File", "Invalid");
123
+ throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
124
+ }
125
+ const params = extensionsHelper_1.populateDefaultParams(envParams, args.paramSpecs);
126
+ extensionsHelper_1.validateCommandLineParams(params, args.paramSpecs);
127
+ logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
128
+ return params;
129
+ }
130
+ exports.getParamsFromFile = getParamsFromFile;
131
+ function readEnvFile(envPath) {
132
+ const buf = fs.readFileSync(path.resolve(envPath), "utf8");
133
+ const result = env.parse(buf.toString().trim());
134
+ if (result.errors.length) {
135
+ throw new error_1.FirebaseError(`Error while parsing ${envPath} - unable to parse following lines:\n${result.errors.join("\n")}`);
104
136
  }
137
+ return result.envs;
105
138
  }
106
- exports.readParamsFile = readParamsFile;
139
+ exports.readEnvFile = readEnvFile;
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUsedProducts = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
3
+ exports.getUsedProducts = exports.bulkCheckProductsProvisioned = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
4
4
  const marked = require("marked");
5
5
  const api = require("../api");
6
+ const functional_1 = require("../functional");
6
7
  const error_1 = require("../error");
8
+ const planner_1 = require("../deploy/extensions/planner");
7
9
  var DeferredProduct;
8
10
  (function (DeferredProduct) {
9
11
  DeferredProduct[DeferredProduct["STORAGE"] = 0] = "STORAGE";
@@ -11,6 +13,18 @@ var DeferredProduct;
11
13
  })(DeferredProduct = exports.DeferredProduct || (exports.DeferredProduct = {}));
12
14
  async function checkProductsProvisioned(projectId, spec) {
13
15
  const usedProducts = getUsedProducts(spec);
16
+ await checkProducts(projectId, usedProducts);
17
+ }
18
+ exports.checkProductsProvisioned = checkProductsProvisioned;
19
+ async function bulkCheckProductsProvisioned(projectId, instanceSpecs) {
20
+ const usedProducts = await Promise.all(instanceSpecs.map(async (i) => {
21
+ const extensionVersion = await planner_1.getExtensionVersion(i);
22
+ return getUsedProducts(extensionVersion.spec);
23
+ }));
24
+ await checkProducts(projectId, [...functional_1.flattenArray(usedProducts)]);
25
+ }
26
+ exports.bulkCheckProductsProvisioned = bulkCheckProductsProvisioned;
27
+ async function checkProducts(projectId, usedProducts) {
14
28
  const needProvisioning = [];
15
29
  let isStorageProvisionedPromise;
16
30
  let isAuthProvisionedPromise;
@@ -29,7 +43,7 @@ async function checkProductsProvisioned(projectId, spec) {
29
43
  if (needProvisioning.length > 0) {
30
44
  let errorMessage = "Some services used by this extension have not been set up on your " +
31
45
  "Firebase project. To ensure this extension works as intended, you must enable these " +
32
- "services by following the provided links, then retry installing the extension\n\n";
46
+ "services by following the provided links, then retry this command\n\n";
33
47
  if (needProvisioning.includes(DeferredProduct.STORAGE)) {
34
48
  errorMessage +=
35
49
  " - Firebase Storage: store and retrieve user-generated files like images, audio, and " +
@@ -46,7 +60,6 @@ async function checkProductsProvisioned(projectId, spec) {
46
60
  throw new error_1.FirebaseError(marked(errorMessage), { exit: 2 });
47
61
  }
48
62
  }
49
- exports.checkProductsProvisioned = checkProductsProvisioned;
50
63
  function getUsedProducts(spec) {
51
64
  var _a, _b;
52
65
  const usedProducts = [];
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.equal = 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;
60
+ function equal(a, b) {
61
+ return (!!a &&
62
+ !!b &&
63
+ a.publisherId === b.publisherId &&
64
+ a.extensionId === b.extensionId &&
65
+ a.version === b.version);
66
+ }
67
+ exports.equal = equal;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.prettySecretName = exports.getSecretLabels = exports.getActiveSecrets = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = exports.SECRET_LABEL = 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
+ exports.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.config.source.spec, instance.config.params).map(async (secretResourceName) => {
30
+ const secret = secretManagerApi.parseSecretResourceName(secretResourceName);
31
+ const labels = (await secretManagerApi.getSecret(secret.projectId, secret.name)).labels;
32
+ if (labels && labels[exports.SECRET_LABEL]) {
33
+ return secretResourceName;
34
+ }
35
+ return Promise.resolve("");
36
+ }))).filter((secretId) => !!secretId);
37
+ }
38
+ exports.getManagedSecrets = getManagedSecrets;
39
+ function getActiveSecrets(spec, params) {
40
+ return spec.params
41
+ .map((p) => (p.type == extensionsApi.ParamType.SECRET ? params[p.param] : ""))
42
+ .filter((pv) => !!pv);
43
+ }
44
+ exports.getActiveSecrets = getActiveSecrets;
45
+ function getSecretLabels(instanceId) {
46
+ const labels = {};
47
+ labels[exports.SECRET_LABEL] = instanceId;
48
+ return labels;
49
+ }
50
+ exports.getSecretLabels = getSecretLabels;
51
+ function prettySecretName(secretResourceName) {
52
+ const nameTokens = secretResourceName.split("/");
53
+ if (nameTokens.length != 4 && nameTokens.length != 6) {
54
+ logger_1.logger.debug(`unable to parse secret secretResourceName: ${secretResourceName}`);
55
+ return secretResourceName;
56
+ }
57
+ return nameTokens.slice(0, 4).join("/");
58
+ }
59
+ exports.prettySecretName = prettySecretName;