firebase-tools 11.21.0 → 11.23.0

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 (94) hide show
  1. package/lib/commands/ext-configure.js +3 -3
  2. package/lib/commands/ext-dev-init.js +16 -4
  3. package/lib/commands/ext-dev-publish.js +3 -3
  4. package/lib/commands/ext-dev-register.js +2 -2
  5. package/lib/commands/ext-info.js +3 -3
  6. package/lib/commands/ext-install.js +2 -2
  7. package/lib/commands/ext-uninstall.js +2 -2
  8. package/lib/commands/ext-update.js +2 -2
  9. package/lib/commands/hosting-channel-create.js +2 -2
  10. package/lib/commands/hosting-channel-delete.js +2 -2
  11. package/lib/commands/hosting-channel-deploy.js +2 -2
  12. package/lib/commands/hosting-clone.js +2 -2
  13. package/lib/deploy/functions/release/fabricator.js +3 -0
  14. package/lib/deploy/functions/runtimes/discovery/index.js +1 -1
  15. package/lib/deploy/functions/runtimes/index.js +5 -2
  16. package/lib/deploy/functions/runtimes/node/index.js +70 -27
  17. package/lib/deploy/functions/runtimes/node/versioning.js +4 -2
  18. package/lib/deploy/functions/runtimes/python/index.js +132 -0
  19. package/lib/deploy/hosting/convertConfig.js +2 -1
  20. package/lib/emulator/auth/apiSpec.js +21 -1
  21. package/lib/emulator/controller.js +5 -5
  22. package/lib/emulator/downloadableEmulators.js +6 -6
  23. package/lib/emulator/extensionsEmulator.js +3 -2
  24. package/lib/emulator/functionsEmulator.js +119 -87
  25. package/lib/emulator/functionsEmulatorRuntime.js +26 -42
  26. package/lib/emulator/functionsRuntimeWorker.js +51 -35
  27. package/lib/emulator/hub.js +6 -6
  28. package/lib/emulator/pubsubEmulator.js +12 -9
  29. package/lib/emulator/storage/apis/shared.js +2 -1
  30. package/lib/emulator/storage/cloudFunctions.js +1 -1
  31. package/lib/emulator/storage/files.js +18 -11
  32. package/lib/emulator/types.js +9 -9
  33. package/lib/extensions/askUserForConsent.js +4 -4
  34. package/lib/extensions/askUserForEventsConfig.js +2 -2
  35. package/lib/extensions/askUserForParam.js +34 -3
  36. package/lib/extensions/billingMigrationHelper.js +4 -4
  37. package/lib/extensions/change-log.js +4 -4
  38. package/lib/extensions/displayExtensionInfo.js +4 -4
  39. package/lib/extensions/emulator/optionsHelper.js +3 -3
  40. package/lib/extensions/emulator/specHelper.js +17 -16
  41. package/lib/extensions/extensionsApi.js +2 -2
  42. package/lib/extensions/extensionsHelper.js +6 -6
  43. package/lib/extensions/provisioningHelper.js +2 -2
  44. package/lib/extensions/updateHelper.js +2 -2
  45. package/lib/extensions/warnings.js +5 -5
  46. package/lib/firestore/checkDatabaseType.js +3 -3
  47. package/lib/frameworks/angular/index.js +6 -4
  48. package/lib/frameworks/index.js +47 -11
  49. package/lib/frameworks/lit/index.js +5 -1
  50. package/lib/frameworks/next/index.js +48 -20
  51. package/lib/frameworks/next/utils.js +1 -1
  52. package/lib/frameworks/nuxt/index.js +18 -26
  53. package/lib/frameworks/nuxt/interfaces.js +2 -0
  54. package/lib/frameworks/nuxt/utils.js +13 -0
  55. package/lib/frameworks/nuxt2/index.js +91 -0
  56. package/lib/frameworks/preact/index.js +5 -1
  57. package/lib/frameworks/react/index.js +5 -1
  58. package/lib/frameworks/svelte/index.js +5 -1
  59. package/lib/frameworks/vite/index.js +6 -4
  60. package/lib/functions/python.js +16 -0
  61. package/lib/gcp/cloudfunctionsv2.js +8 -0
  62. package/lib/getDefaultHostingSite.js +3 -1
  63. package/lib/init/features/firestore/index.js +1 -3
  64. package/lib/init/features/functions/index.js +10 -0
  65. package/lib/init/features/functions/python.js +48 -0
  66. package/lib/init/features/hosting/index.js +3 -2
  67. package/lib/projectUtils.js +2 -2
  68. package/lib/rc.js +4 -4
  69. package/lib/serve/functions.js +1 -3
  70. package/npm-shrinkwrap.json +1295 -276
  71. package/package.json +2 -2
  72. package/templates/extensions/extension.yaml +1 -1
  73. package/templates/extensions/integration-test.env +2 -0
  74. package/templates/extensions/integration-test.json +14 -0
  75. package/templates/extensions/javascript/WELCOME.md +14 -5
  76. package/templates/extensions/javascript/index.js +10 -10
  77. package/templates/extensions/javascript/integration-test.js +13 -0
  78. package/templates/extensions/javascript/package.lint.json +12 -4
  79. package/templates/extensions/javascript/package.nolint.json +11 -2
  80. package/templates/extensions/typescript/WELCOME.md +18 -5
  81. package/templates/extensions/typescript/_mocharc +10 -0
  82. package/templates/extensions/typescript/index.ts +16 -15
  83. package/templates/extensions/typescript/integration-test.ts +13 -0
  84. package/templates/extensions/typescript/package.lint.json +16 -4
  85. package/templates/extensions/typescript/package.nolint.json +12 -4
  86. package/templates/init/functions/javascript/_eslintrc +16 -2
  87. package/templates/init/functions/javascript/package.lint.json +4 -4
  88. package/templates/init/functions/javascript/package.nolint.json +3 -3
  89. package/templates/init/functions/python/_gitignore +0 -0
  90. package/templates/init/functions/python/main.py +13 -0
  91. package/templates/init/functions/python/requirements.txt +1 -0
  92. package/templates/init/functions/typescript/_eslintrc +1 -0
  93. package/templates/init/functions/typescript/package.lint.json +4 -4
  94. package/templates/init/functions/typescript/package.nolint.json +4 -3
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Delegate = exports.getPythonBinary = exports.tryCreateDelegate = exports.LATEST_VERSION = void 0;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const node_fetch_1 = require("node-fetch");
7
+ const util_1 = require("util");
8
+ const portfinder = require("portfinder");
9
+ const runtimes = require("..");
10
+ const discovery = require("../discovery");
11
+ const logger_1 = require("../../../../logger");
12
+ const python_1 = require("../../../../functions/python");
13
+ const error_1 = require("../../../../error");
14
+ exports.LATEST_VERSION = "python310";
15
+ async function tryCreateDelegate(context) {
16
+ const requirementsTextPath = path.join(context.sourceDir, "requirements.txt");
17
+ if (!(await (0, util_1.promisify)(fs.exists)(requirementsTextPath))) {
18
+ logger_1.logger.debug("Customer code is not Python code.");
19
+ return;
20
+ }
21
+ const runtime = context.runtime ? context.runtime : exports.LATEST_VERSION;
22
+ if (!runtimes.isValidRuntime(runtime)) {
23
+ throw new error_1.FirebaseError(`Runtime ${runtime} is not a valid Python runtime`);
24
+ }
25
+ return Promise.resolve(new Delegate(context.projectId, context.sourceDir, runtime));
26
+ }
27
+ exports.tryCreateDelegate = tryCreateDelegate;
28
+ function getPythonBinary(runtime) {
29
+ if (process.platform === "win32") {
30
+ return "python.exe";
31
+ }
32
+ if (runtime === "python310") {
33
+ return "python3.10";
34
+ }
35
+ else if (runtime === "python311") {
36
+ return "python3.11";
37
+ }
38
+ return "python";
39
+ }
40
+ exports.getPythonBinary = getPythonBinary;
41
+ class Delegate {
42
+ constructor(projectId, sourceDir, runtime) {
43
+ this.projectId = projectId;
44
+ this.sourceDir = sourceDir;
45
+ this.runtime = runtime;
46
+ this.name = "python";
47
+ this._bin = "";
48
+ this._modulesDir = "";
49
+ }
50
+ get bin() {
51
+ if (this._bin === "") {
52
+ this._bin = this.getPythonBinary();
53
+ }
54
+ return this._bin;
55
+ }
56
+ async modulesDir() {
57
+ var _a;
58
+ if (!this._modulesDir) {
59
+ const child = (0, python_1.runWithVirtualEnv)([
60
+ this.bin,
61
+ "-c",
62
+ '"import firebase_functions; import os; print(os.path.dirname(firebase_functions.__file__))"',
63
+ ], this.sourceDir, {});
64
+ let out = "";
65
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
66
+ const chunkString = chunk.toString();
67
+ out = out + chunkString;
68
+ logger_1.logger.debug(`stdout: ${chunkString}`);
69
+ });
70
+ await new Promise((resolve, reject) => {
71
+ child.on("exit", resolve);
72
+ child.on("error", reject);
73
+ });
74
+ this._modulesDir = out.trim();
75
+ }
76
+ return this._modulesDir;
77
+ }
78
+ getPythonBinary() {
79
+ return getPythonBinary(this.runtime);
80
+ }
81
+ validate() {
82
+ return Promise.resolve();
83
+ }
84
+ watch() {
85
+ return Promise.resolve(() => Promise.resolve());
86
+ }
87
+ async build() {
88
+ return Promise.resolve();
89
+ }
90
+ async serveAdmin(port, envs) {
91
+ var _a, _b;
92
+ const modulesDir = await this.modulesDir();
93
+ const envWithAdminPort = Object.assign(Object.assign({}, envs), { ADMIN_PORT: port.toString() });
94
+ const args = [this.bin, path.join(modulesDir, "private", "serving.py")];
95
+ logger_1.logger.debug(`Running admin server with args: ${JSON.stringify(args)} and env: ${JSON.stringify(envWithAdminPort)} in ${this.sourceDir}`);
96
+ const childProcess = (0, python_1.runWithVirtualEnv)(args, this.sourceDir, envWithAdminPort);
97
+ (_a = childProcess.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
98
+ const chunkString = chunk.toString();
99
+ logger_1.logger.debug(`stdout: ${chunkString}`);
100
+ });
101
+ (_b = childProcess.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (chunk) => {
102
+ const chunkString = chunk.toString();
103
+ logger_1.logger.debug(`stderr: ${chunkString}`);
104
+ });
105
+ return Promise.resolve(async () => {
106
+ await (0, node_fetch_1.default)(`http://127.0.0.1:${port}/__/quitquitquit`);
107
+ const quitTimeout = setTimeout(() => {
108
+ if (!childProcess.killed) {
109
+ childProcess.kill("SIGKILL");
110
+ }
111
+ }, 10000);
112
+ clearTimeout(quitTimeout);
113
+ });
114
+ }
115
+ async discoverBuild(_configValues, envs) {
116
+ let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
117
+ if (!discovered) {
118
+ const adminPort = await portfinder.getPortPromise({
119
+ port: 8081,
120
+ });
121
+ const killProcess = await this.serveAdmin(adminPort, envs);
122
+ try {
123
+ discovered = await discovery.detectFromPort(adminPort, this.projectId, this.runtime);
124
+ }
125
+ finally {
126
+ await killProcess();
127
+ }
128
+ }
129
+ return discovered;
130
+ }
131
+ }
132
+ exports.Delegate = Delegate;
@@ -81,6 +81,7 @@ async function convertConfig(context, functionsPayload, deploy) {
81
81
  }
