firebase-tools 11.8.1 → 11.10.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 (37) hide show
  1. package/lib/bin/firebase.js +5 -5
  2. package/lib/commands/ext-install.js +2 -2
  3. package/lib/crashlytics/buildToolsJarHelper.js +1 -1
  4. package/lib/deploy/functions/build.js +3 -4
  5. package/lib/deploy/functions/cache/applyHash.js +2 -9
  6. package/lib/deploy/functions/cel.js +86 -47
  7. package/lib/deploy/functions/deploy.js +21 -2
  8. package/lib/deploy/functions/params.js +35 -8
  9. package/lib/deploy/functions/prepare.js +13 -2
  10. package/lib/deploy/functions/prepareFunctionsUpload.js +2 -2
  11. package/lib/deploy/functions/release/planner.js +1 -0
  12. package/lib/deploy/functions/services/index.js +11 -0
  13. package/lib/deploy/functions/services/remoteConfig.js +14 -0
  14. package/lib/emulator/auth/server.js +6 -0
  15. package/lib/emulator/auth/state.js +3 -1
  16. package/lib/emulator/downloadableEmulators.js +10 -10
  17. package/lib/emulator/functionsEmulator.js +71 -171
  18. package/lib/emulator/functionsEmulatorRuntime.js +104 -142
  19. package/lib/emulator/functionsEmulatorShared.js +3 -0
  20. package/lib/emulator/functionsEmulatorShell.js +1 -1
  21. package/lib/emulator/functionsRuntimeWorker.js +55 -36
  22. package/lib/emulator/storage/files.js +5 -5
  23. package/lib/emulator/storage/rules/runtime.js +41 -6
  24. package/lib/emulator/storage/rules/types.js +7 -1
  25. package/lib/emulator/storage/rules/utils.js +3 -1
  26. package/lib/emulator/storage/server.js +6 -0
  27. package/lib/ensureApiEnabled.js +2 -0
  28. package/lib/extensions/askUserForConsent.js +1 -38
  29. package/lib/extensions/displayExtensionInfo.js +27 -2
  30. package/lib/extensions/updateHelper.js +3 -35
  31. package/lib/functions/events/v2.js +2 -1
  32. package/lib/gcp/cloudfunctionsv2.js +5 -2
  33. package/lib/previews.js +1 -1
  34. package/lib/rulesDeploy.js +1 -2
  35. package/lib/serve/hosting.js +1 -1
  36. package/npm-shrinkwrap.json +69 -9
  37. package/package.json +3 -2
@@ -25,7 +25,6 @@ const workQueue_1 = require("./workQueue");
25
25
  const utils_1 = require("../utils");
26
26
  const defaultCredentials_1 = require("../defaultCredentials");
27
27
  const adminSdkConfig_1 = require("./adminSdkConfig");
28
- const types_2 = require("./events/types");
29
28
  const validate_1 = require("../deploy/functions/validate");
30
29
  const secretManager_1 = require("../gcp/secretManager");
31
30
  const runtimes = require("../deploy/functions/runtimes");
