firebase-tools 11.2.1 → 11.4.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 (40) hide show
  1. package/lib/apiv2.js +5 -0
  2. package/lib/checkValidTargetFilters.js +3 -2
  3. package/lib/commands/ext-export.js +2 -0
  4. package/lib/database/rulesConfig.js +35 -8
  5. package/lib/deploy/extensions/planner.js +1 -0
  6. package/lib/deploy/extensions/prepare.js +18 -1
  7. package/lib/deploy/extensions/release.js +4 -0
  8. package/lib/deploy/functions/build.js +43 -52
  9. package/lib/deploy/functions/params.js +189 -0
  10. package/lib/deploy/functions/prepare.js +1 -1
  11. package/lib/deploy/functions/release/index.js +4 -0
  12. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +3 -0
  13. package/lib/deploy/functions/runtimes/node/parseTriggers.js +1 -1
  14. package/lib/deploy/hosting/convertConfig.js +76 -16
  15. package/lib/deploy/index.js +1 -1
  16. package/lib/deploy/lifecycleHooks.js +1 -1
  17. package/lib/deploy/storage/prepare.js +29 -6
  18. package/lib/downloadUtils.js +1 -1
  19. package/lib/emulator/controller.js +0 -1
  20. package/lib/emulator/downloadableEmulators.js +9 -9
  21. package/lib/emulator/functionsEmulator.js +9 -1
  22. package/lib/emulator/functionsEmulatorRuntime.js +68 -58
  23. package/lib/emulator/functionsRuntimeWorker.js +35 -9
  24. package/lib/emulator/storage/apis/gcloud.js +1 -1
  25. package/lib/extensions/etags.js +28 -0
  26. package/lib/extensions/warnings.js +7 -1
  27. package/lib/functions/env.js +51 -2
  28. package/lib/functionsConfig.js +1 -1
  29. package/lib/gcp/iam.js +20 -17
  30. package/lib/gcp/rules.js +2 -3
  31. package/lib/gcp/runtimeconfig.js +1 -1
  32. package/lib/hosting/api.js +9 -11
  33. package/lib/hosting/expireUtils.js +2 -2
  34. package/lib/management/projects.js +6 -7
  35. package/lib/profileReport.js +16 -14
  36. package/lib/rc.js +14 -10
  37. package/lib/requireConfig.js +6 -6
  38. package/lib/rulesDeploy.js +1 -1
  39. package/npm-shrinkwrap.json +2 -2
  40. package/package.json +2 -1
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertConfig = void 0;
4
4
  const error_1 = require("../../error");
5
5
  const backend_1 = require("../functions/backend");
6
+ const backend = require("../functions/backend");
6
7
  function has(obj, k) {
7
8
  return obj[k] !== undefined;
8
9
  }
@@ -39,24 +40,66 @@ async function convertConfig(context, payload, config, finalize) {
39
40
  if (!config) {
40
41
  return out;
41
42
  }
42
- const endpointBeingDeployed = (serviceId, region = "us-central1") => {
43
- var _a;
43
+ const endpointFromBackend = (targetBackend, functionsEndpointInfo) => {
44
+ const backendsForId = backend.allEndpoints(targetBackend).filter((endpoint) => {
45
+ return endpoint.id === functionsEndpointInfo.serviceId;
46
+ });
47
+ const matchingBackends = backendsForId.filter((endpoint) => {
48
+ return ((!functionsEndpointInfo.region || endpoint.region === functionsEndpointInfo.region) &&
49
+ (!functionsEndpointInfo.platform || endpoint.platform === functionsEndpointInfo.platform));
50
+ });
51
+ if (matchingBackends.length > 1) {
52
+ throw new error_1.FirebaseError(`More than one backend found for function name: ${functionsEndpointInfo.serviceId}. If the function is deployed in multiple regions, you must specify a region.`);
53
+ }
54
+ if (matchingBackends.length === 1) {
55
+ const endpoint = matchingBackends[0];
56
+ if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint)) {
57
+ return endpoint;
58
+ }
59
+ }
60
+ return;
61
+ };
62
+ const endpointBeingDeployed = (functionsEndpointInfo) => {
44
63
  for (const { wantBackend } of Object.values(payload.functions || {})) {
45
- const endpoint = (_a = wantBackend === null || wantBackend === void 0 ? void 0 : wantBackend.endpoints[region]) === null || _a === void 0 ? void 0 : _a[serviceId];
46
- if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint) && endpoint.platform === "gcfv2")
64
+ if (!wantBackend) {
65
+ continue;
66
+ }
67
+ const endpoint = endpointFromBackend(wantBackend, functionsEndpointInfo);
68
+ if (endpoint) {
47
69
  return endpoint;
70
+ }
48
71
  }
