firebase-tools 10.6.0 → 10.7.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 (97) 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/hosting-channel-deploy.js +2 -2
  10. package/lib/deploy/database/deploy.js +4 -0
  11. package/lib/deploy/database/index.js +1 -0
  12. package/lib/deploy/extensions/deploy.js +4 -4
  13. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  14. package/lib/deploy/extensions/planner.js +36 -9
  15. package/lib/deploy/extensions/prepare.js +1 -1
  16. package/lib/deploy/extensions/secrets.js +2 -2
  17. package/lib/deploy/extensions/tasks.js +60 -21
  18. package/lib/deploy/functions/backend.js +37 -2
  19. package/lib/deploy/functions/build.js +173 -0
  20. package/lib/deploy/functions/checkIam.js +11 -14
  21. package/lib/deploy/functions/containerCleaner.js +8 -7
  22. package/lib/deploy/functions/deploy.js +49 -28
  23. package/lib/deploy/functions/ensure.js +4 -4
  24. package/lib/deploy/functions/functionsDeployHelper.js +99 -24
  25. package/lib/deploy/functions/prepare.js +129 -71
  26. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  27. package/lib/deploy/functions/pricing.js +6 -3
  28. package/lib/deploy/functions/prompts.js +1 -7
  29. package/lib/deploy/functions/release/executor.js +1 -1
  30. package/lib/deploy/functions/release/fabricator.js +69 -25
  31. package/lib/deploy/functions/release/index.js +20 -6
  32. package/lib/deploy/functions/release/planner.js +18 -10
  33. package/lib/deploy/functions/release/reporter.js +14 -11
  34. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  35. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +50 -3
  36. package/lib/deploy/functions/runtimes/golang/index.js +3 -0
  37. package/lib/deploy/functions/runtimes/node/index.js +7 -0
  38. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  39. package/lib/deploy/functions/runtimes/node/parseTriggers.js +132 -6
  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/validate.js +33 -7
  44. package/lib/deploy/hosting/args.js +2 -0
  45. package/lib/deploy/hosting/convertConfig.js +39 -8
  46. package/lib/deploy/hosting/deploy.js +3 -3
  47. package/lib/deploy/hosting/prepare.js +2 -2
  48. package/lib/deploy/hosting/release.js +6 -2
  49. package/lib/deploy/index.js +82 -93
  50. package/lib/deploy/remoteconfig/deploy.js +4 -0
  51. package/lib/deploy/remoteconfig/index.js +3 -1
  52. package/lib/emulator/auth/cloudFunctions.js +6 -2
  53. package/lib/emulator/auth/operations.js +5 -1
  54. package/lib/emulator/auth/server.js +8 -1
  55. package/lib/emulator/auth/state.js +27 -24
  56. package/lib/emulator/auth/utils.js +3 -25
  57. package/lib/emulator/controller.js +17 -14
  58. package/lib/emulator/databaseEmulator.js +36 -3
  59. package/lib/emulator/downloadableEmulators.js +39 -23
  60. package/lib/emulator/extensions/validation.js +2 -2
  61. package/lib/emulator/extensionsEmulator.js +85 -21
  62. package/lib/emulator/functionsEmulator.js +89 -15
  63. package/lib/emulator/functionsEmulatorRuntime.js +1 -1
  64. package/lib/emulator/functionsEmulatorShared.js +25 -2
  65. package/lib/emulator/functionsEmulatorShell.js +2 -3
  66. package/lib/emulator/functionsEmulatorUtils.js +5 -1
  67. package/lib/emulator/pubsubEmulator.js +13 -9
  68. package/lib/emulator/registry.js +34 -12
  69. package/lib/emulator/storage/apis/firebase.js +33 -6
  70. package/lib/emulator/storage/apis/gcloud.js +6 -3
  71. package/lib/emulator/storage/files.js +9 -1
  72. package/lib/ensureApiEnabled.js +8 -4
  73. package/lib/extensions/changelog.js +1 -1
  74. package/lib/extensions/emulator/optionsHelper.js +4 -3
  75. package/lib/extensions/emulator/specHelper.js +7 -1
  76. package/lib/extensions/extensionsHelper.js +30 -24
  77. package/lib/extensions/manifest.js +27 -7
  78. package/lib/extensions/paramHelper.js +7 -5
  79. package/lib/extensions/provisioningHelper.js +2 -2
  80. package/lib/extensions/warnings.js +11 -4
  81. package/lib/functions/events/index.js +7 -0
  82. package/lib/functions/events/v1.js +6 -0
  83. package/lib/functions/projectConfig.js +32 -6
  84. package/lib/functionsShellCommandAction.js +1 -1
  85. package/lib/gcp/cloudfunctions.js +38 -5
  86. package/lib/gcp/cloudfunctionsv2.js +46 -7
  87. package/lib/gcp/identityPlatform.js +44 -0
  88. package/lib/gcp/secretManager.js +1 -1
  89. package/lib/metaprogramming.js +2 -0
  90. package/lib/previews.js +1 -1
  91. package/lib/serve/functions.js +16 -19
  92. package/lib/serve/hosting.js +25 -12
  93. package/lib/serve/index.js +6 -0
  94. package/lib/track.js +15 -21
  95. package/npm-shrinkwrap.json +256 -527
  96. package/package.json +6 -3
  97. package/schema/firebase-config.json +6 -0
