firebase-tools 15.22.3 → 15.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 (49) hide show
  1. package/lib/accountExporter.js +1 -0
  2. package/lib/accountImporter.js +1 -0
  3. package/lib/apiv2.js +91 -32
  4. package/lib/apphosting/backend.js +11 -4
  5. package/lib/apphosting/localbuilds.js +50 -0
  6. package/lib/commands/functions-delete.js +5 -3
  7. package/lib/commands/functions-lifecycle-list.js +94 -0
  8. package/lib/commands/functions-lifecycle-run.js +29 -0
  9. package/lib/commands/index.js +3 -0
  10. package/lib/database/import.js +3 -3
  11. package/lib/dataconnect/webhook.js +1 -2
  12. package/lib/deploy/apphosting/prepare.js +100 -1
  13. package/lib/deploy/functions/backend.js +15 -1
  14. package/lib/deploy/functions/build.js +28 -0
  15. package/lib/deploy/functions/prepare.js +91 -2
  16. package/lib/deploy/functions/prompts.js +62 -0
  17. package/lib/deploy/functions/release/fabricator.js +79 -4
  18. package/lib/deploy/functions/release/index.js +42 -23
  19. package/lib/deploy/functions/release/lifecycle.js +113 -0
  20. package/lib/deploy/functions/release/planner.js +41 -5
  21. package/lib/deploy/functions/runtimes/discovery/index.js +4 -3
  22. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +50 -0
  23. package/lib/deploy/functions/runtimes/node/index.js +1 -2
  24. package/lib/deploy/functions/runtimes/python/index.js +1 -2
  25. package/lib/deploy/functions/validate.js +44 -1
  26. package/lib/deploy/hosting/uploader.js +2 -3
  27. package/lib/downloadUtils.js +3 -1
  28. package/lib/emulator/auth/operations.js +18 -8
  29. package/lib/emulator/downloadableEmulatorInfo.json +31 -31
  30. package/lib/emulator/storage/apis/gcloud.js +61 -0
  31. package/lib/emulator/storage/multipart.js +82 -2
  32. package/lib/emulator/taskQueue.js +3 -11
  33. package/lib/extensions/extensionsHelper.js +6 -4
  34. package/lib/firestore/api.js +3 -1
  35. package/lib/gcp/cloudtasks.js +4 -0
  36. package/lib/gcp/iam.js +68 -2
  37. package/lib/gcp/knownRoles.json +99 -0
  38. package/lib/gcp/resourceManager.js +42 -1
  39. package/lib/gemini/fdcExperience.js +2 -2
  40. package/lib/hosting/initMiddleware.js +1 -1
  41. package/lib/hosting/proxy.js +42 -20
  42. package/lib/management/apps.js +4 -2
  43. package/lib/profiler.js +1 -2
  44. package/lib/streamUtils.js +24 -0
  45. package/lib/track.js +1 -2
  46. package/lib/tsconfig.compile.tsbuildinfo +1 -1
  47. package/lib/tsconfig.publish.tsbuildinfo +1 -1
  48. package/lib/utils.js +4 -21
  49. package/package.json +2 -4
@@ -24,6 +24,7 @@ const EXPORTED_JSON_KEYS = [
24
24
  "phoneNumber",
25
25
  "disabled",
26
26
  "customAttributes",
27
+ "mfaInfo",
27
28
  ];
