firebase-tools 10.5.0 → 10.7.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 (105) hide show
  1. package/lib/command.js +4 -4
  2. package/lib/commands/deploy.js +1 -1
  3. package/lib/commands/emulators-start.js +7 -2
  4. package/lib/commands/ext-configure.js +15 -5
  5. package/lib/commands/ext-export.js +6 -5
  6. package/lib/commands/ext-install.js +28 -44
  7. package/lib/commands/ext-update.js +9 -1
  8. package/lib/commands/functions-delete.js +7 -3
  9. package/lib/commands/functions-secrets-destroy.js +23 -3
  10. package/lib/commands/functions-secrets-prune.js +15 -12
  11. package/lib/commands/functions-secrets-set.js +51 -4
  12. package/lib/commands/hosting-channel-deploy.js +2 -2
  13. package/lib/deploy/database/deploy.js +4 -0
  14. package/lib/deploy/database/index.js +1 -0
  15. package/lib/deploy/extensions/deploy.js +4 -4
  16. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  17. package/lib/deploy/extensions/planner.js +36 -9
  18. package/lib/deploy/extensions/prepare.js +1 -1
  19. package/lib/deploy/extensions/secrets.js +2 -2
  20. package/lib/deploy/extensions/tasks.js +60 -21
  21. package/lib/deploy/functions/backend.js +37 -6
  22. package/lib/deploy/functions/build.js +162 -0
  23. package/lib/deploy/functions/checkIam.js +10 -6
  24. package/lib/deploy/functions/deploy.js +49 -28
  25. package/lib/deploy/functions/ensure.js +4 -4
  26. package/lib/deploy/functions/functionsDeployHelper.js +99 -24
  27. package/lib/deploy/functions/prepare.js +130 -62
  28. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  29. package/lib/deploy/functions/pricing.js +6 -3
  30. package/lib/deploy/functions/prompts.js +1 -7
  31. package/lib/deploy/functions/release/fabricator.js +70 -28
  32. package/lib/deploy/functions/release/index.js +41 -6
  33. package/lib/deploy/functions/release/planner.js +19 -12
  34. package/lib/deploy/functions/release/reporter.js +14 -11
  35. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  36. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +61 -13
  37. package/lib/deploy/functions/runtimes/node/index.js +1 -1
  38. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  39. package/lib/deploy/functions/runtimes/node/parseTriggers.js +29 -24
  40. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  41. package/lib/deploy/functions/services/auth.js +95 -0
  42. package/lib/deploy/functions/services/index.js +41 -21
  43. package/lib/deploy/functions/services/storage.js +1 -6
  44. package/lib/deploy/functions/validate.js +32 -6
  45. package/lib/deploy/hosting/args.js +2 -0
  46. package/lib/deploy/hosting/convertConfig.js +39 -8
  47. package/lib/deploy/hosting/deploy.js +3 -3
  48. package/lib/deploy/hosting/prepare.js +2 -2
  49. package/lib/deploy/hosting/release.js +6 -2
  50. package/lib/deploy/index.js +82 -93
  51. package/lib/deploy/remoteconfig/deploy.js +4 -0
  52. package/lib/deploy/remoteconfig/index.js +3 -1
  53. package/lib/emulator/auth/operations.js +5 -0
  54. package/lib/emulator/auth/utils.js +3 -25
  55. package/lib/emulator/controller.js +17 -14
  56. package/lib/emulator/downloadableEmulators.js +39 -23
  57. package/lib/emulator/extensions/postinstall.js +41 -0
  58. package/lib/emulator/extensions/validation.js +2 -2
  59. package/lib/emulator/extensionsEmulator.js +85 -21
  60. package/lib/emulator/functionsEmulator.js +88 -10
  61. package/lib/emulator/functionsEmulatorShared.js +37 -21
  62. package/lib/emulator/functionsEmulatorShell.js +2 -3
  63. package/lib/emulator/pubsubEmulator.js +13 -9
  64. package/lib/emulator/registry.js +34 -12
  65. package/lib/emulator/storage/apis/firebase.js +13 -8
  66. package/lib/emulator/storage/apis/gcloud.js +15 -9
  67. package/lib/emulator/storage/files.js +14 -3
  68. package/lib/emulator/storage/index.js +9 -1
  69. package/lib/emulator/storage/metadata.js +18 -8
  70. package/lib/emulator/storage/rules/manager.js +7 -17
  71. package/lib/emulator/storage/server.js +38 -12
  72. package/lib/ensureApiEnabled.js +8 -4
  73. package/lib/extensions/askUserForParam.js +14 -11
  74. package/lib/extensions/changelog.js +1 -1
  75. package/lib/extensions/emulator/optionsHelper.js +9 -10
  76. package/lib/extensions/emulator/specHelper.js +7 -1
  77. package/lib/extensions/emulator/triggerHelper.js +11 -14
  78. package/lib/extensions/extensionsApi.js +2 -1
  79. package/lib/extensions/extensionsHelper.js +30 -24
  80. package/lib/extensions/manifest.js +28 -8
  81. package/lib/extensions/paramHelper.js +19 -13
  82. package/lib/extensions/provisioningHelper.js +2 -2
  83. package/lib/extensions/warnings.js +3 -3
  84. package/lib/functions/env.js +10 -2
  85. package/lib/functions/events/index.js +7 -0
  86. package/lib/functions/events/v1.js +6 -0
  87. package/lib/functions/projectConfig.js +32 -6
  88. package/lib/functions/runtimeConfigExport.js +10 -6
  89. package/lib/functions/secrets.js +99 -6
  90. package/lib/functionsShellCommandAction.js +1 -1
  91. package/lib/gcp/cloudfunctions.js +44 -18
  92. package/lib/gcp/cloudfunctionsv2.js +48 -25
  93. package/lib/gcp/cloudtasks.js +5 -3
  94. package/lib/gcp/identityPlatform.js +44 -0
  95. package/lib/gcp/secretManager.js +2 -2
  96. package/lib/metaprogramming.js +2 -0
  97. package/lib/previews.js +1 -1
  98. package/lib/serve/functions.js +16 -19
  99. package/lib/serve/hosting.js +25 -12
  100. package/lib/serve/index.js +6 -0
  101. package/lib/track.js +15 -21
  102. package/lib/utils.js +30 -1
  103. package/npm-shrinkwrap.json +44 -2
  104. package/package.json +4 -1
  105. package/schema/firebase-config.json +6 -0
