firebase-tools 10.1.2 → 10.2.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 (65) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +92 -48
  3. package/lib/archiveDirectory.js +63 -73
  4. package/lib/auth.js +62 -25
  5. package/lib/commands/ext-configure.js +1 -0
  6. package/lib/commands/ext-dev-usage.js +3 -8
  7. package/lib/commands/ext-install.js +1 -0
  8. package/lib/commands/ext-uninstall.js +1 -0
  9. package/lib/commands/ext-update.js +1 -0
  10. package/lib/commands/functions-secrets-access.js +17 -0
  11. package/lib/commands/functions-secrets-destroy.js +40 -0
  12. package/lib/commands/functions-secrets-get.js +21 -0
  13. package/lib/commands/functions-secrets-prune.js +50 -0
  14. package/lib/commands/functions-secrets-set.js +46 -0
  15. package/lib/commands/index.js +7 -3
  16. package/lib/commands/login.js +1 -1
  17. package/lib/database/metadata.js +16 -24
  18. package/lib/deploy/functions/backend.js +11 -1
  19. package/lib/deploy/functions/ensure.js +112 -0
  20. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  21. package/lib/deploy/functions/prepare.js +13 -19
  22. package/lib/deploy/functions/release/fabricator.js +4 -1
  23. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -0
  24. package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -0
  25. package/lib/deploy/functions/validate.js +83 -1
  26. package/lib/deploy/hosting/convertConfig.js +45 -24
  27. package/lib/deploy/hosting/prepare.js +1 -1
  28. package/lib/emulator/controller.js +3 -1
  29. package/lib/emulator/emulatorLogger.js +7 -0
  30. package/lib/emulator/functionsEmulator.js +113 -79
  31. package/lib/emulator/functionsEmulatorRuntime.js +100 -83
  32. package/lib/emulator/functionsEmulatorShared.js +51 -1
  33. package/lib/emulator/functionsEmulatorShell.js +1 -2
  34. package/lib/emulator/functionsRuntimeWorker.js +1 -1
  35. package/lib/emulator/storage/apis/gcloud.js +2 -2
  36. package/lib/emulator/storage/files.js +8 -3
  37. package/lib/extensions/askUserForParam.js +1 -1
  38. package/lib/extensions/diagnose.js +56 -0
  39. package/lib/extensions/extensionsApi.js +0 -1
  40. package/lib/extensions/extensionsHelper.js +10 -17
  41. package/lib/extensions/resolveSource.js +1 -53
  42. package/lib/extensions/secretsUtils.js +1 -1
  43. package/lib/extensions/updateHelper.js +0 -14
  44. package/lib/extensions/utils.js +4 -2
  45. package/lib/functions/env.js +5 -7
  46. package/lib/functions/secrets.js +112 -0
  47. package/lib/gcp/cloudbilling.js +8 -19
  48. package/lib/gcp/cloudfunctions.js +24 -48
  49. package/lib/gcp/cloudlogging.js +8 -11
  50. package/lib/gcp/cloudmonitoring.js +8 -5
  51. package/lib/gcp/cloudscheduler.js +7 -18
  52. package/lib/gcp/firedata.js +5 -4
  53. package/lib/gcp/firestore.js +5 -5
  54. package/lib/gcp/iam.js +18 -33
  55. package/lib/gcp/resourceManager.js +8 -13
  56. package/lib/gcp/runtimeconfig.js +31 -53
  57. package/lib/gcp/secretManager.js +137 -77
  58. package/lib/gcp/storage.js +25 -29
  59. package/lib/previews.js +1 -1
  60. package/lib/serve/functions.js +2 -2
  61. package/lib/utils.js +6 -1
  62. package/npm-shrinkwrap.json +962 -987
  63. package/package.json +5 -3
  64. package/schema/firebase-config.json +387 -12
  65. package/templates/init/hosting/index.html +1 -1
@@ -1,11 +1,24 @@
1
1
  "use strict";
