firebase-tools 10.5.0 → 10.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/lib/command.js +4 -4
  2. package/lib/commands/deploy.js +1 -1
  3. package/lib/commands/emulators-start.js +7 -2
  4. package/lib/commands/ext-configure.js +15 -5
  5. package/lib/commands/ext-export.js +6 -5
  6. package/lib/commands/ext-install.js +28 -44
  7. package/lib/commands/ext-update.js +9 -1
  8. package/lib/commands/functions-delete.js +7 -3
  9. package/lib/commands/functions-secrets-destroy.js +23 -3
  10. package/lib/commands/functions-secrets-prune.js +15 -12
  11. package/lib/commands/functions-secrets-set.js +51 -4
  12. package/lib/commands/hosting-channel-deploy.js +2 -2
  13. package/lib/deploy/database/deploy.js +4 -0
  14. package/lib/deploy/database/index.js +1 -0
  15. package/lib/deploy/extensions/deploy.js +4 -4
  16. package/lib/deploy/extensions/deploymentSummary.js +8 -5
  17. package/lib/deploy/extensions/planner.js +36 -9
  18. package/lib/deploy/extensions/prepare.js +1 -1
  19. package/lib/deploy/extensions/secrets.js +2 -2
  20. package/lib/deploy/extensions/tasks.js +60 -21
  21. package/lib/deploy/functions/backend.js +37 -6
  22. package/lib/deploy/functions/build.js +162 -0
  23. package/lib/deploy/functions/checkIam.js +10 -6
  24. package/lib/deploy/functions/deploy.js +49 -28
  25. package/lib/deploy/functions/ensure.js +4 -4
  26. package/lib/deploy/functions/functionsDeployHelper.js +99 -24
  27. package/lib/deploy/functions/prepare.js +130 -62
  28. package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
  29. package/lib/deploy/functions/pricing.js +6 -3
  30. package/lib/deploy/functions/prompts.js +1 -7
  31. package/lib/deploy/functions/release/fabricator.js +70 -28
  32. package/lib/deploy/functions/release/index.js +41 -6
  33. package/lib/deploy/functions/release/planner.js +19 -12
  34. package/lib/deploy/functions/release/reporter.js +14 -11
  35. package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
  36. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +61 -13
  37. package/lib/deploy/functions/runtimes/node/index.js +1 -1
  38. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
  39. package/lib/deploy/functions/runtimes/node/parseTriggers.js +29 -24
  40. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  41. package/lib/deploy/functions/services/auth.js +95 -0
  42. package/lib/deploy/functions/services/index.js +41 -21
  43. package/lib/deploy/functions/services/storage.js +1 -6
  44. package/lib/deploy/functions/validate.js +32 -6
  45. package/lib/deploy/hosting/args.js +2 -0
  46. package/lib/deploy/hosting/convertConfig.js +39 -8
  47. package/lib/deploy/hosting/deploy.js +3 -3
  48. package/lib/deploy/hosting/prepare.js +2 -2
  49. package/lib/deploy/hosting/release.js +6 -2
  50. package/lib/deploy/index.js +82 -93
  51. package/lib/deploy/remoteconfig/deploy.js +4 -0
  52. package/lib/deploy/remoteconfig/index.js +3 -1
  53. package/lib/emulator/auth/operations.js +5 -0
  54. package/lib/emulator/auth/utils.js +3 -25
  55. package/lib/emulator/controller.js +17 -14
  56. package/lib/emulator/downloadableEmulators.js +39 -23
  57. package/lib/emulator/extensions/postinstall.js +41 -0
  58. package/lib/emulator/extensions/validation.js +2 -2
  59. package/lib/emulator/extensionsEmulator.js +85 -21
  60. package/lib/emulator/functionsEmulator.js +88 -10
  61. package/lib/emulator/functionsEmulatorShared.js +37 -21
  62. package/lib/emulator/functionsEmulatorShell.js +2 -3
  63. package/lib/emulator/pubsubEmulator.js +13 -9
  64. package/lib/emulator/registry.js +34 -12
  65. package/lib/emulator/storage/apis/firebase.js +13 -8
  66. package/lib/emulator/storage/apis/gcloud.js +15 -9
  67. package/lib/emulator/storage/files.js +14 -3
  68. package/lib/emulator/storage/index.js +9 -1
  69. package/lib/emulator/storage/metadata.js +18 -8
  70. package/lib/emulator/storage/rules/manager.js +7 -17
  71. package/lib/emulator/storage/server.js +38 -12
  72. package/lib/ensureApiEnabled.js +8 -4
  73. package/lib/extensions/askUserForParam.js +14 -11
  74. package/lib/extensions/changelog.js +1 -1
  75. package/lib/extensions/emulator/optionsHelper.js +9 -10
  76. package/lib/extensions/emulator/specHelper.js +7 -1
  77. package/lib/extensions/emulator/triggerHelper.js +11 -14
  78. package/lib/extensions/extensionsApi.js +2 -1
  79. package/lib/extensions/extensionsHelper.js +30 -24
  80. package/lib/extensions/manifest.js +28 -8
  81. package/lib/extensions/paramHelper.js +19 -13
  82. package/lib/extensions/provisioningHelper.js +2 -2
  83. package/lib/extensions/warnings.js +3 -3
  84. package/lib/functions/env.js +10 -2
  85. package/lib/functions/events/index.js +7 -0
  86. package/lib/functions/events/v1.js +6 -0
  87. package/lib/functions/projectConfig.js +32 -6
  88. package/lib/functions/runtimeConfigExport.js +10 -6
  89. package/lib/functions/secrets.js +99 -6
  90. package/lib/functionsShellCommandAction.js +1 -1
  91. package/lib/gcp/cloudfunctions.js +44 -18
  92. package/lib/gcp/cloudfunctionsv2.js +48 -25
  93. package/lib/gcp/cloudtasks.js +5 -3
  94. package/lib/gcp/identityPlatform.js +44 -0
  95. package/lib/gcp/secretManager.js +2 -2
  96. package/lib/metaprogramming.js +2 -0
  97. package/lib/previews.js +1 -1
  98. package/lib/serve/functions.js +16 -19
  99. package/lib/serve/hosting.js +25 -12
  100. package/lib/serve/index.js +6 -0
  101. package/lib/track.js +15 -21
  102. package/lib/utils.js +30 -1
  103. package/npm-shrinkwrap.json +44 -2
  104. package/package.json +4 -1
  105. package/schema/firebase-config.json +6 -0