49
- return undefined;
72
+ return;
50
73
  };
51
- const matchingEndpoint = async (serviceId, region = "us-central1") => {
52
- const pendingEndpoint = endpointBeingDeployed(serviceId, region);
74
+ const matchingEndpoint = async (functionsEndpointInfo) => {
75
+ const pendingEndpoint = endpointBeingDeployed(functionsEndpointInfo);
53
76
  if (pendingEndpoint)
54
77
  return pendingEndpoint;
55
78
  const backend = await (0, backend_1.existingBackend)(context);
56
79
  return (0, backend_1.allEndpoints)(backend).find((it) => (0, backend_1.isHttpsTriggered)(it) &&
57
- it.platform === "gcfv2" &&
58
- it.id === serviceId &&
59
- it.region === region);
80
+ it.id === functionsEndpointInfo.serviceId &&
81
+ (!functionsEndpointInfo.platform || it.platform === functionsEndpointInfo.platform) &&
82
+ (!functionsEndpointInfo.region || it.region === functionsEndpointInfo.region));
83
+ };
84
+ const findEndpointWithValidRegion = async (rewrite, context) => {
85
+ if ("function" in rewrite) {
86
+ const foundEndpointToBeDeployed = endpointBeingDeployed({
87
+ serviceId: rewrite.function,
88
+ region: rewrite.region,
89
+ });
90
+ if (foundEndpointToBeDeployed) {
91
+ return foundEndpointToBeDeployed;
92
+ }
93
+ const existingBackend = await backend.existingBackend(context);
94
+ const endpointAlreadyDeployed = endpointFromBackend(existingBackend, {
95
+ serviceId: rewrite.function,
96
+ region: rewrite.region,
97
+ });
98
+ if (endpointAlreadyDeployed) {
99
+ return endpointAlreadyDeployed;
100
+ }
101
+ }
102
+ return;
60
103
  };