@@ -13,7 +13,7 @@ const url_1 = require("url");
13
13
  const events_1 = require("events");
14
14
  const api = require("../api");
15
15
  const logger_1 = require("../logger");
16
- const track = require("../track");
16
+ const track_1 = require("../track");
17
17
  const constants_1 = require("./constants");
18
18
  const types_1 = require("./types");
19
19
  const chokidar = require("chokidar");
@@ -34,6 +34,7 @@ const secretManager_1 = require("../gcp/secretManager");
34
34
  const runtimes = require("../deploy/functions/runtimes");
35
35
  const backend = require("../deploy/functions/backend");
36
36
  const functionsEnv = require("../functions/env");
37
+ const v1_1 = require("../functions/events/v1");
37
38
  const EVENT_INVOKE = "functions:invoke";
38
39
  const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
39
40
  class FunctionsEmulator {
@@ -54,6 +55,16 @@ class FunctionsEmulator {
54
55
  : types_1.FunctionsExecutionMode.AUTO;
55
56
  this.workerPool = new functionsRuntimeWorker_1.RuntimeWorkerPool(mode);
56
57
  this.workQueue = new workQueue_1.WorkQueue(mode);
58
+ this.blockingFunctionsConfig = {
59
+ triggers: {
60
+ beforeCreate: {
61
+ functionUri: "",
62
+ },
63
+ beforeSignIn: {
64
+ functionUri: "",
65
+ },
66
+ },
67
+ };
57
68
  }
58
69
  static getHttpFunctionUrl(host, port, projectId, name, region) {
59
70
  return `http://${host}:${port}/${projectId}/${region}/${name}`;
@@ -166,7 +177,9 @@ class FunctionsEmulator {
166
177
  });
167
178
  return hub;
168
179
  }
169
- async invokeTrigger(backend, trigger, proto, runtimeOpts) {
180
+ async invokeTrigger(trigger, proto, runtimeOpts) {
181
+ const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger));
182
+ const backend = record.backend;
170
183
  const bundleTemplate = this.getBaseBundle();
171
184
  const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { proto });
