firebase-tools 10.3.1 → 10.4.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 (63) 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 +7 -6
  5. package/lib/commands/ext-export.js +7 -1
  6. package/lib/commands/ext-install.js +7 -6
  7. package/lib/commands/ext-update.js +4 -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/checkIam.js +44 -1
  24. package/lib/deploy/functions/deploy.js +3 -7
  25. package/lib/deploy/functions/prepare.js +7 -5
  26. package/lib/deploy/functions/prepareFunctionsUpload.js +7 -13
  27. package/lib/deploy/functions/release/fabricator.js +13 -1
  28. package/lib/deploy/functions/release/index.js +1 -1
  29. package/lib/deploy/functions/services/firebaseAlerts.js +1 -17
  30. package/lib/deploy/functions/services/index.js +2 -1
  31. package/lib/deploy/hosting/deploy.js +10 -0
  32. package/lib/emulator/auth/apiSpec.js +37 -0
  33. package/lib/emulator/commandUtils.js +2 -2
  34. package/lib/emulator/controller.js +14 -8
  35. package/lib/emulator/downloadableEmulators.js +5 -5
  36. package/lib/emulator/extensionsEmulator.js +3 -0
  37. package/lib/emulator/functionsEmulator.js +8 -18
  38. package/lib/emulator/functionsEmulatorShared.js +31 -1
  39. package/lib/emulator/storage/apis/firebase.js +4 -6
  40. package/lib/emulator/storage/files.js +5 -5
  41. package/lib/emulator/storage/index.js +6 -9
  42. package/lib/emulator/storage/rules/config.js +6 -5
  43. package/lib/emulator/storage/rules/manager.js +49 -32
  44. package/lib/emulator/storage/rules/runtime.js +4 -0
  45. package/lib/emulator/storage/rules/utils.js +2 -2
  46. package/lib/emulator/storage/server.js +1 -1
  47. package/lib/extensions/askUserForParam.js +87 -16
  48. package/lib/extensions/extensionsHelper.js +11 -2
  49. package/lib/extensions/manifest.js +36 -4
  50. package/lib/extensions/paramHelper.js +11 -6
  51. package/lib/fsutils.js +14 -1
  52. package/lib/functions/projectConfig.js +34 -0
  53. package/lib/init/features/functions/index.js +4 -2
  54. package/lib/init/features/hosting/index.js +32 -41
  55. package/lib/init/features/index.js +22 -12
  56. package/lib/init/index.js +28 -11
  57. package/lib/requireConfig.js +11 -9
  58. package/lib/requirePermissions.js +4 -1
  59. package/lib/serve/functions.js +5 -5
  60. package/npm-shrinkwrap.json +2 -2
  61. package/package.json +1 -1
  62. package/schema/firebase-config.json +93 -36
  63. package/lib/prepareUpload.js +0 -44
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
3
+ exports.toBackendInfo = exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
4
4
  const _ = require("lodash");
5
5
  const os = require("os");
6
6
  const path = require("path");
@@ -10,6 +10,7 @@ const constants_1 = require("./constants");
10
10
  const proto_1 = require("../gcp/proto");
11
11
  const logger_1 = require("../logger");
12
12
  const manifest_1 = require("../extensions/manifest");
