firebase-tools 10.3.0 → 10.4.1

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 (60) hide show
  1. package/lib/accountExporter.js +95 -84
  2. package/lib/commands/deploy.js +1 -1
  3. package/lib/commands/experimental-functions-shell.js +1 -1
  4. package/lib/commands/ext-configure.js +13 -6
  5. package/lib/commands/ext-export.js +7 -1
  6. package/lib/commands/ext-install.js +12 -7
  7. package/lib/commands/ext-update.js +5 -3
  8. package/lib/commands/functions-config-export.js +5 -3
  9. package/lib/commands/functions-shell.js +1 -1
  10. package/lib/commands/hosting-channel-create.js +2 -2
  11. package/lib/commands/hosting-channel-delete.js +2 -2
  12. package/lib/commands/hosting-channel-deploy.js +2 -2
  13. package/lib/commands/hosting-channel-list.js +2 -2
  14. package/lib/commands/hosting-channel-open.js +2 -2
  15. package/lib/commands/hosting-sites-delete.js +2 -2
  16. package/lib/commands/serve.js +1 -1
  17. package/lib/commands/target-apply.js +2 -2
  18. package/lib/commands/target-clear.js +2 -2
  19. package/lib/commands/target-remove.js +2 -2
  20. package/lib/commands/target.js +2 -2
  21. package/lib/config.js +14 -4
  22. package/lib/deploy/extensions/planner.js +9 -3
  23. package/lib/deploy/functions/deploy.js +3 -7
  24. package/lib/deploy/functions/prepare.js +7 -5
  25. package/lib/deploy/functions/prepareFunctionsUpload.js +7 -13
  26. package/lib/deploy/functions/release/fabricator.js +13 -1
  27. package/lib/deploy/functions/release/index.js +1 -1
  28. package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -5
  29. package/lib/deploy/hosting/deploy.js +10 -0
  30. package/lib/emulator/auth/apiSpec.js +37 -0
  31. package/lib/emulator/commandUtils.js +2 -2
  32. package/lib/emulator/controller.js +14 -8
  33. package/lib/emulator/downloadableEmulators.js +5 -5
  34. package/lib/emulator/extensionsEmulator.js +3 -0
  35. package/lib/emulator/functionsEmulator.js +4 -4
  36. package/lib/emulator/functionsEmulatorShared.js +17 -1
  37. package/lib/emulator/storage/apis/firebase.js +4 -6
  38. package/lib/emulator/storage/files.js +5 -5
  39. package/lib/emulator/storage/index.js +6 -9
  40. package/lib/emulator/storage/rules/config.js +6 -5
  41. package/lib/emulator/storage/rules/manager.js +49 -32
  42. package/lib/emulator/storage/rules/runtime.js +4 -0
  43. package/lib/emulator/storage/rules/utils.js +2 -2
  44. package/lib/emulator/storage/server.js +1 -1
  45. package/lib/extensions/askUserForParam.js +103 -28
  46. package/lib/extensions/manifest.js +38 -6
  47. package/lib/extensions/paramHelper.js +28 -6
  48. package/lib/fsutils.js +14 -1
  49. package/lib/functions/projectConfig.js +34 -0
  50. package/lib/gcp/cloudfunctions.js +5 -4
  51. package/lib/init/features/functions/index.js +4 -2
  52. package/lib/init/features/hosting/index.js +32 -41
  53. package/lib/init/features/index.js +22 -12
  54. package/lib/init/index.js +28 -11
  55. package/lib/requireConfig.js +11 -9
  56. package/lib/serve/functions.js +5 -5
  57. package/npm-shrinkwrap.json +2 -2
  58. package/package.json +1 -1
  59. package/schema/firebase-config.json +93 -36
  60. package/lib/prepareUpload.js +0 -44
@@ -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.ask = exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = 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;
@@ -55,9 +60,35 @@ function checkResponse(response, spec) {
55
60
  return valid;
56
61
  }
57
62
  exports.checkResponse = checkResponse;
58
- async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
63
+ async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
64
+ if (_.isEmpty(paramSpecs)) {
65
+ logger_1.logger.debug("No params were specified for this extension.");
66
+ return {};
67
+ }
68
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
69
+ const substituted = (0, extensionsHelper_1.substituteParams)(paramSpecs, firebaseProjectParams);
70
+ const result = {};
71
+ const promises = _.map(substituted, (paramSpec) => {
72
+ return async () => {
73
+ result[paramSpec.param] = await askForParam({
74
+ projectId,
75
+ instanceId,
76
+ paramSpec,
77
+ reconfiguring,
78
+ });
79
+ };
80
+ });
81
+ await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
82
+ logger_1.logger.info();
83
+ return result;
84
+ }
85
+ exports.ask = ask;
86
+ async function askForParam(args) {
87
+ const paramSpec = args.paramSpec;
59
88
  let valid = false;
60
89
  let response = "";
90
+ let responseForLocal;
91
+ let secretLocations = [];
61
92
  const description = paramSpec.description || "";
62
93
  const label = paramSpec.label.trim();
63
94
  logger_1.logger.info(`\n${clc.bold(label)}${clc.bold(paramSpec.required ? "" : " (Optional)")}: ${marked(description).trim()}`);
@@ -92,16 +123,23 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
92
123
  }
