firebase-tools 10.6.0 → 10.7.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 (83) hide show
  1. package/lib/command.js +4 -4
  2. package/lib/commands/deploy.js +1 -1
  3. package/lib/commands/emulators-start.js +7 -2
  4. package/lib/commands/ext-configure.js +15 -5
  5. package/lib/commands/ext-export.js +6 -5
  6. package/lib/commands/ext-install.js +28 -44
  7. package/lib/commands/ext-update.js +9 -1
  8. package/lib/commands/functions-delete.js +2 -5
  9. package/lib/commands/hosting-channel-deploy.js +2 -2
  10. package/lib/deploy/database/deploy.js +4 -0
  11. package/lib/deploy/database/index.js +1 -0
  12. package/lib/deploy/extensions/deploy.js +4 -4
  13. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  14. package/lib/deploy/extensions/planner.js +36 -9
  15. package/lib/deploy/extensions/prepare.js +1 -1
  16. package/lib/deploy/extensions/secrets.js +2 -2
  17. package/lib/deploy/extensions/tasks.js +60 -21
  18. package/lib/deploy/functions/backend.js +17 -2
  19. package/lib/deploy/functions/build.js +162 -0
  20. package/lib/deploy/functions/checkIam.js +6 -5
  21. package/lib/deploy/functions/deploy.js +14 -15
  22. package/lib/deploy/functions/ensure.js +4 -4
  23. package/lib/deploy/functions/functionsDeployHelper.js +54 -23
  24. package/lib/deploy/functions/prepare.js +85 -42
  25. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  26. package/lib/deploy/functions/pricing.js +6 -3
  27. package/lib/deploy/functions/prompts.js +1 -7
  28. package/lib/deploy/functions/release/fabricator.js +43 -2
  29. package/lib/deploy/functions/release/index.js +10 -6
  30. package/lib/deploy/functions/release/planner.js +9 -6
  31. package/lib/deploy/functions/release/reporter.js +14 -11
  32. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  33. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +50 -3
  34. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  35. package/lib/deploy/functions/runtimes/node/parseTriggers.js +24 -5
  36. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  37. package/lib/deploy/functions/services/auth.js +95 -0
  38. package/lib/deploy/functions/services/index.js +41 -21
  39. package/lib/deploy/functions/validate.js +8 -5
  40. package/lib/deploy/hosting/args.js +2 -0
  41. package/lib/deploy/hosting/convertConfig.js +37 -8
  42. package/lib/deploy/hosting/deploy.js +3 -3
  43. package/lib/deploy/hosting/prepare.js +2 -2
  44. package/lib/deploy/hosting/release.js +6 -2
  45. package/lib/deploy/index.js +82 -93
  46. package/lib/deploy/remoteconfig/deploy.js +4 -0
  47. package/lib/deploy/remoteconfig/index.js +3 -1
  48. package/lib/emulator/auth/operations.js +5 -0
  49. package/lib/emulator/auth/utils.js +3 -25
  50. package/lib/emulator/controller.js +5 -5
  51. package/lib/emulator/downloadableEmulators.js +39 -23
  52. package/lib/emulator/extensions/validation.js +2 -2
  53. package/lib/emulator/extensionsEmulator.js +85 -21
  54. package/lib/emulator/functionsEmulator.js +79 -7
  55. package/lib/emulator/functionsEmulatorShared.js +20 -1
  56. package/lib/emulator/registry.js +34 -12
  57. package/lib/emulator/storage/apis/firebase.js +7 -2
  58. package/lib/emulator/storage/apis/gcloud.js +6 -3
  59. package/lib/emulator/storage/files.js +9 -1
  60. package/lib/ensureApiEnabled.js +8 -4
  61. package/lib/extensions/changelog.js +1 -1
  62. package/lib/extensions/emulator/optionsHelper.js +4 -3
  63. package/lib/extensions/emulator/specHelper.js +7 -1
  64. package/lib/extensions/extensionsHelper.js +30 -24
  65. package/lib/extensions/manifest.js +27 -7
  66. package/lib/extensions/paramHelper.js +5 -5
  67. package/lib/extensions/provisioningHelper.js +2 -2
  68. package/lib/extensions/warnings.js +3 -3
  69. package/lib/functions/events/index.js +7 -0
  70. package/lib/functions/events/v1.js +6 -0
  71. package/lib/functions/projectConfig.js +24 -3
  72. package/lib/gcp/cloudfunctions.js +31 -5
  73. package/lib/gcp/cloudfunctionsv2.js +27 -2
  74. package/lib/gcp/identityPlatform.js +44 -0
  75. package/lib/gcp/secretManager.js +1 -1
  76. package/lib/metaprogramming.js +2 -0
  77. package/lib/previews.js +1 -1
  78. package/lib/serve/hosting.js +25 -12
  79. package/lib/serve/index.js +6 -0
  80. package/lib/track.js +15 -21
  81. package/npm-shrinkwrap.json +44 -2
  82. package/package.json +4 -1
  83. package/schema/firebase-config.json +6 -0
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enableApiURI = exports.ensure = exports.check = exports.POLL_SETTINGS = void 0;
4
4
  const cli_color_1 = require("cli-color");