@@ -173,7 +173,7 @@ function createFirebaseEndpoints(emulator) {
173
173
  return res.status(400).json({
174
174
  error: {
175
175
  code: 400,
176
- message: err.toString(),
176
+ message: err.message,
177
177
  },
178
178
  });
179
179
  }
@@ -201,7 +201,7 @@ function createFirebaseEndpoints(emulator) {
201
201
  }
202
202
  throw err;
203
203
  }
204
- metadata.addDownloadToken();
204
+ metadata.addDownloadToken(false);
205
205
  return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
206
206
  }
207
207
  const uploadCommand = req.header("x-goog-upload-command");
@@ -219,9 +219,13 @@ function createFirebaseEndpoints(emulator) {
219
219
  res.header("x-goog-upload-chunk-granularity", "10000");
220
220
  res.header("x-goog-upload-control-url", "");
221
221
  res.header("x-goog-upload-status", "active");
222
- const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
223
- res.header("x-goog-upload-url", `http://${req.hostname}:${emulatorInfo === null || emulatorInfo === void 0 ? void 0 : emulatorInfo.port}/v0/b/${bucketId}/o?name=${objectId}&upload_id=${upload.id}&upload_protocol=resumable`);
224
222
  res.header("x-gupload-uploadid", upload.id);
223
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
224
+ uploadUrl.pathname = `/v0/b/${bucketId}/o`;
225
+ uploadUrl.searchParams.set("name", objectId);
226
+ uploadUrl.searchParams.set("upload_id", upload.id);
227
+ uploadUrl.searchParams.set("upload_protocol", "resumable");
228
+ res.header("x-goog-upload-url", uploadUrl.toString());
225
229
  return res.sendStatus(200);
226
230
  }
227
231
  if (!req.query.upload_id) {
@@ -291,9 +295,9 @@ function createFirebaseEndpoints(emulator) {
291
295
  }
292
296
  throw err;
293
297
  }
294
- let metadata;
298
+ let storedMetadata;
295
299
  try {
296
- metadata = await storageLayer.uploadObject(upload);
300
+ storedMetadata = await storageLayer.uploadObject(upload);
297
301
  }
