firebase-tools 10.1.5 → 10.2.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 (134) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +3 -0
  3. package/lib/appdistribution/options-parser-util.js +1 -1
  4. package/lib/auth.js +62 -25
  5. package/lib/command.js +1 -1
  6. package/lib/commands/apps-android-sha-create.js +2 -2
  7. package/lib/commands/apps-sdkconfig.js +1 -1
  8. package/lib/commands/auth-import.js +1 -1
  9. package/lib/commands/database-rules-list.js +2 -2
  10. package/lib/commands/emulators-start.js +1 -1
  11. package/lib/commands/ext-configure.js +1 -0
  12. package/lib/commands/ext-dev-init.js +49 -49
  13. package/lib/commands/ext-export.js +12 -2
  14. package/lib/commands/ext-install.js +104 -103
  15. package/lib/commands/ext-uninstall.js +9 -8
  16. package/lib/commands/ext-update.js +10 -9
  17. package/lib/commands/functions-config-clone.js +1 -1
  18. package/lib/commands/functions-config-export.js +1 -1
  19. package/lib/commands/functions-secrets-access.js +17 -0
  20. package/lib/commands/functions-secrets-destroy.js +40 -0
  21. package/lib/commands/functions-secrets-get.js +21 -0
  22. package/lib/commands/functions-secrets-prune.js +50 -0
  23. package/lib/commands/functions-secrets-set.js +46 -0
  24. package/lib/commands/hosting-clone.js +3 -3
  25. package/lib/commands/index.js +7 -3
  26. package/lib/commands/login.js +1 -1
  27. package/lib/commands/remoteconfig-get.js +1 -1
  28. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  29. package/lib/deploy/extensions/params.js +3 -0
  30. package/lib/deploy/extensions/planner.js +2 -1
  31. package/lib/deploy/extensions/tasks.js +1 -1
  32. package/lib/deploy/functions/backend.js +20 -5
  33. package/lib/deploy/functions/checkIam.js +1 -1
  34. package/lib/deploy/functions/containerCleaner.js +3 -3
  35. package/lib/deploy/functions/ensure.js +112 -0
  36. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  37. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  38. package/lib/deploy/functions/prepare.js +15 -20
  39. package/lib/deploy/functions/pricing.js +1 -1
  40. package/lib/deploy/functions/prompts.js +2 -2
  41. package/lib/deploy/functions/release/fabricator.js +3 -3
  42. package/lib/deploy/functions/release/index.js +1 -1
  43. package/lib/deploy/functions/release/planner.js +11 -8
  44. package/lib/deploy/functions/release/reporter.js +3 -0
  45. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  46. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  47. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +17 -11
  48. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  49. package/lib/deploy/functions/runtimes/node/index.js +26 -0
  50. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  51. package/lib/deploy/functions/runtimes/node/parseTriggers.js +40 -7
  52. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  53. package/lib/deploy/functions/validate.js +58 -3
  54. package/lib/deploy/hosting/client.js +9 -0
  55. package/lib/deploy/hosting/convertConfig.js +6 -0
  56. package/lib/deploy/hosting/deploy.js +2 -2
  57. package/lib/deploy/hosting/hashcache.js +21 -19
  58. package/lib/deploy/hosting/index.js +5 -5
  59. package/lib/deploy/hosting/prepare.js +25 -25
  60. package/lib/deploy/hosting/release.js +21 -24
  61. package/lib/deploy/hosting/uploader.js +5 -5
  62. package/lib/deploy/remoteconfig/functions.js +2 -2
  63. package/lib/emulator/auth/cloudFunctions.js +1 -1
  64. package/lib/emulator/auth/operations.js +1 -1
  65. package/lib/emulator/commandUtils.js +5 -1
  66. package/lib/emulator/constants.js +3 -0
  67. package/lib/emulator/controller.js +48 -18
  68. package/lib/emulator/download.js +18 -1
  69. package/lib/emulator/downloadableEmulators.js +30 -13
  70. package/lib/emulator/emulatorLogger.js +19 -1
  71. package/lib/emulator/extensions/validation.js +35 -0
  72. package/lib/emulator/extensionsEmulator.js +140 -0
  73. package/lib/emulator/functionsEmulator.js +175 -86
  74. package/lib/emulator/functionsEmulatorRuntime.js +108 -83
  75. package/lib/emulator/functionsEmulatorShared.js +51 -1
  76. package/lib/emulator/functionsEmulatorShell.js +1 -2
  77. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  78. package/lib/emulator/functionsRuntimeWorker.js +3 -3
  79. package/lib/emulator/hub.js +4 -3
  80. package/lib/emulator/loggingEmulator.js +1 -1
  81. package/lib/emulator/pubsubEmulator.js +1 -1
  82. package/lib/emulator/registry.js +10 -2
  83. package/lib/emulator/storage/apis/firebase.js +31 -26
  84. package/lib/emulator/storage/apis/gcloud.js +7 -12
  85. package/lib/emulator/storage/files.js +36 -34
  86. package/lib/emulator/storage/index.js +2 -2
  87. package/lib/emulator/storage/metadata.js +2 -2
  88. package/lib/emulator/storage/rules/runtime.js +8 -7
  89. package/lib/emulator/types.js +3 -0
  90. package/lib/ensureApiEnabled.js +5 -1
  91. package/lib/error.js +1 -1
  92. package/lib/extensions/askUserForParam.js +2 -2
  93. package/lib/extensions/changelog.js +3 -1
  94. package/lib/extensions/checkProjectBilling.js +1 -1
  95. package/lib/extensions/diagnose.js +56 -0
  96. package/lib/extensions/displayExtensionInfo.js +1 -1
  97. package/lib/extensions/emulator/optionsHelper.js +24 -8
  98. package/lib/extensions/emulator/specHelper.js +10 -23
  99. package/lib/extensions/export.js +1 -51
  100. package/lib/extensions/extensionsApi.js +1 -1
  101. package/lib/extensions/extensionsHelper.js +23 -10
  102. package/lib/extensions/listExtensions.js +2 -0
  103. package/lib/extensions/manifest.js +48 -0
  104. package/lib/extensions/metricsUtils.js +4 -4
  105. package/lib/extensions/paramHelper.js +4 -4
  106. package/lib/extensions/refs.js +1 -1
  107. package/lib/extensions/secretsUtils.js +4 -4
  108. package/lib/functional.js +1 -1
  109. package/lib/functions/env.js +7 -8
  110. package/lib/functions/secrets.js +112 -0
  111. package/lib/gcp/cloudfunctions.js +24 -5
  112. package/lib/gcp/cloudfunctionsv2.js +18 -5
  113. package/lib/gcp/cloudtasks.js +1 -1
  114. package/lib/gcp/docker.js +2 -2
  115. package/lib/gcp/run.js +2 -2
  116. package/lib/gcp/secretManager.js +128 -46
  117. package/lib/gcp/storage.js +1 -0
  118. package/lib/hosting/api.js +1 -1
  119. package/lib/hosting/functionsProxy.js +15 -5
  120. package/lib/hosting/proxy.js +2 -2
  121. package/lib/init/features/account.js +1 -1
  122. package/lib/management/database.js +1 -1
  123. package/lib/previews.js +1 -1
  124. package/lib/responseToError.js +16 -7
  125. package/lib/serve/functions.js +2 -2
  126. package/lib/serve/hosting.js +1 -1
  127. package/lib/utils.js +7 -2
  128. package/npm-shrinkwrap.json +904 -412
  129. package/package.json +3 -3
  130. package/schema/firebase-config.json +32 -0
  131. package/templates/init/functions/javascript/package.lint.json +3 -3
  132. package/templates/init/functions/javascript/package.nolint.json +2 -2
  133. package/templates/init/functions/typescript/package.lint.json +7 -7
  134. package/templates/init/functions/typescript/package.nolint.json +3 -3