5
- const track = require("./track");
5
+ const track_1 = require("./track");
6
6
  const api_1 = require("./api");
7
7
  const apiv2_1 = require("./apiv2");
8
8
  const utils = require("./utils");
@@ -16,7 +16,9 @@ const apiClient = new apiv2_1.Client({
16
16
  apiVersion: "v1",
17
17
  });
18
18
  async function check(projectId, apiName, prefix, silent = false) {
19
- const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`);
19
+ const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`, {
20
+ skipLog: { resBody: true },
21
+ });
20
22
  const isEnabled = res.body.state === "ENABLED";
21
23
  if (isEnabled && !silent) {
22
24
  utils.logLabeledSuccess(prefix, `required API ${(0, cli_color_1.bold)(apiName)} is enabled`);
@@ -26,7 +28,9 @@ async function check(projectId, apiName, prefix, silent = false) {
26
28
  exports.check = check;
27
29
  async function enable(projectId, apiName) {
28
30
  try {
29
- await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`);
31
+ await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, {
32
+ skipLog: { resBody: true },
33
+ });
30
34
  }
31
35
  catch (err) {
32
36
  if ((0, error_1.isBillingError)(err)) {
@@ -46,7 +50,7 @@ async function pollCheckEnabled(projectId, apiName, prefix, silent, enablementRe
46
50
  });
47
51
  const isEnabled = await check(projectId, apiName, prefix, silent);
48
52
  if (isEnabled) {
49
- void track("api_enabled", apiName);
53
+ void (0, track_1.track)("api_enabled", apiName);
50
54
  return;
51
55
  }
52
56
  if (!silent) {
@@ -16,7 +16,7 @@ marked.setOptions({
16
16
  renderer: new TerminalRenderer(),
17
17
  });
18
18
  const EXTENSIONS_CHANGELOG = "CHANGELOG.md";
19
- const VERSION_LINE_REGEX = /##.*(\d+\.\d+\.\d+).*/;
19
+ const VERSION_LINE_REGEX = /##.*(\d+\.\d+\.\d+(?:-((\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?).*/;
20
20
  async function getReleaseNotesForUpdate(args) {
21
21
  const releaseNotes = {};
22
22
  const filter = `id<="${args.toVersion}" AND id>"${args.fromVersion}"`;
@@ -9,6 +9,7 @@ const localHelper = require("../localHelper");
9
9
  const triggerHelper = require("./triggerHelper");
10
10
  const extensionsApi_1 = require("../extensionsApi");
11
11
  const extensionsHelper = require("../extensionsHelper");
12
+ const planner = require("../../deploy/extensions/planner");
12
13
  const config_1 = require("../../config");
13
14
  const error_1 = require("../../error");
14
15
  const emulatorLogger_1 = require("../../emulator/emulatorLogger");
@@ -35,13 +36,13 @@ async function buildOptions(options) {
35
36
  return options;
36
37
  }
37
38
  exports.buildOptions = buildOptions;
38
- async function getExtensionFunctionInfo(extensionDir, instanceId, paramValues) {
39
- const spec = await specHelper.readExtensionYaml(extensionDir);
39
+ async function getExtensionFunctionInfo(instance, paramValues) {
40
+ const spec = await planner.getExtensionSpec(instance);
40
41
  const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues);
41
42
  const extensionTriggers = functionResources
42
43
  .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r))
43
44
  .map((trigger) => {
44
- trigger.name = `ext-${instanceId}-${trigger.name}`;
45
+ trigger.name = `ext-${instance.instanceId}-${trigger.name}`;
45
46
  return trigger;
46
47
  });
47
48
  const nodeMajorVersion = specHelper.getNodeVersion(functionResources);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNodeVersion = exports.getFunctionProperties = exports.getFunctionResourcesWithParamSubstitution = exports.readFileFromDirectory = exports.readExtensionYaml = void 0;
3
+ exports.getNodeVersion = exports.getFunctionProperties = exports.getFunctionResourcesWithParamSubstitution = exports.readFileFromDirectory = exports.readPostinstall = exports.readExtensionYaml = void 0;
4
4
  const yaml = require("js-yaml");
5
5
  const path = require("path");
6
6
  const fs = require("fs-extra");
@@ -8,6 +8,7 @@ const error_1 = require("../../error");
8
8
  const extensionsHelper_1 = require("../extensionsHelper");
9
9
  const functionsEmulatorUtils_1 = require("../../emulator/functionsEmulatorUtils");
10
10
  const SPEC_FILE = "extension.yaml";
11
+ const POSTINSTALL_FILE = "POSTINSTALL.md";
11
12
  const validFunctionTypes = [
12
13
  "firebaseextensions.v1beta.function",
13
14
  "firebaseextensions.v1beta.scheduledFunction",
@@ -29,6 +30,11 @@ async function readExtensionYaml(directory) {
29
30
  return wrappedSafeLoad(source);
30
31
  }
31
32
  exports.readExtensionYaml = readExtensionYaml;
33
+ async function readPostinstall(directory) {
34
+ const content = await readFileFromDirectory(directory, POSTINSTALL_FILE);
35
+ return content.source;
36
+ }
37
+ exports.readPostinstall = readPostinstall;
32
38
  function readFileFromDirectory(directory, file) {
33
39
  return new Promise((resolve, reject) => {
34
40
  fs.readFile(path.resolve(directory, file), "utf8", (err, data) => {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
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;
3
+ exports.canonicalizeRefInput = 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");
@@ -305,7 +305,7 @@ async function publishExtensionVersionFromLocalSource(args) {
305
305
  "Please create one and add an entry for this version. " +
306
306
  marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
307
307
  }
308
- if (!notes && extension) {
308
+ if (!notes && !semver.prerelease(extensionSpec.version) && extension) {
309
309
  throw new error_1.FirebaseError(`No entry for version ${extensionSpec.version} found in CHANGELOG.md. ` +
310
310
  "Please add one so users know what has changed in this version. " +
311
311
  marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
@@ -363,32 +363,26 @@ async function publishExtensionVersionFromLocalSource(args) {
363
363
  }
364
364
  exports.publishExtensionVersionFromLocalSource = publishExtensionVersionFromLocalSource;
365
365
  async function createSourceFromLocation(projectId, sourceUri) {
366
+ const extensionRoot = "/";
366
367
  let packageUri;
367
- let extensionRoot;
368
368
  let objectPath = "";
369
- if (!sourceUri.startsWith("https:")) {
370
- const uploadSpinner = ora(" Archiving and uploading extension source code");
371
- try {
372
- uploadSpinner.start();
373
- objectPath = await archiveAndUploadSource(sourceUri, exports.EXTENSIONS_BUCKET_NAME);
374
- uploadSpinner.succeed(" Uploaded extension source code");
375
- packageUri = api_1.storageOrigin + objectPath + "?alt=media";
376
- extensionRoot = "/";
377
- }
378
- catch (err) {
379
- uploadSpinner.fail();
380
- throw new error_1.FirebaseError(`Failed to archive and upload extension source, ${err}`, {
381
- original: err,
382
- });
383
- }
369
+ const spinner = ora(" Archiving and uploading extension source code");
370
+ try {
371
+ spinner.start();
372
+ objectPath = await archiveAndUploadSource(sourceUri, exports.EXTENSIONS_BUCKET_NAME);
373
+ spinner.succeed(" Uploaded extension source code");
374
+ packageUri = api_1.storageOrigin + objectPath + "?alt=media";
375
+ const res = await (0, extensionsApi_1.createSource)(projectId, packageUri, extensionRoot);
376
+ logger_1.logger.debug("Created new Extension Source %s", res.name);
377
+ await deleteUploadedSource(objectPath);
378
+ return res;
384
379
  }
385
- else {
386
- [packageUri, extensionRoot] = sourceUri.split("#");
380
+ catch (err) {
381
+ spinner.fail();
382
+ throw new error_1.FirebaseError(`Failed to archive and upload extension source from ${sourceUri}, ${err}`, {
383
+ original: err,
384
+ });
387
385
  }
388
- const res = await (0, extensionsApi_1.createSource)(projectId, packageUri, extensionRoot);
389
- logger_1.logger.debug("Created new Extension Source %s", res.name);
390
- await deleteUploadedSource(objectPath);
391
- return res;
392
386
  }
393
387
  exports.createSourceFromLocation = createSourceFromLocation;
394
388
  async function deleteUploadedSource(objectPath) {
@@ -535,3 +529,15 @@ async function diagnoseAndFixProject(options) {
535
529
  }
536
530
  }
537
531
  exports.diagnoseAndFixProject = diagnoseAndFixProject;
532
+ function canonicalizeRefInput(extensionName) {
533
+ if (extensionName.split("/").length < 2) {
534
+ const [extensionID, version] = extensionName.split("@");
535
+ extensionName = `firebase/${extensionID}@${version || "latest"}`;
536
+ }
537
+ const ref = refs.parse(extensionName);
538
+ if (!ref.version) {
539
+ extensionName = `${extensionName}@latest`;
540
+ }
541
+ return extensionName;
542
+ }
543
+ exports.canonicalizeRefInput = canonicalizeRefInput;
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.showPreviewWarning = exports.showDeprecationWarning = exports.readInstanceParam = exports.getInstanceRef = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeLocalSecrets = exports.writeToManifest = exports.ENV_DIRECTORY = void 0;
3
+ exports.showPreviewWarning = exports.showDeprecationWarning = exports.readInstanceParam = exports.getInstanceRef = exports.getInstanceTarget = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeLocalSecrets = exports.writeToManifest = exports.ENV_DIRECTORY = void 0;
4
4
  const clc = require("cli-color");
5
5
  const path = require("path");
6
6
  const refs = require("./refs");
7
7
  const config_1 = require("../config");
8
+ const planner_1 = require("../deploy/extensions/planner");
8
9
  const logger_1 = require("../logger");
9
10
  const prompt_1 = require("../prompt");
10
11
  const paramHelper_1 = require("./paramHelper");
@@ -42,11 +43,12 @@ async function writeToManifest(specs, config, options, allowOverwrite = false) {
42
43
  exports.writeToManifest = writeToManifest;
43
44
  async function writeLocalSecrets(specs, config, force) {
44
45
  for (const spec of specs) {
45
- if (!spec.paramSpecs) {
46
+ const extensionSpec = await (0, planner_1.getExtensionSpec)(spec);
47
+ if (!extensionSpec.params) {
46
48
  continue;
47
49
  }
48
50
  const writeBuffer = {};
49
- const locallyOverridenSecretParams = spec.paramSpecs.filter((p) => p.type === extensionsApi_1.ParamType.SECRET && spec.params[p.param].local);
51
+ const locallyOverridenSecretParams = extensionSpec.params.filter((p) => p.type === extensionsApi_1.ParamType.SECRET && spec.params[p.param].local);
50
52
  for (const paramSpec of locallyOverridenSecretParams) {
51
53
  const key = paramSpec.param;
52
54
  const localValue = spec.params[key].local;
@@ -97,18 +99,35 @@ function instanceExists(instanceId, config) {
97
99
  return !!config.get("extensions", {})[instanceId];
98
100
  }
99
101
  exports.instanceExists = instanceExists;
100
- function getInstanceRef(instanceId, config) {
102
+ function getInstanceTarget(instanceId, config) {
101
103
  if (!instanceExists(instanceId, config)) {
102
104
  throw new error_1.FirebaseError(`Could not find extension instance ${instanceId} in firebase.json`);
103
105
  }
104
- const ref = config.get("extensions", {})[instanceId];
105
- return refs.parse(ref);
106
+ return config.get("extensions", {})[instanceId];
107
+ }
108
+ exports.getInstanceTarget = getInstanceTarget;
109
+ function getInstanceRef(instanceId, config) {
110
+ const source = getInstanceTarget(instanceId, config);
111
+ if ((0, extensionsHelper_1.isLocalPath)(source)) {
112
+ throw new error_1.FirebaseError(`Extension instance ${instanceId} doesn't have a ref because it is from a local source`);
113
+ }
114
+ return refs.parse(source);
106
115
  }
107
116
  exports.getInstanceRef = getInstanceRef;
108
117
  function writeExtensionsToFirebaseJson(specs, config) {
109
118
  const extensions = config.get("extensions", {});
110
119
  for (const s of specs) {
111
- extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
120
+ let target;
121
+ if (s.ref) {
122
+ target = refs.toExtensionVersionRef(s.ref);
123
+ }
124
+ else if (s.localPath) {
125
+ target = s.localPath;
126
+ }
127
+ else {
128
+ throw new error_1.FirebaseError(`Unable to resolve ManifestInstanceSpec, make sure you provide either extension ref or a local path to extension source code`);
129
+ }
130
+ extensions[s.instanceId] = target;
112
131
  }
113
132
  config.set("extensions", extensions);
114
133
  config.writeProjectFile("firebase.json", config.src);
@@ -117,6 +136,7 @@ function writeExtensionsToFirebaseJson(specs, config) {
117
136
  async function writeEnvFiles(specs, config, force) {
118
137
  for (const spec of specs) {
119
138
  const content = Object.entries(spec.params)
139
+ .filter((r) => r[1].baseValue !== "")
120
140
  .sort((a, b) => {
121
141
  return a[0].localeCompare(b[0]);
122
142
  })
@@ -8,7 +8,7 @@ const error_1 = require("../error");
8
8
  const logger_1 = require("../logger");
9
9
  const extensionsHelper_1 = require("./extensionsHelper");
10
10
  const askUserForParam = require("./askUserForParam");
11
- const track = require("../track");
11
+ const track_1 = require("../track");
12
12
  const env = require("../functions/env");
13
13
  const utils_1 = require("../utils");
14
14
  function getBaseParamBindings(params) {
@@ -73,7 +73,7 @@ async function getParams(args) {
73
73
  });
74
74
  }
75
75
  const paramNames = Object.keys(params);
76
- void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
76
+ void (0, track_1.track)("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
77
77
  return params;
78
78
  }
79
79
  exports.getParams = getParams;
@@ -106,7 +106,7 @@ async function getParamsForUpdate(args) {
106
106
  });
107
107
  }
108
108
  const paramNames = Object.keys(params);
109
- void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
109
+ void (0, track_1.track)("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
110
110
  return params;
111
111
  }
112
112
  exports.getParamsForUpdate = getParamsForUpdate;
@@ -150,10 +150,10 @@ function getParamsFromFile(args) {
150
150
  let envParams;
151
151
  try {
152
152
  envParams = readEnvFile(args.paramsEnvPath);
153
- void track("Extension Env File", "Present");
153
+ void (0, track_1.track)("Extension Env File", "Present");
154
154
  }
155
155
  catch (err) {
156
- void track("Extension Env File", "Invalid");
156
+ void (0, track_1.track)("Extension Env File", "Invalid");
157
157
  throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
158
158
  }
159
159
  const params = (0, extensionsHelper_1.populateDefaultParams)(envParams, args.paramSpecs);
@@ -19,8 +19,8 @@ async function checkProductsProvisioned(projectId, spec) {
19
19
  exports.checkProductsProvisioned = checkProductsProvisioned;
20
20
  async function bulkCheckProductsProvisioned(projectId, instanceSpecs) {
21
21
  const usedProducts = await Promise.all(instanceSpecs.map(async (i) => {
22
- const extensionVersion = await (0, planner_1.getExtensionVersion)(i);
23
- return getUsedProducts(extensionVersion.spec);
22
+ const extensionSpec = await (0, planner_1.getExtensionSpec)(i);
23
+ return getUsedProducts(extensionSpec);
24
24
  }));
25
25
  await checkProducts(projectId, [...(0, functional_1.flattenArray)(usedProducts)]);
26
26
  }
@@ -49,11 +49,11 @@ const toListEntry = (i) => {
49
49
  };
50
50
  async function displayWarningsForDeploy(instancesToCreate) {
51
51
  const trustedPublishers = await (0, resolveSource_1.getTrustedPublishers)();
52
- for (const i of instancesToCreate) {
52
+ const publishedExtensionInstances = instancesToCreate.filter((i) => i.ref);
53
+ for (const i of publishedExtensionInstances) {
53
54
  await (0, planner_1.getExtension)(i);
54
- await (0, planner_1.getExtensionVersion)(i);
55
55
  }
56
- const [eapExtensions, nonEapExtensions] = (0, functional_1.partition)(instancesToCreate, (i) => { var _a, _b; return !trustedPublishers.includes((_b = (_a = i.ref) === null || _a === void 0 ? void 0 : _a.publisherId) !== null && _b !== void 0 ? _b : ""); });
56
+ const [eapExtensions, nonEapExtensions] = (0, functional_1.partition)(publishedExtensionInstances, (i) => { var _a, _b; return !trustedPublishers.includes((_b = (_a = i.ref) === null || _a === void 0 ? void 0 : _a.publisherId) !== null && _b !== void 0 ? _b : ""); });
57
57
  const experimental = nonEapExtensions.filter((i) => i.extension.registryLaunchStage === extensionsApi_1.RegistryLaunchStage.EXPERIMENTAL);
58
58
  if (experimental.length) {
59
59
  const humanReadableList = experimental.map((i) => `\t${(0, deploymentSummary_1.humanReadable)(i)}`).join("\n");
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.v2 = exports.v1 = void 0;
4
+ const v1 = require("./v1");
5
+ exports.v1 = v1;
6
+ const v2 = require("./v2");
7
+ exports.v2 = v2;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUTH_BLOCKING_EVENTS = exports.BEFORE_SIGN_IN_EVENT = exports.BEFORE_CREATE_EVENT = void 0;
4
+ exports.BEFORE_CREATE_EVENT = "providers/cloud.auth/eventTypes/user.beforeCreate";
5
+ exports.BEFORE_SIGN_IN_EVENT = "providers/cloud.auth/eventTypes/user.beforeSignIn";
6
+ exports.AUTH_BLOCKING_EVENTS = [exports.BEFORE_CREATE_EVENT, exports.BEFORE_SIGN_IN_EVENT];
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normalizeAndValidate = exports.validate = exports.normalize = void 0;
3
+ exports.normalizeAndValidate = exports.validate = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
4
4
  const error_1 = require("../error");
5
+ exports.DEFAULT_CODEBASE = "default";
5
6
  function normalize(config) {
6
7
  if (!config) {
7
8
  throw new error_1.FirebaseError("No valid functions configuration detected in firebase.json");
@@ -19,13 +20,33 @@ function validateSingle(config) {
19
20
  if (!config.source) {
20
21
  throw new error_1.FirebaseError("functions.source must be specified");
21
22
  }
22
- return Object.assign(Object.assign({}, config), { source: config.source });
23
+ if (!config.codebase) {
24
+ config.codebase = exports.DEFAULT_CODEBASE;
25
+ }
26
+ if (config.codebase.length > 63 || !/^[a-z0-9_-]+$/.test(config.codebase)) {
27
+ throw new error_1.FirebaseError("Invalid codebase name. Codebase must be less than 63 characters and " +
28
+ "can contain only lowercase letters, numeric characters, underscores, and dashes.");
29
+ }
30
+ return Object.assign(Object.assign({}, config), { source: config.source, codebase: config.codebase });
31
+ }
32
+ function assertUnique(config, property) {
33
+ const values = new Set();
34
+ for (const single of config) {
35
+ const value = single[property];
36
+ if (values.has(value)) {
37
+ throw new error_1.FirebaseError(`functions.${property} must be unique but '${value}' was used more than once.`);
38
+ }
39
+ values.add(value);
40
+ }
23
41
  }
24
42
  function validate(config) {
25
43
  if (config.length > 1) {
26
44
  throw new error_1.FirebaseError("More than one functions.source detected in firebase.json.");
27
45
  }
28
- return [validateSingle(config[0])];
46
+ const validated = validateSingle(config[0]);
47
+ assertUnique([validated], "source");
48
+ assertUnique([validated], "codebase");
49
+ return [validated];
29
50
  }
30
51
  exports.validate = validate;
31
52
  function normalizeAndValidate(config) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
3
+ exports.functionFromEndpoint = exports.endpointFromFunction = exports.listAllFunctions = exports.listFunctions = exports.deleteFunction = exports.updateFunction = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.createFunction = exports.generateUploadUrl = exports.BLOCKING_LABEL = exports.CODEBASE_LABEL = exports.API_VERSION = void 0;
4
4
  const clc = require("cli-color");
5
5
  const error_1 = require("../error");
6
6
  const logger_1 = require("../logger");
@@ -9,10 +9,21 @@ const backend = require("../deploy/functions/backend");
9
9
  const utils = require("../utils");
10
10
  const proto = require("./proto");
11
11
  const runtimes = require("../deploy/functions/runtimes");
12
+ const projectConfig = require("../functions/projectConfig");
12
13
  const apiv2_1 = require("../apiv2");
13
14
  const api_1 = require("../api");
14
15
  exports.API_VERSION = "v1";
16
+ exports.CODEBASE_LABEL = "firebase-functions-codebase";
15
17
  const client = new apiv2_1.Client({ urlPrefix: api_1.functionsOrigin, apiVersion: exports.API_VERSION });
18
+ exports.BLOCKING_LABEL = "deployment-blocking";
19
+ const BLOCKING_LABEL_KEY_TO_EVENT = {
20
+ "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate",
21
+ "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn",
22
+ };
23
+ const BLOCKING_EVENT_TO_LABEL_KEY = {
24
+ "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create",
25
+ "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in",
26
+ };
16
27
  function validateFunction(func) {
17
28
  proto.assertOneOf("Cloud Function", func, "sourceCode", "sourceArchiveUrl", "sourceRepository", "sourceUploadUrl");
18
29
  proto.assertOneOf("Cloud Function", func, "trigger", "httpsTrigger", "eventTrigger");
@@ -202,7 +213,7 @@ async function listAllFunctions(projectId) {
202
213
  }
203
214
  exports.listAllFunctions = listAllFunctions;
204
215
  function endpointFromFunction(gcfFunction) {
205
- var _a, _b, _c, _d, _e;
216
+ var _a, _b, _c, _d, _e, _f, _g;
206
217
  const [, project, , region, , id] = gcfFunction.name.split("/");
207
218
  let trigger;
208
219
  let uri;
@@ -223,20 +234,29 @@ function endpointFromFunction(gcfFunction) {
223
234
  callableTrigger: {},
224
235
  };
225
236
  }
237
+ else if ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[exports.BLOCKING_LABEL]) {
238
+ trigger = {
239
+ blockingTrigger: {
240
+ eventType: BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[exports.BLOCKING_LABEL]],
241
+ },
242
+ };
243
+ }
226
244
  else if (gcfFunction.httpsTrigger) {
227
245
  trigger = { httpsTrigger: {} };
228
- uri = gcfFunction.httpsTrigger.url;
229
- securityLevel = gcfFunction.httpsTrigger.securityLevel;
230
246
  }
231
247
  else {
232
248
  trigger = {
233
249
  eventTrigger: {
234
250
  eventType: gcfFunction.eventTrigger.eventType,
235
251
  eventFilters: { resource: gcfFunction.eventTrigger.resource },
236
- retry: !!((_e = gcfFunction.eventTrigger.failurePolicy) === null || _e === void 0 ? void 0 : _e.retry),
252
+ retry: !!((_f = gcfFunction.eventTrigger.failurePolicy) === null || _f === void 0 ? void 0 : _f.retry),
237
253
  },
238
254
  };
239
255
  }
256
+ if (gcfFunction.httpsTrigger) {
257
+ uri = gcfFunction.httpsTrigger.url;
258
+ securityLevel = gcfFunction.httpsTrigger.securityLevel;
259
+ }
240
260
  if (!runtimes.isValidRuntime(gcfFunction.runtime)) {
241
261
  logger_1.logger.debug("GCFv1 function has a deprecated runtime:", JSON.stringify(gcfFunction, null, 2));
242
262
  }
@@ -255,6 +275,7 @@ function endpointFromFunction(gcfFunction) {
255
275
  endpoint.vpc = { connector: gcfFunction.vpcConnector };
256
276
  proto.renameIfPresent(endpoint.vpc, gcfFunction, "egressSettings", "vpcConnectorEgressSettings");
257
277
  }
278
+ endpoint.codebase = ((_g = gcfFunction.labels) === null || _g === void 0 ? void 0 : _g[exports.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
258
279
  return endpoint;
259
280
  }
260
281
  exports.endpointFromFunction = endpointFromFunction;
@@ -294,6 +315,10 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
294
315
  gcfFunction.httpsTrigger = {};
295
316
  gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-taskqueue": "true" });
296
317
  }
318
+ else if (backend.isBlockingTriggered(endpoint)) {
319
+ gcfFunction.httpsTrigger = {};
320
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.BLOCKING_LABEL]: BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
321
+ }
297
322
  else {
298
323
  gcfFunction.httpsTrigger = {};
299
324
  if (backend.isCallableTriggered(endpoint)) {
@@ -309,6 +334,7 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
309
334
  proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector");
310
335
  proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
311
336
  }
337
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.CODEBASE_LABEL]: endpoint.codebase || projectConfig.DEFAULT_CODEBASE });
312
338
  return gcfFunction;
313
339
  }
314
340
  exports.functionFromEndpoint = functionFromEndpoint;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.megabytes = exports.API_VERSION = void 0;
3
+ exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.megabytes = exports.BLOCKING_LABEL = exports.CODEBASE_LABEL = exports.API_VERSION = void 0;
4
4
  const clc = require("cli-color");
5
5
  const apiv2_1 = require("../apiv2");
6
6
  const error_1 = require("../error");
@@ -11,12 +11,23 @@ const backend = require("../deploy/functions/backend");
11
11
  const runtimes = require("../deploy/functions/runtimes");
12
12
  const proto = require("./proto");
13
13
  const utils = require("../utils");
14
+ const projectConfig = require("../functions/projectConfig");
14
15
  exports.API_VERSION = "v2alpha";
16
+ exports.CODEBASE_LABEL = "firebase-functions-codebase";
15
17
  const client = new apiv2_1.Client({
16
18
  urlPrefix: api_1.functionsV2Origin,
17
19
  auth: true,
18
20
  apiVersion: exports.API_VERSION,
19
21
  });
22
+ exports.BLOCKING_LABEL = "deployment-blocking";
23
+ const BLOCKING_LABEL_KEY_TO_EVENT = {
24
+ "before-create": "providers/cloud.auth/eventTypes/user.beforeCreate",
25
+ "before-sign-in": "providers/cloud.auth/eventTypes/user.beforeSignIn",
26
+ };
27
+ const BLOCKING_EVENT_TO_LABEL_KEY = {
28
+ "providers/cloud.auth/eventTypes/user.beforeCreate": "before-create",
29
+ "providers/cloud.auth/eventTypes/user.beforeSignIn": "before-sign-in",
30
+ };
20
31
  const BYTES_PER_UNIT = {
21
32
  "": 1,
22
33
  k: 1e3,
@@ -194,6 +205,7 @@ function functionFromEndpoint(endpoint, source) {
194
205
  }
195
206
  }
196
207
  proto.renameIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "triggerRegion", "region");
208
+ proto.copyIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "channel");
197
209
  if (endpoint.eventTrigger.retry) {
198
210
  logger_1.logger.warn("Cannot set a retry policy on Cloud Function", endpoint.id);
199
211
  }
@@ -208,11 +220,15 @@ function functionFromEndpoint(endpoint, source) {
208
220
  else if (backend.isCallableTriggered(endpoint)) {
209
221
  gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-callable": "true" });
210
222
  }
223
+ else if (backend.isBlockingTriggered(endpoint)) {
224
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.BLOCKING_LABEL]: BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
225
+ }
226
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [exports.CODEBASE_LABEL]: endpoint.codebase || projectConfig.DEFAULT_CODEBASE });
211
227
  return gcfFunction;
212
228
  }
