firebase-tools 9.21.0 → 9.23.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 (82) hide show
  1. package/CHANGELOG.md +7 -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 +8 -0
  13. package/lib/commands/index.js +6 -5
  14. package/lib/commands/init.js +3 -0
  15. package/lib/commands/remoteconfig-get.js +6 -5
  16. package/lib/config.js +3 -2
  17. package/lib/deploy/extensions/args.js +2 -0
  18. package/lib/deploy/extensions/deploy.js +49 -0
  19. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  20. package/lib/deploy/extensions/errors.js +31 -0
  21. package/lib/deploy/extensions/index.js +8 -0
  22. package/lib/deploy/extensions/params.js +39 -0
  23. package/lib/deploy/extensions/planner.js +94 -0
  24. package/lib/deploy/extensions/prepare.js +111 -0
  25. package/lib/deploy/extensions/release.js +43 -0
  26. package/lib/deploy/extensions/secrets.js +150 -0
  27. package/lib/deploy/extensions/tasks.js +98 -0
  28. package/lib/deploy/extensions/validate.js +17 -0
  29. package/lib/deploy/functions/backend.js +8 -1
  30. package/lib/deploy/functions/checkIam.js +65 -4
  31. package/lib/deploy/functions/containerCleaner.js +97 -50
  32. package/lib/deploy/functions/eventTypes.js +10 -0
  33. package/lib/deploy/functions/prepare.js +12 -1
  34. package/lib/deploy/functions/release/fabricator.js +75 -10
  35. package/lib/deploy/functions/release/index.js +9 -1
  36. package/lib/deploy/functions/release/planner.js +3 -0
  37. package/lib/deploy/functions/release/reporter.js +8 -1
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
  39. package/lib/deploy/functions/runtimes/node/parseTriggers.js +10 -11
  40. package/lib/deploy/functions/services/index.js +38 -0
  41. package/lib/deploy/functions/services/storage.js +43 -0
  42. package/lib/deploy/functions/triggerRegionHelper.js +6 -30
  43. package/lib/deploy/index.js +10 -1
  44. package/lib/emulator/auth/handlers.js +1 -1
  45. package/lib/emulator/auth/operations.js +27 -9
  46. package/lib/emulator/auth/widget_ui.js +17 -3
  47. package/lib/emulator/functionsEmulator.js +18 -2
  48. package/lib/emulator/functionsEmulatorShared.js +1 -0
  49. package/lib/emulator/pubsubEmulator.js +58 -45
  50. package/lib/emulator/storage/cloudFunctions.js +13 -6
  51. package/lib/ensureApiEnabled.js +11 -14
  52. package/lib/extensions/askUserForParam.js +42 -10
  53. package/lib/extensions/checkProjectBilling.js +7 -7
  54. package/lib/extensions/emulator/triggerHelper.js +1 -0
  55. package/lib/extensions/export.js +107 -0
  56. package/lib/extensions/extensionsApi.js +103 -21
  57. package/lib/extensions/extensionsHelper.js +4 -1
  58. package/lib/extensions/listExtensions.js +16 -11
  59. package/lib/extensions/paramHelper.js +6 -4
  60. package/lib/extensions/provisioningHelper.js +16 -3
  61. package/lib/extensions/refs.js +9 -1
  62. package/lib/extensions/secretsUtils.js +10 -9
  63. package/lib/extensions/updateHelper.js +12 -2
  64. package/lib/extensions/versionHelper.js +14 -0
  65. package/lib/extensions/warnings.js +33 -1
  66. package/lib/functions/env.js +2 -2
  67. package/lib/gcp/artifactregistry.js +16 -0
  68. package/lib/gcp/cloudfunctions.js +27 -7
  69. package/lib/gcp/cloudfunctionsv2.js +45 -5
  70. package/lib/gcp/cloudtasks.js +143 -0
  71. package/lib/gcp/docker.js +36 -2
  72. package/lib/gcp/location.js +44 -0
  73. package/lib/gcp/proto.js +2 -2
  74. package/lib/gcp/secretManager.js +27 -6
  75. package/lib/gcp/storage.js +48 -32
  76. package/lib/init/features/functions/index.js +3 -3
  77. package/lib/init/features/hosting/github.js +3 -0
  78. package/lib/init/features/project.js +2 -1
  79. package/lib/previews.js +1 -1
  80. package/lib/projectUtils.js +10 -1
  81. package/package.json +5 -4
  82. package/schema/firebase-config.json +9 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
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
+ - Fixes issue when installing a Firebase Extension where secrets would be created before validation.
2
+ - Fixes issue with filtering on a specific storage bucket using functions in the emulator. (#3893)
3
+ - Fixes check in Cloud Functions for Firebase initialization to check for API enablement before trying to enable them. (#2574)
4
+ - No longer tries to clean up function build images from Artifact Registry when Artifact Registry is not enabled. (#3943)
5
+ - Show error message when running `firebase init hosting:github` with no Hosting config in `firebase.json`. (#3113)
6
+ - Fixes issue where `remoteconfig:get` was not fetching the latest version by default. (#3559)
7
+ - Fixes issue where empty variables in .env files would instead read as multi-line values. (#3934)
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. " +
@@ -8,6 +8,7 @@ const projectUtils_1 = require("../projectUtils");
8
8
  const prompt_1 = require("../prompt");
9
9
  const functional_1 = require("../functional");
10
10
  const requirePermissions_1 = require("../requirePermissions");
11
+ const ensure = require("../ensureApiEnabled");
11
12
  const helper = require("../deploy/functions/functionsDeployHelper");
12
13
  const utils = require("../utils");
13
14
  const backend = require("../deploy/functions/backend");
@@ -15,6 +16,7 @@ const planner = require("../deploy/functions/release/planner");
15
16
  const fabricator = require("../deploy/functions/release/fabricator");
16
17
  const executor = require("../deploy/functions/release/executor");
17
18
  const reporter = require("../deploy/functions/release/reporter");
19
+ const containerCleaner = require("../deploy/functions/containerCleaner");
18
20
  exports.default = new command_1.Command("functions:delete [filters...]")
19
21
  .description("delete one or more Cloud Functions by name or group name.")
20
22
  .option("--region <region>", "Specify region of the function to be deleted. " +
@@ -83,4 +85,10 @@ exports.default = new command_1.Command("functions:delete [filters...]")
83
85
  exit: 1,
84
86
  });
85
87
  }
88
+ const opts = {};
89
+ const arEnabled = await ensure.check(projectUtils_1.needProjectId(options), "artifactregistry.googleapis.com", "functions", true);
90
+ if (!arEnabled) {
91
+ opts.ar = new containerCleaner.NoopArtifactRegistryCleaner();
92
+ }
93
+ await containerCleaner.cleanupBuildImages([], allEpToDelete, opts);
86
94
  });
@@ -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 () {
@@ -11,13 +11,14 @@ const utils = require("../utils");
11
11
  const Table = require("cli-table");
12
12
  const fs = require("fs");
13
13
  const util = require("util");
14
+ const error_1 = require("../error");
14
15
  const tableHead = ["Entry Name", "Value"];
15
16
  const MAX_DISPLAY_ITEMS = 20;
16
- function checkValidNumber(versionNumber) {
17
- if (typeof Number(versionNumber) == "number") {
17
+ function checkValidOptionalNumber(versionNumber) {
18
+ if (!versionNumber || typeof Number(versionNumber) == "number") {
18
19
  return versionNumber;
19
20
  }
20
- return "null";
21
+ throw new error_1.FirebaseError(`Could not interpret "${versionNumber}" as a valid number.`);
21
22
  }
22
23
  module.exports = new command_1.Command("remoteconfig:get")
23
24
  .description("get a Firebase project's Remote Config template")
@@ -26,8 +27,8 @@ module.exports = new command_1.Command("remoteconfig:get")
26
27
  .before(requireAuth_1.requireAuth)
27
28
  .before(requirePermissions_1.requirePermissions, ["cloudconfig.configs.get"])
28
29
  .action(async (options) => {
29
- utils.assertIsString(options.versionNumber);
30
- const template = await rcGet.getTemplate(projectUtils_1.needProjectId(options), checkValidNumber(options.versionNumber));
30
+ utils.assertIsStringOrUndefined(options.versionNumber);
31
+ const template = await rcGet.getTemplate(projectUtils_1.needProjectId(options), checkValidOptionalNumber(options.versionNumber));
31
32
  const table = new Table({ head: tableHead, style: { head: ["green"] } });
32
33
  if (template.conditions) {
33
34
  let updatedConditions = template.conditions
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,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readParams = void 0;
4
+ const path = require("path");
5
+ const logger_1 = require("../../logger");
6
+ const paramHelper_1 = require("../../extensions/paramHelper");
7
+ const error_1 = require("../../error");
8
+ const ENV_DIRECTORY = "extensions";
9
+ function readParams(args) {
10
+ const filesToCheck = [
11
+ `${args.instanceId}.env`,
12
+ ...args.aliases.map((alias) => `${args.instanceId}.env.${alias}`),
13
+ `${args.instanceId}.env.${args.projectNumber}`,
14
+ `${args.instanceId}.env.${args.projectId}`,
15
+ ];
16
+ let noFilesFound = true;
17
+ const combinedParams = {};
18
+ for (const fileToCheck of filesToCheck) {
19
+ try {
20
+ const params = readParamsFile(args.projectDir, fileToCheck);
21
+ logger_1.logger.debug(`Successfully read params from ${fileToCheck}`);
22
+ noFilesFound = false;
23
+ Object.assign(combinedParams, params);
24
+ }
25
+ catch (err) {
26
+ logger_1.logger.debug(`${err}`);
27
+ }
28
+ }
29
+ if (noFilesFound) {
30
+ throw new error_1.FirebaseError(`No params file found for ${args.instanceId}`);
31
+ }
32
+ return combinedParams;
33
+ }
34
+ exports.readParams = readParams;
35
+ function readParamsFile(projectDir, fileName) {
36
+ const paramPath = path.join(projectDir, ENV_DIRECTORY, fileName);
37
+ const params = paramHelper_1.readEnvFile(paramPath);
38
+ return params;
39
+ }