93
124
  },
94
125
  message: "Which options do you want enabled for this parameter? " +
95
- "Press Space to select, then Enter to confirm your choices. " +
96
- "You may select multiple options.",
126
+ "Press Space to select, then Enter to confirm your choices. ",
97
127
  choices: (0, utils_1.convertExtensionOptionToLabeledList)(paramSpec.options),
98
128
  });
99
129
  valid = checkResponse(response, paramSpec);
100
130
  break;
101
131
  case extensionsApi_1.ParamType.SECRET:
102
- response = reconfiguring
103
- ? await promptReconfigureSecret(projectId, instanceId, paramSpec)
104
- : await promptCreateSecret(projectId, instanceId, paramSpec);
132
+ do {
133
+ secretLocations = await promptSecretLocations(paramSpec);
134
+ } while (!isValidSecretLocations(secretLocations, paramSpec));
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
+ }
105
143
  valid = true;
106
144
  break;
107
145
  default:
@@ -114,9 +152,64 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
114
152
  valid = checkResponse(response, paramSpec);
115
153
  }
116
154
  }
117
- return response;
155
+ return Object.assign({ baseValue: response }, (responseForLocal ? { local: responseForLocal } : {}));
118
156
  }
119
157
  exports.askForParam = askForParam;
158
+ function isValidSecretLocations(secretLocations, paramSpec) {
159
+ if (paramSpec.required) {
160
+ return !!secretLocations.length;
161
+ }
162
+ return true;
163
+ }
164
+ async function promptSecretLocations(paramSpec) {
165
+ if (paramSpec.required) {
166
+ return await (0, prompt_1.promptOnce)({
167
+ name: "input",
168
+ type: "checkbox",
169
+ message: "Where would you like to store your secrets? You must select at least one value",
170
+ choices: [
171
+ {
172
+ checked: true,
173
+ name: "Google Cloud Secret Manager",
174
+ value: SecretLocation.CLOUD.toString(),
175
+ },
176
+ {
177
+ checked: false,
178
+ name: "Local file (Only used by Firebase Emulator)",
179
+ value: SecretLocation.LOCAL.toString(),
180
+ },
181
+ ],
182
+ });
183
+ }
184
+ return await (0, prompt_1.promptOnce)({
185
+ name: "input",
186
+ type: "checkbox",
187
+ message: "Where would you like to store your secrets? " +
188
+ "If you don't want to set this optional secret, leave both options unselected to skip it",
189
+ choices: [
190
+ {
191
+ checked: false,
192
+ name: "Google Cloud Secret Manager",
193
+ value: SecretLocation.CLOUD.toString(),
194
+ },
195
+ {
196
+ checked: false,
197
+ name: "Local file (Only used by Firebase Emulator)",
198
+ value: SecretLocation.LOCAL.toString(),
199
+ },
200
+ ],
201
+ });
202
+ }
203
+ async function promptLocalSecret(instanceId, paramSpec) {
204
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
205
+ const value = await (0, prompt_1.promptOnce)({
206
+ name: paramSpec.param,
207
+ type: "input",
208
+ message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
209
+ `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
210
+ });
211
+ return value;
212
+ }
120
213
  async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
121
214
  const action = await (0, prompt_1.promptOnce)({
122
215
  type: "list",
@@ -210,21 +303,3 @@ function getInquirerDefault(options, def) {
210
303
  return defaultOption ? defaultOption.label || defaultOption.value : "";
211
304
  }
212
305
  exports.getInquirerDefault = getInquirerDefault;
213
- async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
214
- if (_.isEmpty(paramSpecs)) {
215
- logger_1.logger.debug("No params were specified for this extension.");
216
- return {};
217
- }
218
- utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
219
- const substituted = (0, extensionsHelper_1.substituteParams)(paramSpecs, firebaseProjectParams);
220
- const result = {};
221
- const promises = _.map(substituted, (paramSpec) => {
222
- return async () => {
223
- result[paramSpec.param] = await askForParam(projectId, instanceId, paramSpec, reconfiguring);
224
- };
225
- });
226
- await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
227
- logger_1.logger.info();
228
- return result;
229
- }
230
- exports.ask = ask;
@@ -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 = 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,7 +11,8 @@ 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 ENV_DIRECTORY = "extensions";
14
+ const extensionsApi_1 = require("./extensionsApi");
15
+ exports.ENV_DIRECTORY = "extensions";
15
16
  async function writeToManifest(specs, config, options, allowOverwrite = false) {
16
17
  if (config.has("extensions") &&
17
18
  Object.keys(config.get("extensions")).length &&
@@ -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
  }
@@ -125,7 +157,7 @@ function readInstanceParam(args) {
125
157
  }
126
158
  exports.readInstanceParam = readInstanceParam;
127
159
  function readParamsFile(projectDir, fileName) {
128
- const paramPath = path.join(projectDir, ENV_DIRECTORY, fileName);
160
+ const paramPath = path.join(projectDir, exports.ENV_DIRECTORY, fileName);
129
161
  const params = (0, paramHelper_1.readEnvFile)(paramPath);
130
162
  return params;
131
163
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = void 0;
3
+ exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = exports.buildBindingOptionsWithBaseValue = exports.getBaseParamBindings = void 0;
4
4
  const _ = require("lodash");
5
5
  const path = require("path");
6
6
  const clc = require("cli-color");
@@ -11,6 +11,22 @@ const extensionsHelper_1 = require("./extensionsHelper");
11
11
  const askUserForParam = require("./askUserForParam");
12
12
  const track = require("../track");
13
13
  const env = require("../functions/env");
14
+ function getBaseParamBindings(params) {
15
+ let ret = {};
16
+ for (const [k, v] of Object.entries(params)) {
17
+ ret = Object.assign(Object.assign({}, ret), { [k]: v.baseValue });
18
+ }
19
+ return ret;
20
+ }
21
+ exports.getBaseParamBindings = getBaseParamBindings;
22
+ function buildBindingOptionsWithBaseValue(baseParams) {
23
+ let paramOptions = {};
24
+ for (const [k, v] of Object.entries(baseParams)) {
25
+ paramOptions = Object.assign(Object.assign({}, paramOptions), { [k]: { baseValue: v } });
26
+ }
27
+ return paramOptions;
28
+ }
29
+ exports.buildBindingOptionsWithBaseValue = buildBindingOptionsWithBaseValue;
14
30
  function setNewDefaults(params, newDefaults) {
15
31
  params.forEach((param) => {
16
32
  if (newDefaults[param.param.toUpperCase()]) {
@@ -88,6 +104,7 @@ async function getParamsForUpdate(args) {
88
104
  }
89
105
  exports.getParamsForUpdate = getParamsForUpdate;
90
106
  async function promptForNewParams(args) {
107
+ const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams);
91
108
  const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
92
109
  const comparer = (param1, param2) => {
93
110
  return param1.type === param2.type && param1.param === param2.param;
@@ -101,17 +118,22 @@ async function promptForNewParams(args) {
101
118
  logger_1.logger.info("The following params will no longer be used:");
102
119
  paramsDiffDeletions.forEach((param) => {
103
120
  logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
104
- delete args.currentParams[param.param.toUpperCase()];
121
+ delete newParamBindingOptions[param.param.toUpperCase()];
105
122
  });
106
123
  }
107
124
  if (paramsDiffAdditions.length) {
108
125
  logger_1.logger.info("To update this instance, configure the following new parameters:");
109
126
  for (const param of paramsDiffAdditions) {
110
- const chosenValue = await askUserForParam.askForParam(args.projectId, args.instanceId, param, false);
111
- args.currentParams[param.param] = chosenValue;
127
+ const chosenValue = await askUserForParam.askForParam({
128
+ projectId: args.projectId,
129
+ instanceId: args.instanceId,
130
+ paramSpec: param,
131
+ reconfiguring: false,
132
+ });
133
+ newParamBindingOptions[param.param] = chosenValue;
112
134
  }
113
135
  }
114
- return args.currentParams;
136
+ return newParamBindingOptions;
115
137
  }
116
138
  exports.promptForNewParams = promptForNewParams;
117
139
  function getParamsFromFile(args) {
@@ -127,7 +149,7 @@ function getParamsFromFile(args) {
127
149
  const params = (0, extensionsHelper_1.populateDefaultParams)(envParams, args.paramSpecs);
128
150
  (0, extensionsHelper_1.validateCommandLineParams)(params, args.paramSpecs);
129
151
  logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
130
- return params;
152
+ return buildBindingOptionsWithBaseValue(params);
131
153
  }
132
154
  function readEnvFile(envPath) {
133
155
  const buf = fs.readFileSync(path.resolve(envPath), "utf8");
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;