82
82
  }
83
83
  config.rewrites = (_b = deploy.config.rewrites) === null || _b === void 0 ? void 0 : _b.map((rewrite) => {
84
+ var _a;
84
85
  const target = extractPattern("rewrite", rewrite);
85
86
  if ("destination" in rewrite) {
86
87
  return Object.assign(Object.assign({}, target), { path: rewrite.destination });
@@ -120,7 +121,7 @@ async function convertConfig(context, functionsPayload, deploy) {
120
121
  return Object.assign(Object.assign({}, target), { function: endpoint.id, functionRegion: endpoint.region });
121
122
  }
122
123
  const apiRewrite = Object.assign(Object.assign({}, target), { run: {
123
- serviceId: endpoint.id,
124
+ serviceId: (_a = endpoint.runServiceId) !== null && _a !== void 0 ? _a : endpoint.id,
124
125
  region: endpoint.region,
125
126
  } });
126
127
  if (rewrite.function.pinTag) {
@@ -4562,6 +4562,10 @@ exports.default = {
4562
4562
  description: "Response message for GetRecaptchaParam.",
4563
4563
  properties: {
4564
4564
  kind: { type: "string" },
4565
+ producerProjectNumber: {
4566
+ description: "The producer project number used to generate PIA tokens",
4567
+ type: "string",
4568
+ },
4565
4569
  recaptchaSiteKey: {
4566
4570
  description: "The reCAPTCHA v2 site key used to invoke the reCAPTCHA service. Always present.",
4567
4571
  type: "string",
@@ -5188,6 +5192,10 @@ exports.default = {
5188
5192
  description: "Request message for SignInWithGameCenter",
5189
5193
  properties: {
5190
5194
  displayName: { description: "The user's Game Center display name.", type: "string" },
5195
+ gamePlayerId: {
5196
+ description: "The user's Game Center game player ID. A unique identifier for a player of the game. https://developer.apple.com/documentation/gamekit/gkplayer/3113960-gameplayerid",
5197
+ type: "string",
5198
+ },
5191
5199
  idToken: {
5192
5200
  description: "A valid ID token for an Identity Platform account. If present, this request will link the Game Center player ID to the account represented by this ID token.",
5193
5201
  type: "string",
@@ -5205,6 +5213,10 @@ exports.default = {
5205
5213
  description: "Required. The verification signature data generated by Apple.",
5206
5214
  type: "string",
5207
5215
  },
5216
+ teamPlayerId: {
5217
+ description: "The user's Game Center team player ID. A unique identifier for a player of all the games that you distribute using your developer account. https://developer.apple.com/documentation/gamekit/gkplayer/3174857-teamplayerid",
5218
+ type: "string",
5219
+ },
5208
5220
  tenantId: {
5209
5221
  description: "The ID of the Identity Platform tenant the user is signing in to.",
5210
5222
  type: "string",
@@ -5226,6 +5238,10 @@ exports.default = {
5226
5238
  format: "int64",
5227
5239
  type: "string",
5228
5240
  },
5241
+ gamePlayerId: {
5242
+ description: "The user's Game Center game player ID. A unique identifier for a player of the game. https://developer.apple.com/documentation/gamekit/gkplayer/3113960-gameplayerid",
5243
+ type: "string",
5244
+ },
5229
5245
  idToken: {
5230
5246
  description: "An Identity Platform ID token for the authenticated user.",
5231
5247
  type: "string",
@@ -5243,6 +5259,10 @@ exports.default = {
5243
5259
  description: "An Identity Platform refresh token for the authenticated user.",
5244
5260
  type: "string",
5245
5261
  },
5262
+ teamPlayerId: {
5263
+ description: "The user's Game Center team player ID. A unique identifier for a player of all the games that you distribute using your developer account. https://developer.apple.com/documentation/gamekit/gkplayer/3174857-teamplayerid",
5264
+ type: "string",
5265
+ },
5246
5266
  },
5247
5267
  type: "object",
5248
5268
  },
@@ -7054,7 +7074,7 @@ exports.default = {
7054
7074
  properties: {
7055
7075
  condition: { $ref: "#/components/schemas/GoogleTypeExpr" },
7056
7076
  members: {
7057
- description: "Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. ",
7077
+ description: "Specifies the principals requesting access for a Google Cloud resource. `members` can have the following values: * `allUsers`: A special identifier that represents anyone who is on the internet; with or without a Google account. * `allAuthenticatedUsers`: A special identifier that represents anyone who is authenticated with a Google account or a service account. Does not include identities that come from external identity providers (IdPs) through identity federation. * `user:{emailid}`: An email address that represents a specific Google account. For example, `alice@example.com` . * `serviceAccount:{emailid}`: An email address that represents a Google service account. For example, `my-other-app@appspot.gserviceaccount.com`. * `serviceAccount:{projectid}.svc.id.goog[{namespace}/{kubernetes-sa}]`: An identifier for a [Kubernetes service account](https://cloud.google.com/kubernetes-engine/docs/how-to/kubernetes-service-accounts). For example, `my-project.svc.id.goog[my-namespace/my-kubernetes-sa]`. * `group:{emailid}`: An email address that represents a Google group. For example, `admins@example.com`. * `domain:{domain}`: The G Suite domain (primary) that represents all the users of that domain. For example, `google.com` or `example.com`. * `deleted:user:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a user that has been recently deleted. For example, `alice@example.com?uid=123456789012345678901`. If the user is recovered, this value reverts to `user:{emailid}` and the recovered user retains the role in the binding. * `deleted:serviceAccount:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a service account that has been recently deleted. For example, `my-other-app@appspot.gserviceaccount.com?uid=123456789012345678901`. If the service account is undeleted, this value reverts to `serviceAccount:{emailid}` and the undeleted service account retains the role in the binding. * `deleted:group:{emailid}?uid={uniqueid}`: An email address (plus unique identifier) representing a Google group that has been recently deleted. For example, `admins@example.com?uid=123456789012345678901`. If the group is recovered, this value reverts to `group:{emailid}` and the recovered group retains the role in the binding.",
7058
7078
  items: { type: "string" },
7059
7079
  type: "array",
7060
7080
  },
@@ -11,7 +11,6 @@ const registry_1 = require("./registry");
11
11
  const types_1 = require("./types");
12
12
  const constants_1 = require("./constants");
13
13
  const functionsEmulator_1 = require("./functionsEmulator");
14
- const functionsEmulatorUtils_1 = require("./functionsEmulatorUtils");
15
14
  const auth_1 = require("./auth");
16
15
  const databaseEmulator_1 = require("./databaseEmulator");
17
16
  const firestoreEmulator_1 = require("./firestoreEmulator");
@@ -152,7 +151,7 @@ function findExportMetadata(importPath) {
152
151
  }
153
152
  }
154
153
  async function startAll(options, showUI = true) {
155
- var _a, _b, _c, _d, _e, _f, _g;
154
+ var _a, _b, _c, _d, _e, _f, _g, _h;
156
155
  const targets = filterEmulatorTargets(options);
157
156
  options.targets = targets;
158
157
  const singleProjectModeEnabled = ((_a = options.config.src.emulators) === null || _a === void 0 ? void 0 : _a.singleProjectMode) === undefined ||
@@ -301,13 +300,14 @@ async function startAll(options, showUI = true) {
301
300
  utils.assertIsStringOrUndefined(options.extDevDir);
302
301
  for (const cfg of functionsCfg) {
303
302
  const functionsDir = path.join(projectDir, cfg.source);
303
+ const runtime = (_e = options.extDevRuntime) !== null && _e !== void 0 ? _e : cfg.runtime;
304
304
  emulatableBackends.push({
305
305
  functionsDir,
306
+ runtime,
306
307
  codebase: cfg.codebase,
307
308
  env: Object.assign({}, options.extDevEnv),
308
309
  secretEnv: [],
309
310
  predefinedTriggers: options.extDevTriggers,
310
- nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || cfg.runtime),
311
311
  });
312
312
  }
313
313
  }
@@ -316,7 +316,7 @@ async function startAll(options, showUI = true) {
316
316
  }
317
317
  if (emulatableBackends.length) {
318
318
  if (!listenForEmulator.functions || !listenForEmulator.eventarc) {
319
- listenForEmulator = await (0, portUtils_1.resolveHostAndAssignPorts)(Object.assign(Object.assign({}, listenForEmulator), { functions: (_e = listenForEmulator.functions) !== null && _e !== void 0 ? _e : getListenConfig(options, types_1.Emulators.FUNCTIONS), eventarc: (_f = listenForEmulator.eventarc) !== null && _f !== void 0 ? _f : getListenConfig(options, types_1.Emulators.EVENTARC) }));
319
+ listenForEmulator = await (0, portUtils_1.resolveHostAndAssignPorts)(Object.assign(Object.assign({}, listenForEmulator), { functions: (_f = listenForEmulator.functions) !== null && _f !== void 0 ? _f : getListenConfig(options, types_1.Emulators.FUNCTIONS), eventarc: (_g = listenForEmulator.eventarc) !== null && _g !== void 0 ? _g : getListenConfig(options, types_1.Emulators.EVENTARC) }));
320
320
  hubLogger.log("DEBUG", "late-assigned ports for functions and eventarc emulators", {
321
321
  user: listenForEmulator,
322
322
  });
@@ -377,7 +377,7 @@ async function startAll(options, showUI = true) {
377
377
  });
378
378
  }
379
379
  const config = options.config;
380
- const rulesLocalPath = (_g = config.src.firestore) === null || _g === void 0 ? void 0 : _g.rules;
380
+ const rulesLocalPath = (_h = config.src.firestore) === null || _h === void 0 ? void 0 : _h.rules;
381
381
  let rulesFileFound = false;
382
382
  if (rulesLocalPath) {
383
383
  const rules = config.path(rulesLocalPath);
@@ -23,9 +23,9 @@ const EMULATOR_UPDATE_DETAILS = {
23
23
  expectedChecksum: "311609538bd65666eb724ef47c2e6466",
24
24
  },
25
25
  firestore: {
26
- version: "1.15.1",
27
- expectedSize: 61475851,
28
- expectedChecksum: "4f41d24a3c0f3b55ea22804a424cc0ee",
26
+ version: "1.16.0",
27
+ expectedSize: 63422812,
28
+ expectedChecksum: "6c1a43c1b327d534f83f7386c595d7ff",
29
29
  },
30
30
  storage: {
31
31
  version: "1.1.3",
@@ -35,9 +35,9 @@ const EMULATOR_UPDATE_DETAILS = {
35
35
  ui: experiments.isEnabled("emulatoruisnapshot")
36
36
  ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" }
37
37
  : {
38
- version: "1.11.2",
39
- expectedSize: 3062873,
40
- expectedChecksum: "fe7f668437d0e3c3b92677aaaade78bf",
38
+ version: "1.11.4",
39
+ expectedSize: 3062916,
40
+ expectedChecksum: "1773926323b07fdb9602d882a7682882",
41
41
  },
42
42
  pubsub: {
43
43
  version: "0.7.1",
@@ -143,14 +143,15 @@ class ExtensionsEmulator {
143
143
  const extensionDir = await this.ensureSourceCode(instance);
144
144
  const functionsDir = path.join(extensionDir, "functions");
145
145
  const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
146
- const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(instance, env);
146
+ const { extensionTriggers, runtime, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(instance, env);
147
147
  const emulatableBackend = {
148
148
  functionsDir,
149
+ runtime,
150
+ bin: process.execPath,
149
151
  env: nonSecretEnv,
150
152
  codebase: instance.instanceId,
151
153
  secretEnv: secretEnvVariables,
152
154
  predefinedTriggers: extensionTriggers,
153
- nodeMajorVersion: nodeMajorVersion,
154
155
  extensionInstanceId: instance.instanceId,
155
156
  };
156
157
  if (instance.ref) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FunctionsEmulator = void 0;
3
+ exports.FunctionsEmulator = exports.TCPConn = exports.IPCConn = void 0;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const express = require("express");
@@ -8,6 +8,7 @@ const clc = require("colorette");
8
8
  const http = require("http");
9
9
  const jwt = require("jsonwebtoken");
10
10
  const cors = require("cors");
11
+ const semver = require("semver");
11
12
  const url_1 = require("url");
12
13
  const events_1 = require("events");
13
14
  const logger_1 = require("../logger");
@@ -15,6 +16,7 @@ const track_1 = require("../track");
15
16
  const constants_1 = require("./constants");
16
17
  const types_1 = require("./types");
17
18
  const chokidar = require("chokidar");
19
+ const portfinder = require("portfinder");
18
20
  const spawn = require("cross-spawn");
19
21
  const functionsEmulatorShared_1 = require("./functionsEmulatorShared");
20
22
  const registry_1 = require("./registry");
@@ -33,10 +35,46 @@ const functionsEnv = require("../functions/env");
33
35
  const v1_1 = require("../functions/events/v1");
34
36
  const build_1 = require("../deploy/functions/build");
35
37
  const env_1 = require("./env");
38
+ const python_1 = require("../functions/python");
36
39
  const EVENT_INVOKE = "functions:invoke";
37
40
  const EVENT_INVOKE_GA4 = "functions_invoke";
38
41
  const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
42
+ class IPCConn {
43
+ constructor(socketPath) {
44
+ this.socketPath = socketPath;
45
+ }
46
+ httpReqOpts() {
47
+ return {
48
+ socketPath: this.socketPath,
49
+ };
50
+ }
51
+ }
52
+ exports.IPCConn = IPCConn;
53
+ class TCPConn {
54
+ constructor(host, port) {
55
+ this.host = host;
56
+ this.port = port;
57
+ }
58
+ httpReqOpts() {
59
+ return {
60
+ host: this.host,
61
+ port: this.port,
62
+ };
63
+ }
64
+ }
65
+ exports.TCPConn = TCPConn;
39
66
  class FunctionsEmulator {
67
+ static getHttpFunctionUrl(projectId, name, region, info) {
68
+ let url;
69
+ if (info) {
70
+ url = new url_1.URL("http://" + (0, functionsEmulatorShared_1.formatHost)(info));
71
+ }
72
+ else {
73
+ url = registry_1.EmulatorRegistry.url(types_1.Emulators.FUNCTIONS);
74
+ }
75
+ url.pathname = `/${projectId}/${region}/${name}`;
76
+ return url.toString();
77
+ }
40
78
  constructor(args) {
41
79
  this.args = args;
42
80
  this.triggers = {};
@@ -60,17 +98,6 @@ class FunctionsEmulator {
60
98
  }
61
99
  this.workQueue = new workQueue_1.WorkQueue(mode);
62
100
  }
63
- static getHttpFunctionUrl(projectId, name, region, info) {
64
- let url;
65
- if (info) {
66
- url = new url_1.URL("http://" + (0, functionsEmulatorShared_1.formatHost)(info));
67
- }
68
- else {
69
- url = registry_1.EmulatorRegistry.url(types_1.Emulators.FUNCTIONS);
70
- }
71
- url.pathname = `/${projectId}/${region}/${name}`;
72
- return url.toString();
73
- }
74
101
  async getCredentialsEnvironment() {
75
102
  const credentialEnv = {};
76
103
  if (process.env.GOOGLE_APPLICATION_CREDENTIALS) {
@@ -168,7 +195,13 @@ class FunctionsEmulator {
168
195
  const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger));
169
196
  const pool = this.workerPools[record.backend.codebase];
170
197
  if (!pool.readyForWork(trigger.id)) {
171
- await this.startRuntime(record.backend, trigger);
198
+ try {
199
+ await this.startRuntime(record.backend, trigger);
200
+ }
201
+ catch (e) {
202
+ this.logger.logLabeled("ERROR", `Failed to start runtime for ${trigger.id}: ${e}`);
203
+ return;
204
+ }
172
205
  }
173
206
  const worker = pool.getIdleWorker(trigger.id);
174
207
  const reqBody = JSON.stringify(body);
@@ -177,20 +210,13 @@ class FunctionsEmulator {
177
210
  "Content-Length": `${reqBody.length}`,
178
211
  };
179
212
  return new Promise((resolve, reject) => {
180
- const req = http.request({
181
- path: `/`,
182
- socketPath: worker.runtime.socketPath,
183
- headers: headers,
184
- }, resolve);
213
+ const req = http.request(Object.assign(Object.assign({}, worker.runtime.conn.httpReqOpts()), { path: `/`, headers: headers }), resolve);
185
214
  req.on("error", reject);
186
215
  req.write(reqBody);
187
216
  req.end();
188
217
  });
189
218
  }
190
219
  async start() {
191
- for (const backend of this.args.emulatableBackends) {
192
- backend.nodeBinary = this.getNodeBinary(backend);
193
- }
194
220
  const credentialEnv = await this.getCredentialsEnvironment();
195
221
  for (const e of this.args.emulatableBackends) {
196
222
  e.env = Object.assign(Object.assign({}, credentialEnv), e.env);
@@ -219,6 +245,7 @@ class FunctionsEmulator {
219
245
  /.+?[\\\/]node_modules[\\\/].+?/,
220
246
  /(^|[\/\\])\../,
221
247
  /.+\.log/,
248
+ /.+?[\\\/]venv[\\\/].+?/,
222
249
  ],
223
250
  persistent: true,
224
251
  });
@@ -257,21 +284,22 @@ class FunctionsEmulator {
257
284
  projectId: this.args.projectId,
258
285
  projectDir: this.args.projectDir,
259
286
  sourceDir: emulatableBackend.functionsDir,
287
+ runtime: emulatableBackend.runtime,
260
288
  };
261
- if (emulatableBackend.nodeMajorVersion) {
262
- runtimeDelegateContext.runtime = `nodejs${emulatableBackend.nodeMajorVersion}`;
263
- }
264
289
  const runtimeDelegate = await runtimes.getRuntimeDelegate(runtimeDelegateContext);
265
290
  logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
266
291
  await runtimeDelegate.validate();
267
292
  logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
268
293
  await runtimeDelegate.build();
294
+ emulatableBackend.runtime = runtimeDelegate.runtime;
295
+ emulatableBackend.bin = runtimeDelegate.bin;
269
296
  const firebaseConfig = this.getFirebaseConfig();
270
297
  const environment = Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: firebaseConfig }), emulatableBackend.env);
271
298
  const userEnvOpt = {
272
299
  functionsSource: emulatableBackend.functionsDir,
273
300
  projectId: this.args.projectId,
274
301
  projectAlias: this.args.projectAlias,
302
+ isEmulator: true,
275
303
  };
276
304
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
277
305
  const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
@@ -286,9 +314,7 @@ class FunctionsEmulator {
286
314
  }
287
315
  }
288
316
  async loadTriggers(emulatableBackend, force = false) {
289
- if (!emulatableBackend.nodeBinary) {
290
- throw new error_1.FirebaseError(`No node binary for ${emulatableBackend.functionsDir}. This should never happen.`);
291
- }
317
+ var _a;
292
318
  let triggerDefinitions = [];
293
319
  try {
294
320
  triggerDefinitions = await this.discoverTriggers(emulatableBackend);
@@ -381,13 +407,23 @@ class FunctionsEmulator {
381
407
  }
382
408
  }
383
409
  if (this.args.debugPort) {
384
- emulatableBackend.secretEnv = Object.values(triggerDefinitions.reduce((acc, curr) => {
385
- for (const secret of curr.secretEnvironmentVariables || []) {
386
- acc[secret.key] = secret;
410
+ if (!((_a = emulatableBackend.bin) === null || _a === void 0 ? void 0 : _a.startsWith("node"))) {
411
+ this.logger.log("WARN", "--inspect-functions only supported for Node.js runtimes.");
412
+ }
413
+ else {
414
+ emulatableBackend.secretEnv = Object.values(triggerDefinitions.reduce((acc, curr) => {
415
+ for (const secret of curr.secretEnvironmentVariables || []) {
416
+ acc[secret.key] = secret;
417
+ }
418
+ return acc;
419
+ }, {}));
420
+ try {
421
+ await this.startRuntime(emulatableBackend);
387
422
  }
388
- return acc;
389
- }, {}));
390
- await this.startRuntime(emulatableBackend);
423
+ catch (e) {
424
+ this.logger.logLabeled("ERROR", `Failed to start functions in ${emulatableBackend.functionsDir}: ${e}`);
425
+ }
426
+ }
391
427
  }
392
428
  }
393
429
  addEventarcTrigger(projectId, key, eventTrigger) {
@@ -632,43 +668,6 @@ class FunctionsEmulator {
632
668
  this.triggers = {};
633
669
  triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
634
670
  }
635
- getNodeBinary(backend) {
636
- const pkg = require(path.join(backend.functionsDir, "package.json"));
637
- if ((!pkg.engines || !pkg.engines.node) && !backend.nodeMajorVersion) {
638
- this.logger.log("WARN", `Your functions directory ${backend.functionsDir} does not specify a Node version.\n ` +
639
- "- Learn more at https://firebase.google.com/docs/functions/manage-functions#set_runtime_options");
640
- return process.execPath;
641
- }
642
- const hostMajorVersion = process.versions.node.split(".")[0];
643
- const requestedMajorVersion = backend.nodeMajorVersion
644
- ? `${backend.nodeMajorVersion}`
645
- : pkg.engines.node;
646
- let localMajorVersion = "0";
647
- const localNodePath = path.join(backend.functionsDir, "node_modules/.bin/node");
648
- try {
649
- const localNodeOutput = spawn.sync(localNodePath, ["--version"]).stdout.toString();
650
- localMajorVersion = localNodeOutput.slice(1).split(".")[0];
651
- }
652
- catch (err) {
653
- }
654
- if (requestedMajorVersion === localMajorVersion) {
655
- this.logger.logLabeled("SUCCESS", "functions", `Using node@${requestedMajorVersion} from local cache.`);
656
- return localNodePath;
657
- }
658
- if (requestedMajorVersion === hostMajorVersion) {
659
- this.logger.logLabeled("SUCCESS", "functions", `Using node@${requestedMajorVersion} from host.`);
660
- }
661
- else {
662
- if (process.env.FIREPIT_VERSION) {
663
- this.logger.log("WARN", `You've requested "node" version "${requestedMajorVersion}", but the standalone Firebase CLI comes with bundled Node "${hostMajorVersion}".`);
664
- this.logger.log("INFO", `To use a different Node.js version, consider removing the standalone Firebase CLI and switching to "firebase-tools" on npm.`);
665
- }
666
- else {
667
- this.logger.log("WARN", `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}". Using node@${hostMajorVersion} from host.`);
668
- }
669
- }
670
- return process.execPath;
671
- }
672
671
  getRuntimeConfig(backend) {
673
672
  const configPath = `${backend.functionsDir}/.runtimeconfig.json`;
674
673
  try {
@@ -701,9 +700,6 @@ class FunctionsEmulator {
701
700
  envs.GCLOUD_PROJECT = this.args.projectId;
702
701
  envs.K_REVISION = "1";
703
702
  envs.PORT = "80";
704
- if (trigger === null || trigger === void 0 ? void 0 : trigger.timeoutSeconds) {
705
- envs.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS = trigger.timeoutSeconds.toString();
706
- }
707
703
  if (trigger) {
708
704
  const target = trigger.entryPoint;
709
705
  envs.FUNCTION_TARGET = target;
@@ -791,13 +787,11 @@ class FunctionsEmulator {
791
787
  }
792
788
  return secretEnvs;
793
789
  }
794
- async startRuntime(backend, trigger) {
795
- var _a;
796
- const emitter = new events_1.EventEmitter();
790
+ async startNode(backend, envs) {
797
791
  const args = [path.join(__dirname, "functionsEmulatorRuntime")];
798
792
  if (this.args.debugPort) {
799
- if (process.env.FIREPIT_VERSION && process.execPath === backend.nodeBinary) {
800
- this.logger.log("WARN", `To enable function inspection, please run "${process.execPath} is:npm i node@${backend.nodeMajorVersion} --save-dev" in your functions directory`);
793
+ if (process.env.FIREPIT_VERSION) {
794
+ this.logger.log("WARN", `To enable function inspection, please run "npm i node@${semver.coerce(backend.runtime || "18.0.0")} --save-dev" in your functions directory`);
801
795
  }
802
796
  else {
803
797
  const { host } = this.getInfo();
@@ -810,26 +804,57 @@ class FunctionsEmulator {
810
804
  "Cloud Functions for Firebase requires a node_modules folder to work correctly and is therefore incompatible with PnP. " +
811
805
  "See https://yarnpkg.com/getting-started/migration#step-by-step for more information.");
812
806
  }
813
- const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
814
- const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
807
+ const bin = backend.bin;
808
+ if (!bin) {
809
+ throw new Error(`No binary associated with ${backend.functionsDir}. ` +
810
+ "Make sure function runtime is configured correctly in firebase.json.");
811
+ }
815
812
  const socketPath = (0, functionsEmulatorShared_1.getTemporarySocketPath)();
816
- const childProcess = spawn(backend.nodeBinary, args, {
813
+ const childProcess = spawn(bin, args, {
817
814
  cwd: backend.functionsDir,
818
- env: Object.assign(Object.assign(Object.assign(Object.assign({ node: backend.nodeBinary }, process.env), runtimeEnv), secretEnvs), { PORT: socketPath }),
815
+ env: Object.assign(Object.assign(Object.assign({ node: backend.bin }, process.env), envs), { PORT: socketPath }),
819
816
  stdio: ["pipe", "pipe", "pipe", "ipc"],
820
817
  });
821
- const runtime = {
818
+ return Promise.resolve({
822
819
  process: childProcess,
823
- events: emitter,
820
+ events: new events_1.EventEmitter(),
824
821
  cwd: backend.functionsDir,
825
- socketPath,
822
+ conn: new IPCConn(socketPath),
823
+ });
824
+ }
825
+ async startPython(backend, envs) {
826
+ const args = ["functions-framework"];
827
+ if (this.args.debugPort) {
828
+ this.logger.log("WARN", "--inspect-functions not supported for Python functions. Ignored.");
829
+ }
830
+ const port = await portfinder.getPortPromise({
831
+ port: 8081 + (0, utils_1.randomInt)(0, 1000),
832
+ });
833
+ const childProcess = (0, python_1.runWithVirtualEnv)(args, backend.functionsDir, Object.assign(Object.assign(Object.assign({}, process.env), envs), { PYTHONUNBUFFERED: "1", DEBUG: "False", HOST: "127.0.0.1", PORT: port.toString() }));
834
+ return {
835
+ process: childProcess,
836
+ events: new events_1.EventEmitter(),
837
+ cwd: backend.functionsDir,
838
+ conn: new TCPConn("127.0.0.1", port),
826
839
  };
840
+ }
841
+ async startRuntime(backend, trigger) {
842
+ var _a;
843
+ const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
844
+ const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
845
+ let runtime;
846
+ if (backend.runtime.startsWith("python")) {
847
+ runtime = await this.startPython(backend, Object.assign(Object.assign({}, runtimeEnv), secretEnvs));
848
+ }
849
+ else {
850
+ runtime = await this.startNode(backend, Object.assign(Object.assign({}, runtimeEnv), secretEnvs));
851
+ }
827
852
  const extensionLogInfo = {
828
853
  instanceId: backend.extensionInstanceId,
829
854
  ref: (_a = backend.extensionVersion) === null || _a === void 0 ? void 0 : _a.ref,
830
855
  };
831
856
  const pool = this.workerPools[backend.codebase];
832
- const worker = pool.addWorker(trigger === null || trigger === void 0 ? void 0 : trigger.id, runtime, extensionLogInfo);
857
+ const worker = pool.addWorker(trigger, runtime, extensionLogInfo);
833
858
  await worker.waitForSocketReady();
834
859
  return worker;
835
860
  }
@@ -927,7 +952,14 @@ class FunctionsEmulator {
927
952
  this.logger.log("DEBUG", `[functions] Got req.url=${req.url}, mapping to path=${path}`);
928
953
  const pool = this.workerPools[record.backend.codebase];
929
954
  if (!pool.readyForWork(trigger.id)) {
930
- await this.startRuntime(record.backend, trigger);
955
+ try {
956
+ await this.startRuntime(record.backend, trigger);
957
+ }
958
+ catch (e) {
959
+ this.logger.logLabeled("ERROR", `Failed to handle request for function ${trigger.id}`);
960
+ this.logger.logLabeled("ERROR", `Failed to start functions in ${record.backend.functionsDir}: ${e}`);
961
+ return;
962
+ }
931
963
  }
932
964
  const debugBundle = this.args.debugPort
933
965
  ? {