172
185
  if (this.args.debugPort) {
@@ -210,7 +223,6 @@ class FunctionsEmulator {
210
223
  return Promise.resolve();
211
224
  }
212
225
  async connect() {
213
- const loadTriggerPromises = [];
214
226
  for (const backend of this.args.emulatableBackends) {
215
227
  this.logger.logLabeled("BULLET", "functions", `Watching "${backend.functionsDir}" for Cloud Functions...`);
216
228
  const watcher = chokidar.watch(backend.functionsDir, {
@@ -226,9 +238,9 @@ class FunctionsEmulator {
226
238
  this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`);
227
239
  return debouncedLoadTriggers();
228
240
  });
229
- loadTriggerPromises.push(this.loadTriggers(backend, true));
241
+ await this.loadTriggers(backend, true);
230
242
  }
231
- await Promise.all(loadTriggerPromises);
243
+ await this.performPostLoadOperations();
232
244
  return;
233
245
  }
234
246
  async stop() {
@@ -271,6 +283,10 @@ class FunctionsEmulator {
271
283
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
272
284
  const discoveredBackend = await runtimeDelegate.discoverSpec(runtimeConfig, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env));
273
285
  const endpoints = backend.allEndpoints(discoveredBackend);
286
+ (0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
287
+ for (const e of endpoints) {
288
+ e.codebase = emulatableBackend.codebase;
289
+ }
274
290
  triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
275
291
  }
276
292
  const toSetup = triggerDefinitions.filter((definition) => {
@@ -327,6 +343,11 @@ class FunctionsEmulator {
327
343
  break;
328
344
  }
329
345
  }
346
+ else if (definition.blockingTrigger) {
347
+ const { host, port } = this.getInfo();
348
+ url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
349
+ added = this.addBlockingTrigger(url, definition.blockingTrigger);
350
+ }
330
351
  else {
331
352
  this.logger.log("WARN", `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.`);
332
353
  }
@@ -350,6 +371,34 @@ class FunctionsEmulator {
350
371
  this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
351
372
  }
352
373
  }
374
+ async performPostLoadOperations() {
375
+ var _a, _b, _c, _d;
376
+ if (((_b = (_a = this.blockingFunctionsConfig.triggers) === null || _a === void 0 ? void 0 : _a.beforeCreate) === null || _b === void 0 ? void 0 : _b.functionUri) === "" &&
377
+ ((_d = (_c = this.blockingFunctionsConfig.triggers) === null || _c === void 0 ? void 0 : _c.beforeSignIn) === null || _d === void 0 ? void 0 : _d.functionUri) === "") {
378
+ return;
379
+ }
380
+ const authEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH);
381
+ if (!authEmu) {
382
+ return;
383
+ }
384
+ const path = `/identitytoolkit.googleapis.com/v2/projects/${this.getProjectId()}/config?updateMask=blockingFunctions`;
385
+ try {
386
+ await api.request("PATCH", path, {
387
+ origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(authEmu.getInfo())}`,
388
+ headers: {
389
+ Authorization: "Bearer owner",
390
+ },
391
+ data: {
392
+ blockingFunctions: this.blockingFunctionsConfig,
393
+ },
394
+ json: true,
395
+ });
396
+ }
397
+ catch (err) {
398
+ this.logger.log("WARN", "Error updating blocking functions config to the auth emulator: " + err);
399
+ throw err;
400
+ }
401
+ }
353
402
  addRealtimeDatabaseTrigger(projectId, key, eventTrigger) {
354
403
  const databaseEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
355
404
  if (!databaseEmu) {
@@ -416,11 +465,10 @@ class FunctionsEmulator {
416
465
  });
417
466
  }
418
467
  async addPubsubTrigger(triggerName, key, eventTrigger, signatureType, schedule) {
419
- const pubsubPort = registry_1.EmulatorRegistry.getPort(types_1.Emulators.PUBSUB);
420
- if (!pubsubPort) {
468
+ const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
469
+ if (!pubsubEmulator) {
421
470
  return false;
422
471
  }
423
- const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
424
472
  logger_1.logger.debug(`addPubsubTrigger`, JSON.stringify({ eventTrigger }));
425
473
  const resource = eventTrigger.resource;
426
474
  let topic;
@@ -458,6 +506,31 @@ class FunctionsEmulator {
458
506
  this.multicastTriggers[eventTriggerId] = triggers;
459
507
  return true;
460
508
  }
509
+ addBlockingTrigger(url, blockingTrigger) {
510
+ logger_1.logger.debug(`addBlockingTrigger`, JSON.stringify({ blockingTrigger }));
511
+ const eventType = blockingTrigger.eventType;
512
+ if (v1_1.AUTH_BLOCKING_EVENTS.includes(eventType)) {
513
+ if (blockingTrigger.eventType === v1_1.BEFORE_CREATE_EVENT) {
514
+ this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeCreate: {
515
+ functionUri: url,
516
+ } });
517
+ }
518
+ else {
519
+ this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeSignIn: {
520
+ functionUri: url,
521
+ } });
522
+ }
523
+ this.blockingFunctionsConfig.forwardInboundCredentials = {
524
+ accessToken: blockingTrigger.options.accessToken,
525
+ idToken: blockingTrigger.options.idToken,
526
+ refreshToken: blockingTrigger.options.refreshToken,
527
+ };
528
+ }
529
+ else {
530
+ return false;
531
+ }
532
+ return true;
533
+ }
461
534
  getProjectId() {
462
535
  return this.args.projectId;
463
536
  }
