firebase-tools 11.3.0 → 11.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 (50) hide show
  1. package/README.md +8 -15
  2. package/lib/apiv2.js +5 -0
  3. package/lib/checkValidTargetFilters.js +3 -2
  4. package/lib/command.js +1 -0
  5. package/lib/commands/hosting-clone.js +5 -0
  6. package/lib/commands/login-ci.js +2 -0
  7. package/lib/database/rulesConfig.js +35 -8
  8. package/lib/deploy/functions/backend.js +6 -4
  9. package/lib/deploy/functions/build.js +107 -95
  10. package/lib/deploy/functions/ensure.js +1 -1
  11. package/lib/deploy/functions/params.js +5 -2
  12. package/lib/deploy/functions/prepare.js +3 -3
  13. package/lib/deploy/functions/pricing.js +3 -2
  14. package/lib/deploy/functions/prompts.js +1 -1
  15. package/lib/deploy/functions/release/fabricator.js +8 -7
  16. package/lib/deploy/functions/release/index.js +4 -0
  17. package/lib/deploy/functions/runtimes/discovery/parsing.js +19 -8
  18. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +115 -107
  19. package/lib/deploy/functions/runtimes/node/parseTriggers.js +53 -21
  20. package/lib/deploy/functions/services/storage.js +6 -0
  21. package/lib/deploy/hosting/convertConfig.js +87 -16
  22. package/lib/deploy/hosting/deploy.js +1 -1
  23. package/lib/deploy/index.js +1 -1
  24. package/lib/deploy/storage/prepare.js +29 -6
  25. package/lib/emulator/controller.js +0 -1
  26. package/lib/emulator/functionsEmulator.js +3 -0
  27. package/lib/emulator/functionsEmulatorRuntime.js +1 -1
  28. package/lib/emulator/functionsEmulatorShared.js +6 -11
  29. package/lib/emulator/storage/files.js +19 -22
  30. package/lib/emulator/storage/metadata.js +6 -6
  31. package/lib/emulator/storage/persistence.js +26 -12
  32. package/lib/extensions/displayExtensionInfo.js +1 -101
  33. package/lib/extensions/emulator/triggerHelper.js +2 -2
  34. package/lib/extensions/updateHelper.js +1 -7
  35. package/lib/functional.js +16 -1
  36. package/lib/functions/env.js +47 -2
  37. package/lib/gcp/cloudfunctions.js +21 -8
  38. package/lib/gcp/cloudfunctionsv2.js +43 -19
  39. package/lib/gcp/cloudscheduler.js +25 -13
  40. package/lib/gcp/cloudtasks.js +4 -3
  41. package/lib/gcp/iam.js +20 -17
  42. package/lib/gcp/proto.js +18 -6
  43. package/lib/gcp/resourceManager.js +25 -3
  44. package/lib/index.js +1 -1
  45. package/lib/previews.js +1 -1
  46. package/lib/rc.js +3 -9
  47. package/lib/requireAuth.js +4 -0
  48. package/lib/rulesDeploy.js +40 -3
  49. package/npm-shrinkwrap.json +48 -37
  50. package/package.json +7 -4
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertConfig = void 0;
4
4
  const error_1 = require("../../error");
5
5
  const backend_1 = require("../functions/backend");
6
+ const backend = require("../functions/backend");
7
+ const utils_1 = require("../../utils");
6
8
  function has(obj, k) {
7
9
  return obj[k] !== undefined;
8
10
  }