28
29
  const EXPORTED_JSON_KEYS_RENAMING = {
29
30
  lastLoginAt: "lastSignedInAt",
@@ -27,6 +27,7 @@ const ALLOWED_JSON_KEYS = [
27
27
  "phoneNumber",
28
28
  "disabled",
29
29
  "customAttributes",
30
+ "mfaInfo",
30
31
  ];
31
32
  const ALLOWED_JSON_KEYS_RENAMING = {
32
33
  lastSignedInAt: "lastLoginAt",
package/lib/apiv2.js CHANGED
@@ -7,10 +7,8 @@ exports.getAccessToken = getAccessToken;
7
7
  exports.noKeepAliveAgent = noKeepAliveAgent;
8
8
  const url_1 = require("url");
9
9
  const stream_1 = require("stream");
10
- const proxy_agent_1 = require("proxy-agent");
10
+ const undici_1 = require("undici");
11
11
  const retry = require("retry");
12
- const abort_controller_1 = require("abort-controller");
13
- const node_fetch_1 = require("node-fetch");
14
12
  const http = require("http");
15
13
  const https = require("https");
16
14
  const util_1 = require("util");
@@ -19,6 +17,7 @@ const error_1 = require("./error");
19
17
  const env_1 = require("./env");
20
18
  const logger_1 = require("./logger");
21
19
  const responseToError_1 = require("./responseToError");
20
+ const streamUtils_1 = require("./streamUtils");
22
21
  const FormData = require("form-data");
23
22
  const pkg = require("../package.json");
24
23
  const CLI_VERSION = pkg.version;
@@ -36,7 +35,6 @@ const standardHeaders = () => {
36
35
  exports.standardHeaders = standardHeaders;
37
36
  const GOOG_QUOTA_USER_HEADER = "x-goog-quota-user";
38
37
  exports.GOOG_USER_PROJECT_HEADER = "x-goog-user-project";
39
- const GOOGLE_CLOUD_QUOTA_PROJECT = process.env.GOOGLE_CLOUD_QUOTA_PROJECT;
40
38
  exports.CLI_OAUTH_PROJECT_NUMBER = "563584335869";
41
39
  let accessToken = "";
42
40
  let refreshToken = "";
@@ -56,11 +54,15 @@ async function getAccessToken() {
56
54
  return data.access_token;
57
55
  }
58
56
  function proxyURIFromEnv() {
59
- return (process.env.HTTPS_PROXY ||
57
+ const uri = process.env.HTTPS_PROXY ||
60
58
  process.env.https_proxy ||
61
59
  process.env.HTTP_PROXY ||
62
60
  process.env.http_proxy ||
63
- undefined);
61
+ undefined;
62
+ if (uri === "undefined" || uri === "null") {
63
+ return undefined;
64
+ }
65
+ return uri;
64
66
  }
65
67
  const httpAgentNoKeepAlive = new http.Agent({ keepAlive: false });
66
68
  const httpsAgentNoKeepAlive = new https.Agent({ keepAlive: false });
@@ -147,7 +149,7 @@ class Client {
147
149
  throw new error_1.FirebaseError("apiv2 will not handle HTTP errors while streaming and you must set `resolveOnHTTPError` and check for res.status >= 400 on your own", { exit: 2 });
148
150
  }
149
151
  let internalReqOptions = Object.assign(reqOptions, {
150
- headers: new node_fetch_1.Headers(reqOptions.headers),
152
+ headers: new Headers(reqOptions.headers),
151
153
  });
152
154
  internalReqOptions = this.addRequestHeaders(internalReqOptions);
153
155
  if (this.opts.auth) {
@@ -176,7 +178,7 @@ class Client {
176
178
  }
177
179
  addRequestHeaders(reqOptions) {
178
180
  if (!reqOptions.headers) {
179
- reqOptions.headers = new node_fetch_1.Headers();
181
+ reqOptions.headers = new Headers();
180
182
  }
181
183
  for (const [h, v] of Object.entries((0, exports.standardHeaders)())) {
182
184
  if (!reqOptions.headers.has(h)) {
@@ -189,15 +191,15 @@ class Client {
189
191
  }
190
192
  }
191
193
  if (!reqOptions.ignoreQuotaProject &&
192
- GOOGLE_CLOUD_QUOTA_PROJECT &&
193
- GOOGLE_CLOUD_QUOTA_PROJECT !== "") {
194
- reqOptions.headers.set(exports.GOOG_USER_PROJECT_HEADER, GOOGLE_CLOUD_QUOTA_PROJECT);
194
+ process.env.GOOGLE_CLOUD_QUOTA_PROJECT &&
195
+ process.env.GOOGLE_CLOUD_QUOTA_PROJECT !== "") {
196
+ reqOptions.headers.set(exports.GOOG_USER_PROJECT_HEADER, process.env.GOOGLE_CLOUD_QUOTA_PROJECT);
195
197
  }
196
198
  return reqOptions;
197
199
  }
198
200
  async addAuthHeader(reqOptions) {
199
201
  if (!reqOptions.headers) {
200
- reqOptions.headers = new node_fetch_1.Headers();
202
+ reqOptions.headers = new Headers();
201
203
  }
202
204
  let token;
203
205
  if (isLocalInsecureRequest(this.opts.urlPrefix)) {
@@ -236,31 +238,21 @@ class Client {
236
238
  headers: options.headers,
237
239
  method: options.method,
238
240
  redirect: options.redirect,
239
- compress: options.compress,
240
241
  };
241
- if (proxyURIFromEnv()) {
242
- fetchOptions.agent = new proxy_agent_1.ProxyAgent();
242
+ const proxyURI = proxyURIFromEnv();
243
+ if (proxyURI) {
244
+ fetchOptions.dispatcher = new undici_1.ProxyAgent({ uri: proxyURI });
243
245
  }
244
246
  if (options.signal) {
245
- const signal = options.signal;
246
- signal.reason = "";
247
- signal.throwIfAborted = () => {
248
- throw new error_1.FirebaseError("Aborted");
249
- };
250
- fetchOptions.signal = signal;
247
+ fetchOptions.signal = options.signal;
251
248
  }
252
249
  let reqTimeout;
253
250
  if (options.timeout) {
254
- const controller = new abort_controller_1.default();
251
+ const controller = new AbortController();
255
252
  reqTimeout = setTimeout(() => {
256
253
  controller.abort();
257
254
  }, options.timeout);
258
- const signal = controller.signal;
259
- signal.reason = "";
260
- signal.throwIfAborted = () => {
261
- throw new error_1.FirebaseError("Aborted");
262
- };
263
- fetchOptions.signal = signal;
255
+ fetchOptions.signal = controller.signal;
264
256
  }
265
257
  let bodyReplayable = true;
266
258
  if (typeof options.body === "string" || Buffer.isBuffer(options.body)) {
@@ -271,6 +263,7 @@ class Client {
271
263
  }
272
264
  else if (isStream(options.body)) {
273
265
  fetchOptions.body = options.body;
266
+ fetchOptions.duplex = "half";
274
267
  bodyReplayable = false;
275
268
  }
276
269
  else if (options.body !== undefined) {
@@ -302,10 +295,39 @@ class Client {
302
295
  }
303
296
  this.logRequest(options);
304
297
  try {
305
- res = await (0, node_fetch_1.default)(fetchURL, fetchOptions);
298
+ if (options.compress === false) {
299
+ const undiciOptions = {
300
+ method: fetchOptions.method,
301
+ headers: {},
302
+ decompress: false,
303
+ };
304
+ if (fetchOptions.dispatcher) {
305
+ undiciOptions.dispatcher = fetchOptions.dispatcher;
306
+ }
307
+ if (fetchOptions.signal) {
308
+ undiciOptions.signal = fetchOptions.signal;
309
+ }
310
+ if (fetchOptions.body) {
311
+ undiciOptions.body = fetchOptions.body;
312
+ }
313
+ if (fetchOptions.headers) {
314
+ for (const [key, value] of fetchOptions.headers.entries()) {
315
+ undiciOptions.headers[key] = value;
316
+ }
317
+ }
318
+ const undiciRes = await (0, undici_1.request)(fetchURL, undiciOptions);
319
+ res = new UndiciResponseCompat(undiciRes);
320
+ }
321
+ else {
322
+ res = await fetch(fetchURL, fetchOptions);
323
+ }
306
324
  }
307
325
  catch (thrown) {
308
- const err = thrown instanceof Error ? thrown : new Error(thrown);
326
+ const err = thrown && typeof thrown === "object" && thrown.cause instanceof Error
327
+ ? thrown.cause
328
+ : thrown instanceof Error
329
+ ? thrown
330
+ : new Error(thrown);
309
331
  logger_1.logger.debug(`*** [apiv2] error from fetch(${fetchURL}, ${JSON.stringify(fetchOptions)}): ${err}`);
310
332
  const isAbortError = err.name.includes("AbortError");
311
333
  if (isAbortError) {
@@ -339,7 +361,19 @@ class Client {
339
361
  body = (await res.text());
340
362
  }
341
363
  else if (options.responseType === "stream") {
342
- body = res.body;
364
+ if (res.body) {
365
+ if (typeof res.body.getReader === "function") {
366
+ body = stream_1.Readable.fromWeb(res.body);
367
+ }
368
+ else {
369
+ body = res.body;
370
+ }
371
+ }
372
+ else {
373
+ const emptyStream = new stream_1.Readable();
374
+ emptyStream.push(null);
375
+ body = emptyStream;
376
+ }
343
377
  }
344
378
  else {
345
379
  throw new error_1.FirebaseError(`Unable to interpret response. Please set responseType.`, {
@@ -354,7 +388,7 @@ class Client {
354
388
  isPrematureCloseError(err)) {
355
389
  disabledKeepAlive = true;
356
390
  fetchOptions.agent = noKeepAliveAgent;
357
- const closeHeaders = new node_fetch_1.Headers(fetchOptions.headers);
391
+ const closeHeaders = new Headers(fetchOptions.headers);
358
392
  closeHeaders.set("Connection", "close");
359
393
  fetchOptions.headers = closeHeaders;
360
394
  logger_1.logger.debug(`*** [apiv2] retrying ${fetchURL} without keep-alive after a premature close error`);
@@ -435,6 +469,31 @@ function isLocalInsecureRequest(urlPrefix) {
435
469
  const u = new url_1.URL(urlPrefix);
436
470
  return u.protocol === "http:";
437
471
  }
472
+ class UndiciResponseCompat {
473
+ constructor(undiciRes) {
474
+ this.undiciRes = undiciRes;
475
+ this.status = undiciRes.statusCode;
476
+ this.ok = undiciRes.statusCode >= 200 && undiciRes.statusCode < 300;
477
+ this.headers = new Headers();
478
+ for (const [key, value] of Object.entries(undiciRes.headers)) {
479
+ if (Array.isArray(value)) {
480
+ for (const v of value) {
481
+ this.headers.append(key, v);
482
+ }
483
+ }
484
+ else if (value !== undefined) {
485
+ this.headers.set(key, String(value));
486
+ }
487
+ }
488
+ this.body = undiciRes.body;
489
+ }
490
+ async text() {
491
+ if (!this.body) {
492
+ return "";
493
+ }
494
+ return (0, streamUtils_1.streamToString)(this.body);
495
+ }
496
+ }
438
497
  function bodyToString(body) {
439
498
  if (isStream(body)) {
440
499
  return "[stream]";
@@ -31,7 +31,6 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
31
31
  const deploymentTool = require("../deploymentTool");
32
32
  const app_1 = require("./app");
33
33
  const ora = require("ora");
34
- const node_fetch_1 = require("node-fetch");
35
34
  const rollout_1 = require("./rollout");
36
35
  const fuzzy = require("fuzzy");
37
36
  const experiments_1 = require("../experiments");
@@ -45,7 +44,7 @@ const apphostingPollerOptions = {
45
44
  };
46
45
  async function tlsReady(url) {
47
46
  try {
48
- await (0, node_fetch_1.default)(url);
47
+ await fetch(url);
49
48
  return true;
50
49
  }
51
50
  catch (err) {
@@ -213,8 +212,16 @@ async function promptNewBackendId(projectId, location) {
213
212
  while (true) {
214
213
  const backendId = await (0, prompt_1.input)({
215
214
  default: "my-web-app",
216
- message: "Provide a name for your backend [1-30 characters]",
217
- validate: (s) => s.length >= 1 && s.length <= 30,
215
+ message: "Provide a name for your backend [3-30 characters]",
216
+ validate: (s) => {
217
+ if (!/^[a-z](?:[a-z0-9-]*[a-z0-9])?$/.test(s)) {
218
+ return "Must begin with a letter, can contain only lowercase, digits, hyphens, and cannot end with hyphen";
219
+ }
220
+ else if (s.length < 3 || s.length > 30) {
221
+ return "Must be between 3 and 30 characters";
222
+ }
223
+ return true;
224
+ },
218
225
  });
219
226
  try {
220
227
  await apphosting.getBackend(projectId, location, backendId);
@@ -2,9 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runUniversalMaker = runUniversalMaker;
4
4
  exports.localBuild = localBuild;
5
+ exports.validateLocalBuildNodeVersion = validateLocalBuildNodeVersion;
5
6
  const childProcess = require("child_process");
6
7
  const fs = require("fs-extra");
7
8
  const path = require("path");
9
+ const semver = require("semver");
8
10
  const index_1 = require("./secrets/index");
9
11
  const prompt_1 = require("../prompt");
10
12
  const error_1 = require("../error");
@@ -138,3 +140,51 @@ async function toProcessEnv(projectId, env) {
138
140
  }));
139
141
  return Object.fromEntries(resolvedEntries);
140
142
  }
143
+ function validateLocalBuildNodeVersion(backend, projectRoot) {
144
+ const runtimeValue = backend.runtime?.value ?? "";
145
+ const isLegacyRuntime = runtimeValue === "" || runtimeValue === "nodejs";
146
+ const abiuEnabled = !isLegacyRuntime && !backend.automaticBaseImageUpdatesDisabled;
147
+ if (!abiuEnabled) {
148
+ throw new error_1.FirebaseError(`Local builds are only supported for backends with ABIU (Automatic Base Image Updates) enabled. ` +
149
+ `Your backend is currently configured with a non-ABIU runtime ("${runtimeValue || "unspecified"}"). ` +
150
+ `Please update your backend to a versioned runtime (e.g., nodejs22) to enable local builds.`, { exit: 1 });
151
+ }
152
+ const targetMajorMatch = runtimeValue.match(/^nodejs(\d+)$/);
153
+ if (!targetMajorMatch) {
154
+ (0, utils_1.logLabeledWarning)("apphosting", `Unable to extract Node.js major version from the backend runtime ("${runtimeValue}"). ` +
155
+ `Skipping local Node.js version compatibility checks.`);
156
+ return;
157
+ }
158
+ const targetMajor = parseInt(targetMajorMatch[1], 10);
159
+ let localNodeVersion;
160
+ try {
161
+ localNodeVersion = childProcess.execSync("node -v", { encoding: "utf8" }).trim();
162
+ }
163
+ catch {
164
+ (0, utils_1.logLabeledWarning)("apphosting", `Unable to detect your local Node.js version (is 'node' installed and in your PATH?). ` +
165
+ `Skipping local Node.js version compatibility checks.`);
166
+ return;
167
+ }
168
+ const packageJsonPath = path.join(projectRoot, "package.json");
169
+ const packageJson = fs.readJsonSync(packageJsonPath, { throws: false });
170
+ const enginesNode = packageJson?.engines?.node;
171
+ if (enginesNode) {
172
+ (0, utils_1.logLabeledWarning)("apphosting", `Your package.json specifies Node.js engine "${enginesNode}". ` +
173
+ `Please note that local builds do NOT use the "engines" field to resolve or download Node.js. ` +
174
+ `Instead, your local build uses your host machine's active Node.js version (${localNodeVersion}) to compile the app, ` +
175
+ `and your deployed app will run on the backend's configured ABIU runtime (${runtimeValue}).`);
176
+ const targetRange = `^${targetMajor}.0.0`;
177
+ if (semver.validRange(enginesNode) && !semver.intersects(targetRange, enginesNode)) {
178
+ (0, utils_1.logLabeledWarning)("apphosting", `The Node.js version range specified in your package.json engines ("${enginesNode}") ` +
179
+ `does not satisfy your backend's target ABIU runtime version (Node.js ${targetMajor}). ` +
180
+ `Please update your package.json engines to align with your backend configuration.`);
181
+ }
182
+ }
183
+ const localMajorMatch = localNodeVersion.match(/^v?(\d+)/);
184
+ const localMajor = localMajorMatch ? parseInt(localMajorMatch[1], 10) : null;
185
+ if (localMajor !== null && localMajor !== targetMajor) {
186
+ (0, utils_1.logLabeledWarning)("apphosting", `Local Node.js version (${localNodeVersion}) does not match your backend's target Node.js version (Node.js ${targetMajor}). ` +
187
+ `This mismatch may cause runtime issues. ` +
188
+ `Please switch your local environment to Node.js ${targetMajor} to ensure build-to-run parity.`);
189
+ }
190
+ }
@@ -40,14 +40,15 @@ exports.command = new command_1.Command("functions:delete [filters...]")
40
40
  if (options.region) {
41
41
  existingBackend.endpoints = { [options.region]: existingBackend.endpoints[options.region] };
42
42
  }
43
- const plan = planner.createDeploymentPlan({
43
+ const plan = await planner.createDeploymentPlan({
44
44
  wantBackend: backend.empty(),
45
45
  haveBackend: existingBackend,
46
46
  codebase: "",
47
+ projectId: context.projectId,
47
48
  filters: context.filters,
48
49
  deleteAll: true,
49
50
  });
50
- const allEpToDelete = Object.values(plan)
51
+ const allEpToDelete = Object.values(plan.regionalChangesets)
51
52
  .map((changes) => changes.endpointsToDelete)
52
53
  .reduce(functional_1.reduceFlat, [])
53
54
  .sort(backend.compareFunctions);
@@ -80,8 +81,9 @@ exports.command = new command_1.Command("functions:delete [filters...]")
80
81
  executor: new executor.QueueExecutor({}),
81
82
  sources: {},
82
83
  projectNumber: options.projectNumber || (await (0, getProjectNumber_1.getProjectNumber)({ projectId: context.projectId })),
84
+ projectId: context.projectId,
83
85
  });
84
- const summary = await fab.applyPlan(plan);
86
+ const summary = await fab.applyPlan({ default: plan });
85
87
  await reporter.logAndTrackDeployStats(summary);
86
88
  reporter.printErrors(summary);
87
89
  }
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ exports.loadCodebaseBuild = loadCodebaseBuild;
5
+ const command_1 = require("../command");
6
+ const logger_1 = require("../logger");
7
+ const prepare_1 = require("../deploy/functions/prepare");
8
+ const projectConfig_1 = require("../functions/projectConfig");
9
+ const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
10
+ const projectUtils_1 = require("../projectUtils");
11
+ const error_1 = require("../error");
12
+ const ensureApiEnabled = require("../ensureApiEnabled");
13
+ const api_1 = require("../api");
14
+ const prepareFunctionsUpload_1 = require("../deploy/functions/prepareFunctionsUpload");
15
+ const self = require("./functions-lifecycle-list");
16
+ async function loadCodebaseBuild(codebase, options) {
17
+ const projectId = (0, projectUtils_1.needProjectId)(options);
18
+ if (!options.config) {
19
+ throw new error_1.FirebaseError("Not in a Firebase project directory (firebase.json not found).");
20
+ }
21
+ const fnConfig = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
22
+ const hasCodebase = fnConfig.some((c) => c.codebase === codebase);
23
+ if (!hasCodebase) {
24
+ throw new error_1.FirebaseError(`Codebase "${codebase}" is not defined in firebase.json.`);
25
+ }
26
+ const firebaseConfig = await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId);
27
+ if (!firebaseConfig) {
28
+ throw new error_1.FirebaseError("Admin SDK config unexpectedly undefined - have you run firebase init?");
29
+ }
30
+ let runtimeConfig = { firebase: firebaseConfig };
31
+ if (fnConfig.some(projectConfig_1.shouldUseRuntimeConfig)) {
32
+ try {
33
+ const runtimeConfigApiEnabled = await ensureApiEnabled.check(projectId, (0, api_1.runtimeconfigOrigin)(), "runtimeconfig", true);
34
+ if (runtimeConfigApiEnabled) {
35
+ runtimeConfig = { ...runtimeConfig, ...(await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId)) };
36
+ }
37
+ }
38
+ catch (err) {
39
+ logger_1.logger.debug("Could not check Runtime Config API status, assuming disabled:", err);
40
+ }
41
+ }
42
+ const wantBuilds = await (0, prepare_1.loadCodebases)(fnConfig, options, firebaseConfig, runtimeConfig, undefined);
43
+ const codebaseBuild = wantBuilds[codebase];
44
+ if (!codebaseBuild) {
45
+ throw new error_1.FirebaseError(`Failed to load build for codebase "${codebase}".`);
46
+ }
47
+ return codebaseBuild;
48
+ }
49
+ exports.command = new command_1.Command("functions:lifecycle:list <codebase>")
50
+ .description("list all the lifecycle hooks defined in a codebase")
51
+ .action(async (codebase, options) => {
52
+ const codebaseBuild = await self.loadCodebaseBuild(codebase, options);
53
+ const hooks = codebaseBuild.lifecycleHooks || {};
54
+ if (Object.keys(hooks).length === 0) {
55
+ logger_1.logger.info(`No lifecycle hooks configured for codebase "${codebase}".`);
56
+ return hooks;
57
+ }
58
+ for (const [event, hook] of Object.entries(hooks)) {
59
+ logger_1.logger.info(`\nEvent: ${event}`);
60
+ if ("task" in hook) {
61
+ logger_1.logger.info(` Action: Task`);
62
+ logger_1.logger.info(` Target Function: ${hook.task.function}`);
63
+ if (hook.task.body) {
64
+ logger_1.logger.info(` Body: ${JSON.stringify(hook.task.body, null, 2).replace(/\n/g, "\n ")}`);
65
+ }
66
+ }
67
+ else if ("call" in hook) {
68
+ logger_1.logger.info(` Action: Call`);
69
+ logger_1.logger.info(` Target Function: ${hook.call.function}`);
70
+ if (hook.call.params) {
71
+ logger_1.logger.info(` Params: ${JSON.stringify(hook.call.params, null, 2).replace(/\n/g, "\n ")}`);
72
+ }
73
+ }
74
+ else if ("http" in hook) {
75
+ logger_1.logger.info(` Action: HTTP`);
76
+ if (hook.http.function) {
77
+ logger_1.logger.info(` Target Function: ${hook.http.function}`);
78
+ }
79
+ if (hook.http.url) {
80
+ logger_1.logger.info(` URL: ${hook.http.url}`);
81
+ }
82
+ if (hook.http.method) {
83
+ logger_1.logger.info(` Method: ${hook.http.method}`);
84
+ }
85
+ if (hook.http.headers) {
86
+ logger_1.logger.info(` Headers: ${JSON.stringify(hook.http.headers, null, 2).replace(/\n/g, "\n ")}`);
87
+ }
88
+ if (hook.http.body) {
89
+ logger_1.logger.info(` Body: ${JSON.stringify(hook.http.body, null, 2).replace(/\n/g, "\n ")}`);
90
+ }
91
+ }
92
+ }
93
+ return hooks;
94
+ });
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const requirePermissions_1 = require("../requirePermissions");
7
+ const error_1 = require("../error");
8
+ const functions_lifecycle_list_1 = require("./functions-lifecycle-list");
9
+ const backend = require("../deploy/functions/backend");
10
+ const lifecycle_1 = require("../deploy/functions/release/lifecycle");
11
+ exports.command = new command_1.Command("functions:lifecycle:run <hookName> <codebase>")
12
+ .description("run a specific lifecycle hook in isolation")
13
+ .before(requirePermissions_1.requirePermissions, ["cloudfunctions.functions.list", "run.services.list"])
14
+ .action(async (hookName, codebase, options) => {
15
+ if (hookName !== "afterFirstDeploy" && hookName !== "afterRedeploy") {
16
+ throw new error_1.FirebaseError(`Invalid hook name "${hookName}". Supported hooks are "afterFirstDeploy" and "afterRedeploy".`);
17
+ }
18
+ const projectId = (0, projectUtils_1.needProjectId)(options);
19
+ const codebaseBuild = await (0, functions_lifecycle_list_1.loadCodebaseBuild)(codebase, options);
20
+ const hook = codebaseBuild.lifecycleHooks?.[hookName];
21
+ if (!hook) {
22
+ throw new error_1.FirebaseError(`No lifecycle hook "${hookName}" configured for codebase "${codebase}".`);
23
+ }
24
+ const context = {
25
+ projectId,
26
+ };
27
+ const existingBackend = await backend.existingBackend(context);
28
+ await (0, lifecycle_1.executeHook)(hookName, hook, existingBackend);
29
+ });
@@ -151,6 +151,9 @@ function load(client) {
151
151
  client.functions.log = loadCommand("functions-log");
152
152
  client.functions.shell = loadCommand("functions-shell");
153
153
  client.functions.list = loadCommand("functions-list");
154
+ client.functions.lifecycle = {};
155
+ client.functions.lifecycle.list = loadCommand("functions-lifecycle-list");
156
+ client.functions.lifecycle.run = loadCommand("functions-lifecycle-run");
154
157
  if (experiments.isEnabled("deletegcfartifacts")) {
155
158
  client.functions.deletegcfartifacts = loadCommand("functions-deletegcfartifacts");
156
159
  }
@@ -6,7 +6,6 @@ const Filter = require("stream-json/filters/Filter");
6
6
  const stream = require("stream");
7
7
  const StreamObject = require("stream-json/streamers/StreamObject");
8
8
  const apiv2_1 = require("../apiv2");
9
- const node_fetch_1 = require("node-fetch");
10
9
  const error_1 = require("../error");
11
10
  const utils_1 = require("../utils");
12
11
  class BatchChunks extends stream.Transform {
@@ -169,8 +168,9 @@ class DatabaseImporter {
169
168
  }
170
169
  catch (err) {
171
170
  const isTimeoutErr = err instanceof error_1.FirebaseError &&
172
- err.original instanceof node_fetch_1.FetchError &&
173
- err.original.code === "ETIMEDOUT";
171
+ (err.original?.name === "AbortError" ||
172
+ err.original?.code === "ETIMEDOUT" ||
173
+ err.original?.cause?.code === "ETIMEDOUT");
174
174
  if (isTimeoutErr) {
175
175
  await new Promise((res) => setTimeout(res, this.nonFatalRetryTimeout));
176
176
  return await doRequest();
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.port = exports.DEFAULT_PORT = exports.VSCODE_MESSAGE = void 0;
4
4
  exports.sendVSCodeMessage = sendVSCodeMessage;
5
- const node_fetch_1 = require("node-fetch");
6
5
  const logger_1 = require("../logger");
7
6
  var VSCODE_MESSAGE;
8
7
  (function (VSCODE_MESSAGE) {
@@ -15,7 +14,7 @@ exports.port = process.env.VSCODE_WEBHOOK_PORT || exports.DEFAULT_PORT;
15
14
  async function sendVSCodeMessage(body) {
16
15
  const jsonBody = JSON.stringify(body);
17
16
  try {
18
- return await (0, node_fetch_1.default)(`http://localhost:${exports.port}/vscode/notify`, {
17
+ return await fetch(`http://localhost:${exports.port}/vscode/notify`, {
19
18
  method: "POST",
20
19
  headers: {
21
20
  Accept: "application/json",