firebase-tools 10.1.5 → 10.2.2

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 (134) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +3 -0
  3. package/lib/appdistribution/options-parser-util.js +1 -1
  4. package/lib/auth.js +62 -25
  5. package/lib/command.js +1 -1
  6. package/lib/commands/apps-android-sha-create.js +2 -2
  7. package/lib/commands/apps-sdkconfig.js +1 -1
  8. package/lib/commands/auth-import.js +1 -1
  9. package/lib/commands/database-rules-list.js +2 -2
  10. package/lib/commands/emulators-start.js +1 -1
  11. package/lib/commands/ext-configure.js +1 -0
  12. package/lib/commands/ext-dev-init.js +49 -49
  13. package/lib/commands/ext-export.js +12 -2
  14. package/lib/commands/ext-install.js +104 -103
  15. package/lib/commands/ext-uninstall.js +9 -8
  16. package/lib/commands/ext-update.js +10 -9
  17. package/lib/commands/functions-config-clone.js +1 -1
  18. package/lib/commands/functions-config-export.js +1 -1
  19. package/lib/commands/functions-secrets-access.js +17 -0
  20. package/lib/commands/functions-secrets-destroy.js +40 -0
  21. package/lib/commands/functions-secrets-get.js +21 -0
  22. package/lib/commands/functions-secrets-prune.js +50 -0
  23. package/lib/commands/functions-secrets-set.js +46 -0
  24. package/lib/commands/hosting-clone.js +3 -3
  25. package/lib/commands/index.js +7 -3
  26. package/lib/commands/login.js +1 -1
  27. package/lib/commands/remoteconfig-get.js +1 -1
  28. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  29. package/lib/deploy/extensions/params.js +3 -0
  30. package/lib/deploy/extensions/planner.js +2 -1
  31. package/lib/deploy/extensions/tasks.js +1 -1
  32. package/lib/deploy/functions/backend.js +20 -5
  33. package/lib/deploy/functions/checkIam.js +1 -1
  34. package/lib/deploy/functions/containerCleaner.js +3 -3
  35. package/lib/deploy/functions/ensure.js +112 -0
  36. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  37. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  38. package/lib/deploy/functions/prepare.js +15 -20
  39. package/lib/deploy/functions/pricing.js +1 -1
  40. package/lib/deploy/functions/prompts.js +2 -2
  41. package/lib/deploy/functions/release/fabricator.js +3 -3
  42. package/lib/deploy/functions/release/index.js +1 -1
  43. package/lib/deploy/functions/release/planner.js +11 -8
  44. package/lib/deploy/functions/release/reporter.js +3 -0
  45. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  46. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  47. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +17 -11
  48. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  49. package/lib/deploy/functions/runtimes/node/index.js +26 -0
  50. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  51. package/lib/deploy/functions/runtimes/node/parseTriggers.js +40 -7
  52. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  53. package/lib/deploy/functions/validate.js +58 -3
  54. package/lib/deploy/hosting/client.js +9 -0
  55. package/lib/deploy/hosting/convertConfig.js +6 -0
  56. package/lib/deploy/hosting/deploy.js +2 -2
  57. package/lib/deploy/hosting/hashcache.js +21 -19
  58. package/lib/deploy/hosting/index.js +5 -5
  59. package/lib/deploy/hosting/prepare.js +25 -25
  60. package/lib/deploy/hosting/release.js +21 -24
  61. package/lib/deploy/hosting/uploader.js +5 -5
  62. package/lib/deploy/remoteconfig/functions.js +2 -2
  63. package/lib/emulator/auth/cloudFunctions.js +1 -1
  64. package/lib/emulator/auth/operations.js +1 -1
  65. package/lib/emulator/commandUtils.js +5 -1
  66. package/lib/emulator/constants.js +3 -0
  67. package/lib/emulator/controller.js +48 -18
  68. package/lib/emulator/download.js +18 -1
  69. package/lib/emulator/downloadableEmulators.js +30 -13
  70. package/lib/emulator/emulatorLogger.js +19 -1
  71. package/lib/emulator/extensions/validation.js +35 -0
  72. package/lib/emulator/extensionsEmulator.js +140 -0
  73. package/lib/emulator/functionsEmulator.js +175 -86
  74. package/lib/emulator/functionsEmulatorRuntime.js +108 -83
  75. package/lib/emulator/functionsEmulatorShared.js +51 -1
  76. package/lib/emulator/functionsEmulatorShell.js +1 -2
  77. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  78. package/lib/emulator/functionsRuntimeWorker.js +3 -3
  79. package/lib/emulator/hub.js +4 -3
  80. package/lib/emulator/loggingEmulator.js +1 -1
  81. package/lib/emulator/pubsubEmulator.js +1 -1
  82. package/lib/emulator/registry.js +10 -2
  83. package/lib/emulator/storage/apis/firebase.js +31 -26
  84. package/lib/emulator/storage/apis/gcloud.js +7 -12
  85. package/lib/emulator/storage/files.js +36 -34
  86. package/lib/emulator/storage/index.js +2 -2
  87. package/lib/emulator/storage/metadata.js +2 -2
  88. package/lib/emulator/storage/rules/runtime.js +8 -7
  89. package/lib/emulator/types.js +3 -0
  90. package/lib/ensureApiEnabled.js +5 -1
  91. package/lib/error.js +1 -1
  92. package/lib/extensions/askUserForParam.js +2 -2
  93. package/lib/extensions/changelog.js +3 -1
  94. package/lib/extensions/checkProjectBilling.js +1 -1
  95. package/lib/extensions/diagnose.js +56 -0
  96. package/lib/extensions/displayExtensionInfo.js +1 -1
  97. package/lib/extensions/emulator/optionsHelper.js +24 -8
  98. package/lib/extensions/emulator/specHelper.js +10 -23
  99. package/lib/extensions/export.js +1 -51
  100. package/lib/extensions/extensionsApi.js +1 -1
  101. package/lib/extensions/extensionsHelper.js +23 -10
  102. package/lib/extensions/listExtensions.js +2 -0
  103. package/lib/extensions/manifest.js +48 -0
  104. package/lib/extensions/metricsUtils.js +4 -4
  105. package/lib/extensions/paramHelper.js +4 -4
  106. package/lib/extensions/refs.js +1 -1
  107. package/lib/extensions/secretsUtils.js +4 -4
  108. package/lib/functional.js +1 -1
  109. package/lib/functions/env.js +7 -8
  110. package/lib/functions/secrets.js +112 -0
  111. package/lib/gcp/cloudfunctions.js +24 -5
  112. package/lib/gcp/cloudfunctionsv2.js +18 -5
  113. package/lib/gcp/cloudtasks.js +1 -1
  114. package/lib/gcp/docker.js +2 -2
  115. package/lib/gcp/run.js +2 -2
  116. package/lib/gcp/secretManager.js +128 -46
  117. package/lib/gcp/storage.js +1 -0
  118. package/lib/hosting/api.js +1 -1
  119. package/lib/hosting/functionsProxy.js +15 -5
  120. package/lib/hosting/proxy.js +2 -2
  121. package/lib/init/features/account.js +1 -1
  122. package/lib/management/database.js +1 -1
  123. package/lib/previews.js +1 -1
  124. package/lib/responseToError.js +16 -7
  125. package/lib/serve/functions.js +2 -2
  126. package/lib/serve/hosting.js +1 -1
  127. package/lib/utils.js +7 -2
  128. package/npm-shrinkwrap.json +904 -412
  129. package/package.json +3 -3
  130. package/schema/firebase-config.json +32 -0
  131. package/templates/init/functions/javascript/package.lint.json +3 -3
  132. package/templates/init/functions/javascript/package.nolint.json +2 -2
  133. package/templates/init/functions/typescript/package.lint.json +7 -7
  134. package/templates/init/functions/typescript/package.nolint.json +3 -3
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getParams = exports.buildOptions = void 0;
3
+ exports.getParams = exports.getExtensionFunctionInfo = exports.buildOptions = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const _ = require("lodash");
6
6
  const path = require("path");