@@ -39,24 +41,73 @@ async function convertConfig(context, payload, config, finalize) {
39
41
  if (!config) {
40
42
  return out;
41
43
  }
42
- const endpointBeingDeployed = (serviceId, region = "us-central1") => {
43
- var _a;
44
+ const endpointFromBackend = (targetBackend, functionsEndpointInfo) => {
45
+ const backendsForId = backend.allEndpoints(targetBackend).filter((endpoint) => {
46
+ return endpoint.id === functionsEndpointInfo.serviceId;
47
+ });
48
+ const matchingBackends = backendsForId.filter((endpoint) => {
49
+ return ((!functionsEndpointInfo.region || endpoint.region === functionsEndpointInfo.region) &&
50
+ (!functionsEndpointInfo.platform || endpoint.platform === functionsEndpointInfo.platform));
51
+ });
52
+ if (matchingBackends.length > 1) {
53
+ for (const endpoint of matchingBackends) {
54
+ if (endpoint.region === "us-central1") {
55
+ (0, utils_1.logLabeledBullet)(`hosting[${config.site}]`, `Function \`${functionsEndpointInfo.serviceId}\` found in multiple regions, defaulting to \`us-central1\`. ` +
56
+ `To rewrite to a different region, specify a \`region\` for the rewrite in \`firebase.json\`.`);
57
+ return endpoint;
58
+ }
59
+ }
60
+ throw new error_1.FirebaseError(`More than one backend found for function name: ${functionsEndpointInfo.serviceId}. If the function is deployed in multiple regions, you must specify a region.`);
61
+ }
62
+ if (matchingBackends.length === 1) {
63
+ const endpoint = matchingBackends[0];
64
+ if (endpoint && ((0, backend_1.isHttpsTriggered)(endpoint) || (0, backend_1.isCallableTriggered)(endpoint))) {
65
+ return endpoint;
66
+ }
67
+ }
68
+ return;
69
+ };
70
+ const endpointBeingDeployed = (functionsEndpointInfo) => {
44
71
  for (const { wantBackend } of Object.values(payload.functions || {})) {
45
- const endpoint = (_a = wantBackend === null || wantBackend === void 0 ? void 0 : wantBackend.endpoints[region]) === null || _a === void 0 ? void 0 : _a[serviceId];
46
- if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint) && endpoint.platform === "gcfv2")
72
+ if (!wantBackend) {
73
+ continue;
74
+ }
75
+ const endpoint = endpointFromBackend(wantBackend, functionsEndpointInfo);
76
+ if (endpoint) {
47
77
  return endpoint;
78
+ }
48
79
  }
49
- return undefined;
80
+ return;
50
81
  };
51
- const matchingEndpoint = async (serviceId, region = "us-central1") => {
52
- const pendingEndpoint = endpointBeingDeployed(serviceId, region);
82
+ const matchingEndpoint = async (functionsEndpointInfo) => {
83
+ const pendingEndpoint = endpointBeingDeployed(functionsEndpointInfo);
53
84
  if (pendingEndpoint)
54
85
  return pendingEndpoint;
55
86
  const backend = await (0, backend_1.existingBackend)(context);
56
87
  return (0, backend_1.allEndpoints)(backend).find((it) => (0, backend_1.isHttpsTriggered)(it) &&
57
- it.platform === "gcfv2" &&
58
- it.id === serviceId &&
59
- it.region === region);
88
+ it.id === functionsEndpointInfo.serviceId &&
89
+ (!functionsEndpointInfo.platform || it.platform === functionsEndpointInfo.platform) &&
90
+ (!functionsEndpointInfo.region || it.region === functionsEndpointInfo.region));
91
+ };
92
+ const findEndpointWithValidRegion = async (rewrite, context) => {
93
+ if ("function" in rewrite) {
94
+ const foundEndpointToBeDeployed = endpointBeingDeployed({
95
+ serviceId: rewrite.function,
96
+ region: rewrite.region,
97
+ });
98
+ if (foundEndpointToBeDeployed) {
99
+ return foundEndpointToBeDeployed;
100
+ }
101
+ const existingBackend = await backend.existingBackend(context);
102
+ const endpointAlreadyDeployed = endpointFromBackend(existingBackend, {
103
+ serviceId: rewrite.function,
104
+ region: rewrite.region,
105
+ });
106
+ if (endpointAlreadyDeployed) {
107
+ return endpointAlreadyDeployed;
108
+ }
109
+ }
110
+ return;
60
111
  };
