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.
- package/lib/appUtils.js +2 -1
- package/lib/command.js +5 -9
- package/lib/commands/dataconnect-sdk-generate.js +66 -11
- package/lib/commands/firestore-databases-clone.js +99 -0
- package/lib/commands/functions-secrets-set.js +19 -1
- package/lib/commands/index.js +1 -0
- package/lib/commands/init.js +12 -8
- package/lib/commands/internaltesting-functions-discover.js +1 -3
- package/lib/dataconnect/provisionCloudSql.js +3 -2
- package/lib/deploy/extensions/prepare.js +3 -1
- package/lib/deploy/functions/checkIam.js +1 -1
- package/lib/deploy/functions/functionsDeployHelper.js +8 -7
- package/lib/deploy/functions/params.js +17 -3
- package/lib/deploy/functions/prepare.js +9 -6
- package/lib/detectProjectRoot.js +1 -1
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/hubExport.js +5 -0
- package/lib/experiments.js +0 -7
- package/lib/firestore/api.js +15 -0
- package/lib/firestore/util.js +22 -1
- package/lib/functions/projectConfig.js +5 -1
- package/lib/functions/secrets.js +14 -1
- package/lib/init/features/dataconnect/index.js +21 -20
- package/lib/init/features/dataconnect/sdk.js +44 -21
- package/lib/init/features/functions/index.js +1 -0
- package/lib/init/index.js +2 -2
- package/lib/mcp/index.js +46 -18
- package/lib/mcp/prompt.js +4 -1
- package/lib/mcp/prompts/core/consult.js +1 -1
- package/lib/mcp/prompts/core/deploy.js +1 -1
- package/lib/mcp/prompts/core/init.js +1 -1
- package/lib/mcp/prompts/crashlytics/connect.js +1 -1
- package/lib/mcp/prompts/dataconnect/schema.js +1 -1
- package/lib/mcp/prompts/index.js +20 -10
- package/lib/mcp/tool.js +17 -2
- package/lib/mcp/tools/apphosting/fetch_logs.js +1 -1
- package/lib/mcp/tools/apphosting/list_backends.js +1 -1
- package/lib/mcp/tools/auth/get_users.js +1 -1
- package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
- package/lib/mcp/tools/auth/update_user.js +1 -1
- package/lib/mcp/tools/core/create_android_sha.js +1 -1
- package/lib/mcp/tools/core/create_app.js +1 -1
- package/lib/mcp/tools/core/create_project.js +1 -1
- package/lib/mcp/tools/core/get_environment.js +1 -1
- package/lib/mcp/tools/core/get_project.js +1 -1
- package/lib/mcp/tools/core/get_sdk_config.js +1 -1
- package/lib/mcp/tools/core/get_security_rules.js +1 -1
- package/lib/mcp/tools/core/init.js +3 -2
- package/lib/mcp/tools/core/list_apps.js +1 -1
- package/lib/mcp/tools/core/list_projects.js +1 -1
- package/lib/mcp/tools/core/login.js +1 -1
- package/lib/mcp/tools/core/logout.js +1 -1
- package/lib/mcp/tools/core/read_resources.js +1 -1
- package/lib/mcp/tools/core/update_environment.js +1 -1
- package/lib/mcp/tools/core/validate_security_rules.js +15 -1
- package/lib/mcp/tools/crashlytics/events.js +3 -3
- package/lib/mcp/tools/crashlytics/issues.js +3 -3
- package/lib/mcp/tools/crashlytics/notes.js +4 -4
- package/lib/mcp/tools/crashlytics/reports.js +6 -6
- package/lib/mcp/tools/dataconnect/compile.js +1 -1
- package/lib/mcp/tools/dataconnect/execute.js +1 -1
- package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
- package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
- package/lib/mcp/tools/dataconnect/list_services.js +1 -1
- package/lib/mcp/tools/firestore/delete_document.js +1 -1
- package/lib/mcp/tools/firestore/get_documents.js +1 -1
- package/lib/mcp/tools/firestore/list_collections.js +1 -1
- package/lib/mcp/tools/firestore/query_collection.js +1 -1
- package/lib/mcp/tools/functions/get_logs.js +1 -1
- package/lib/mcp/tools/index.js +14 -4
- package/lib/mcp/tools/messaging/send_message.js +1 -1
- package/lib/mcp/tools/realtime_database/get_data.js +1 -1
- package/lib/mcp/tools/realtime_database/set_data.js +1 -1
- package/lib/mcp/tools/remoteconfig/get_template.js +1 -1
- package/lib/mcp/tools/remoteconfig/update_template.js +1 -1
- package/lib/mcp/tools/storage/get_download_url.js +1 -1
- package/lib/mcp/util/availability.js +22 -0
- package/lib/mcp/util/crashlytics/availability.js +81 -0
- package/lib/mcp/util.js +26 -6
- package/package.json +1 -1
- package/schema/firebase-config.json +3 -0
package/lib/experiments.js
CHANGED
|
@@ -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,
|
package/lib/firestore/api.js
CHANGED
|
@@ -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, {
|
package/lib/firestore/util.js
CHANGED
|
@@ -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;
|
package/lib/functions/secrets.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
?
|
|
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 (!
|
|
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.
|
|
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:
|
|
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.
|
|
164
|
+
info.flow += "_save_downloaded";
|
|
164
165
|
return await writeFiles(config, info, info.serviceGql, options);
|
|
165
166
|
}
|
|
166
|
-
info.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
326
|
+
info.flow += "_pick_new_service";
|
|
326
327
|
return;
|
|
327
328
|
}
|
|
328
|
-
info.
|
|
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.
|
|
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.
|
|
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: "
|
|
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 "
|
|
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", "
|
|
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
|
|
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
|
-
|
|
129
|
-
if (
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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] : [];
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
136
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
174
|
+
async getAvailableTools() {
|
|
168
175
|
var _a;
|
|
169
|
-
|
|
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
|
-
|
|
182
|
+
async getTool(name) {
|
|
183
|
+
const tools = await this.getAvailableTools();
|
|
184
|
+
return tools.find((t) => t.mcp.name === name) || null;
|
|
173
185
|
}
|
|
174
|
-
|
|
186
|
+
async getAvailablePrompts() {
|
|
175
187
|
var _a;
|
|
176
|
-
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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: [
|