firebase-tools 10.3.1 → 10.4.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 (41) hide show
  1. package/lib/commands/deploy.js +1 -1
  2. package/lib/commands/experimental-functions-shell.js +1 -1
  3. package/lib/commands/ext-configure.js +3 -2
  4. package/lib/commands/ext-export.js +7 -1
  5. package/lib/commands/ext-install.js +2 -2
  6. package/lib/commands/ext-update.js +2 -2
  7. package/lib/commands/functions-config-export.js +2 -2
  8. package/lib/commands/functions-shell.js +1 -1
  9. package/lib/commands/hosting-channel-create.js +2 -2
  10. package/lib/commands/hosting-channel-delete.js +2 -2
  11. package/lib/commands/hosting-channel-deploy.js +2 -2
  12. package/lib/commands/hosting-channel-list.js +2 -2
  13. package/lib/commands/hosting-channel-open.js +2 -2
  14. package/lib/commands/hosting-sites-delete.js +2 -2
  15. package/lib/commands/serve.js +1 -1
  16. package/lib/commands/target-apply.js +2 -2
  17. package/lib/commands/target-clear.js +2 -2
  18. package/lib/commands/target-remove.js +2 -2
  19. package/lib/commands/target.js +2 -2
  20. package/lib/config.js +3 -0
  21. package/lib/deploy/extensions/planner.js +9 -3
  22. package/lib/emulator/auth/apiSpec.js +37 -0
  23. package/lib/emulator/commandUtils.js +2 -2
  24. package/lib/emulator/extensionsEmulator.js +3 -0
  25. package/lib/emulator/functionsEmulatorShared.js +4 -0
  26. package/lib/emulator/storage/apis/firebase.js +4 -6
  27. package/lib/emulator/storage/files.js +5 -5
  28. package/lib/emulator/storage/index.js +6 -9
  29. package/lib/emulator/storage/rules/config.js +6 -5
  30. package/lib/emulator/storage/rules/manager.js +49 -32
  31. package/lib/emulator/storage/rules/runtime.js +4 -0
  32. package/lib/emulator/storage/rules/utils.js +2 -2
  33. package/lib/emulator/storage/server.js +1 -1
  34. package/lib/extensions/askUserForParam.js +52 -9
  35. package/lib/extensions/manifest.js +36 -4
  36. package/lib/extensions/paramHelper.js +4 -3
  37. package/lib/fsutils.js +14 -1
  38. package/lib/requireConfig.js +11 -9
  39. package/npm-shrinkwrap.json +2 -2
  40. package/package.json +1 -1
  41. package/lib/prepareUpload.js +0 -44
@@ -6,7 +6,7 @@ const { checkServiceAccountIam } = require("../deploy/functions/checkIam");
6
6
  const checkValidTargetFilters = require("../checkValidTargetFilters");
7
7
  const { Command } = require("../command");
8
8
  const deploy = require("../deploy");
9
- const requireConfig = require("../requireConfig");
9
+ const { requireConfig } = require("../requireConfig");
10
10
  const { filterTargets } = require("../filterTargets");
11
11
  const { requireHostingSite } = require("../requireHostingSite");
12
12
  const VALID_TARGETS = [
@@ -2,7 +2,7 @@
2
2
  var { Command } = require("../command");
3
3
  var { requirePermissions } = require("../requirePermissions");
4
4
  var { actionFunction } = require("../functionsShellCommandAction");
5
- var requireConfig = require("../requireConfig");
5
+ var { requireConfig } = require("../requireConfig");
6
6
  module.exports = new Command("experimental:functions:shell")
7
7
  .description("launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)")
8
8
  .option("-p, --port <port>", "the port on which to emulate functions (default: 5000)", 5000)
@@ -58,12 +58,13 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
58
58
  instanceId,
59
59
  reconfiguring: true,
60
60
  });
61
- const newParamValues = Object.assign(Object.assign({}, oldParamValues), (0, paramHelper_1.getBaseParamBindings)(mutableParamsBindingOptions));
61
+ const newParamOptions = Object.assign(Object.assign({}, (0, paramHelper_1.buildBindingOptionsWithBaseValue)(oldParamValues)), mutableParamsBindingOptions);
62
62
  await manifest.writeToManifest([
63
63
  {
64
64
  instanceId,
65
65
  ref: targetRef,
66
- params: newParamValues,
66
+ params: newParamOptions,
67
+ paramSpecs: extensionVersion.spec.params,
67
68
  },
68
69
  ], config, {
69
70
  nonInteractive: false,
@@ -6,6 +6,7 @@ const planner = require("../deploy/extensions/planner");
6
6
  const export_1 = require("../extensions/export");
7
7
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
8
8
  const manifest = require("../extensions/manifest");
9
+ const paramHelper_1 = require("../extensions/paramHelper");
9
10
  const functional_1 = require("../functional");
10
11
  const getProjectNumber_1 = require("../getProjectNumber");
11
12
  const logger_1 = require("../logger");
@@ -41,8 +42,13 @@ module.exports = new command_1.Command("ext:export")
41
42
  logger_1.logger.info("Exiting. No changes made.");
42
43
  return;
43
44
  }
45
+ const manifestSpecs = withRef.map((spec) => ({
46
+ instanceId: spec.instanceId,
47
+ ref: spec.ref,
48
+ params: (0, paramHelper_1.buildBindingOptionsWithBaseValue)(spec.params),
49
+ }));
44
50
  const existingConfig = manifest.loadConfig(options);
45
- await manifest.writeToManifest(withRef, existingConfig, {
51
+ await manifest.writeToManifest(manifestSpecs, existingConfig, {
46
52
  nonInteractive: options.nonInteractive,
47
53
  force: options.force,
48
54
  }, true);
@@ -183,13 +183,13 @@ async function installToManifest(options) {
183
183
  paramsEnvPath,
184
184
  instanceId,
185
185
  });
186
- const params = (0, paramHelper_1.getBaseParamBindings)(paramBindingOptions);
187
186
  const ref = refs.parse(extVersion.ref);
188
187
  await manifest.writeToManifest([
189
188
  {
190
189
  instanceId,
191
190
  ref,
192
- params,
191
+ params: paramBindingOptions,
192
+ paramSpecs: spec.params,
193
193
  },
194
194
  ], config, { nonInteractive, force: force !== null && force !== void 0 ? force : false });
195
195
  manifest.showPreviewWarning();
@@ -79,12 +79,12 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
79
79
  nonInteractive: options.nonInteractive,
80
80
  instanceId,
81
81
  });