13
+ const extensionsHelper_1 = require("../extensions/extensionsHelper");
13
14
  const memoryLookup = {
14
15
  "128MB": 128,
15
16
  "256MB": 256,
@@ -65,6 +66,10 @@ function emulatedFunctionsFromEndpoints(endpoints) {
65
66
  if (backend.isHttpsTriggered(endpoint)) {
66
67
  def.httpsTrigger = endpoint.httpsTrigger;
67
68
  }
69
+ else if (backend.isCallableTriggered(endpoint)) {
70
+ def.httpsTrigger = {};
71
+ def.labels = Object.assign(Object.assign({}, def.labels), { "deployment-callable": "true" });
72
+ }
68
73
  else if (backend.isEventTriggered(endpoint)) {
69
74
  const eventTrigger = endpoint.eventTrigger;
70
75
  if (endpoint.platform === "gcfv1") {
@@ -238,3 +243,28 @@ function getSecretLocalPath(backend, projectDir) {
238
243
  return path.join(secretDirectory, secretsFile);
239
244
  }
240
245
  exports.getSecretLocalPath = getSecretLocalPath;
246
+ function toBackendInfo(e, cf3Triggers) {
247
+ var _a;
248
+ const envWithSecrets = Object.assign({}, e.env);
249
+ for (const s of e.secretEnv) {
250
+ envWithSecrets[s.key] = backend.secretVersionName(s);
251
+ }
252
+ let extensionVersion = e.extensionVersion;
253
+ if (extensionVersion) {
254
+ extensionVersion = (0, extensionsHelper_1.substituteParams)(extensionVersion, e.env);
255
+ }
256
+ let extensionSpec = e.extensionSpec;
257
+ if (extensionSpec) {
258
+ extensionSpec = (0, extensionsHelper_1.substituteParams)(extensionSpec, e.env);
259
+ }
260
+ return JSON.parse(JSON.stringify({
261
+ directory: e.functionsDir,
262
+ env: envWithSecrets,
263
+ extensionInstanceId: e.extensionInstanceId,
264
+ extension: e.extension,
265
+ extensionVersion: extensionVersion,
266
+ extensionSpec: extensionSpec,
267
+ functionTriggers: (_a = e.predefinedTriggers) !== null && _a !== void 0 ? _a : cf3Triggers,
268
+ }));
269
+ }
270
+ exports.toBackendInfo = toBackendInfo;
@@ -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,16 @@ 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
+ const projectUtils_1 = require("../projectUtils");
16
+ var SecretLocation;
17
+ (function (SecretLocation) {
18
+ SecretLocation[SecretLocation["CLOUD"] = 1] = "CLOUD";
19
+ SecretLocation[SecretLocation["LOCAL"] = 2] = "LOCAL";
20
+ })(SecretLocation = exports.SecretLocation || (exports.SecretLocation = {}));
15
21
  var SecretUpdateAction;
16
22
  (function (SecretUpdateAction) {
17
- SecretUpdateAction[SecretUpdateAction["LEAVE"] = 0] = "LEAVE";
18
- SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 1] = "SET_NEW";
23
+ SecretUpdateAction[SecretUpdateAction["LEAVE"] = 1] = "LEAVE";
24
+ SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 2] = "SET_NEW";
19
25
  })(SecretUpdateAction || (SecretUpdateAction = {}));
20
26
  function checkResponse(response, spec) {
21
27
  let valid = true;
@@ -55,21 +61,21 @@ function checkResponse(response, spec) {
55
61
  return valid;
56
62
  }
57
63
  exports.checkResponse = checkResponse;
58
- async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
59
- if (_.isEmpty(paramSpecs)) {
64
+ async function ask(args) {
65
+ if (_.isEmpty(args.paramSpecs)) {
60
66
  logger_1.logger.debug("No params were specified for this extension.");
61
67
  return {};
62
68
  }
63
69
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
64
- const substituted = (0, extensionsHelper_1.substituteParams)(paramSpecs, firebaseProjectParams);
70
+ const substituted = (0, extensionsHelper_1.substituteParams)(args.paramSpecs, args.firebaseProjectParams);
65
71
  const result = {};
66
72
  const promises = _.map(substituted, (paramSpec) => {
67
73
  return async () => {
68
74
  result[paramSpec.param] = await askForParam({
69
- projectId,
70
- instanceId,
71
- paramSpec,
72
- reconfiguring,
75
+ projectId: args.projectId,
76
+ instanceId: args.instanceId,
77
+ paramSpec: paramSpec,
78
+ reconfiguring: args.reconfiguring,
73
79
  });
74
80
  };
75
81
  });
@@ -82,6 +88,8 @@ async function askForParam(args) {
82
88
  const paramSpec = args.paramSpec;
83
89
  let valid = false;
84
90
  let response = "";
91
+ let responseForLocal;
92
+ let secretLocations = [];
85
93
  const description = paramSpec.description || "";
86
94
  const label = paramSpec.label.trim();
87
95
  logger_1.logger.info(`\n${clc.bold(label)}${clc.bold(paramSpec.required ? "" : " (Optional)")}: ${marked(description).trim()}`);
@@ -116,16 +124,24 @@ async function askForParam(args) {
116
124
  }
117
125
  },
118
126
  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.",
127
+ "Press Space to select, then Enter to confirm your choices. ",
121
128
  choices: (0, utils_1.convertExtensionOptionToLabeledList)(paramSpec.options),
122
129
  });
123
130
  valid = checkResponse(response, paramSpec);
124
131
  break;
125
132
  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);
133
+ do {
134
+ secretLocations = await promptSecretLocations(paramSpec);
135
+ } while (!isValidSecretLocations(secretLocations, paramSpec));
136
+ if (secretLocations.includes(SecretLocation.CLOUD.toString())) {
137
+ const projectId = (0, projectUtils_1.needProjectId)({ projectId: args.projectId });
138
+ response = args.reconfiguring
139
+ ? await promptReconfigureSecret(projectId, args.instanceId, paramSpec)
140
+ : await promptCreateSecret(projectId, args.instanceId, paramSpec);
141
+ }
142
+ if (secretLocations.includes(SecretLocation.LOCAL.toString())) {
143
+ responseForLocal = await promptLocalSecret(args.instanceId, paramSpec);
144
+ }
129
145
  valid = true;
130
146
  break;
131
147
  default:
@@ -138,9 +154,64 @@ async function askForParam(args) {
138
154
  valid = checkResponse(response, paramSpec);
139
155
  }
140
156
  }
141
- return { baseValue: response };
157
+ return Object.assign({ baseValue: response }, (responseForLocal ? { local: responseForLocal } : {}));
142
158
  }
