firebase-tools 9.21.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 (59) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/lib/api.js +2 -0
  3. package/lib/apiv2.js +3 -2
  4. package/lib/commands/crashlytics-symbols-upload.js +1 -1
  5. package/lib/commands/deploy.js +9 -1
  6. package/lib/commands/ext-configure.js +1 -1
  7. package/lib/commands/ext-dev-deprecate.js +63 -0
  8. package/lib/commands/ext-dev-undeprecate.js +56 -0
  9. package/lib/commands/ext-export.js +44 -0
  10. package/lib/commands/ext-install.js +1 -1
  11. package/lib/commands/ext-update.js +1 -1
  12. package/lib/commands/functions-delete.js +2 -0
  13. package/lib/commands/index.js +6 -5
  14. package/lib/commands/init.js +3 -0
  15. package/lib/config.js +3 -2
  16. package/lib/deploy/extensions/args.js +2 -0
  17. package/lib/deploy/extensions/deploy.js +49 -0
  18. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  19. package/lib/deploy/extensions/errors.js +31 -0
  20. package/lib/deploy/extensions/index.js +8 -0
  21. package/lib/deploy/extensions/planner.js +95 -0
  22. package/lib/deploy/extensions/prepare.js +103 -0
  23. package/lib/deploy/extensions/release.js +43 -0
  24. package/lib/deploy/extensions/secrets.js +150 -0
  25. package/lib/deploy/extensions/tasks.js +98 -0
  26. package/lib/deploy/extensions/validate.js +17 -0
  27. package/lib/deploy/functions/backend.js +8 -1
  28. package/lib/deploy/functions/containerCleaner.js +77 -21
  29. package/lib/deploy/functions/release/fabricator.js +69 -9
  30. package/lib/deploy/functions/release/index.js +5 -1
  31. package/lib/deploy/functions/release/planner.js +3 -0
  32. package/lib/deploy/functions/release/reporter.js +4 -1
  33. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
  34. package/lib/deploy/functions/runtimes/node/parseTriggers.js +7 -2
  35. package/lib/deploy/index.js +1 -0
  36. package/lib/emulator/functionsEmulator.js +3 -1
  37. package/lib/extensions/askUserForParam.js +14 -6
  38. package/lib/extensions/checkProjectBilling.js +7 -7
  39. package/lib/extensions/export.js +107 -0
  40. package/lib/extensions/extensionsApi.js +103 -21
  41. package/lib/extensions/extensionsHelper.js +4 -1
  42. package/lib/extensions/listExtensions.js +16 -11
  43. package/lib/extensions/paramHelper.js +6 -4
  44. package/lib/extensions/provisioningHelper.js +16 -3
  45. package/lib/extensions/refs.js +9 -1
  46. package/lib/extensions/secretsUtils.js +10 -9
  47. package/lib/extensions/updateHelper.js +12 -2
  48. package/lib/extensions/versionHelper.js +14 -0
  49. package/lib/extensions/warnings.js +33 -1
  50. package/lib/gcp/artifactregistry.js +16 -0
  51. package/lib/gcp/cloudfunctions.js +25 -7
  52. package/lib/gcp/cloudfunctionsv2.js +10 -2
  53. package/lib/gcp/cloudtasks.js +143 -0
  54. package/lib/gcp/docker.js +7 -1
  55. package/lib/gcp/proto.js +2 -2
  56. package/lib/gcp/secretManager.js +27 -6
  57. package/lib/previews.js +1 -1
  58. package/package.json +2 -1
  59. package/schema/firebase-config.json +9 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,3 @@
