firebase-tools 14.20.0 → 14.21.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 (81) hide show
  1. package/lib/appUtils.js +2 -1
  2. package/lib/command.js +5 -9
  3. package/lib/commands/dataconnect-sdk-generate.js +66 -11
  4. package/lib/commands/firestore-databases-clone.js +99 -0
  5. package/lib/commands/functions-secrets-set.js +19 -1
  6. package/lib/commands/index.js +1 -0
  7. package/lib/commands/init.js +12 -8
  8. package/lib/commands/internaltesting-functions-discover.js +1 -3
  9. package/lib/dataconnect/provisionCloudSql.js +3 -2
  10. package/lib/deploy/extensions/prepare.js +3 -1
  11. package/lib/deploy/functions/checkIam.js +1 -1
  12. package/lib/deploy/functions/functionsDeployHelper.js +8 -7
  13. package/lib/deploy/functions/params.js +17 -3
  14. package/lib/deploy/functions/prepare.js +9 -6
  15. package/lib/detectProjectRoot.js +1 -1
  16. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  17. package/lib/emulator/hubExport.js +5 -0
  18. package/lib/experiments.js +0 -7
  19. package/lib/firestore/api.js +15 -0
  20. package/lib/firestore/util.js +22 -1
  21. package/lib/functions/projectConfig.js +5 -1
  22. package/lib/functions/secrets.js +14 -1
  23. package/lib/init/features/dataconnect/index.js +21 -20
  24. package/lib/init/features/dataconnect/sdk.js +44 -21
  25. package/lib/init/features/functions/index.js +1 -0
  26. package/lib/init/index.js +2 -2
  27. package/lib/mcp/index.js +46 -18
  28. package/lib/mcp/prompt.js +4 -1
  29. package/lib/mcp/prompts/core/consult.js +1 -1
  30. package/lib/mcp/prompts/core/deploy.js +1 -1
  31. package/lib/mcp/prompts/core/init.js +1 -1
  32. package/lib/mcp/prompts/crashlytics/connect.js +1 -1
  33. package/lib/mcp/prompts/dataconnect/schema.js +1 -1
  34. package/lib/mcp/prompts/index.js +20 -10
  35. package/lib/mcp/tool.js +17 -2
  36. package/lib/mcp/tools/apphosting/fetch_logs.js +1 -1
  37. package/lib/mcp/tools/apphosting/list_backends.js +1 -1
  38. package/lib/mcp/tools/auth/get_users.js +1 -1
  39. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  40. package/lib/mcp/tools/auth/update_user.js +1 -1
  41. package/lib/mcp/tools/core/create_android_sha.js +1 -1
  42. package/lib/mcp/tools/core/create_app.js +1 -1
  43. package/lib/mcp/tools/core/create_project.js +1 -1
  44. package/lib/mcp/tools/core/get_environment.js +1 -1
  45. package/lib/mcp/tools/core/get_project.js +1 -1
  46. package/lib/mcp/tools/core/get_sdk_config.js +1 -1
  47. package/lib/mcp/tools/core/get_security_rules.js +1 -1
  48. package/lib/mcp/tools/core/init.js +3 -2
  49. package/lib/mcp/tools/core/list_apps.js +1 -1
  50. package/lib/mcp/tools/core/list_projects.js +1 -1
  51. package/lib/mcp/tools/core/login.js +1 -1
  52. package/lib/mcp/tools/core/logout.js +1 -1
  53. package/lib/mcp/tools/core/read_resources.js +1 -1
  54. package/lib/mcp/tools/core/update_environment.js +1 -1
  55. package/lib/mcp/tools/core/validate_security_rules.js +15 -1
  56. package/lib/mcp/tools/crashlytics/events.js +3 -3
  57. package/lib/mcp/tools/crashlytics/issues.js +3 -3
  58. package/lib/mcp/tools/crashlytics/notes.js +4 -4
  59. package/lib/mcp/tools/crashlytics/reports.js +6 -6
  60. package/lib/mcp/tools/dataconnect/compile.js +1 -1
  61. package/lib/mcp/tools/dataconnect/execute.js +1 -1
  62. package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
  63. package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
  64. package/lib/mcp/tools/dataconnect/list_services.js +1 -1
  65. package/lib/mcp/tools/firestore/delete_document.js +1 -1
  66. package/lib/mcp/tools/firestore/get_documents.js +1 -1
  67. package/lib/mcp/tools/firestore/list_collections.js +1 -1
  68. package/lib/mcp/tools/firestore/query_collection.js +1 -1
  69. package/lib/mcp/tools/functions/get_logs.js +1 -1
  70. package/lib/mcp/tools/index.js +14 -4
  71. package/lib/mcp/tools/messaging/send_message.js +1 -1
  72. package/lib/mcp/tools/realtime_database/get_data.js +1 -1
  73. package/lib/mcp/tools/realtime_database/set_data.js +1 -1
  74. package/lib/mcp/tools/remoteconfig/get_template.js +1 -1
  75. package/lib/mcp/tools/remoteconfig/update_template.js +1 -1
  76. package/lib/mcp/tools/storage/get_download_url.js +1 -1
  77. package/lib/mcp/util/availability.js +22 -0
  78. package/lib/mcp/util/crashlytics/availability.js +81 -0
  79. package/lib/mcp/util.js +26 -6
  80. package/package.json +1 -1
  81. package/schema/firebase-config.json +3 -0