61
104
  if (Array.isArray(config.rewrites)) {
62
105
  out.rewrites = [];
@@ -66,19 +109,30 @@ async function convertConfig(context, payload, config, finalize) {
66
109
  vRewrite.path = rewrite.destination;
67
110
  }
68
111
  else if ("function" in rewrite) {
69
- if (!finalize && endpointBeingDeployed(rewrite.function, rewrite.region))
112
+ if (!finalize &&
113
+ endpointBeingDeployed({
114
+ serviceId: rewrite.function,
115
+ platform: "gcfv2",
116
+ region: rewrite.region,
117
+ })) {
70
118
  continue;
71
- const endpoint = await matchingEndpoint(rewrite.function, rewrite.region);
119
+ }
120
+ const endpoint = await matchingEndpoint({
121
+ serviceId: rewrite.function,
122
+ platform: "gcfv2",
123
+ region: rewrite.region,
124
+ });
72
125
  if (endpoint) {
73
126
  vRewrite.run = { serviceId: endpoint.id, region: endpoint.region };
74
127
  }
75
128
  else {
76
129
  vRewrite.function = rewrite.function;
77
- if (rewrite.region) {
78
- vRewrite.functionRegion = rewrite.region;
130
+ const foundEndpoint = await findEndpointWithValidRegion(rewrite, context);
131
+ if (foundEndpoint) {
132
+ vRewrite.functionRegion = foundEndpoint.region;
79
133
  }
80
134
  else {
81
- vRewrite.functionRegion = "us-central1";
135
+ throw new error_1.FirebaseError(`Unable to find a valid endpoint for function ${vRewrite.function}`);
82
136
  }
83
137
  }
84
138
  }
@@ -86,8 +140,14 @@ async function convertConfig(context, payload, config, finalize) {
86
140
  vRewrite.dynamicLinks = rewrite.dynamicLinks;
87
141
  }
88
142
  else if ("run" in rewrite) {
89
- if (!finalize && endpointBeingDeployed(rewrite.run.serviceId, rewrite.run.region))
143
+ if (!finalize &&
144
+ endpointBeingDeployed({
145
+ serviceId: rewrite.run.serviceId,
146
+ platform: "gcfv2",
147
+ region: rewrite.run.region,
148
+ })) {
90
149
  continue;
150
+ }
91
151
  vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run);
92
152
  }
93
153
  out.rewrites.push(vRewrite);
@@ -52,7 +52,7 @@ const deploy = async function (targetNames, options, customContext = {}) {
52
52
  for (const targetName of targetNames) {
53
53
  const target = TARGETS[targetName];
54
54
  if (!target) {
55
- return Promise.reject(new error_1.FirebaseError((0, cli_color_1.bold)(targetName) + " is not a valid deploy target", { exit: 1 }));
55
+ return Promise.reject(new error_1.FirebaseError(`${(0, cli_color_1.bold)(targetName)} is not a valid deploy target`));
56
56
  }
57
57
  predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
58
58
  prepares.push(target.prepare);
@@ -31,7 +31,7 @@ function runCommand(command, childOptions) {
31
31
  reject(new Error("Command terminated with signal " + signal));
32
32
  }
33
33
  else if (code !== 0) {
34
- reject(new Error("Command terminated with non-zero exit code" + code));
34
+ reject(new Error("Command terminated with non-zero exit code " + code));
35
35
  }
36
36
  else {
37
37
  resolve();
@@ -3,25 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const _ = require("lodash");
4
4
  const gcp = require("../../gcp");
5
5
  const rulesDeploy_1 = require("../../rulesDeploy");
6
+ const error_1 = require("../../error");
6
7
  async function default_1(context, options) {
7
8
  let rulesConfig = options.config.get("storage");
8
9
  if (!rulesConfig) {
9
10
  return;
10
11
  }
12
+ const onlyTargets = new Set();
13
+ let allStorage = !options.only;
14
+ if (options.only) {
15
+ const split = options.only.split(",");
16
+ if (split.includes("storage")) {
17
+ allStorage = true;
18
+ }
19
+ else {
20
+ for (const value of split) {
21
+ if (value.startsWith("storage:")) {
22
+ onlyTargets.add(value.split(":")[1]);
23
+ }
24
+ }
25
+ }
26
+ }
11
27
  _.set(context, "storage.rules", rulesConfig);
12
28
  const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
13
29
  _.set(context, "storage.rulesDeploy", rulesDeploy);
14
- if (_.isPlainObject(rulesConfig)) {
30
+ if (!Array.isArray(rulesConfig)) {
15
31
  const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
16
32
  rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
17
33
  _.set(context, "storage.rules", rulesConfig);
18
34
  }
19
- rulesConfig.forEach((ruleConfig) => {
20
- if (ruleConfig.target) {
21
- options.rc.requireTarget(context.projectId, "storage", ruleConfig.target);
35
+ for (const ruleConfig of rulesConfig) {
36
+ const target = ruleConfig.target;
37
+ if (target) {
38
+ options.rc.requireTarget(context.projectId, "storage", target);
22
39
  }
23
- rulesDeploy.addFile(ruleConfig.rules);
24
- });
40
+ if (allStorage || onlyTargets.has(target)) {
41
+ rulesDeploy.addFile(ruleConfig.rules);
42
+ onlyTargets.delete(target);
43
+ }
44
+ }
45
+ if (!allStorage && onlyTargets.size !== 0) {
46
+ throw new error_1.FirebaseError(`Could not find rules for the following storage targets: ${[...onlyTargets].join(", ")}`);
47
+ }
25
48
  await rulesDeploy.compile();
26
49
  }
27
50
  exports.default = default_1;
@@ -20,7 +20,7 @@ async function downloadToTmp(remoteUrl) {
20
20
  resolveOnHTTPError: true,
21
21
  });
22
22
  if (res.status !== 200) {
23
- throw new error_1.FirebaseError(`download failed, status ${res.status}`, { exit: 1 });
23
+ throw new error_1.FirebaseError(`download failed, status ${res.status}: ${await res.response.text()}`);
24
24
  }
25
25
  const total = parseInt(res.response.headers.get("content-length") || "0", 10);
26
26
  const totalMb = Math.ceil(total / 1000000);
@@ -301,7 +301,6 @@ async function startAll(options, showUI = true) {
301
301
  const extensionsBackends = await extensionEmulator.getExtensionBackends();
302
302
  const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
303
303
  emulatableBackends.push(...filteredExtensionsBackends);
304
- void (0, track_1.track)("Emulator Run", types_1.Emulators.EXTENSIONS);
305
304
  await startEmulator(extensionEmulator);
306
305
  }
307
306
  if (emulatableBackends.length) {
@@ -55,7 +55,7 @@ exports.DownloadDetails = {
55
55
  version: "SNAPSHOT",
56
56
  downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
57
57
  unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
58
- binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"),
58
+ binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server", "server.js"),
59
59
  opts: {
60
60
  cacheDir: CACHE_DIR,
61
61
  remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
@@ -67,15 +67,15 @@ exports.DownloadDetails = {
67
67
  },
68
68
  }
69
69
  : {
70
- version: "1.7.0",
71
- downloadPath: path.join(CACHE_DIR, "ui-v1.7.0.zip"),
72
- unzipDir: path.join(CACHE_DIR, "ui-v1.7.0"),
73
- binaryPath: path.join(CACHE_DIR, "ui-v1.7.0", "server.bundle.js"),
70
+ version: "1.8.1",
71
+ downloadPath: path.join(CACHE_DIR, "ui-v1.8.1.zip"),
72
+ unzipDir: path.join(CACHE_DIR, "ui-v1.8.1"),
73
+ binaryPath: path.join(CACHE_DIR, "ui-v1.8.1", "server", "server.js"),
74
74
  opts: {
75
75
  cacheDir: CACHE_DIR,
76
- remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.7.0.zip",
77
- expectedSize: 4053708,
78
- expectedChecksum: "aea9ae19091df5974a88a8847aaf127c",
76
+ remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.8.1.zip",
77
+ expectedSize: 3056552,
78
+ expectedChecksum: "92590fdda20f9883588438d9551111b5",
79
79
  namePrefix: "ui",
80
80
  },
81
81
  },
@@ -164,7 +164,7 @@ const Commands = {
164
164
  },
165
165
  ui: {
166
166
  binary: "node",
167
- args: ["--dns-result-order=ipv4first", getExecPath(types_1.Emulators.UI)],
167
+ args: [getExecPath(types_1.Emulators.UI)],
168
168
  optionalArgs: [],
169
169
  joinArgs: false,
170
170
  },
@@ -272,8 +272,13 @@ class FunctionsEmulator {
272
272
  await runtimeDelegate.build();
273
273
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
274
274
  const environment = Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env);
275
+ const userEnvOpt = {
276
+ functionsSource: emulatableBackend.functionsDir,
277
+ projectId: this.args.projectId,
278
+ projectAlias: this.args.projectAlias,
279
+ };
275
280
  const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
276
- const discoveredBackend = (0, build_1.resolveBackend)(discoveredBuild, environment);
281
+ const discoveredBackend = await (0, build_1.resolveBackend)(discoveredBuild, userEnvOpt, environment);
277
282
  const endpoints = backend.allEndpoints(discoveredBackend);
278
283
  (0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
279
284
  for (const e of endpoints) {
@@ -641,6 +646,9 @@ class FunctionsEmulator {
641
646
  envs.GCLOUD_PROJECT = this.args.projectId;
642
647
  envs.K_REVISION = "1";
643
648
  envs.PORT = "80";
649
+ if (trigger === null || trigger === void 0 ? void 0 : trigger.timeoutSeconds) {
650
+ envs.FUNCTIONS_EMULATOR_TIMEOUT_SECONDS = trigger.timeoutSeconds.toString();
651
+ }
644
652
  if (trigger) {
645
653
  const target = trigger.entryPoint;
646
654
  envs.FUNCTION_TARGET = target;
@@ -104,7 +104,7 @@ class Proxied {
104
104
  return this.proxy;
105
105
  }
106
106
  }
107
- async function resolveDeveloperNodeModule(frb, name) {
107
+ async function resolveDeveloperNodeModule(name) {
108
108
  const pkg = requirePackageJson();
109
109
  if (!pkg) {
110
110
  new types_1.EmulatorLog("SYSTEM", "missing-package-json", "").log();
@@ -130,20 +130,20 @@ async function resolveDeveloperNodeModule(frb, name) {
130
130
  logDebug(`Resolved module ${name}`, moduleResolution);
131
131
  return moduleResolution;
132
132
  }
133
- async function assertResolveDeveloperNodeModule(frb, name) {
134
- const resolution = await resolveDeveloperNodeModule(frb, name);
133
+ async function assertResolveDeveloperNodeModule(name) {
134
+ const resolution = await resolveDeveloperNodeModule(name);
135
135
  if (!(resolution.installed && resolution.declared && resolution.resolution && resolution.version)) {
136
136
  throw new Error(`Assertion failure: could not fully resolve ${name}: ${JSON.stringify(resolution)}`);
137
137
  }
138
138
  return resolution;
139
139
  }
140
- async function verifyDeveloperNodeModules(frb) {
140
+ async function verifyDeveloperNodeModules() {
141
141
  const modBundles = [
142
142
  { name: "firebase-admin", isDev: false, minVersion: "8.9.0" },
143
143
  { name: "firebase-functions", isDev: false, minVersion: "3.13.1" },
144
144
  ];
145
145
  for (const modBundle of modBundles) {
146
- const resolution = await resolveDeveloperNodeModule(frb, modBundle.name);
146
+ const resolution = await resolveDeveloperNodeModule(modBundle.name);
147
147
  if (!resolution.declared) {
148
148
  new types_1.EmulatorLog("SYSTEM", "missing-module", "", modBundle).log();
149
149
  return false;
@@ -240,8 +240,8 @@ function initializeNetworkFiltering() {
240
240
  });
241
241
  logDebug("Outgoing network have been stubbed.", results);
242
242
  }
243
- async function initializeFirebaseFunctionsStubs(frb) {
244
- const firebaseFunctionsResolution = await assertResolveDeveloperNodeModule(frb, "firebase-functions");
243
+ async function initializeFirebaseFunctionsStubs() {
244
+ const firebaseFunctionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
245
245
  const firebaseFunctionsRoot = (0, functionsEmulatorShared_1.findModuleRoot)("firebase-functions", firebaseFunctionsResolution.resolution);
246
246
  const httpsProviderResolution = path.join(firebaseFunctionsRoot, "lib/providers/https");
247
247
  const httpsProviderV1Resolution = path.join(firebaseFunctionsRoot, "lib/v1/providers/https");
@@ -343,10 +343,10 @@ function initializeRuntimeConfig() {
343
343
  }
344
344
  }
345
345
  }
346
- async function initializeFirebaseAdminStubs(frb) {
347
- const adminResolution = await assertResolveDeveloperNodeModule(frb, "firebase-admin");
346
+ async function initializeFirebaseAdminStubs() {
347
+ const adminResolution = await assertResolveDeveloperNodeModule("firebase-admin");
348
348
  const localAdminModule = require(adminResolution.resolution);
349
- const functionsResolution = await assertResolveDeveloperNodeModule(frb, "firebase-functions");
349
+ const functionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
350
350
  const localFunctionsModule = require(functionsResolution.resolution);
351
351
  const defaultConfig = getDefaultConfig();
352
352
  const adminModuleProxy = new Proxied(localAdminModule);
@@ -360,7 +360,7 @@ async function initializeFirebaseAdminStubs(frb) {
360
360
  new types_1.EmulatorLog("SYSTEM", "default-admin-app-used", `config=${defaultAppOptions}`, {
361
361
  opts: defaultAppOptions,
362
362
  }).log();
363
- const defaultApp = makeProxiedFirebaseApp(frb, adminModuleTarget.initializeApp(defaultAppOptions));
363
+ const defaultApp = makeProxiedFirebaseApp(adminModuleTarget.initializeApp(defaultAppOptions));
364
364
  logDebug("initializeApp(DEFAULT)", defaultAppOptions);
365
365
  localFunctionsModule.app.setEmulatedAdminApp(defaultApp);
366
366
  if (process.env[constants_1.Constants.FIREBASE_AUTH_EMULATOR_HOST]) {
@@ -405,7 +405,7 @@ async function initializeFirebaseAdminStubs(frb) {
405
405
  adminResolution,
406
406
  });
407
407
  }
408
- function makeProxiedFirebaseApp(frb, original) {
408
+ function makeProxiedFirebaseApp(original) {
409
409
  const appProxy = new Proxied(original);
410
410
  return appProxy
411
411
  .when("firestore", (target) => {
@@ -450,8 +450,8 @@ function warnAboutStorageProd() {
450
450
  }
451
451
  new types_1.EmulatorLog("WARN_ONCE", "runtime-status", "The Firebase Storage emulator is not running, so calls to Firebase Storage will affect production.").log();
452
452
  }
453
- async function initializeFunctionsConfigHelper(frb) {
454
- const functionsResolution = await assertResolveDeveloperNodeModule(frb, "firebase-functions");
453
+ async function initializeFunctionsConfigHelper() {
454
+ const functionsResolution = await assertResolveDeveloperNodeModule("firebase-functions");
455
455
  const localFunctionsModule = require(functionsResolution.resolution);
456
456
  logDebug("Checked functions.config()", {
457
457
  config: localFunctionsModule.config(),
@@ -487,27 +487,7 @@ function rawBodySaver(req, res, buf) {
487
487
  }
488
488
  async function processHTTPS(trigger) {
489
489
  const ephemeralServer = express();
490
- const functionRouter = express.Router();
491
490
  await new Promise((resolveEphemeralServer, rejectEphemeralServer) => {
492
- const handler = async (req, res) => {
493
- try {
494
- logDebug(`Ephemeral server handling ${req.method} request`);
495
- res.on("finish", () => {
496
- instance.close((err) => {
497
- if (err) {
498
- rejectEphemeralServer(err);
499
- }
500
- else {
501
- resolveEphemeralServer();
502
- }
503
- });
504
- });
505
- await runHTTPS(trigger, [req, res]);
506
- }
507
- catch (err) {
508
- rejectEphemeralServer(err);
509
- }
510
- };
511
491
  ephemeralServer.enable("trust proxy");
512
492
  ephemeralServer.use(bodyParser.json({
513
493
  limit: "10mb",
@@ -527,14 +507,39 @@ async function processHTTPS(trigger) {
527
507
  limit: "10mb",
528
508
  verify: rawBodySaver,
529
509
  }));
530
- functionRouter.all("*", handler);
531
- ephemeralServer.use([`/`, `/*`], functionRouter);
532
- logDebug(`Attempting to listen to port: ${process.env.PORT}`);
533
- const instance = ephemeralServer.listen(process.env.PORT, () => {
534
- logDebug(`Listening to port: ${process.env.PORT}`);
535
- new types_1.EmulatorLog("SYSTEM", "runtime-status", "ready", { state: "ready" }).log();
510
+ let server;
511
+ function closeServer() {
512
+ if (server) {
513
+ server.close((err) => {
514
+ if (err) {
515
+ rejectEphemeralServer(err);
516
+ }
517
+ else {
518
+ resolveEphemeralServer();
519
+ }
520
+ });
521
+ }
522
+ }
523
+ ephemeralServer.get("/__/health", (req, res) => {
524
+ res.status(200).send();
536
525
  });
537
- instance.on("error", rejectEphemeralServer);
526
+ ephemeralServer.all("/favicon.ico|/robots.txt", (req, res) => {
527
+ res.on("finish", closeServer);
528
+ res.status(404).send();
529
+ });
530
+ ephemeralServer.all(`/*`, async (req, res) => {
531
+ try {
532
+ logDebug(`Ephemeral server handling ${req.method} request`);
533
+ res.on("finish", closeServer);
534
+ await runHTTPS(trigger, [req, res]);
535
+ }
536
+ catch (err) {
537
+ rejectEphemeralServer(err);
538
+ }
539
+ });
540
+ server = ephemeralServer.listen(process.env.PORT);
541
+ logDebug(`Listening to port: ${process.env.PORT}`);
542
+ server.on("error", rejectEphemeralServer);
538
543
  });
539
544
  }
540
545
  async function processBackground(trigger, frb, signature) {
@@ -587,7 +592,7 @@ async function runHTTPS(trigger, args) {
587
592
  return trigger(args[0], args[1]);
588
593
  });
589
594
  }
590
- async function moduleResolutionDetective(frb, error) {
595
+ async function moduleResolutionDetective(error) {
591
596
  const clues = {
592
597
  tsconfigJSON: await requireAsync("./tsconfig.json", { paths: [process.cwd()] }).catch(noOp),
593
598
  packageJSON: await requireAsync("./package.json", { paths: [process.cwd()] }).catch(noOp),
@@ -645,7 +650,7 @@ async function invokeTrigger(trigger, frb) {
645
650
  clearInterval(timerId);
646
651
  new types_1.EmulatorLog("INFO", "runtime-status", `Finished "${FUNCTION_TARGET_NAME}" in ~${Math.max(seconds, 1)}s`).log();
647
652
  }
648
- async function initializeRuntime(frb) {
653
+ async function initializeRuntime() {
649
654
  FUNCTION_DEBUG_MODE = process.env.FUNCTION_DEBUG_MODE || "";
650
655
  if (!FUNCTION_DEBUG_MODE) {
651
656
  FUNCTION_TARGET_NAME = process.env.FUNCTION_TARGET || "";
@@ -659,19 +664,18 @@ async function initializeRuntime(frb) {
659
664
  await flushAndExit(1);
660
665
  }
661
666
  }
662
- logDebug(`Disabled runtime features: ${JSON.stringify(frb.disabled_features)}`);
663
- const verified = await verifyDeveloperNodeModules(frb);
667
+ const verified = await verifyDeveloperNodeModules();
664
668
  if (!verified) {
665
669
  new types_1.EmulatorLog("INFO", "runtime-status", `Your functions could not be parsed due to an issue with your node_modules (see above)`).log();
666
670
  return;
667
671
  }
668
672
  initializeRuntimeConfig();
669
673
  initializeNetworkFiltering();
670
- await initializeFunctionsConfigHelper(frb);
671
- await initializeFirebaseFunctionsStubs(frb);
672
- await initializeFirebaseAdminStubs(frb);
674
+ await initializeFunctionsConfigHelper();
675
+ await initializeFirebaseFunctionsStubs();
676
+ await initializeFirebaseAdminStubs();
673
677
  }
674
- async function loadTriggers(frb, serializedFunctionTrigger) {
678
+ async function loadTriggers(serializedFunctionTrigger) {
675
679
  let triggerModule;
676
680
  if (serializedFunctionTrigger) {
677
681
  triggerModule = eval(serializedFunctionTrigger)();
@@ -682,7 +686,7 @@ async function loadTriggers(frb, serializedFunctionTrigger) {
682
686
  }
683
687
  catch (err) {
684
688
  if (err.code !== "ERR_REQUIRE_ESM") {
685
- await moduleResolutionDetective(frb, err);
689
+ await moduleResolutionDetective(err);
686
690
  throw err;
687
691
  }
688
692
  const modulePath = require.resolve(process.cwd());
@@ -712,9 +716,8 @@ async function handleMessage(message) {
712
716
  }
713
717
  if (!functionModule) {
714
718
  try {
715
- await initializeRuntime(runtimeArgs.frb);
716
719
  const serializedTriggers = runtimeArgs.opts ? runtimeArgs.opts.serializedTriggers : undefined;
717
- functionModule = await loadTriggers(runtimeArgs.frb, serializedTriggers);
720
+ functionModule = await loadTriggers(serializedTriggers);
718
721
  }
719
722
  catch (e) {
720
723
  logDebug(e);
@@ -748,7 +751,7 @@ async function handleMessage(message) {
748
751
  await flushAndExit(1);
749
752
  }
750
753
  }
751
- function main() {
754
+ async function main() {
752
755
  let lastSignal = new Date().getTime();
753
756
  let signalCount = 0;
754
757
  process.on("SIGINT", () => {
@@ -762,10 +765,7 @@ function main() {
762
765
  process.exit(1);
763
766
  }
764
767
  });
765
- logDebug("Functions runtime initialized.", {
766
- cwd: process.cwd(),
767
- node_version: process.versions.node,
768
- });
768
+ await initializeRuntime();
769
769
  let messageHandlePromise = Promise.resolve();
770
770
  process.on("message", (message) => {
771
771
  messageHandlePromise = messageHandlePromise
@@ -780,5 +780,15 @@ function main() {
780
780
  });
781
781
  }
782
782
  if (require.main === module) {
783
- main();
783
+ main()
784
+ .then(() => {
785
+ logDebug("Functions runtime initialized.", {
786
+ cwd: process.cwd(),
787
+ node_version: process.versions.node,
788
+ });
789
+ })
790
+ .catch((err) => {
791
+ new types_1.EmulatorLog("FATAL", "runtime-error", err.message || err, err).log();
792
+ return flushAndExit(1);
793
+ });
784
794
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RuntimeWorkerPool = exports.RuntimeWorker = exports.RuntimeWorkerState = void 0;
4
+ const http = require("http");
4
5
  const uuid = require("uuid");
5
6
  const types_1 = require("./types");
6
7
  const events_1 = require("events");
@@ -49,17 +50,11 @@ class RuntimeWorker {
49
50
  return this._state;
50
51
  }
51
52
  set state(state) {
52
- if (state === RuntimeWorkerState.BUSY) {
53
- this.socketReady = types_1.EmulatorLog.waitForLog(this.runtime.events, "SYSTEM", "runtime-status", (el) => {
54
- return el.data.state === "ready";
55
- });
56
- }
57
53
  if (state === RuntimeWorkerState.IDLE) {
58
54
  for (const l of this.logListeners) {
59
55
  this.runtime.events.removeListener("log", l);
60
56
  }
61
57
  this.logListeners = [];
62
- this.socketReady = undefined;
63
58
  }
64
59
  if (state === RuntimeWorkerState.FINISHED) {
65
60
  this.runtime.events.removeAllListeners();
@@ -88,9 +83,40 @@ class RuntimeWorker {
88
83
  this.stateEvents.once(RuntimeWorkerState.FINISHED, listener);
89
84
  });
90
85
  }
91
- waitForSocketReady() {
92
- return (this.socketReady ||
93
- Promise.reject(new Error("Cannot call waitForSocketReady() if runtime is not BUSY")));
86
+ isSocketReady() {
87
+ return new Promise((resolve, reject) => {
88
+ const req = http
89
+ .request({
90
+ method: "GET",
91
+ path: "/__/health",
92
+ socketPath: this.runtime.socketPath,
93
+ }, () => resolve())
94
+ .end();
95
+ req.on("error", (error) => {
96
+ reject(error);
97
+ });
98
+ });
99
+ }
100
+ async waitForSocketReady() {
101
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
102
+ const timeout = new Promise((resolve, reject) => {
103
+ setTimeout(() => {
104
+ reject(new error_1.FirebaseError("Failed to load function."));
105
+ }, 7000);
106
+ });
107
+ while (true) {
108
+ try {
109
+ await Promise.race([this.isSocketReady(), timeout]);
110
+ break;
111
+ }
112
+ catch (err) {
113
+ if (["ECONNREFUSED", "ENOENT"].includes(err === null || err === void 0 ? void 0 : err.code)) {
114
+ await sleep(100);
115
+ continue;
116
+ }
117
+ throw err;
118
+ }
119
+ }
94
120
  }
95
121
  log(msg) {
96
122
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).log("DEBUG", `[worker-${this.key}-${this.id}]: ${msg}`);
@@ -324,7 +324,7 @@ function sendFileBytes(md, data, req, res) {
324
324
  res.setHeader("Accept-Ranges", "bytes");
325
325
  res.setHeader("Content-Type", md.contentType);
326
326
  res.setHeader("Content-Disposition", md.contentDisposition);
327
- res.setHeader("Content-Encoding", md.contentEncoding);
327
+ res.setHeader("Content-Encoding", isGZipped ? "identity" : md.contentEncoding);
328
328
  res.setHeader("ETag", md.etag);
329
329
  res.setHeader("Cache-Control", md.cacheControl);
330
330
  res.setHeader("x-goog-generation", `${md.generation}`);