firebase-tools 14.26.0 → 14.27.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.
@@ -7,25 +7,35 @@ const requirePermissions_1 = require("../requirePermissions");
7
7
  const backend = require("../deploy/functions/backend");
8
8
  const logger_1 = require("../logger");
9
9
  const Table = require("cli-table3");
10
+ const PLATFORM_TO_DISPLAY_NAME = {
11
+ gcfv1: "v1",
12
+ gcfv2: "v2",
13
+ run: "run",
14
+ };
10
15
  exports.command = new command_1.Command("functions:list")
11
16
  .description("list all deployed functions in your Firebase project")
12
- .before(requirePermissions_1.requirePermissions, ["cloudfunctions.functions.list"])
17
+ .before(requirePermissions_1.requirePermissions, ["cloudfunctions.functions.list", "run.services.list"])
13
18
  .action(async (options) => {
19
+ const projectId = (0, projectUtils_1.needProjectId)(options);
14
20
  const context = {
15
- projectId: (0, projectUtils_1.needProjectId)(options),
21
+ projectId,
16
22
  };
17
23
  const existing = await backend.existingBackend(context);
18
- const endpointsList = backend.allEndpoints(existing).sort(backend.compareFunctions);
24
+ const endpoints = backend.allEndpoints(existing).sort(backend.compareFunctions);
25
+ if (endpoints.length === 0) {
26
+ logger_1.logger.info(`No functions found in project ${projectId}.`);
27
+ return [];
28
+ }
19
29
  const table = new Table({
20
30
  head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"],
21
31
  style: { head: ["yellow"] },
22
32
  });
23
- for (const endpoint of endpointsList) {
33
+ for (const endpoint of endpoints) {
24
34
  const trigger = backend.endpointTriggerType(endpoint);
25
35
  const availableMemoryMb = endpoint.availableMemoryMb || "---";
26
36
  const entry = [
27
37
  endpoint.id,
28
- endpoint.platform === "gcfv2" ? "v2" : "v1",
38
+ PLATFORM_TO_DISPLAY_NAME[endpoint.platform] || "v1",
29
39
  trigger,
30
40
  endpoint.region,
31
41
  availableMemoryMb,
@@ -34,5 +44,5 @@ exports.command = new command_1.Command("functions:list")
34
44
  table.push(entry);
35
45
  }
36
46
  logger_1.logger.info(table.toString());
37
- return endpointsList;
47
+ return endpoints;
38
48
  });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
3
+ exports.printVersionsTable = exports.command = void 0;
4
4
  const logger_1 = require("../logger");
5
5
  const rcVersion = require("../remoteconfig/versionslist");
6
6
  const command_1 = require("../command");
@@ -25,10 +25,14 @@ exports.command = new command_1.Command("remoteconfig:versions:list")
25
25
  .before(requirePermissions_1.requirePermissions, ["cloudconfig.configs.get"])
26
26
  .action(async (options) => {
27
27
  const versionsList = await rcVersion.getVersions((0, projectUtils_1.needProjectId)(options), options.limit);
28
+ printVersionsTable(versionsList);
29
+ return versionsList;
30
+ });
31
+ function printVersionsTable(versionsList) {
28
32
  const table = new Table({ head: tableHead, style: { head: ["green"] } });
29
- for (let item = 0; item < versionsList.versions.length; item++) {
30
- pushTableContents(table, versionsList.versions[item]);
33
+ for (const version of versionsList.versions || []) {
34
+ pushTableContents(table, version);
31
35
  }
32
36
  logger_1.logger.info(table.toString());
33
- return versionsList;
34
- });
37
+ }
38
+ exports.printVersionsTable = printVersionsTable;
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.merge = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.memoryToGen2Cpu = exports.memoryToGen1Cpu = exports.memoryOptionDisplayName = exports.isValidAttemptDeadline = exports.MAX_ATTEMPT_DEADLINE_SECONDS = exports.MIN_ATTEMPT_DEADLINE_SECONDS = exports.isValidEgressSetting = exports.isValidMemoryOption = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
3
+ exports.compareFunctions = exports.missingEndpoint = exports.hasEndpoint = exports.regionalEndpoints = exports.matchingBackend = exports.findEndpoint = exports.someEndpoint = exports.allEndpoints = exports.checkAvailability = exports.existingBackend = exports.scheduleIdForFunction = exports.functionName = exports.isEmptyBackend = exports.merge = exports.of = exports.empty = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.AllFunctionsPlatforms = exports.secretVersionName = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.memoryToGen2Cpu = exports.memoryToGen1Cpu = exports.memoryOptionDisplayName = exports.isValidEgressSetting = exports.isValidMemoryOption = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.endpointTriggerType = void 0;
4
4
  const gcf = require("../../gcp/cloudfunctions");
5
5
  const gcfV2 = require("../../gcp/cloudfunctionsv2");
6
+ const run = require("../../gcp/runv2");
6
7
  const utils = require("../../utils");
7
8
  const error_1 = require("../../error");
8
9
  const functional_1 = require("../../functional");
10
+ const logger_1 = require("../../logger");
11
+ const experiments = require("../../experiments");
9
12
  function endpointTriggerType(endpoint) {
10
13
  if (isScheduleTriggered(endpoint)) {
11
14
  return "scheduled";
@@ -43,12 +46,6 @@ function isValidEgressSetting(egress) {
43
46
  return egress === "PRIVATE_RANGES_ONLY" || egress === "ALL_TRAFFIC";
44
47
  }
45
48
  exports.isValidEgressSetting = isValidEgressSetting;
46
- exports.MIN_ATTEMPT_DEADLINE_SECONDS = 15;
47
- exports.MAX_ATTEMPT_DEADLINE_SECONDS = 1800;
48
- function isValidAttemptDeadline(seconds) {
49
- return seconds >= exports.MIN_ATTEMPT_DEADLINE_SECONDS && seconds <= exports.MAX_ATTEMPT_DEADLINE_SECONDS;
50
- }
51
- exports.isValidAttemptDeadline = isValidAttemptDeadline;
52
49
  function memoryOptionDisplayName(option) {
53
50
  return {
54
51
  128: "128MB",
@@ -184,7 +181,6 @@ function existingBackend(context, forceRefresh) {
184
181
  }
185
182
  exports.existingBackend = existingBackend;
186
183
  async function loadExistingBackend(ctx) {
187
- var _a;
188
184
  const existingBackend = Object.assign({}, empty());
189
185
  const unreachableRegions = {
190
186
  gcfV1: [],
@@ -198,9 +194,23 @@ async function loadExistingBackend(ctx) {
198
194
  existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
199
195
  }
200
196
  unreachableRegions.gcfV1 = gcfV1Results.unreachable;
201
- let gcfV2Results;
202
- try {
203
- gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
197
+ if (experiments.isEnabled("functionsrunapionly")) {
198
+ try {
199
+ const runServices = await run.listServices(ctx.projectId);
200
+ for (const service of runServices) {
201
+ const endpoint = run.endpointFromService(service);
202
+ existingBackend.endpoints[endpoint.region] =
203
+ existingBackend.endpoints[endpoint.region] || {};
204
+ existingBackend.endpoints[endpoint.region][endpoint.id] = endpoint;
205
+ }
206
+ }
207
+ catch (err) {
208
+ logger_1.logger.debug(err.message);
209
+ unreachableRegions.run = ["unknown"];
210
+ }
211
+ }
212
+ else {
213
+ const gcfV2Results = await gcfV2.listAllFunctions(ctx.projectId);
204
214
  for (const apiFunction of gcfV2Results.functions) {
205
215
  const endpoint = gcfV2.endpointFromFunction(apiFunction);
206
216
  existingBackend.endpoints[endpoint.region] = existingBackend.endpoints[endpoint.region] || {};
@@ -208,13 +218,6 @@ async function loadExistingBackend(ctx) {
208
218
  }
209
219
  unreachableRegions.gcfV2 = gcfV2Results.unreachable;
210
220
  }
211
- catch (err) {
212
- if (err.status === 404 && ((_a = err.message) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("method not found"))) {
213
- }
214
- else {
215
- throw err;
216
- }
217
- }
218
221
  ctx.existingBackend = existingBackend;
219
222
  ctx.unreachableRegions = unreachableRegions;
220
223
  return ctx.existingBackend;
@@ -285,14 +285,6 @@ function discoverTrigger(endpoint, region, r) {
285
285
  else if (endpoint.scheduleTrigger.retryConfig === null) {
286
286
  bkSchedule.retryConfig = null;
287
287
  }
288
- if (typeof endpoint.scheduleTrigger.attemptDeadlineSeconds !== "undefined") {
289
- const attemptDeadlineSeconds = r.resolveInt(endpoint.scheduleTrigger.attemptDeadlineSeconds);
290
- if (attemptDeadlineSeconds !== null &&
291
- !backend.isValidAttemptDeadline(attemptDeadlineSeconds)) {
292
- throw new error_1.FirebaseError(`attemptDeadlineSeconds must be between ${backend.MIN_ATTEMPT_DEADLINE_SECONDS} and ${backend.MAX_ATTEMPT_DEADLINE_SECONDS} seconds (inclusive).`);
293
- }
294
- bkSchedule.attemptDeadlineSeconds = attemptDeadlineSeconds;
295
- }
296
288
  return { scheduleTrigger: bkSchedule };
297
289
  }
298
290
  else if ("taskQueueTrigger" in endpoint) {
@@ -143,7 +143,6 @@ function assertBuildEndpoint(ep, id) {
143
143
  schedule: "Field<string>",
144
144
  timeZone: "Field<string>?",
145
145
  retryConfig: "object?",
146
- attemptDeadlineSeconds: "Field<number>?",
147
146
  });
148
147
  if (ep.scheduleTrigger.retryConfig) {
149
148
  (0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger.retryConfig", ep.scheduleTrigger.retryConfig, {
@@ -240,7 +239,6 @@ function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
240
239
  else if (ep.scheduleTrigger.retryConfig === null) {
241
240
  st.retryConfig = null;
242
241
  }
243
- (0, proto_1.copyIfPresent)(st, ep.scheduleTrigger, "attemptDeadlineSeconds");
244
242
  triggered = { scheduleTrigger: st };
245
243
  }
246
244
  else if (build.isTaskQueueTriggered(ep)) {
@@ -61,7 +61,7 @@ exports.RUNTIMES = runtimes({
61
61
  },
62
62
  nodejs24: {
63
63
  friendly: "Node.js 24",
64
- status: "beta",
64
+ status: "GA",
65
65
  deprecationDate: "2028-04-30",
66
66
  decommissionDate: "2028-10-31",
67
67
  },
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.cpuConfigIsValid = exports.endpointsAreValid = void 0;
3
+ exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.cpuConfigIsValid = exports.endpointsAreValid = exports.MAX_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS = exports.DEFAULT_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS = void 0;
4
4
  const path = require("path");
5
5
  const clc = require("colorette");
6
6
  const error_1 = require("../../error");
@@ -11,6 +11,8 @@ const backend = require("./backend");
11
11
  const utils = require("../../utils");
12
12
  const secrets = require("../../functions/secrets");
13
13
  const services_1 = require("./services");
14
+ exports.DEFAULT_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS = 180;
15
+ exports.MAX_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS = 1800;
14
16
  function matchingIds(endpoints, filter) {
15
17
  return endpoints
16
18
  .filter(filter)
@@ -24,10 +26,21 @@ const cpu = (endpoint) => {
24
26
  ? backend.memoryToGen1Cpu(mem(endpoint))
25
27
  : (_a = endpoint.cpu) !== null && _a !== void 0 ? _a : backend.memoryToGen2Cpu(mem(endpoint));
26
28
  };
29
+ function validateScheduledTimeout(ep) {
30
+ if (backend.isScheduleTriggered(ep) &&
31
+ ep.timeoutSeconds &&
32
+ ep.timeoutSeconds > exports.MAX_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS) {
33
+ utils.logLabeledWarning("functions", `Scheduled function ${ep.id} has a timeout of ${ep.timeoutSeconds} seconds, ` +
34
+ `which exceeds the maximum attempt deadline of ${exports.MAX_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS} seconds for Cloud Scheduler. ` +
35
+ "This is probably not what you want! Having a timeout longer than the attempt deadline may lead to unexpected retries and multiple function executions. " +
36
+ `The attempt deadline will be capped at ${exports.MAX_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS} seconds.`);
37
+ }
38
+ }
27
39
  function endpointsAreValid(wantBackend) {
28
40
  const endpoints = backend.allEndpoints(wantBackend);
29
41
  functionIdsAreValid(endpoints);
30
42
  for (const ep of endpoints) {
43
+ validateScheduledTimeout(ep);
31
44
  (0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, wantBackend);
32
45
  }
33
46
  const gcfV1WithConcurrency = matchingIds(endpoints, (endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1");
@@ -23,7 +23,7 @@ const DataConnectTarget = require("./dataconnect");
23
23
  const AppHostingTarget = require("./apphosting");
24
24
  const frameworks_1 = require("../frameworks");
25
25
  const prepare_1 = require("./hosting/prepare");
26
- const github_1 = require("../init/features/hosting/github");
26
+ const utils_2 = require("../utils");
27
27
  const deploy_1 = require("../commands/deploy");
28
28
  const requirePermissions_1 = require("../requirePermissions");
29
29
  const context_1 = require("./dataconnect/context");
@@ -86,7 +86,7 @@ const deploy = async function (targetNames, options, customContext = {}) {
86
86
  await (0, requirePermissions_1.requirePermissions)(options, deploy_1.TARGET_PERMISSIONS["functions"]);
87
87
  }
88
88
  catch (e) {
89
- if ((0, github_1.isRunningInGithubAction)()) {
89
+ if ((0, utils_2.isRunningInGithubAction)()) {
90
90
  throw new error_1.FirebaseError("It looks like you are deploying a Hosting site along with Cloud Functions " +
91
91
  "using a GitHub action version that did not include Cloud Functions " +
92
92
  "permissions. Please reinstall the GitHub action with" +
@@ -54,36 +54,36 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.17.2",
58
- "expectedSize": 30012256,
59
- "expectedChecksum": "9e8d43abb6e93f4c969088078fdaf780",
60
- "expectedChecksumSHA256": "6001b86795f727f90755b497cba7ebcd2f50d024b0e56d1d6e957fcb98ff463c",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v2.17.2",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.2"
57
+ "version": "2.17.3",
58
+ "expectedSize": 30053216,
59
+ "expectedChecksum": "b9bc9fefd65143b8ed3a5b0f0e1f26fa",
60
+ "expectedChecksumSHA256": "03766907fdbd513e333602f989ebaf486cd53a73d9cdefd221a02988f757a7cc",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v2.17.3",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.3"
63
63
  },
64
64
  "darwin_arm64": {
65
- "version": "2.17.2",
66
- "expectedSize": 29475730,
67
- "expectedChecksum": "1a82213baded25c2a6c584a72a5600f2",
68
- "expectedChecksumSHA256": "c51a6810e975e13ac5ea5f038addf259da8b0d17b0b739dab1267dca133e5a54",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v2.17.2",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.2"
65
+ "version": "2.17.3",
66
+ "expectedSize": 29510226,
67
+ "expectedChecksum": "a585f578ac6891366059a6c4814b10cc",
68
+ "expectedChecksumSHA256": "c1b6ad2976d6399435a11a9079a5e63e19b0af04c792cdb843a7464be793618c",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v2.17.3",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.3"
71
71
  },
72
72
  "win32": {
73
- "version": "2.17.2",
74
- "expectedSize": 30507008,
75
- "expectedChecksum": "1a071a45267279463516f8264aaa5a97",
76
- "expectedChecksumSHA256": "8f84b312a049f80e2f9466eb1f77d15e1dedec8ce0696c51fc221fbb3b436eee",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v2.17.2",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.2.exe"
73
+ "version": "2.17.3",
74
+ "expectedSize": 30547968,
75
+ "expectedChecksum": "5a9d3490a1de85978441afff8582e7c3",
76
+ "expectedChecksumSHA256": "8f2b8d38cbad8138cc33479456c91c5e4b046c689a4209a63fbb0c4dca9c6af8",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v2.17.3",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.3.exe"
79
79
  },
80
80
  "linux": {
81
- "version": "2.17.2",
82
- "expectedSize": 29937848,
83
- "expectedChecksum": "70bc4fc8eecc1c1c6dd4f404ed7e2b89",
84
- "expectedChecksumSHA256": "9b90874daf84f0cfb7ae72f3a8c4ccec523593ccd318cd967f5bef7d574efa77",
85
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v2.17.2",
86
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.2"
81
+ "version": "2.17.3",
82
+ "expectedSize": 29974712,
83
+ "expectedChecksum": "aacf4f08de500df0913ff8ec6261a0ad",
84
+ "expectedChecksumSHA256": "2d593500745c4ce0d522d6f5f75c89ed413ad4853891b8afafb5cbfbb45d4abd",
85
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v2.17.3",
86
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.3"
87
87
  }
88
88
  }
89
89
  }
@@ -6,7 +6,7 @@ const leven = require("leven");
6
6
  const path_1 = require("path");
7
7
  const configstore_1 = require("./configstore");
8
8
  const error_1 = require("./error");
9
- const github_1 = require("./init/features/hosting/github");
9
+ const utils_1 = require("./utils");
10
10
  function experiments(exp) {
11
11
  return Object.freeze(exp);
12
12
  }
@@ -45,6 +45,10 @@ exports.ALL_EXPERIMENTS = experiments({
45
45
  shortDescription: "Functions created using the V2 API target Cloud Run Functions (not production ready)",
46
46
  public: false,
47
47
  },
48
+ functionsrunapionly: {
49
+ shortDescription: "Use Cloud Run API to list v2 functions",
50
+ public: false,
51
+ },
48
52
  emulatoruisnapshot: {
49
53
  shortDescription: "Load pre-release versions of the emulator UI",
50
54
  },
@@ -179,7 +183,7 @@ function assertEnabled(name, task) {
179
183
  var _a;
180
184
  if (!isEnabled(name)) {
181
185
  const prefix = `Cannot ${task} because the experiment ${(0, colorette_1.bold)(name)} is not enabled.`;
182
- if ((0, github_1.isRunningInGithubAction)()) {
186
+ if ((0, utils_1.isRunningInGithubAction)()) {
183
187
  const path = (_a = process.env.GITHUB_WORKFLOW_REF) === null || _a === void 0 ? void 0 : _a.split("@")[0];
184
188
  const filename = path ? `.github/workflows/${(0, path_1.basename)(path)}` : "your action's yml";
185
189
  const newValue = [process.env.FIREBASE_CLI_EXPERIMENTS, name].filter((it) => !!it).join(",");
@@ -179,6 +179,30 @@ async function build(dir, target, context) {
179
179
  destination,
180
180
  }));
181
181
  const wantsBackend = reasonsForBackend.size > 0;
182
+ if (wantsBackend && (0, utils_2.isUsingAppDirectory)((0, path_1.join)(dir, distDir))) {
183
+ const nextVersion = (0, utils_2.getNextVersionRaw)(dir);
184
+ if (nextVersion && (0, utils_2.isNextJsVersionVulnerable)(nextVersion)) {
185
+ let message = `Next.js version ${nextVersion} is vulnerable to CVE-2025-66478.\n` +
186
+ `Please upgrade to a patched version: `;
187
+ const { major } = (0, semver_1.coerce)(nextVersion) || {};
188
+ if (major === 16) {
189
+ message += "16.0.7+.";
190
+ }
191
+ else if (major === 15) {
192
+ message += "15.0.5+, 15.1.9+, 15.2.6+, 15.3.6+, 15.4.8+, or 15.5.7+.";
193
+ }
194
+ else if (major === 14) {
195
+ message += "downgrade to a stable Next.js 14.x release.";
196
+ }
197
+ else {
198
+ message +=
199
+ "15.0.5+, 15.1.9+, 15.2.6+, 15.3.6+, 15.4.8+, 15.5.7+, 16.0.7+ " +
200
+ "or downgrade to a stable Next.js 14.x release if using canary.";
201
+ }
202
+ message += `\nSee https://nextjs.org/blog/CVE-2025-66478 for more details.`;
203
+ throw new error_1.FirebaseError(message);
204
+ }
205
+ }
182
206
  if (wantsBackend) {
183
207
  logger_1.logger.info("Building a Cloud Function to run this application. This is needed due to:");
184
208
  for (const reason of Array.from(reasonsForBackend).slice(0, DEFAULT_NUMBER_OF_REASONS_TO_LIST)) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.installEsbuild = exports.getGlobalEsbuildVersion = exports.findEsbuildPath = exports.whichNextConfigFile = exports.getProductionDistDirFiles = exports.getRoutesWithServerAction = exports.hasStaticAppNotFoundComponent = exports.getNextVersion = exports.getBuildId = exports.getAppMetadataFromMetaFiles = exports.getNonStaticServerComponents = exports.getNonStaticRoutes = exports.getMiddlewareMatcherRegexes = exports.allDependencyNames = exports.isUsingAppDirectory = exports.isUsingNextImageInAppDirectory = exports.isUsingImageOptimization = exports.isUsingMiddleware = exports.hasUnoptimizedImage = exports.usesNextImage = exports.usesAppDirRouter = exports.getNextjsRewritesToUse = exports.isHeaderSupportedByHosting = exports.isRedirectSupportedByHosting = exports.isRewriteSupportedByHosting = exports.cleanI18n = exports.cleanCustomRouteI18n = exports.cleanEscapedChars = exports.I18N_SOURCE = void 0;
3
+ exports.isNextJsVersionVulnerable = exports.installEsbuild = exports.getGlobalEsbuildVersion = exports.findEsbuildPath = exports.whichNextConfigFile = exports.getProductionDistDirFiles = exports.getRoutesWithServerAction = exports.hasStaticAppNotFoundComponent = exports.getNextVersionRaw = exports.getNextVersion = exports.getBuildId = exports.getAppMetadataFromMetaFiles = exports.getNonStaticServerComponents = exports.getNonStaticRoutes = exports.getMiddlewareMatcherRegexes = exports.allDependencyNames = exports.isUsingAppDirectory = exports.isUsingNextImageInAppDirectory = exports.isUsingImageOptimization = exports.isUsingMiddleware = exports.hasUnoptimizedImage = exports.usesNextImage = exports.usesAppDirRouter = exports.getNextjsRewritesToUse = exports.isHeaderSupportedByHosting = exports.isRedirectSupportedByHosting = exports.isRewriteSupportedByHosting = exports.cleanI18n = exports.cleanCustomRouteI18n = exports.cleanEscapedChars = exports.I18N_SOURCE = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const fs_extra_1 = require("fs-extra");
6
6
  const path_1 = require("path");
@@ -206,6 +206,11 @@ function getNextVersion(cwd) {
206
206
  return nextVersionSemver.toString();
207
207
  }
208
208
  exports.getNextVersion = getNextVersion;
209
+ function getNextVersionRaw(cwd) {
210
+ const dependency = (0, utils_1.findDependency)("next", { cwd, depth: 0, omitDev: false });
211
+ return dependency === null || dependency === void 0 ? void 0 : dependency.version;
212
+ }
213
+ exports.getNextVersionRaw = getNextVersionRaw;
209
214
  async function hasStaticAppNotFoundComponent(sourceDir, distDir) {
210
215
  return (0, fs_extra_1.pathExists)((0, path_1.join)(sourceDir, distDir, "server", "app", "_not-found.html"));
211
216
  }
@@ -296,3 +301,38 @@ function installEsbuild(version) {
296
301
  }
297
302
  }
298
303
  exports.installEsbuild = installEsbuild;
304
+ function isNextJsVersionVulnerable(versionStr) {
305
+ const v = (0, semver_1.parse)(versionStr);
306
+ if (!v)
307
+ return false;
308
+ if (v.major === 15) {
309
+ if (v.minor === 0)
310
+ return (0, semver_1.lt)(versionStr, "15.0.5");
311
+ if (v.minor === 1)
312
+ return (0, semver_1.lt)(versionStr, "15.1.9");
313
+ if (v.minor === 2)
314
+ return (0, semver_1.lt)(versionStr, "15.2.6");
315
+ if (v.minor === 3)
316
+ return (0, semver_1.lt)(versionStr, "15.3.6");
317
+ if (v.minor === 4)
318
+ return (0, semver_1.lt)(versionStr, "15.4.8");
319
+ if (v.minor === 5)
320
+ return (0, semver_1.lt)(versionStr, "15.5.7");
321
+ return false;
322
+ }
323
+ if (v.major === 16) {
324
+ if (v.minor === 0)
325
+ return (0, semver_1.lt)(versionStr, "16.0.7");
326
+ return false;
327
+ }
328
+ if (v.major === 14) {
329
+ const pre = (0, semver_1.prerelease)(versionStr);
330
+ if (pre && pre.includes("canary")) {
331
+ if ((0, semver_1.gte)(versionStr, "14.3.0-canary.77")) {
332
+ return true;
333
+ }
334
+ }
335
+ }
336
+ return false;
337
+ }
338
+ exports.isNextJsVersionVulnerable = isNextJsVersionVulnerable;
@@ -6,10 +6,11 @@ const error_1 = require("../error");
6
6
  const logger_1 = require("../logger");
7
7
  const api_1 = require("../api");
8
8
  const apiv2_1 = require("../apiv2");
9
+ const functional_1 = require("../functional");
10
+ const validate_1 = require("../deploy/functions/validate");
9
11
  const backend = require("../deploy/functions/backend");
10
12
  const proto = require("./proto");
11
13
  const gce = require("../gcp/computeEngine");
12
- const functional_1 = require("../functional");
13
14
  const VERSION = "v1";
14
15
  const DEFAULT_TIME_ZONE_V1 = "America/Los_Angeles";
15
16
  const DEFAULT_TIME_ZONE_V2 = "UTC";
@@ -145,7 +146,13 @@ async function jobFromEndpoint(endpoint, location, projectNumber) {
145
146
  }
146
147
  job.schedule = endpoint.scheduleTrigger.schedule;
147
148
  if (endpoint.platform === "gcfv2" || endpoint.platform === "run") {
148
- proto.convertIfPresent(job, endpoint.scheduleTrigger, "attemptDeadline", "attemptDeadlineSeconds", (0, functional_1.nullsafeVisitor)(proto.durationFromSeconds));
149
+ proto.convertIfPresent(job, endpoint, "attemptDeadline", "timeoutSeconds", (timeout) => {
150
+ if (timeout === null) {
151
+ return null;
152
+ }
153
+ const attemptDeadlineSeconds = Math.max(Math.min(timeout, validate_1.MAX_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS), validate_1.DEFAULT_V2_SCHEDULE_ATTEMPT_DEADLINE_SECONDS);
154
+ return proto.durationFromSeconds(attemptDeadlineSeconds);
155
+ });
149
156
  }
150
157
  if (endpoint.scheduleTrigger.retryConfig) {
151
158
  job.retryConfig = {};
package/lib/gcp/runv2.js CHANGED
@@ -11,7 +11,7 @@ const backend = require("../deploy/functions/backend");
11
11
  const constants_1 = require("../functions/constants");
12
12
  const k8s_1 = require("./k8s");
13
13
  const supported_1 = require("../deploy/functions/runtimes/supported");
14
- const __1 = require("..");
14
+ const logger_1 = require("../logger");
15
15
  const functional_1 = require("../functional");
16
16
  exports.API_VERSION = "v2";
17
17
  const client = new apiv2_1.Client({
@@ -99,7 +99,7 @@ function endpointFromService(service) {
99
99
  svcId;
100
100
  const memory = (0, k8s_1.mebibytes)(service.template.containers[0].resources.limits.memory);
101
101
  if (!backend.isValidMemoryOption(memory)) {
102
- __1.logger.debug("Converting a service to an endpoint with an invalid memory option", memory);
102
+ logger_1.logger.debug("Converting a service to an endpoint with an invalid memory option", memory);
103
103
  }
104
104
  const cpu = Number(service.template.containers[0].resources.limits.cpu);
105
105
  const endpoint = Object.assign({ platform: ((_e = service.labels) === null || _e === void 0 ? void 0 : _e[exports.CLIENT_NAME_LABEL]) === "cloud-functions" ? "gcfv2" : "run", id,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDownloadUrl = exports.getServiceAccount = exports.listBuckets = exports.upsertBucket = exports.randomString = exports.patchBucket = exports.createBucket = exports.getBucket = exports.deleteObject = exports.getObject = exports.uploadObject = exports.upload = exports.getDefaultBucket = void 0;
3
+ exports.getDownloadUrl = exports.getServiceAccount = exports.listBuckets = exports.upsertBucket = exports.randomString = exports.patchBucket = exports.createBucket = exports.getBucket = exports.deleteObject = exports.getObject = exports.uploadObject = exports.upload = exports.getDefaultBucket = exports.ContentType = void 0;
4
4
  const path = require("path");
5
5
  const clc = require("colorette");
6
6
  const crypto_1 = require("crypto");
@@ -11,6 +11,11 @@ const logger_1 = require("../logger");
11
11
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
12
12
  const utils = require("../utils");
13
13
  const proto_1 = require("./proto");
14
+ var ContentType;
15
+ (function (ContentType) {
16
+ ContentType["ZIP"] = "ZIP";
17
+ ContentType["TAR"] = "TAR";
18
+ })(ContentType = exports.ContentType || (exports.ContentType = {}));
14
19
  async function getDefaultBucket(projectId) {
15
20
  var _a;
16
21
  await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.firebaseStorageOrigin)(), "storage", false);
@@ -54,9 +59,17 @@ async function upload(source, uploadUrl, extraHeaders, ignoreQuotaProject) {
54
59
  };
55
60
  }
56
61
  exports.upload = upload;
57
- async function uploadObject(source, bucketName) {
58
- if (path.extname(source.file) !== ".zip") {
59
- throw new error_1.FirebaseError(`Expected a file name ending in .zip, got ${source.file}`);
62
+ async function uploadObject(source, bucketName, contentType) {
63
+ switch (contentType) {
64
+ case ContentType.TAR:
65
+ if (!source.file.endsWith(".tar.gz")) {
66
+ throw new error_1.FirebaseError(`Expected a file name ending in .tar.gz, got ${source.file}`);
67
+ }
68
+ break;
69
+ default:
70
+ if (path.extname(source.file) !== ".zip") {
71
+ throw new error_1.FirebaseError(`Expected a file name ending in .zip, got ${source.file}`);
72
+ }
60
73
  }
61
74
  const localAPIClient = new apiv2_1.Client({ urlPrefix: (0, api_1.storageOrigin)() });
62
75
  const location = `/${bucketName}/${path.basename(source.file)}`;
@@ -64,7 +77,7 @@ async function uploadObject(source, bucketName) {
64
77
  method: "PUT",
65
78
  path: location,
66
79
  headers: {
67
- "Content-Type": "application/zip",
80
+ "Content-Type": contentType === ContentType.TAR ? "application/octet-stream" : "application/zip",
68
81
  "x-goog-content-length-range": "0,123289600",
69
82
  },
70
83
  body: source.stream,
@@ -463,10 +463,6 @@ async function promptForCloudSQL(setup, info) {
463
463
  }
464
464
  if (freeTrialUsed) {
465
465
  (0, utils_1.logLabeledWarning)("dataconnect", "CloudSQL no cost trial has already been used on this project.");
466
- if (!billingEnabled) {
467
- setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project", true));
468
- return;
469
- }
470
466
  }
471
467
  else if (instrumentlessTrialEnabled || billingEnabled) {
472
468
  (0, utils_1.logLabeledSuccess)("dataconnect", "CloudSQL no cost trial available!");
@@ -487,7 +483,11 @@ async function promptForCloudSQL(setup, info) {
487
483
  choices.push({ name: "Create a new free trial instance", value: "", location: "" });
488
484
  }
489
485
  else {
490
- choices.push({ name: "Create a new CloudSQL instance", value: "", location: "" });
486
+ choices.push({
487
+ name: `Create a new CloudSQL instance${billingEnabled ? "" : " (requires billing account)"}`,
488
+ value: "",
489
+ location: "",
490
+ });
491
491
  }
492
492
  info.cloudSqlInstanceId = await (0, prompt_1.select)({
493
493
  message: `Which CloudSQL instance would you like to use?`,
@@ -499,6 +499,10 @@ async function promptForCloudSQL(setup, info) {
499
499
  }
500
500
  else {
501
501
  info.flow += "_pick_new_csql";
502
+ if (!billingEnabled && freeTrialUsed) {
503
+ setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project", true));
504
+ return;
505
+ }
502
506
  info.cloudSqlInstanceId = await (0, prompt_1.input)({
503
507
  message: `What ID would you like to use for your new CloudSQL instance?`,
504
508
  default: (0, utils_1.newUniqueId)(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isRunningInGithubAction = exports.initGitHub = void 0;
3
+ exports.initGitHub = void 0;
4
4
  const colorette_1 = require("colorette");
5
5
  const fs = require("fs");
6
6
  const yaml = require("yaml");
@@ -389,7 +389,3 @@ async function encryptServiceAccountJSON(serviceAccountJSON, key) {
389
389
  const encryptedBytes = libsodium.crypto_box_seal(messageBytes, keyBytes);
390
390
  return Buffer.from(encryptedBytes).toString("base64");
391
391
  }
392
- function isRunningInGithubAction() {
393
- return process.env.GITHUB_ACTION_REPOSITORY === HOSTING_GITHUB_ACTION_NAME.split("@")[0];
394
- }
395
- exports.isRunningInGithubAction = isRunningInGithubAction;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.get_environment = void 0;
3
+ exports.get_environment = exports.hydrateTemplate = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const tool_1 = require("../../tool");
6
6
  const util_1 = require("../../util");
@@ -9,6 +9,48 @@ const js_yaml_1 = require("js-yaml");
9
9
  const auth_1 = require("../../../auth");
10
10
  const configstore_1 = require("../../../configstore");
11
11
  const appUtils_1 = require("../../../appUtils");
12
+ function hydrateTemplate(config) {
13
+ const activeProject = config.projectId
14
+ ? `${config.projectId}${config.projectAliases.length ? ` (alias: ${config.projectAliases.join(",")})` : ""}`
15
+ : "<NONE>";
16
+ const projectConfigPath = config.projectConfigPath || "<NO CONFIG PRESENT>";
17
+ const geminiTosAccepted = config.geminiTosAccepted ? "Accepted" : "<NOT ACCEPTED>";
18
+ const authenticatedUser = config.authenticatedUser || "<NONE>";
19
+ const detectedApps = Object.entries(config.detectedAppIds).length > 0
20
+ ? `\n\n${(0, js_yaml_1.dump)(config.detectedAppIds).trim()}\n`
21
+ : "<NONE>";
22
+ const availableProjects = Object.entries(config.projectAliasMap).length > 0
23
+ ? `\n\n${(0, js_yaml_1.dump)(config.projectAliasMap)}`
24
+ : "<NONE>";
25
+ const hasOtherAccounts = config.allAccounts.filter((email) => email !== config.authenticatedUser).length > 0;
26
+ const availableAccounts = hasOtherAccounts ? `${(0, js_yaml_1.dump)(config.allAccounts).trim()}` : "";
27
+ return `# Environment Information
28
+
29
+ Project Directory: ${config.projectDir}
30
+ Project Config Path: ${projectConfigPath}
31
+ Active Project ID: ${activeProject}
32
+ Gemini in Firebase Terms of Service: ${geminiTosAccepted}
33
+ Authenticated User: ${authenticatedUser}
34
+ Detected App IDs: ${detectedApps}
35
+ Available Project Aliases (format: '[alias]: [projectId]'): ${availableProjects}${hasOtherAccounts ? `\nAvailable Accounts: \n\n${availableAccounts}` : ""}
36
+ ${config.projectFileContents
37
+ ? `\nfirebase.json contents:
38
+
39
+ \`\`\`json
40
+ ${config.projectFileContents}
41
+ \`\`\``
42
+ : `\nNo firebase.json file was found.
43
+
44
+ If this project does not use Firebase services that require a firebase.json file, no action is necessary.
45
+
46
+ If this project uses Firebase services that require a firebase.json file, the user will most likely want to:
47
+
48
+ a) Change the project directory using the 'firebase_update_environment' tool to select a directory with a 'firebase.json' file in it, or
49
+ b) Initialize a new Firebase project directory using the 'firebase_init' tool.
50
+
51
+ Confirm with the user before taking action.`}`;
52
+ }
53
+ exports.hydrateTemplate = hydrateTemplate;
12
54
  exports.get_environment = (0, tool_1.tool)("core", {
13
55
  name: "get_environment",
14
56
  description: "Use this to retrieve the current Firebase **environment** configuration for the Firebase CLI and Firebase MCP server, including current authenticated user, project directory, active Firebase Project, and more.",
@@ -27,45 +69,26 @@ exports.get_environment = (0, tool_1.tool)("core", {
27
69
  const projectFileExists = config.projectFileExists("firebase.json");
28
70
  const detectedApps = await (0, appUtils_1.detectApps)(process.cwd());
29
71
  const allAccounts = (0, auth_1.getAllAccounts)().map((account) => account.user.email);
30
- const hasOtherAccounts = allAccounts.filter((email) => email !== accountEmail).length > 0;
31
- const projectConfigPathString = projectFileExists
32
- ? config.path("firebase.json")
33
- : "<NO CONFIG PRESENT>";
34
- const detectedAppsMap = detectedApps
72
+ const detectedAppsMap = {};
73
+ detectedApps
35
74
  .filter((app) => !!app.appId)
36
75
  .reduce((map, app) => {
37
76
  if (app.appId) {
38
- map.set(app.appId, app.bundleId ? app.bundleId : "<UNKNOWN BUNDLE ID>");
77
+ map[app.appId] = app.bundleId ? app.bundleId : "<UNKNOWN BUNDLE ID>";
39
78
  }
40
79
  return map;
41
- }, new Map());
42
- const activeProjectString = projectId
43
- ? `${projectId}${aliases.length ? ` (alias: ${aliases.join(",")})` : ""}`
44
- : "<NONE>";
45
- const acceptedGeminiTosString = geminiTosAccepted ? "Accepted" : "<NOT ACCEPTED>";
46
- return (0, util_1.toContent)(`# Environment Information
47
-
48
- Project Directory: ${host.cachedProjectDir}
49
- Project Config Path: ${projectConfigPathString}
50
- Active Project ID: ${activeProjectString}
51
- Gemini in Firebase Terms of Service: ${acceptedGeminiTosString}
52
- Authenticated User: ${accountEmail || "<NONE>"}
53
- Detected App IDs: ${detectedAppsMap.size > 0 ? `\n\n${(0, js_yaml_1.dump)(Object.fromEntries(detectedAppsMap)).trim()}\n` : "<NONE>"}
54
- Available Project Aliases (format: '[alias]: [projectId]'): ${Object.entries(rc.projects).length > 0 ? `\n\n${(0, js_yaml_1.dump)(rc.projects).trim()}\n` : "<NONE>"}${hasOtherAccounts ? `\nAvailable Accounts: \n\n${(0, js_yaml_1.dump)(allAccounts).trim()}` : ""}
55
- ${projectFileExists
56
- ? `\nfirebase.json contents:
57
-
58
- \`\`\`json
59
- ${config.readProjectFile("firebase.json")}
60
- \`\`\``
61
- : `\nNo firebase.json file was found.
62
-
63
- If this project does not use Firebase services that require a firebase.json file, no action is necessary.
64
-
65
- If this project uses Firebase services that require a firebase.json file, the user will most likely want to:
66
-
67
- a) Change the project directory using the 'firebase_update_environment' tool to select a directory with a 'firebase.json' file in it, or
68
- b) Initialize a new Firebase project directory using the 'firebase_init' tool.
69
-
70
- Confirm with the user before taking action.`}`);
80
+ }, detectedAppsMap);
81
+ const environmentTemplateConfig = {
82
+ projectId,
83
+ projectAliases: aliases,
84
+ projectDir: host.cachedProjectDir,
85
+ projectConfigPath: projectFileExists ? config.path("firebase.json") : undefined,
86
+ geminiTosAccepted,
87
+ authenticatedUser: accountEmail || undefined,
88
+ projectAliasMap: rc.projects,
89
+ allAccounts,
90
+ detectedAppIds: detectedAppsMap,
91
+ projectFileContents: projectFileExists ? config.readProjectFile("firebase.json") : undefined,
92
+ };
93
+ return (0, util_1.toContent)(hydrateTemplate(environmentTemplateConfig));
71
94
  });
@@ -46,22 +46,10 @@ async function androidAppUsesCrashlytics(appPath) {
46
46
  return false;
47
47
  }
48
48
  async function iosAppUsesCrashlytics(appPath) {
49
- const podfiles = await (0, appUtils_1.detectFiles)(appPath, "Podfile");
50
- for (const file of podfiles) {
51
- const content = await fs.readFile(path.join(appPath, file), "utf8");
52
- if (content.includes("Crashlytics")) {
53
- return true;
54
- }
55
- }
56
- const swiftPackageFiles = await (0, appUtils_1.detectFiles)(appPath, "Package.swift");
57
- for (const file of swiftPackageFiles) {
58
- const content = await fs.readFile(path.join(appPath, file), "utf8");
59
- if (content.includes("Crashlytics")) {
60
- return true;
61
- }
62
- }
63
- const cartFiles = await (0, appUtils_1.detectFiles)(appPath, "Cartfile*");
64
- for (const file of cartFiles) {
49
+ const filePatternsToDetect = ["Podfile", "Package.swift", "Cartfile*", "project.pbxproj"];
50
+ const fileArrays = await Promise.all(filePatternsToDetect.map((term) => (0, appUtils_1.detectFiles)(appPath, term)));
51
+ const files = fileArrays.flat();
52
+ for (const file of files) {
65
53
  const content = await fs.readFile(path.join(appPath, file), "utf8");
66
54
  if (content.includes("Crashlytics")) {
67
55
  return true;
package/lib/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.openInBrowserPopup = exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.tryParse = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarningToStderr = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
4
- exports.resolveWithin = exports.commandExistsSync = exports.newUniqueId = exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
3
+ exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInGithubAction = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.tryParse = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarningToStderr = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
4
+ exports.resolveWithin = exports.commandExistsSync = exports.newUniqueId = exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = void 0;
5
5
  const fs = require("fs-extra");
6
6
  const tty = require("tty");
7
7
  const path = require("node:path");
@@ -367,6 +367,10 @@ function isRunningInWSL() {
367
367
  return !!process.env.WSL_DISTRO_NAME;
368
368
  }
369
369
  exports.isRunningInWSL = isRunningInWSL;
370
+ function isRunningInGithubAction() {
371
+ return process.env.GITHUB_ACTION_REPOSITORY === "FirebaseExtended/action-hosting-deploy";
372
+ }
373
+ exports.isRunningInGithubAction = isRunningInGithubAction;
370
374
  function thirtyDaysFromNow() {
371
375
  return new Date(Date.now() + THIRTY_DAYS_IN_MILLISECONDS);
372
376
  }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "14.26.0",
3
+ "version": "14.27.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
+ "mcpName": "io.github.firebase/firebase-mcp",
6
7
  "bin": {
7
8
  "firebase": "./lib/bin/firebase.js"
8
9
  },