298
302
  catch (err) {
299
303
  if (err instanceof errors_1.ForbiddenError) {
@@ -306,8 +310,9 @@ function createFirebaseEndpoints(emulator) {
306
310
  }
307
311
  throw err;
308
312
  }
309
- metadata.addDownloadToken();
310
- return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(metadata));
313
+ res.header("x-goog-upload-status", "final");
314
+ storedMetadata.addDownloadToken(false);
315
+ return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(storedMetadata));
311
316
  }
312
317
  return res.sendStatus(400);
313
318
  };
@@ -200,9 +200,12 @@ function createCloudEndpoints(emulator) {
200
200
  metadataRaw: JSON.stringify(req.body),
201
201
  authorization: req.header("authorization"),
202
202
  });
203
- const { host, port } = emulatorInfo;
204
- const uploadUrl = `http://${host}:${port}/upload/storage/v1/b/${req.params.bucketId}/o?name=${name}&uploadType=resumable&upload_id=${upload.id}`;
205
- return res.header("location", uploadUrl).sendStatus(200);
203
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
204
+ uploadUrl.pathname = `/upload/storage/v1/b/${req.params.bucketId}/o`;
205
+ uploadUrl.searchParams.set("name", name);
206
+ uploadUrl.searchParams.set("uploadType", "resumable");
207
+ uploadUrl.searchParams.set("upload_id", upload.id);
208
+ return res.header("location", uploadUrl.toString()).sendStatus(200);
206
209
  }
207
210
  let metadataRaw;
208
211
  let dataRaw;
@@ -210,12 +213,15 @@ function createCloudEndpoints(emulator) {
210
213
  ({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
211
214
  }
212
215
  catch (err) {
213
- return res.status(400).json({
214
- error: {
215
- code: 400,
216
- message: err,
217
- },
218
- });
216
+ if (err instanceof Error) {
217
+ return res.status(400).json({
218
+ error: {
219
+ code: 400,
220
+ message: err.message,
221
+ },
222
+ });
223
+ }
224
+ throw err;
219
225
  }
220
226
  const upload = uploadService.multipartUpload({
221
227
  bucketId: req.params.bucketId,
@@ -155,6 +155,7 @@ class StorageLayer {
155
155
  cacheControl: upload.metadata.cacheControl,
156
156
  customMetadata: upload.metadata.metadata,
157
157
  }, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
158
+ metadata.update(upload.metadata, false);
158
159
  const authorized = 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);
159
160
  if (!authorized) {
160
161
  this._persistence.deleteFile(upload.path);
@@ -347,10 +348,16 @@ class StorageLayer {
347
348
  logger_1.logger.warn(`Could not find file "${blobPath}" in storage export.`);
348
349
  continue;
349
350
  }
350
- const file = new StoredFile(metadata, blobPath);
351
- this._files.set(blobPath, file);
351
+ let decodedBlobPath = decodeURIComponent(blobPath);
352
+ const decodedBlobPathSep = getPathSep(decodedBlobPath);
353
+ if (decodedBlobPathSep !== path.sep) {
354
+ decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep);
355
+ }
356
+ const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath);
357
+ const file = new StoredFile(metadata, blobDiskPath);
358
+ this._files.set(decodedBlobPath, file);
359
+ fse.copyFileSync(blobAbsPath, blobDiskPath);
352
360
  }
353
- fse.copySync(blobsDir, this.dirPath);
354
361
  }
355
362
  *walkDirSync(dir) {
356
363
  const files = (0, fs_1.readdirSync)(dir);
@@ -366,3 +373,7 @@ class StorageLayer {
366
373
  }
367
374
  }
368
375
  exports.StorageLayer = StorageLayer;