@@ -18,13 +18,37 @@ const validation_1 = require("./extensions/validation");
18
18
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
19
19
  const shortenUrl_1 = require("../shortenUrl");
20
20
  const constants_1 = require("./constants");
21
+ const registry_1 = require("./registry");
21
22
  class ExtensionsEmulator {
22
23
  constructor(args) {
23
24
  this.want = [];
25
+ this.backends = [];
24
26
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
25
27
  this.pendingDownloads = new Map();
26
28
  this.args = args;
27
29
  }
30
+ start() {
31
+ this.logger.logLabeled("DEBUG", "Extensions", "Started Extensions emulator, this is a noop.");
32
+ return Promise.resolve();
33
+ }
34
+ stop() {
35
+ this.logger.logLabeled("DEBUG", "Extensions", "Stopping Extensions emulator, this is a noop.");
36
+ return Promise.resolve();
37
+ }
38
+ connect() {
39
+ this.logger.logLabeled("DEBUG", "Extensions", "Connecting Extensions emulator, this is a noop.");
40
+ return Promise.resolve();
41
+ }
42
+ getInfo() {
43
+ const info = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FUNCTIONS);
44
+ if (!info) {
45
+ throw new error_1.FirebaseError("Extensions Emulator is running but Functions emulator is not. This should never happen.");
46
+ }
47
+ return info;
48
+ }
49
+ getName() {
50
+ return types_1.Emulators.EXTENSIONS;
51
+ }
28
52
  async readManifest() {
29
53
  var _a;
30
54
  this.want = await planner.want({
@@ -37,22 +61,30 @@ class ExtensionsEmulator {
37
61
  });
38
62
  }
39
63
  async ensureSourceCode(instance) {
40
- if (!instance.ref) {
41
- throw new error_1.FirebaseError(`No ref found for ${instance.instanceId}. Emulating local extensions is not yet supported.`);
64
+ if (instance.localPath) {
65
+ if (!this.hasValidSource({ path: instance.localPath, extTarget: instance.localPath })) {
66
+ throw new error_1.FirebaseError(`Tried to emulate local extension at ${instance.localPath}, but it was missing required files.`);
67
+ }
68
+ return instance.localPath;
42
69
  }
43
- const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
44
- const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
45
- path.join(os.homedir(), ".cache", "firebase", "extensions");
46
- const sourceCodePath = path.join(cacheDir, ref);
47
- if (this.pendingDownloads.get(ref)) {
48
- await this.pendingDownloads.get(ref);
70
+ else if (instance.ref) {
71
+ const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
72
+ const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
73
+ path.join(os.homedir(), ".cache", "firebase", "extensions");
74
+ const sourceCodePath = path.join(cacheDir, ref);
75
+ if (this.pendingDownloads.get(ref)) {
76
+ await this.pendingDownloads.get(ref);
77
+ }
78
+ if (!this.hasValidSource({ path: sourceCodePath, extTarget: ref })) {
79
+ const promise = this.downloadSource(instance, ref, sourceCodePath);
80
+ this.pendingDownloads.set(ref, promise);
81
+ await promise;
82
+ }
83
+ return sourceCodePath;
49
84
  }
50
- if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) {
51
- const promise = this.downloadSource(instance, ref, sourceCodePath);
52
- this.pendingDownloads.set(ref, promise);
53
- await promise;
85
+ else {
86
+ throw new error_1.FirebaseError("Tried to emulate an extension instance without a ref or localPath. This should never happen.");
54
87
  }
55
- return sourceCodePath;
56
88
  }
57
89
  async downloadSource(instance, ref, sourceCodePath) {
58
90
  const extensionVersion = await planner.getExtensionVersion(instance);
@@ -71,7 +103,7 @@ class ExtensionsEmulator {
71
103
  for (const requiredFile of requiredFiles) {
72
104
  const f = path.join(args.path, requiredFile);
73
105
  if (!fs.existsSync(f)) {
74
- emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.extRef }).logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extRef}, expected to find ${f}`);
106
+ emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.extTarget }).logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extTarget}, expected to find ${f}`);
75
107
  return false;
76
108
  }
77
109
  }
@@ -92,27 +124,32 @@ class ExtensionsEmulator {
92
124
  async getExtensionBackends() {
93
125
  await this.readManifest();
94
126
  await this.checkAndWarnAPIs(this.want);
95
- return Promise.all(this.want.map((i) => {
127
+ this.backends = await Promise.all(this.want.map((i) => {
96
128
  return this.toEmulatableBackend(i);
97
129
  }));
130
+ return this.backends;
98
131
  }
99
132
  async toEmulatableBackend(instance) {
100
133
  const extensionDir = await this.ensureSourceCode(instance);
101
134
  const functionsDir = path.join(extensionDir, "functions");
102
135
  const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
103
- const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
104
- const extension = await planner.getExtension(instance);
105
- const extensionVersion = await planner.getExtensionVersion(instance);
106
- return {
136
+ const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(instance, env);
137
+ const emulatableBackend = {
107
138
  functionsDir,
108
139
  env: nonSecretEnv,
109
140
  secretEnv: secretEnvVariables,
110
141
  predefinedTriggers: extensionTriggers,
111
142
  nodeMajorVersion: nodeMajorVersion,
112
143
  extensionInstanceId: instance.instanceId,
113
- extension,
114
- extensionVersion,
115
144
  };
145
+ if (instance.ref) {
146
+ emulatableBackend.extension = await planner.getExtension(instance);
147
+ emulatableBackend.extensionVersion = await planner.getExtensionVersion(instance);
148
+ }
149
+ else if (instance.localPath) {
150
+ emulatableBackend.extensionSpec = await planner.getExtensionSpec(instance);
151
+ }
152
+ return emulatableBackend;
116
153
  }
117
154
  autoPopulatedParams(instance) {
118
155
  const projectId = this.args.projectId;
@@ -174,5 +211,32 @@ class ExtensionsEmulator {
174
211
  }
175
212
  return filteredBackends;
176
213
  }
214
+ extensionDetailsUILink(backend) {
215
+ const uiInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.UI);
216
+ if (!uiInfo || !backend.extensionInstanceId) {
217
+ return "";
218
+ }
219
+ const uiUrl = registry_1.EmulatorRegistry.getInfoHostString(uiInfo);
220
+ return clc.underline(clc.bold(`http://${uiUrl}/${types_1.Emulators.EXTENSIONS}/${backend.extensionInstanceId}`));
221
+ }
222
+ extensionsInfoTable(options) {
223
+ var _a;
224
+ const filtedBackends = this.filterUnemulatedTriggers(options, this.backends);
225
+ const uiRunning = registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI);
226
+ const tableHead = ["Extension Instance Name", "Extension Ref"];
227
+ if (uiRunning) {
228
+ tableHead.push("View in Emulator UI");
229
+ }
230
+ const table = new Table({ head: tableHead, style: { head: ["yellow"] } });
231
+ for (const b of filtedBackends) {
232
+ if (b.extensionInstanceId) {
233
+ const tableEntry = [b.extensionInstanceId, ((_a = b.extensionVersion) === null || _a === void 0 ? void 0 : _a.ref) || "Local Extension"];
234
+ if (uiRunning)
235
+ tableEntry.push(this.extensionDetailsUILink(b));
236
+ table.push(tableEntry);
237
+ }
238
+ }
239
+ return table.toString();
240
+ }
177
241
  }
178
242
  exports.ExtensionsEmulator = ExtensionsEmulator;
@@ -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) {
@@ -229,6 +242,7 @@ class FunctionsEmulator {
229
242
  loadTriggerPromises.push(this.loadTriggers(backend, true));
230
243
  }
231
244
  await Promise.all(loadTriggerPromises);
245
+ await this.performPostLoadOperations();
232
246
  return;
233
247
  }
234
248
  async stop() {
@@ -271,6 +285,10 @@ class FunctionsEmulator {
271
285
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
272
286
  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
287
  const endpoints = backend.allEndpoints(discoveredBackend);
288
+ (0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
289
+ for (const e of endpoints) {
290
+ e.codebase = emulatableBackend.codebase;
291
+ }
274
292
  triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
275
293
  }
276
294
  const toSetup = triggerDefinitions.filter((definition) => {
@@ -327,6 +345,11 @@ class FunctionsEmulator {
327
345
  break;
328
346
  }
329
347
  }
348
+ else if (definition.blockingTrigger) {
349
+ const { host, port } = this.getInfo();
350
+ url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
351
+ added = this.addBlockingTrigger(url, definition.blockingTrigger);
352
+ }
330
353
  else {
331
354
  this.logger.log("WARN", `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.`);
332
355
  }
@@ -350,6 +373,34 @@ class FunctionsEmulator {
350
373
  this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
351
374
  }
352
375
  }
376
+ async performPostLoadOperations() {
377
+ var _a, _b, _c, _d;
378
+ if (((_b = (_a = this.blockingFunctionsConfig.triggers) === null || _a === void 0 ? void 0 : _a.beforeCreate) === null || _b === void 0 ? void 0 : _b.functionUri) === "" &&
379
+ ((_d = (_c = this.blockingFunctionsConfig.triggers) === null || _c === void 0 ? void 0 : _c.beforeSignIn) === null || _d === void 0 ? void 0 : _d.functionUri) === "") {
380
+ return;
381
+ }
382
+ const authEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH);
383
+ if (!authEmu) {
384
+ return;
385
+ }
386
+ const path = `/identitytoolkit.googleapis.com/v2/projects/${this.getProjectId()}/config?updateMask=blockingFunctions`;
387
+ try {
388
+ await api.request("PATCH", path, {
389
+ origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(authEmu.getInfo())}`,
390
+ headers: {
391
+ Authorization: "Bearer owner",
392
+ },
393
+ data: {
394
+ blockingFunctions: this.blockingFunctionsConfig,
395
+ },
396
+ json: true,
397
+ });
398
+ }
399
+ catch (err) {
400
+ this.logger.log("WARN", "Error updating blocking functions config to the auth emulator: " + err);
401
+ throw err;
402
+ }
403
+ }
353
404
  addRealtimeDatabaseTrigger(projectId, key, eventTrigger) {
354
405
  const databaseEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
355
406
  if (!databaseEmu) {
@@ -416,11 +467,10 @@ class FunctionsEmulator {
416
467
  });
417
468
  }
418
469
  async addPubsubTrigger(triggerName, key, eventTrigger, signatureType, schedule) {
419
- const pubsubPort = registry_1.EmulatorRegistry.getPort(types_1.Emulators.PUBSUB);
420
- if (!pubsubPort) {
470
+ const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
471
+ if (!pubsubEmulator) {
421
472
  return false;
422
473
  }
423
- const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
424
474
  logger_1.logger.debug(`addPubsubTrigger`, JSON.stringify({ eventTrigger }));
425
475
  const resource = eventTrigger.resource;
426
476
  let topic;
@@ -458,6 +508,31 @@ class FunctionsEmulator {
458
508
  this.multicastTriggers[eventTriggerId] = triggers;
459
509
  return true;
460
510
  }
511
+ addBlockingTrigger(url, blockingTrigger) {
512
+ logger_1.logger.debug(`addBlockingTrigger`, JSON.stringify({ blockingTrigger }));
513
+ const eventType = blockingTrigger.eventType;
514
+ if (v1_1.AUTH_BLOCKING_EVENTS.includes(eventType)) {
515
+ if (blockingTrigger.eventType === v1_1.BEFORE_CREATE_EVENT) {
516
+ this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeCreate: {
517
+ functionUri: url,
518
+ } });
519
+ }
520
+ else {
521
+ this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeSignIn: {
522
+ functionUri: url,
523
+ } });
524
+ }
525
+ this.blockingFunctionsConfig.forwardInboundCredentials = {
526
+ accessToken: blockingTrigger.options.accessToken,
527
+ idToken: blockingTrigger.options.idToken,
528
+ refreshToken: blockingTrigger.options.refreshToken,
529
+ };
530
+ }
531
+ else {
532
+ return false;
533
+ }
534
+ return true;
535
+ }
461
536
  getProjectId() {
462
537
  return this.args.projectId;
463
538
  }
@@ -509,6 +584,7 @@ class FunctionsEmulator {
509
584
  };
510
585
  }
511
586
  setTriggersForTesting(triggers, backend) {
587
+ this.triggers = {};
512
588
  triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
513
589
  }
514
590
  getBaseBundle() {
@@ -781,7 +857,9 @@ class FunctionsEmulator {
781
857
  for (const backend of this.args.emulatableBackends) {
782
858
  loadTriggerPromises.push(this.loadTriggers(backend));
783
859
  }
784
- return Promise.all(loadTriggerPromises);
860
+ await Promise.all(loadTriggerPromises);
861
+ await this.performPostLoadOperations();
862
+ return;
785
863
  }
786
864
  async handleBackgroundTrigger(projectId, triggerKey, proto) {
787
865
  const record = this.getTriggerRecordByKey(triggerKey);
@@ -790,7 +868,7 @@ class FunctionsEmulator {
790
868
  }
791
869
  const trigger = record.def;
792
870
  const service = (0, functionsEmulatorShared_1.getFunctionService)(trigger);
793
- const worker = await this.invokeTrigger(record.backend, trigger, proto);
871
+ const worker = await this.invokeTrigger(trigger, proto);
794
872
  return new Promise((resolve, reject) => {
795
873
  if (projectId !== this.args.projectId) {
796
874
  if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
@@ -809,7 +887,7 @@ class FunctionsEmulator {
809
887
  reject({ code: 500, body: el.text });
810
888
  }
811
889
  });
812
- void track(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
890
+ void (0, track_1.track)(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
813
891
  worker.waitForDone().then(() => {
814
892
  resolve({ status: "acknowledged" });
815
893
  });
@@ -877,14 +955,14 @@ class FunctionsEmulator {
877
955
  req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
878
956
  }
879
957
  }
880
- const worker = await this.invokeTrigger(record.backend, trigger);
958
+ const worker = await this.invokeTrigger(trigger);
881
959
  worker.onLogs((el) => {
882
960
  if (el.level === "FATAL") {
883
961
  res.status(500).send(el.text);
884
962
  }
885
963
  });
886
964
  await worker.waitForSocketReady();
887
- void track(EVENT_INVOKE, "https");
965
+ void (0, track_1.track)(EVENT_INVOKE, "https");
888
966
  this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
889
967
  if (!worker.lastArgs) {
890
968
  throw new error_1.FirebaseError("Cannot execute on a worker with no arguments");
@@ -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");
@@ -8,9 +8,11 @@ const fs = require("fs");
8
8
  const backend = require("../deploy/functions/backend");
9
9
  const constants_1 = require("./constants");
10
10
  const proto_1 = require("../gcp/proto");
11
- const logger_1 = require("../logger");
12
11
  const manifest_1 = require("../extensions/manifest");
13
12
  const extensionsHelper_1 = require("../extensions/extensionsHelper");
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,
@@ -33,12 +35,7 @@ class EmulatedTrigger {
33
35
  return memoryLookup[this.definition.availableMemoryMb || "128MB"] * 1024 * 1024;
34
36
  }
35
37
  get timeoutMs() {
36
- if (typeof this.definition.timeout === "number") {
37
- return this.definition.timeout * 1000;
38
- }
39
- else {
40
- return parseInt((this.definition.timeout || "60s").split("s")[0], 10) * 1000;
41
- }
38
+ return (this.definition.timeoutSeconds || 60) * 1000;
42
39
  }
43
40
  getRawFunction() {
44
41
  if (!this.module) {
@@ -49,6 +46,14 @@ class EmulatedTrigger {
49
46
  }
50
47
  }
51
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;
52
57
  function emulatedFunctionsFromEndpoints(endpoints) {
53
58
  const regionDefinitions = [];
54
59
  for (const endpoint of endpoints) {
@@ -61,8 +66,9 @@ function emulatedFunctionsFromEndpoints(endpoints) {
61
66
  region: endpoint.region,
62
67
  name: endpoint.id,
63
68
  id: `${endpoint.region}-${endpoint.id}`,
69
+ codebase: endpoint.codebase,
64
70
  };
65
- (0, proto_1.copyIfPresent)(def, endpoint, "timeout", "availableMemoryMb", "labels", "platform", "secretEnvironmentVariables");
71
+ (0, proto_1.copyIfPresent)(def, endpoint, "availableMemoryMb", "labels", "timeoutSeconds", "platform", "secretEnvironmentVariables");
66
72
  if (backend.isHttpsTriggered(endpoint)) {
67
73
  def.httpsTrigger = endpoint.httpsTrigger;
68
74
  }
@@ -73,25 +79,20 @@ function emulatedFunctionsFromEndpoints(endpoints) {
73
79
  else if (backend.isEventTriggered(endpoint)) {
74
80
  const eventTrigger = endpoint.eventTrigger;
75
81
  if (endpoint.platform === "gcfv1") {
76
- const resourceFilter = backend.findEventFilter(endpoint, "resource");
77
- if (!resourceFilter) {
78
- logger_1.logger.debug(`Invalid event trigger ${JSON.stringify(endpoint)}, expected event filter with resource attribute. Skipping.`);
79
- continue;
80
- }
81
82
  def.eventTrigger = {
82
83
  eventType: eventTrigger.eventType,
83
- resource: resourceFilter.value,
84
+ resource: eventTrigger.eventFilters.resource,
84
85
  };
85
86
  }
86
87
  else {
87
- const [eventFilter] = endpoint.eventTrigger.eventFilters;
88
- if (!eventFilter) {
89
- logger_1.logger.debug(`Invalid event trigger ${JSON.stringify(endpoint)}, expected at least one event filter. Skipping.`);
88
+ const { resource, topic, bucket } = endpoint.eventTrigger.eventFilters;
89
+ const eventResource = resource || topic || bucket;
90
+ if (!eventResource) {
90
91
  continue;
91
92
  }
92
93
  def.eventTrigger = {
93
94
  eventType: eventTrigger.eventType,
94
- resource: eventFilter.value,
95
+ resource: eventResource,
95
96
  };
96
97
  }
97
98
  }
@@ -99,6 +100,12 @@ function emulatedFunctionsFromEndpoints(endpoints) {
99
100
  def.eventTrigger = { eventType: "pubsub", resource: "" };
100
101
  def.schedule = endpoint.scheduleTrigger;
101
102
  }
103
+ else if (backend.isBlockingTriggered(endpoint)) {
104
+ def.blockingTrigger = {
105
+ eventType: endpoint.blockingTrigger.eventType,
106
+ options: endpoint.blockingTrigger.options || {},
107
+ };
108
+ }
102
109
  else {
103
110
  }
104
111
  regionDefinitions.push(def);
@@ -146,6 +153,9 @@ function getFunctionService(def) {
146
153
  if (def.eventTrigger) {
147
154
  return (_a = def.eventTrigger.service) !== null && _a !== void 0 ? _a : getServiceFromEventType(def.eventTrigger.eventType);
148
155
  }
156
+ if (def.blockingTrigger) {
157
+ return def.blockingTrigger.eventType;
158
+ }
149
159
  return "unknown";
150
160
  }
151
161
  exports.getFunctionService = getFunctionService;
@@ -244,7 +254,7 @@ function getSecretLocalPath(backend, projectDir) {
244
254
  }
245
255
  exports.getSecretLocalPath = getSecretLocalPath;
246
256
  function toBackendInfo(e, cf3Triggers) {
247
- var _a;
257
+ var _a, _b;
248
258
  const envWithSecrets = Object.assign({}, e.env);
249
259
  for (const s of e.secretEnv) {
250
260
  envWithSecrets[s.key] = backend.secretVersionName(s);
@@ -252,10 +262,16 @@ function toBackendInfo(e, cf3Triggers) {
252
262
  let extensionVersion = e.extensionVersion;
253
263
  if (extensionVersion) {
254
264
  extensionVersion = (0, extensionsHelper_1.substituteParams)(extensionVersion, e.env);
265
+ if ((_a = extensionVersion.spec) === null || _a === void 0 ? void 0 : _a.postinstallContent) {
266
+ extensionVersion.spec.postinstallContent = (0, postinstall_1.replaceConsoleLinks)(extensionVersion.spec.postinstallContent);
267
+ }
255
268
  }
256
269
  let extensionSpec = e.extensionSpec;
257
270
  if (extensionSpec) {
258
271
  extensionSpec = (0, extensionsHelper_1.substituteParams)(extensionSpec, e.env);
272
+ if (extensionSpec === null || extensionSpec === void 0 ? void 0 : extensionSpec.postinstallContent) {
273
+ extensionSpec.postinstallContent = (0, postinstall_1.replaceConsoleLinks)(extensionSpec.postinstallContent);
274
+ }
259
275
  }
260
276
  return JSON.parse(JSON.stringify({
261
277
  directory: e.functionsDir,
@@ -264,7 +280,7 @@ function toBackendInfo(e, cf3Triggers) {
264
280
  extension: e.extension,
265
281
  extensionVersion: extensionVersion,
266
282
  extensionSpec: extensionSpec,
267
- functionTriggers: (_a = e.predefinedTriggers) !== null && _a !== void 0 ? _a : cf3Triggers,
283
+ functionTriggers: (_b = e.predefinedTriggers) !== null && _b !== void 0 ? _b : cf3Triggers.filter((t) => t.codebase === e.codebase),
268
284
  }));
269
285
  }
270
286
  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) => {
@@ -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();