@@ -509,6 +582,7 @@ class FunctionsEmulator {
509
582
  };
510
583
  }
511
584
  setTriggersForTesting(triggers, backend) {
585
+ this.triggers = {};
512
586
  triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
513
587
  }
514
588
  getBaseBundle() {
@@ -777,11 +851,11 @@ class FunctionsEmulator {
777
851
  }
778
852
  async reloadTriggers() {
779
853
  this.triggerGeneration++;
780
- const loadTriggerPromises = [];
781
854
  for (const backend of this.args.emulatableBackends) {
782
- loadTriggerPromises.push(this.loadTriggers(backend));
855
+ await this.loadTriggers(backend);
783
856
  }
784
- return Promise.all(loadTriggerPromises);
857
+ await this.performPostLoadOperations();
858
+ return;
785
859
  }
786
860
  async handleBackgroundTrigger(projectId, triggerKey, proto) {
787
861
  const record = this.getTriggerRecordByKey(triggerKey);
@@ -790,7 +864,7 @@ class FunctionsEmulator {
790
864
  }
791
865
  const trigger = record.def;
792
866
  const service = (0, functionsEmulatorShared_1.getFunctionService)(trigger);
793
- const worker = await this.invokeTrigger(record.backend, trigger, proto);
867
+ const worker = await this.invokeTrigger(trigger, proto);
794
868
  return new Promise((resolve, reject) => {
795
869
  if (projectId !== this.args.projectId) {
796
870
  if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
@@ -809,7 +883,7 @@ class FunctionsEmulator {
809
883
  reject({ code: 500, body: el.text });
810
884
  }
811
885
  });
812
- void track(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
886
+ void (0, track_1.track)(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
813
887
  worker.waitForDone().then(() => {
814
888
  resolve({ status: "acknowledged" });
815
889
  });
@@ -877,14 +951,14 @@ class FunctionsEmulator {
877
951
  req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
878
952
  }
879
953
  }
880
- const worker = await this.invokeTrigger(record.backend, trigger);
954
+ const worker = await this.invokeTrigger(trigger);
881
955
  worker.onLogs((el) => {
882
956
  if (el.level === "FATAL") {
883
957
  res.status(500).send(el.text);
884
958
  }
885
959
  });
886
960
  await worker.waitForSocketReady();
887
- void track(EVENT_INVOKE, "https");
961
+ void (0, track_1.track)(EVENT_INVOKE, "https");
888
962
  this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
889
963
  if (!worker.lastArgs) {
890
964
  throw new error_1.FirebaseError("Cannot execute on a worker with no arguments");
@@ -213,7 +213,7 @@ function initializeNetworkFiltering(frb) {
213
213
  })
214
214
  .filter((v) => v);
215
215
  const href = (hrefs.length && hrefs[0]) || "";
216
- if (href && !history[href] && !href.startsWith("http://localhost")) {
216
+ if (href && !history[href] && !(0, functionsEmulatorUtils_1.isLocalHost)(href)) {
217
217
  history[href] = true;
218
218
  if (href.indexOf("googleapis.com") !== -1) {
219
219
  new types_1.EmulatorLog("SYSTEM", "googleapis-network-access", "", {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toBackendInfo = exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
3
+ exports.toBackendInfo = exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.prepareEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
4
4
  const _ = require("lodash");
5
5
  const os = require("os");
6
6
  const path = require("path");
@@ -11,6 +11,8 @@ const proto_1 = require("../gcp/proto");
11
11
  const manifest_1 = require("../extensions/manifest");
12
12
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
13
13
  const postinstall_1 = require("./extensions/postinstall");
14
+ const services_1 = require("../deploy/functions/services");
15
+ const prepare_1 = require("../deploy/functions/prepare");
14
16
  const memoryLookup = {
15
17
  "128MB": 128,
16
18
  "256MB": 256,
@@ -44,6 +46,14 @@ class EmulatedTrigger {
44
46
  }
45
47
  }
46
48
  exports.EmulatedTrigger = EmulatedTrigger;
49
+ function prepareEndpoints(endpoints) {
50
+ const bkend = backend.of(...endpoints);
51
+ for (const ep of endpoints) {
52
+ (0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, bkend);
53
+ }
54
+ (0, prepare_1.inferBlockingDetails)(bkend);
55
+ }
56
+ exports.prepareEndpoints = prepareEndpoints;
47
57
  function emulatedFunctionsFromEndpoints(endpoints) {
48
58
  const regionDefinitions = [];
49
59
  for (const endpoint of endpoints) {
@@ -56,6 +66,7 @@ function emulatedFunctionsFromEndpoints(endpoints) {
56
66
  region: endpoint.region,
57
67
  name: endpoint.id,
58
68
  id: `${endpoint.region}-${endpoint.id}`,
69
+ codebase: endpoint.codebase,
59
70
  };
60
71
  (0, proto_1.copyIfPresent)(def, endpoint, "availableMemoryMb", "labels", "timeoutSeconds", "platform", "secretEnvironmentVariables");
61
72
  if (backend.isHttpsTriggered(endpoint)) {
@@ -89,6 +100,15 @@ function emulatedFunctionsFromEndpoints(endpoints) {
89
100
  def.eventTrigger = { eventType: "pubsub", resource: "" };
90
101
  def.schedule = endpoint.scheduleTrigger;
91
102
  }
103
+ else if (backend.isBlockingTriggered(endpoint)) {
104
+ def.blockingTrigger = {
105
+ eventType: endpoint.blockingTrigger.eventType,
106
+ options: endpoint.blockingTrigger.options || {},
107
+ };
108
+ }
109
+ else if (backend.isTaskQueueTriggered(endpoint)) {
110
+ def.httpsTrigger = {};
111
+ }
92
112
  else {
93
113
  }
94
114
  regionDefinitions.push(def);
@@ -136,6 +156,9 @@ function getFunctionService(def) {
136
156
  if (def.eventTrigger) {
137
157
  return (_a = def.eventTrigger.service) !== null && _a !== void 0 ? _a : getServiceFromEventType(def.eventTrigger.eventType);
138
158
  }
159
+ if (def.blockingTrigger) {
160
+ return def.blockingTrigger.eventType;
161
+ }
139
162
  return "unknown";
140
163
  }
141
164
  exports.getFunctionService = getFunctionService;
@@ -260,7 +283,7 @@ function toBackendInfo(e, cf3Triggers) {
260
283
  extension: e.extension,
261
284
  extensionVersion: extensionVersion,
262
285
  extensionSpec: extensionSpec,
263
- functionTriggers: (_b = e.predefinedTriggers) !== null && _b !== void 0 ? _b : cf3Triggers,
286
+ functionTriggers: (_b = e.predefinedTriggers) !== null && _b !== void 0 ? _b : cf3Triggers.filter((t) => t.codebase === e.codebase),
264
287
  }));
265
288
  }
266
289
  exports.toBackendInfo = toBackendInfo;
@@ -7,9 +7,8 @@ const utils = require("../utils");
7
7
  const logger_1 = require("../logger");
8
8
  const error_1 = require("../error");
9
9
  class FunctionsEmulatorShell {
10
- constructor(emu, backend) {
10
+ constructor(emu) {
11
11
  this.emu = emu;
12
- this.backend = backend;
13
12
  this.urls = {};
14
13
  this.triggers = emu.getTriggerDefinitions();
15
14
  this.emulatedFunctions = this.triggers.map((t) => t.id);
@@ -42,7 +41,7 @@ class FunctionsEmulatorShell {
42
41
  auth: opts.auth,
43
42
  data,
44
43
  };
45
- this.emu.invokeTrigger(this.backend, trigger, proto);
44
+ this.emu.invokeTrigger(trigger, proto);
46
45
  }
47
46
  getTrigger(name) {
48
47
  const result = this.triggers.find((trigger) => {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compareVersionStrings = exports.parseVersionString = exports.parseRuntimeVersion = exports.removePathSegments = exports.trimSlashes = exports.isValidWildcardMatch = exports.extractParamsFromPath = void 0;
3
+ exports.isLocalHost = exports.compareVersionStrings = exports.parseVersionString = exports.parseRuntimeVersion = exports.removePathSegments = exports.trimSlashes = exports.isValidWildcardMatch = exports.extractParamsFromPath = void 0;
4
4
  const wildcardRegex = new RegExp("{[^/{}]*}");
5
5
  const wildcardKeyRegex = new RegExp("^{(.+)}$");
6
6
  function extractParamsFromPath(wildcardPath, snapshotPath) {
@@ -85,3 +85,7 @@ function compareVersionStrings(a, b) {
85
85
  return 0;
86
86
  }
87
87
  exports.compareVersionStrings = compareVersionStrings;
88
+ function isLocalHost(href) {
89
+ return !!href.match(/^(http(s)?:\/\/)?(localhost|127.0.0.1|\[::1])/);
90
+ }
91
+ exports.isLocalHost = isLocalHost;
@@ -44,14 +44,7 @@ class PubsubEmulator {
44
44
  getName() {
45
45
  return types_1.Emulators.PUBSUB;
46
46
  }
47
- async addTrigger(topicName, triggerKey, signatureType) {
48
- this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
49
- const triggers = this.triggersForTopic.get(topicName) || [];
50
- if (triggers.some((t) => t.triggerKey === triggerKey) &&
51
- this.subscriptionForTopic.has(topicName)) {
52
- this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
53
- return;
54
- }
47
+ async maybeCreateTopicAndSub(topicName) {
55
48
  const topic = this.pubsub.topic(topicName);
56
49
  try {
57
50
  this.logger.logLabeled("DEBUG", "pubsub", `Creating topic: ${topicName}`);
@@ -74,7 +67,7 @@ class PubsubEmulator {
74
67
  catch (e) {
75
68
  if (e && e.code === 6) {
76
69
  this.logger.logLabeled("DEBUG", "pubsub", `Sub for ${topicName} exists`);
77
- sub = topic.subscription(`emulator-sub-${topicName}`);
70
+ sub = topic.subscription(subName);
78
71
  }
79
72
  else {
80
73
  throw new error_1.FirebaseError(`Could not create sub ${subName}`, { original: e });
@@ -83,6 +76,17 @@ class PubsubEmulator {
83
76
  sub.on("message", (message) => {
84
77
  this.onMessage(topicName, message);
85
78
  });
79
+ return sub;
80
+ }
81
+ async addTrigger(topicName, triggerKey, signatureType) {
82
+ this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
83
+ const sub = await this.maybeCreateTopicAndSub(topicName);
84
+ const triggers = this.triggersForTopic.get(topicName) || [];
85
+ if (triggers.some((t) => t.triggerKey === triggerKey) &&
86
+ this.subscriptionForTopic.has(topicName)) {
87
+ this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
88
+ return;
89
+ }
86
90
  triggers.push({ triggerKey, signatureType });
87
91
  this.triggersForTopic.set(topicName, triggers);
88
92
  this.subscriptionForTopic.set(topicName, sub);
@@ -14,11 +14,10 @@ class EmulatorRegistry {
14
14
  }
15
15
  this.set(instance.getName(), instance);
16
16
  await instance.start();
17
- const info = instance.getInfo();
18
- await portUtils.waitForPortClosed(info.port, info.host);
19
- }
20
- static registerExtensionsEmulator() {
21
- this.extensionsEmulatorRegistered = true;
17
+ if (instance.getName() !== types_1.Emulators.EXTENSIONS) {
18
+ const info = instance.getInfo();
19
+ await portUtils.waitForPortClosed(info.port, info.host);
20
+ }
22
21
  }
23
22
  static async stop(name) {
24
23
  emulatorLogger_1.EmulatorLogger.forEmulator(name).logLabeled("BULLET", name, `Stopping ${constants_1.Constants.description(name)}`);
@@ -57,7 +56,7 @@ class EmulatorRegistry {
57
56
  }
58
57
  static isRunning(emulator) {
59
58
  if (emulator === types_1.Emulators.EXTENSIONS) {
60
- return this.extensionsEmulatorRegistered && this.isRunning(types_1.Emulators.FUNCTIONS);
59
+ return this.INSTANCES.get(emulator) !== undefined && this.isRunning(types_1.Emulators.FUNCTIONS);
61
60
  }
62
61
  const instance = this.INSTANCES.get(emulator);
63
62
  return instance !== undefined;
@@ -89,12 +88,36 @@ class EmulatorRegistry {
89
88
  return `${host}:${port}`;
90
89
  }
91
90
  }
92
- static getPort(emulator) {
93
- const instance = this.INSTANCES.get(emulator);
94
- if (!instance) {
95
- return undefined;
91
+ static url(emulator, req) {
92
+ const url = new URL("http://unknown/");
93
+ if (req) {
94
+ url.protocol = req.protocol;
95
+ const host = req.headers.host;
96
+ if (host) {
97
+ url.host = host;
98
+ return url;
99
+ }
100
+ }
101
+ const info = EmulatorRegistry.getInfo(emulator);
102
+ if (info) {
103
+ if (info.host === "0.0.0.0") {
104
+ url.hostname = "127.0.0.1";
105
+ }
106
+ else if (info.host === "::") {
107
+ url.hostname = "[::1]";
108
+ }
109
+ else if (info.host.includes(":")) {
110
+ url.hostname = `[${info.host}]`;
111
+ }
112
+ else {
113
+ url.hostname = info.host;
114
+ }
115
+ url.port = info.port.toString();
116
+ }
117
+ else {
118
+ console.warn(`Cannot determine host and port of ${emulator}`);
96
119
  }
97
- return instance.getInfo().port;
120
+ return url;
98
121
  }
99
122
  static set(emulator, instance) {
100
123
  this.INSTANCES.set(emulator, instance);
@@ -104,5 +127,4 @@ class EmulatorRegistry {
104
127
  }
105
128
  }
106
129
  exports.EmulatorRegistry = EmulatorRegistry;
107
- EmulatorRegistry.extensionsEmulatorRegistered = false;
108
130
  EmulatorRegistry.INSTANCES = new Map();
@@ -118,7 +118,7 @@ function createFirebaseEndpoints(emulator) {
118
118
  return res.json(new metadata_1.OutgoingFirebaseMetadata(metadata));
119
119
  });
120
120
  firebaseStorageAPI.get("/b/:bucketId/o", async (req, res) => {
121
- var _a, _b, _c, _d, _e;
121
+ var _a, _b, _c, _d;
122
122
  const maxResults = (_a = req.query.maxResults) === null || _a === void 0 ? void 0 : _a.toString();
123
123
  let listResponse;
124
124
  try {
@@ -144,10 +144,12 @@ function createFirebaseEndpoints(emulator) {
144
144
  }
145
145
  return res.status(200).json({
146
146
  nextPageToken: listResponse.nextPageToken,
147
- prefixes: (_c = listResponse.prefixes) !== null && _c !== void 0 ? _c : [],
148
- items: (_e = (_d = listResponse.items) === null || _d === void 0 ? void 0 : _d.map((item) => {
147
+ prefixes: ((_c = listResponse.prefixes) !== null && _c !== void 0 ? _c : []).filter(isValidPrefix),
148
+ items: ((_d = listResponse.items) !== null && _d !== void 0 ? _d : [])
149
+ .filter((item) => isValidNonEncodedPathString(item.name))
150
+ .map((item) => {
149
151
  return { name: item.name, bucket: item.bucket };
150
- })) !== null && _e !== void 0 ? _e : [],
152
+ }),
151
153
  });
152
154
  });
153
155
  const handleUpload = async (req, res) => {
@@ -219,9 +221,13 @@ function createFirebaseEndpoints(emulator) {
219
221
  res.header("x-goog-upload-chunk-granularity", "10000");
220
222
  res.header("x-goog-upload-control-url", "");
221
223
  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
224
  res.header("x-gupload-uploadid", upload.id);
225
+ const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
226
+ uploadUrl.pathname = `/v0/b/${bucketId}/o`;
227
+ uploadUrl.searchParams.set("name", objectId);
228
+ uploadUrl.searchParams.set("upload_id", upload.id);
229
+ uploadUrl.searchParams.set("upload_protocol", "resumable");
230
+ res.header("x-goog-upload-url", uploadUrl.toString());
225
231
  return res.sendStatus(200);
226
232
  }
227
233
  if (!req.query.upload_id) {
@@ -306,6 +312,7 @@ function createFirebaseEndpoints(emulator) {
306
312
  }
307
313
  throw err;
308
314
  }
315
+ res.header("x-goog-upload-status", "final");
309
316
  storedMetadata.addDownloadToken(false);
310
317
  return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(storedMetadata));
311
318
  }
@@ -462,3 +469,23 @@ function setObjectHeaders(res, metadata, headerOverride = { "Content-Encoding":
462
469
  res.setHeader("Content-Language", metadata.contentLanguage);
463
470
  }
464
471
  }
472
+ function isValidPrefix(prefix) {
473
+ return isValidNonEncodedPathString(removeAtMostOneTrailingSlash(prefix));
474
+ }
475
+ function isValidNonEncodedPathString(path) {
476
+ if (path.startsWith("/")) {
477
+ path = path.substring(1);
478
+ }
479
+ if (!path) {
480
+ return false;
481
+ }
482
+ for (const pathSegment of path.split("/")) {
483
+ if (!pathSegment) {
484
+ return false;
485
+ }
486
+ }
487
+ return true;
488
+ }
489
+ function removeAtMostOneTrailingSlash(path) {
490
+ return path.replace(/\/$/, "");
491
+ }
@@ -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;
@@ -348,7 +348,11 @@ class StorageLayer {
348
348
  logger_1.logger.warn(`Could not find file "${blobPath}" in storage export.`);
349
349
  continue;
350
350
  }
351
- const decodedBlobPath = decodeURIComponent(blobPath);
351
+ let decodedBlobPath = decodeURIComponent(blobPath);
352
+ const decodedBlobPathSep = getPathSep(decodedBlobPath);
353
+ if (decodedBlobPathSep !== path.sep) {
354
+ decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep);
355
+ }
352
356
  const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath);
353
357
  const file = new StoredFile(metadata, blobDiskPath);
354
358
  this._files.set(decodedBlobPath, file);
@@ -369,3 +373,7 @@ class StorageLayer {
369
373
  }
370
374
  }
371
375
  exports.StorageLayer = StorageLayer;
376
+ function getPathSep(decodedPath) {
377
+ const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g);
378
+ return decodedPath[firstSepIndex];
379
+ }
@@ -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) {
@@ -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}"`;
@@ -9,6 +9,7 @@ const localHelper = require("../localHelper");
9
9
  const triggerHelper = require("./triggerHelper");
10
10
  const extensionsApi_1 = require("../extensionsApi");
11
11
  const extensionsHelper = require("../extensionsHelper");
12
+ const planner = require("../../deploy/extensions/planner");
12
13
  const config_1 = require("../../config");
13
14
  const error_1 = require("../../error");
14
15
  const emulatorLogger_1 = require("../../emulator/emulatorLogger");
@@ -35,13 +36,13 @@ async function buildOptions(options) {
35
36
  return options;
36
37
  }
37
38
  exports.buildOptions = buildOptions;
38
- async function getExtensionFunctionInfo(extensionDir, instanceId, paramValues) {
39
- const spec = await specHelper.readExtensionYaml(extensionDir);
39
+ async function getExtensionFunctionInfo(instance, paramValues) {
40
+ const spec = await planner.getExtensionSpec(instance);
40
41
  const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues);
41
42
  const extensionTriggers = functionResources
42
43
  .map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r))
43
44
  .map((trigger) => {
44
- trigger.name = `ext-${instanceId}-${trigger.name}`;
45
+ trigger.name = `ext-${instance.instanceId}-${trigger.name}`;
45
46
  return trigger;
46
47
  });
47
48
  const nodeMajorVersion = specHelper.getNodeVersion(functionResources);