1
- - Fix Auth Emulator deleteTenant not working with Node Admin (#3817).
2
- - Fix Crashlytics Android Native Symbols not working on Windows due to ":" in the path (#3842)
3
- - Fixes Firestore emulator UI showing requests out of order
1
+ - Adds `firebase ext:export` command, and adds `extensions` to `firebase deploy`. See https://firebase.google.com/docs/extensions/reuse-project-config for more infomation on how to manage your extensions with these commands.
2
+ - Fixes issue where `init` would crash with multiple Hosting items selected (#3742).
3
+ - Adds a command (`crashlytics:symbols:upload`) to upload native symbol files, used in Android NDK crash symbolication.
package/lib/api.js CHANGED
@@ -72,6 +72,7 @@ var api = {
72
72
  cloudbillingOrigin: utils.envOverride("FIREBASE_CLOUDBILLING_URL", "https://cloudbilling.googleapis.com"),
73
73
  cloudloggingOrigin: utils.envOverride("FIREBASE_CLOUDLOGGING_URL", "https://logging.googleapis.com"),
74
74
  containerRegistryDomain: utils.envOverride("CONTAINER_REGISTRY_DOMAIN", "gcr.io"),
75
+ artifactRegistryDomain: utils.envOverride("ARTIFACT_REGISTRY_DOMAIN", "https://artifactregistry.googleapis.com"),
75
76
  appDistributionOrigin: utils.envOverride("FIREBASE_APP_DISTRIBUTION_URL", "https://firebaseappdistribution.googleapis.com"),
76
77
  appengineOrigin: utils.envOverride("FIREBASE_APPENGINE_URL", "https://appengine.googleapis.com"),
77
78
  authOrigin: utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com"),
@@ -95,6 +96,7 @@ var api = {
95
96
  functionsUploadRegion: utils.envOverride("FIREBASE_FUNCTIONS_UPLOAD_REGION", "us-central1"),
96
97
  functionsDefaultRegion: utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1"),
97
98
  cloudschedulerOrigin: utils.envOverride("FIREBASE_CLOUDSCHEDULER_URL", "https://cloudscheduler.googleapis.com"),
99
+ cloudTasksOrigin: utils.envOverride("FIREBASE_CLOUD_TAKS_URL", "https://cloudtasks.googleapis.com"),
98
100
  pubsubOrigin: utils.envOverride("FIREBASE_PUBSUB_URL", "https://pubsub.googleapis.com"),
99
101
  googleOrigin: utils.envOverride("FIREBASE_TOKEN_URL", utils.envOverride("FIREBASE_GOOGLE_URL", "https://www.googleapis.com")),
100
102
  hostingOrigin: utils.envOverride("FIREBASE_HOSTING_URL", "https://web.app"),
package/lib/apiv2.js CHANGED
@@ -219,11 +219,12 @@ class Client {
219
219
  }
220
220
  let body;
221
221
  if (options.responseType === "json") {
222
- if (res.status === 204) {
222
+ const text = await res.text();
223
+ if (!text.length) {
223
224
  body = undefined;
224
225
  }
225
226
  else {
226
- body = (await res.json());
227
+ body = JSON.parse(text);
227
228
  }
228
229
  }
229
230
  else if (options.responseType === "stream") {
@@ -20,7 +20,7 @@ const SYMBOL_CACHE_ROOT_DIR = process.env.FIREBASE_CRASHLYTICS_CACHE_PATH || os.
20
20
  const JAR_CACHE_DIR = process.env.FIREBASE_CRASHLYTICS_BUILDTOOLS_PATH ||
21
21
  path.join(os.homedir(), ".cache", "firebase", "crashlytics", "buildtools");
22
22
  const JAR_VERSION = "2.8.0";
23
- const JAR_URL = `https://storage.googleapis.com/firebase-preview-drop/android/crashlytics-eap/crashlytics-buildtools/firebase-crashlytics-buildtools-${JAR_VERSION}-alpha-all.jar`;
23
+ const JAR_URL = `https://dl.google.com/android/maven2/com/google/firebase/firebase-crashlytics-buildtools/${JAR_VERSION}/firebase-crashlytics-buildtools-${JAR_VERSION}.jar`;
24
24
  exports.default = new command_1.Command("crashlytics:symbols:upload <symbolFiles...>")
25
25
  .description("Upload symbols for native code, to symbolicate stack traces.")
26
26
  .option("--app <appID>", "the app id of your Firebase app")
@@ -9,7 +9,15 @@ const deploy = require("../deploy");
9
9
  const requireConfig = require("../requireConfig");
10
10
  const { filterTargets } = require("../filterTargets");
11
11
  const { requireHostingSite } = require("../requireHostingSite");
12
- const VALID_TARGETS = ["database", "storage", "firestore", "functions", "hosting", "remoteconfig"];
12
+ const VALID_TARGETS = [
13
+ "database",
14
+ "storage",
15
+ "firestore",
16
+ "functions",
17
+ "hosting",
18
+ "remoteconfig",
19
+ "extensions",
20
+ ];
13
21
  const TARGET_PERMISSIONS = {
14
22
  database: ["firebasedatabase.instances.update"],
15
23
  hosting: ["firebasehosting.sites.update"],
@@ -69,7 +69,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
69
69
  ", uninstall the extension, then install a new instance of this extension.");
70
70
  }
71
71
  spinner.start();
72
- const res = await extensionsApi.configureInstance(projectId, instanceId, params);
72
+ const res = await extensionsApi.configureInstance({ projectId, instanceId, params });
73
73
  spinner.stop();
74
74
  utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `successfully configured ${clc.bold(instanceId)}.`);
75
75
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl(projectId, `/extensions/instances/${instanceId}?tab=config`)}`));
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const clc = require("cli-color");
4
+ const semver = require("semver");
5
+ const refs = require("../extensions/refs");
6
+ const utils = require("../utils");
7
+ const command_1 = require("../command");
8
+ const prompt_1 = require("../prompt");
9
+ const extensionsHelper_1 = require("../extensions/extensionsHelper");
10
+ const extensionsApi_1 = require("../extensions/extensionsApi");
11
+ const versionHelper_1 = require("../extensions/versionHelper");
12
+ const requireAuth_1 = require("../requireAuth");
13
+ const error_1 = require("../error");
14
+ exports.default = new command_1.Command("ext:dev:deprecate <extensionRef> <versionPredicate>")
15
+ .description("deprecate extension versions that match the version predicate")
16
+ .option("-m, --message <deprecationMessage>", "deprecation message")
17
+ .option("-f, --force", "override deprecation message for existing deprecated extension versions that match")
18
+ .before(requireAuth_1.requireAuth)
19
+ .before(extensionsHelper_1.ensureExtensionsApiEnabled)
20
+ .action(async (extensionRef, versionPredicate, options) => {
21
+ const { publisherId, extensionId, version } = refs.parse(extensionRef);
22
+ if (version) {
23
+ throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should be supplied in the version predicate argument.`);
24
+ }
25
+ if (!publisherId || !extensionId) {
26
+ throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
27
+ }
28
+ const { comparator, targetSemVer } = versionHelper_1.parseVersionPredicate(versionPredicate);
29
+ const filter = `id${comparator}"${targetSemVer}"`;
30
+ const extensionVersions = await extensionsApi_1.listExtensionVersions(extensionRef, filter);
31
+ const filteredExtensionVersions = extensionVersions
32
+ .sort((ev1, ev2) => {
33
+ return -semver.compare(ev1.spec.version, ev2.spec.version);
34
+ })
35
+ .filter((extensionVersion) => {
36
+ if (extensionVersion.state === "DEPRECATED" && !options.force) {
37
+ return false;
38
+ }
39
+ const message = extensionVersion.state === "DEPRECATED" ? ", will overwrite deprecation message" : "";
40
+ utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state + message);
41
+ return true;
42
+ });
43
+ if (filteredExtensionVersions.length > 0) {
44
+ if (!options.force) {
45
+ const confirmMessage = "You are about to deprecate these extension version(s). Do you wish to continue?";
46
+ const consent = await prompt_1.promptOnce({
47
+ type: "confirm",
48
+ message: confirmMessage,
49
+ default: false,
50
+ });
51
+ if (!consent) {
52
+ throw new error_1.FirebaseError("Deprecation canceled.");
53
+ }
54
+ }
55
+ }
56
+ else {
57
+ throw new error_1.FirebaseError("No extension versions matched the version predicate.");
58
+ }
59
+ await utils.allSettled(filteredExtensionVersions.map(async (extensionVersion) => {
60
+ await extensionsApi_1.deprecateExtensionVersion(extensionVersion.ref, options.deprecationMessage);
61
+ }));
62
+ utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully deprecated extension version(s).");
63
+ });
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const clc = require("cli-color");
4
+ const semver = require("semver");
5
+ const refs = require("../extensions/refs");
6
+ const utils = require("../utils");
7
+ const command_1 = require("../command");
8
+ const prompt_1 = require("../prompt");
9
+ const extensionsHelper_1 = require("../extensions/extensionsHelper");
10
+ const extensionsApi_1 = require("../extensions/extensionsApi");
11
+ const versionHelper_1 = require("../extensions/versionHelper");
12
+ const requireAuth_1 = require("../requireAuth");
13
+ const error_1 = require("../error");
14
+ exports.default = new command_1.Command("ext:dev:undeprecate <extensionRef> <versionPredicate>")
15
+ .description("undeprecate extension versions that match the version predicate")
16
+ .before(requireAuth_1.requireAuth)
17
+ .before(extensionsHelper_1.ensureExtensionsApiEnabled)
18
+ .action(async (extensionRef, versionPredicate, options) => {
19
+ const { publisherId, extensionId, version } = refs.parse(extensionRef);
20
+ if (version) {
21
+ throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should be supplied in the version predicate argument.`);
22
+ }
23
+ if (!publisherId || !extensionId) {
24
+ throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
25
+ }
26
+ const { comparator, targetSemVer } = versionHelper_1.parseVersionPredicate(versionPredicate);
27
+ const filter = `id${comparator}"${targetSemVer}"`;
28
+ const extensionVersions = await extensionsApi_1.listExtensionVersions(extensionRef, filter);
29
+ extensionVersions
30
+ .sort((ev1, ev2) => {
31
+ return -semver.compare(ev1.spec.version, ev2.spec.version);
32
+ })
33
+ .forEach((extensionVersion) => {
34
+ utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state);
35
+ });
36
+ if (extensionVersions.length > 0) {
37
+ if (!options.force) {
38
+ const confirmMessage = "You are about to undeprecate these extension version(s). Do you wish to continue?";
39
+ const consent = await prompt_1.promptOnce({
40
+ type: "confirm",
41
+ message: confirmMessage,
42
+ default: false,
43
+ });
44
+ if (!consent) {
45
+ throw new error_1.FirebaseError("Undeprecation canceled.");
46
+ }
47
+ }
48
+ }
49
+ else {
50
+ throw new error_1.FirebaseError("No extension versions matched the version predicate.");
51
+ }
52
+ await utils.allSettled(extensionVersions.map(async (extensionVersion) => {
53
+ await extensionsApi_1.undeprecateExtensionVersion(extensionVersion.ref);
54
+ }));
55
+ utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully undeprecated extension version(s).");
56
+ });
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
4
+ const command_1 = require("../command");
5
+ const planner = require("../deploy/extensions/planner");
6
+ const export_1 = require("../extensions/export");
7
+ const extensionsHelper_1 = require("../extensions/extensionsHelper");
8
+ const functional_1 = require("../functional");
9
+ const getProjectNumber_1 = require("../getProjectNumber");
10
+ const logger_1 = require("../logger");
11
+ const projectUtils_1 = require("../projectUtils");
12
+ const prompt_1 = require("../prompt");
13
+ const requirePermissions_1 = require("../requirePermissions");
14
+ module.exports = new command_1.Command("ext:export")
15
+ .description("export all Extension instances installed on a project to a local Firebase directory")
16
+ .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.list"])
17
+ .before(extensionsHelper_1.ensureExtensionsApiEnabled)
18
+ .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
19
+ .withForce()
20
+ .action(async (options) => {
21
+ const projectId = projectUtils_1.needProjectId(options);
22
+ const projectNumber = await getProjectNumber_1.getProjectNumber(options);
23
+ const have = await Promise.all((await planner.have(projectId)).map(async (i) => {
24
+ const subbed = await export_1.setSecretParamsToLatest(i);
25
+ return export_1.parameterizeProject(projectId, projectNumber, subbed);
26
+ }));
27
+ if (have.length == 0) {
28
+ logger_1.logger.info(`No extension instances installed on ${projectId}, so there is nothing to export.`);
29
+ return;
30
+ }
31
+ const [withRef, withoutRef] = functional_1.partition(have, (s) => !!s.ref);
32
+ export_1.displayExportInfo(withRef, withoutRef);
33
+ if (!options.nonInteractive &&
34
+ !options.force &&
35
+ !(await prompt_1.promptOnce({
36
+ message: "Do you wish to add these Extension instances to firebase.json?",
37
+ type: "confirm",
38
+ default: true,
39
+ }))) {
40
+ logger_1.logger.info("Exiting. No changes made.");
41
+ return;
42
+ }
43
+ await export_1.writeFiles(withRef, options);
44
+ });
@@ -47,7 +47,7 @@ async function installExtension(options) {
47
47
  }
48
48
  else if (!enabled) {
49
49
  await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, false);
50
- await checkProjectBilling_1.enableBilling(projectId, spec.displayName || spec.name);
50
+ await checkProjectBilling_1.enableBilling(projectId);
51
51
  }
52
52
  else {
53
53
  await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, !nonInteractive);
@@ -138,7 +138,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
138
138
  }
139
139
  if (!enabled) {
140
140
  if (!options.nonInteractive) {
141
- await checkProjectBilling_1.enableBilling(projectId, instanceId);
141
+ await checkProjectBilling_1.enableBilling(projectId);
142
142
  }
143
143
  else {
144
144
  throw new error_1.FirebaseError("The extension requires your project to be upgraded to the Blaze plan. " +
@@ -15,6 +15,7 @@ const planner = require("../deploy/functions/release/planner");
15
15
  const fabricator = require("../deploy/functions/release/fabricator");
16
16
  const executor = require("../deploy/functions/release/executor");
17
17
  const reporter = require("../deploy/functions/release/reporter");
18
+ const containerCleaner = require("../deploy/functions/containerCleaner");
18
19
  exports.default = new command_1.Command("functions:delete [filters...]")
19
20
  .description("delete one or more Cloud Functions by name or group name.")
20
21
  .option("--region <region>", "Specify region of the function to be deleted. " +
@@ -83,4 +84,5 @@ exports.default = new command_1.Command("functions:delete [filters...]")
83
84
  exit: 1,
84
85
  });
85
86
  }
87
+ await containerCleaner.cleanupBuildImages([], allEpToDelete);
86
88
  });
@@ -26,11 +26,9 @@ module.exports = function (client) {
26
26
  client.auth = {};
27
27
  client.auth.export = loadCommand("auth-export");
28
28
  client.auth.upload = loadCommand("auth-import");
29
- if (previews.crashlyticsSymbolsUpload) {
30
- client.crashlytics = {};
31
- client.crashlytics.symbols = {};
32
- client.crashlytics.symbols.upload = loadCommand("crashlytics-symbols-upload");
33
- }
29
+ client.crashlytics = {};
30
+ client.crashlytics.symbols = {};
31
+ client.crashlytics.symbols.upload = loadCommand("crashlytics-symbols-upload");
34
32
  client.database = {};
35
33
  client.database.get = loadCommand("database-get");
36
34
  client.database.instances = {};
@@ -63,6 +61,7 @@ module.exports = function (client) {
63
61
  client.ext = loadCommand("ext");
64
62
  client.ext.configure = loadCommand("ext-configure");
65
63
  client.ext.info = loadCommand("ext-info");
64
+ client.ext.export = loadCommand("ext-export");
66
65
  client.ext.install = loadCommand("ext-install");
67
66
  client.ext.list = loadCommand("ext-list");
68
67
  client.ext.uninstall = loadCommand("ext-uninstall");
@@ -79,6 +78,8 @@ module.exports = function (client) {
79
78
  client.ext.dev.emulators = {};
80
79
  client.ext.dev.emulators.start = loadCommand("ext-dev-emulators-start");
81
80
  client.ext.dev.emulators.exec = loadCommand("ext-dev-emulators-exec");
81
+ client.ext.dev.deprecate = loadCommand("ext-dev-deprecate");
82
+ client.ext.dev.undeprecate = loadCommand("ext-dev-undeprecate");
82
83
  client.ext.dev.unpublish = loadCommand("ext-dev-unpublish");
83
84
  client.ext.dev.publish = loadCommand("ext-dev-publish");
84
85
  client.ext.dev.delete = loadCommand("ext-dev-extension-delete");
@@ -155,6 +155,9 @@ module.exports = new Command("init [feature]")
155
155
  if (allAccounts.length > 1) {
156
156
  setup.features.unshift("account");
157
157
  }
158
+ if (setup.features.includes("hosting") && setup.features.includes("hosting:github")) {
159
+ setup.features = setup.features.filter((f) => f != "hosting:github");
160
+ }
158
161
  return init(setup, config, options);
159
162
  })
160
163
  .then(function () {
package/lib/config.js CHANGED
@@ -144,10 +144,10 @@ class Config {
144
144
  fs.ensureFileSync(this.path(p));
145
145
  fs.writeFileSync(this.path(p), content, "utf8");
146
146
  }
147
- askWriteProjectFile(p, content) {
147
+ askWriteProjectFile(p, content, force) {
148
148
  const writeTo = this.path(p);
149
149
  let next;
150
- if (fsutils.fileExistsSync(writeTo)) {
150
+ if (fsutils.fileExistsSync(writeTo) && !force) {
151
151
  next = prompt_1.promptOnce({
152
152
  type: "confirm",
153
153
  message: "File " + clc.underline(p) + " already exists. Overwrite?",
@@ -203,6 +203,7 @@ Config.FILENAME = "firebase.json";
203
203
  Config.MATERIALIZE_TARGETS = [
204
204
  "database",
205
205
  "emulators",
206
+ "extensions",
206
207
  "firestore",
207
208
  "functions",
208
209
  "hosting",
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deploy = void 0;
4
+ const tasks = require("./tasks");
5
+ const queue_1 = require("../../throttler/queue");
6
+ const error_1 = require("../../error");
7
+ const errors_1 = require("./errors");
8
+ const projectUtils_1 = require("../../projectUtils");
9
+ const provisioningHelper_1 = require("../../extensions/provisioningHelper");
10
+ const secrets_1 = require("./secrets");
11
+ const validate_1 = require("./validate");
12
+ async function deploy(context, options, payload) {
13
+ var _a, _b, _c, _d, _e, _f;
14
+ const projectId = projectUtils_1.needProjectId(options);
15
+ await validate_1.checkBilling(projectId, options.nonInteractive);
16
+ await provisioningHelper_1.bulkCheckProductsProvisioned(projectId, [
17
+ ...((_a = payload.instancesToCreate) !== null && _a !== void 0 ? _a : []),
18
+ ...((_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []),
19
+ ...((_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : []),
20
+ ]);
21
+ await secrets_1.handleSecretParams(payload, context.have, options.nonInteractive);
22
+ const errorHandler = new errors_1.ErrorHandler();
23
+ const validationQueue = new queue_1.default({
24
+ retries: 5,
25
+ concurrency: 5,
26
+ handler: tasks.extensionsDeploymentHandler(errorHandler),
27
+ });
28
+ for (const create of (_d = payload.instancesToCreate) !== null && _d !== void 0 ? _d : []) {
29
+ const task = tasks.createExtensionInstanceTask(projectId, create, true);
30
+ void validationQueue.run(task);
31
+ }
32
+ for (const update of (_e = payload.instancesToUpdate) !== null && _e !== void 0 ? _e : []) {
33
+ const task = tasks.updateExtensionInstanceTask(projectId, update, true);
34
+ void validationQueue.run(task);
35
+ }
36
+ for (const configure of (_f = payload.instancesToConfigure) !== null && _f !== void 0 ? _f : []) {
37
+ const task = tasks.configureExtensionInstanceTask(projectId, configure, true);
38
+ void validationQueue.run(task);
39
+ }
40
+ const validationPromise = validationQueue.wait();
41
+ validationQueue.process();
42
+ validationQueue.close();
43
+ await validationPromise;
44
+ if (errorHandler.hasErrors()) {
45
+ errorHandler.print();
46
+ throw new error_1.FirebaseError(`Extensions deployment failed validation. No changes have been made to the Extension instances on ${projectId}`);
47
+ }
48
+ }
49
+ exports.deploy = deploy;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deletesSummary = exports.configuresSummary = exports.updatesSummary = exports.createsSummary = exports.humanReadable = void 0;
4
+ const clc = require("cli-color");
5
+ const refs = require("../../extensions/refs");
6
+ exports.humanReadable = (dep) => `${clc.bold(dep.instanceId)} (${dep.ref ? `${refs.toExtensionVersionRef(dep.ref)}` : `Installed from local source`})`;
7
+ const humanReadableUpdate = (from, to) => {
8
+ var _a, _b, _c, _d, _e;
9
+ if (((_a = from.ref) === null || _a === void 0 ? void 0 : _a.publisherId) == ((_b = to.ref) === null || _b === void 0 ? void 0 : _b.publisherId) &&
10
+ ((_c = from.ref) === null || _c === void 0 ? void 0 : _c.extensionId) == ((_d = to.ref) === null || _d === void 0 ? void 0 : _d.extensionId)) {
11
+ return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref)} => ${(_e = to.ref) === null || _e === void 0 ? void 0 : _e.version})`;
12
+ }
13
+ else {
14
+ const fromRef = from.ref
15
+ ? `${refs.toExtensionVersionRef(from.ref)}`
16
+ : `Installed from local source`;
17
+ return `\t${clc.bold(from.instanceId)} (${fromRef} => ${refs.toExtensionVersionRef(to.ref)})`;
18
+ }
19
+ };
20
+ function createsSummary(toCreate) {
21
+ const instancesToCreate = toCreate.map((s) => `\t${exports.humanReadable(s)}`).join("\n");
22
+ return toCreate.length
23
+ ? `The following extension instances will be created:\n${instancesToCreate}\n`
24
+ : "";
25
+ }
26
+ exports.createsSummary = createsSummary;
27
+ function updatesSummary(toUpdate, have) {
28
+ const instancesToUpdate = toUpdate
29
+ .map((to) => {
30
+ const from = have.find((exists) => exists.instanceId == to.instanceId);
31
+ return humanReadableUpdate(from, to);
32
+ })
33
+ .join("\n");
34
+ return toUpdate.length
35
+ ? `The following extension instances will be updated:\n${instancesToUpdate}\n`
36
+ : "";
37
+ }
38
+ exports.updatesSummary = updatesSummary;
39
+ function configuresSummary(toConfigure) {
40
+ const instancesToConfigure = toConfigure.map((s) => `\t${exports.humanReadable(s)}`).join("\n");
41
+ return toConfigure.length
42
+ ? `The following extension instances will be configured:\n${instancesToConfigure}\n`
43
+ : "";
44
+ }
45
+ exports.configuresSummary = configuresSummary;
46
+ function deletesSummary(toDelete) {
47
+ const instancesToDelete = toDelete.map((s) => `\t${exports.humanReadable(s)}`).join("\n");
48
+ return toDelete.length
49
+ ? `The following extension instances are not listed in 'firebase.json':\n${instancesToDelete}\n`
50
+ : "";
51
+ }
52
+ exports.deletesSummary = deletesSummary;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ErrorHandler = void 0;
4
+ const clc = require("cli-color");
5
+ const logger_1 = require("../../logger");
6
+ class ErrorHandler {
7
+ constructor() {
8
+ this.errors = [];
9
+ }
10
+ record(instanceId, type, message) {
11
+ this.errors.push({
12
+ instanceId,
13
+ type,
14
+ message: message,
15
+ });
16
+ }
17
+ print() {
18
+ logger_1.logger.info("");
19
+ logger_1.logger.info("Extensions deploy had errors:");
20
+ logger_1.logger.info("");
21
+ for (const err of this.errors) {
22
+ logger_1.logger.info(`- ${err.type} ${clc.bold(err.instanceId)}`);
23
+ logger_1.logger.info(err.message);
24
+ logger_1.logger.info("");
25
+ }
26
+ }
27
+ hasErrors() {
28
+ return this.errors.length > 0;
29
+ }
30
+ }
31
+ exports.ErrorHandler = ErrorHandler;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ var prepare_1 = require("./prepare");
4
+ Object.defineProperty(exports, "prepare", { enumerable: true, get: function () { return prepare_1.prepare; } });
5
+ var deploy_1 = require("./deploy");
6
+ Object.defineProperty(exports, "deploy", { enumerable: true, get: function () { return deploy_1.deploy; } });
7
+ var release_1 = require("./release");
8
+ Object.defineProperty(exports, "release", { enumerable: true, get: function () { return release_1.release; } });
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveVersion = exports.want = exports.have = exports.getExtension = exports.getExtensionVersion = void 0;
4
+ const path = require("path");
5
+ const semver = require("semver");
6
+ const error_1 = require("../../error");
7
+ const extensionsApi = require("../../extensions/extensionsApi");
8
+ const extensionsHelper_1 = require("../../extensions/extensionsHelper");
9
+ const refs = require("../../extensions/refs");
10
+ const paramHelper_1 = require("../../extensions/paramHelper");
11
+ const logger_1 = require("../../logger");
12
+ async function getExtensionVersion(i) {
13
+ if (!i.extensionVersion) {
14
+ if (!i.ref) {
15
+ throw new error_1.FirebaseError(`Can't get ExtensionVersion for ${i.instanceId} because it has no ref`);
16
+ }
17
+ i.extensionVersion = await extensionsApi.getExtensionVersion(refs.toExtensionVersionRef(i.ref));
18
+ }
19
+ return i.extensionVersion;
20
+ }
21
+ exports.getExtensionVersion = getExtensionVersion;
22
+ async function getExtension(i) {
23
+ if (!i.ref) {
24
+ throw new error_1.FirebaseError(`Can't get Extensionfor ${i.instanceId} because it has no ref`);
25
+ }
26
+ if (!i.extension) {
27
+ i.extension = await extensionsApi.getExtension(refs.toExtensionRef(i.ref));
28
+ }
29
+ return i.extension;
30
+ }
31
+ exports.getExtension = getExtension;
32
+ const ENV_DIRECTORY = "extensions";
33
+ async function have(projectId) {
34
+ const instances = await extensionsApi.listInstances(projectId);
35
+ return instances.map((i) => {
36
+ const dep = {
37
+ instanceId: i.name.split("/").pop(),
38
+ params: i.config.params,
39
+ };
40
+ if (i.config.extensionRef) {
41
+ const ref = refs.parse(i.config.extensionRef);
42
+ dep.ref = ref;
43
+ dep.ref.version = i.config.extensionVersion;
44
+ }
45
+ return dep;
46
+ });
47
+ }
48
+ exports.have = have;
49
+ async function want(projectId, projectDir, extensions) {
50
+ const instanceSpecs = [];
51
+ const errors = [];
52
+ for (const e of Object.entries(extensions)) {
53
+ try {
54
+ const instanceId = e[0];
55
+ const ref = refs.parse(e[1]);
56
+ ref.version = await resolveVersion(ref);
57
+ const params = readParams(projectDir, instanceId);
58
+ const autoPopulatedParams = await extensionsHelper_1.getFirebaseProjectParams(projectId);
59
+ const subbedParams = extensionsHelper_1.substituteParams(params, autoPopulatedParams);
60
+ instanceSpecs.push({
61
+ instanceId,
62
+ ref,
63
+ params: subbedParams,
64
+ });
65
+ }
66
+ catch (err) {
67
+ logger_1.logger.debug(`Got error reading extensions entry ${e}: ${err}`);
68
+ errors.push(err);
69
+ }
70
+ }
71
+ if (errors.length) {
72
+ const messages = errors.map((err) => `- ${err.message}`).join("\n");
73
+ throw new error_1.FirebaseError(`Errors while reading 'extensions' in 'firebase.json'\n${messages}`);
74
+ }
75
+ return instanceSpecs;
76
+ }
77
+ exports.want = want;
78
+ async function resolveVersion(ref) {
79
+ if (!ref.version || ref.version == "latest") {
80
+ return "latest";
81
+ }
82
+ const extensionRef = refs.toExtensionRef(ref);
83
+ const versions = await extensionsApi.listExtensionVersions(extensionRef);
84
+ const maxSatisfying = semver.maxSatisfying(versions.map((ev) => ev.spec.version), ref.version);
85
+ if (!maxSatisfying) {
86
+ throw new error_1.FirebaseError(`No version of ${extensionRef} matches requested version ${ref.version}`);
87
+ }
88
+ return maxSatisfying;
89
+ }
90
+ exports.resolveVersion = resolveVersion;
91
+ function readParams(projectDir, instanceId) {
92
+ const paramPath = path.join(projectDir, ENV_DIRECTORY, `${instanceId}.env`);
93
+ const params = paramHelper_1.readEnvFile(paramPath);
94
+ return params;
95
+ }