61
112
  if (Array.isArray(config.rewrites)) {
62
113
  out.rewrites = [];
@@ -66,19 +117,33 @@ async function convertConfig(context, payload, config, finalize) {
66
117
  vRewrite.path = rewrite.destination;
67
118
  }
68
119
  else if ("function" in rewrite) {
69
- if (!finalize && endpointBeingDeployed(rewrite.function, rewrite.region))
120
+ if (!finalize &&
121
+ endpointBeingDeployed({
122
+ serviceId: rewrite.function,
123
+ platform: "gcfv2",
124
+ region: rewrite.region,
125
+ })) {
70
126
  continue;
71
- const endpoint = await matchingEndpoint(rewrite.function, rewrite.region);
127
+ }
128
+ const endpoint = await matchingEndpoint({
129
+ serviceId: rewrite.function,
130
+ platform: "gcfv2",
131
+ region: rewrite.region,
132
+ });
72
133
  if (endpoint) {
73
134
  vRewrite.run = { serviceId: endpoint.id, region: endpoint.region };
74
135
  }
75
136
  else {
76
137
  vRewrite.function = rewrite.function;
77
- if (rewrite.region) {
78
- vRewrite.functionRegion = rewrite.region;
138
+ const foundEndpoint = await findEndpointWithValidRegion(rewrite, context);
139
+ if (foundEndpoint) {
140
+ vRewrite.functionRegion = foundEndpoint.region;
79
141
  }
80
142
  else {
81
- vRewrite.functionRegion = "us-central1";
143
+ if (rewrite.region && rewrite.region !== "us-central1") {
144
+ throw new error_1.FirebaseError(`Unable to find a valid endpoint for function \`${vRewrite.function}\``);
145
+ }
146
+ (0, utils_1.logLabeledWarning)(`hosting[${config.site}]`, `Unable to find a valid endpoint for function \`${vRewrite.function}\`, but still including it in the config`);
82
147
  }
83
148
  }
84
149
  }
@@ -86,8 +151,14 @@ async function convertConfig(context, payload, config, finalize) {
86
151
  vRewrite.dynamicLinks = rewrite.dynamicLinks;
87
152
  }
88
153
  else if ("run" in rewrite) {
89
- if (!finalize && endpointBeingDeployed(rewrite.run.serviceId, rewrite.run.region))
154
+ if (!finalize &&
155
+ endpointBeingDeployed({
156
+ serviceId: rewrite.run.serviceId,
157
+ platform: "gcfv2",
158
+ region: rewrite.run.region,
159
+ })) {
90
160
  continue;
161
+ }
91
162
  vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run);
92
163
  }
93
164
  out.rewrites.push(vRewrite);
@@ -36,7 +36,7 @@ async function deploy(context, options) {
36
36
  (0, utils_1.logLabeledBullet)(`hosting[${deploy.site}]`, 'no "public" directory to upload, continuing with release');
37
37
  return runDeploys(deploys, debugging);
38
38
  }
39
- (0, utils_1.logLabeledBullet)("hosting[" + deploy.site + "]", "beginning deploy...");
39
+ (0, utils_1.logLabeledBullet)(`hosting[${deploy.site}]`, "beginning deploy...");
40
40
  const t0 = Date.now();
41
41
  const publicDir = options.config.path(deploy.config.public);
42
42
  const files = (0, listFiles_1.listFiles)(publicDir, deploy.config.ignore);
@@ -52,7 +52,7 @@ const deploy = async function (targetNames, options, customContext = {}) {
52
52
  for (const targetName of targetNames) {
53
53
  const target = TARGETS[targetName];
54
54
  if (!target) {
55
- return Promise.reject(new error_1.FirebaseError((0, cli_color_1.bold)(targetName) + " is not a valid deploy target", { exit: 1 }));
55
+ return Promise.reject(new error_1.FirebaseError(`${(0, cli_color_1.bold)(targetName)} is not a valid deploy target`));
56
56
  }
57
57
  predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
58
58
  prepares.push(target.prepare);
@@ -3,25 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const _ = require("lodash");
4
4
  const gcp = require("../../gcp");
5
5
  const rulesDeploy_1 = require("../../rulesDeploy");
6
+ const error_1 = require("../../error");
6
7
  async function default_1(context, options) {
7
8
  let rulesConfig = options.config.get("storage");
8
9
  if (!rulesConfig) {
9
10
  return;
10
11
  }
12
+ const onlyTargets = new Set();
13
+ let allStorage = !options.only;
14
+ if (options.only) {
15
+ const split = options.only.split(",");
16
+ if (split.includes("storage")) {
17
+ allStorage = true;
18
+ }
19
+ else {
20
+ for (const value of split) {
21
+ if (value.startsWith("storage:")) {
22
+ onlyTargets.add(value.split(":")[1]);
23
+ }
24
+ }
25
+ }
26
+ }
11
27
  _.set(context, "storage.rules", rulesConfig);
12
28
  const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
13
29
  _.set(context, "storage.rulesDeploy", rulesDeploy);
14
- if (typeof rulesConfig === "object" && rulesConfig !== null) {
30
+ if (!Array.isArray(rulesConfig)) {
15
31
  const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
16
32
  rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
17
33
  _.set(context, "storage.rules", rulesConfig);
18
34
  }
19
- rulesConfig.forEach((ruleConfig) => {
20
- if (ruleConfig.target) {
21
- options.rc.requireTarget(context.projectId, "storage", ruleConfig.target);
35
+ for (const ruleConfig of rulesConfig) {
36
+ const target = ruleConfig.target;
37
+ if (target) {
38
+ options.rc.requireTarget(context.projectId, "storage", target);
22
39
  }
23
- rulesDeploy.addFile(ruleConfig.rules);
24
- });
40
+ if (allStorage || onlyTargets.has(target)) {
41
+ rulesDeploy.addFile(ruleConfig.rules);
42
+ onlyTargets.delete(target);
43
+ }
44
+ }
45
+ if (!allStorage && onlyTargets.size !== 0) {
46
+ throw new error_1.FirebaseError(`Could not find rules for the following storage targets: ${[...onlyTargets].join(", ")}`);
47
+ }
25
48
  await rulesDeploy.compile();
26
49
  }
27
50
  exports.default = default_1;
@@ -301,7 +301,6 @@ async function startAll(options, showUI = true) {
301
301
  const extensionsBackends = await extensionEmulator.getExtensionBackends();
302
302
  const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
303
303
  emulatableBackends.push(...filteredExtensionsBackends);
304
- void (0, track_1.track)("Emulator Run", types_1.Emulators.EXTENSIONS);
305
304
  await startEmulator(extensionEmulator);
306
305
  }
307
306
  if (emulatableBackends.length) {
@@ -646,6 +646,9 @@ class FunctionsEmulator {
646
646
  envs.GCLOUD_PROJECT = this.args.projectId;
647
647
  envs.K_REVISION = "1";
648
648
  envs.PORT = "80";
649
+ if (trigger === null || trigger === void 0 ? void 0 : trigger.timeoutSeconds) {
650
+ envs.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS = trigger.timeoutSeconds.toString();
651
+ }
649
652
  if (trigger) {
650
653
  const target = trigger.entryPoint;
651
654
  envs.FUNCTION_TARGET = target;
@@ -537,8 +537,8 @@ async function processHTTPS(trigger) {
537
537
  rejectEphemeralServer(err);
538
538
  }
539
539
  });
540
- logDebug(`Attempting to listen to port: ${process.env.PORT}`);
541
540
  server = ephemeralServer.listen(process.env.PORT);
541
+ logDebug(`Listening to port: ${process.env.PORT}`);
542
542
  server.on("error", rejectEphemeralServer);
543
543
  });
544
544
  }
@@ -8,20 +8,11 @@ const crypto_1 = require("crypto");
8
8
  const _ = require("lodash");
9
9
  const backend = require("../deploy/functions/backend");
10
10
  const constants_1 = require("./constants");
11
- const proto_1 = require("../gcp/proto");
12
11
  const manifest_1 = require("../extensions/manifest");
13
12
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
14
13
  const postinstall_1 = require("./extensions/postinstall");
15
14
  const services_1 = require("../deploy/functions/services");
16
15
  const prepare_1 = require("../deploy/functions/prepare");
17
- const memoryLookup = {
18
- "128MB": 128,
19
- "256MB": 256,
20
- "512MB": 512,
21
- "1GB": 1024,
22
- "2GB": 2048,
23
- "4GB": 4096,
24
- };
25
16
  class HttpConstants {
26
17
  }
27
18
  exports.HttpConstants = HttpConstants;
@@ -33,7 +24,7 @@ class EmulatedTrigger {
33
24
  this.module = module;
34
25
  }
35
26
  get memoryLimitBytes() {
36
- return memoryLookup[this.definition.availableMemoryMb || "128MB"] * 1024 * 1024;
27
+ return (this.definition.availableMemoryMb || 128) * 1024 * 1024;
37
28
  }
38
29
  get timeoutMs() {
39
30
  return (this.definition.timeoutSeconds || 60) * 1000;
@@ -69,7 +60,11 @@ function emulatedFunctionsFromEndpoints(endpoints) {
69
60
  id: `${endpoint.region}-${endpoint.id}`,
70
61
  codebase: endpoint.codebase,
71
62
  };
72
- (0, proto_1.copyIfPresent)(def, endpoint, "availableMemoryMb", "labels", "timeoutSeconds", "platform", "secretEnvironmentVariables");
63
+ def.availableMemoryMb = endpoint.availableMemoryMb || 256;
64
+ def.labels = endpoint.labels || {};
65
+ def.timeoutSeconds = endpoint.timeoutSeconds || 60;
66
+ def.secretEnvironmentVariables = endpoint.secretEnvironmentVariables || [];
67
+ def.platform = endpoint.platform;
73
68
  if (backend.isHttpsTriggered(endpoint)) {
74
69
  def.httpsTrigger = endpoint.httpsTrigger;
75
70
  }
@@ -18,9 +18,8 @@ const adminSdkConfig_1 = require("../adminSdkConfig");
18
18
  const types_1 = require("./rules/types");
19
19
  const upload_1 = require("./upload");
20
20
  class StoredFile {
21
- constructor(metadata, path) {
21
+ constructor(metadata) {
22
22
  this.metadata = metadata;
23
- this._path = path;
24
23
  }
25
24
  get metadata() {
26
25
  return this._metadata;
@@ -28,12 +27,6 @@ class StoredFile {
28
27
  set metadata(value) {
29
28
  this._metadata = value;
30
29
  }
31
- get path() {
32
- return this._path;
33
- }
34
- set path(value) {
35
- this._path = value;
36
- }
37
30
  }
38
31
  exports.StoredFile = StoredFile;
39
32
  class StorageLayer {
@@ -163,7 +156,7 @@ class StorageLayer {
163
156
  }
164
157
  this._persistence.deleteFile(filePath, true);
165
158
  this._persistence.renameFile(upload.path, filePath);
166
- this._files.set(filePath, new StoredFile(metadata, this._persistence.getDiskPath(filePath)));
159
+ this._files.set(filePath, new StoredFile(metadata));
167
160
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(metadata));
168
161
  return metadata;
169
162
  }
@@ -202,7 +195,7 @@ class StorageLayer {
202
195
  cacheControl: newMetadata.cacheControl,
203
196
  customMetadata: newMetadata.metadata,
204
197
  }, this._cloudFunctions, sourceBytes, incomingMetadata);
205
- const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(destinationFilePath));
198
+ const file = new StoredFile(copiedFileMetadata);
206
199
  this._files.set(destinationFilePath, file);
207
200
  this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
208
201
  return file.metadata;
@@ -306,13 +299,14 @@ class StorageLayer {
306
299
  await fse.writeFile(bucketsFilePath, JSON.stringify(bucketsList, undefined, 2));
307
300
  const blobsDirPath = path.join(storageExportPath, "blobs");
308
301
  await fse.ensureDir(blobsDirPath);
309
- await fse.copy(this.dirPath, blobsDirPath, { recursive: true });
310
302
  const metadataDirPath = path.join(storageExportPath, "metadata");
311
303
  await fse.ensureDir(metadataDirPath);
312
304
  try {
313
305
  for (var _b = __asyncValues(this._files.entries()), _c; _c = await _b.next(), !_c.done;) {
314
- const [p, file] = _c.value;
315
- const metadataExportPath = path.join(metadataDirPath, encodeURIComponent(p)) + ".json";
306
+ const [, file] = _c.value;
307
+ const diskFileName = this._persistence.getDiskFileName(this.path(file.metadata.bucket, file.metadata.name));
308
+ await fse.copy(path.join(this.dirPath, diskFileName), path.join(blobsDirPath, diskFileName));
309
+ const metadataExportPath = path.join(metadataDirPath, encodeURIComponent(diskFileName)) + ".json";
316
310
  await fse.writeFile(metadataExportPath, metadata_1.StoredFileMetadata.toJSON(file.metadata));
317
311
  }
318
312
  }
@@ -333,6 +327,10 @@ class StorageLayer {
333
327
  }
334
328
  const metadataDir = path.join(storageExportPath, "metadata");
335
329
  const blobsDir = path.join(storageExportPath, "blobs");
330
+ if (!(0, fs_1.existsSync)(metadataDir) || !(0, fs_1.existsSync)(blobsDir)) {
331
+ logger_1.logger.warn(`Could not find metadata directory at "${metadataDir}" and/or blobs directory at "${blobsDir}".`);
332
+ return;
333
+ }
336
334
  const metadataList = this.walkDirSync(metadataDir);
337
335
  const dotJson = ".json";
338
336
  for (const f of metadataList) {
@@ -348,15 +346,14 @@ class StorageLayer {
348
346
  logger_1.logger.warn(`Could not find file "${blobPath}" in storage export.`);
349
347
  continue;
350
348
  }
351
- let decodedBlobPath = decodeURIComponent(blobPath);
352
- const decodedBlobPathSep = getPathSep(decodedBlobPath);
353
- if (decodedBlobPathSep !== path.sep) {
354
- decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep);
349
+ let fileName = metadata.name;
350
+ const objectNameSep = getPathSep(fileName);
351
+ if (fileName !== path.sep) {
352
+ fileName = fileName.split(objectNameSep).join(path.sep);
355
353
  }
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);
354
+ const filepath = this.path(metadata.bucket, fileName);
355
+ this._persistence.copyFromExternalPath(blobAbsPath, filepath);
356
+ this._files.set(filepath, new StoredFile(metadata));
360
357
  }
361
358
  }
362
359
  *walkDirSync(dir) {
@@ -374,6 +371,6 @@ class StorageLayer {
374
371
  }
375
372
  exports.StorageLayer = StorageLayer;
376
373
  function getPathSep(decodedPath) {
377
- const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g);
374
+ const firstSepIndex = decodedPath.search(/[\/|\\\\]/g);
378
375
  return decodedPath[firstSepIndex];
379
376
  }
@@ -287,15 +287,15 @@ class CloudStorageObjectMetadata {
287
287
  }
288
288
  exports.CloudStorageObjectMetadata = CloudStorageObjectMetadata;
289
289
  function toSerializedDate(d) {
290
- const day = `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d
291
- .getDate()
290
+ const day = `${d.getUTCFullYear()}-${(d.getUTCMonth() + 1).toString().padStart(2, "0")}-${d
291
+ .getUTCDate()
292
292
  .toString()
293
293
  .padStart(2, "0")}`;
294
- const time = `${d.getHours().toString().padStart(2, "0")}:${d
295
- .getMinutes()
294
+ const time = `${d.getUTCHours().toString().padStart(2, "0")}:${d
295
+ .getUTCMinutes()
296
296
  .toString()
297
- .padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${d
298
- .getMilliseconds()
297
+ .padStart(2, "0")}:${d.getUTCSeconds().toString().padStart(2, "0")}.${d
298
+ .getUTCMilliseconds()
299
299
  .toString()
300
300
  .padStart(3, "0")}`;
301
301
  return `${day}T${time}Z`;
@@ -4,9 +4,12 @@ exports.Persistence = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const rimraf = require("rimraf");
6
6
  const fs = require("fs");
7
+ const fse = require("fs-extra");
7
8
  const path = require("path");
9
+ const uuid = require("uuid");
8
10
  class Persistence {
9
11
  constructor(dirPath) {
12
+ this._diskPathMap = new Map();
10
13
  this.reset(dirPath);
11
14
  }
12
15
  reset(dirPath) {
@@ -14,22 +17,18 @@ class Persistence {
14
17
  (0, fs_1.mkdirSync)(dirPath, {
15
18
  recursive: true,
16
19
  });
20
+ this._diskPathMap = new Map();
17
21
  }
18
22
  get dirPath() {
19
23
  return this._dirPath;
20
24
  }
21
25
  appendBytes(fileName, bytes) {
22
- const filepath = this.getDiskPath(fileName);
23
- let fd;
24
- try {
25
- fs.appendFileSync(filepath, bytes);
26
- return filepath;
27
- }
28
- finally {
29
- if (fd) {
30
- (0, fs_1.closeSync)(fd);
31
- }
26
+ if (!this._diskPathMap.has(fileName)) {
27
+ this._diskPathMap.set(fileName, this.generateNewDiskName());
32
28
  }
29
+ const filepath = this.getDiskPath(fileName);
30
+ fs.appendFileSync(filepath, bytes);
31
+ return filepath;
33
32
  }
34
33
  readBytes(fileName, size, fileOffset) {
35
34
  let fd;
@@ -55,6 +54,7 @@ class Persistence {
55
54
  throw err;
56
55
  }
57
56
  }
57
+ this._diskPathMap.delete(fileName);
58
58
  }
59
59
  deleteAll() {
60
60
  return new Promise((resolve, reject) => {
@@ -63,16 +63,30 @@ class Persistence {
63
63
  reject(err);
64
64
  }
65
65
  else {
66
+ this._diskPathMap = new Map();
66
67
  resolve();
67
68
  }
68
69
  });
69
70
  });
70
71
  }
71
72
  renameFile(oldName, newName) {
72
- (0, fs_1.renameSync)(this.getDiskPath(oldName), this.getDiskPath(newName));
73
+ const oldNameId = this.getDiskFileName(oldName);
74
+ this._diskPathMap.set(newName, oldNameId);
75
+ this._diskPathMap.delete(oldName);
73
76
  }
74
77
  getDiskPath(fileName) {
75
- return path.join(this._dirPath, encodeURIComponent(fileName));
78
+ const shortenedDiskPath = this.getDiskFileName(fileName);
79
+ return path.join(this._dirPath, encodeURIComponent(shortenedDiskPath));
80
+ }
81
+ getDiskFileName(fileName) {
82
+ return this._diskPathMap.get(fileName);
83
+ }
84
+ copyFromExternalPath(sourcePath, newName) {
85
+ this._diskPathMap.set(newName, this.generateNewDiskName());
86
+ fse.copyFileSync(sourcePath, this.getDiskPath(newName));
87
+ }
88
+ generateNewDiskName() {
89
+ return uuid.v4();
76
90
  }
77
91
  }
78
92
  exports.Persistence = Persistence;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printSourceDownloadLink = exports.displayUpdateChangesRequiringConfirmation = exports.displayUpdateChangesNoInput = exports.displayExtInfo = void 0;
4
- const _ = require("lodash");
3
+ exports.printSourceDownloadLink = exports.displayExtInfo = void 0;
5
4
  const clc = require("cli-color");
6
5
  const { marked } = require("marked");
7
6
  const TerminalRenderer = require("marked-terminal");
@@ -12,8 +11,6 @@ const error_1 = require("../error");
12
11
  marked.setOptions({
13
12
  renderer: new TerminalRenderer(),
14
13
  });
15
- const additionColor = clc.green;
16
- const deletionColor = clc.red;
17
14
  function displayExtInfo(extensionName, publisher, spec, published = false) {
18
15
  const lines = [];
19
16
  lines.push(`**Name**: ${spec.displayName}`);
@@ -46,103 +43,6 @@ function displayExtInfo(extensionName, publisher, spec, published = false) {
46
43
  }
47
44
  }
48
45
  exports.displayExtInfo = displayExtInfo;
49
- function displayUpdateChangesNoInput(spec, newSpec) {
50
- var _a, _b, _c, _d;
51
- const lines = [];
52
- if (spec.displayName !== newSpec.displayName) {
53
- lines.push("", "**Name:**", deletionColor(`- ${spec.displayName}`), additionColor(`+ ${newSpec.displayName}`));
54
- }
55
- if (((_a = spec.author) === null || _a === void 0 ? void 0 : _a.authorName) !== ((_b = newSpec.author) === null || _b === void 0 ? void 0 : _b.authorName)) {
56
- lines.push("", "**Author:**", deletionColor(`- ${(_c = spec.author) === null || _c === void 0 ? void 0 : _c.authorName}`), additionColor(`+ ${(_d = spec.author) === null || _d === void 0 ? void 0 : _d.authorName}`));
57
- }
58
- if (spec.description !== newSpec.description) {
59
- lines.push("", "**Description:**", deletionColor(`- ${spec.description}`), additionColor(`+ ${newSpec.description}`));
60
- }
61
- if (spec.sourceUrl !== newSpec.sourceUrl) {
62
- lines.push("", "**Source code:**", deletionColor(`- ${spec.sourceUrl}`), additionColor(`+ ${newSpec.sourceUrl}`));
63
- }
64
- if (spec.billingRequired && !newSpec.billingRequired) {
65
- lines.push("", "**Billing is no longer required for this extension.**");
66
- }
67
- logger_1.logger.info(marked(lines.join("\n")));
68
- return lines;
69
- }
70
- exports.displayUpdateChangesNoInput = displayUpdateChangesNoInput;
71
- async function displayUpdateChangesRequiringConfirmation(args) {
72
- const equals = (a, b) => {
73
- return _.isEqual(a, b);
74
- };
75
- if (args.spec.license !== args.newSpec.license) {
76
- const message = "\n" +
77
- "**License**\n" +
78
- deletionColor(args.spec.license ? `- ${args.spec.license}\n` : "- None\n") +
79
- additionColor(args.newSpec.license ? `+ ${args.newSpec.license}\n` : "+ None\n");
80
- logger_1.logger.info(message);
81
- if (!(await (0, extensionsHelper_1.confirm)({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
82
- throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'License'.");
83
- }
84
- }
85
- const apisDiffDeletions = _.differenceWith(args.spec.apis, _.get(args.newSpec, "apis", []), equals);
86
- const apisDiffAdditions = _.differenceWith(args.newSpec.apis, _.get(args.spec, "apis", []), equals);
87
- if (apisDiffDeletions.length || apisDiffAdditions.length) {
88
- let message = "\n**APIs:**\n";
89
- apisDiffDeletions.forEach((api) => {
90
- message += deletionColor(`- ${api.apiName} (${api.reason})\n`);
91
- });
92
- apisDiffAdditions.forEach((api) => {
93
- message += additionColor(`+ ${api.apiName} (${api.reason})\n`);
94
- });
95
- logger_1.logger.info(message);
96
- if (!(await (0, extensionsHelper_1.confirm)({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
97
- throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'APIs'.");
98
- }
99
- }
100
- const resourcesDiffDeletions = _.differenceWith(args.spec.resources, _.get(args.newSpec, "resources", []), compareResources);
101
- const resourcesDiffAdditions = _.differenceWith(args.newSpec.resources, _.get(args.spec, "resources", []), compareResources);
102
- if (resourcesDiffDeletions.length || resourcesDiffAdditions.length) {
103
- let message = "\n**Resources:**\n";
104
- resourcesDiffDeletions.forEach((resource) => {
105
- message += deletionColor(` - ${getResourceReadableName(resource)}`);
106
- });
107
- resourcesDiffAdditions.forEach((resource) => {
108
- message += additionColor(`+ ${getResourceReadableName(resource)}`);
109
- });
110
- logger_1.logger.info(message);
111
- if (!(await (0, extensionsHelper_1.confirm)({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
112
- throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'Resources'.");
113
- }
114
- }
115
- const rolesDiffDeletions = _.differenceWith(args.spec.roles, _.get(args.newSpec, "roles", []), equals);
116
- const rolesDiffAdditions = _.differenceWith(args.newSpec.roles, _.get(args.spec, "roles", []), equals);
117
- if (rolesDiffDeletions.length || rolesDiffAdditions.length) {
118
- let message = "\n**Permissions:**\n";
119
- rolesDiffDeletions.forEach((role) => {
120
- message += deletionColor(`- ${role.role} (${role.reason})\n`);
121
- });
122
- rolesDiffAdditions.forEach((role) => {
123
- message += additionColor(`+ ${role.role} (${role.reason})\n`);
124
- });
125
- logger_1.logger.info(message);
126
- if (!(await (0, extensionsHelper_1.confirm)({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
127
- throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'Permissions'.");
128
- }
129
- }
130
- if (!args.spec.billingRequired && args.newSpec.billingRequired) {
131
- logger_1.logger.info("Billing is now required for the new version of this extension.");
132
- if (!(await (0, extensionsHelper_1.confirm)({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
133
- throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'BillingRequired'.");
134
- }
135
- }
136
- }
137
- exports.displayUpdateChangesRequiringConfirmation = displayUpdateChangesRequiringConfirmation;
138
- function compareResources(resource1, resource2) {
139
- return resource1.name === resource2.name && resource1.type === resource2.type;
140
- }
141
- function getResourceReadableName(resource) {
142
- return resource.type === "firebaseextensions.v1beta.function"
143
- ? `${resource.name} (Cloud Function): ${resource.description}\n`
144
- : `${resource.name} (${resource.type})\n`;
145
- }
146
46
  function printSourceDownloadLink(sourceDownloadUri) {
147
47
  const sourceDownloadMsg = `Want to review the source code that will be installed? Download it here: ${sourceDownloadUri}`;
148
48
  utils.logBullet(marked(sourceDownloadMsg));
@@ -12,8 +12,8 @@ function functionResourceToEmulatedTriggerDefintion(resource) {
12
12
  platform: "gcfv1",
13
13
  };
14
14
  const properties = resource.properties || {};
15
- proto.renameIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
16
- proto.renameIfPresent(etd, properties, "regions", "location", (str) => [str]);
15
+ proto.convertIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
16
+ proto.convertIfPresent(etd, properties, "regions", "location", (str) => [str]);
17
17
  proto.copyIfPresent(etd, properties, "availableMemoryMb");
18
18
  if (properties.httpsTrigger) {
19
19
  etd.httpsTrigger = properties.httpsTrigger;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.inferUpdateSource = exports.updateFromPublisherSource = exports.updateToVersionFromPublisherSource = exports.updateFromUrlSource = exports.updateFromLocalSource = exports.update = exports.displayChanges = exports.warningUpdateToOtherSource = exports.getExistingSourceOrigin = void 0;
3
+ exports.inferUpdateSource = exports.updateFromPublisherSource = exports.updateToVersionFromPublisherSource = exports.updateFromUrlSource = exports.updateFromLocalSource = exports.update = exports.warningUpdateToOtherSource = exports.getExistingSourceOrigin = void 0;
4
4
  const clc = require("cli-color");
5
5
  const semver = require("semver");
6
6
  const { marked } = require("marked");
@@ -52,12 +52,6 @@ function warningUpdateToOtherSource(sourceOrigin) {
52
52
  logger_1.logger.info(marked(warning));
53
53
  }
54
54
  exports.warningUpdateToOtherSource = warningUpdateToOtherSource;
55
- async function displayChanges(args) {
56
- utils.logLabeledBullet("extensions", "This update contains the following changes:");
57
- (0, displayExtensionInfo_1.displayUpdateChangesNoInput)(args.spec, args.newSpec);
58
- await (0, displayExtensionInfo_1.displayUpdateChangesRequiringConfirmation)(args);
59
- }
60
- exports.displayChanges = displayChanges;
61
55
  async function update(updateOptions) {
62
56
  const { projectId, instanceId, source, extRef, params, canEmitEvents, allowedEventTypes, eventarcChannel, } = updateOptions;
63
57
  if (extRef) {