376
+ function getPathSep(decodedPath) {
377
+ const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g);
378
+ return decodedPath[firstSepIndex];
379
+ }
@@ -21,7 +21,7 @@ class StorageEmulator {
21
21
  this._files = new Map();
22
22
  this._buckets = new Map();
23
23
  this._rulesRuntime = new runtime_1.StorageRulesRuntime();
24
- this._rulesManager = (0, manager_1.createStorageRulesManager)(this.args.rules, this._rulesRuntime);
24
+ this._rulesManager = this.createRulesManager(this.args.rules);
25
25
  this._cloudFunctions = new cloudFunctions_1.StorageCloudFunctions(args.projectId);
26
26
  this._persistence = new persistence_1.Persistence(this.getPersistenceTmpDir());
27
27
  this._uploadService = new upload_1.UploadService(this._persistence);
@@ -82,6 +82,14 @@ class StorageEmulator {
82
82
  getApp() {
83
83
  return this._app;
84
84
  }
85
+ async replaceRules(rules) {
86
+ await this._rulesManager.stop();
87
+ this._rulesManager = this.createRulesManager(rules);
88
+ return this._rulesManager.start();
89
+ }
90
+ createRulesManager(rules) {
91
+ return (0, manager_1.createStorageRulesManager)(rules, this._rulesRuntime);
92
+ }
85
93
  getPersistenceTmpDir() {
86
94
  return `${(0, os_1.tmpdir)()}/firebase/storage/blobs`;
87
95
  }
@@ -44,7 +44,7 @@ class StoredFileMetadata {
44
44
  throw new Error("Must pass bytes array or opts object with size, md5hash, and crc32c");
45
45
  }
46
46
  if (incomingMetadata) {
47
- this.update(incomingMetadata);
47
+ this.update(incomingMetadata, false);
48
48
  }
49
49
  this.deleteFieldsSetAsNull();
50
50
  this.setDownloadTokensFromCustomMetadata();
@@ -86,8 +86,10 @@ class StoredFileMetadata {
86
86
  }
87
87
  if (this.customMetadata.firebaseStorageDownloadTokens) {
88
88
  this.downloadTokens = [
89
- ...this.downloadTokens,
90
- ...this.customMetadata.firebaseStorageDownloadTokens.split(","),
89
+ ...new Set([
90
+ ...this.downloadTokens,
91
+ ...this.customMetadata.firebaseStorageDownloadTokens.split(","),
92
+ ]),
91
93
  ];
92
94
  delete this.customMetadata.firebaseStorageDownloadTokens;
93
95
  }
@@ -115,7 +117,7 @@ class StoredFileMetadata {
115
117
  });
116
118
  }
117
119
  }
118
- update(incoming) {
120
+ update(incoming, shouldTrigger = true) {
119
121
  if (incoming.contentDisposition) {
120
122
  this.contentDisposition = incoming.contentDisposition;
121
123
  }
@@ -143,15 +145,17 @@ class StoredFileMetadata {
143
145
  }
144
146
  this.setDownloadTokensFromCustomMetadata();
145
147
  this.deleteFieldsSetAsNull();
146
- this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this));
148
+ if (shouldTrigger) {
149
+ this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this));
150
+ }
147
151
  }