@@ -114,10 +114,10 @@ function createFirebaseEndpoints(emulator) {
114
114
  return;
115
115
  }
116
116
  let isGZipped = false;
117
- if (md.contentEncoding == "gzip") {
117
+ if (md.contentEncoding === "gzip") {
118
118
  isGZipped = true;
119
119
  }
120
- if (req.query.alt == "media") {
120
+ if (req.query.alt === "media") {
121
121
  let data = storageLayer.getBytes(req.params.bucketId, req.params.objectId);
122
122
  if (!data) {
123
123
  res.sendStatus(404);
@@ -206,7 +206,6 @@ function createFirebaseEndpoints(emulator) {
206
206
  res.json(storageLayer.listItemsAndPrefixes(req.params.bucketId, prefix, delimiter, pageToken, maxRes));
207
207
  });
208
208
  const handleUpload = async (req, res) => {
209
- var _a;
210
209
  if (req.query.create_token || req.query.delete_token) {
211
210
  const decodedObjectId = decodeURIComponent(req.params.objectId);
212
211
  const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
@@ -239,7 +238,7 @@ function createFirebaseEndpoints(emulator) {
239
238
  const deleteTokenParam = req.query["delete_token"];
240
239
  let md;
241
240
  if (createTokenParam) {
242
- if (createTokenParam != "true") {
241
+ if (createTokenParam !== "true") {
243
242
  res.sendStatus(400);
244
243
  return;
245
244
  }
@@ -261,7 +260,7 @@ function createFirebaseEndpoints(emulator) {
261
260
  }
262
261
  const name = req.query.name.toString();
263
262
  const uploadType = req.header("x-goog-upload-protocol");
264
- if (uploadType == "multipart") {
263
+ if (uploadType === "multipart") {
265
264
  const contentType = req.header("content-type");
266
265
  if (!contentType || !contentType.startsWith("multipart/related")) {
267
266
  res.sendStatus(400);
@@ -311,7 +310,7 @@ function createFirebaseEndpoints(emulator) {
311
310
  },
312
311
  });
313
312
  }
314
- if (md.downloadTokens.length == 0) {
313
+ if (md.downloadTokens.length === 0) {
315
314
  md.addDownloadToken();
316
315
  }
317
316
  res.json(new metadata_1.OutgoingFirebaseMetadata(md));
@@ -324,7 +323,7 @@ function createFirebaseEndpoints(emulator) {
324
323
  res.sendStatus(400);
325
324
  return;
326
325
  }
327
- if (uploadCommand == "start") {
326
+ if (uploadCommand === "start") {
328
327
  let objectContentType = req.header("x-goog-upload-header-content-type") ||
329
328
  req.header("x-goog-upload-content-type");
330
329
  if (!objectContentType) {
@@ -336,7 +335,7 @@ function createFirebaseEndpoints(emulator) {
336
335
  objectContentType = mimeTypeFromName;
337
336
  }
338
337
  }
339
- const upload = storageLayer.startUpload(req.params.bucketId, name, objectContentType, req.body);
338
+ const upload = storageLayer.startUpload(req.params.bucketId, name, objectContentType, req.body, req.header("authorization"));
340
339
  storageLayer.uploadBytes(upload.uploadId, Buffer.alloc(0));
341
340
  const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
342
341
  res.header("x-goog-upload-chunk-granularity", "10000");
@@ -352,7 +351,7 @@ function createFirebaseEndpoints(emulator) {
352
351
  return;
353
352
  }
354
353
  const uploadId = req.query.upload_id.toString();
355
- if (uploadCommand == "query") {
354
+ if (uploadCommand === "query") {
356
355
  const upload = storageLayer.queryUpload(uploadId);
357
356
  if (!upload) {
358
357
  res.sendStatus(400);
@@ -362,13 +361,15 @@ function createFirebaseEndpoints(emulator) {
362
361
  res.sendStatus(200);
363
362
  return;
364
363
  }
365
- if (uploadCommand == "cancel") {
366
- const upload = storageLayer.cancelUpload(uploadId);
367
- if (!upload) {
368
- res.sendStatus(400);
369
- return;
364
+ if (uploadCommand === "cancel") {
365
+ const upload = storageLayer.queryUpload(uploadId);
366
+ if (upload) {
367
+ const cancelled = storageLayer.cancelUpload(upload);
368
+ res.sendStatus(cancelled ? 200 : 400);
369
+ }
370
+ else {
371
+ res.sendStatus(404);
370
372
  }
371
- res.sendStatus(200);
372
373
  return;
373
374
  }
374
375
  let upload;
@@ -394,20 +395,18 @@ function createFirebaseEndpoints(emulator) {
394
395
  res.header("x-gupload-uploadid", upload.uploadId);
395
396
  }
396
397
  if (uploadCommand.includes("finalize")) {
397
- const finalizedUpload = storageLayer.finalizeUpload(uploadId);
398
- if (!finalizedUpload) {
398
+ upload = storageLayer.queryUpload(uploadId);
399
+ if (!upload) {
399
400
  res.sendStatus(400);
400
401
  return;
401
402
  }
402
- upload = finalizedUpload.upload;
403
- res.header("x-goog-upload-status", "final");
404
403
  if (!(await isPermitted({
405
404
  ruleset: emulator.rules,
406
405
  method: types_2.RulesetOperationMethod.CREATE,
407
406
  path: operationPath,
408
- authorization: req.header("authorization"),
407
+ authorization: upload.authorization,
409
408
  file: {
410
- after: (_a = storageLayer.getMetadata(req.params.bucketId, name)) === null || _a === void 0 ? void 0 : _a.asRulesResource(),
409
+ after: storageLayer.createMetadata(upload).asRulesResource(),
411
410
  },
412
411
  }))) {
413
412
  storageLayer.deleteFile(upload.bucketId, name);
@@ -418,11 +417,13 @@ function createFirebaseEndpoints(emulator) {
418
417
  },
419
418
  });
420
419
  }
421
- const md = finalizedUpload.file.metadata;
422
- if (md.downloadTokens.length == 0) {
420
+ res.header("x-goog-upload-status", "final");
421
+ const uploadedFile = storageLayer.finalizeUpload(upload);
422
+ const md = uploadedFile.metadata;
423
+ if (md.downloadTokens.length === 0) {
423
424
  md.addDownloadToken();
424
425
  }
425
- res.json(new metadata_1.OutgoingFirebaseMetadata(finalizedUpload.file.metadata));
426
+ res.json(new metadata_1.OutgoingFirebaseMetadata(uploadedFile.metadata));
426
427
  }
427
428
  else if (!upload) {
428
429
  res.sendStatus(400);
@@ -447,12 +448,17 @@ function createFirebaseEndpoints(emulator) {
447
448
  firebaseStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
448
449
  const decodedObjectId = decodeURIComponent(req.params.objectId);
449
450
  const operationPath = ["b", req.params.bucketId, "o", decodedObjectId].join("/");
451
+ const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId);
452
+ const rulesFiles = {};
453
+ if (md) {
454
+ rulesFiles.before = md.asRulesResource();
455
+ }
450
456
  if (!(await isPermitted({
451
457
  ruleset: emulator.rules,
452
458
  method: types_2.RulesetOperationMethod.DELETE,
453
459
  path: operationPath,
454
460
  authorization: req.header("authorization"),
455
- file: {},
461
+ file: rulesFiles,
456
462
  }))) {
457
463
  return res.status(403).json({
458
464
  error: {
@@ -461,7 +467,6 @@ function createFirebaseEndpoints(emulator) {
461
467
  },
462
468
  });
463
469
  }
464
- const md = storageLayer.getMetadata(req.params.bucketId, decodedObjectId);
465
470
  if (!md) {
466
471
  res.sendStatus(404);
467
472
  return;
@@ -26,7 +26,7 @@ function createCloudEndpoints(emulator) {
26
26
  res.sendStatus(404);
27
27
  return;
28
28
  }
29
- if (req.query.alt == "media") {
29
+ if (req.query.alt === "media") {
30
30
  return sendFileBytes(md, storageLayer, req, res);
31
31
  }
32
32
  const outgoingMd = new metadata_1.CloudStorageObjectMetadata(md);
@@ -80,18 +80,13 @@ function createCloudEndpoints(emulator) {
80
80
  resolve();
81
81
  });
82
82
  });
83
- let upload = storageLayer.uploadBytes(uploadId, req.body);
83
+ const upload = storageLayer.uploadBytes(uploadId, req.body);
84
84
  if (!upload) {
85
85
  res.sendStatus(400);
86
86
  return;
87
87
  }
88
- const finalizedUpload = storageLayer.finalizeUpload(uploadId);
89
- if (!finalizedUpload) {
90
- res.sendStatus(400);
91
- return;
92
- }
93
- upload = finalizedUpload.upload;
94
- res.status(200).json(new metadata_1.CloudStorageObjectMetadata(finalizedUpload.file.metadata)).send();
88
+ const uploadedFile = storageLayer.finalizeUpload(upload);
89
+ res.status(200).json(new metadata_1.CloudStorageObjectMetadata(uploadedFile.metadata)).send();
95
90
  });
96
91
  gcloudStorageAPI.post("/b/:bucketId/o/:objectId/acl", (req, res) => {
97
92
  var _a, _b;
@@ -130,10 +125,10 @@ function createCloudEndpoints(emulator) {
130
125
  res.sendStatus(400);
131
126
  return;
132
127
  }
133
- if (req.query.uploadType == "resumable") {
128
+ if (req.query.uploadType === "resumable") {
134
129
  const upload = storageLayer.startUpload(req.params.bucketId, name, contentType, req.body);
135
130
  const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
136
- if (emulatorInfo == undefined) {
131
+ if (emulatorInfo === undefined) {
137
132
  res.sendStatus(500);
138
133
  return;
139
134
  }
@@ -202,7 +197,7 @@ function sendFileBytes(md, storageLayer, req, res) {
202
197
  res.sendStatus(404);
203
198
  return;
204
199
  }
205
- const isGZipped = md.contentEncoding == "gzip";
200
+ const isGZipped = md.contentEncoding === "gzip";
206
201
  if (isGZipped) {
207
202
  data = (0, zlib_1.gunzipSync)(data);
208
203
  }
@@ -40,7 +40,7 @@ class StoredFile {
40
40
  }
41
41
  exports.StoredFile = StoredFile;
42
42
  class ResumableUpload {
43
- constructor(bucketId, objectId, uploadId, contentType, metadata) {
43
+ constructor(bucketId, objectId, uploadId, contentType, metadata, authorization) {
44
44
  this._currentBytesUploaded = 0;
45
45
  this._status = UploadStatus.ACTIVE;
46
46
  this._bucketId = bucketId;
@@ -48,6 +48,7 @@ class ResumableUpload {
48
48
  this._uploadId = uploadId;
49
49
  this._contentType = contentType;
50
50
  this._metadata = metadata;
51
+ this._authorization = authorization;
51
52
  this._fileLocation = encodeURIComponent(`${uploadId}_b_${bucketId}_o_${objectId}`);
52
53
  this._currentBytesUploaded = 0;
53
54
  }
@@ -69,6 +70,9 @@ class ResumableUpload {
69
70
  set contentType(contentType) {
70
71
  this._contentType = contentType;
71
72
  }
73
+ get authorization() {
74
+ return this._authorization;
75
+ }
72
76
  get currentBytesUploaded() {
73
77
  return this._currentBytesUploaded;
74
78
  }
@@ -110,7 +114,7 @@ class StorageLayer {
110
114
  }
111
115
  }
112
116
  async listBuckets() {
113
- if (this._buckets.size == 0) {
117
+ if (this._buckets.size === 0) {
114
118
  let adminSdkConfig = await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(this._projectId);
115
119
  if (!adminSdkConfig) {
116
120
  adminSdkConfig = (0, adminSdkConfig_1.constructDefaultAdminSdkConfig)(this._projectId);
@@ -127,6 +131,16 @@ class StorageLayer {
127
131
  }
128
132
  return;
129
133
  }
134
+ createMetadata(upload) {
135
+ const bytes = this._persistence.readBytes(upload.fileLocation, upload.currentBytesUploaded);
136
+ return new metadata_1.StoredFileMetadata({
137
+ name: upload.objectId,
138
+ bucket: upload.bucketId,
139
+ contentType: "",
140
+ contentEncoding: upload.metadata.contentEncoding,
141
+ customMetadata: upload.metadata.metadata,
142
+ }, this._cloudFunctions, bytes);
143
+ }
130
144
  getBytes(bucket, object, size, offset) {
131
145
  const key = this.path(bucket, object);
132
146
  const val = this._files.get(key);
@@ -139,22 +153,21 @@ class StorageLayer {
139
153
  public(value) {
140
154
  this._files = value;
141
155
  }
142
- startUpload(bucket, object, contentType, metadata) {
156
+ startUpload(bucket, object, contentType, metadata, authorization) {
143
157
  const uploadId = (0, uuid_1.v4)();
144
- const upload = new ResumableUpload(bucket, object, uploadId, contentType, metadata);
158
+ const upload = new ResumableUpload(bucket, object, uploadId, contentType, metadata, authorization);
145
159
  this._uploads.set(uploadId, upload);
146
160
  return upload;
147
161
  }
148
162
  queryUpload(uploadId) {
149
163
  return this._uploads.get(uploadId);
150
164
  }
151
- cancelUpload(uploadId) {
152
- const upload = this._uploads.get(uploadId);
153
- if (!upload) {
154
- return undefined;
165
+ cancelUpload(upload) {
166
+ if (upload.status === UploadStatus.ACTIVE) {
167
+ this._persistence.deleteFile(upload.fileLocation);
168
+ upload.status = UploadStatus.CANCELLED;
155
169
  }
156
- upload.status = UploadStatus.CANCELLED;
157
- this._persistence.deleteFile(upload.fileLocation);
170
+ return upload.status === UploadStatus.CANCELLED;
158
171
  }
159
172
  uploadBytes(uploadId, bytes) {
160
173
  const upload = this._uploads.get(uploadId);
@@ -175,7 +188,7 @@ class StorageLayer {
175
188
  filePath += "%2F";
176
189
  }
177
190
  const file = this._files.get(filePath);
178
- if (file == undefined) {
191
+ if (file === undefined) {
179
192
  return false;
180
193
  }
181
194
  else {
@@ -188,27 +201,16 @@ class StorageLayer {
188
201
  async deleteAll() {
189
202
  return this._persistence.deleteAll();
190
203
  }
191
- finalizeUpload(uploadId) {
192
- const upload = this._uploads.get(uploadId);
193
- if (!upload) {
194
- return undefined;
195
- }
204
+ finalizeUpload(upload) {
196
205
  upload.status = UploadStatus.FINISHED;
206
+ const metadata = this.createMetadata(upload);
197
207
  const filePath = this.path(upload.bucketId, upload.objectId);
198
- const bytes = this._persistence.readBytes(upload.fileLocation, upload.currentBytesUploaded);
199
- const finalMetadata = new metadata_1.StoredFileMetadata({
200
- name: upload.objectId,
201
- bucket: upload.bucketId,
202
- contentType: "",
203
- contentEncoding: upload.metadata.contentEncoding,
204
- customMetadata: upload.metadata.metadata,
205
- }, this._cloudFunctions, bytes, upload.metadata);
206
- const file = new StoredFile(finalMetadata, filePath);
208
+ const file = new StoredFile(metadata, filePath);
207
209
  this._files.set(filePath, file);
208
210
  this._persistence.deleteFile(filePath, true);
209
211
  this._persistence.renameFile(upload.fileLocation, filePath);
210
212
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
211
- return { upload: upload, file: file };
213
+ return file;
212
214
  }
213
215
  oneShotUpload(bucket, object, contentType, incomingMetadata, bytes) {
214
216
  const filePath = this.path(bucket, object);
@@ -220,7 +222,7 @@ class StorageLayer {
220
222
  contentType: incomingMetadata.contentType || "application/octet-stream",
221
223
  contentEncoding: incomingMetadata.contentEncoding,
222
224
  customMetadata: incomingMetadata.metadata,
223
- }, this._cloudFunctions, bytes, incomingMetadata);
225
+ }, this._cloudFunctions, bytes);
224
226
  const file = new StoredFile(md, this._persistence.getDiskPath(filePath));
225
227
  this._files.set(filePath, file);
226
228
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
@@ -242,7 +244,7 @@ class StorageLayer {
242
244
  let items = [];
243
245
  const prefixes = new Set();
244
246
  for (const [, file] of this._files) {
245
- if (file.metadata.bucket != bucket) {
247
+ if (file.metadata.bucket !== bucket) {
246
248
  continue;
247
249
  }
248
250
  let name = `${delimiter}${file.metadata.name}`;
@@ -254,7 +256,7 @@ class StorageLayer {
254
256
  name = name.substring(prefix.length);
255
257
  }
256
258
  const startAtIndex = name.indexOf(delimiter);
257
- if (startAtIndex == -1) {
259
+ if (startAtIndex === -1) {
258
260
  if (!file.metadata.name.endsWith("/")) {
259
261
  items.push(file.metadata.name);
260
262
  }
@@ -266,8 +268,8 @@ class StorageLayer {
266
268
  }
267
269
  items.sort();
268
270
  if (pageToken) {
269
- const idx = items.findIndex((v) => v == pageToken);
270
- if (idx != -1) {
271
+ const idx = items.findIndex((v) => v === pageToken);
272
+ if (idx !== -1) {
271
273
  items = items.slice(idx);
272
274
  }
273
275
  }
@@ -293,7 +295,7 @@ class StorageLayer {
293
295
  }
294
296
  let items = [];
295
297
  for (const [, file] of this._files) {
296
- if (file.metadata.bucket != bucket) {
298
+ if (file.metadata.bucket !== bucket) {
297
299
  continue;
298
300
  }
299
301
  let name = file.metadata.name;
@@ -308,8 +310,8 @@ class StorageLayer {
308
310
  }
309
311
  items.sort();
310
312
  if (pageToken) {
311
- const idx = items.findIndex((v) => v == pageToken);
312
- if (idx != -1) {
313
+ const idx = items.findIndex((v) => v === pageToken);
314
+ if (idx !== -1) {
313
315
  items = items.slice(idx);
314
316
  }
315
317
  }
@@ -33,14 +33,14 @@ class StorageEmulator {
33
33
  const { host, port } = this.getInfo();
34
34
  await this._rulesRuntime.start(this.args.auto_download);
35
35
  this._app = await (0, server_1.createApp)(this.args.projectId, this);
36
- if (typeof this.args.rules == "string") {
36
+ if (typeof this.args.rules === "string") {
37
37
  const rulesFile = this.args.rules;
38
38
  this.updateRulesSource(rulesFile);
39
39
  }
40
40
  else {
41
41
  this._rulesetSource = this.args.rules;
42
42
  }
43
- if (!this._rulesetSource || this._rulesetSource.files.length == 0) {
43
+ if (!this._rulesetSource || this._rulesetSource.files.length === 0) {
44
44
  throw new error_1.FirebaseError("Can not initialize Storage emulator without a rules source / file.");
45
45
  }
46
46
  else if (this._rulesetSource.files.length > 1) {
@@ -149,9 +149,9 @@ class StoredFileMetadata {
149
149
  if (!this.downloadTokens.length) {
150
150
  return;
151
151
  }
152
- const remainingTokens = this.downloadTokens.filter((t) => t != token);
152
+ const remainingTokens = this.downloadTokens.filter((t) => t !== token);
153
153
  this.downloadTokens = remainingTokens;
154
- if (remainingTokens.length == 0) {
154
+ if (remainingTokens.length === 0) {
155
155
  this.addDownloadToken();
156
156
  }
157
157
  this.update({});
@@ -20,7 +20,7 @@ class StorageRulesetInstance {
20
20
  this.rulesetName = rulesetName;
21
21
  }
22
22
  async verify(opts, runtimeVariableOverrides = {}) {
23
- if (opts.method == types_1.RulesetOperationMethod.LIST && this.rulesVersion < 2) {
23
+ if (opts.method === types_1.RulesetOperationMethod.LIST && this.rulesVersion < 2) {
24
24
  const issues = new StorageRulesIssues();
25
25
  issues.warnings.push("Permission denied. List operations are only allowed for rules_version='2'.");
26
26
  return {
@@ -103,6 +103,7 @@ class StorageRulesRuntime {
103
103
  (_a = this._childprocess.stderr) === null || _a === void 0 ? void 0 : _a.on("data", (buf) => {
104
104
  const error = buf.toString();
105
105
  if (error.includes("jarfile")) {
106
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_2.Emulators.STORAGE).log("ERROR", error);
106
107
  throw new error_1.FirebaseError("There was an issue starting the rules emulator, please run 'firebase setup:emulators:storage` again");
107
108
  }
108
109
  else {
@@ -111,7 +112,7 @@ class StorageRulesRuntime {
111
112
  });
112
113
  (_b = this._childprocess.stdout) === null || _b === void 0 ? void 0 : _b.on("data", (buf) => {
113
114
  const serializedRuntimeActionResponse = buf.toString("UTF8").trim();
114
- if (serializedRuntimeActionResponse != "") {
115
+ if (serializedRuntimeActionResponse !== "") {
115
116
  let rap;
116
117
  try {
117
118
  rap = JSON.parse(serializedRuntimeActionResponse);
@@ -217,14 +218,14 @@ class StorageRulesRuntime {
217
218
  }
218
219
  exports.StorageRulesRuntime = StorageRulesRuntime;
219
220
  function toExpressionValue(obj) {
220
- if (typeof obj == "string") {
221
+ if (typeof obj === "string") {
221
222
  return { string_value: obj };
222
223
  }
223
- if (typeof obj == "boolean") {
224
+ if (typeof obj === "boolean") {
224
225
  return { bool_value: obj };
225
226
  }
226
- if (typeof obj == "number") {
227
- if (Math.floor(obj) == obj) {
227
+ if (typeof obj === "number") {
228
+ if (Math.floor(obj) === obj) {
228
229
  return { int_value: obj };
229
230
  }
230
231
  else {
@@ -255,7 +256,7 @@ function toExpressionValue(obj) {
255
256
  null_value: 0,
256
257
  };
257
258
  }
258
- if (typeof obj == "object") {
259
+ if (typeof obj === "object") {
259
260
  const fields = {};
260
261
  Object.keys(obj).forEach((key) => {
261
262
  fields[key] = toExpressionValue(obj[key]);
@@ -13,6 +13,7 @@ var Emulators;
13
13
  Emulators["UI"] = "ui";
14
14
  Emulators["LOGGING"] = "logging";
15
15
  Emulators["STORAGE"] = "storage";
16
+ Emulators["EXTENSIONS"] = "extensions";
16
17
  })(Emulators = exports.Emulators || (exports.Emulators = {}));
17
18
  exports.DOWNLOADABLE_EMULATORS = [
18
19
  Emulators.FIRESTORE,
@@ -48,6 +49,7 @@ exports.EMULATORS_SUPPORTED_BY_UI = [
48
49
  Emulators.FIRESTORE,
49
50
  Emulators.FUNCTIONS,
50
51
  Emulators.STORAGE,
52
+ Emulators.EXTENSIONS,
51
53
  ];
52
54
  exports.EMULATORS_SUPPORTED_BY_USE_EMULATOR = [
53
55
  Emulators.AUTH,
@@ -59,6 +61,7 @@ exports.ALL_EMULATORS = [
59
61
  Emulators.HUB,
60
62
  Emulators.UI,
61
63
  Emulators.LOGGING,
64
+ Emulators.EXTENSIONS,
62
65
  ...exports.ALL_SERVICE_EMULATORS,
63
66
  ];
64
67
  function isDownloadableEmulator(value) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensure = exports.check = exports.POLL_SETTINGS = void 0;
3
+ exports.enableApiURI = exports.ensure = exports.check = exports.POLL_SETTINGS = void 0;
4
4
  const cli_color_1 = require("cli-color");
5
5
  const track = require("./track");
6
6
  const api_1 = require("./api");
@@ -75,3 +75,7 @@ async function ensure(projectId, apiName, prefix, silent = false) {
75
75
  return enableApiWithRetries(projectId, apiName, prefix, silent);
76
76
  }
77
77
  exports.ensure = ensure;
78
+ function enableApiURI(projectId, apiName) {
79
+ return `https://console.cloud.google.com/apis/library/${apiName}?project=${projectId}`;
80
+ }
81
+ exports.enableApiURI = enableApiURI;
package/lib/error.js CHANGED
@@ -23,7 +23,7 @@ function isBillingError(e) {
23
23
  return !!((_d = (_c = (_b = (_a = e.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) === null || _c === void 0 ? void 0 : _c.details) === null || _d === void 0 ? void 0 : _d.find((d) => {
24
24
  var _a;
25
25
  return (((_a = d.violations) === null || _a === void 0 ? void 0 : _a.find((v) => v.type === "serviceusage/billing-enabled")) ||
26
- d.reason == "UREQ_PROJECT_BILLING_NOT_FOUND");
26
+ d.reason === "UREQ_PROJECT_BILLING_NOT_FOUND");
27
27
  }));
28
28
  }
29
29
  exports.isBillingError = isBillingError;
@@ -20,7 +20,7 @@ var SecretUpdateAction;
20
20
  function checkResponse(response, spec) {
21
21
  let valid = true;
22
22
  let responses;
23
- if (spec.required && (response == "" || response == undefined)) {
23
+ if (spec.required && (response === "" || response === undefined)) {
24
24
  utils.logWarning(`Param ${spec.param} is required, but no value was provided.`);
25
25
  return false;
26
26
  }
@@ -199,7 +199,7 @@ async function generateSecretName(projectId, instanceId, paramName) {
199
199
  return secretName;
200
200
  }
201
201
  async function addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue) {
202
- const version = await secretManagerApi.addVersion(secret, secretValue);
202
+ const version = await secretManagerApi.addVersion(projectId, secret.name, secretValue);
203
203
  await secretsUtils.grantFirexServiceAgentSecretAdminRole(secret);
204
204
  return `projects/${version.secret.projectId}/secrets/${version.secret.name}/versions/${version.versionId}`;
205
205
  }
@@ -58,7 +58,9 @@ function breakingChangesInUpdate(versionsInUpdate) {
58
58
  const semvers = versionsInUpdate.map((v) => semver.parse(v)).sort(semver.compare);
59
59
  for (let i = 1; i < semvers.length; i++) {
60
60
  const hasMajorBump = semvers[i - 1].major < semvers[i].major;
61
- const hasMinorBumpInPreview = semvers[i - 1].major == 0 && semvers[i].major == 0 && semvers[i - 1].minor < semvers[i].minor;
61
+ const hasMinorBumpInPreview = semvers[i - 1].major === 0 &&
62
+ semvers[i].major === 0 &&
63
+ semvers[i - 1].minor < semvers[i].minor;
62
64
  if (hasMajorBump || hasMinorBumpInPreview) {
63
65
  breakingVersions.push(semvers[i].raw);
64
66
  }
@@ -49,7 +49,7 @@ Please select the one that you would like to associate with this project:`,
49
49
  billingEnabled = await openBillingAccount(projectId, billingURL, true);
50
50
  }
51
51
  else {
52
- const billingAccount = accounts.find((a) => a.displayName == answer);
52
+ const billingAccount = accounts.find((a) => a.displayName === answer);
53
53
  billingEnabled = await cloudbilling.setBillingAccount(projectId, billingAccount.name);
54
54
  }
55
55
  return logBillingStatus(billingEnabled, projectId);
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diagnose = void 0;
4
+ const extensionsHelper_1 = require("./extensionsHelper");
5
+ const getProjectNumber_1 = require("../getProjectNumber");
6
+ const utils = require("../utils");
7
+ const resourceManager = require("../gcp/resourceManager");
8
+ const extensionsApi_1 = require("./extensionsApi");
9
+ const prompt_1 = require("../prompt");
10
+ const logger_1 = require("../logger");
11
+ const error_1 = require("../error");
12
+ const SERVICE_AGENT_ROLE = "roles/firebasemods.serviceAgent";
13
+ async function diagnose(projectId) {
14
+ const projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
15
+ const firexSaProjectId = utils.envOverride("FIREBASE_EXTENSIONS_SA_PROJECT_ID", "gcp-sa-firebasemods");
16
+ const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`;
17
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Checking project IAM policy...");
18
+ await (0, extensionsApi_1.listInstances)(projectId);
19
+ let policy;
20
+ try {
21
+ policy = await resourceManager.getIamPolicy(projectId);
22
+ logger_1.logger.debug(policy);
23
+ }
24
+ catch (e) {
25
+ if (e instanceof error_1.FirebaseError && e.status === 403) {
26
+ throw new error_1.FirebaseError("Unable to get project IAM policy, permission denied (403). Please " +
27
+ "make sure you have sufficient project privileges or if this is a brand new project " +
28
+ "try again in a few minutes.");
29
+ }
30
+ throw e;
31
+ }
32
+ if (policy.bindings.find((b) => b.role === SERVICE_AGENT_ROLE && b.members.includes("serviceAccount:" + saEmail))) {
33
+ utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "Project IAM policy OK");
34
+ return true;
35
+ }
36
+ else {
37
+ utils.logWarning("Firebase Extensions Service Agent is missing a required IAM role " +
38
+ "`Firebase Extensions API Service Agent`.");
39
+ const fix = await (0, prompt_1.promptOnce)({
40
+ type: "confirm",
41
+ message: "Would you like to fix the issue by updating IAM policy to include Firebase " +
42
+ "Extensions Service Agent with role `Firebase Extensions API Service Agent`",
43
+ });
44
+ if (fix) {
45
+ policy.bindings.push({
46
+ role: SERVICE_AGENT_ROLE,
47
+ members: ["serviceAccount:" + saEmail],
48
+ });
49
+ await resourceManager.setIamPolicy(projectId, policy, "bindings");
50
+ utils.logSuccess("Project IAM policy updated successfully");
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ }
56
+ exports.diagnose = diagnose;
@@ -136,7 +136,7 @@ async function displayUpdateChangesRequiringConfirmation(args) {
136
136
  }
137
137
  exports.displayUpdateChangesRequiringConfirmation = displayUpdateChangesRequiringConfirmation;
138
138
  function compareResources(resource1, resource2) {
139
- return resource1.name == resource2.name && resource1.type == resource2.type;
139
+ return resource1.name === resource2.name && resource1.type === resource2.type;
140
140
  }
141
141
  function getResourceReadableName(resource) {
142
142
  return resource.type === "firebaseextensions.v1beta.function"