82
- const newParamBindings = paramHelper.getBaseParamBindings(newParamBindingOptions);
83
82
  await manifest.writeToManifest([
84
83
  {
85
84
  instanceId,
86
85
  ref: refs.parse(newExtensionVersion.ref),
87
- params: newParamBindings,
86
+ params: newParamBindingOptions,
87
+ paramSpecs: newExtensionVersion.spec.params,
88
88
  },
89
89
  ], config, {
90
90
  nonInteractive: options.nonInteractive,
@@ -12,7 +12,7 @@ const requirePermissions_1 = require("../requirePermissions");
12
12
  const utils_1 = require("../utils");
13
13
  const functional_1 = require("../functional");
14
14
  const configExport = require("../functions/runtimeConfigExport");
15
- const requireConfig = require("../requireConfig");
15
+ const requireConfig_1 = require("../requireConfig");
16
16
  const REQUIRED_PERMISSIONS = [
17
17
  "runtimeconfig.configs.list",
18
18
  "runtimeconfig.configs.get",
@@ -76,7 +76,7 @@ exports.default = new command_1.Command("functions:config:export")
76
76
  "runtimeconfig.variables.list",
77
77
  "runtimeconfig.variables.get",
78
78
  ])
79
- .before(requireConfig)
79
+ .before(requireConfig_1.requireConfig)
80
80
  .before(requireInteractive_1.default)
81
81
  .action(async (options) => {
82
82
  let pInfos = configExport.getProjectInfos(options);
@@ -2,7 +2,7 @@
2
2
  var { Command } = require("../command");
3
3
  var { requirePermissions } = require("../requirePermissions");
4
4
  var { actionFunction } = require("../functionsShellCommandAction");
5
- var requireConfig = require("../requireConfig");
5
+ var { requireConfig } = require("../requireConfig");
6
6
  var commandUtils = require("../emulator/commandUtils");
7
7
  module.exports = new Command("functions:shell")
8
8
  .description("launch full Node shell with emulated functions")
@@ -10,7 +10,7 @@ const prompt_1 = require("../prompt");
10
10
  const requirePermissions_1 = require("../requirePermissions");
11
11
  const projectUtils_1 = require("../projectUtils");
12
12
  const logger_1 = require("../logger");
13
- const requireConfig = require("../requireConfig");
13
+ const requireConfig_1 = require("../requireConfig");
14
14
  const { marked } = require("marked");
15
15
  const requireHostingSite_1 = require("../requireHostingSite");
16
16
  const LOG_TAG = "hosting:channel";
@@ -18,7 +18,7 @@ exports.default = new command_1.Command("hosting:channel:create [channelId]")
18
18
  .description("create a Firebase Hosting channel")
19
19
  .option("-e, --expires <duration>", "duration string (e.g. 12h or 30d) for channel expiration, max 30d")
20
20
  .option("--site <siteId>", "site for which to create the channel")
21
- .before(requireConfig)
21
+ .before(requireConfig_1.requireConfig)
22
22
  .before(requirePermissions_1.requirePermissions, ["firebasehosting.sites.update"])
23
23
  .before(requireHostingSite_1.requireHostingSite)
24
24
  .action(async (channelId, options) => {
@@ -9,13 +9,13 @@ const prompt_1 = require("../prompt");
9
9
  const requireHostingSite_1 = require("../requireHostingSite");
10
10
  const requirePermissions_1 = require("../requirePermissions");
11
11
  const projectUtils_1 = require("../projectUtils");
12
- const requireConfig = require("../requireConfig");
12
+ const requireConfig_1 = require("../requireConfig");
13
13
  const logger_1 = require("../logger");
14
14
  exports.default = new command_1.Command("hosting:channel:delete <channelId>")
15
15
  .description("delete a Firebase Hosting channel")
16
16
  .withForce()
17
17
  .option("--site <siteId>", "site in which the channel exists")
18
- .before(requireConfig)
18
+ .before(requireConfig_1.requireConfig)
19
19
  .before(requirePermissions_1.requirePermissions, ["firebasehosting.sites.update"])
20
20
  .before(requireHostingSite_1.requireHostingSite)
21
21
  .action(async (channelId, options) => {
@@ -9,7 +9,7 @@ const requirePermissions_1 = require("../requirePermissions");
9
9
  const deploy = require("../deploy");
10
10
  const projectUtils_1 = require("../projectUtils");
11
11
  const logger_1 = require("../logger");
12
- const requireConfig = require("../requireConfig");
12
+ const requireConfig_1 = require("../requireConfig");
13
13
  const expireUtils_1 = require("../hosting/expireUtils");
14
14
  const utils_1 = require("../utils");
15
15
  const { marked } = require("marked");
@@ -21,7 +21,7 @@ exports.default = new command_1.Command("hosting:channel:deploy [channelId]")
21
21
  .option("--only <target1,target2...>", "only create previews for specified targets")
22
22
  .option("--open", "open a browser to the channel after deploying")
23
23
  .option("--no-authorized-domains", "do not sync channel domains with Firebase Auth")
24
- .before(requireConfig)
24
+ .before(requireConfig_1.requireConfig)
25
25
  .before(requirePermissions_1.requirePermissions, ["firebasehosting.sites.update"])
26
26
  .before(requireHostingSite_1.requireHostingSite)
27
27
  .action(async (channelId, options) => {
@@ -7,14 +7,14 @@ const command_1 = require("../command");
7
7
  const requirePermissions_1 = require("../requirePermissions");
8
8
  const projectUtils_1 = require("../projectUtils");
9
9
  const logger_1 = require("../logger");
10
- const requireConfig = require("../requireConfig");
10
+ const requireConfig_1 = require("../requireConfig");
11
11
  const utils_1 = require("../utils");
12
12
  const requireHostingSite_1 = require("../requireHostingSite");
13
13
  const TABLE_HEAD = ["Channel ID", "Last Release Time", "URL", "Expire Time"];
14
14
  exports.default = new command_1.Command("hosting:channel:list")
15
15
  .description("list all Firebase Hosting channels for your project")
16
16
  .option("--site <siteName>", "list channels for the specified site")
17
- .before(requireConfig)
17
+ .before(requireConfig_1.requireConfig)
18
18
  .before(requirePermissions_1.requirePermissions, ["firebasehosting.sites.update"])
19
19
  .before(requireHostingSite_1.requireHostingSite)
20
20
  .action(async (options) => {
@@ -8,7 +8,7 @@ const error_1 = require("../error");
8
8
  const api_1 = require("../hosting/api");
9
9
  const requirePermissions_1 = require("../requirePermissions");
10
10
  const projectUtils_1 = require("../projectUtils");
11
- const requireConfig = require("../requireConfig");
11
+ const requireConfig_1 = require("../requireConfig");
12
12
  const utils_1 = require("../utils");
13
13
  const prompt_1 = require("../prompt");
14
14
  const requireHostingSite_1 = require("../requireHostingSite");
@@ -16,7 +16,7 @@ exports.default = new command_1.Command("hosting:channel:open [channelId]")
16
16
  .description("opens the URL for a Firebase Hosting channel")
17
17
  .help("if unable to open the URL in a browser, it will be displayed in the output")
18
18
  .option("--site <siteId>", "the site to which the channel belongs")
19
- .before(requireConfig)
19
+ .before(requireConfig_1.requireConfig)
20
20
  .before(requirePermissions_1.requirePermissions, ["firebasehosting.sites.get"])
21
21
  .before(requireHostingSite_1.requireHostingSite)
22
22
  .action(async (channelId, options) => {
@@ -8,13 +8,13 @@ const prompt_1 = require("../prompt");
8
8
  const error_1 = require("../error");
9
9
  const requirePermissions_1 = require("../requirePermissions");
10
10
  const projectUtils_1 = require("../projectUtils");
11
- const requireConfig = require("../requireConfig");
11
+ const requireConfig_1 = require("../requireConfig");
12
12
  const logger_1 = require("../logger");
13
13
  const LOG_TAG = "hosting:sites";
14
14
  exports.default = new command_1.Command("hosting:sites:delete <siteId>")
15
15
  .description("delete a Firebase Hosting site")
16
16
  .withForce()
17
- .before(requireConfig)
17
+ .before(requireConfig_1.requireConfig)
18
18
  .before(requirePermissions_1.requirePermissions, ["firebasehosting.sites.delete"])
19
19
  .action(async (siteId, options) => {
20
20
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -5,7 +5,7 @@ var { Command } = require("../command");
5
5
  const { logger } = require("../logger");
6
6
  var utils = require("../utils");
7
7
  var { requirePermissions } = require("../requirePermissions");
8
- var requireConfig = require("../requireConfig");
8
+ var { requireConfig } = require("../requireConfig");
9
9
  var { serve } = require("../serve/index");
10
10
  var { filterTargets } = require("../filterTargets");
11
11
  var { needProjectNumber } = require("../projectUtils");
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
5
  const logger_1 = require("../logger");
6
- const requireConfig = require("../requireConfig");
6
+ const requireConfig_1 = require("../requireConfig");
7
7
  const utils = require("../utils");
8
8
  const error_1 = require("../error");
9
9
  exports.default = new command_1.Command("target:apply <type> <name> <resources...>")
10
10
  .description("apply a deploy target to a resource")
11
- .before(requireConfig)
11
+ .before(requireConfig_1.requireConfig)
12
12
  .action((type, name, resources, options) => {
13
13
  if (!options.project) {
14
14
  throw new error_1.FirebaseError(`Must have an active project to set deploy targets. Try ${clc.bold("firebase use --add")}`);
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
- const requireConfig = require("../requireConfig");
5
+ const requireConfig_1 = require("../requireConfig");
6
6
  const utils = require("../utils");
7
7
  exports.default = new command_1.Command("target:clear <type> <target>")
8
8
  .description("clear all resources from a named resource target")
9
- .before(requireConfig)
9
+ .before(requireConfig_1.requireConfig)
10
10
  .action((type, name, options) => {
11
11
  const existed = options.rc.clearTarget(options.project, type, name);
12
12
  if (existed) {
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
- const requireConfig = require("../requireConfig");
5
+ const requireConfig_1 = require("../requireConfig");
6
6
  const utils = require("../utils");
7
7
  exports.default = new command_1.Command("target:remove <type> <resource>")
8
8
  .description("remove a resource target")
9
- .before(requireConfig)
9
+ .before(requireConfig_1.requireConfig)
10
10
  .action((type, resource, options) => {
11
11
  const name = options.rc.removeTarget(options.project, type, resource);
12
12
  if (name) {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const clc = require("cli-color");
4
4
  const command_1 = require("../command");
5
5
  const logger_1 = require("../logger");
6
- const requireConfig = require("../requireConfig");
6
+ const requireConfig_1 = require("../requireConfig");
7
7
  const utils = require("../utils");
8
8
  function logTargets(type, targets) {
9
9
  logger_1.logger.info(clc.cyan("[ " + type + " ]"));
@@ -13,7 +13,7 @@ function logTargets(type, targets) {
13
13
  }
14
14
  exports.default = new command_1.Command("target [type]")
15
15
  .description("display configured deploy targets for the current project")
16
- .before(requireConfig)
16
+ .before(requireConfig_1.requireConfig)
17
17
  .action((type, options) => {
18
18
  if (!options.project) {
19
19
  return utils.reject("No active project, cannot list deploy targets.");
package/lib/config.js CHANGED
@@ -144,6 +144,9 @@ class Config {
144
144
  fs.ensureFileSync(this.path(p));
145
145
  fs.writeFileSync(this.path(p), content, "utf8");
146
146
  }
147
+ projectFileExists(p) {
148
+ return fs.existsSync(this.path(p));
149
+ }
147
150
  deleteProjectFile(p) {
148
151
  fs.removeSync(this.path(p));
149
152
  }
@@ -81,11 +81,17 @@ async function want(args) {
81
81
  }
82
82
  exports.want = want;
83
83
  async function resolveVersion(ref) {
84
- if (!ref.version || ref.version === "latest") {
85
- return "latest";
86
- }
87
84
  const extensionRef = refs.toExtensionRef(ref);
88
85
  const versions = await extensionsApi.listExtensionVersions(extensionRef);
86
+ if (versions.length === 0) {
87
+ throw new error_1.FirebaseError(`No versions found for ${extensionRef}`);
88
+ }
89
+ if (!ref.version || ref.version === "latest") {
90
+ return versions
91
+ .map((ev) => ev.spec.version)
92
+ .sort(semver.compare)
93
+ .pop();
94
+ }
89
95
  const maxSatisfying = semver.maxSatisfying(versions.map((ev) => ev.spec.version), ref.version);
90
96
  if (!maxSatisfying) {
91
97
  throw new error_1.FirebaseError(`No version of ${extensionRef} matches requested version ${ref.version}`);
@@ -5705,6 +5705,28 @@ exports.default = {
5705
5705
  },
5706
5706
  type: "object",
5707
5707
  },
5708
+ GoogleCloudIdentitytoolkitAdminV2AllowByDefault: {
5709
+ description: "Defines a policy of allowing every region by default and adding disallowed regions to a disallow list.",
5710
+ properties: {
5711
+ disallowedRegions: {
5712
+ description: "Two letter unicode region codes to disallow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json",
5713
+ items: { type: "string" },
5714
+ type: "array",
5715
+ },
5716
+ },
5717
+ type: "object",
5718
+ },
5719
+ GoogleCloudIdentitytoolkitAdminV2AllowlistOnly: {
5720
+ description: "Defines a policy of only allowing regions by explicitly adding them to an allowlist.",
5721
+ properties: {
5722
+ allowedRegions: {
5723
+ description: "Two letter unicode region codes to allow as defined by https://cldr.unicode.org/ The full list of these region codes is here: https://github.com/unicode-cldr/cldr-localenames-full/blob/master/main/en/territories.json",
5724
+ items: { type: "string" },
5725
+ type: "array",
5726
+ },
5727
+ },
5728
+ type: "object",
5729
+ },
5708
5730
  GoogleCloudIdentitytoolkitAdminV2Anonymous: {
5709
5731
  description: "Configuration options related to authenticating an anonymous user.",
5710
5732
  properties: {
@@ -6315,6 +6337,18 @@ exports.default = {
6315
6337
  },
6316
6338
  type: "object",
6317
6339
  },
6340
+ GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig: {
6341
+ description: "Configures the regions where users are allowed to send verification SMS for the project or tenant. This is based on the calling code of the destination phone number.",
6342
+ properties: {
6343
+ allowByDefault: {
6344
+ $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2AllowByDefault",
6345
+ },
6346
+ allowlistOnly: {
6347
+ $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2AllowlistOnly",
6348
+ },
6349
+ },
6350
+ type: "object",
6351
+ },
6318
6352
  GoogleCloudIdentitytoolkitAdminV2SmsTemplate: {
6319
6353
  description: "The template to use when sending an SMS.",
6320
6354
  properties: {
@@ -6424,6 +6458,9 @@ exports.default = {
6424
6458
  readOnly: true,
6425
6459
  type: "string",
6426
6460
  },
6461
+ smsRegionConfig: {
6462
+ $ref: "#/components/schemas/GoogleCloudIdentitytoolkitAdminV2SmsRegionConfig",
6463
+ },
6427
6464
  testPhoneNumbers: {
6428
6465
  additionalProperties: { type: "string" },
6429
6466
  description: "A map of pairs that can be used for MFA. The phone number should be in E.164 format (https://www.itu.int/rec/T-REC-E.164/) and a maximum of 10 pairs can be added (error will be thrown once exceeded).",
@@ -10,7 +10,7 @@ const logger_1 = require("../logger");
10
10
  const path = require("path");
11
11
  const constants_1 = require("./constants");
12
12
  const requireAuth_1 = require("../requireAuth");
13
- const requireConfig = require("../requireConfig");
13
+ const requireConfig_1 = require("../requireConfig");
14
14
  const types_1 = require("./types");
15
15
  const error_1 = require("../error");
16
16
  const registry_1 = require("./registry");
@@ -102,7 +102,7 @@ async function beforeEmulatorCommand(options) {
102
102
  options.config = DEFAULT_CONFIG;
103
103
  }
104
104
  else {
105
- await requireConfig(options);
105
+ await (0, requireConfig_1.requireConfig)(options);
106
106
  }
107
107
  }
108
108
  exports.beforeEmulatorCommand = beforeEmulatorCommand;
@@ -65,6 +65,9 @@ class ExtensionsEmulator {
65
65
  "./functions/package.json",
66
66
  "./functions/node_modules",
67
67
  ];
68
+ if (!fs.existsSync(args.path)) {
69
+ return false;
70
+ }
68
71
  for (const requiredFile of requiredFiles) {
69
72
  const f = path.join(args.path, requiredFile);
70
73
  if (!fs.existsSync(f)) {
@@ -65,6 +65,10 @@ function emulatedFunctionsFromEndpoints(endpoints) {
65
65
  if (backend.isHttpsTriggered(endpoint)) {
66
66
  def.httpsTrigger = endpoint.httpsTrigger;
67
67
  }
68
+ else if (backend.isCallableTriggered(endpoint)) {
69
+ def.httpsTrigger = {};
70
+ def.labels = Object.assign(Object.assign({}, def.labels), { "deployment-callable": "true" });
71
+ }
68
72
  else if (backend.isEventTriggered(endpoint)) {
69
73
  const eventTrigger = endpoint.eventTrigger;
70
74
  if (endpoint.platform === "gcfv1") {
@@ -52,8 +52,10 @@ function createFirebaseEndpoints(emulator) {
52
52
  next();
53
53
  });
54
54
  }
55
- firebaseStorageAPI.use((req, res, next) => {
56
- if (!emulator.rules) {
55
+ firebaseStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
56
+ const bucketId = req.params[0];
57
+ storageLayer.createBucket(bucketId);
58
+ if (!emulator.rulesManager.getRuleset(bucketId)) {
57
59
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN", "Permission denied because no Storage ruleset is currently loaded, check your rules for syntax errors.");
58
60
  return res.status(403).json({
59
61
  error: {
@@ -64,10 +66,6 @@ function createFirebaseEndpoints(emulator) {
64
66
  }
65
67
  next();
66
68
  });
67
- firebaseStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
68
- storageLayer.createBucket(req.params[0]);
69
- next();
70
- });
71
69
  firebaseStorageAPI.get("/b/:bucketId/o/:objectId", async (req, res) => {
72
70
  var _a;
73
71
  let metadata;
@@ -72,7 +72,7 @@ class StorageLayer {
72
72
  const hasValidDownloadToken = ((metadata === null || metadata === void 0 ? void 0 : metadata.downloadTokens) || []).includes((_a = request.downloadToken) !== null && _a !== void 0 ? _a : "");
73
73
  let authorized = skipAuth || hasValidDownloadToken;
74
74
  if (!authorized) {
75
- authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
75
+ authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
76
76
  }
77
77
  if (!authorized) {
78
78
  throw new errors_1.ForbiddenError("Failed auth");
@@ -102,7 +102,7 @@ class StorageLayer {
102
102
  async handleDeleteObject(request, skipAuth = false) {
103
103
  const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
104
104
  const authorized = skipAuth ||
105
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization));
105
+ (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization));
106
106
  if (!authorized) {
107
107
  throw new errors_1.ForbiddenError();
108
108
  }
@@ -134,7 +134,7 @@ class StorageLayer {
134
134
  async handleUpdateObjectMetadata(request, skipAuth = false) {
135
135
  const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
136
136
  const authorized = skipAuth ||
137
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), types_1.RulesetOperationMethod.UPDATE, {
137
+ (await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
138
138
  before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
139
139
  after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
140
140
  }, request.authorization));
@@ -163,7 +163,7 @@ class StorageLayer {
163
163
  customMetadata: upload.metadata.metadata,
164
164
  }, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
165
165
  const authorized = skipAuth ||
166
- (await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization));
166
+ (await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization));
167
167
  if (!authorized) {
168
168
  this._persistence.deleteFile(upload.path);
169
169
  throw new errors_1.ForbiddenError();
@@ -210,7 +210,7 @@ class StorageLayer {
210
210
  async handleListObjects(request, skipAuth = false) {
211
211
  var _a, _b, _c;
212
212
  const authorized = skipAuth ||
213
- (await this._rulesValidator.validate(["b", request.bucketId, "o", request.prefix].join("/"), types_1.RulesetOperationMethod.LIST, {}, request.authorization));
213
+ (await this._rulesValidator.validate(["b", request.bucketId, "o", request.prefix].join("/"), request.bucketId, types_1.RulesetOperationMethod.LIST, {}, request.authorization));
214
214
  if (!authorized) {
215
215
  throw new errors_1.ForbiddenError();
216
216
  }
@@ -18,9 +18,9 @@ class StorageEmulator {
18
18
  this.args = args;
19
19
  this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
20
20
  this._rulesRuntime = new runtime_1.StorageRulesRuntime();
21
- this._rulesManager = new manager_1.StorageRulesManager(this._rulesRuntime);
21
+ this._rulesManager = (0, manager_1.createStorageRulesManager)(this.args.rules, this._rulesRuntime);
22
22
  this._persistence = new persistence_1.Persistence(this.getPersistenceTmpDir());
23
- this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)(() => this.rules), (0, utils_1.getAdminCredentialValidator)(), this._persistence);
23
+ this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)((resource) => this._rulesManager.getRuleset(resource)), (0, utils_1.getAdminCredentialValidator)(), this._persistence);
24
24
  this._uploadService = new upload_1.UploadService(this._persistence);
25
25
  }
26
26
  get storageLayer() {
@@ -29,8 +29,8 @@ class StorageEmulator {
29
29
  get uploadService() {
30
30
  return this._uploadService;
31
31
  }
32
- get rules() {
33
- return this._rulesManager.ruleset;
32
+ get rulesManager() {
33
+ return this._rulesManager;
34
34
  }
35
35
  get logger() {
36
36
  return this._logger;
@@ -43,19 +43,16 @@ class StorageEmulator {
43
43
  async start() {
44
44
  const { host, port } = this.getInfo();
45
45
  await this._rulesRuntime.start(this.args.auto_download);
46
- await this._rulesManager.setSourceFile(this.args.rules[0].rules);
46
+ await this._rulesManager.start();
47
47
  this._app = await (0, server_1.createApp)(this.args.projectId, this);
48
48
  const server = this._app.listen(port, host);
49
49
  this.destroyServer = utils.createDestroyer(server);
50
50
  }
51
51
  async connect() {
52
52
  }
53
- async setRules(rules) {
54
- return this._rulesManager.setSourceFile(rules);
55
- }
56
53
  async stop() {
57
54
  await this._persistence.deleteAll();
58
- await this._rulesManager.close();
55
+ await this._rulesManager.stop();
59
56
  return this.destroyServer ? this.destroyServer() : Promise.resolve();
60
57
  }
61
58
  getInfo() {
@@ -2,8 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getStorageRulesConfig = void 0;
4
4
  const error_1 = require("../../../error");
5
- function getAbsoluteRulesPath(rules, options) {
6
- return options.config.path(rules);
5
+ const fsutils_1 = require("../../../fsutils");
6
+ function getSourceFile(rules, options) {
7
+ const path = options.config.path(rules);
8
+ return { name: path, content: (0, fsutils_1.readFile)(path) };
7
9
  }
8
10
  function getStorageRulesConfig(projectId, options) {
9
11
  const storageConfig = options.config.data.storage;
@@ -14,8 +16,7 @@ function getStorageRulesConfig(projectId, options) {
14
16
  if (!storageConfig.rules) {
15
17
  throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
16
18
  }
17
- const resource = "default";
18
- return [{ resource, rules: getAbsoluteRulesPath(storageConfig.rules, options) }];
19
+ return getSourceFile(storageConfig.rules, options);
19
20
  }
20
21
  const results = [];
21
22
  const { rc } = options;
@@ -25,7 +26,7 @@ function getStorageRulesConfig(projectId, options) {
25
26
  }
26
27
  rc.requireTarget(projectId, "storage", targetConfig.target);
27
28
  rc.target(projectId, "storage", targetConfig.target).forEach((resource) => {
28
- results.push({ resource, rules: getAbsoluteRulesPath(targetConfig.rules, options) });
29
+ results.push({ resource, rules: getSourceFile(targetConfig.rules, options) });
29
30
  });
30
31
  }
31
32
  return results;
@@ -1,39 +1,37 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StorageRulesManager = void 0;
3
+ exports.createStorageRulesManager = void 0;
4
4
  const chokidar = require("chokidar");
5
- const fs = require("fs");
6
5
  const emulatorLogger_1 = require("../../emulatorLogger");
7
6
  const types_1 = require("../../types");
8
- const error_1 = require("../../../error");
9
- class StorageRulesManager {
10
- constructor(_runtime) {
7
+ const runtime_1 = require("./runtime");
8
+ function createStorageRulesManager(rules, runtime) {
9
+ return Array.isArray(rules)
10
+ ? new ResourceBasedStorageRulesManager(rules, runtime)
11
+ : new DefaultStorageRulesManager(rules, runtime);
12
+ }
13
+ exports.createStorageRulesManager = createStorageRulesManager;
14
+ class DefaultStorageRulesManager {
15
+ constructor(_rules, _runtime) {
11
16
  this._runtime = _runtime;
12
17
  this._watcher = new chokidar.FSWatcher();
13
18
  this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
19
+ this._rules = _rules;
20
+ }
21
+ start() {
22
+ return this.updateSourceFile(this._rules);
14
23
  }
15
- get ruleset() {
24
+ getRuleset() {
16
25
  return this._ruleset;
17
26
  }
18
- async setSourceFile(rules) {
19
- var _a;
20
- const prevRulesFile = (_a = this._sourceFile) === null || _a === void 0 ? void 0 : _a.name;
21
- let rulesFile;
22
- if (typeof rules === "string") {
23
- this._sourceFile = { name: rules, content: readSourceFile(rules) };
24
- rulesFile = rules;
25
- }
26
- else {
27
- this._sourceFile = rules;
28
- rulesFile = rules.name;
29
- }
27
+ async updateSourceFile(rules) {
28
+ const prevRulesFile = this._rules.name;
29
+ this._rules = rules;
30
30
  const issues = await this.loadRuleset();
31
- this.updateWatcher(rulesFile, prevRulesFile);
31
+ this.updateWatcher(rules.name, prevRulesFile);
32
32
  return issues;
33
33
  }
34
- async close() {
35
- delete this._sourceFile;
36
- delete this._ruleset;
34
+ async stop() {
37
35
  await this._watcher.close();
38
36
  }
39
37
  updateWatcher(rulesFile, prevRulesFile) {
@@ -49,12 +47,11 @@ class StorageRulesManager {
49
47
  });
50
48
  }
51
49
  async loadRuleset() {
52
- const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._sourceFile] });
50
+ const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._rules] });
53
51
  if (ruleset) {
54
52
  this._ruleset = ruleset;
55
53
  return issues;
56
54
  }
57
- delete this._ruleset;
58
55
  issues.all.forEach((issue) => {
59
56
  try {
60
57
  const parsedIssue = JSON.parse(issue);
@@ -67,15 +64,35 @@ class StorageRulesManager {
67
64
  return issues;
68
65
  }
69
66
  }
70
- exports.StorageRulesManager = StorageRulesManager;
71
- function readSourceFile(fileName) {
72
- try {
73
- return fs.readFileSync(fileName).toString();
67
+ class ResourceBasedStorageRulesManager {
68
+ constructor(_rulesConfig, _runtime) {
69
+ this._runtime = _runtime;
70
+ this._rulesManagers = new Map();
71
+ for (const { resource, rules } of _rulesConfig) {
72
+ this.createRulesManager(resource, rules);
73
+ }
74
74
  }
75
- catch (error) {
76
- if (error.code === "ENOENT") {
77
- throw new error_1.FirebaseError(`File not found: ${fileName}`);
75
+ async start() {
76
+ const allIssues = new runtime_1.StorageRulesIssues();
77
+ for (const rulesManager of this._rulesManagers.values()) {
78
+ allIssues.extend(await rulesManager.start());
78
79
  }
79
- throw error;
80
+ return allIssues;
81
+ }
82
+ getRuleset(resource) {
83
+ var _a;
84
+ return (_a = this._rulesManagers.get(resource)) === null || _a === void 0 ? void 0 : _a.getRuleset();
85
+ }
86
+ updateSourceFile(rules, resource) {
87
+ const rulesManager = this._rulesManagers.get(resource) || this.createRulesManager(resource, rules);
88
+ return rulesManager.updateSourceFile(rules);
89
+ }
90
+ async stop() {
91
+ await Promise.all(Array.from(this._rulesManagers.values(), async (rulesManager) => await rulesManager.stop()));
92
+ }
93
+ createRulesManager(resource, rules) {
94
+ const rulesManager = new DefaultStorageRulesManager(rules, this._runtime);
95
+ this._rulesManagers.set(resource, rulesManager);
96
+ return rulesManager;
80
97
  }
81
98
  }
@@ -49,6 +49,10 @@ class StorageRulesIssues {
49
49
  exist() {
50
50
  return !!(this.errors.length || this.warnings.length);
51
51
  }
52
+ extend(other) {
53
+ this.errors.push(...other.errors);
54
+ this.warnings.push(...other.warnings);
55
+ }
52
56
  }
53
57
  exports.StorageRulesIssues = StorageRulesIssues;
54
58
  class StorageRulesRuntime {
@@ -5,9 +5,9 @@ const emulatorLogger_1 = require("../../emulatorLogger");
5
5
  const types_1 = require("../../types");
6
6
  function getRulesValidator(rulesetProvider) {
7
7
  return {
8
- validate: async (path, method, variableOverrides, authorization) => {
8
+ validate: async (path, bucketId, method, variableOverrides, authorization) => {
9
9
  return await isPermitted({
10
- ruleset: rulesetProvider(),
10
+ ruleset: rulesetProvider(bucketId),
11
11
  file: variableOverrides,
12
12
  path,
13
13
  method,
@@ -58,7 +58,7 @@ function createApp(defaultProjectId, emulator) {
58
58
  }
59
59
  const name = file.name;
60
60
  const content = file.content;
61
- const issues = await emulator.setRules({ name, content });
61
+ const issues = await emulator.rulesManager.updateSourceFile({ name, content }, req.params.bucketId);
62
62
  if (issues.errors.length > 0) {
63
63
  res.status(400).json({
64
64
  message: "There was an error updating rules, see logs for more details",
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.ask = exports.checkResponse = void 0;
3
+ exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.ask = exports.checkResponse = exports.SecretLocation = void 0;
4
4
  const _ = require("lodash");
5
5
  const clc = require("cli-color");
6
6
  const { marked } = require("marked");
@@ -12,10 +12,15 @@ const utils_1 = require("./utils");
12
12
  const logger_1 = require("../logger");
13
13
  const prompt_1 = require("../prompt");
14
14
  const utils = require("../utils");
15
+ var SecretLocation;
16
+ (function (SecretLocation) {
17
+ SecretLocation[SecretLocation["CLOUD"] = 1] = "CLOUD";
18
+ SecretLocation[SecretLocation["LOCAL"] = 2] = "LOCAL";
19
+ })(SecretLocation = exports.SecretLocation || (exports.SecretLocation = {}));
15
20
  var SecretUpdateAction;
16
21
  (function (SecretUpdateAction) {
17
- SecretUpdateAction[SecretUpdateAction["LEAVE"] = 0] = "LEAVE";
18
- SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 1] = "SET_NEW";
22
+ SecretUpdateAction[SecretUpdateAction["LEAVE"] = 1] = "LEAVE";
23
+ SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 2] = "SET_NEW";
19
24
  })(SecretUpdateAction || (SecretUpdateAction = {}));
20
25
  function checkResponse(response, spec) {
21
26
  let valid = true;
@@ -82,6 +87,8 @@ async function askForParam(args) {
82
87
  const paramSpec = args.paramSpec;
83
88
  let valid = false;
84
89
  let response = "";
90
+ let responseForLocal;
91
+ let secretLocations = [];
85
92
  const description = paramSpec.description || "";
86
93
  const label = paramSpec.label.trim();
87
94
  logger_1.logger.info(`\n${clc.bold(label)}${clc.bold(paramSpec.required ? "" : " (Optional)")}: ${marked(description).trim()}`);
@@ -116,16 +123,23 @@ async function askForParam(args) {
116
123
  }
117
124
  },
118
125
  message: "Which options do you want enabled for this parameter? " +
119
- "Press Space to select, then Enter to confirm your choices. " +
120
- "You may select multiple options.",
126
+ "Press Space to select, then Enter to confirm your choices. ",
121
127
  choices: (0, utils_1.convertExtensionOptionToLabeledList)(paramSpec.options),
122
128
  });
123
129
  valid = checkResponse(response, paramSpec);
124
130
  break;
125
131
  case extensionsApi_1.ParamType.SECRET:
126
- response = args.reconfiguring
127
- ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec)
128
- : await promptCreateSecret(args.projectId, args.instanceId, paramSpec);
132
+ while (!secretLocations.length) {
133
+ secretLocations = await promptSecretLocations();
134
+ }
135
+ if (secretLocations.includes(SecretLocation.CLOUD.toString())) {
136
+ response = args.reconfiguring
137
+ ? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec)
138
+ : await promptCreateSecret(args.projectId, args.instanceId, paramSpec);
139
+ }
140
+ if (secretLocations.includes(SecretLocation.LOCAL.toString())) {
141
+ responseForLocal = await promptLocalSecret(args.instanceId, paramSpec);
142
+ }
129
143
  valid = true;
130
144
  break;
131
145
  default:
@@ -138,9 +152,38 @@ async function askForParam(args) {
138
152
  valid = checkResponse(response, paramSpec);
139
153
  }
140
154
  }
141
- return { baseValue: response };
155
+ return Object.assign({ baseValue: response }, (responseForLocal ? { local: responseForLocal } : {}));
142
156
  }
143
157
  exports.askForParam = askForParam;
158
+ async function promptSecretLocations() {
159
+ return await (0, prompt_1.promptOnce)({
160
+ name: "input",
161
+ type: "checkbox",
162
+ message: "Where would you like to store your secrets? You must select at least one value",
163
+ choices: [
164
+ {
165
+ checked: true,
166
+ name: "Google Cloud Secret Manager",
167
+ value: SecretLocation.CLOUD.toString(),
168
+ },
169
+ {
170
+ checked: false,
171
+ name: "Local file (Only used by Firebase Emulator)",
172
+ value: SecretLocation.LOCAL.toString(),
173
+ },
174
+ ],
175
+ });
176
+ }
177
+ async function promptLocalSecret(instanceId, paramSpec) {
178
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
179
+ const value = await (0, prompt_1.promptOnce)({
180
+ name: paramSpec.param,
181
+ type: "input",
182
+ message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
183
+ `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
184
+ });
185
+ return value;
186
+ }
144
187
  async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
145
188
  const action = await (0, prompt_1.promptOnce)({
146
189
  type: "list",
@@ -1,6 +1,6 @@
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.writeToManifest = exports.ENV_DIRECTORY = void 0;
3
+ exports.showPreviewWarning = exports.showDeprecationWarning = exports.readInstanceParam = exports.getInstanceRef = 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");
@@ -11,6 +11,7 @@ const paramHelper_1 = require("./paramHelper");
11
11
  const error_1 = require("../error");
12
12
  const utils = require("../utils");
13
13
  const extensionsHelper_1 = require("./extensionsHelper");
14
+ const extensionsApi_1 = require("./extensionsApi");
14
15
  exports.ENV_DIRECTORY = "extensions";
15
16
  async function writeToManifest(specs, config, options, allowOverwrite = false) {
16
17
  if (config.has("extensions") &&
@@ -36,8 +37,33 @@ async function writeToManifest(specs, config, options, allowOverwrite = false) {
36
37
  }
37
38
  writeExtensionsToFirebaseJson(specs, config);
38
39
  await writeEnvFiles(specs, config, options.force);
40
+ await writeLocalSecrets(specs, config, options.force);
39
41
  }
40
42
  exports.writeToManifest = writeToManifest;
43
+ async function writeLocalSecrets(specs, config, force) {
44
+ for (const spec of specs) {
45
+ if (!spec.paramSpecs) {
46
+ continue;
47
+ }
48
+ const writeBuffer = {};
49
+ const locallyOverridenSecretParams = spec.paramSpecs.filter((p) => p.type === extensionsApi_1.ParamType.SECRET && spec.params[p.param].local);
50
+ for (const paramSpec of locallyOverridenSecretParams) {
51
+ const key = paramSpec.param;
52
+ const localValue = spec.params[key].local;
53
+ writeBuffer[key] = localValue;
54
+ }
55
+ const content = Object.entries(writeBuffer)
56
+ .sort((a, b) => {
57
+ return a[0].localeCompare(b[0]);
58
+ })
59
+ .map((r) => `${r[0]}=${r[1]}`)
60
+ .join("\n");
61
+ if (content) {
62
+ await config.askWriteProjectFile(`extensions/${spec.instanceId}.secret.local`, content, force);
63
+ }
64
+ }
65
+ }
66
+ exports.writeLocalSecrets = writeLocalSecrets;
41
67
  function removeFromManifest(instanceId, config) {
42
68
  if (!instanceExists(instanceId, config)) {
43
69
  throw new error_1.FirebaseError(`Extension instance ${instanceId} not found in firebase.json.`);
@@ -49,8 +75,14 @@ function removeFromManifest(instanceId, config) {
49
75
  logger_1.logger.info(`Removed extension instance ${instanceId} from firebase.json`);
50
76
  config.deleteProjectFile(`extensions/${instanceId}.env`);
51
77
  logger_1.logger.info(`Removed extension instance environment config extensions/${instanceId}.env`);
52
- config.deleteProjectFile(`extensions/${instanceId}.env.local`);
53
- logger_1.logger.info(`Removed extension instance environment config extensions/${instanceId}.env.local`);
78
+ if (config.projectFileExists(`extensions/${instanceId}.env.local`)) {
79
+ config.deleteProjectFile(`extensions/${instanceId}.env.local`);
80
+ logger_1.logger.info(`Removed extension instance local environment config extensions/${instanceId}.env.local`);
81
+ }
82
+ if (config.projectFileExists(`extensions/${instanceId}.secret.local`)) {
83
+ config.deleteProjectFile(`extensions/${instanceId}.secret.local`);
84
+ logger_1.logger.info(`Removed extension instance local secret config extensions/${instanceId}.secret.local`);
85
+ }
54
86
  }
55
87
  exports.removeFromManifest = removeFromManifest;
56
88
  function loadConfig(options) {
@@ -88,7 +120,7 @@ async function writeEnvFiles(specs, config, force) {
88
120
  .sort((a, b) => {
89
121
  return a[0].localeCompare(b[0]);
90
122
  })
91
- .map((r) => `${r[0]}=${r[1]}`)
123
+ .map((r) => `${r[0]}=${r[1].baseValue}`)
92
124
  .join("\n");
93
125
  await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
94
126
  }
@@ -104,6 +104,7 @@ async function getParamsForUpdate(args) {
104
104
  }
105
105
  exports.getParamsForUpdate = getParamsForUpdate;
106
106
  async function promptForNewParams(args) {
107
+ const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams);
107
108
  const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
108
109
  const comparer = (param1, param2) => {
109
110
  return param1.type === param2.type && param1.param === param2.param;
@@ -117,7 +118,7 @@ async function promptForNewParams(args) {
117
118
  logger_1.logger.info("The following params will no longer be used:");
118
119
  paramsDiffDeletions.forEach((param) => {
119
120
  logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
120
- delete args.currentParams[param.param.toUpperCase()];
121
+ delete newParamBindingOptions[param.param.toUpperCase()];
121
122
  });
122
123
  }
123
124
  if (paramsDiffAdditions.length) {
@@ -129,10 +130,10 @@ async function promptForNewParams(args) {
129
130
  paramSpec: param,
130
131
  reconfiguring: false,
131
132
  });
132
- args.currentParams[param.param] = chosenValue.baseValue;
133
+ newParamBindingOptions[param.param] = chosenValue;
133
134
  }
134
135
  }
135
- return buildBindingOptionsWithBaseValue(args.currentParams);
136
+ return newParamBindingOptions;
136
137
  }
137
138
  exports.promptForNewParams = promptForNewParams;
138
139
  function getParamsFromFile(args) {
package/lib/fsutils.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.dirExistsSync = exports.fileExistsSync = void 0;
3
+ exports.readFile = exports.dirExistsSync = exports.fileExistsSync = void 0;
4
4
  const fs_1 = require("fs");
5
+ const error_1 = require("./error");
5
6
  function fileExistsSync(path) {
6
7
  try {
7
8
  return (0, fs_1.statSync)(path).isFile();
@@ -20,3 +21,15 @@ function dirExistsSync(path) {
20
21
  }
21
22
  }
22
23
  exports.dirExistsSync = dirExistsSync;
24
+ function readFile(path) {
25
+ try {
26
+ return (0, fs_1.readFileSync)(path).toString();
27
+ }
28
+ catch (e) {
29
+ if (e.code === "ENOENT") {
30
+ throw new error_1.FirebaseError(`File not found: ${path}`);
31
+ }
32
+ throw e;
33
+ }
34
+ }
35
+ exports.readFile = readFile;
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
- var { FirebaseError } = require("./error");
3
- module.exports = function (options) {
4
- if (options.config) {
5
- return Promise.resolve();
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireConfig = void 0;
4
+ const error_1 = require("./error");
5
+ async function requireConfig(options) {
6
+ await Promise.resolve();
7
+ if (!options.config) {
8
+ throw options.configError
9
+ ? options.configError
10
+ : new error_1.FirebaseError("Not in a Firebase project directory (could not locate firebase.json)");
6
11
  }
7
- return Promise.reject(options.configError ||
8
- new FirebaseError("Not in a Firebase project directory (could not locate firebase.json)", {
9
- exit: 1,
10
- }));
11
- };
12
+ }
13
+ exports.requireConfig = requireConfig;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "10.3.1",
3
+ "version": "10.4.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "firebase-tools",
9
- "version": "10.3.1",
9
+ "version": "10.4.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@google-cloud/pubsub": "^2.18.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "10.3.1",
3
+ "version": "10.4.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -1,44 +0,0 @@
1
- "use strict";
2
- var fs = require("fs");
3
- var path = require("path");
4
- var tar = require("tar");
5
- var tmp = require("tmp");
6
- var { listFiles } = require("./listFiles");
7
- var { FirebaseError } = require("./error");
8
- var fsutils = require("./fsutils");
9
- module.exports = function (options) {
10
- var hostingConfig = options.config.get("hosting");
11
- var publicDir = options.config.path(hostingConfig.public);
12
- var indexPath = path.join(publicDir, "index.html");
13
- var tmpFile = tmp.fileSync({
14
- prefix: "firebase-upload-",
15
- postfix: ".tar.gz",
16
- });
17
- var manifest = listFiles(publicDir, hostingConfig.ignore);
18
- return tar
19
- .c({
20
- gzip: true,
21
- file: tmpFile.name,
22
- cwd: publicDir,
23
- prefix: "public",
24
- follow: true,
25
- noDirRecurse: true,
26
- portable: true,
27
- }, manifest.slice(0))
28
- .then(function () {
29
- var stats = fs.statSync(tmpFile.name);
30
- return {
31
- file: tmpFile.name,
32
- stream: fs.createReadStream(tmpFile.name),
33
- manifest: manifest,
34
- foundIndex: fsutils.fileExistsSync(indexPath),
35
- size: stats.size,
36
- };
37
- })
38
- .catch(function (err) {
39
- return Promise.reject(new FirebaseError("There was an issue preparing Hosting files for upload.", {
40
- original: err,
41
- exit: 2,
42
- }));
43
- });
44
- };