firebase-tools 14.15.2 → 14.16.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/commands/firestore-bulkdelete.js +73 -0
- package/lib/commands/firestore-operations-cancel.js +44 -0
- package/lib/commands/firestore-operations-describe.js +29 -0
- package/lib/commands/firestore-operations-list.js +29 -0
- package/lib/commands/firestore-utils.js +15 -0
- package/lib/commands/functions-config-export.js +5 -2
- package/lib/commands/index.js +5 -0
- package/lib/config.js +16 -4
- package/lib/dataconnect/ensureApis.js +3 -3
- package/lib/deploy/functions/deploy.js +4 -3
- package/lib/deploy/functions/prepare.js +8 -6
- package/lib/emulator/commandUtils.js +7 -1
- package/lib/emulator/controller.js +15 -31
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/hub.js +7 -1
- package/lib/extensions/runtimes/common.js +3 -2
- package/lib/firestore/api.js +45 -0
- package/lib/firestore/pretty-print.js +23 -0
- package/lib/functions/projectConfig.js +69 -9
- package/lib/gcp/cloudfunctions.js +1 -6
- package/lib/gcp/cloudfunctionsv2.js +1 -9
- package/lib/gcp/cloudsql/cloudsqladmin.js +2 -2
- package/lib/init/features/dataconnect/create_app.js +7 -2
- package/lib/init/features/dataconnect/index.js +72 -56
- package/lib/init/features/dataconnect/sdk.js +23 -10
- package/lib/mcp/errors.js +2 -10
- package/lib/mcp/index.js +0 -3
- package/lib/mcp/prompts/crashlytics/connect.js +114 -0
- package/lib/mcp/prompts/crashlytics/index.js +2 -3
- package/lib/mcp/tools/auth/disable_user.js +1 -1
- package/lib/mcp/tools/auth/get_user.js +9 -2
- package/lib/mcp/tools/core/index.js +4 -0
- package/lib/mcp/tools/core/init.js +11 -2
- package/lib/mcp/tools/core/login.js +46 -0
- package/lib/mcp/tools/core/logout.js +62 -0
- package/lib/mcp/tools/dataconnect/index.js +2 -2
- package/lib/mcp/tools/dataconnect/{info.js → list_services.js} +5 -5
- package/lib/mcp/util.js +1 -17
- package/lib/serve/functions.js +4 -3
- package/lib/unzip.js +13 -0
- package/lib/utils.js +17 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +160 -59
- package/lib/mcp/prompts/crashlytics/common.js +0 -10
- package/lib/mcp/prompts/crashlytics/fix_issue.js +0 -89
- package/lib/mcp/prompts/crashlytics/prioritize_issues.js +0 -79
- package/lib/mcp/tools/database/set_rules.js +0 -41
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.connect = void 0;
|
|
4
|
+
const prompt_1 = require("../../prompt");
|
|
5
|
+
exports.connect = (0, prompt_1.prompt)({
|
|
6
|
+
name: "connect",
|
|
7
|
+
omitPrefix: false,
|
|
8
|
+
description: "Access a Firebase application's Crashlytics data.",
|
|
9
|
+
annotations: {
|
|
10
|
+
title: "Access Crashlytics data",
|
|
11
|
+
},
|
|
12
|
+
}, async (unused, { accountEmail }) => {
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
role: "user",
|
|
16
|
+
content: {
|
|
17
|
+
type: "text",
|
|
18
|
+
text: `
|
|
19
|
+
You are going to help a developer prioritize and fix issues in their
|
|
20
|
+
mobile application by accessing their Firebase Crashlytics data.
|
|
21
|
+
|
|
22
|
+
Active user: ${accountEmail || "<NONE>"}
|
|
23
|
+
|
|
24
|
+
## Required first steps! Absolutely required! Incredibly important!
|
|
25
|
+
|
|
26
|
+
1. **Make sure the user is logged in. No Crashlytics tools will work if the user is not logged in.**
|
|
27
|
+
a. Use the \`firebase_get_environment\` tool to verify that the user is logged in,
|
|
28
|
+
and find the active Firebase project.
|
|
29
|
+
b. If the Firebase 'Active user' is set to <NONE>, instruct the user to run \`firebase login\`
|
|
30
|
+
before continuing.
|
|
31
|
+
|
|
32
|
+
2. **Get the app_id for the Firebase application.**
|
|
33
|
+
a. If this is an Android app, read the mobilesdk_app_id value specified in the
|
|
34
|
+
google-services.json file. If there are multiple files or multiple app ids in a
|
|
35
|
+
single file, ask the user to choose one by providing a numbered list of all the package names.
|
|
36
|
+
b. If this is an iOS app, read the GOOGLE_APP_ID from GoogleService-Info.plist file.
|
|
37
|
+
If there are multiple files or multiple app ids in single file, ask the user to
|
|
38
|
+
choose one by providing a numbered list of all the bundle names.
|
|
39
|
+
c. If you can't find either of the above, just ask the user for the app id.
|
|
40
|
+
|
|
41
|
+
## Next steps
|
|
42
|
+
|
|
43
|
+
Once you have confirmed that the user is logged in to Firebase, and confirmed the
|
|
44
|
+
id for the application that they want to access, then you can ask the user what actions
|
|
45
|
+
they would like to perform. Here are some possibilities and instructions follow below:
|
|
46
|
+
|
|
47
|
+
1. Prioritize the most impactful stability issues
|
|
48
|
+
2. Diagnose and propose a fix for a crash
|
|
49
|
+
|
|
50
|
+
## Instructions for Using Crashlytics Data
|
|
51
|
+
|
|
52
|
+
### How to prioritize issues
|
|
53
|
+
|
|
54
|
+
Follow these steps to fetch issues and prioritize them.
|
|
55
|
+
|
|
56
|
+
1. Use the 'crashlytics_list_top_issues' tool to fetch up to 20 issues.
|
|
57
|
+
2. Use the 'crashlytics_list_top_versions' tool to fetch the top versions for this app.
|
|
58
|
+
3. If the user instructions include statements about prioritization, use those instructions.
|
|
59
|
+
4. If the user instructions do not include statements about prioritization,
|
|
60
|
+
then prioritize the returned issues using the following criteria:
|
|
61
|
+
4a. The app versions for the issue include the most recent version of the app.
|
|
62
|
+
4b. The number of users experiencing the issue across variants
|
|
63
|
+
4c. The volume of crashes
|
|
64
|
+
5. Return the top 5 issues, with a brief description each in a numerical list with the following format:
|
|
65
|
+
1. Issue <issue id>
|
|
66
|
+
* <the issue title>
|
|
67
|
+
* <the issue subtitle>
|
|
68
|
+
* **Description:** <a discription of the issue based on information from the tool response>
|
|
69
|
+
* **Rationale:** <the reason this issue was prioritized in the way it was>
|
|
70
|
+
|
|
71
|
+
### How to diagnose and fix issues
|
|
72
|
+
|
|
73
|
+
Follow these steps to diagnose and fix issues.
|
|
74
|
+
|
|
75
|
+
1. Make sure you have a good understanding of the code structure and where different functionality exists
|
|
76
|
+
2. Use the 'crashlytics_get_issue_details' tool to get more context on the issue.
|
|
77
|
+
3. Use the 'crashlytics_get_sample_crash_for_issue' tool to get 3 example crashes for this issue.
|
|
78
|
+
4. Read the files that exist in the stack trace of the issue to understand the crash deeply.
|
|
79
|
+
5. Determine the root cause of the crash.
|
|
80
|
+
6. Write out a plan using the following criteria:
|
|
81
|
+
6a. Write out a description of the issue and including
|
|
82
|
+
* A brief description of the cause of the issue
|
|
83
|
+
* A determination of your level of confidence in the cause of the issue
|
|
84
|
+
* A determination of which library is at fault, this codebase or a dependent library
|
|
85
|
+
* A determination for how complex the fix will be
|
|
86
|
+
6b. The plan should include relevant files to change
|
|
87
|
+
6c. The plan should include a test plan to verify the fix
|
|
88
|
+
6d. Use the following format for the plan:
|
|
89
|
+
|
|
90
|
+
## Cause
|
|
91
|
+
<A description of the root cause leading to the issue>
|
|
92
|
+
- **Fault**: <a determination of whether this code base is at fault or a dependent library is at fault>
|
|
93
|
+
- **Complexity**: <one of "simple", "moderately simple", "moderately hard", "hard", "oof, I don't know where to start">
|
|
94
|
+
|
|
95
|
+
## Fix
|
|
96
|
+
<A description of the fix for this issue and a break down of the changes.>
|
|
97
|
+
1. <Step 1>
|
|
98
|
+
2. <Step 2>
|
|
99
|
+
|
|
100
|
+
## Test
|
|
101
|
+
<A plan for how to test that the issue has been fixed and protect against regressions>
|
|
102
|
+
1. <Test case 1>
|
|
103
|
+
2. <Test case 2>
|
|
104
|
+
|
|
105
|
+
7. Present the plan to the user and get approval before making the change.
|
|
106
|
+
8. Fix the issue.
|
|
107
|
+
8a. Be mindful of API contracts and do not add fields to resources without a clear way to populate those fields
|
|
108
|
+
8b. If there is not enough information in the crash report to find a root cause, describe why you cannot fix the issue instead of making a guess.
|
|
109
|
+
9. Ask the developer if they would like you to test the fix for them.
|
|
110
|
+
`.trim(),
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.crashlyticsPrompts = void 0;
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
exports.crashlyticsPrompts = [fix_issue_1.fix_issue, prioritize_issues_1.prioritize_issues];
|
|
4
|
+
const connect_1 = require("./connect");
|
|
5
|
+
exports.crashlyticsPrompts = [connect_1.connect];
|
|
@@ -24,7 +24,7 @@ exports.disable_user = (0, tool_1.tool)({
|
|
|
24
24
|
}, async ({ uid, disabled }, { projectId }) => {
|
|
25
25
|
const res = await (0, auth_1.disableUser)(projectId, uid, disabled);
|
|
26
26
|
if (res) {
|
|
27
|
-
return (0, util_1.toContent)(`User ${uid}
|
|
27
|
+
return (0, util_1.toContent)(`User ${uid} has been ${disabled ? "disabled" : "enabled"}`);
|
|
28
28
|
}
|
|
29
29
|
return (0, util_1.toContent)(`Failed to ${disabled ? "disable" : "enable"} user ${uid}`);
|
|
30
30
|
});
|
|
@@ -32,7 +32,14 @@ exports.get_user = (0, tool_1.tool)({
|
|
|
32
32
|
},
|
|
33
33
|
}, async ({ email, phone_number, uid }, { projectId }) => {
|
|
34
34
|
if (email === undefined && phone_number === undefined && uid === undefined) {
|
|
35
|
-
return (0, util_1.mcpError)(
|
|
35
|
+
return (0, util_1.mcpError)("No user identifier supplied in auth_get_user tool");
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
let user;
|
|
38
|
+
try {
|
|
39
|
+
user = await (0, auth_1.findUser)(projectId, email, phone_number, uid);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return (0, util_1.mcpError)("Unable to find user");
|
|
43
|
+
}
|
|
44
|
+
return (0, util_1.toContent)(user);
|
|
38
45
|
});
|
|
@@ -13,7 +13,11 @@ const get_environment_1 = require("./get_environment");
|
|
|
13
13
|
const update_environment_1 = require("./update_environment");
|
|
14
14
|
const list_projects_1 = require("./list_projects");
|
|
15
15
|
const consult_assistant_1 = require("./consult_assistant");
|
|
16
|
+
const login_1 = require("./login");
|
|
17
|
+
const logout_1 = require("./logout");
|
|
16
18
|
exports.coreTools = [
|
|
19
|
+
login_1.login,
|
|
20
|
+
logout_1.logout,
|
|
17
21
|
get_project_1.get_project,
|
|
18
22
|
list_apps_1.list_apps,
|
|
19
23
|
get_admin_sdk_config_1.get_admin_sdk_config,
|
|
@@ -6,6 +6,7 @@ const tool_1 = require("../../tool");
|
|
|
6
6
|
const util_1 = require("../../util");
|
|
7
7
|
const database_1 = require("../../../init/features/database");
|
|
8
8
|
const index_1 = require("../../../init/index");
|
|
9
|
+
const freeTrial_1 = require("../../../dataconnect/freeTrial");
|
|
9
10
|
exports.init = (0, tool_1.tool)({
|
|
10
11
|
name: "init",
|
|
11
12
|
description: "Initializes selected Firebase features in the workspace (Firestore, Data Connect, Realtime Database). All features are optional; provide only the products you wish to set up. " +
|
|
@@ -70,12 +71,19 @@ exports.init = (0, tool_1.tool)({
|
|
|
70
71
|
cloudsql_instance_id: zod_1.z
|
|
71
72
|
.string()
|
|
72
73
|
.optional()
|
|
73
|
-
.describe("The GCP Cloud SQL instance ID to use in the Firebase Data Connect service. By default, use <serviceId>-fdc."
|
|
74
|
+
.describe("The GCP Cloud SQL instance ID to use in the Firebase Data Connect service. By default, use <serviceId>-fdc. " +
|
|
75
|
+
"\nSet `provision_cloudsql` to true to start Cloud SQL provisioning."),
|
|
74
76
|
cloudsql_database: zod_1.z
|
|
75
77
|
.string()
|
|
76
78
|
.optional()
|
|
77
79
|
.default("fdcdb")
|
|
78
80
|
.describe("The Postgres database ID to use in the Firebase Data Connect service."),
|
|
81
|
+
provision_cloudsql: zod_1.z
|
|
82
|
+
.boolean()
|
|
83
|
+
.optional()
|
|
84
|
+
.default(false)
|
|
85
|
+
.describe("If true, provision the Cloud SQL instance if `cloudsql_instance_id` does not exist already. " +
|
|
86
|
+
`\nThe first Cloud SQL instance in the project will use the Data Connect no-cost trial. See its terms of service: ${(0, freeTrial_1.freeTrialTermsLink)()}.`),
|
|
79
87
|
})
|
|
80
88
|
.optional()
|
|
81
89
|
.describe("Provide this object to initialize Firebase Data Connect with Cloud SQL Postgres in this project directory.\n" +
|
|
@@ -138,6 +146,7 @@ exports.init = (0, tool_1.tool)({
|
|
|
138
146
|
locationId: features.dataconnect.location_id || "",
|
|
139
147
|
cloudSqlInstanceId: features.dataconnect.cloudsql_instance_id || "",
|
|
140
148
|
cloudSqlDatabase: features.dataconnect.cloudsql_database || "",
|
|
149
|
+
shouldProvisionCSQL: !!features.dataconnect.provision_cloudsql,
|
|
141
150
|
};
|
|
142
151
|
featureInfo.dataconnectSdk = {
|
|
143
152
|
apps: [],
|
|
@@ -155,7 +164,7 @@ exports.init = (0, tool_1.tool)({
|
|
|
155
164
|
config.writeProjectFile("firebase.json", setup.config);
|
|
156
165
|
config.writeProjectFile(".firebaserc", setup.rcfile);
|
|
157
166
|
if (featureInfo.dataconnectSdk && !featureInfo.dataconnectSdk.apps.length) {
|
|
158
|
-
setup.instructions.push(`No app is found in the current folder. We recommend you create an app (web, ios, android) first, then re-run the 'firebase_init' MCP tool to add Data Connect SDKs to your apps.
|
|
167
|
+
setup.instructions.push(`No app is found in the current folder. We recommend you create an app (web, ios, android) first, then re-run the 'firebase_init' MCP tool with the same input without app_description to add Data Connect SDKs to your apps.
|
|
159
168
|
Consider popular commands like 'npx create-react-app my-app', 'npx create-next-app my-app', 'flutter create my-app', etc`);
|
|
160
169
|
}
|
|
161
170
|
return (0, util_1.toContent)(`Successfully setup those features: ${featuresList.join(", ")}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.login = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const tool_1 = require("../../tool");
|
|
6
|
+
const auth_1 = require("../../../auth");
|
|
7
|
+
const util_1 = require("../../util");
|
|
8
|
+
const LoginInputSchema = zod_1.z.object({
|
|
9
|
+
authCode: zod_1.z.string().optional().describe("The authorization code from the login flow"),
|
|
10
|
+
});
|
|
11
|
+
exports.login = (0, tool_1.tool)({
|
|
12
|
+
name: "login",
|
|
13
|
+
description: "Logs the user into the Firebase CLI and MCP server.",
|
|
14
|
+
inputSchema: LoginInputSchema,
|
|
15
|
+
_meta: {
|
|
16
|
+
requiresAuth: false,
|
|
17
|
+
},
|
|
18
|
+
}, async (input, ctx) => {
|
|
19
|
+
const { authCode } = input;
|
|
20
|
+
const serverWithState = ctx.host;
|
|
21
|
+
if (authCode) {
|
|
22
|
+
if (!serverWithState.authorize) {
|
|
23
|
+
return (0, util_1.mcpError)("Login flow not started. Please call this tool without the authCode argument first to get a login URI.");
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const creds = await serverWithState.authorize(authCode);
|
|
27
|
+
delete serverWithState.authorize;
|
|
28
|
+
const user = creds.user;
|
|
29
|
+
return (0, util_1.toContent)(`Successfully logged in as ${user.email}`);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
delete serverWithState.authorize;
|
|
33
|
+
return (0, util_1.mcpError)(`Login failed: ${e.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const prototyper = await (0, auth_1.loginPrototyper)();
|
|
38
|
+
serverWithState.authorize = prototyper.authorize;
|
|
39
|
+
const result = {
|
|
40
|
+
uri: prototyper.uri,
|
|
41
|
+
sessionId: prototyper.sessionId,
|
|
42
|
+
};
|
|
43
|
+
const humanReadable = `Please visit this URL to login: ${result.uri}\nYour session ID is: ${result.sessionId}\nInstruct the use to copy the authorization code from that link, and paste it into chat.\nThen, run this tool again with that as the authCode argument to complete the login.`;
|
|
44
|
+
return (0, util_1.toContent)(humanReadable);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logout = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const clc = require("colorette");
|
|
6
|
+
const tool_1 = require("../../tool");
|
|
7
|
+
const util_1 = require("../../util");
|
|
8
|
+
const auth_1 = require("../../../auth");
|
|
9
|
+
const logger_1 = require("../../../logger");
|
|
10
|
+
exports.logout = (0, tool_1.tool)({
|
|
11
|
+
name: "logout",
|
|
12
|
+
description: "Log the CLI out of Firebase",
|
|
13
|
+
inputSchema: zod_1.z.object({
|
|
14
|
+
email: zod_1.z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("The email of the account to log out. If not provided, all accounts will be logged out."),
|
|
18
|
+
}),
|
|
19
|
+
_meta: {
|
|
20
|
+
requiresAuth: false,
|
|
21
|
+
requiresProject: false,
|
|
22
|
+
},
|
|
23
|
+
}, async ({ email }) => {
|
|
24
|
+
const allAccounts = (0, auth_1.getAllAccounts)();
|
|
25
|
+
if (allAccounts.length === 0) {
|
|
26
|
+
return (0, util_1.toContent)("No need to log out, not logged in");
|
|
27
|
+
}
|
|
28
|
+
const defaultAccount = (0, auth_1.getGlobalDefaultAccount)();
|
|
29
|
+
const additionalAccounts = (0, auth_1.getAdditionalAccounts)();
|
|
30
|
+
const accountsToLogOut = email
|
|
31
|
+
? allAccounts.filter((a) => a.user.email === email)
|
|
32
|
+
: allAccounts;
|
|
33
|
+
if (email && accountsToLogOut.length === 0) {
|
|
34
|
+
return (0, util_1.toContent)(`No account matches ${email}, can't log out.`);
|
|
35
|
+
}
|
|
36
|
+
const logoutDefault = email === (defaultAccount === null || defaultAccount === void 0 ? void 0 : defaultAccount.user.email);
|
|
37
|
+
let newDefaultAccount = undefined;
|
|
38
|
+
if (logoutDefault && additionalAccounts.length > 0) {
|
|
39
|
+
newDefaultAccount = additionalAccounts[0];
|
|
40
|
+
}
|
|
41
|
+
const logoutMessages = [];
|
|
42
|
+
for (const account of accountsToLogOut) {
|
|
43
|
+
const token = account.tokens.refresh_token;
|
|
44
|
+
if (token) {
|
|
45
|
+
try {
|
|
46
|
+
await (0, auth_1.logout)(token);
|
|
47
|
+
logoutMessages.push(`Logged out from ${clc.bold(account.user.email)}`);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
if (e instanceof Error) {
|
|
51
|
+
logger_1.logger.debug(e.message);
|
|
52
|
+
}
|
|
53
|
+
logoutMessages.push(`Could not deauthorize ${account.user.email}, assuming already deauthorized.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (newDefaultAccount) {
|
|
58
|
+
(0, auth_1.setGlobalDefaultAccount)(newDefaultAccount);
|
|
59
|
+
logoutMessages.push(`Setting default account to "${newDefaultAccount.user.email}"`);
|
|
60
|
+
}
|
|
61
|
+
return (0, util_1.toContent)(logoutMessages.join("\n"));
|
|
62
|
+
});
|
|
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.dataconnectTools = void 0;
|
|
4
4
|
const generate_operation_1 = require("./generate_operation");
|
|
5
5
|
const generate_schema_1 = require("./generate_schema");
|
|
6
|
-
const
|
|
6
|
+
const list_services_1 = require("./list_services");
|
|
7
7
|
const compile_1 = require("./compile");
|
|
8
8
|
const execute_1 = require("./execute");
|
|
9
9
|
exports.dataconnectTools = [
|
|
10
10
|
compile_1.compile,
|
|
11
11
|
generate_schema_1.generate_schema,
|
|
12
12
|
generate_operation_1.generate_operation,
|
|
13
|
-
|
|
13
|
+
list_services_1.list_services,
|
|
14
14
|
execute_1.execute,
|
|
15
15
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.list_services = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const tool_1 = require("../../tool");
|
|
@@ -9,12 +9,12 @@ const client = require("../../../dataconnect/client");
|
|
|
9
9
|
const load_1 = require("../../../dataconnect/load");
|
|
10
10
|
const js_yaml_1 = require("js-yaml");
|
|
11
11
|
const logger_1 = require("../../../logger");
|
|
12
|
-
exports.
|
|
13
|
-
name: "
|
|
14
|
-
description: "
|
|
12
|
+
exports.list_services = (0, tool_1.tool)({
|
|
13
|
+
name: "list_services",
|
|
14
|
+
description: "List existing local and backend Firebase Data Connect services",
|
|
15
15
|
inputSchema: zod_1.z.object({}),
|
|
16
16
|
annotations: {
|
|
17
|
-
title: "
|
|
17
|
+
title: "List existing Firebase Data Connect services",
|
|
18
18
|
readOnlyHint: true,
|
|
19
19
|
},
|
|
20
20
|
_meta: {
|
package/lib/mcp/util.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.cleanSchema = exports.checkFeatureActive = exports.
|
|
4
|
-
const child_process_1 = require("child_process");
|
|
3
|
+
exports.cleanSchema = exports.checkFeatureActive = exports.mcpError = exports.toContent = void 0;
|
|
5
4
|
const js_yaml_1 = require("js-yaml");
|
|
6
|
-
const os_1 = require("os");
|
|
7
5
|
const api_1 = require("../api");
|
|
8
6
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
9
7
|
const timeout_1 = require("../timeout");
|
|
@@ -41,20 +39,6 @@ function mcpError(message, code) {
|
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
41
|
exports.mcpError = mcpError;
|
|
44
|
-
function commandExistsSync(command) {
|
|
45
|
-
try {
|
|
46
|
-
const isWindows = (0, os_1.platform)() === "win32";
|
|
47
|
-
const commandToCheck = isWindows
|
|
48
|
-
? `where "${command}" > nul 2> nul`
|
|
49
|
-
: `which "${command}" > /dev/null 2> /dev/null`;
|
|
50
|
-
(0, child_process_1.execSync)(commandToCheck);
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
exports.commandExistsSync = commandExistsSync;
|
|
58
42
|
const SERVER_FEATURE_APIS = {
|
|
59
43
|
core: "",
|
|
60
44
|
firestore: (0, api_1.firestoreOrigin)(),
|
package/lib/serve/functions.js
CHANGED
|
@@ -21,11 +21,12 @@ class FunctionsServer {
|
|
|
21
21
|
const config = projectConfig.normalizeAndValidate(options.config.src.functions);
|
|
22
22
|
const backends = [];
|
|
23
23
|
for (const cfg of config) {
|
|
24
|
-
const
|
|
24
|
+
const localCfg = projectConfig.requireLocal(cfg, "Remote sources are not supported in the Functions emulator.");
|
|
25
|
+
const functionsDir = path.join(options.config.projectDir, localCfg.source);
|
|
25
26
|
backends.push({
|
|
26
27
|
functionsDir,
|
|
27
|
-
codebase:
|
|
28
|
-
runtime:
|
|
28
|
+
codebase: localCfg.codebase,
|
|
29
|
+
runtime: localCfg.runtime,
|
|
29
30
|
env: {},
|
|
30
31
|
secretEnv: [],
|
|
31
32
|
});
|
package/lib/unzip.js
CHANGED
|
@@ -57,6 +57,9 @@ const extractEntriesFromBuffer = async (data, outputDir) => {
|
|
|
57
57
|
logger_1.logger.debug(`[unzip] Entry: ${entry.fileName} (compressed_size=${entry.compressedSize} bytes, uncompressed_size=${entry.uncompressedSize} bytes)`);
|
|
58
58
|
entry.fileName = entry.fileName.replace(/\//g, path.sep);
|
|
59
59
|
const outputFilePath = path.normalize(path.join(outputDir, entry.fileName));
|
|
60
|
+
if (!isChildDir(outputDir, outputFilePath)) {
|
|
61
|
+
throw new error_1.FirebaseError(`ZIP contained an entry for ${outputFilePath}, a path outside of ${outputDir}`);
|
|
62
|
+
}
|
|
60
63
|
logger_1.logger.debug(`[unzip] Processing entry: ${entry.fileName}`);
|
|
61
64
|
if (entry.fileName.endsWith(path.sep)) {
|
|
62
65
|
logger_1.logger.debug(`[unzip] mkdir: ${outputFilePath}`);
|
|
@@ -82,6 +85,16 @@ const extractEntriesFromBuffer = async (data, outputDir) => {
|
|
|
82
85
|
position += entry.headerSize + entry.compressedSize + dataDescriptorSize;
|
|
83
86
|
}
|
|
84
87
|
};
|
|
88
|
+
function isChildDir(parentDir, potentialChild) {
|
|
89
|
+
try {
|
|
90
|
+
const resolvedParent = path.resolve(parentDir);
|
|
91
|
+
const resolvedChild = path.resolve(potentialChild);
|
|
92
|
+
return resolvedChild.startsWith(resolvedParent) && resolvedChild !== resolvedParent;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
85
98
|
const unzip = async (inputPath, outputDir) => {
|
|
86
99
|
const data = await fs.promises.readFile(inputPath);
|
|
87
100
|
await extractEntriesFromBuffer(data, outputDir);
|
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.newUniqueId = exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generatePassword = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = 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;
|
|
5
5
|
const fs = require("fs-extra");
|
|
6
6
|
const tty = require("tty");
|
|
7
7
|
const path = require("node:path");
|
|
@@ -24,6 +24,8 @@ const prompt_1 = require("./prompt");
|
|
|
24
24
|
const templates_1 = require("./templates");
|
|
25
25
|
const vsCodeUtils_1 = require("./vsCodeUtils");
|
|
26
26
|
const fsutils_1 = require("./fsutils");
|
|
27
|
+
const node_os_1 = require("node:os");
|
|
28
|
+
const node_child_process_1 = require("node:child_process");
|
|
27
29
|
exports.IS_WINDOWS = process.platform === "win32";
|
|
28
30
|
const SUCCESS_CHAR = exports.IS_WINDOWS ? "+" : "✔";
|
|
29
31
|
const WARNING_CHAR = exports.IS_WINDOWS ? "!" : "⚠";
|
|
@@ -657,3 +659,17 @@ function newUniqueId(recommended, existingIDs) {
|
|
|
657
659
|
return id;
|
|
658
660
|
}
|
|
659
661
|
exports.newUniqueId = newUniqueId;
|
|
662
|
+
function commandExistsSync(command) {
|
|
663
|
+
try {
|
|
664
|
+
const isWindows = (0, node_os_1.platform)() === "win32";
|
|
665
|
+
const commandToCheck = isWindows
|
|
666
|
+
? `where "${command}" > nul 2> nul`
|
|
667
|
+
: `which "${command}" > /dev/null 2> /dev/null`;
|
|
668
|
+
(0, node_child_process_1.execSync)(commandToCheck);
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
catch (error) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
exports.commandExistsSync = commandExistsSync;
|