@@ -41,13 +41,6 @@ exports.ALL_EXPERIMENTS = experiments({
41
41
  "of how that image was created.",
42
42
  public: true,
43
43
  },
44
- dangerouslyAllowFunctionsConfig: {
45
- shortDescription: "Allows the use of deprecated functions.config() API",
46
- fullDescription: "The functions.config() API is deprecated and will be removed on December 31, 2025. " +
47
- "This experiment allows continued use of the API during the migration period.",
48
- default: true,
49
- public: true,
50
- },
51
44
  runfunctions: {
52
45
  shortDescription: "Functions created using the V2 API target Cloud Run Functions (not production ready)",
53
46
  public: false,
@@ -580,6 +580,21 @@ class FirestoreApi {
580
580
  }
581
581
  return database;
582
582
  }
583
+ async cloneDatabase(project, pitrSnapshot, databaseId, encryptionConfig) {
584
+ const url = `/projects/${project}/databases:clone`;
585
+ const payload = {
586
+ databaseId,
587
+ pitrSnapshot,
588
+ encryptionConfig,
589
+ };
590
+ const options = { queryParams: { databaseId: databaseId } };
591
+ const res = await this.apiClient.post(url, payload, options);
592
+ const lro = res.body;
593
+ if (!lro) {
594
+ throw new error_1.FirebaseError("Not found");
595
+ }
596
+ return lro;
597
+ }
583
598
  async listOperations(project, databaseId, limit) {
584
599
  const url = `/projects/${project}/databases/${databaseId}/operations`;
585
600
  const res = await this.apiClient.get(url, {
@@ -1,9 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.booleanXOR = exports.parseFieldName = exports.parseIndexName = void 0;
3
+ exports.getCurrentMinuteAsIsoString = exports.booleanXOR = exports.parseFieldName = exports.parseIndexName = exports.parseDatabaseName = void 0;
4
4
  const error_1 = require("../error");
5
+ const DATABASE_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+)/;
5
6
  const INDEX_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/indexes\/([^\/]*)/;
6
7
  const FIELD_NAME_REGEX = /projects\/([^\/]+?)\/databases\/([^\/]+?)\/collectionGroups\/([^\/]+?)\/fields\/([^\/]*)/;
8
+ function parseDatabaseName(name) {
9
+ if (!name) {
10
+ throw new error_1.FirebaseError(`Cannot parse undefined database name.`);
11
+ }
12
+ const m = name.match(DATABASE_NAME_REGEX);
13
+ if (!m || m.length < 3) {
14
+ throw new error_1.FirebaseError(`Error parsing database name: ${name}`);
15
+ }
16
+ return {
17
+ projectId: m[1],
18
+ databaseId: m[2],
19
+ };
20
+ }
21
+ exports.parseDatabaseName = parseDatabaseName;
7
22
  function parseIndexName(name) {
8
23
  if (!name) {
9
24
  throw new error_1.FirebaseError(`Cannot parse undefined index name.`);
@@ -37,3 +52,9 @@ function booleanXOR(a, b) {
37
52
  return !!(Number(a) - Number(b));
38
53
  }
39
54
  exports.booleanXOR = booleanXOR;
55
+ function getCurrentMinuteAsIsoString() {
56
+ const mostRecentTimestamp = new Date(Date.now());
57
+ mostRecentTimestamp.setSeconds(0, 0);
58
+ return mostRecentTimestamp.toISOString();
59
+ }
60
+ exports.getCurrentMinuteAsIsoString = getCurrentMinuteAsIsoString;
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.resolveConfigDir = exports.requireLocal = exports.isRemoteConfig = exports.isLocalConfig = exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
14
+ exports.shouldUseRuntimeConfig = exports.resolveConfigDir = exports.requireLocal = exports.isRemoteConfig = exports.isLocalConfig = exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
15
15
  const error_1 = require("../error");
16
16
  exports.DEFAULT_CODEBASE = "default";
17
17
  function normalize(config) {
@@ -149,3 +149,7 @@ function resolveConfigDir(c) {
149
149
  return c.configDir || c.source;
150
150
  }
151
151
  exports.resolveConfigDir = resolveConfigDir;
152
+ function shouldUseRuntimeConfig(cfg) {
153
+ return isLocalConfig(cfg) && cfg.disallowLegacyRuntimeConfig !== true;
154
+ }
155
+ exports.shouldUseRuntimeConfig = shouldUseRuntimeConfig;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.describeSecret = exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.versionInUse = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = void 0;
3
+ exports.describeSecret = exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.versionInUse = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.validateJsonSecret = exports.ensureValidKey = void 0;
4
4
  const utils = require("../utils");
5
5
  const poller = require("../operation-poller");
6
6
  const gcfV1 = require("../gcp/cloudfunctions");
@@ -61,6 +61,19 @@ async function ensureValidKey(key, options) {
61
61
  return transformedKey;
62
62
  }
63
63
  exports.ensureValidKey = ensureValidKey;
64
+ function validateJsonSecret(secretName, secretValue) {
65
+ try {
66
+ JSON.parse(secretValue);
67
+ }
68
+ catch (e) {
69
+ throw new error_1.FirebaseError(`Provided value for ${secretName} is not valid JSON: ${e.message}\n\n` +
70
+ `For complex JSON values, use:\n` +
71
+ ` firebase functions:secrets:set ${secretName} --data-file <file.json>\n` +
72
+ `Or pipe from stdin:\n` +
73
+ ` cat <file.json> | firebase functions:secrets:set ${secretName} --format=json`);
74
+ }
75
+ }
76
+ exports.validateJsonSecret = validateJsonSecret;
64
77
  async function ensureSecret(projectId, name, options) {
65
78
  try {
66
79
  const secret = await (0, secretManager_1.getSecret)(projectId, name);
@@ -50,7 +50,7 @@ const templateServiceInfo = {
50
50
  };
51
51
  async function askQuestions(setup) {
52
52
  const info = {
53
- analyticsFlow: "cli",
53
+ flow: "",
54
54
  appDescription: "",
55
55
  serviceId: "",
56
56
  locationId: "",
@@ -97,7 +97,7 @@ async function askQuestions(setup) {
97
97
  }
98
98
  exports.askQuestions = askQuestions;
99
99
  async function actuate(setup, config, options) {
100
- var _a;
100
+ var _a, _b, _c;
101
101
  const dir = config.get("dataconnect.source", "dataconnect");
102
102
  const dataDir = config.get("emulators.dataconnect.dataDir", `${dir}/.dataconnect/pgliteData`);
103
103
  config.set("emulators.dataconnect.dataDir", dataDir);
@@ -109,37 +109,38 @@ async function actuate(setup, config, options) {
109
109
  info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId.toLowerCase()}-fdc`;
110
110
  info.locationId = info.locationId || exports.FDC_DEFAULT_REGION;
111
111
  info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
112
+ const startTime = Date.now();
112
113
  try {
113
114
  await actuateWithInfo(setup, config, info, options);
114
115
  await sdk.actuate(setup, config);
115
116
  }
116
117
  finally {
117
- void (0, track_1.trackGA4)("dataconnect_init", {
118
- flow: info.analyticsFlow,
119
- project_status: setup.projectId
120
- ? setup.isBillingEnabled
118
+ const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
119
+ const source = ((_c = setup.featureInfo) === null || _c === void 0 ? void 0 : _c.dataconnectSource) || "init";
120
+ void (0, track_1.trackGA4)("dataconnect_init", Object.assign({ source, flow: info.flow.substring(1), project_status: setup.projectId
121
+ ? (await (0, cloudbilling_1.isBillingEnabled)(setup))
121
122
  ? info.shouldProvisionCSQL
122
123
  ? "blaze_provisioned_csql"
123
124
  : "blaze"
124
125
  : "spark"
125
- : "missing",
126
- });
126
+ : "missing" }, (sdkInfo ? sdk.initAppCounters(sdkInfo) : {})), Date.now() - startTime);
127
127
  }
128
128
  if (info.appDescription) {
129
129
  setup.instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
130
130
 
131
131
  https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
132
132
  }
133
- if (!setup.isBillingEnabled) {
133
+ if (!(await (0, cloudbilling_1.isBillingEnabled)(setup))) {
134
134
  setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
135
135
  }
136
136
  setup.instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
137
137
  }
138
138
  exports.actuate = actuate;
139
139
  async function actuateWithInfo(setup, config, info, options) {
140
+ var _a;
140
141
  const projectId = setup.projectId;
141
142
  if (!projectId) {
142
- info.analyticsFlow += "_save_template";
143
+ info.flow += "_save_template";
143
144
  return await writeFiles(config, info, templateServiceInfo, options);
144
145
  }
145
146
  await (0, ensureApis_1.ensureApis)(projectId, true);
@@ -151,7 +152,7 @@ async function actuateWithInfo(setup, config, info, options) {
151
152
  instanceId: info.cloudSqlInstanceId,
152
153
  databaseId: info.cloudSqlDatabase,
153
154
  requireGoogleMlIntegration: false,
154
- source: info.analyticsFlow.startsWith("mcp") ? "mcp_init" : "init",
155
+ source: ((_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnectSource) || "init",
155
156
  });
156
157
  }
157
158
  const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
@@ -160,10 +161,10 @@ async function actuateWithInfo(setup, config, info, options) {
160
161
  await downloadService(info, serviceName);
161
162
  }
162
163
  if (info.serviceGql) {
163
- info.analyticsFlow += "_save_downloaded";
164
+ info.flow += "_save_downloaded";
164
165
  return await writeFiles(config, info, info.serviceGql, options);
165
166
  }
166
- info.analyticsFlow += "_save_template";
167
+ info.flow += "_save_template";
167
168
  return await writeFiles(config, info, templateServiceInfo, options);
168
169
  }
169
170
  const serviceAlreadyExists = !(await (0, client_1.createService)(projectId, info.locationId, info.serviceId));
@@ -171,7 +172,7 @@ async function actuateWithInfo(setup, config, info, options) {
171
172
  const schemaFiles = [{ path: "schema.gql", content: schemaGql }];
172
173
  if (serviceAlreadyExists) {
173
174
  (0, utils_1.logLabeledError)("dataconnect", `Data Connect Service ${serviceName} already exists. Skip saving them...`);
174
- info.analyticsFlow += "_save_gemini_service_already_exists";
175
+ info.flow += "_save_gemini_service_already_exists";
175
176
  return await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
176
177
  }
177
178
  await (0, utils_1.promiseWithSpinner)(async () => {
@@ -198,12 +199,12 @@ async function actuateWithInfo(setup, config, info, options) {
198
199
  ],
199
200
  },
200
201
  ];
201
- info.analyticsFlow += "_save_gemini";
202
+ info.flow += "_save_gemini";
202
203
  await writeFiles(config, info, { schemaGql: schemaFiles, connectors: connectors, seedDataGql: seedDataGql }, options);
203
204
  }
204
205
  catch (err) {
205
206
  (0, utils_1.logLabeledError)("dataconnect", `Operation Generation failed...`);
206
- info.analyticsFlow += "_save_gemini_operation_error";
207
+ info.flow += "_save_gemini_operation_error";
207
208
  await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
208
209
  throw err;
209
210
  }
@@ -322,10 +323,10 @@ async function promptForExistingServices(setup, info) {
322
323
  if (!choice) {
323
324
  const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
324
325
  info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
325
- info.analyticsFlow += "_pick_new_service";
326
+ info.flow += "_pick_new_service";
326
327
  return;
327
328
  }
328
- info.analyticsFlow += "_pick_existing_service";
329
+ info.flow += "_pick_existing_service";
329
330
  const serviceName = (0, names_1.parseServiceName)(choice.name);
330
331
  info.serviceId = serviceName.serviceId;
331
332
  info.locationId = serviceName.location;
@@ -429,11 +430,11 @@ async function promptForCloudSQL(setup, info) {
429
430
  choices,
430
431
  });
431
432
  if (info.cloudSqlInstanceId !== "") {
432
- info.analyticsFlow += "_pick_existing_csql";
433
+ info.flow += "_pick_existing_csql";
433
434
  info.locationId = choices.find((c) => c.value === info.cloudSqlInstanceId).location;
434
435
  }
435
436
  else {
436
- info.analyticsFlow += "_pick_new_csql";
437
+ info.flow += "_pick_new_csql";
437
438
  info.cloudSqlInstanceId = await (0, prompt_1.input)({
438
439
  message: `What ID would you like to use for your new CloudSQL instance?`,
439
440
  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.addSdkGenerateToConnectorYaml = exports.actuate = exports.chooseApp = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
3
+ exports.addSdkGenerateToConnectorYaml = exports.initAppCounters = exports.actuate = exports.chooseApp = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
4
4
  const yaml = require("yaml");
5
5
  const clc = require("colorette");
6
6
  const path = require("path");
@@ -16,6 +16,7 @@ const auth_1 = require("../../../auth");
16
16
  const create_app_1 = require("./create_app");
17
17
  const track_1 = require("../../../track");
18
18
  const fsutils_1 = require("../../../fsutils");
19
+ const cloudbilling_1 = require("../../../gcp/cloudbilling");
19
20
  exports.FDC_APP_FOLDER = "FDC_APP_FOLDER";
20
21
  exports.FDC_SDK_FRAMEWORKS_ENV = "FDC_SDK_FRAMEWORKS";
21
22
  exports.FDC_SDK_PLATFORM_ENV = "FDC_SDK_PLATFORM";
@@ -37,7 +38,7 @@ async function askQuestions(setup) {
37
38
  { name: `React${npxMissingWarning}`, value: "react" },
38
39
  { name: `Next.JS${npxMissingWarning}`, value: "next" },
39
40
  { name: `Flutter${flutterMissingWarning}`, value: "flutter" },
40
- { name: "no", value: "no" },
41
+ { name: "skip", value: "skip" },
41
42
  ],
42
43
  });
43
44
  try {
@@ -51,7 +52,7 @@ async function askQuestions(setup) {
51
52
  case "flutter":
52
53
  await (0, create_app_1.createFlutterApp)((0, utils_1.newUniqueId)("flutter_app", (0, fsutils_1.listFiles)(cwd)));
53
54
  break;
54
- case "no":
55
+ case "skip":
55
56
  break;
56
57
  }
57
58
  }
@@ -69,7 +70,7 @@ async function chooseApp() {
69
70
  (0, utils_1.logLabeledSuccess)("dataconnect", `Detected existing apps ${apps.map((a) => (0, appUtils_1.appDescription)(a)).join(", ")}`);
70
71
  }
71
72
  else {
72
- (0, utils_1.logLabeledWarning)("dataconnect", "No app exists in the current directory.");
73
+ (0, utils_1.logLabeledWarning)("dataconnect", "Cannot detect an existing app in the current directory.");
73
74
  }
74
75
  const envAppFolder = (0, utils_1.envOverride)(exports.FDC_APP_FOLDER, "");
75
76
  const envPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, "");
@@ -115,33 +116,55 @@ async function chooseApp() {
115
116
  }
116
117
  exports.chooseApp = chooseApp;
117
118
  async function actuate(setup, config) {
118
- var _a, _b;
119
- const fdcInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
120
- const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
119
+ var _a, _b, _c;
120
+ const sdkInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnectSdk;
121
121
  if (!sdkInfo) {
122
122
  throw new Error("Data Connect SDK feature RequiredInfo is not provided");
123
123
  }
124
+ const startTime = Date.now();
124
125
  try {
125
126
  await actuateWithInfo(setup, config, sdkInfo);
126
127
  }
127
128
  finally {
128
- let flow = "no_app";
129
- if (sdkInfo.apps.length) {
130
- const platforms = sdkInfo.apps.map((a) => a.platform.toLowerCase()).sort();
131
- flow = `${platforms.join("_")}_app`;
132
- }
133
- if (fdcInfo) {
134
- fdcInfo.analyticsFlow += `_${flow}`;
135
- }
136
- else {
137
- void (0, track_1.trackGA4)("dataconnect_init", {
138
- project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
139
- flow: `cli_sdk_${flow}`,
140
- });
129
+ const fdcInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnect;
130
+ if (!fdcInfo) {
131
+ const source = ((_c = setup.featureInfo) === null || _c === void 0 ? void 0 : _c.dataconnectSource) || "init_sdk";
132
+ void (0, track_1.trackGA4)("dataconnect_init", Object.assign({ source, project_status: setup.projectId
133
+ ? (await (0, cloudbilling_1.isBillingEnabled)(setup))
134
+ ? "blaze"
135
+ : "spark"
136
+ : "missing" }, initAppCounters(sdkInfo)), Date.now() - startTime);
141
137
  }
142
138
  }
143
139
  }
144
140
  exports.actuate = actuate;
141
+ function initAppCounters(info) {
142
+ var _a;
143
+ const counts = {
144
+ num_web_apps: 0,
145
+ num_android_apps: 0,
146
+ num_ios_apps: 0,
147
+ num_flutter_apps: 0,
148
+ };
149
+ for (const app of (_a = info.apps) !== null && _a !== void 0 ? _a : []) {
150
+ switch (app.platform) {
151
+ case appUtils_1.Platform.WEB:
152
+ counts.num_web_apps++;
153
+ break;
154
+ case appUtils_1.Platform.ANDROID:
155
+ counts.num_android_apps++;
156
+ break;
157
+ case appUtils_1.Platform.IOS:
158
+ counts.num_ios_apps++;
159
+ break;
160
+ case appUtils_1.Platform.FLUTTER:
161
+ counts.num_flutter_apps++;
162
+ break;
163
+ }
164
+ }
165
+ return counts;
166
+ }
167
+ exports.initAppCounters = initAppCounters;
145
168
  async function actuateWithInfo(setup, config, info) {
146
169
  if (!info.apps.length) {
147
170
  info.apps = await (0, appUtils_1.detectApps)(cwd);
@@ -245,7 +268,7 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
245
268
  case appUtils_1.Platform.FLUTTER: {
246
269
  const dartSdk = {
247
270
  outputDir: path.relative(connectorDir, path.join(appDir, `lib/dataconnect_generated`)),
248
- package: "dataconnect_generated",
271
+ package: "dataconnect_generated/generated.dart",
249
272
  };
250
273
  if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.dartSdk)) {
251
274
  generate.dartSdk = generate.dartSdk ? [generate.dartSdk] : [];
@@ -98,6 +98,7 @@ async function initNewCodebase(setup, config) {
98
98
  setup.config.functions.push({
99
99
  source,
100
100
  codebase,
101
+ disallowLegacyRuntimeConfig: true,
101
102
  });
102
103
  setup.functions.source = source;
103
104
  setup.functions.codebase = codebase;
package/lib/init/index.js CHANGED
@@ -84,7 +84,7 @@ async function init(setup, config, options) {
84
84
  await f.postSetup(setup, config, options);
85
85
  }
86
86
  const duration = Math.floor((process.uptime() - start) * 1000);
87
- await (0, track_1.trackGA4)("product_init", { feature: nextFeature }, duration);
87
+ void (0, track_1.trackGA4)("product_init", { feature: nextFeature }, duration);
88
88
  return init(setup, config, options);
89
89
  }
90
90
  }
@@ -105,7 +105,7 @@ async function actuate(setup, config, options) {
105
105
  }
106
106
  }
107
107
  const duration = Math.floor((process.uptime() - start) * 1000);
108
- await (0, track_1.trackGA4)("product_init_mcp", { feature: nextFeature }, duration);
108
+ void (0, track_1.trackGA4)("product_init_mcp", { feature: nextFeature }, duration);
109
109
  return actuate(setup, config, options);
110
110
  }
111
111
  }
package/lib/mcp/index.js CHANGED
@@ -23,6 +23,7 @@ const env_1 = require("../env");
23
23
  const timeout_1 = require("../timeout");
24
24
  const resources_1 = require("./resources");
25
25
  const crossSpawn = require("cross-spawn");
26
+ const availability_1 = require("./util/availability");
26
27
  const SERVER_VERSION = "0.3.0";
27
28
  const cmd = new command_1.Command("mcp");
28
29
  const orderedLogLevels = [
@@ -50,6 +51,7 @@ class FirebaseMcpServer {
50
51
  constructor(options) {
51
52
  this._ready = false;
52
53
  this._readyPromises = [];
54
+ this._pendingMessages = [];
53
55
  this.currentLogLevel = process.env.FIREBASE_MCP_DEBUG_LOG ? "debug" : undefined;
54
56
  this.logger = Object.fromEntries(orderedLogLevels.map((logLevel) => [
55
57
  logLevel,
@@ -92,8 +94,7 @@ class FirebaseMcpServer {
92
94
  this.currentLogLevel = params.level;
93
95
  return {};
94
96
  });
95
- this.detectProjectRoot();
96
- this.detectActiveFeatures();
97
+ void this.detectProjectSetup();
97
98
  }
98
99
  ready() {
99
100
  if (this._ready)
@@ -118,6 +119,10 @@ class FirebaseMcpServer {
118
119
  configstore_1.configstore.set(this.clientConfigKey, newConfig);
119
120
  return newConfig;
120
121
  }
122
+ async detectProjectSetup() {
123
+ await this.detectProjectRoot();
124
+ await this.detectActiveFeatures();
125
+ }
121
126
  async detectProjectRoot() {
122
127
  await (0, timeout_1.timeoutFallback)(this.ready(), null, 2000);
123
128
  if (this.cachedProjectDir)
@@ -132,10 +137,12 @@ class FirebaseMcpServer {
132
137
  if ((_a = this.detectedFeatures) === null || _a === void 0 ? void 0 : _a.length)
133
138
  return this.detectedFeatures;
134
139
  this.log("debug", "detecting active features of Firebase MCP server...");
135
- const options = await this.resolveOptions();
136
- const projectId = await this.getProjectId();
140
+ const projectId = (await this.getProjectId()) || "";
141
+ const accountEmail = await this.getAuthenticatedUser();
142
+ const ctx = this._createMcpContext(projectId, accountEmail);
137
143
  const detected = await Promise.all(types_1.SERVER_FEATURES.map(async (f) => {
138
- if (await (0, util_1.checkFeatureActive)(f, projectId, options))
144
+ const availabilityCheck = (0, availability_1.getDefaultFeatureAvailabilityCheck)(f);
145
+ if (await availabilityCheck(ctx))
139
146
  return f;
140
147
  return null;
141
148
  }));
@@ -164,19 +171,29 @@ class FirebaseMcpServer {
164
171
  const host = emulatorInfo.host.includes(":") ? `[${emulatorInfo.host}]` : emulatorInfo.host;
165
172
  return `http://${host}:${emulatorInfo.port}`;
166
173
  }
167
- get availableTools() {
174
+ async getAvailableTools() {
168
175
  var _a;
169
- return (0, index_1.availableTools)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
176
+ const features = ((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures;
177
+ const projectId = (await this.getProjectId()) || "";
178
+ const accountEmail = await this.getAuthenticatedUser();
179
+ const ctx = this._createMcpContext(projectId, accountEmail);
180
+ return (0, index_1.availableTools)(ctx, features);
170
181
  }
171
- getTool(name) {
172
- return this.availableTools.find((t) => t.mcp.name === name) || null;
182
+ async getTool(name) {
183
+ const tools = await this.getAvailableTools();
184
+ return tools.find((t) => t.mcp.name === name) || null;
173
185
  }
174
- get availablePrompts() {
186
+ async getAvailablePrompts() {
175
187
  var _a;
176
- return (0, index_2.availablePrompts)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
188
+ const features = ((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures;
189
+ const projectId = (await this.getProjectId()) || "";
190
+ const accountEmail = await this.getAuthenticatedUser();
191
+ const ctx = this._createMcpContext(projectId, accountEmail);
192
+ return (0, index_2.availablePrompts)(ctx, features);
177
193
  }
178
- getPrompt(name) {
179
- return this.availablePrompts.find((p) => p.mcp.name === name) || null;
194
+ async getPrompt(name) {
195
+ const prompts = await this.getAvailablePrompts();
196
+ return prompts.find((p) => p.mcp.name === name) || null;
180
197
  }
181
198
  setProjectRoot(newRoot) {
182
199
  this.updateStoredClientConfig({ projectRoot: newRoot });
@@ -229,8 +246,9 @@ class FirebaseMcpServer {
229
246
  await this.trackGA4("mcp_list_tools");
230
247
  const skipAutoAuthForStudio = (0, env_1.isFirebaseStudio)();
231
248
  this.log("debug", `skip auto-auth in studio environment: ${skipAutoAuthForStudio}`);
249
+ const availableTools = await this.getAvailableTools();
232
250
  return {
233
- tools: this.availableTools.map((t) => t.mcp),
251
+ tools: availableTools.map((t) => t.mcp),
234
252
  _meta: {
235
253
  projectRoot: this.cachedProjectDir,
236
254
  projectDetected: hasActiveProject,
@@ -245,7 +263,7 @@ class FirebaseMcpServer {
245
263
  await this.detectProjectRoot();
246
264
  const toolName = request.params.name;
247
265
  const toolArgs = request.params.arguments;
248
- const tool = this.getTool(toolName);
266
+ const tool = await this.getTool(toolName);
249
267
  if (!tool)
250
268
  throw new Error(`Tool '${toolName}' could not be found.`);
251
269
  if (!((_a = tool.mcp._meta) === null || _a === void 0 ? void 0 : _a.optionalProjectDir)) {
@@ -291,7 +309,7 @@ class FirebaseMcpServer {
291
309
  await this.trackGA4("mcp_list_prompts");
292
310
  const skipAutoAuthForStudio = (0, env_1.isFirebaseStudio)();
293
311
  return {
294
- prompts: this.availablePrompts.map((p) => ({
312
+ prompts: (await this.getAvailablePrompts()).map((p) => ({
295
313
  name: p.mcp.name,
296
314
  description: p.mcp.description,
297
315
  annotations: p.mcp.annotations,
@@ -310,7 +328,7 @@ class FirebaseMcpServer {
310
328
  await this.detectProjectRoot();
311
329
  const promptName = req.params.name;
312
330
  const promptArgs = req.params.arguments || {};
313
- const prompt = this.getPrompt(promptName);
331
+ const prompt = await this.getPrompt(promptName);
314
332
  if (!prompt) {
315
333
  throw new Error(`Prompt '${promptName}' could not be found.`);
316
334
  }
@@ -376,8 +394,18 @@ class FirebaseMcpServer {
376
394
  if (orderedLogLevels.indexOf(this.currentLogLevel) > orderedLogLevels.indexOf(level)) {
377
395
  return;
378
396
  }
379
- if (this._ready)
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 });
403
+ }
380
404
  void this.server.sendLoggingMessage({ level, data });
405
+ }
406
+ else {
407
+ this._pendingMessages.push({ level, data });
408
+ }
381
409
  }
382
410
  }
383
411
  exports.FirebaseMcpServer = FirebaseMcpServer;
package/lib/mcp/prompt.js CHANGED
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prompt = void 0;
4
- function prompt(options, fn) {
4
+ const availability_1 = require("./util/availability");
5
+ function prompt(feature, options, fn, isAvailable) {
6
+ const isAvailableFunc = isAvailable || (0, availability_1.getDefaultFeatureAvailabilityCheck)(feature);
5
7
  return {
6
8
  mcp: options,
7
9
  fn,
10
+ isAvailable: isAvailableFunc,
8
11
  };
9
12
  }
10
13
  exports.prompt = prompt;
@@ -5,7 +5,7 @@ const appUtils_1 = require("../../../appUtils");
5
5
  const fdcExperience_1 = require("../../../gemini/fdcExperience");
6
6
  const errors_1 = require("../../errors");
7
7
  const prompt_1 = require("../../prompt");
8
- exports.consult = (0, prompt_1.prompt)({
8
+ exports.consult = (0, prompt_1.prompt)("core", {
9
9
  name: "consult",
10
10
  description: "Use this command to consult the Firebase Assistant with access to detailed up-to-date documentation for the Firebase platform.",
11
11
  arguments: [
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deploy = void 0;
4
4
  const prompt_1 = require("../../prompt");
5
- exports.deploy = (0, prompt_1.prompt)({
5
+ exports.deploy = (0, prompt_1.prompt)("core", {
6
6
  name: "deploy",
7
7
  description: "Use this command to deploy resources to Firebase.",
8
8
  arguments: [
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.init = void 0;
4
4
  const appUtils_1 = require("../../../appUtils");
5
5
  const prompt_1 = require("../../prompt");
6
- exports.init = (0, prompt_1.prompt)({
6
+ exports.init = (0, prompt_1.prompt)("core", {
7
7
  name: "init",
8
8
  description: "Use this command to set up Firebase services, like backend and AI features.",
9
9
  annotations: {
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.connect = void 0;
4
4
  const prompt_1 = require("../../prompt");
5
- exports.connect = (0, prompt_1.prompt)({
5
+ exports.connect = (0, prompt_1.prompt)("crashlytics", {
6
6
  name: "connect",
7
7
  omitPrefix: false,
8
8
  description: "Access a Firebase application's Crashlytics data.",
@@ -18,7 +18,7 @@ ${(_a = fdcServices[0].schema.source.files) === null || _a === void 0 ? void 0 :
18
18
  function renderErrors(errors) {
19
19
  return `\n\n## Current Schema Build Errors\n\n${errors || "<NO ERRORS>"}`;
20
20
  }
21
- exports.schema = (0, prompt_1.prompt)({
21
+ exports.schema = (0, prompt_1.prompt)("core", {
22
22
  name: "schema",
23
23
  description: "Generate or update your Firebase Data Connect schema.",
24
24
  arguments: [