213
229
  exports.functionFromEndpoint = functionFromEndpoint;
214
230
  function endpointFromFunction(gcfFunction) {
215
- var _a, _b, _c;
231
+ var _a, _b, _c, _d, _e;
216
232
  const [, project, , region, , id] = gcfFunction.name.split("/");
217
233
  let trigger;
218
234
  if (((_a = gcfFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) === "true") {
@@ -230,6 +246,13 @@ function endpointFromFunction(gcfFunction) {
230
246
  callableTrigger: {},
231
247
  };
232
248
  }
249
+ else if ((_d = gcfFunction.labels) === null || _d === void 0 ? void 0 : _d[exports.BLOCKING_LABEL]) {
250
+ trigger = {
251
+ blockingTrigger: {
252
+ eventType: BLOCKING_LABEL_KEY_TO_EVENT[gcfFunction.labels[exports.BLOCKING_LABEL]],
253
+ },
254
+ };
255
+ }
233
256
  else if (gcfFunction.eventTrigger) {
234
257
  trigger = {
235
258
  eventTrigger: {
@@ -246,6 +269,7 @@ function endpointFromFunction(gcfFunction) {
246
269
  trigger.eventTrigger.eventFilters[attribute] = value;
247
270
  }
248
271
  }
272
+ proto.copyIfPresent(trigger.eventTrigger, gcfFunction.eventTrigger, "channel");
249
273
  proto.renameIfPresent(trigger.eventTrigger, gcfFunction.eventTrigger, "region", "triggerRegion");
250
274
  }
251
275
  else {
@@ -266,6 +290,7 @@ function endpointFromFunction(gcfFunction) {
266
290
  endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector };
267
291
  proto.renameIfPresent(endpoint.vpc, gcfFunction.serviceConfig, "egressSettings", "vpcConnectorEgressSettings");
268
292
  }
293
+ endpoint.codebase = ((_e = gcfFunction.labels) === null || _e === void 0 ? void 0 : _e[exports.CODEBASE_LABEL]) || projectConfig.DEFAULT_CODEBASE;
269
294
  return endpoint;
270
295
  }
271
296
  exports.endpointFromFunction = endpointFromFunction;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.updateConfig = exports.setBlockingFunctionsConfig = exports.getConfig = exports.getBlockingFunctionsConfig = void 0;
4
+ const proto = require("./proto");
5
+ const api_1 = require("../api");
6
+ const apiv2_1 = require("../apiv2");
7
+ const API_VERSION = "v2";
8
+ const adminApiClient = new apiv2_1.Client({
9
+ urlPrefix: api_1.identityOrigin + "/admin",
10
+ apiVersion: API_VERSION,
11
+ });
12
+ async function getBlockingFunctionsConfig(project) {
13
+ const config = (await getConfig(project)) || {};
14
+ if (!config.blockingFunctions) {
15
+ config.blockingFunctions = {};
16
+ }
17
+ return config.blockingFunctions;
18
+ }
19
+ exports.getBlockingFunctionsConfig = getBlockingFunctionsConfig;
20
+ async function getConfig(project) {
21
+ const response = await adminApiClient.get(`projects/${project}/config`);
22
+ return response.body;
23
+ }
24
+ exports.getConfig = getConfig;
25
+ async function setBlockingFunctionsConfig(project, blockingConfig) {
26
+ const config = (await updateConfig(project, { blockingFunctions: blockingConfig }, "blockingFunctions")) || {};
27
+ if (!config.blockingFunctions) {
28
+ config.blockingFunctions = {};
29
+ }
30
+ return config.blockingFunctions;
31
+ }
32
+ exports.setBlockingFunctionsConfig = setBlockingFunctionsConfig;
33
+ async function updateConfig(project, config, updateMask) {
34
+ if (!updateMask) {
35
+ updateMask = proto.fieldMasks(config).join(",");
36
+ }
37
+ const response = await adminApiClient.patch(`projects/${project}/config`, config, {
38
+ queryParams: {
39
+ updateMask,
40
+ },
41
+ });
42
+ return response.body;
43
+ }
44
+ exports.updateConfig = updateConfig;
@@ -33,7 +33,7 @@ async function listSecrets(projectId, filter) {
33
33
  ? baseOpts
34
34
  : Object.assign(Object.assign({}, baseOpts), { queryParams: Object.assign(Object.assign({}, baseOpts === null || baseOpts === void 0 ? void 0 : baseOpts.queryParams), { pageToken }) });
35
35
  const res = await client.get(path, opts);
36
- for (const s of res.body.secrets) {
36
+ for (const s of res.body.secrets || []) {
37
37
  secrets.push(Object.assign(Object.assign({}, parseSecretResourceName(s.name)), { labels: (_a = s.labels) !== null && _a !== void 0 ? _a : {} }));
38
38
  }
39
39
  if (!res.body.nextPageToken) {
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });