firebase-tools 14.25.1 → 14.26.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.
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.upgradeInstructions = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
4
4
  const clc = require("colorette");
5
5
  const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
6
- const utils = require("../utils");
7
6
  function freeTrialTermsLink() {
8
7
  return "https://firebase.google.com/pricing";
9
8
  }
@@ -27,17 +26,11 @@ async function checkFreeTrialInstanceUsed(projectId) {
27
26
  catch (err) {
28
27
  used = false;
29
28
  }
30
- if (used) {
31
- utils.logLabeledWarning("dataconnect", "CloudSQL no cost trial has already been used on this project.");
32
- }
33
- else {
34
- utils.logLabeledSuccess("dataconnect", "CloudSQL no cost trial available!");
35
- }
36
29
  return used;
37
30
  }
38
31
  exports.checkFreeTrialInstanceUsed = checkFreeTrialInstanceUsed;
39
- function upgradeInstructions(projectId) {
40
- return `To provision a CloudSQL Postgres instance on the Firebase Data Connect no-cost trial:
32
+ function upgradeInstructions(projectId, trialUsed) {
33
+ return `To provision a ${trialUsed ? "paid CloudSQL Postgres instance" : "CloudSQL Postgres instance on the Firebase Data Connect no-cost trial"}:
41
34
 
42
35
  1. Please upgrade to the pay-as-you-go (Blaze) billing plan. Visit the following page:
43
36
 
@@ -9,6 +9,7 @@ const freeTrial_1 = require("./freeTrial");
9
9
  const utils_1 = require("../utils");
10
10
  const track_1 = require("../track");
11
11
  const utils = require("../utils");
12
+ const cloudbilling_1 = require("../gcp/cloudbilling");
12
13
  const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
13
14
  async function setupCloudSql(args) {
14
15
  var _a;
@@ -86,19 +87,22 @@ async function createInstance(args) {
86
87
  enableGoogleMlIntegration: requireGoogleMlIntegration,
87
88
  freeTrialLabel,
88
89
  });
89
- utils.logLabeledBullet("dataconnect", cloudSQLBeingCreated(projectId, instanceId, freeTrialLabel === "ft"));
90
+ utils.logLabeledBullet("dataconnect", cloudSQLBeingCreated(projectId, instanceId, freeTrialLabel === "ft", await (0, cloudbilling_1.checkBillingEnabled)(projectId)));
90
91
  }
91
92
  }
92
- function cloudSQLBeingCreated(projectId, instanceId, includeFreeTrialToS) {
93
+ function cloudSQLBeingCreated(projectId, instanceId, isFreeTrial, billingEnabled) {
93
94
  return (`Cloud SQL Instance ${clc.bold(instanceId)} is being created.` +
94
- (includeFreeTrialToS
95
+ (isFreeTrial
95
96
  ? `\nThis instance is provided under the terms of the Data Connect no-cost trial ${(0, freeTrial_1.freeTrialTermsLink)()}`
96
97
  : "") +
97
- `
98
- Meanwhile, your data are saved in a temporary database and will be migrated once complete. Monitor its progress at
99
-
100
- ${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}
101
- `);
98
+ `\n
99
+ Meanwhile, your data are saved in a temporary database and will be migrated once complete.` +
100
+ (isFreeTrial && !billingEnabled
101
+ ? `
102
+ Your free trial instance won't show in google cloud console until a billing account is added.
103
+ However, you can still use the gcloud cli to monitor your database instance:\n\n\te.g. gcloud sql instances list --project ${projectId}\n`
104
+ : `
105
+ Monitor its progress at\n\n\t${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}\n`));
102
106
  }
103
107
  exports.cloudSQLBeingCreated = cloudSQLBeingCreated;
104
108
  async function upsertDatabase(args) {
@@ -11,12 +11,11 @@ const ensureApis_1 = require("../../dataconnect/ensureApis");
11
11
  const requireTosAcceptance_1 = require("../../requireTosAcceptance");
12
12
  const firedata_1 = require("../../gcp/firedata");
13
13
  const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
14
- const cloudbilling_1 = require("../../gcp/cloudbilling");
15
14
  const names_1 = require("../../dataconnect/names");
16
15
  const error_1 = require("../../error");
17
16
  const types_1 = require("../../dataconnect/types");
18
17
  const schemaMigration_1 = require("../../dataconnect/schemaMigration");
19
- const freeTrial_1 = require("../../dataconnect/freeTrial");
18
+ const cloudbilling_1 = require("../../gcp/cloudbilling");
20
19
  const context_1 = require("./context");
21
20
  async function default_1(context, options) {
22
21
  var _a, _b, _c;
@@ -30,7 +29,6 @@ async function default_1(context, options) {
30
29
  const { serviceInfos, filters, deployStats } = context.dataconnect;
31
30
  if (!(await (0, cloudbilling_1.checkBillingEnabled)(projectId))) {
32
31
  deployStats.missingBilling = true;
33
- throw new error_1.FirebaseError((0, freeTrial_1.upgradeInstructions)(projectId));
34
32
  }
35
33
  await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
36
34
  for (const si of serviceInfos) {
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireFunctionsYaml = exports.getRemoteSource = void 0;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const url_1 = require("url");
7
+ const error_1 = require("../../error");
8
+ const logger_1 = require("../../logger");
9
+ const utils_1 = require("../../utils");
10
+ const fsutils_1 = require("../../fsutils");
11
+ const downloadUtils = require("../../downloadUtils");
12
+ const unzipModule = require("../../unzip");
13
+ async function getRemoteSource(repository, ref, destDir, subDir) {
14
+ logger_1.logger.debug(`Downloading remote source: ${repository}@${ref} (destDir: ${destDir}, subDir: ${subDir || "."})`);
15
+ const gitHubInfo = parseGitHubUrl(repository);
16
+ if (!gitHubInfo) {
17
+ throw new error_1.FirebaseError(`Could not parse GitHub repository URL: ${repository}. ` +
18
+ `Only GitHub repositories are supported.`);
19
+ }
20
+ let rootDir = destDir;
21
+ try {
22
+ logger_1.logger.debug(`Attempting to download via GitHub Archive API for ${repository}@${ref}...`);
23
+ const archiveUrl = `https://github.com/${gitHubInfo.owner}/${gitHubInfo.repo}/archive/${ref}.zip`;
24
+ const archivePath = await downloadUtils.downloadToTmp(archiveUrl);
25
+ logger_1.logger.debug(`Downloaded archive to ${archivePath}, unzipping...`);
26
+ await unzipModule.unzip(archivePath, destDir);
27
+ const files = fs.readdirSync(destDir);
28
+ if (files.length === 1 && fs.statSync(path.join(destDir, files[0])).isDirectory()) {
29
+ rootDir = path.join(destDir, files[0]);
30
+ logger_1.logger.debug(`Found top-level directory in archive: ${files[0]}`);
31
+ }
32
+ }
33
+ catch (err) {
34
+ throw new error_1.FirebaseError(`Failed to download GitHub archive for ${repository}@${ref}. ` +
35
+ `Make sure the repository is public and the ref exists. ` +
36
+ `Private repositories are not supported via this method.`, { original: err });
37
+ }
38
+ const sourceDir = subDir
39
+ ? (0, utils_1.resolveWithin)(rootDir, subDir, `Subdirectory '${subDir}' in remote source must not escape the repository root.`)
40
+ : rootDir;
41
+ if (subDir && !(0, fsutils_1.dirExistsSync)(sourceDir)) {
42
+ throw new error_1.FirebaseError(`Directory '${subDir}' not found in repository ${repository}@${ref}`);
43
+ }
44
+ const origin = `${repository}@${ref}${subDir ? `/${subDir}` : ""}`;
45
+ (0, utils_1.logLabeledBullet)("functions", `downloaded remote source (${origin})`);
46
+ return sourceDir;
47
+ }
48
+ exports.getRemoteSource = getRemoteSource;
49
+ function parseGitHubUrl(url) {
50
+ const shorthandMatch = /^[a-zA-Z0-9-]+\/[a-zA-Z0-9-_.]+$/.exec(url);
51
+ if (shorthandMatch) {
52
+ const [owner, repo] = url.split("/");
53
+ return { owner, repo };
54
+ }
55
+ try {
56
+ const u = new url_1.URL(url);
57
+ if (u.hostname !== "github.com") {
58
+ return undefined;
59
+ }
60
+ const parts = u.pathname.split("/").filter((p) => !!p);
61
+ if (parts.length < 2) {
62
+ return undefined;
63
+ }
64
+ const owner = parts[0];
65
+ let repo = parts[1];
66
+ if (repo.endsWith(".git")) {
67
+ repo = repo.slice(0, -4);
68
+ }
69
+ return { owner, repo };
70
+ }
71
+ catch (_a) {
72
+ return undefined;
73
+ }
74
+ }
75
+ function requireFunctionsYaml(codeDir) {
76
+ const functionsYamlPath = path.join(codeDir, "functions.yaml");
77
+ if (!(0, fsutils_1.fileExistsSync)(functionsYamlPath)) {
78
+ throw new error_1.FirebaseError(`The remote repository is missing a required deployment manifest (functions.yaml).\n\n` +
79
+ `For your security, Firebase requires a static manifest to deploy functions from a remote source. ` +
80
+ `This prevents the execution of arbitrary code on your machine during the function discovery process.\n\n` +
81
+ `If you trust this repository and want to use it anyway, clone the repository locally, inspect the code for safety, and deploy it as a local source.`);
82
+ }
83
+ }
84
+ exports.requireFunctionsYaml = requireFunctionsYaml;
@@ -59,6 +59,12 @@ exports.RUNTIMES = runtimes({
59
59
  deprecationDate: "2027-04-30",
60
60
  decommissionDate: "2028-10-31",
61
61
  },
62
+ nodejs24: {
63
+ friendly: "Node.js 24",
64
+ status: "beta",
65
+ deprecationDate: "2028-04-30",
66
+ decommissionDate: "2028-10-31",
67
+ },
62
68
  python310: {
63
69
  friendly: "Python 3.10",
64
70
  status: "GA",
@@ -54,36 +54,36 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.17.1",
58
- "expectedSize": 30004064,
59
- "expectedChecksum": "89314d496595b250e149d8705ccd2ddc",
60
- "expectedChecksumSHA256": "1e95733bf75fd433047345cb4c069ff4ddd2a2e296c53da3787dd708a26a8642",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v2.17.1",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.1"
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"
63
63
  },
64
64
  "darwin_arm64": {
65
- "version": "2.17.1",
66
- "expectedSize": 29476450,
67
- "expectedChecksum": "038125be57400e11f5013001cf68ce9e",
68
- "expectedChecksumSHA256": "a11f6ea1f8b8d80f502df6e9b34865fb12186b4157f2c282a0e6f99c53077ca2",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v2.17.1",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.1"
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"
71
71
  },
72
72
  "win32": {
73
- "version": "2.17.1",
74
- "expectedSize": 30496768,
75
- "expectedChecksum": "939a77bb9a549bc9460f888ac3442397",
76
- "expectedChecksumSHA256": "04d6859668c4afc1fa301f5cc892623669bcbf26ce5195bc80dc208783c33853",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v2.17.1",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.1.exe"
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"
79
79
  },
80
80
  "linux": {
81
- "version": "2.17.1",
82
- "expectedSize": 29925560,
83
- "expectedChecksum": "239d20f4aedf0bf6f79caca7453bf5cf",
84
- "expectedChecksumSHA256": "756fa6343a659a8a2606ee0df2a321ffaf47645fff8f4b209286681ee36820e3",
85
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v2.17.1",
86
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.17.1"
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"
87
87
  }
88
88
  }
89
89
  }
@@ -114,6 +114,11 @@ exports.ALL_EXPERIMENTS = experiments({
114
114
  default: false,
115
115
  public: true,
116
116
  },
117
+ fdcift: {
118
+ shortDescription: "Enable instrumentless trial for Data Connect",
119
+ public: false,
120
+ default: false,
121
+ },
117
122
  apptesting: {
118
123
  shortDescription: "Adds experimental App Testing feature",
119
124
  public: true,
package/lib/fsAsync.js CHANGED
@@ -19,7 +19,9 @@ async function readdirRecursiveHelper(options) {
19
19
  if (!fstat.isDirectory()) {
20
20
  continue;
21
21
  }
22
- filePromises.push(readdirRecursiveHelper({ path: p, filter: options.filter }));
22
+ if (options.maxDepth > 1) {
23
+ filePromises.push(readdirRecursiveHelper({ path: p, filter: options.filter, maxDepth: options.maxDepth - 1 }));
24
+ }
23
25
  }
24
26
  const files = await Promise.all(filePromises);
25
27
  let flatFiles = _.flattenDeep(files);
@@ -42,9 +44,11 @@ async function readdirRecursive(options) {
42
44
  return rule(t);
43
45
  });
44
46
  };
47
+ const maxDepth = options.maxDepth && options.maxDepth > 0 ? options.maxDepth : Infinity;
45
48
  return await readdirRecursiveHelper({
46
49
  path: options.path,
47
50
  filter: filter,
51
+ maxDepth,
48
52
  });
49
53
  }
50
54
  exports.readdirRecursive = readdirRecursive;
package/lib/gcp/runv2.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.serviceFromEndpoint = exports.endpointFromService = exports.FIREBASE_FUNCTION_METADTA_ANNOTATION = exports.FUNCTION_SIGNATURE_TYPE_ENV = exports.FUNCTION_TARGET_ENV = exports.FUNCTION_ID_ANNOTATION = exports.FUNCTION_TARGET_ANNOTATION = exports.TRIGGER_TYPE_ANNOTATION = exports.CLIENT_NAME_LABEL = exports.RUNTIME_LABEL = exports.updateService = exports.submitBuild = exports.API_VERSION = void 0;
3
+ exports.serviceFromEndpoint = exports.endpointFromService = exports.FIREBASE_FUNCTION_METADTA_ANNOTATION = exports.FUNCTION_SIGNATURE_TYPE_ENV = exports.FUNCTION_TARGET_ENV = exports.FUNCTION_ID_ANNOTATION = exports.FUNCTION_TARGET_ANNOTATION = exports.TRIGGER_TYPE_ANNOTATION = exports.CLIENT_NAME_LABEL = exports.RUNTIME_LABEL = exports.listServices = exports.updateService = exports.submitBuild = exports.API_VERSION = void 0;
4
4
  const apiv2_1 = require("../apiv2");
5
5
  const error_1 = require("../error");
6
6
  const api_1 = require("../api");
@@ -48,6 +48,34 @@ async function updateService(service) {
48
48
  return svc;
49
49
  }
50
50
  exports.updateService = updateService;
51
+ async function listServices(projectId) {
52
+ var _a, _b;
53
+ const allServices = [];
54
+ let pageToken = undefined;
55
+ do {
56
+ const queryParams = {};
57
+ if (pageToken) {
58
+ queryParams["pageToken"] = pageToken;
59
+ }
60
+ const res = await client.get(`/projects/${projectId}/locations/-/services`, { queryParams });
61
+ if (res.status !== 200) {
62
+ throw new error_1.FirebaseError(`Failed to list services. HTTP Error: ${res.status}`, {
63
+ original: res.body,
64
+ });
65
+ }
66
+ if (res.body.services) {
67
+ for (const service of res.body.services) {
68
+ if (((_a = service.labels) === null || _a === void 0 ? void 0 : _a[exports.CLIENT_NAME_LABEL]) === "cloud-functions" ||
69
+ ((_b = service.labels) === null || _b === void 0 ? void 0 : _b[exports.CLIENT_NAME_LABEL]) === "firebase-functions") {
70
+ allServices.push(service);
71
+ }
72
+ }
73
+ }
74
+ pageToken = res.body.nextPageToken;
75
+ } while (pageToken);
76
+ return allServices;
77
+ }
78
+ exports.listServices = listServices;
51
79
  function functionNameToServiceName(id) {
52
80
  return id.toLowerCase().replace(/_/g, "-");
53
81
  }
@@ -60,7 +88,7 @@ exports.FUNCTION_TARGET_ENV = "FUNCTION_TARGET";
60
88
  exports.FUNCTION_SIGNATURE_TYPE_ENV = "FUNCTION_SIGNATURE_TYPE";
61
89
  exports.FIREBASE_FUNCTION_METADTA_ANNOTATION = "firebase-functions-metadata";
62
90
  function endpointFromService(service) {
63
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
91
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
64
92
  const [, project, , location, , svcId] = service.name.split("/");
65
93
  const metadata = JSON.parse(((_a = service.annotations) === null || _a === void 0 ? void 0 : _a[exports.FIREBASE_FUNCTION_METADTA_ANNOTATION]) || "{}");
66
94
  const [env, secretEnv] = (0, functional_1.partition)(service.template.containers[0].env || [], (e) => "value" in e);
@@ -74,21 +102,18 @@ function endpointFromService(service) {
74
102
  __1.logger.debug("Converting a service to an endpoint with an invalid memory option", memory);
75
103
  }
76
104
  const cpu = Number(service.template.containers[0].resources.limits.cpu);
77
- const endpoint = {
78
- platform: ((_e = service.labels) === null || _e === void 0 ? void 0 : _e[exports.CLIENT_NAME_LABEL]) === "cloud-functions" ? "gcfv2" : "run",
79
- id,
80
- project,
81
- labels: service.labels || {},
82
- region: location,
83
- runtime: ((_f = service.labels) === null || _f === void 0 ? void 0 : _f[exports.RUNTIME_LABEL]) || (0, supported_1.latest)("nodejs"),
84
- availableMemoryMb: memory,
85
- cpu: cpu,
86
- entryPoint: ((_g = env.find((e) => e.name === exports.FUNCTION_TARGET_ENV)) === null || _g === void 0 ? void 0 : _g.value) ||
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,
106
+ project, labels: service.labels || {}, region: location, runtime: ((_f = service.labels) === null || _f === void 0 ? void 0 : _f[exports.RUNTIME_LABEL]) || (0, supported_1.latest)("nodejs"), availableMemoryMb: memory, cpu: cpu, entryPoint: ((_g = env.find((e) => e.name === exports.FUNCTION_TARGET_ENV)) === null || _g === void 0 ? void 0 : _g.value) ||
87
107
  ((_h = service.annotations) === null || _h === void 0 ? void 0 : _h[exports.FUNCTION_TARGET_ANNOTATION]) ||
88
108
  ((_j = service.annotations) === null || _j === void 0 ? void 0 : _j[exports.FUNCTION_ID_ANNOTATION]) ||
89
- id,
90
- httpsTrigger: {},
91
- };
109
+ id }, (((_k = service.annotations) === null || _k === void 0 ? void 0 : _k[exports.TRIGGER_TYPE_ANNOTATION]) === "HTTP_TRIGGER"
110
+ ? { httpsTrigger: {} }
111
+ : {
112
+ eventTrigger: {
113
+ eventType: ((_l = service.annotations) === null || _l === void 0 ? void 0 : _l[exports.TRIGGER_TYPE_ANNOTATION]) || "unknown",
114
+ retry: false,
115
+ },
116
+ }));
92
117
  proto.renameIfPresent(endpoint, service.template, "concurrency", "containerConcurrency");
93
118
  proto.renameIfPresent(endpoint, service.labels || {}, "codebase", constants_1.CODEBASE_LABEL);
94
119
  proto.renameIfPresent(endpoint, service.scaling || {}, "minInstances", "minInstanceCount");
@@ -22,9 +22,11 @@ const sdk = require("./sdk");
22
22
  const fdcExperience_1 = require("../../../gemini/fdcExperience");
23
23
  const configstore_1 = require("../../../configstore");
24
24
  const track_1 = require("../../../track");
25
+ const experiments_1 = require("../../../experiments");
25
26
  exports.FDC_DEFAULT_REGION = "us-east4";
26
27
  const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
27
28
  const DATACONNECT_WEBHOOKS_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect-fdcwebhooks.yaml");
29
+ const SECONDARY_SCHEMA_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/secondary_schema.yaml");
28
30
  const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
29
31
  const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
30
32
  const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
@@ -61,7 +63,6 @@ async function askQuestions(setup) {
61
63
  shouldProvisionCSQL: false,
62
64
  };
63
65
  if (setup.projectId) {
64
- const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
65
66
  await (0, ensureApis_1.ensureApis)(setup.projectId);
66
67
  await promptForExistingServices(setup, info);
67
68
  if (!info.serviceGql) {
@@ -86,12 +87,7 @@ async function askQuestions(setup) {
86
87
  });
87
88
  }
88
89
  }
89
- if (hasBilling) {
90
- await promptForCloudSQL(setup, info);
91
- }
92
- else if (info.appDescription) {
93
- await promptForLocation(setup, info);
94
- }
90
+ await promptForCloudSQL(setup, info);
95
91
  }
96
92
  setup.featureInfo = setup.featureInfo || {};
97
93
  setup.featureInfo.dataconnect = info;
@@ -132,9 +128,6 @@ async function actuate(setup, config, options) {
132
128
 
133
129
  https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
134
130
  }
135
- if (!(await (0, cloudbilling_1.isBillingEnabled)(setup))) {
136
- setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
137
- }
138
131
  setup.instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
139
132
  }
140
133
  exports.actuate = actuate;
@@ -146,8 +139,7 @@ async function actuateWithInfo(setup, config, info, options) {
146
139
  return await writeFiles(config, info, templateServiceInfo, options);
147
140
  }
148
141
  await (0, ensureApis_1.ensureApis)(projectId, true);
149
- const provisionCSQL = info.shouldProvisionCSQL && (await (0, cloudbilling_1.isBillingEnabled)(setup));
150
- if (provisionCSQL) {
142
+ if (info.shouldProvisionCSQL) {
151
143
  await (0, provisionCloudSql_1.setupCloudSql)({
152
144
  projectId: projectId,
153
145
  location: info.locationId,
@@ -178,7 +170,7 @@ async function actuateWithInfo(setup, config, info, options) {
178
170
  return await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
179
171
  }
180
172
  await (0, utils_1.promiseWithSpinner)(async () => {
181
- const [saveSchemaGql, waitForCloudSQLProvision] = schemasDeploySequence(projectId, info, schemaFiles, provisionCSQL);
173
+ const [saveSchemaGql, waitForCloudSQLProvision] = schemasDeploySequence(projectId, info, schemaFiles, info.shouldProvisionCSQL);
182
174
  await (0, client_1.upsertSchema)(saveSchemaGql);
183
175
  if (waitForCloudSQLProvision) {
184
176
  void (0, client_1.upsertSchema)(waitForCloudSQLProvision);
@@ -262,8 +254,9 @@ function schemasDeploySequence(projectId, info, schemaFiles, linkToCloudSql) {
262
254
  ];
263
255
  }
264
256
  async function writeFiles(config, info, serviceGql, options) {
257
+ var _a, _b;
265
258
  const dir = config.get("dataconnect.source") || "dataconnect";
266
- const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: serviceGql.connectors.map((c) => c.path) }));
259
+ const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: serviceGql.connectors.map((c) => c.path) }), (_a = serviceGql.secondarySchemaGqls) === null || _a === void 0 ? void 0 : _a.map((sch) => ({ id: sch.id, uri: sch.uri })));
267
260
  config.set("dataconnect", { source: dir });
268
261
  await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml, !!options.force, true);
269
262
  if (serviceGql.seedDataGql) {
@@ -277,6 +270,13 @@ async function writeFiles(config, info, serviceGql, options) {
277
270
  else {
278
271
  fs.ensureFileSync((0, path_1.join)(dir, "schema", "schema.gql"));
279
272
  }
273
+ if ((_b = serviceGql.secondarySchemaGqls) === null || _b === void 0 ? void 0 : _b.length) {
274
+ for (const sch of serviceGql.secondarySchemaGqls) {
275
+ for (const f of sch.files) {
276
+ await config.askWriteProjectFile((0, path_1.join)(dir, `schema_${sch.id}`, f.path), f.content, !!options.force);
277
+ }
278
+ }
279
+ }
280
280
  for (const c of serviceGql.connectors) {
281
281
  await writeConnectorFiles(config, c, options);
282
282
  }
@@ -289,17 +289,33 @@ async function writeConnectorFiles(config, connectorInfo, options) {
289
289
  await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content, !!options.force);
290
290
  }
291
291
  }
292
- function subDataconnectYamlValues(replacementValues) {
292
+ function subDataconnectYamlValues(replacementValues, secondarySchemas) {
293
293
  const replacements = {
294
294
  serviceId: "__serviceId__",
295
295
  locationId: "__location__",
296
296
  cloudSqlDatabase: "__cloudSqlDatabase__",
297
297
  cloudSqlInstanceId: "__cloudSqlInstanceId__",
298
298
  connectorDirs: "__connectorDirs__",
299
+ secondarySchemaId: "__secondarySchemaId__",
300
+ secondarySchemaSource: "__secondarySchemaSource__",
301
+ secondarySchemaUri: "__secondarySchemaUri__",
299
302
  };
300
303
  let replaced = experiments.isEnabled("fdcwebhooks")
301
304
  ? DATACONNECT_WEBHOOKS_YAML_TEMPLATE
302
305
  : DATACONNECT_YAML_TEMPLATE;
306
+ if (secondarySchemas && secondarySchemas.length > 0) {
307
+ let secondaryReplaced = "";
308
+ for (const schema of secondarySchemas) {
309
+ secondaryReplaced += SECONDARY_SCHEMA_YAML_TEMPLATE;
310
+ secondaryReplaced = secondaryReplaced.replace(replacements.secondarySchemaId, JSON.stringify(schema.id));
311
+ secondaryReplaced = secondaryReplaced.replace(replacements.secondarySchemaSource, `"./schema_${schema.id}"`);
312
+ secondaryReplaced = secondaryReplaced.replace(replacements.secondarySchemaUri, JSON.stringify(schema.uri));
313
+ }
314
+ replaced = replaced.replace("#__secondarySchemaPlaceholder__\n", secondaryReplaced);
315
+ }
316
+ else {
317
+ replaced = replaced.replace("#__secondarySchemaPlaceholder__\n", "");
318
+ }
303
319
  for (const [k, v] of Object.entries(replacementValues)) {
304
320
  replaced = replaced.replace(replacements[k], JSON.stringify(v));
305
321
  }
@@ -337,7 +353,7 @@ async function promptForExistingServices(setup, info) {
337
353
  await downloadService(info, choice.name);
338
354
  }
339
355
  async function downloadService(info, serviceName) {
340
- var _a, _b, _c, _d, _e;
356
+ var _a, _b, _c, _d, _e, _f, _g;
341
357
  let schemas = [];
342
358
  try {
343
359
  schemas = await (0, client_1.listSchemas)(serviceName, [
@@ -364,16 +380,29 @@ async function downloadService(info, serviceName) {
364
380
  },
365
381
  ],
366
382
  };
367
- const mainSch = (0, types_1.mainSchema)(schemas);
368
- const primaryDatasource = mainSch.datasources.find((d) => d.postgresql);
369
- if ((_b = (_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance) {
370
- const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
371
- info.cloudSqlInstanceId = instanceName.instanceId;
372
- }
373
- if ((_c = mainSch.source.files) === null || _c === void 0 ? void 0 : _c.length) {
374
- info.serviceGql.schemaGql = mainSch.source.files;
383
+ for (const sch of schemas) {
384
+ if ((0, types_1.isMainSchema)(sch)) {
385
+ const primaryDatasource = sch.datasources.find((d) => d.postgresql);
386
+ if ((_b = (_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance) {
387
+ const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
388
+ info.cloudSqlInstanceId = instanceName.instanceId;
389
+ }
390
+ info.cloudSqlDatabase = (_d = (_c = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
391
+ if ((_e = sch.source.files) === null || _e === void 0 ? void 0 : _e.length) {
392
+ info.serviceGql.schemaGql = sch.source.files;
393
+ }
394
+ }
395
+ else {
396
+ if (!info.serviceGql.secondarySchemaGqls) {
397
+ info.serviceGql.secondarySchemaGqls = [];
398
+ }
399
+ info.serviceGql.secondarySchemaGqls.push({
400
+ id: sch.name.split("/").pop(),
401
+ files: sch.source.files || [],
402
+ uri: (_g = (_f = sch.datasources[0].httpGraphql) === null || _f === void 0 ? void 0 : _f.uri) !== null && _g !== void 0 ? _g : "",
403
+ });
404
+ }
375
405
  }
376
- info.cloudSqlDatabase = (_e = (_d = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _d === void 0 ? void 0 : _d.database) !== null && _e !== void 0 ? _e : "";
377
406
  const connectors = await (0, client_1.listConnectors)(serviceName, [
378
407
  "connectors.name",
379
408
  "connectors.source.files",
@@ -424,6 +453,24 @@ async function promptForCloudSQL(setup, info) {
424
453
  if (!setup.projectId) {
425
454
  return;
426
455
  }
456
+ const instrumentlessTrialEnabled = (0, experiments_1.isEnabled)("fdcift");
457
+ const billingEnabled = await (0, cloudbilling_1.isBillingEnabled)(setup);
458
+ const freeTrialUsed = await (0, freeTrial_1.checkFreeTrialInstanceUsed)(setup.projectId);
459
+ const freeTrialAvailable = !freeTrialUsed && (billingEnabled || instrumentlessTrialEnabled);
460
+ if (!billingEnabled && !instrumentlessTrialEnabled) {
461
+ setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project", false));
462
+ return;
463
+ }
464
+ if (freeTrialUsed) {
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
+ }
471
+ else if (instrumentlessTrialEnabled || billingEnabled) {
472
+ (0, utils_1.logLabeledSuccess)("dataconnect", "CloudSQL no cost trial available!");
473
+ }
427
474
  if (info.cloudSqlInstanceId === "") {
428
475
  const instances = await cloudsql.listInstances(setup.projectId);
429
476
  let choices = instances.map((i) => {
@@ -436,7 +483,7 @@ async function promptForCloudSQL(setup, info) {
436
483
  });
437
484
  choices = choices.filter((c) => info.locationId === "" || info.locationId === c.location);
438
485
  if (choices.length) {
439
- if (!(await (0, freeTrial_1.checkFreeTrialInstanceUsed)(setup.projectId))) {
486
+ if (freeTrialAvailable) {
440
487
  choices.push({ name: "Create a new free trial instance", value: "", location: "" });
441
488
  }
442
489
  else {
@@ -462,7 +509,7 @@ async function promptForCloudSQL(setup, info) {
462
509
  if (info.locationId === "") {
463
510
  await promptForLocation(setup, info);
464
511
  info.shouldProvisionCSQL = await (0, prompt_1.confirm)({
465
- message: `Would you like to provision your Cloud SQL instance and database now?`,
512
+ message: `Would you like to provision your ${freeTrialAvailable ? "free trial " : ""}Cloud SQL instance and database now?`,
466
513
  default: true,
467
514
  });
468
515
  }
package/lib/mcp/index.js CHANGED
@@ -53,10 +53,6 @@ class FirebaseMcpServer {
53
53
  this._readyPromises = [];
54
54
  this._pendingMessages = [];
55
55
  this.currentLogLevel = process.env.FIREBASE_MCP_DEBUG_LOG ? "debug" : undefined;
56
- this.logger = Object.fromEntries(orderedLogLevels.map((logLevel) => [
57
- logLevel,
58
- (message) => this.log(logLevel, message),
59
- ]));
60
56
  this.activeFeatures = options.activeFeatures;
61
57
  this.startupRoot = options.projectRoot || process.env.PROJECT_ROOT;
62
58
  this.server = new index_js_1.Server({ name: "firebase", version: SERVER_VERSION });
@@ -129,14 +125,14 @@ class FirebaseMcpServer {
129
125
  return this.cachedProjectDir;
130
126
  const storedRoot = this.getStoredClientConfig().projectRoot;
131
127
  this.cachedProjectDir = storedRoot || this.startupRoot || process.cwd();
132
- this.log("debug", "detected and cached project root: " + this.cachedProjectDir);
128
+ this.logger.debug(`detected and cached project root: ${this.cachedProjectDir}`);
133
129
  return this.cachedProjectDir;
134
130
  }
135
131
  async detectActiveFeatures() {
136
132
  var _a;
137
133
  if ((_a = this.detectedFeatures) === null || _a === void 0 ? void 0 : _a.length)
138
134
  return this.detectedFeatures;
139
- this.log("debug", "detecting active features of Firebase MCP server...");
135
+ this.logger.debug("detecting active features of Firebase MCP server...");
140
136
  const projectId = (await this.getProjectId()) || "";
141
137
  const accountEmail = await this.getAuthenticatedUser();
142
138
  const ctx = this._createMcpContext(projectId, accountEmail);
@@ -147,7 +143,7 @@ class FirebaseMcpServer {
147
143
  return null;
148
144
  }));
149
145
  this.detectedFeatures = detected.filter((f) => !!f);
150
- this.log("debug", "detected features of Firebase MCP server: " + (this.detectedFeatures.join(", ") || "<none>"));
146
+ this.logger.debug(`detected features of Firebase MCP server: ${this.detectedFeatures.join(", ") || "<none>"}`);
151
147
  return this.detectedFeatures;
152
148
  }
153
149
  async getEmulatorHubClient() {
@@ -212,13 +208,13 @@ class FirebaseMcpServer {
212
208
  }
213
209
  async getAuthenticatedUser(skipAutoAuth = false) {
214
210
  try {
215
- this.log("debug", `calling requireAuth`);
211
+ this.logger.debug("calling requireAuth");
216
212
  const email = await (0, requireAuth_1.requireAuth)(await this.resolveOptions(), skipAutoAuth);
217
- this.log("debug", `detected authenticated account: ${email || "<none>"}`);
213
+ this.logger.debug(`detected authenticated account: ${email || "<none>"}`);
218
214
  return email !== null && email !== void 0 ? email : (skipAutoAuth ? null : "Application Default Credentials");
219
215
  }
220
216
  catch (e) {
221
- this.log("debug", `error in requireAuth: ${e}`);
217
+ this.logger.debug(`error in requireAuth: ${e}`);
222
218
  return null;
223
219
  }
224
220
  }
@@ -245,7 +241,7 @@ class FirebaseMcpServer {
245
241
  const hasActiveProject = !!(await this.getProjectId());
246
242
  await this.trackGA4("mcp_list_tools");
247
243
  const skipAutoAuthForStudio = (0, env_1.isFirebaseStudio)();
248
- this.log("debug", `skip auto-auth in studio environment: ${skipAutoAuthForStudio}`);
244
+ this.logger.debug(`skip auto-auth in studio environment: ${skipAutoAuthForStudio}`);
249
245
  const availableTools = await this.getAvailableTools();
250
246
  return {
251
247
  tools: availableTools.map((t) => t.mcp),
@@ -383,29 +379,38 @@ class FirebaseMcpServer {
383
379
  : new stdio_js_1.StdioServerTransport();
384
380
  await this.server.connect(transport);
385
381
  }
386
- log(level, message) {
387
- let data = message;
388
- if (typeof message === "string") {
389
- data = { message };
390
- }
391
- if (!this.currentLogLevel) {
392
- return;
393
- }
394
- if (orderedLogLevels.indexOf(this.currentLogLevel) > orderedLogLevels.indexOf(level)) {
395
- return;
396
- }
397
- if (this._ready) {
398
- while (this._pendingMessages.length) {
399
- const message = this._pendingMessages.shift();
400
- if (!message)
401
- continue;
402
- this.server.sendLoggingMessage({ level: message.level, data: message.data });
382
+ get logger() {
383
+ const logAtLevel = (level, message) => {
384
+ let data = message;
385
+ if (typeof message === "string") {
386
+ data = { message };
403
387
  }
404
- void this.server.sendLoggingMessage({ level, data });
405
- }
406
- else {
407
- this._pendingMessages.push({ level, data });
408
- }
388
+ if (!this.currentLogLevel) {
389
+ return;
390
+ }
391
+ if (orderedLogLevels.indexOf(this.currentLogLevel) > orderedLogLevels.indexOf(level)) {
392
+ return;
393
+ }
394
+ if (this._ready) {
395
+ while (this._pendingMessages.length) {
396
+ const message = this._pendingMessages.shift();
397
+ if (!message)
398
+ continue;
399
+ this.server.sendLoggingMessage({
400
+ level: message.level,
401
+ data: message.data,
402
+ });
403
+ }
404
+ void this.server.sendLoggingMessage({ level, data });
405
+ }
406
+ else {
407
+ this._pendingMessages.push({ level, data });
408
+ }
409
+ };
410
+ return Object.fromEntries(orderedLogLevels.map((logLevel) => [
411
+ logLevel,
412
+ (message) => logAtLevel(logLevel, message),
413
+ ]));
409
414
  }
410
415
  }
411
416
  exports.FirebaseMcpServer = FirebaseMcpServer;
@@ -11,7 +11,7 @@ async function isAppTestingAvailable(ctx) {
11
11
  const platforms = await (0, appUtils_1.getPlatformsFromFolder)(projectDir);
12
12
  const supportedPlatforms = [appUtils_1.Platform.FLUTTER, appUtils_1.Platform.ANDROID, appUtils_1.Platform.IOS];
13
13
  if (!platforms.some((p) => supportedPlatforms.includes(p))) {
14
- host.log("debug", `Found no supported App Testing platforms.`);
14
+ host.logger.debug("Found no supported App Testing platforms.");
15
15
  return false;
16
16
  }
17
17
  try {
@@ -5,7 +5,7 @@ const appUtils_1 = require("../../../appUtils");
5
5
  const fs = require("fs-extra");
6
6
  const path = require("path");
7
7
  async function isCrashlyticsAvailable(ctx) {
8
- ctx.host.log("debug", `Looking for whether crashlytics is installed...`);
8
+ ctx.host.logger.debug("Looking for whether crashlytics is installed...");
9
9
  return await isCrashlyticsInstalled(ctx);
10
10
  }
11
11
  exports.isCrashlyticsAvailable = isCrashlyticsAvailable;
@@ -16,22 +16,22 @@ async function isCrashlyticsInstalled(ctx) {
16
16
  if (!platforms.includes(appUtils_1.Platform.FLUTTER) &&
17
17
  !platforms.includes(appUtils_1.Platform.ANDROID) &&
18
18
  !platforms.includes(appUtils_1.Platform.IOS)) {
19
- host.log("debug", `Found no supported Crashlytics platforms.`);
19
+ host.logger.debug("Found no supported Crashlytics platforms.");
20
20
  return false;
21
21
  }
22
22
  if (platforms.includes(appUtils_1.Platform.FLUTTER) && (await flutterAppUsesCrashlytics(projectDir))) {
23
- host.log("debug", `Found Flutter app using Crashlytics`);
23
+ host.logger.debug("Found Flutter app using Crashlytics");
24
24
  return true;
25
25
  }
26
26
  if (platforms.includes(appUtils_1.Platform.ANDROID) && (await androidAppUsesCrashlytics(projectDir))) {
27
- host.log("debug", `Found Android app using Crashlytics`);
27
+ host.logger.debug("Found Android app using Crashlytics");
28
28
  return true;
29
29
  }
30
30
  if (platforms.includes(appUtils_1.Platform.IOS) && (await iosAppUsesCrashlytics(projectDir))) {
31
- host.log("debug", `Found iOS app using Crashlytics`);
31
+ host.logger.debug("Found iOS app using Crashlytics");
32
32
  return true;
33
33
  }
34
- host.log("debug", `Found supported platforms ${JSON.stringify(platforms)}, but did not find a Crashlytics dependency.`);
34
+ host.logger.debug(`Found supported platforms ${JSON.stringify(platforms)}, but did not find a Crashlytics dependency.`);
35
35
  return false;
36
36
  }
37
37
  async function androidAppUsesCrashlytics(appPath) {
package/lib/utils.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
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.commandExistsSync = exports.newUniqueId = exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = 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;
5
5
  const fs = require("fs-extra");
6
6
  const tty = require("tty");
7
7
  const path = require("node:path");
@@ -676,3 +676,12 @@ function commandExistsSync(command) {
676
676
  }
677
677
  }
678
678
  exports.commandExistsSync = commandExistsSync;
679
+ function resolveWithin(base, subPath, errMsg) {
680
+ const abs = path.resolve(base, subPath);
681
+ const rel = path.relative(base, abs);
682
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
683
+ throw new error_1.FirebaseError(errMsg || `Path "${subPath}" must be within "${base}".`);
684
+ }
685
+ return abs;
686
+ }
687
+ exports.resolveWithin = resolveWithin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "14.25.1",
3
+ "version": "14.26.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -7,6 +7,7 @@
7
7
  "nodejs18",
8
8
  "nodejs20",
9
9
  "nodejs22",
10
+ "nodejs24",
10
11
  "python310",
11
12
  "python311",
12
13
  "python312",
@@ -915,6 +916,7 @@
915
916
  "nodejs18",
916
917
  "nodejs20",
917
918
  "nodejs22",
919
+ "nodejs24",
918
920
  "python310",
919
921
  "python311",
920
922
  "python312",
@@ -10,4 +10,5 @@ schemas:
10
10
  instanceId: __cloudSqlInstanceId__
11
11
  # schemaValidation: "STRICT" # STRICT mode makes Postgres schema match Data Connect exactly.
12
12
  # schemaValidation: "COMPATIBLE" # COMPATIBLE mode makes Postgres schema compatible with Data Connect.
13
+ #__secondarySchemaPlaceholder__
13
14
  connectorDirs: __connectorDirs__
@@ -0,0 +1,5 @@
1
+ - id: __secondarySchemaId__
2
+ source: __secondarySchemaSource__
3
+ datasource:
4
+ httpGraphql:
5
+ uri: __secondarySchemaUri__
@@ -14,13 +14,13 @@
14
14
  },
15
15
  "main": "index.js",
16
16
  "dependencies": {
17
- "firebase-admin": "^12.6.0",
18
- "firebase-functions": "^6.0.1"
17
+ "firebase-admin": "^13.6.0",
18
+ "firebase-functions": "^7.0.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "eslint": "^8.15.0",
22
22
  "eslint-config-google": "^0.14.0",
23
- "firebase-functions-test": "^3.1.0"
23
+ "firebase-functions-test": "^3.4.1"
24
24
  },
25
25
  "private": true
26
26
  }
@@ -13,11 +13,11 @@
13
13
  },
14
14
  "main": "index.js",
15
15
  "dependencies": {
16
- "firebase-admin": "^12.6.0",
17
- "firebase-functions": "^6.0.1"
16
+ "firebase-admin": "^13.6.0",
17
+ "firebase-functions": "^7.0.0"
18
18
  },
19
19
  "devDependencies": {
20
- "firebase-functions-test": "^3.1.0"
20
+ "firebase-functions-test": "^3.4.1"
21
21
  },
22
22
  "private": true
23
23
  }
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "main": "lib/index.js",
17
17
  "dependencies": {
18
- "firebase-admin": "^12.6.0",
19
- "firebase-functions": "^6.0.1"
18
+ "firebase-admin": "^13.6.0",
19
+ "firebase-functions": "^7.0.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@typescript-eslint/eslint-plugin": "^5.12.0",
@@ -24,7 +24,7 @@
24
24
  "eslint": "^8.9.0",
25
25
  "eslint-config-google": "^0.14.0",
26
26
  "eslint-plugin-import": "^2.25.4",
27
- "firebase-functions-test": "^3.1.0",
27
+ "firebase-functions-test": "^3.4.1",
28
28
  "typescript": "^5.7.3"
29
29
  },
30
30
  "private": true
@@ -14,12 +14,12 @@
14
14
  },
15
15
  "main": "lib/index.js",
16
16
  "dependencies": {
17
- "firebase-admin": "^12.6.0",
18
- "firebase-functions": "^6.0.1"
17
+ "firebase-admin": "^13.6.0",
18
+ "firebase-functions": "^7.0.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "typescript": "^5.7.3",
22
- "firebase-functions-test": "^3.1.0"
22
+ "firebase-functions-test": "^3.4.1"
23
23
  },
24
24
  "private": true
25
25
  }