@@ -15,9 +15,9 @@ const emulatorLogger_1 = require("../../emulator/emulatorLogger");
15
15
  const projectUtils_1 = require("../../projectUtils");
16
16
  const types_1 = require("../../emulator/types");
17
17
  async function buildOptions(options) {
18
- const extensionDir = localHelper.findExtensionYaml(process.cwd());
19
- options.extensionDir = extensionDir;
20
- const spec = await specHelper.readExtensionYaml(extensionDir);
18
+ const extDevDir = localHelper.findExtensionYaml(process.cwd());
19
+ options.extDevDir = extDevDir;
20
+ const spec = await specHelper.readExtensionYaml(extDevDir);
21
21
  extensionsHelper.validateSpec(spec);
22
22
  const params = getParams(options, spec);
23
23
  extensionsHelper.validateCommandLineParams(params, spec.params);
@@ -28,13 +28,29 @@ async function buildOptions(options) {
28
28
  checkTestConfig(testConfig, functionResources);
29
29
  }
30
30
  options.config = buildConfig(functionResources, testConfig);
31
- options.extensionEnv = params;
31
+ options.extDevEnv = params;
32
32
  const functionEmuTriggerDefs = functionResources.map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r));
33
- options.extensionTriggers = functionEmuTriggerDefs;
34
- options.extensionNodeVersion = specHelper.getNodeVersion(functionResources);
33
+ options.extDevTriggers = functionEmuTriggerDefs;
34
+ options.extDevNodeVersion = specHelper.getNodeVersion(functionResources);
35
35
  return options;
36
36
  }