148
- addDownloadToken() {
152
+ addDownloadToken(shouldTrigger = true) {
149
153
  if (!this.downloadTokens.length) {
150
154
  this.downloadTokens.push(uuid.v4());
151
155
  return;
152
156
  }
153
157
  this.downloadTokens = [...this.downloadTokens, uuid.v4()];
154
- this.update({});
158
+ this.update({}, shouldTrigger);
155
159
  }
156
160
  deleteDownloadToken(token) {
157
161
  if (!this.downloadTokens.length) {
@@ -160,7 +164,7 @@ class StoredFileMetadata {
160
164
  const remainingTokens = this.downloadTokens.filter((t) => t !== token);
161
165
  this.downloadTokens = remainingTokens;
162
166
  if (remainingTokens.length === 0) {
163
- this.addDownloadToken();
167
+ this.addDownloadToken(false);
164
168
  }
165
169
  this.update({});
166
170
  }
@@ -265,6 +269,12 @@ class CloudStorageObjectMetadata {
265
269
  if (metadata.cacheControl) {
266
270
  this.cacheControl = metadata.cacheControl;
267
271
  }
272
+ if (metadata.contentDisposition) {
273
+ this.contentDisposition = metadata.contentDisposition;
274
+ }
275
+ if (metadata.contentEncoding) {
276
+ this.contentEncoding = metadata.contentEncoding;
277
+ }
268
278
  if (metadata.customTime) {
269
279
  this.customTime = toSerializedDate(metadata.customTime);
270
280
  }
@@ -5,6 +5,7 @@ const chokidar = require("chokidar");
5
5
  const emulatorLogger_1 = require("../../emulatorLogger");
6
6
  const types_1 = require("../../types");
7
7
  const runtime_1 = require("./runtime");
8
+ const fsutils_1 = require("../../../fsutils");
8
9
  function createStorageRulesManager(rules, runtime) {
9
10
  return Array.isArray(rules)
10
11
  ? new ResourceBasedStorageRulesManager(rules, runtime)
@@ -18,31 +19,24 @@ class DefaultStorageRulesManager {
18
19
  this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
19
20
  this._rules = _rules;
20
21
  }
21
- start() {
22
- return this.updateSourceFile(this._rules);
22
+ async start() {
23
+ const issues = await this.loadRuleset();
24
+ this.updateWatcher(this._rules.name);
25
+ return issues;
23
26
  }
24
27
  getRuleset() {
25
28
  return this._ruleset;
26
29
  }
27
- async updateSourceFile(rules) {
28
- const prevRulesFile = this._rules.name;
29
- this._rules = rules;
30
- const issues = await this.loadRuleset();
31
- this.updateWatcher(rules.name, prevRulesFile);
32
- return issues;
33
- }
34
30
  async stop() {
35
31
  await this._watcher.close();
36
32
  }
37
- updateWatcher(rulesFile, prevRulesFile) {
38
- if (prevRulesFile) {
39
- this._watcher.unwatch(prevRulesFile);
40
- }
33
+ updateWatcher(rulesFile) {
41
34
  this._watcher = chokidar
42
35
  .watch(rulesFile, { persistent: true, ignoreInitial: true })
43
36
  .on("change", async () => {
44
37
  await new Promise((res) => setTimeout(res, 5));
45
38
  this._logger.logLabeled("BULLET", "storage", "Change detected, updating rules for Cloud Storage...");
39
+ this._rules.content = (0, fsutils_1.readFile)(rulesFile);
46
40
  await this.loadRuleset();
47
41
  });
48
42
  }
@@ -83,10 +77,6 @@ class ResourceBasedStorageRulesManager {
83
77
  var _a;
84
78
  return (_a = this._rulesManagers.get(resource)) === null || _a === void 0 ? void 0 : _a.getRuleset();
85
79
  }
86
- updateSourceFile(rules, resource) {
87
- const rulesManager = this._rulesManagers.get(resource) || this.createRulesManager(resource, rules);
88
- return rulesManager.updateSourceFile(rules);
89
- }
90
80
  async stop() {
91
81
  await Promise.all(Array.from(this._rulesManagers.values(), async (rulesManager) => await rulesManager.stop()));
92
82
  }
@@ -8,6 +8,7 @@ const types_1 = require("../types");
8
8
  const bodyParser = require("body-parser");
9
9
  const gcloud_1 = require("./apis/gcloud");
10
10
  const firebase_1 = require("./apis/firebase");
11
+ const errors_1 = require("../auth/errors");
11
12
  function createApp(defaultProjectId, emulator) {
12
13
  const { storageLayer } = emulator;
13
14
  const app = express();
@@ -44,21 +45,43 @@ function createApp(defaultProjectId, emulator) {
44
45
  res.sendStatus(200);
45
46
  });
46
47
  app.put("/internal/setRules", async (req, res) => {
47
- const rules = req.body.rules;
48
- if (!(rules && Array.isArray(rules.files) && rules.files.length > 0)) {
49
- res.status(400).send("Request body must include 'rules.files' array .");
48
+ const rulesRaw = req.body.rules;
49
+ if (!(rulesRaw && Array.isArray(rulesRaw.files) && rulesRaw.files.length > 0)) {
50
+ res.status(400).json({
51
+ message: "Request body must include 'rules.files' array",
52
+ });
50
53
  return;
51
54
  }
52
- const file = rules.files[0];
53
- if (!(file.name && file.content)) {
54
- res
55
- .status(400)
56
- .send("Request body must include 'rules.files' array where each member contains 'name' and 'content'.");
57
- return;
55
+ const { files } = rulesRaw;
56
+ function parseRulesFromFiles(files) {
57
+ if (files.length === 1) {
58
+ const file = files[0];
59
+ if (!isRulesFile(file)) {
60
+ throw new errors_1.InvalidArgumentError("Each member of 'rules.files' array must contain 'name' and 'content'");
61
+ }
62
+ return { name: file.name, content: file.content };
63
+ }
64
+ const rules = [];
65
+ for (const file of files) {
66
+ if (!isRulesFile(file) || !file.resource) {
67
+ throw new errors_1.InvalidArgumentError("Each member of 'rules.files' array must contain 'name', 'content', and 'resource'");
68
+ }
69
+ rules.push({ resource: file.resource, rules: { name: file.name, content: file.content } });
70
+ }
71
+ return rules;
58
72
  }
59
- const name = file.name;
60
- const content = file.content;
61
- const issues = await emulator.rulesManager.updateSourceFile({ name, content }, req.params.bucketId);
73
+ let rules;
74
+ try {
75
+ rules = parseRulesFromFiles(files);
76
+ }
77
+ catch (err) {
78
+ if (err instanceof errors_1.InvalidArgumentError) {
79
+ res.status(400).json({ message: err.message });
80
+ return;
81
+ }
82
+ throw err;
83
+ }
84
+ const issues = await emulator.replaceRules(rules);
62
85
  if (issues.errors.length > 0) {
63
86
  res.status(400).json({
64
87
  message: "There was an error updating rules, see logs for more details",
@@ -78,3 +101,6 @@ function createApp(defaultProjectId, emulator) {
78
101
  return Promise.resolve(app);
79
102
  }
80
103
  exports.createApp = createApp;
104
+ function isRulesFile(file) {
105
+ return (typeof file.name === "string" && typeof file.content === "string");
106
+ }
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enableApiURI = exports.ensure = exports.check = exports.POLL_SETTINGS = void 0;
4
4
  const cli_color_1 = require("cli-color");
5
- const track = require("./track");
5
+ const track_1 = require("./track");
6
6
  const api_1 = require("./api");
7
7
  const apiv2_1 = require("./apiv2");
8
8
  const utils = require("./utils");
@@ -16,7 +16,9 @@ const apiClient = new apiv2_1.Client({
16
16
  apiVersion: "v1",
17
17
  });
18
18
  async function check(projectId, apiName, prefix, silent = false) {
19
- const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`);
19
+ const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`, {
20
+ skipLog: { resBody: true },
21
+ });
20
22
  const isEnabled = res.body.state === "ENABLED";
21
23
  if (isEnabled && !silent) {
22
24
  utils.logLabeledSuccess(prefix, `required API ${(0, cli_color_1.bold)(apiName)} is enabled`);
@@ -26,7 +28,9 @@ async function check(projectId, apiName, prefix, silent = false) {
26
28
  exports.check = check;
27
29
  async function enable(projectId, apiName) {
28
30
  try {
29
- await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`);
31
+ await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, undefined, {
32
+ skipLog: { resBody: true },
33
+ });
30
34
  }
31
35
  catch (err) {
32
36
  if ((0, error_1.isBillingError)(err)) {
@@ -46,7 +50,7 @@ async function pollCheckEnabled(projectId, apiName, prefix, silent, enablementRe
46
50
  });
47
51
  const isEnabled = await check(projectId, apiName, prefix, silent);
48
52
  if (isEnabled) {
49
- void track("api_enabled", apiName);
53
+ void (0, track_1.track)("api_enabled", apiName);
50
54
  return;
51
55
  }
52
56
  if (!silent) {
@@ -172,12 +172,12 @@ async function promptSecretLocations(paramSpec) {
172
172
  choices: [
173
173
  {
174
174
  checked: true,
175
- name: "Google Cloud Secret Manager",
175
+ name: "Google Cloud Secret Manager (Used by deployed extensions and emulator)",
176
176
  value: SecretLocation.CLOUD.toString(),
177
177
  },
178
178
  {
179
179
  checked: false,
180
- name: "Local file (Only used by Firebase Emulator)",
180
+ name: "Local file (Used by emulator only)",
181
181
  value: SecretLocation.LOCAL.toString(),
182
182
  },
183
183
  ],
@@ -191,25 +191,28 @@ async function promptSecretLocations(paramSpec) {
191
191
  choices: [
192
192
  {
193
193
  checked: false,
194
- name: "Google Cloud Secret Manager",
194
+ name: "Google Cloud Secret Manager (Used by deployed extensions and emulator)",
195
195
  value: SecretLocation.CLOUD.toString(),
196
196
  },
197
197
  {
198
198
  checked: false,
199
- name: "Local file (Only used by Firebase Emulator)",
199
+ name: "Local file (Used by emulator only)",
200
200
  value: SecretLocation.LOCAL.toString(),
201
201
  },
202
202
  ],
203
203
  });
204
204
  }
205
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
- });
206
+ let value;
207
+ do {
208
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
209
+ value = await (0, prompt_1.promptOnce)({
210
+ name: paramSpec.param,
211
+ type: "input",
212
+ message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
213
+ `Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
214
+ });
215
+ } while (!value);
213
216
  return value;
214
217
  }
215
218
  async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
@@ -16,7 +16,7 @@ marked.setOptions({
16
16
  renderer: new TerminalRenderer(),
17
17
  });
18
18
  const EXTENSIONS_CHANGELOG = "CHANGELOG.md";
19
- const VERSION_LINE_REGEX = /##.*(\d+\.\d+\.\d+).*/;
19
+ const VERSION_LINE_REGEX = /##.*(\d+\.\d+\.\d+(?:-((\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(\d+|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?).*/;
20
20
  async function getReleaseNotesForUpdate(args) {
21
21
  const releaseNotes = {};
22
22
  const filter = `id<="${args.toVersion}" AND id>"${args.fromVersion}"`;
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getParams = exports.getSecretEnvVars = exports.getNonSecretEnv = exports.getExtensionFunctionInfo = exports.buildOptions = void 0;
4
4
  const fs = require("fs-extra");
5
- const _ = require("lodash");
6
5
  const path = require("path");
7
6
  const paramHelper = require("../paramHelper");
8
7
  const specHelper = require("./specHelper");
@@ -10,6 +9,7 @@ const localHelper = require("../localHelper");
10
9
  const triggerHelper = require("./triggerHelper");
11
10
  const extensionsApi_1 = require("../extensionsApi");
12
11
  const extensionsHelper = require("../extensionsHelper");
12
+ const planner = require("../../deploy/extensions/planner");
13
13
  const config_1 = require("../../config");
14
14
  const error_1 = require("../../error");
15
15
  const emulatorLogger_1 = require("../../emulator/emulatorLogger");
@@ -36,13 +36,13 @@ async function buildOptions(options) {
36
36
  return options;
37
37
  }
38
38
  exports.buildOptions = buildOptions;
39
- async function getExtensionFunctionInfo(extensionDir, instanceId, paramValues) {
40
- const spec = await specHelper.readExtensionYaml(extensionDir);
39
+ async function getExtensionFunctionInfo(instance, paramValues) {
40
+ const spec = await planner.getExtensionSpec(instance);
41
41
  const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues);
42
42
  const extensionTriggers = functionResources
43
43
  .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r))
44
44
  .map((trigger) => {
45
- trigger.name = `ext-${instanceId}-${trigger.name}`;
45
+ trigger.name = `ext-${instance.instanceId}-${trigger.name}`;
46
46
  return trigger;
47
47
  });
48
48
  const nodeMajorVersion = specHelper.getNodeVersion(functionResources);
@@ -159,12 +159,10 @@ function buildConfig(functionResources, testConfig) {
159
159
  return config;
160
160
  }
161
161
  function getFunctionSourceDirectory(functionResources) {
162
+ var _a;
162
163
  let sourceDirectory;
163
164
  for (const r of functionResources) {
164
- let dir = _.get(r, "properties.sourceDirectory");
165
- if (!dir) {
166
- dir = "functions";
167
- }
165
+ const dir = ((_a = r.properties) === null || _a === void 0 ? void 0 : _a.sourceDirectory) || "functions";
168
166
  if (!sourceDirectory) {
169
167
  sourceDirectory = dir;
170
168
  }
@@ -172,14 +170,15 @@ function getFunctionSourceDirectory(functionResources) {
172
170
  throw new error_1.FirebaseError(`Found function resources with different sourceDirectories: '${sourceDirectory}' and '${dir}'. The extensions emulator only supports a single sourceDirectory.`);
173
171
  }
174
172
  }
175
- return sourceDirectory;
173
+ return sourceDirectory || "functions";
176
174
  }
177
175
  function shouldEmulateFunctions(resources) {
178
176
  return resources.length > 0;
179
177
  }
180
178
  function shouldEmulate(emulatorName, resources) {
179
+ var _a, _b;
181
180
  for (const r of resources) {
182
- const eventType = _.get(r, "properties.eventTrigger.eventType", "");
181
+ const eventType = ((_b = (_a = r.properties) === null || _a === void 0 ? void 0 : _a.eventTrigger) === null || _b === void 0 ? void 0 : _b.eventType) || "";
183
182
  if (eventType.includes(emulatorName)) {
184
183
  return true;
185
184
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNodeVersion = exports.getFunctionProperties = exports.getFunctionResourcesWithParamSubstitution = exports.readFileFromDirectory = exports.readExtensionYaml = void 0;
3
+ exports.getNodeVersion = exports.getFunctionProperties = exports.getFunctionResourcesWithParamSubstitution = exports.readFileFromDirectory = exports.readPostinstall = exports.readExtensionYaml = void 0;
4
4
  const yaml = require("js-yaml");
5
5
  const path = require("path");
6
6
  const fs = require("fs-extra");
@@ -8,6 +8,7 @@ const error_1 = require("../../error");
8
8
  const extensionsHelper_1 = require("../extensionsHelper");
9
9
  const functionsEmulatorUtils_1 = require("../../emulator/functionsEmulatorUtils");
10
10
  const SPEC_FILE = "extension.yaml";
11
+ const POSTINSTALL_FILE = "POSTINSTALL.md";
11
12
  const validFunctionTypes = [
12
13
  "firebaseextensions.v1beta.function",
13
14
  "firebaseextensions.v1beta.scheduledFunction",
@@ -29,6 +30,11 @@ async function readExtensionYaml(directory) {
29
30
  return wrappedSafeLoad(source);
30
31
  }
31
32
  exports.readExtensionYaml = readExtensionYaml;
33
+ async function readPostinstall(directory) {
34
+ const content = await readFileFromDirectory(directory, POSTINSTALL_FILE);
35
+ return content.source;
36
+ }
37
+ exports.readPostinstall = readPostinstall;
32
38
  function readFileFromDirectory(directory, file) {
33
39
  return new Promise((resolve, reject) => {
34
40
  fs.readFile(path.resolve(directory, file), "utf8", (err, data) => {
@@ -1,32 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.functionResourceToEmulatedTriggerDefintion = void 0;
4
- const _ = require("lodash");
5
4
  const functionsEmulatorShared_1 = require("../../emulator/functionsEmulatorShared");
6
5
  const emulatorLogger_1 = require("../../emulator/emulatorLogger");
7
6
  const types_1 = require("../../emulator/types");
7
+ const proto = require("../../gcp/proto");
8
8
  function functionResourceToEmulatedTriggerDefintion(resource) {
9
9
  const etd = {
10
10
  name: resource.name,
11
11
  entryPoint: resource.name,
12
12
  platform: "gcfv1",
13
13
  };
14
- const properties = _.get(resource, "properties", {});
15
- if (properties.timeout) {
16
- etd.timeout = properties.timeout;
17
- }
18
- if (properties.location) {
19
- etd.regions = [properties.location];
20
- }
21
- if (properties.availableMemoryMb) {
22
- etd.availableMemoryMb = properties.availableMemoryMb;
23
- }
14
+ const properties = resource.properties || {};
15
+ proto.renameIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
16
+ proto.renameIfPresent(etd, properties, "regions", "location", (str) => [str]);
17
+ proto.copyIfPresent(etd, properties, "availableMemoryMb");
24
18
  if (properties.httpsTrigger) {
25
19
  etd.httpsTrigger = properties.httpsTrigger;
26
20
  }
27
- else if (properties.eventTrigger) {
28
- properties.eventTrigger.service = (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType);
29
- etd.eventTrigger = properties.eventTrigger;
21
+ if (properties.eventTrigger) {
22
+ etd.eventTrigger = {
23
+ eventType: properties.eventTrigger.eventType,
24
+ resource: properties.eventTrigger.resource,
25
+ service: (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType),
26
+ };
30
27
  }
31
28
  else {
32
29
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).log("WARN", `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.`);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.undeprecateExtensionVersion = exports.deprecateExtensionVersion = exports.registerPublisherProfile = exports.getPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.Visibility = exports.RegistryLaunchStage = void 0;
3
+ exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.undeprecateExtensionVersion = exports.deprecateExtensionVersion = exports.registerPublisherProfile = exports.getPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.FUNCTIONS_RESOURCE_TYPE = exports.Visibility = exports.RegistryLaunchStage = void 0;
4
4
  const yaml = require("js-yaml");
5
5
  const clc = require("cli-color");
6
6
  const { marked } = require("marked");
@@ -26,6 +26,7 @@ var Visibility;
26
26
  Visibility["UNLISTED"] = "unlisted";
27
27
  Visibility["PUBLIC"] = "public";
28
28
  })(Visibility = exports.Visibility || (exports.Visibility = {}));
29
+ exports.FUNCTIONS_RESOURCE_TYPE = "firebaseextensions.v1beta.function";
29
30
  var ParamType;
30
31
  (function (ParamType) {
31
32
  ParamType["STRING"] = "STRING";