2
- const _ = require("lodash");
3
- const { FirebaseError } = require("../../error");
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertConfig = void 0;
4
+ const error_1 = require("../../error");
5
+ function has(obj, k) {
6
+ return obj[k] !== undefined;
7
+ }
4
8
  function extractPattern(type, spec) {
5
- const glob = spec.source || spec.glob;
6
- const regex = spec.regex;
9
+ let glob = "";
10
+ let regex = "";
11
+ if ("source" in spec) {
12
+ glob = spec.source;
13
+ }
14
+ if ("glob" in spec) {
15
+ glob = spec.glob;
16
+ }
17
+ if ("regex" in spec) {
18
+ regex = spec.regex;
19
+ }
7
20
  if (glob && regex) {
8
- throw new FirebaseError("Cannot specify a " + type + " pattern with both a glob and regex.");
21
+ throw new error_1.FirebaseError(`Cannot specify a ${type} pattern with both a glob and regex.`);
9
22
  }
10
23
  else if (glob) {
11
24
  return { glob: glob };
@@ -13,33 +26,38 @@ function extractPattern(type, spec) {
13
26
  else if (regex) {
14
27
  return { regex: regex };
15
28
  }
16
- throw new FirebaseError("Cannot specify a " + type + " with no pattern (either a glob or regex required).");
29
+ throw new error_1.FirebaseError(`Cannot specify a ${type} with no pattern (either a glob or regex required).`);
17
30
  }
18
- module.exports = function (config) {
31
+ function convertConfig(config) {
32
+ if (Array.isArray(config)) {
33
+ throw new error_1.FirebaseError(`convertConfig should be given a single configuration, not an array.`, {
34
+ exit: 2,
35
+ });
36
+ }
19
37
  const out = {};
20
38
  if (!config) {
21
39
  return out;
22
40
  }
23
- if (_.isArray(config.rewrites)) {
24
- out.rewrites = config.rewrites.map(function (rewrite) {
41
+ if (Array.isArray(config.rewrites)) {
42
+ out.rewrites = config.rewrites.map((rewrite) => {
25
43
  const vRewrite = extractPattern("rewrite", rewrite);
26
- if (rewrite.destination) {
44
+ if ("destination" in rewrite) {
27
45
  vRewrite.path = rewrite.destination;
28
46
  }
29
- else if (rewrite.function) {
47
+ else if ("function" in rewrite) {
30
48
  vRewrite.function = rewrite.function;
31
49
  }
32
- else if (rewrite.dynamicLinks) {
50
+ else if ("dynamicLinks" in rewrite) {
33
51
  vRewrite.dynamicLinks = rewrite.dynamicLinks;
34
52
  }
35
- else if (rewrite.run) {
53
+ else if ("run" in rewrite) {
36
54
  vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run);
37
55
  }
38
56
  return vRewrite;
39
57
  });
40
58
  }
41
- if (_.isArray(config.redirects)) {
42
- out.redirects = config.redirects.map(function (redirect) {
59
+ if (Array.isArray(config.redirects)) {
60
+ out.redirects = config.redirects.map((redirect) => {
43
61
  const vRedirect = extractPattern("redirect", redirect);
44
62
  vRedirect.location = redirect.destination;
45
63
  if (redirect.type) {
@@ -48,17 +66,19 @@ module.exports = function (config) {
48
66
  return vRedirect;
49
67
  });
50
68
  }
51
- if (_.isArray(config.headers)) {
52
- out.headers = config.headers.map(function (header) {
69
+ if (Array.isArray(config.headers)) {
70
+ out.headers = config.headers.map((header) => {
53
71
  const vHeader = extractPattern("header", header);
54
72
  vHeader.headers = {};
55
- (header.headers || []).forEach(function (h) {
56
- vHeader.headers[h.key] = h.value;
57
- });
73
+ if (Array.isArray(header.headers) && header.headers.length) {
74
+ header.headers.forEach((h) => {
75
+ vHeader.headers[h.key] = h.value;
76
+ });
77
+ }
58
78
  return vHeader;
59
79
  });
60
80
  }
61
- if (_.has(config, "cleanUrls")) {
81
+ if (has(config, "cleanUrls")) {
62
82
  out.cleanUrls = config.cleanUrls;
63
83
  }
64
84
  if (config.trailingSlash === true) {
@@ -67,11 +87,12 @@ module.exports = function (config) {
67
87
  else if (config.trailingSlash === false) {
68
88
  out.trailingSlashBehavior = "REMOVE";
69
89
  }
70
- if (_.has(config, "appAssociation")) {
90
+ if (has(config, "appAssociation")) {
71
91
  out.appAssociation = config.appAssociation;
72
92
  }
73
- if (_.has(config, "i18n")) {
93
+ if (has(config, "i18n")) {
74
94
  out.i18n = config.i18n;
75
95
  }
76
96
  return out;
77
- };
97
+ }
98
+ exports.convertConfig = convertConfig;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const _ = require("lodash");
3
3
  const api = require("../../api");
4
- const convertConfig = require("./convertConfig");
4
+ const { convertConfig } = require("./convertConfig");
5
5
  const deploymentTool = require("../../deploymentTool");
6
6
  const { FirebaseError } = require("../../error");
7
7
  const { normalizedHostingConfigs } = require("../../hosting/normalizedHostingConfigs");
@@ -246,7 +246,8 @@ async function startAll(options, showUI = true) {
246
246
  utils.assertDefined(options.config.src.functions);
247
247
  utils.assertDefined(options.config.src.functions.source, "Error: 'functions.source' is not defined");
248
248
  utils.assertIsStringOrUndefined(options.extensionDir);
249
- const functionsDir = path.join(options.extensionDir || options.config.projectDir, options.config.src.functions.source);
249
+ const projectDir = options.extensionDir || options.config.projectDir;
250
+ const functionsDir = path.join(projectDir, options.config.src.functions.source);
250
251
  let inspectFunctions;
251
252
  if (options.inspectFunctions) {
252
253
  inspectFunctions = commandUtils.parseInspectionPort(options);
@@ -269,6 +270,7 @@ async function startAll(options, showUI = true) {
269
270
  ];
270
271
  const functionsEmulator = new functionsEmulator_1.FunctionsEmulator({
271
272
  projectId,
273
+ projectDir,
272
274
  emulatableBackends,
273
275
  account,
274
276
  host: functionsAddr.host,
@@ -13,6 +13,7 @@ const TYPE_VERBOSITY = {
13
13
  USER: 2,
14
14
  WARN: 2,
15
15
  WARN_ONCE: 2,
16
+ ERROR: 2,
16
17
  };
17
18
  var Verbosity;
18
19
  (function (Verbosity) {
@@ -79,6 +80,9 @@ class EmulatorLogger {
79
80
  case "SUCCESS":
80
81
  utils.logSuccess(text, "info", mergedData);
81
82
  break;
83
+ case "ERROR":
84
+ utils.logBullet(text, "error", mergedData);
85
+ break;
82
86
  }
83
87
  }
84
88
  handleRuntimeLog(log, ignore = []) {
@@ -196,6 +200,9 @@ You can probably fix this by running "npm install ${systemLog.data.name}@latest"
196
200
  EmulatorLogger.warnOnceCache.add(text);
197
201
  }
198
202
  break;
203
+ case "ERROR":
204
+ utils.logLabeledError(label, text, "error", mergedData);
205
+ break;
199
206
  }
200
207
  }
201
208
  static shouldSupress(type) {
@@ -27,10 +27,14 @@ const workQueue_1 = require("./workQueue");
27
27
  const utils_1 = require("../utils");
28
28
  const defaultCredentials_1 = require("../defaultCredentials");
29
29
  const adminSdkConfig_1 = require("./adminSdkConfig");
30
- const functionsEnv = require("../functions/env");
31
30
  const types_2 = require("./events/types");
32
31
  const validate_1 = require("../deploy/functions/validate");
32
+ const runtimes_1 = require("../deploy/functions/runtimes");
33
+ const backend = require("../deploy/functions/backend");
34
+ const functionsEnv = require("../functions/env");
35
+ const secretManager_1 = require("../gcp/secretManager");
33
36
  const EVENT_INVOKE = "functions:invoke";
37
+ const LOCAL_SECRETS_FILE = ".secret.local";
34
38
  const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
35
39
  class FunctionsEmulator {
36
40
  constructor(args) {
@@ -44,9 +48,7 @@ class FunctionsEmulator {
44
48
  this.args.disabledRuntimeFeatures = this.args.disabledRuntimeFeatures || {};
45
49
  this.args.disabledRuntimeFeatures.timeout = true;
46
50
  }
47
- this.adminSdkConfig = {
48
- projectId: this.args.projectId,
49
- };
51
+ this.adminSdkConfig = Object.assign(Object.assign({}, this.args.adminSdkConfig), { projectId: this.args.projectId });
50
52
  const mode = this.args.debugPort
51
53
  ? types_1.FunctionsExecutionMode.SEQUENTIAL
52
54
  : types_1.FunctionsExecutionMode.AUTO;
@@ -159,25 +161,17 @@ class FunctionsEmulator {
159
161
  });
160
162
  return hub;
161
163
  }
162
- startFunctionRuntime(backend, triggerId, targetName, signatureType, proto, runtimeOpts) {
163
- const bundleTemplate = this.getBaseBundle(backend);
164
- const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { emulators: {
165
- firestore: this.getEmulatorInfo(types_1.Emulators.FIRESTORE),
166
- database: this.getEmulatorInfo(types_1.Emulators.DATABASE),
167
- pubsub: this.getEmulatorInfo(types_1.Emulators.PUBSUB),
168
- auth: this.getEmulatorInfo(types_1.Emulators.AUTH),
169
- storage: this.getEmulatorInfo(types_1.Emulators.STORAGE),
170
- }, nodeMajorVersion: backend.nodeMajorVersion, proto,
171
- triggerId,
172
- targetName });
164
+ async startFunctionRuntime(backend, trigger, proto, runtimeOpts) {
165
+ const bundleTemplate = this.getBaseBundle();
166
+ const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { proto });
173
167
  if (!backend.nodeBinary) {
174
- throw new error_1.FirebaseError(`No node binary for ${triggerId}. This should never happen.`);
168
+ throw new error_1.FirebaseError(`No node binary for ${trigger.id}. This should never happen.`);
175
169
  }
176
170
  const opts = runtimeOpts || {
177
171
  nodeBinary: backend.nodeBinary,
178
172
  extensionTriggers: backend.predefinedTriggers,
179
173
  };
180
- const worker = this.invokeRuntime(runtimeBundle, opts, this.getRuntimeEnvs(backend, { targetName, signatureType }));
174
+ const worker = await this.invokeRuntime(backend, trigger, runtimeBundle, opts);
181
175
  return worker;
182
176
  }
183
177
  async start() {
@@ -188,13 +182,15 @@ class FunctionsEmulator {
188
182
  for (const e of this.args.emulatableBackends) {
189
183
  e.env = Object.assign(Object.assign({}, credentialEnv), e.env);
190
184
  }
191
- const adminSdkConfig = await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(this.args.projectId);
192
- if (adminSdkConfig) {
193
- this.adminSdkConfig = adminSdkConfig;
194
- }
195
- else {
196
- this.logger.logLabeled("WARN", "functions", "Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect.");
197
- this.adminSdkConfig = (0, adminSdkConfig_1.constructDefaultAdminSdkConfig)(this.args.projectId);
185
+ if (Object.keys(this.adminSdkConfig || {}).length <= 1) {
186
+ const adminSdkConfig = await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(this.args.projectId);
187
+ if (adminSdkConfig) {
188
+ this.adminSdkConfig = adminSdkConfig;
189
+ }
190
+ else {
191
+ this.logger.logLabeled("WARN", "functions", "Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect.");
192
+ this.adminSdkConfig = (0, adminSdkConfig_1.constructDefaultAdminSdkConfig)(this.args.projectId);
193
+ }
198
194
  }
199
195
  const { host, port } = this.getInfo();
200
196
  this.workQueue.start();
@@ -242,14 +238,25 @@ class FunctionsEmulator {
242
238
  if (!emulatableBackend.nodeBinary) {
243
239
  throw new error_1.FirebaseError(`No node binary for ${emulatableBackend.functionsDir}. This should never happen.`);
244
240
  }
245
- const worker = this.invokeRuntime(this.getBaseBundle(emulatableBackend), {
246
- nodeBinary: emulatableBackend.nodeBinary,
247
- extensionTriggers: emulatableBackend.predefinedTriggers,
248
- }, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env));
249
- const triggerParseEvent = await types_1.EmulatorLog.waitForLog(worker.runtime.events, "SYSTEM", "triggers-parsed");
250
- const parsedDefinitions = triggerParseEvent.data
251
- .triggerDefinitions;
252
- const triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsByRegion)(parsedDefinitions);
241
+ let triggerDefinitions;
242
+ if (emulatableBackend.predefinedTriggers) {
243
+ triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsByRegion)(emulatableBackend.predefinedTriggers);
244
+ }
245
+ else {
246
+ const runtimeDelegate = await (0, runtimes_1.getRuntimeDelegate)({
247
+ projectId: this.args.projectId,
248
+ projectDir: this.args.projectDir,
249
+ sourceDir: emulatableBackend.functionsDir,
250
+ });
251
+ logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
252
+ await runtimeDelegate.validate();
253
+ logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
254
+ await runtimeDelegate.build();
255
+ logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
256
+ const discoveredBackend = await runtimeDelegate.discoverSpec({}, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env));
257
+ const endpoints = backend.allEndpoints(discoveredBackend);
258
+ triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
259
+ }
253
260
  const toSetup = triggerDefinitions.filter((definition) => {
254
261
  if (force) {
255
262
  return true;
@@ -305,7 +312,7 @@ class FunctionsEmulator {
305
312
  }
306
313
  }
307
314
  else {
308
- this.logger.log("WARN", `Trigger trigger "${definition.name}" has has neither "httpsTrigger" or "eventTrigger" member`);
315
+ this.logger.log("WARN", `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.`);
309
316
  }
310
317
  const ignored = !added;
311
318
  this.addTriggerRecord(definition, { backend: emulatableBackend, ignored, url });
@@ -331,7 +338,7 @@ class FunctionsEmulator {
331
338
  }
332
339
  const result = DATABASE_PATH_PATTERN.exec(eventTrigger.resource);
333
340
  if (result === null || result.length !== 3) {
334
- this.logger.log("WARN", `Event trigger "${key}" has malformed "resource" member. ` + `${eventTrigger.resource}`);
341
+ this.logger.log("WARN", `Event function "${key}" has malformed "resource" member. ` + `${eventTrigger.resource}`);
335
342
  return Promise.reject();
336
343
  }
337
344
  const instance = result[1];
@@ -347,7 +354,7 @@ class FunctionsEmulator {
347
354
  setTriggersPath += `?ns=${instance}`;
348
355
  }
349
356
  else {
350
- this.logger.log("WARN", `No project in use. Registering function trigger for sentinel namespace '${constants_1.Constants.DEFAULT_DATABASE_EMULATOR_NAMESPACE}'`);
357
+ this.logger.log("WARN", `No project in use. Registering function for sentinel namespace '${constants_1.Constants.DEFAULT_DATABASE_EMULATOR_NAMESPACE}'`);
351
358
  }
352
359
  return api
353
360
  .request("POST", setTriggersPath, {
@@ -362,7 +369,7 @@ class FunctionsEmulator {
362
369
  return true;
363
370
  })
364
371
  .catch((err) => {
365
- this.logger.log("WARN", "Error adding trigger: " + err);
372
+ this.logger.log("WARN", "Error adding Realtime Database function: " + err);
366
373
  throw err;
367
374
  });
368
375
  }
@@ -371,7 +378,9 @@ class FunctionsEmulator {
371
378
  if (!firestoreEmu) {
372
379
  return Promise.resolve(false);
373
380
  }
374
- const bundle = JSON.stringify({ eventTrigger });
381
+ const bundle = JSON.stringify({
382
+ eventTrigger: Object.assign(Object.assign({}, eventTrigger), { service: "firestore.googleapis.com" }),
383
+ });
375
384
  logger_1.logger.debug(`addFirestoreTrigger`, JSON.stringify(bundle));
376
385
  return api
377
386
  .request("PUT", `/emulator/v1/projects/${projectId}/triggers/${key}`, {
@@ -383,7 +392,7 @@ class FunctionsEmulator {
383
392
  return true;
384
393
  })
385
394
  .catch((err) => {
386
- this.logger.log("WARN", "Error adding trigger: " + err);
395
+ this.logger.log("WARN", "Error adding firestore function: " + err);
387
396
  throw err;
388
397
  });
389
398
  }
@@ -452,7 +461,7 @@ class FunctionsEmulator {
452
461
  const record = this.triggers[triggerKey];
453
462
  if (!record) {
454
463
  logger_1.logger.debug(`Could not find key=${triggerKey} in ${JSON.stringify(this.triggers)}`);
455
- throw new error_1.FirebaseError(`No trigger with key ${triggerKey}`);
464
+ throw new error_1.FirebaseError(`No function with key ${triggerKey}`);
456
465
  }
457
466
  return record;
458
467
  }
@@ -475,30 +484,12 @@ class FunctionsEmulator {
475
484
  setTriggersForTesting(triggers, backend) {
476
485
  triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
477
486
  }
478
- getBaseBundle(backend) {
487
+ getBaseBundle() {
479
488
  return {
480
- cwd: backend.functionsDir,
481
- projectId: this.args.projectId,
482
- triggerId: "",
483
- targetName: "",
484
- emulators: {
485
- firestore: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FIRESTORE),
486
- database: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.DATABASE),
487
- pubsub: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.PUBSUB),
488
- auth: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.AUTH),
489
- storage: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE),
490
- },
491
- adminSdkConfig: {
492
- databaseURL: this.adminSdkConfig.databaseURL,
493
- storageBucket: this.adminSdkConfig.storageBucket,
494
- },
489
+ proto: {},
495
490
  disabled_features: this.args.disabledRuntimeFeatures,
496
491
  };
497
492
  }
498
- getRequestedNodeRuntimeVersion(frb) {
499
- const pkg = require(path.join(frb.cwd, "package.json"));
500
- return frb.nodeMajorVersion || (pkg.engines && pkg.engines.node);
501
- }
502
493
  getNodeBinary(backend) {
503
494
  const pkg = require(path.join(backend.functionsDir, "package.json"));
504
495
  if ((!pkg.engines || !pkg.engines.node) && !backend.nodeMajorVersion) {
@@ -525,7 +516,9 @@ class FunctionsEmulator {
525
516
  if (requestedMajorVersion === hostMajorVersion) {
526
517
  this.logger.logLabeled("SUCCESS", "functions", `Using node@${requestedMajorVersion} from host.`);
527
518
  }
528
- this.logger.log("WARN", `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}"`);
519
+ else {
520
+ this.logger.log("WARN", `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}". Using node@${hostMajorVersion} from host.`);
521
+ }
529
522
  return process.execPath;
530
523
  }
531
524
  getUserEnvs(backend) {
@@ -544,16 +537,16 @@ class FunctionsEmulator {
544
537
  }
545
538
  return {};
546
539
  }
547
- getSystemEnvs(triggerDef) {
540
+ getSystemEnvs(trigger) {
548
541
  const envs = {};
549
542
  envs.GCLOUD_PROJECT = this.args.projectId;
550
543
  envs.K_REVISION = "1";
551
544
  envs.PORT = "80";
552
- if (triggerDef) {
553
- const service = triggerDef.targetName;
545
+ if (trigger) {
546
+ const service = trigger.name;
554
547
  const target = service.replace(/-/g, ".");
555
548
  envs.FUNCTION_TARGET = target;
556
- envs.FUNCTION_SIGNATURE_TYPE = triggerDef.signatureType;
549
+ envs.FUNCTION_SIGNATURE_TYPE = (0, functionsEmulatorShared_1.getSignatureType)(trigger);
557
550
  envs.K_SERVICE = service;
558
551
  }
559
552
  return envs;
@@ -605,12 +598,50 @@ class FunctionsEmulator {
605
598
  projectId: this.args.projectId,
606
599
  });
607
600
  }
608
- getRuntimeEnvs(backend, triggerDef) {
609
- return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this.getUserEnvs(backend)), this.getSystemEnvs(triggerDef)), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), backend.env);
601
+ getRuntimeEnvs(backend, trigger) {
602
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this.getUserEnvs(backend)), this.getSystemEnvs(trigger)), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), backend.env);
603
+ }
604
+ async resolveSecretEnvs(backend, trigger) {
605
+ let secretEnvs = {};
606
+ try {
607
+ const data = fs.readFileSync(path.join(backend.functionsDir, LOCAL_SECRETS_FILE), "utf8");
608
+ secretEnvs = functionsEnv.parseStrict(data);
609
+ }
610
+ catch (e) {
611
+ if (e.code !== "ENOENT") {
612
+ this.logger.logLabeled("ERROR", "functions", `Failed to read local secrets file ${LOCAL_SECRETS_FILE}: ${e.message}`);
613
+ }
614
+ }
615
+ const secrets = trigger.secretEnvironmentVariables || [];
616
+ const accesses = secrets
617
+ .filter((s) => !secretEnvs[s.secret])
618
+ .map(async (s) => {
619
+ this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.key}@latest`);
620
+ const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.key, "latest");
621
+ return [s.secret, value];
622
+ });
623
+ const accessResults = await (0, utils_1.allSettled)(accesses);
624
+ const errs = [];
625
+ for (const result of accessResults) {
626
+ if (result.status === "rejected") {
627
+ errs.push(result.reason);
628
+ }
629
+ else {
630
+ const [k, v] = result.value;
631
+ secretEnvs[k] = v;
632
+ }
633
+ }
634
+ if (errs.length > 0) {
635
+ this.logger.logLabeled("ERROR", "functions", "Unable to access secret environment variables from Google Cloud Secret Manager. " +
636
+ "Make sure the credential used for the Functions Emulator have access " +
637
+ `or provide override values in ${LOCAL_SECRETS_FILE}:\n\t` +
638
+ errs.join("\n\t"));
639
+ }
640
+ return secretEnvs;
610
641
  }
611
- invokeRuntime(frb, opts, runtimeEnv) {
612
- if (this.workerPool.readyForWork(frb.triggerId)) {
613
- return this.workerPool.submitWork(frb.triggerId, frb, opts);
642
+ async invokeRuntime(backend, trigger, frb, opts) {
643
+ if (this.workerPool.readyForWork(trigger.id)) {
644
+ return this.workerPool.submitWork(trigger.id, frb, opts);
614
645
  }
615
646
  const emitter = new events_1.EventEmitter();
616
647
  const args = [path.join(__dirname, "functionsEmulatorRuntime")];
@@ -619,7 +650,7 @@ class FunctionsEmulator {
619
650
  }
620
651
  if (this.args.debugPort) {
621
652
  if (process.env.FIREPIT_VERSION && process.execPath == opts.nodeBinary) {
622
- const requestedMajorNodeVersion = this.getRequestedNodeRuntimeVersion(frb);
653
+ const requestedMajorNodeVersion = this.getNodeBinary(backend);
623
654
  this.logger.log("WARN", `To enable function inspection, please run "${process.execPath} is:npm i node@${requestedMajorNodeVersion} --save-dev" in your functions directory`);
624
655
  }
625
656
  else {
@@ -627,15 +658,17 @@ class FunctionsEmulator {
627
658
  args.unshift(`--inspect=${host}:${this.args.debugPort}`);
628
659
  }
629
660
  }
630
- const pnpPath = path.join(frb.cwd, ".pnp.js");
661
+ const pnpPath = path.join(backend.functionsDir, ".pnp.js");
631
662
  if (fs.existsSync(pnpPath)) {
632
663
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).logLabeled("WARN_ONCE", "functions", "Detected yarn@2 with PnP. " +
633
664
  "Cloud Functions for Firebase requires a node_modules folder to work correctly and is therefore incompatible with PnP. " +
634
665
  "See https://yarnpkg.com/getting-started/migration#step-by-step for more information.");
635
666
  }
667
+ const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
668
+ const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
636
669
  const childProcess = spawn(opts.nodeBinary, args, {
637
- env: Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), (runtimeEnv !== null && runtimeEnv !== void 0 ? runtimeEnv : {})),
638
- cwd: frb.cwd,
670
+ cwd: backend.functionsDir,
671
+ env: Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs),
639
672
  stdio: ["pipe", "pipe", "pipe", "ipc"],
640
673
  });
641
674
  if (!childProcess.stderr) {
@@ -666,6 +699,7 @@ class FunctionsEmulator {
666
699
  childProcess.on("exit", resolve);
667
700
  }),
668
701
  events: emitter,
702
+ cwd: backend.functionsDir,
669
703
  shutdown: () => {
670
704
  childProcess.kill();
671
705
  },
@@ -677,8 +711,8 @@ class FunctionsEmulator {
677
711
  return childProcess.send(JSON.stringify(args));
678
712
  },
679
713
  };
680
- this.workerPool.addWorker(frb.triggerId, runtime);
681
- return this.workerPool.submitWork(frb.triggerId, frb, opts);
714
+ this.workerPool.addWorker(trigger.id, runtime);
715
+ return this.workerPool.submitWork(trigger.id, frb, opts);
682
716
  }
683
717
  async disableBackgroundTriggers() {
684
718
  Object.values(this.triggers).forEach((record) => {
@@ -704,7 +738,7 @@ class FunctionsEmulator {
704
738
  }
705
739
  const trigger = record.def;
706
740
  const service = (0, functionsEmulatorShared_1.getFunctionService)(trigger);
707
- const worker = this.startFunctionRuntime(record.backend, trigger.id, trigger.name, (0, functionsEmulatorShared_1.getSignatureType)(trigger), proto);
741
+ const worker = await this.startFunctionRuntime(record.backend, trigger, proto);
708
742
  return new Promise((resolve, reject) => {
709
743
  if (projectId !== this.args.projectId) {
710
744
  if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
@@ -738,7 +772,7 @@ class FunctionsEmulator {
738
772
  return registry_1.EmulatorRegistry.getInfo(emulator);
739
773
  }
740
774
  tokenFromAuthHeader(authHeader) {
741
- const match = authHeader.match(/^Bearer (.*)$/);
775
+ const match = /^Bearer (.*)$/.exec(authHeader);
742
776
  if (!match) {
743
777
  return;
744
778
  }
@@ -770,7 +804,7 @@ class FunctionsEmulator {
770
804
  if (!this.triggers[triggerId]) {
771
805
  res
772
806
  .status(404)
773
- .send(`Function ${triggerId} does not exist, valid triggers are: ${Object.keys(this.triggers).join(", ")}`);
807
+ .send(`Function ${triggerId} does not exist, valid functions are: ${Object.keys(this.triggers).join(", ")}`);
774
808
  return;
775
809
  }
776
810
  const record = this.getTriggerRecordByKey(triggerId);
@@ -791,7 +825,7 @@ class FunctionsEmulator {
791
825
  req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
792
826
  }
793
827
  }
794
- const worker = this.startFunctionRuntime(record.backend, trigger.id, trigger.name, "http", undefined);
828
+ const worker = await this.startFunctionRuntime(record.backend, trigger);
795
829
  worker.onLogs((el) => {
796
830
  if (el.level === "FATAL") {
797
831
  res.status(500).send(el.text);