37
37
  exports.buildOptions = buildOptions;
38
+ async function getExtensionFunctionInfo(extensionDir, instanceId, params) {
39
+ const spec = await specHelper.readExtensionYaml(extensionDir);
40
+ const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, params);
41
+ const extensionTriggers = functionResources
42
+ .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r))
43
+ .map((trigger) => {
44
+ trigger.name = `ext-${instanceId}-${trigger.name}`;
45
+ return trigger;
46
+ });
47
+ const nodeMajorVersion = specHelper.getNodeVersion(functionResources);
48
+ return {
49
+ extensionTriggers,
50
+ nodeMajorVersion,
51
+ };
52
+ }
53
+ exports.getExtensionFunctionInfo = getExtensionFunctionInfo;
38
54
  function getParams(options, extensionSpec) {
39
55
  const projectId = (0, projectUtils_1.needProjectId)(options);
40
56
  const userParams = paramHelper.readEnvFile(options.testParams);
@@ -120,7 +136,7 @@ function getFunctionSourceDirectory(functionResources) {
120
136
  if (!sourceDirectory) {
121
137
  sourceDirectory = dir;
122
138
  }
123
- else if (sourceDirectory != dir) {
139
+ else if (sourceDirectory !== dir) {
124
140
  throw new error_1.FirebaseError(`Found function resources with different sourceDirectories: '${sourceDirectory}' and '${dir}'. The extensions emulator only supports a single sourceDirectory.`);
125
141
  }
126
142
  }
@@ -2,13 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getNodeVersion = exports.getFunctionProperties = exports.getFunctionResourcesWithParamSubstitution = exports.readFileFromDirectory = exports.readExtensionYaml = void 0;
4
4
  const yaml = require("js-yaml");
5
- const _ = require("lodash");
6
5
  const path = require("path");
7
6
  const fs = require("fs-extra");
8
7
  const error_1 = require("../../error");
9
8
  const extensionsHelper_1 = require("../extensionsHelper");
10
- const emulatorLogger_1 = require("../../emulator/emulatorLogger");
11
- const types_1 = require("../../emulator/types");
9
+ const functionsEmulatorUtils_1 = require("../../emulator/functionsEmulatorUtils");
12
10
  const SPEC_FILE = "extension.yaml";
13
11
  const validFunctionTypes = [
14
12
  "firebaseextensions.v1beta.function",
@@ -62,35 +60,24 @@ function getFunctionProperties(resources) {
62
60
  }
63
61
  exports.getFunctionProperties = getFunctionProperties;
64
62
  function getNodeVersion(resources) {
65
- const functionNamesWithoutRuntime = [];
63
+ const invalidRuntimes = [];
66
64
  const versions = resources.map((r) => {
67
65
  var _a, _b;
68
- if (_.includes(r.type, "function")) {
69
- if ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.runtime) {
70
- return (_b = r.properties) === null || _b === void 0 ? void 0 : _b.runtime;
66
+ if ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.runtime) {
67
+ const runtimeName = (_b = r.properties) === null || _b === void 0 ? void 0 : _b.runtime;
68
+ const runtime = (0, functionsEmulatorUtils_1.parseRuntimeVersion)(runtimeName);
69
+ if (!runtime) {
70
+ invalidRuntimes.push(runtimeName);
71
71
  }
72
72
  else {
73
- functionNamesWithoutRuntime.push(r.name);
73
+ return runtime;
74
74
  }
75
75
  }
76
- return "nodejs8";
77
- });
78
- if (functionNamesWithoutRuntime.length) {
79
- emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).logLabeled("WARN", "extensions", `No 'runtime' property found for the following functions, defaulting to nodejs8: ${functionNamesWithoutRuntime.join(", ")}`);
80
- }
81
- const invalidRuntimes = _.filter(versions, (v) => {
82
- return !_.includes(v, "nodejs");
76
+ return 14;
83
77
  });
84
78
  if (invalidRuntimes.length) {
85
79
  throw new error_1.FirebaseError(`The following runtimes are not supported by the Emulator Suite: ${invalidRuntimes.join(", ")}. \n Only Node runtimes are supported.`);
86
80
  }
87
- if (_.includes(versions, "nodejs10")) {
88
- return "10";
89
- }
90
- if (_.includes(versions, "nodejs6")) {
91
- emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).logLabeled("WARN", "extensions", "Node 6 is deprecated. We recommend upgrading to a newer version.");
92
- return "6";
93
- }
94
- return "8";
81
+ return Math.max(...versions);
95
82
  }
96
83
  exports.getNodeVersion = getNodeVersion;
@@ -1,14 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeFiles = exports.displayExportInfo = exports.setSecretParamsToLatest = exports.parameterizeProject = void 0;
4
- const clc = require("cli-color");
5
- const refs = require("./refs");
6
- const config_1 = require("../config");
3
+ exports.displayExportInfo = exports.setSecretParamsToLatest = exports.parameterizeProject = void 0;
7
4
  const planner_1 = require("../deploy/extensions/planner");
8
5
  const deploymentSummary_1 = require("../deploy/extensions/deploymentSummary");
9
6
  const logger_1 = require("../logger");
10
- const error_1 = require("../error");
11
- const prompt_1 = require("../prompt");
12
7
  const secretManager_1 = require("../gcp/secretManager");
13
8
  const secretsUtils_1 = require("./secretsUtils");
14
9
  function parameterizeProject(projectId, projectNumber, spec) {
@@ -60,48 +55,3 @@ function displaySpecs(specs) {
60
55
  logger_1.logger.info("");
61
56
  }
62
57
  }
63
- function writeExtensionsToFirebaseJson(have, existingConfig) {
64
- const extensions = existingConfig.get("extensions", {});
65
- for (const s of have) {
66
- extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
67
- }
68
- existingConfig.set("extensions", extensions);
69
- logger_1.logger.info("Adding Extensions to " + clc.bold("firebase.json") + "...");
70
- existingConfig.writeProjectFile("firebase.json", existingConfig.src);
71
- }
72
- async function writeEnvFile(spec, existingConfig, force) {
73
- const content = Object.entries(spec.params)
74
- .map((r) => `${r[0]}=${r[1]}`)
75
- .join("\n");
76
- await existingConfig.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
77
- }
78
- async function writeFiles(have, options) {
79
- const existingConfig = config_1.Config.load(options, true);
80
- if (!existingConfig) {
81
- throw new error_1.FirebaseError("Not currently in a Firebase directory. Please run `firebase init` to create a Firebase directory.");
82
- }
83
- if (existingConfig.has("extensions") &&
84
- Object.keys(existingConfig.get("extensions")).length &&
85
- !options.nonInteractive &&
86
- !options.force) {
87
- const currentExtensions = Object.entries(existingConfig.get("extensions"))
88
- .map((i) => `${i[0]}: ${i[1]}`)
89
- .join("\n\t");
90
- const overwrite = await (0, prompt_1.promptOnce)({
91
- type: "list",
92
- message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
93
- choices: [
94
- { name: "Overwrite", value: true },
95
- { name: "Merge", value: false },
96
- ],
97
- });
98
- if (overwrite) {
99
- existingConfig.set("extensions", {});
100
- }
101
- }
102
- writeExtensionsToFirebaseJson(have, existingConfig);
103
- for (const spec of have) {
104
- await writeEnvFile(spec, existingConfig, options.force);
105
- }
106
- }
107
- exports.writeFiles = writeFiles;
@@ -295,7 +295,7 @@ async function listExtensionVersions(ref, filter = "") {
295
295
  exports.listExtensionVersions = listExtensionVersions;
296
296
  async function getPublisherProfile(projectId, publisherId) {
297
297
  const res = await apiClient.get(`/projects/${projectId}/publisherProfile`, {
298
- queryParams: publisherId == undefined
298
+ queryParams: publisherId === undefined
299
299
  ? undefined
300
300
  : {
301
301
  publisherId,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = 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.diagnoseAndFixProject = exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = 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");
@@ -16,6 +16,7 @@ const utils_1 = require("./utils");
16
16
  const functionsConfig_1 = require("../functionsConfig");
17
17
  const resolveSource_1 = require("./resolveSource");
18
18
  const error_1 = require("../error");
19
+ const diagnose_1 = require("./diagnose");
19
20
  const askUserForParam_1 = require("./askUserForParam");
20
21
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
21
22
  const storage_1 = require("../gcp/storage");
@@ -111,7 +112,7 @@ function populateDefaultParams(paramVars, paramSpecs) {
111
112
  const newParams = paramVars;
112
113
  for (const param of paramSpecs) {
113
114
  if (!paramVars[param.param]) {
114
- if (param.default != undefined && param.required) {
115
+ if (param.default !== undefined && param.required) {
115
116
  newParams[param.param] = param.default;
116
117
  }
117
118
  else if (param.required) {
@@ -196,13 +197,13 @@ function validateSpec(spec) {
196
197
  if (param.type && !_.includes(SpecParamType, param.type)) {
197
198
  errors.push(`Invalid type ${param.type} for param${param.param ? ` ${param.param}` : ""}. Valid types are ${_.values(SpecParamType).join(", ")}`);
198
199
  }
199
- if (!param.type || param.type == SpecParamType.STRING) {
200
+ if (!param.type || param.type === SpecParamType.STRING) {
200
201
  if (param.options) {
201
202
  errors.push(`Param${param.param ? ` ${param.param}` : ""} cannot have options because it is type STRING`);
202
203
  }
203
204
  }
204
205
  if (param.type &&
205
- (param.type == SpecParamType.SELECT || param.type == SpecParamType.MULTISELECT)) {
206
+ (param.type === SpecParamType.SELECT || param.type === SpecParamType.MULTISELECT)) {
206
207
  if (param.validationRegex) {
207
208
  errors.push(`Param${param.param ? ` ${param.param}` : ""} cannot have validationRegex because it is type ${param.type}`);
208
209
  }
@@ -210,12 +211,12 @@ function validateSpec(spec) {
210
211
  errors.push(`Param${param.param ? ` ${param.param}` : ""} requires options because it is type ${param.type}`);
211
212
  }
212
213
  for (const opt of param.options || []) {
213
- if (opt.value == undefined) {
214
+ if (opt.value === undefined) {
214
215
  errors.push(`Option for param${param.param ? ` ${param.param}` : ""} is missing required field: value`);
215
216
  }
216
217
  }
217
218
  }
218
- if (param.type && param.type == SpecParamType.SELECTRESOURCE) {
219
+ if (param.type && param.type === SpecParamType.SELECTRESOURCE) {
219
220
  if (!param.resourceType) {
220
221
  errors.push(`Param${param.param ? ` ${param.param}` : ""} must have resourceType because it is type ${param.type}`);
221
222
  }
@@ -267,7 +268,7 @@ async function archiveAndUploadSource(extPath, bucketName) {
267
268
  }
268
269
  async function publishExtensionVersionFromLocalSource(args) {
269
270
  const extensionSpec = await (0, localHelper_1.getLocalExtensionSpec)(args.rootDirectory);
270
- if (extensionSpec.name != args.extensionId) {
271
+ if (extensionSpec.name !== args.extensionId) {
271
272
  throw new error_1.FirebaseError(`Extension ID '${clc.bold(args.extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
272
273
  }
273
274
  const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
@@ -324,7 +325,9 @@ async function publishExtensionVersionFromLocalSource(args) {
324
325
  }
325
326
  catch (err) {
326
327
  uploadSpinner.fail();
327
- throw err;
328
+ throw new error_1.FirebaseError(`Failed to archive and upload extension source, ${err}`, {
329
+ original: err,
330
+ });
328
331
  }
329
332
  const publishSpinner = ora(`Publishing ${clc.bold(ref)}`);
330
333
  let res;
@@ -335,7 +338,7 @@ async function publishExtensionVersionFromLocalSource(args) {
335
338
  }
336
339
  catch (err) {
337
340
  publishSpinner.fail();
338
- if (err.status == 404) {
341
+ if (err.status === 404) {
339
342
  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.`));
340
343
  }
341
344
  throw err;
@@ -359,7 +362,9 @@ async function createSourceFromLocation(projectId, sourceUri) {
359
362
  }
360
363
  catch (err) {
361
364
  uploadSpinner.fail();
362
- throw err;
365
+ throw new error_1.FirebaseError(`Failed to archive and upload extension source, ${err}`, {
366
+ original: err,
367
+ });
363
368
  }
364
369
  }
365
370
  else {
@@ -504,3 +509,11 @@ async function confirm(args) {
504
509
  }
505
510
  }
506
511
  exports.confirm = confirm;
512
+ async function diagnoseAndFixProject(options) {
513
+ const projectId = (0, projectUtils_1.needProjectId)(options);
514
+ const ok = await (0, diagnose_1.diagnose)(projectId);
515
+ if (!ok) {
516
+ throw new error_1.FirebaseError("Unable to proceed until all issues are resolved.");
517
+ }
518
+ }
519
+ exports.diagnoseAndFixProject = diagnoseAndFixProject;
@@ -8,6 +8,7 @@ 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");
11
12
  async function listExtensions(projectId) {
12
13
  const instances = await (0, extensionsApi_1.listInstances)(projectId);
13
14
  if (instances.length < 1) {
@@ -47,6 +48,7 @@ async function listExtensions(projectId) {
47
48
  });
48
49
  });
49
50
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`);
51
+ logger_1.logger.info(table.toString());
50
52
  return formatted;
51
53
  }
52
54
  exports.listExtensions = listExtensions;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeToManifest = void 0;
4
+ const clc = require("cli-color");
5
+ const refs = require("./refs");
6
+ const logger_1 = require("../logger");
7
+ const prompt_1 = require("../prompt");
8
+ async function writeToManifest(specs, config, options) {
9
+ if (config.has("extensions") &&
10
+ Object.keys(config.get("extensions")).length &&
11
+ !options.nonInteractive &&
12
+ !options.force) {
13
+ const currentExtensions = Object.entries(config.get("extensions"))
14
+ .map((i) => `${i[0]}: ${i[1]}`)
15
+ .join("\n\t");
16
+ const overwrite = await (0, prompt_1.promptOnce)({
17
+ type: "list",
18
+ message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
19
+ choices: [
20
+ { name: "Overwrite", value: true },
21
+ { name: "Merge", value: false },
22
+ ],
23
+ });
24
+ if (overwrite) {
25
+ config.set("extensions", {});
26
+ }
27
+ }
28
+ writeExtensionsToFirebaseJson(specs, config);
29
+ await writeEnvFiles(specs, config, options.force);
30
+ }
31
+ exports.writeToManifest = writeToManifest;
32
+ function writeExtensionsToFirebaseJson(specs, config) {
33
+ const extensions = config.get("extensions", {});
34
+ for (const s of specs) {
35
+ extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
36
+ }
37
+ config.set("extensions", extensions);
38
+ logger_1.logger.info("Adding Extensions to " + clc.bold("firebase.json") + "...");
39
+ config.writeProjectFile("firebase.json", config.src);
40
+ }
41
+ async function writeEnvFiles(specs, config, force) {
42
+ for (const spec of specs) {
43
+ const content = Object.entries(spec.params)
44
+ .map((r) => `${r[0]}=${r[1]}`)
45
+ .join("\n");
46
+ await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
47
+ }
48
+ }
@@ -7,19 +7,19 @@ function parseTimeseriesResponse(series) {
7
7
  const ret = [];
8
8
  for (const s of series) {
9
9
  const ref = buildRef(s);
10
- if (ref == undefined) {
10
+ if (ref === undefined) {
11
11
  continue;
12
12
  }
13
13
  let valueToday;
14
14
  let value7dAgo;
15
15
  let value28dAgo;
16
- if (s.points.length >= 28 && s.points[27].value.int64Value != undefined) {
16
+ if (s.points.length >= 28 && s.points[27].value.int64Value !== undefined) {
17
17
  value28dAgo = parseBucket(s.points[27].value.int64Value);
18
18
  }
19
- if (s.points.length >= 7 && s.points[6].value.int64Value != undefined) {
19
+ if (s.points.length >= 7 && s.points[6].value.int64Value !== undefined) {
20
20
  value7dAgo = parseBucket(s.points[6].value.int64Value);
21
21
  }
22
- if (s.points.length >= 1 && s.points[0].value.int64Value != undefined) {
22
+ if (s.points.length >= 1 && s.points[0].value.int64Value !== undefined) {
23
23
  valueToday = parseBucket(s.points[0].value.int64Value);
24
24
  }
25
25
  ret.push({
@@ -49,7 +49,7 @@ async function getParams(args) {
49
49
  const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
50
50
  params = await askUserForParam.ask(args.projectId, args.instanceId, args.paramSpecs, firebaseProjectParams, !!args.reconfiguring);
51
51
  }
52
- track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
52
+ void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
53
53
  return params;
54
54
  }
55
55
  exports.getParams = getParams;
@@ -82,7 +82,7 @@ async function getParamsForUpdate(args) {
82
82
  instanceId: args.instanceId,
83
83
  });
84
84
  }
85
- track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
85
+ void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
86
86
  return params;
87
87
  }
88
88
  exports.getParamsForUpdate = getParamsForUpdate;
@@ -116,10 +116,10 @@ function getParamsFromFile(args) {
116
116
  let envParams;
117
117
  try {
118
118
  envParams = readEnvFile(args.paramsEnvPath);
119
- track("Extension Env File", "Present");
119
+ void track("Extension Env File", "Present");
120
120
  }
121
121
  catch (err) {
122
- track("Extension Env File", "Invalid");
122
+ void track("Extension Env File", "Invalid");
123
123
  throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
124
124
  }
125
125
  const params = (0, extensionsHelper_1.populateDefaultParams)(envParams, args.paramSpecs);
@@ -20,7 +20,7 @@ function parse(refOrName) {
20
20
  exports.parse = parse;
21
21
  function parseRef(ref) {
22
22
  const parts = refRegex.exec(ref);
23
- if (parts && (parts.length == 5 || parts.length == 7)) {
23
+ if (parts && (parts.length === 5 || parts.length === 7)) {
24
24
  const publisherId = parts[1];
25
25
  const extensionId = parts[2];
26
26
  const version = parts[4];
@@ -15,14 +15,14 @@ async function ensureSecretManagerApiEnabled(options) {
15
15
  }
16
16
  exports.ensureSecretManagerApiEnabled = ensureSecretManagerApiEnabled;
17
17
  function usesSecrets(spec) {
18
- return spec.params && !!spec.params.find((p) => p.type == extensionsApi.ParamType.SECRET);
18
+ return spec.params && !!spec.params.find((p) => p.type === extensionsApi.ParamType.SECRET);
19
19
  }
20
20
  exports.usesSecrets = usesSecrets;
21
21
  async function grantFirexServiceAgentSecretAdminRole(secret) {
22
22
  const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId: secret.projectId });
23
23
  const firexSaProjectId = utils.envOverride("FIREBASE_EXTENSIONS_SA_PROJECT_ID", "gcp-sa-firebasemods");
24
24
  const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`;
25
- return secretManagerApi.grantServiceAgentRole(secret, saEmail, "roles/secretmanager.admin");
25
+ return secretManagerApi.ensureServiceAgentRole(secret, [saEmail], "roles/secretmanager.admin");
26
26
  }
27
27
  exports.grantFirexServiceAgentSecretAdminRole = grantFirexServiceAgentSecretAdminRole;
28
28
  async function getManagedSecrets(instance) {
@@ -38,7 +38,7 @@ async function getManagedSecrets(instance) {
38
38
  exports.getManagedSecrets = getManagedSecrets;
39
39
  function getActiveSecrets(spec, params) {
40
40
  return spec.params
41
- .map((p) => (p.type == extensionsApi.ParamType.SECRET ? params[p.param] : ""))
41
+ .map((p) => (p.type === extensionsApi.ParamType.SECRET ? params[p.param] : ""))
42
42
  .filter((pv) => !!pv);
43
43
  }
44
44
  exports.getActiveSecrets = getActiveSecrets;
@@ -50,7 +50,7 @@ function getSecretLabels(instanceId) {
50
50
  exports.getSecretLabels = getSecretLabels;
51
51
  function prettySecretName(secretResourceName) {
52
52
  const nameTokens = secretResourceName.split("/");
53
- if (nameTokens.length != 4 && nameTokens.length != 6) {
53
+ if (nameTokens.length !== 4 && nameTokens.length !== 6) {
54
54
  logger_1.logger.debug(`unable to parse secret secretResourceName: ${secretResourceName}`);
55
55
  return secretResourceName;
56
56
  }
package/lib/functional.js CHANGED
@@ -40,7 +40,7 @@ function reduceFlat(accum, next) {
40
40
  }
41
41
  exports.reduceFlat = reduceFlat;
42
42
  function* zip(left, right) {
43
- if (left.length != right.length) {
43
+ if (left.length !== right.length) {
44
44
  throw new Error("Cannot zip between two lists of differen lengths");
45
45
  }
46
46
  for (let i = 0; i < left.length; i++) {
@@ -1,17 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.hasUserEnvs = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
3
+ exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.hasUserEnvs = exports.parseStrict = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
4
4
  const clc = require("cli-color");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
  const error_1 = require("../error");
8
8
  const logger_1 = require("../logger");
9
- const previews_1 = require("../previews");
10
9
  const utils_1 = require("../utils");
11
10
  const FUNCTIONS_EMULATOR_DOTENV = ".env.local";
11
+ const RESERVED_PREFIXES = ["X_GOOGLE_", "FIREBASE_", "EXT_"];
12
12
  const RESERVED_KEYS = [
13
13
  "FIREBASE_CONFIG",
14
14
  "CLOUD_RUNTIME_CONFIG",
15
+ "EVENTARC_CLOUD_EVENT_SOURCE",
15
16
  "ENTRY_POINT",
16
17
  "GCP_PROJECT",
17
18
  "GCLOUD_PROJECT",
@@ -87,8 +88,8 @@ function validateKey(key) {
87
88
  throw new KeyValidationError(key, `Key ${key} must start with an uppercase ASCII letter or underscore` +
88
89
  ", and then consist of uppercase ASCII letters, digits, and underscores.");
89
90
  }
90
- if (key.startsWith("X_GOOGLE_") || key.startsWith("FIREBASE_")) {
91
- throw new KeyValidationError(key, `Key ${key} starts with a reserved prefix (X_GOOGLE_ or FIREBASE_)`);
91
+ if (RESERVED_PREFIXES.some((prefix) => key.startsWith(prefix))) {
92
+ throw new KeyValidationError(key, `Key ${key} starts with a reserved prefix (${RESERVED_PREFIXES.join(" ")})`);
92
93
  }
93
94
  }
94
95
  exports.validateKey = validateKey;
@@ -117,6 +118,7 @@ function parseStrict(data) {
117
118
  }
118
119
  return envs;
119
120
  }
121
+ exports.parseStrict = parseStrict;
120
122
  function findEnvfiles(functionsSource, projectId, projectAlias, isEmulator) {
121
123
  const files = [".env"];
122
124
  if (isEmulator) {
@@ -139,11 +141,8 @@ function hasUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, })
139
141
  exports.hasUserEnvs = hasUserEnvs;
140
142
  function loadUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
141
143
  var _a;
142
- if (!previews_1.previews.dotenv) {
143
- return {};
144
- }
145
144
  const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
146
- if (envFiles.length == 0) {
145
+ if (envFiles.length === 0) {
147
146
  return {};
148
147
  }
149
148
  if (projectAlias) {
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pruneSecrets = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
4
+ const secretManager_1 = require("../gcp/secretManager");
5
+ const error_1 = require("../error");
6
+ const utils_1 = require("../utils");
7
+ const prompt_1 = require("../prompt");
8
+ const env_1 = require("./env");
9
+ const FIREBASE_MANGED = "firebase-managed";
10
+ function isFirebaseManaged(secret) {
11
+ return Object.keys(secret.labels || []).includes(FIREBASE_MANGED);
12
+ }
13
+ exports.isFirebaseManaged = isFirebaseManaged;
14
+ function labels() {
15
+ return { [FIREBASE_MANGED]: "true" };
16
+ }
17
+ exports.labels = labels;
18
+ function toUpperSnakeCase(key) {
19
+ return key
20
+ .replace("-", "_")
21
+ .replace(".", "_")
22
+ .replace(/([a-z])([A-Z])/g, "$1_$2")
23
+ .toUpperCase();
24
+ }
25
+ async function ensureValidKey(key, options) {
26
+ const transformedKey = toUpperSnakeCase(key);
27
+ if (transformedKey !== key) {
28
+ if (options.force) {
29
+ throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
30
+ }
31
+ (0, utils_1.logWarning)(`By convention, secret key must be in UPPER_SNAKE_CASE.`);
32
+ const confirm = await (0, prompt_1.promptOnce)({
33
+ name: "updateKey",
34
+ type: "confirm",
35
+ default: true,
36
+ message: `Would you like to use ${transformedKey} as key instead?`,
37
+ }, options);
38
+ if (!confirm) {
39
+ throw new error_1.FirebaseError("Secret key must be in UPPER_SNAKE_CASE.");
40
+ }
41
+ }
42
+ try {
43
+ (0, env_1.validateKey)(transformedKey);
44
+ }
45
+ catch (err) {
46
+ throw new error_1.FirebaseError(`Invalid secret key ${transformedKey}`, { children: [err] });
47
+ }
48
+ return transformedKey;
49
+ }
50
+ exports.ensureValidKey = ensureValidKey;
51
+ async function ensureSecret(projectId, name, options) {
52
+ try {
53
+ const secret = await (0, secretManager_1.getSecret)(projectId, name);
54
+ if (!isFirebaseManaged(secret)) {
55
+ if (!options.force) {
56
+ (0, utils_1.logWarning)("Your secret is not managed by Firebase. " +
57
+ "Firebase managed secrets are automatically pruned to reduce your monthly cost for using Secret Manager. ");
58
+ const confirm = await (0, prompt_1.promptOnce)({
59
+ name: "updateLabels",
60
+ type: "confirm",
61
+ default: true,
62
+ message: `Would you like to have your secret ${secret.name} managed by Firebase?`,
63
+ }, options);
64
+ if (confirm) {
65
+ return (0, secretManager_1.patchSecret)(projectId, secret.name, Object.assign(Object.assign({}, secret.labels), labels()));
66
+ }
67
+ }
68
+ }
69
+ return secret;
70
+ }
71
+ catch (err) {
72
+ if (err.status !== 404) {
73
+ throw err;
74
+ }
75
+ }
76
+ return await (0, secretManager_1.createSecret)(projectId, name, labels());
77
+ }
78
+ exports.ensureSecret = ensureSecret;
79
+ function of(endpoints) {
80
+ return endpoints.reduce((envs, endpoint) => [...envs, ...(endpoint.secretEnvironmentVariables || [])], []);
81
+ }
82
+ exports.of = of;
83
+ async function pruneSecrets(projectInfo, endpoints) {
84
+ const { projectId, projectNumber } = projectInfo;
85
+ const pruneKey = (name, version) => `${name}@${version}`;
86
+ const prunedSecrets = new Set();
87
+ const haveSecrets = await (0, secretManager_1.listSecrets)(projectId, `labels.${FIREBASE_MANGED}=true`);
88
+ for (const secret of haveSecrets) {
89
+ const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `state: ENABLED`);
90
+ for (const version of versions) {
91
+ prunedSecrets.add(pruneKey(secret.name, version.versionId));
92
+ }
93
+ }
94
+ const sevs = of(endpoints).filter((sev) => sev.projectId === projectId || sev.projectId === projectNumber);
95
+ for (const sev of sevs) {
96
+ let name = sev.secret;
97
+ if (name.includes("/")) {
98
+ const secret = (0, secretManager_1.parseSecretResourceName)(name);
99
+ name = secret.name;
100
+ }
101
+ let version = sev.version;
102
+ if (version === "latest") {
103
+ const resolved = await (0, secretManager_1.getSecretVersion)(projectId, name, version);
104
+ version = resolved.versionId;
105
+ }
106
+ prunedSecrets.delete(pruneKey(name, version));
107
+ }
108
+ return Array.from(prunedSecrets)
109
+ .map((key) => key.split("@"))
110
+ .map(([secret, version]) => ({ projectId, version, secret, key: secret }));
111
+ }
112
+ exports.pruneSecrets = pruneSecrets;