143
159
  exports.askForParam = askForParam;
160
+ function isValidSecretLocations(secretLocations, paramSpec) {
161
+ if (paramSpec.required) {
162
+ return !!secretLocations.length;
163
+ }
164
+ return true;
165
+ }
166
+ async function promptSecretLocations(paramSpec) {
167
+ if (paramSpec.required) {
168
+ return await (0, prompt_1.promptOnce)({
169
+ name: "input",
170
+ type: "checkbox",
171
+ message: "Where would you like to store your secrets? You must select at least one value",
172
+ choices: [
173
+ {
174
+ checked: true,
175
+ name: "Google Cloud Secret Manager",
176
+ value: SecretLocation.CLOUD.toString(),
177
+ },
178
+ {
179
+ checked: false,
180
+ name: "Local file (Only used by Firebase Emulator)",
181
+ value: SecretLocation.LOCAL.toString(),
182
+ },
183
+ ],
184
+ });
185
+ }
186
+ return await (0, prompt_1.promptOnce)({
187
+ name: "input",
188
+ type: "checkbox",
189
+ message: "Where would you like to store your secrets? " +
190
+ "If you don't want to set this optional secret, leave both options unselected to skip it",
191
+ choices: [
192
+ {
193
+ checked: false,
194
+ name: "Google Cloud Secret Manager",
195
+ value: SecretLocation.CLOUD.toString(),
196
+ },
197
+ {
198
+ checked: false,
199
+ name: "Local file (Only used by Firebase Emulator)",
200
+ value: SecretLocation.LOCAL.toString(),
201
+ },
202
+ ],
203
+ });
204
+ }
205
+ async function promptLocalSecret(instanceId, paramSpec) {
206
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
207
+ const value = await (0, prompt_1.promptOnce)({
208
+ name: paramSpec.param,
209
+ type: "input",
210
+ message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
211
+ `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
212
+ });
213
+ return value;
214
+ }
144
215
  async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
145
216
  const action = await (0, prompt_1.promptOnce)({
146
217
  type: "list",
@@ -80,6 +80,9 @@ function getDBInstanceFromURL(databaseUrl = "") {
80
80
  exports.getDBInstanceFromURL = getDBInstanceFromURL;
81
81
  async function getFirebaseProjectParams(projectId, emulatorMode = false) {
82
82
  var _a, _b;
83
+ if (!projectId) {
84
+ return {};
85
+ }
83
86
  const body = emulatorMode
84
87
  ? await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)
85
88
  : await (0, functionsConfig_1.getFirebaseConfig)({ project: projectId });
@@ -263,7 +266,10 @@ async function promptForValidInstanceId(instanceId) {
263
266
  }
264
267
  exports.promptForValidInstanceId = promptForValidInstanceId;
265
268
  async function ensureExtensionsApiEnabled(options) {
266
- const projectId = (0, projectUtils_1.needProjectId)(options);
269
+ const projectId = (0, projectUtils_1.getProjectId)(options);
270
+ if (!projectId) {
271
+ return;
272
+ }
267
273
  return await (0, ensureApiEnabled_1.ensure)(projectId, "firebaseextensions.googleapis.com", "extensions", options.markdown);
268
274
  }
269
275
  exports.ensureExtensionsApiEnabled = ensureExtensionsApiEnabled;
@@ -519,7 +525,10 @@ async function confirm(args) {
519
525
  }
520
526
  exports.confirm = confirm;
521
527
  async function diagnoseAndFixProject(options) {
522
- const projectId = (0, projectUtils_1.needProjectId)(options);
528
+ const projectId = (0, projectUtils_1.getProjectId)(options);
529
+ if (!projectId) {
530
+ return;
531
+ }
523
532
  const ok = await (0, diagnose_1.diagnose)(projectId);
524
533
  if (!ok) {
525
534
  throw new error_1.FirebaseError("Unable to proceed until all issues are resolved.");
@@ -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
  }