@@ -99,32 +98,6 @@ class FunctionsEmulator {
99
98
  const multicastFunctionRoute = `/functions/projects/:project_id/trigger_multicast`;
100
99
  const httpsFunctionRoutes = [httpsFunctionRoute, `${httpsFunctionRoute}/*`];
101
100
  const listBackendsRoute = `/backends`;
102
- const backgroundHandler = (req, res) => {
103
- var _a;
104
- const triggerId = req.params.trigger_name;
105
- const projectId = req.params.project_id;
106
- const reqBody = req.rawBody;
107
- let proto = JSON.parse(reqBody.toString());
108
- if ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.includes("cloudevent")) {
109
- if (types_2.EventUtils.isBinaryCloudEvent(req)) {
110
- proto = types_2.EventUtils.extractBinaryCloudEventContext(req);
111
- proto.data = req.body;
112
- }
113
- }
114
- this.workQueue.submit(() => {
115
- this.logger.log("DEBUG", `Accepted request ${req.method} ${req.url} --> ${triggerId}`);
116
- return this.handleBackgroundTrigger(projectId, triggerId, proto)
117
- .then((x) => res.json(x))
118
- .catch((errorBundle) => {
119
- if (errorBundle.body) {
120
- res.status(errorBundle.code).send(errorBundle.body);
121
- }
122
- else {
123
- res.sendStatus(errorBundle.code);
124
- }
125
- });
126
- });
127
- };
128
101
  const httpsHandler = (req, res) => {
129
102
  this.workQueue.submit(() => {
130
103
  return this.handleHttpsTrigger(req, res);
@@ -133,27 +106,34 @@ class FunctionsEmulator {
133
106
  const multicastHandler = (req, res) => {
134
107
  var _a;
135
108
  const projectId = req.params.project_id;
136
- const reqBody = req.rawBody;
137
- let proto = JSON.parse(reqBody.toString());
109
+ const rawBody = req.rawBody;
110
+ const event = JSON.parse(rawBody.toString());
138
111
  let triggerKey;
139
112
  if ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.includes("cloudevent")) {
140
- triggerKey = `${this.args.projectId}:${proto.type}`;
141
- if (types_2.EventUtils.isBinaryCloudEvent(req)) {
142
- proto = types_2.EventUtils.extractBinaryCloudEventContext(req);
143
- proto.data = req.body;
144
- }
113
+ triggerKey = `${this.args.projectId}:${event.type}`;
145
114
  }
146
115
  else {
147
- triggerKey = `${this.args.projectId}:${proto.eventType}`;
116
+ triggerKey = `${this.args.projectId}:${event.eventType}`;
148
117
  }
149
- if (proto.data.bucket) {
150
- triggerKey += `:${proto.data.bucket}`;
118
+ if (event.data.bucket) {
119
+ triggerKey += `:${event.data.bucket}`;
151
120
  }
152
121
  const triggers = this.multicastTriggers[triggerKey] || [];
122
+ const { host, port } = this.getInfo();
153
123
  triggers.forEach((triggerId) => {
154
124
  this.workQueue.submit(() => {
155
- this.logger.log("DEBUG", `Accepted multicast request ${req.method} ${req.url} --> ${triggerId}`);
156
- return this.handleBackgroundTrigger(projectId, triggerId, proto);
125
+ return new Promise((resolve, reject) => {
126
+ const trigReq = http.request({
127
+ host,
128
+ port,
129
+ method: req.method,
130
+ path: `/functions/projects/${projectId}/triggers/${triggerId}`,
131
+ headers: req.headers,
132
+ }, resolve);
133
+ trigReq.on("error", reject);
134
+ trigReq.write(rawBody);
135
+ trigReq.end();
136
+ });
157
137
  });
158
138
  });
159
139
  res.json({ status: "multicast_acknowledged" });
@@ -162,7 +142,7 @@ class FunctionsEmulator {
162
142
  res.json({ backends: this.getBackendInfo() });
163
143
  };
164
144
  hub.get(listBackendsRoute, cors({ origin: true }), listBackendsHandler);
165
- hub.post(backgroundFunctionRoute, dataMiddleware, backgroundHandler);
145
+ hub.post(backgroundFunctionRoute, dataMiddleware, httpsHandler);
166
146
  hub.post(multicastFunctionRoute, dataMiddleware, multicastHandler);
167
147
  hub.all(httpsFunctionRoutes, dataMiddleware, httpsHandler);
168
148
  hub.all("*", dataMiddleware, (req, res) => {
@@ -171,26 +151,28 @@ class FunctionsEmulator {
171
151
  });
172
152
  return hub;
173
153
  }
174
- async invokeTrigger(trigger, proto, runtimeOpts) {
154
+ async sendRequest(trigger, body) {
175
155
  const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger));
176
- const backend = record.backend;
177
- const bundleTemplate = this.getBaseBundle();
178
- const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { proto });
179
- if (this.args.debugPort) {
180
- runtimeBundle.debug = {
181
- functionTarget: trigger.entryPoint,
182
- functionSignature: (0, functionsEmulatorShared_1.getSignatureType)(trigger),
183
- };
184
- }
185
- if (!backend.nodeBinary) {
186
- throw new error_1.FirebaseError(`No node binary for ${trigger.id}. This should never happen.`);
156
+ const pool = this.workerPools[record.backend.codebase];
157
+ if (!pool.readyForWork(trigger.id)) {
158
+ await this.startRuntime(record.backend, trigger);
187
159
  }
188
- const opts = runtimeOpts || {
189
- nodeBinary: backend.nodeBinary,
190
- extensionTriggers: backend.predefinedTriggers,
160
+ const worker = pool.getIdleWorker(trigger.id);
161
+ const reqBody = JSON.stringify(body);
162
+ const headers = {
163
+ "Content-Type": "application/json",
164
+ "Content-Length": `${reqBody.length}`,
191
165
  };
192
- const worker = await this.invokeRuntime(backend, trigger, runtimeBundle, opts);
193
- return worker;
166
+ return new Promise((resolve, reject) => {
167
+ const req = http.request({
168
+ path: `/`,
169
+ socketPath: worker.runtime.socketPath,
170
+ headers: headers,
171
+ }, resolve);
172
+ req.on("error", reject);
173
+ req.write(reqBody);
174
+ req.end();
175
+ });
194
176
  }
195
177
  async start() {
196
178
  for (const backend of this.args.emulatableBackends) {
@@ -272,14 +254,15 @@ class FunctionsEmulator {
272
254
  logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
273
255
  await runtimeDelegate.build();
274
256
  logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
275
- const environment = Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env);
257
+ const firebaseConfig = this.getFirebaseConfig();
258
+ const environment = Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: firebaseConfig }), emulatableBackend.env);
276
259
  const userEnvOpt = {
277
260
  functionsSource: emulatableBackend.functionsDir,
278
261
  projectId: this.args.projectId,
279
262
  projectAlias: this.args.projectAlias,
280
263
  };
281
264
  const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
282
- const resolution = await (0, build_1.resolveBackend)(discoveredBuild, userEnvOpt, environment);
265
+ const resolution = await (0, build_1.resolveBackend)(discoveredBuild, JSON.parse(firebaseConfig), userEnvOpt, environment);
283
266
  const discoveredBackend = resolution.backend;
284
267
  const endpoints = backend.allEndpoints(discoveredBackend);
285
268
  (0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
@@ -329,8 +312,8 @@ class FunctionsEmulator {
329
312
  }
330
313
  let added = false;
331
314
  let url = undefined;
315
+ const { host, port } = this.getInfo();
332
316
  if (definition.httpsTrigger) {
333
- const { host, port } = this.getInfo();
334
317
  added = true;
335
318
  url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
336
319
  }
@@ -387,7 +370,7 @@ class FunctionsEmulator {
387
370
  }
388
371
  }
389
372
  if (this.args.debugPort) {
390
- await this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
373
+ await this.startRuntime(emulatableBackend);
391
374
  }
392
375
  }
393
376
  addEventarcTrigger(projectId, key, eventTrigger) {
@@ -620,12 +603,6 @@ class FunctionsEmulator {
620
603
  this.triggers = {};
621
604
  triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
622
605
  }
623
- getBaseBundle() {
624
- return {
625
- proto: {},
626
- disabled_features: this.args.disabledRuntimeFeatures,
627
- };
628
- }
629
606
  getNodeBinary(backend) {
630
607
  const pkg = require(path.join(backend.functionsDir, "package.json"));
631
608
  if ((!pkg.engines || !pkg.engines.node) && !backend.nodeMajorVersion) {
@@ -808,24 +785,13 @@ class FunctionsEmulator {
808
785
  }
809
786
  return secretEnvs;
810
787
  }
811
- async invokeRuntime(backend, trigger, frb, opts) {
812
- const pool = this.workerPools[backend.codebase];
813
- if (!pool.readyForWork(trigger.id)) {
814
- await this.startRuntime(backend, opts, trigger);
815
- }
816
- return pool.submitWork(trigger.id, frb, opts);
817
- }
818
- async startRuntime(backend, opts, trigger) {
788
+ async startRuntime(backend, trigger) {
819
789
  var _a;
820
790
  const emitter = new events_1.EventEmitter();
821
791
  const args = [path.join(__dirname, "functionsEmulatorRuntime")];
822
- if (opts.ignore_warnings) {
823
- args.unshift("--no-warnings");
824
- }
825
792
  if (this.args.debugPort) {
826
- if (process.env.FIREPIT_VERSION && process.execPath === opts.nodeBinary) {
827
- const requestedMajorNodeVersion = this.getNodeBinary(backend);
828
- this.logger.log("WARN", `To enable function inspection, please run "${process.execPath} is:npm i node@${requestedMajorNodeVersion} --save-dev" in your functions directory`);
793
+ if (process.env.FIREPIT_VERSION && process.execPath === backend.nodeBinary) {
794
+ this.logger.log("WARN", `To enable function inspection, please run "${process.execPath} is:npm i node@${backend.nodeMajorVersion} --save-dev" in your functions directory`);
829
795
  }
830
796
  else {
831
797
  const { host } = this.getInfo();
@@ -841,9 +807,9 @@ class FunctionsEmulator {
841
807
  const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
842
808
  const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
843
809
  const socketPath = (0, functionsEmulatorShared_1.getTemporarySocketPath)();
844
- const childProcess = spawn(opts.nodeBinary, args, {
810
+ const childProcess = spawn(backend.nodeBinary, args, {
845
811
  cwd: backend.functionsDir,
846
- env: Object.assign(Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs), { PORT: socketPath }),
812
+ env: Object.assign(Object.assign(Object.assign(Object.assign({ node: backend.nodeBinary }, process.env), runtimeEnv), secretEnvs), { PORT: socketPath }),
847
813
  stdio: ["pipe", "pipe", "pipe", "ipc"],
848
814
  });
849
815
  const runtime = {
@@ -857,8 +823,9 @@ class FunctionsEmulator {
857
823
  ref: (_a = backend.extensionVersion) === null || _a === void 0 ? void 0 : _a.ref,
858
824
  };
859
825
  const pool = this.workerPools[backend.codebase];
860
- pool.addWorker(trigger === null || trigger === void 0 ? void 0 : trigger.id, runtime, extensionLogInfo);
861
- return;
826
+ const worker = pool.addWorker(trigger === null || trigger === void 0 ? void 0 : trigger.id, runtime, extensionLogInfo);
827
+ await worker.waitForSocketReady();
828
+ return worker;
862
829
  }
863
830
  async disableBackgroundTriggers() {
864
831
  Object.values(this.triggers).forEach((record) => {
@@ -877,41 +844,6 @@ class FunctionsEmulator {
877
844
  await this.performPostLoadOperations();
878
845
  return;
879
846
  }
880
- async handleBackgroundTrigger(projectId, triggerKey, proto) {
881
- const record = this.getTriggerRecordByKey(triggerKey);
882
- if (record && !record.enabled) {
883
- return Promise.reject({ code: 204, body: "Background triggers are curently disabled." });
884
- }
885
- const trigger = record.def;
886
- const service = (0, functionsEmulatorShared_1.getFunctionService)(trigger);
887
- const worker = await this.invokeTrigger(trigger, proto);
888
- return new Promise((resolve, reject) => {
889
- if (projectId !== this.args.projectId) {
890
- if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
891
- logger_1.logger.debug(`Received functions trigger for service "${service}" for unknown project "${projectId}".`);
892
- reject({ code: 404 });
893
- return;
894
- }
895
- if (!trigger.eventTrigger.resource.startsWith(`projects/_/instances/${projectId}`)) {
896
- logger_1.logger.debug(`Received functions trigger for function "${trigger.name}" of project "${projectId}" that did not match definition: ${JSON.stringify(trigger)}.`);
897
- reject({ code: 404 });
898
- return;
899
- }
900
- }
901
- worker.onLogs((el) => {
902
- if (el.level === "FATAL") {
903
- reject({ code: 500, body: el.text });
904
- }
905
- });
906
- void (0, track_1.track)(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
907
- void (0, track_1.trackEmulator)(EVENT_INVOKE_GA4, {
908
- function_service: (0, functionsEmulatorShared_1.getFunctionService)(trigger),
909
- });
910
- worker.waitForDone().then(() => {
911
- resolve({ status: "acknowledged" });
912
- });
913
- });
914
- }
915
847
  getEmulatorInfo(emulator) {
916
848
  if (this.args.remoteEmulators) {
917
849
  if (this.args.remoteEmulators[emulator]) {
@@ -947,9 +879,10 @@ class FunctionsEmulator {
947
879
  }
948
880
  async handleHttpsTrigger(req, res) {
949
881
  const method = req.method;
950
- const region = req.params.region;
951
- const triggerName = req.params.trigger_name;
952
- const triggerId = `${region}-${triggerName}`;
882
+ let triggerId = req.params.trigger_name;
883
+ if (req.params.region) {
884
+ triggerId = `${req.params.region}-${triggerId}`;
885
+ }
953
886
  if (!this.triggers[triggerId]) {
954
887
  res
955
888
  .status(404)
@@ -974,62 +907,29 @@ class FunctionsEmulator {
974
907
  req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
975
908
  }
976
909
  }
977
- const worker = await this.invokeTrigger(trigger);
978
- worker.onLogs((el) => {
979
- if (el.level === "FATAL") {
980
- res.status(500).send(el.text);
981
- }
982
- });
983
- await worker.waitForSocketReady();
984
- void (0, track_1.track)(EVENT_INVOKE, "https");
910
+ void (0, track_1.track)(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
985
911
  void (0, track_1.trackEmulator)(EVENT_INVOKE_GA4, {
986
- function_service: "https",
912
+ function_service: (0, functionsEmulatorShared_1.getFunctionService)(trigger),
987
913
  });
988
914
  this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
989
915
  const url = new url_1.URL(`${req.protocol}://${req.hostname}${req.url}`);
990
- const path = `${url.pathname}${url.search}`.replace(new RegExp(`\/${this.args.projectId}\/[^\/]*\/${triggerName}\/?`), "/");
916
+ const path = `${url.pathname}${url.search}`.replace(new RegExp(`\/${this.args.projectId}\/[^\/]*\/${req.params.trigger_name}\/?`), "/");
991
917
  this.logger.log("DEBUG", `[functions] Got req.url=${req.url}, mapping to path=${path}`);
992
- const runtimeReq = http.request({
918
+ const pool = this.workerPools[record.backend.codebase];
919
+ if (!pool.readyForWork(trigger.id)) {
920
+ await this.startRuntime(record.backend, trigger);
921
+ }
922
+ const debugBundle = this.args.debugPort
923
+ ? {
924
+ functionTarget: trigger.entryPoint,
925
+ functionSignature: (0, functionsEmulatorShared_1.getSignatureType)(trigger),
926
+ }
927
+ : undefined;
928
+ await pool.submitRequest(trigger.id, {
993
929
  method,
994
930
  path,
995
931
  headers: req.headers,
996
- socketPath: worker.runtime.socketPath,
997
- }, (runtimeRes) => {
998
- function forwardStatusAndHeaders() {
999
- res.status(runtimeRes.statusCode || 200);
1000
- if (!res.headersSent) {
1001
- Object.keys(runtimeRes.headers).forEach((key) => {
1002
- const val = runtimeRes.headers[key];
1003
- if (val) {
1004
- res.setHeader(key, val);
1005
- }
1006
- });
1007
- }
1008
- }
1009
- runtimeRes.on("data", (buf) => {
1010
- forwardStatusAndHeaders();
1011
- res.write(buf);
1012
- });
1013
- runtimeRes.on("close", () => {
1014
- forwardStatusAndHeaders();
1015
- res.end();
1016
- });
1017
- runtimeRes.on("end", () => {
1018
- forwardStatusAndHeaders();
1019
- res.end();
1020
- });
1021
- });
1022
- runtimeReq.on("error", () => {
1023
- res.end();
1024
- });
1025
- if (reqBody) {
1026
- runtimeReq.write(reqBody);
1027
- runtimeReq.end();
1028
- }
1029
- req.pipe(runtimeReq, { end: true }).on("error", () => {
1030
- res.end();
1031
- });
1032
- await worker.waitForDone();
932
+ }, res, reqBody, debugBundle);
1033
933
  }
1034
934
  }
1035
935
  exports.FunctionsEmulator = FunctionsEmulator;