firebase-tools 14.20.0 → 14.22.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/deploy.js +6 -4
- package/lib/commands/firestore-databases-clone.js +99 -0
- package/lib/commands/functions-secrets-set.js +19 -1
- package/lib/commands/hosting-sites-create.js +4 -3
- 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 +15 -5
- 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/frameworks/angular/index.js +1 -1
- package/lib/frameworks/flutter/index.js +1 -1
- package/lib/frameworks/next/index.js +1 -1
- package/lib/frameworks/nuxt/index.js +1 -1
- package/lib/frameworks/vite/index.js +5 -2
- package/lib/functions/projectConfig.js +5 -1
- package/lib/functions/secrets.js +14 -1
- package/lib/hosting/interactive.js +14 -19
- 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/features/hosting/index.js +96 -93
- package/lib/init/features/index.js +3 -2
- package/lib/init/index.js +7 -3
- 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/resources/guides/init_backend.js +3 -26
- package/lib/mcp/resources/guides/init_hosting.js +15 -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 +30 -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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.actuate = exports.askQuestions = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
6
|
const path_1 = require("path");
|
|
@@ -15,13 +15,16 @@ const getDefaultHostingSite_1 = require("../../../getDefaultHostingSite");
|
|
|
15
15
|
const utils_1 = require("../../../utils");
|
|
16
16
|
const interactive_1 = require("../../../hosting/interactive");
|
|
17
17
|
const templates_1 = require("../../../templates");
|
|
18
|
+
const error_1 = require("../../../error");
|
|
19
|
+
const api_1 = require("../../../hosting/api");
|
|
18
20
|
const INDEX_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/index.html");
|
|
19
21
|
const MISSING_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/404.html");
|
|
20
22
|
const DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"];
|
|
21
|
-
async function
|
|
22
|
-
var _a, _b, _c, _d;
|
|
23
|
-
var
|
|
24
|
-
setup.
|
|
23
|
+
async function askQuestions(setup, config, options) {
|
|
24
|
+
var _a, _b, _c, _d, _e, _f;
|
|
25
|
+
var _g, _h, _j, _k, _l;
|
|
26
|
+
setup.featureInfo = setup.featureInfo || {};
|
|
27
|
+
setup.featureInfo.hosting = {};
|
|
25
28
|
if (setup.projectId) {
|
|
26
29
|
let hasHostingSite = true;
|
|
27
30
|
try {
|
|
@@ -33,61 +36,50 @@ async function doSetup(setup, config, options) {
|
|
|
33
36
|
}
|
|
34
37
|
hasHostingSite = false;
|
|
35
38
|
}
|
|
36
|
-
if (!hasHostingSite
|
|
37
|
-
|
|
39
|
+
if (!hasHostingSite &&
|
|
40
|
+
(await (0, prompt_1.confirm)({
|
|
38
41
|
message: "A Firebase Hosting site is required to deploy. Would you like to create one now?",
|
|
39
42
|
default: true,
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const newSite = await (0, interactive_1.interactiveCreateHostingSite)("", "", createOptions);
|
|
47
|
-
logger_1.logger.info();
|
|
48
|
-
(0, utils_1.logSuccess)(`Firebase Hosting site ${(0, utils_1.last)(newSite.name.split("/"))} created!`);
|
|
49
|
-
logger_1.logger.info();
|
|
50
|
-
}
|
|
43
|
+
}))) {
|
|
44
|
+
const createOptions = {
|
|
45
|
+
projectId: setup.projectId,
|
|
46
|
+
nonInteractive: options.nonInteractive,
|
|
47
|
+
};
|
|
48
|
+
setup.featureInfo.hosting.newSiteId = await (0, interactive_1.pickHostingSiteName)("", createOptions);
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
|
-
let discoveredFramework = experiments.isEnabled("webframeworks")
|
|
54
|
-
? await (0, frameworks_1.discover)(config.projectDir, false)
|
|
55
|
-
: undefined;
|
|
56
51
|
if (experiments.isEnabled("webframeworks")) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
let discoveredFramework = experiments.isEnabled("webframeworks")
|
|
53
|
+
? await (0, frameworks_1.discover)(config.projectDir, false)
|
|
54
|
+
: undefined;
|
|
55
|
+
if (discoveredFramework &&
|
|
56
|
+
(await (0, prompt_1.confirm)({
|
|
57
|
+
message: `Detected an existing ${frameworks_1.WebFrameworks[discoveredFramework.framework].name} codebase in the current directory, do you want to use this?`,
|
|
61
58
|
default: true,
|
|
62
|
-
}))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
setup.hosting.
|
|
66
|
-
setup.hosting.
|
|
59
|
+
}))) {
|
|
60
|
+
setup.featureInfo.hosting.source = ".";
|
|
61
|
+
setup.featureInfo.hosting.useWebFrameworks = true;
|
|
62
|
+
setup.featureInfo.hosting.useDiscoveredFramework = true;
|
|
63
|
+
setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
|
|
67
64
|
}
|
|
68
65
|
else {
|
|
69
|
-
setup.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
|
|
66
|
+
setup.featureInfo.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
|
|
70
67
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
default: "hosting",
|
|
76
|
-
}));
|
|
77
|
-
if (setup.hosting.source !== ".")
|
|
78
|
-
delete setup.hosting.useDiscoveredFramework;
|
|
79
|
-
discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.hosting.source));
|
|
80
|
-
if (discoveredFramework) {
|
|
81
|
-
const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
|
|
82
|
-
(_c = (_g = setup.hosting).useDiscoveredFramework) !== null && _c !== void 0 ? _c : (_g.useDiscoveredFramework = await (0, prompt_1.confirm)({
|
|
83
|
-
message: `Detected an existing ${name} codebase in ${setup.hosting.source}, should we use this?`,
|
|
84
|
-
default: true,
|
|
68
|
+
if (setup.featureInfo.hosting.useWebFrameworks) {
|
|
69
|
+
(_a = (_g = setup.featureInfo.hosting).source) !== null && _a !== void 0 ? _a : (_g.source = await (0, prompt_1.input)({
|
|
70
|
+
message: "What folder would you like to use for your web application's root directory?",
|
|
71
|
+
default: "hosting",
|
|
85
72
|
}));
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
73
|
+
discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source));
|
|
74
|
+
if (discoveredFramework) {
|
|
75
|
+
const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
|
|
76
|
+
(_b = (_h = setup.featureInfo.hosting).useDiscoveredFramework) !== null && _b !== void 0 ? _b : (_h.useDiscoveredFramework = await (0, prompt_1.confirm)({
|
|
77
|
+
message: `Detected an existing ${name} codebase in ${setup.featureInfo.hosting.source}, should we use this?`,
|
|
78
|
+
default: true,
|
|
79
|
+
}));
|
|
80
|
+
if (setup.featureInfo.hosting.useDiscoveredFramework)
|
|
81
|
+
setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
|
|
82
|
+
}
|
|
91
83
|
const choices = [];
|
|
92
84
|
for (const value in frameworks_1.WebFrameworks) {
|
|
93
85
|
if (frameworks_1.WebFrameworks[value]) {
|
|
@@ -96,32 +88,20 @@ async function doSetup(setup, config, options) {
|
|
|
96
88
|
choices.push({ name, value });
|
|
97
89
|
}
|
|
98
90
|
}
|
|
99
|
-
const defaultChoice = (
|
|
100
|
-
setup.hosting.
|
|
101
|
-
|
|
91
|
+
const defaultChoice = (_c = choices.find(({ value }) => value === (discoveredFramework === null || discoveredFramework === void 0 ? void 0 : discoveredFramework.framework))) === null || _c === void 0 ? void 0 : _c.value;
|
|
92
|
+
(_d = (_j = setup.featureInfo.hosting).webFramework) !== null && _d !== void 0 ? _d : (_j.webFramework = await (0, prompt_1.select)({
|
|
93
|
+
message: "Please choose the framework:",
|
|
94
|
+
default: defaultChoice,
|
|
95
|
+
choices,
|
|
96
|
+
}));
|
|
97
|
+
setup.featureInfo.hosting.region =
|
|
98
|
+
setup.featureInfo.hosting.region ||
|
|
102
99
|
(await (0, prompt_1.select)({
|
|
103
|
-
message: "
|
|
104
|
-
default:
|
|
105
|
-
choices,
|
|
100
|
+
message: "In which region would you like to host server-side content, if applicable?",
|
|
101
|
+
default: constants_1.DEFAULT_REGION,
|
|
102
|
+
choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
|
|
106
103
|
}));
|
|
107
|
-
if (discoveredFramework)
|
|
108
|
-
(0, node_fs_1.rmSync)(setup.hosting.source, { recursive: true });
|
|
109
|
-
await frameworks_1.WebFrameworks[setup.hosting.whichFramework].init(setup, config);
|
|
110
104
|
}
|
|
111
|
-
setup.hosting.region =
|
|
112
|
-
setup.hosting.region ||
|
|
113
|
-
(await (0, prompt_1.select)({
|
|
114
|
-
message: "In which region would you like to host server-side content, if applicable?",
|
|
115
|
-
default: constants_1.DEFAULT_REGION,
|
|
116
|
-
choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
|
|
117
|
-
}));
|
|
118
|
-
setup.config.hosting = {
|
|
119
|
-
source: setup.hosting.source,
|
|
120
|
-
ignore: DEFAULT_IGNORES,
|
|
121
|
-
frameworksBackend: {
|
|
122
|
-
region: setup.hosting.region,
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
105
|
}
|
|
126
106
|
else {
|
|
127
107
|
logger_1.logger.info();
|
|
@@ -129,35 +109,58 @@ async function doSetup(setup, config, options) {
|
|
|
129
109
|
logger_1.logger.info(`will contain Hosting assets to be uploaded with ${clc.bold("firebase deploy")}. If you`);
|
|
130
110
|
logger_1.logger.info("have a build process for your assets, use your build's output directory.");
|
|
131
111
|
logger_1.logger.info();
|
|
132
|
-
setup.hosting.public =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
112
|
+
(_e = (_k = setup.featureInfo.hosting).public) !== null && _e !== void 0 ? _e : (_k.public = await (0, prompt_1.input)({
|
|
113
|
+
message: "What do you want to use as your public directory?",
|
|
114
|
+
default: "public",
|
|
115
|
+
}));
|
|
116
|
+
(_f = (_l = setup.featureInfo.hosting).spa) !== null && _f !== void 0 ? _f : (_l.spa = await (0, prompt_1.confirm)("Configure as a single-page app (rewrite all urls to /index.html)?"));
|
|
117
|
+
}
|
|
118
|
+
if (await (0, prompt_1.confirm)("Set up automatic builds and deploys with GitHub?")) {
|
|
119
|
+
return (0, github_1.initGitHub)(setup);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
exports.askQuestions = askQuestions;
|
|
123
|
+
async function actuate(setup, config, options) {
|
|
124
|
+
var _a;
|
|
125
|
+
const hostingInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.hosting;
|
|
126
|
+
if (!hostingInfo) {
|
|
127
|
+
throw new error_1.FirebaseError("Could not find hosting info in setup.featureInfo.hosting. This should not happen.", { exit: 2 });
|
|
128
|
+
}
|
|
129
|
+
if (hostingInfo.newSiteId && setup.projectId) {
|
|
130
|
+
await (0, api_1.createSite)(setup.projectId, hostingInfo.newSiteId);
|
|
131
|
+
logger_1.logger.info();
|
|
132
|
+
(0, utils_1.logSuccess)(`Firebase Hosting site ${hostingInfo.newSiteId} created!`);
|
|
133
|
+
logger_1.logger.info();
|
|
134
|
+
}
|
|
135
|
+
if (hostingInfo.webFramework) {
|
|
136
|
+
if (!hostingInfo.useDiscoveredFramework) {
|
|
137
|
+
if (hostingInfo.source && (0, node_fs_1.existsSync)(hostingInfo.source)) {
|
|
138
|
+
(0, node_fs_1.rmSync)(hostingInfo.source, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
await frameworks_1.WebFrameworks[hostingInfo.webFramework].init(setup, config);
|
|
141
|
+
}
|
|
141
142
|
setup.config.hosting = {
|
|
142
|
-
|
|
143
|
+
source: hostingInfo.source,
|
|
143
144
|
ignore: DEFAULT_IGNORES,
|
|
145
|
+
frameworksBackend: {
|
|
146
|
+
region: hostingInfo.region,
|
|
147
|
+
},
|
|
144
148
|
};
|
|
145
149
|
}
|
|
146
|
-
|
|
147
|
-
setup.hosting
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
else {
|
|
151
|
+
setup.config.hosting = {
|
|
152
|
+
public: hostingInfo.public,
|
|
153
|
+
ignore: DEFAULT_IGNORES,
|
|
154
|
+
};
|
|
155
|
+
if (hostingInfo.spa) {
|
|
150
156
|
setup.config.hosting.rewrites = [{ source: "**", destination: "/index.html" }];
|
|
151
157
|
}
|
|
152
158
|
else {
|
|
153
|
-
await config.askWriteProjectFile(`${
|
|
159
|
+
await config.askWriteProjectFile(`${hostingInfo.public}/404.html`, MISSING_TEMPLATE, !!options.force);
|
|
154
160
|
}
|
|
155
161
|
const c = new apiv2_1.Client({ urlPrefix: "https://www.gstatic.com", auth: false });
|
|
156
162
|
const response = await c.get("/firebasejs/releases.json");
|
|
157
|
-
await config.askWriteProjectFile(`${
|
|
158
|
-
}
|
|
159
|
-
if (setup.hosting.github) {
|
|
160
|
-
return (0, github_1.initGitHub)(setup);
|
|
163
|
+
await config.askWriteProjectFile(`${hostingInfo.public}/index.html`, INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version), !!options.force);
|
|
161
164
|
}
|
|
162
165
|
}
|
|
163
|
-
exports.
|
|
166
|
+
exports.actuate = actuate;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.aiLogicActuate = exports.aiLogicAskQuestions = exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.
|
|
3
|
+
exports.aiLogicActuate = exports.aiLogicAskQuestions = exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hostingActuate = exports.hostingAskQuestions = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
|
|
4
4
|
var account_1 = require("./account");
|
|
5
5
|
Object.defineProperty(exports, "account", { enumerable: true, get: function () { return account_1.doSetup; } });
|
|
6
6
|
var database_1 = require("./database");
|
|
@@ -12,7 +12,8 @@ Object.defineProperty(exports, "firestoreActuate", { enumerable: true, get: func
|
|
|
12
12
|
var functions_1 = require("./functions");
|
|
13
13
|
Object.defineProperty(exports, "functions", { enumerable: true, get: function () { return functions_1.doSetup; } });
|
|
14
14
|
var hosting_1 = require("./hosting");
|
|
15
|
-
Object.defineProperty(exports, "
|
|
15
|
+
Object.defineProperty(exports, "hostingAskQuestions", { enumerable: true, get: function () { return hosting_1.askQuestions; } });
|
|
16
|
+
Object.defineProperty(exports, "hostingActuate", { enumerable: true, get: function () { return hosting_1.actuate; } });
|
|
16
17
|
var storage_1 = require("./storage");
|
|
17
18
|
Object.defineProperty(exports, "storageAskQuestions", { enumerable: true, get: function () { return storage_1.askQuestions; } });
|
|
18
19
|
Object.defineProperty(exports, "storageActuate", { enumerable: true, get: function () { return storage_1.actuate; } });
|
package/lib/init/index.js
CHANGED
|
@@ -30,7 +30,11 @@ const featuresList = [
|
|
|
30
30
|
actuate: features.dataconnectSdkActuate,
|
|
31
31
|
},
|
|
32
32
|
{ name: "functions", doSetup: features.functions },
|
|
33
|
-
{
|
|
33
|
+
{
|
|
34
|
+
name: "hosting",
|
|
35
|
+
askQuestions: features.hostingAskQuestions,
|
|
36
|
+
actuate: features.hostingActuate,
|
|
37
|
+
},
|
|
34
38
|
{
|
|
35
39
|
name: "storage",
|
|
36
40
|
askQuestions: features.storageAskQuestions,
|
|
@@ -84,7 +88,7 @@ async function init(setup, config, options) {
|
|
|
84
88
|
await f.postSetup(setup, config, options);
|
|
85
89
|
}
|
|
86
90
|
const duration = Math.floor((process.uptime() - start) * 1000);
|
|
87
|
-
|
|
91
|
+
void (0, track_1.trackGA4)("product_init", { feature: nextFeature }, duration);
|
|
88
92
|
return init(setup, config, options);
|
|
89
93
|
}
|
|
90
94
|
}
|
|
@@ -105,7 +109,7 @@ async function actuate(setup, config, options) {
|
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
const duration = Math.floor((process.uptime() - start) * 1000);
|
|
108
|
-
|
|
112
|
+
void (0, track_1.trackGA4)("product_init_mcp", { feature: nextFeature }, duration);
|
|
109
113
|
return actuate(setup, config, options);
|
|
110
114
|
}
|
|
111
115
|
}
|
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: [
|
package/lib/mcp/prompts/index.js
CHANGED
|
@@ -5,15 +5,15 @@ const core_1 = require("./core");
|
|
|
5
5
|
const dataconnect_1 = require("./dataconnect");
|
|
6
6
|
const crashlytics_1 = require("./crashlytics");
|
|
7
7
|
const prompts = {
|
|
8
|
-
core: core_1.corePrompts,
|
|
8
|
+
core: namespacePrompts(core_1.corePrompts, "core"),
|
|
9
9
|
firestore: [],
|
|
10
10
|
storage: [],
|
|
11
|
-
dataconnect: dataconnect_1.dataconnectPrompts,
|
|
11
|
+
dataconnect: namespacePrompts(dataconnect_1.dataconnectPrompts, "dataconnect"),
|
|
12
12
|
auth: [],
|
|
13
13
|
messaging: [],
|
|
14
14
|
functions: [],
|
|
15
15
|
remoteconfig: [],
|
|
16
|
-
crashlytics: crashlytics_1.crashlyticsPrompts,
|
|
16
|
+
crashlytics: namespacePrompts(crashlytics_1.crashlyticsPrompts, "crashlytics"),
|
|
17
17
|
apphosting: [],
|
|
18
18
|
database: [],
|
|
19
19
|
};
|
|
@@ -33,23 +33,33 @@ function namespacePrompts(promptsToNamespace, feature) {
|
|
|
33
33
|
return newPrompt;
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
function availablePrompts(activeFeatures) {
|
|
37
|
-
const allPrompts =
|
|
36
|
+
async function availablePrompts(ctx, activeFeatures) {
|
|
37
|
+
const allPrompts = getAllPrompts(activeFeatures);
|
|
38
|
+
const availabilities = await Promise.all(allPrompts.map((p) => {
|
|
39
|
+
if (p.isAvailable) {
|
|
40
|
+
return p.isAvailable(ctx);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}));
|
|
44
|
+
return allPrompts.filter((_, i) => availabilities[i]);
|
|
45
|
+
}
|
|
46
|
+
exports.availablePrompts = availablePrompts;
|
|
47
|
+
function getAllPrompts(activeFeatures) {
|
|
48
|
+
const promptDefs = [];
|
|
38
49
|
if (!(activeFeatures === null || activeFeatures === void 0 ? void 0 : activeFeatures.length)) {
|
|
39
50
|
activeFeatures = Object.keys(prompts);
|
|
40
51
|
}
|
|
41
52
|
if (!activeFeatures.includes("core")) {
|
|
42
|
-
activeFeatures
|
|
53
|
+
activeFeatures.unshift("core");
|
|
43
54
|
}
|
|
44
55
|
for (const feature of activeFeatures) {
|
|
45
|
-
|
|
56
|
+
promptDefs.push(...prompts[feature]);
|
|
46
57
|
}
|
|
47
|
-
return
|
|
58
|
+
return promptDefs;
|
|
48
59
|
}
|
|
49
|
-
exports.availablePrompts = availablePrompts;
|
|
50
60
|
function markdownDocsOfPrompts() {
|
|
51
61
|
var _a, _b;
|
|
52
|
-
const allPrompts =
|
|
62
|
+
const allPrompts = getAllPrompts();
|
|
53
63
|
let doc = `
|
|
54
64
|
| Prompt Name | Feature Group | Description |
|
|
55
65
|
| ----------- | ------------- | ----------- |`;
|
|
@@ -7,7 +7,7 @@ exports.init_backend = (0, resource_1.resource)({
|
|
|
7
7
|
name: "backend_init_guide",
|
|
8
8
|
title: "Firebase Backend Init Guide",
|
|
9
9
|
description: "guides the coding agent through configuring Firebase backend services in the current project",
|
|
10
|
-
}, async (uri) => {
|
|
10
|
+
}, async (uri, ctx) => {
|
|
11
11
|
return {
|
|
12
12
|
contents: [
|
|
13
13
|
{
|
|
@@ -24,31 +24,8 @@ The user will likely need to setup Firestore, Authentication, and Hosting. Read
|
|
|
24
24
|
3. [Firestore Rules](firebase://guides/init/firestore_rules): read this to setup the \`firestore.rules\` file for securing your database
|
|
25
25
|
4. [Hosting](firebase://guides/init/hosting): read this if the user would like to deploy to Firebase Hosting
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Here is an example firebase.json file with Firebase Hosting, Firestore, and Cloud Functions. Note that you do not need entries for services that the user isn't using. Do not remove sections from the user's firebase.json unless the user gives explicit permission. For more information, refer to [firebase.json file documentation](https://firebase.google.com/docs/cli/#the_firebasejson_file)
|
|
31
|
-
\`\`\`json
|
|
32
|
-
{
|
|
33
|
-
"hosting": {
|
|
34
|
-
"public": "public",
|
|
35
|
-
"ignore": [
|
|
36
|
-
"firebase.json",
|
|
37
|
-
"**/.*",
|
|
38
|
-
"**/node_modules/**"
|
|
39
|
-
]
|
|
40
|
-
},
|
|
41
|
-
"firestore": {
|
|
42
|
-
"rules": "firestore.rules",
|
|
43
|
-
"indexes": "firestore.indexes.json"
|
|
44
|
-
},
|
|
45
|
-
"functions": {
|
|
46
|
-
"predeploy": [
|
|
47
|
-
"npm --prefix "$RESOURCE_DIR" run lint",
|
|
48
|
-
"npm --prefix "$RESOURCE_DIR" run build"
|
|
49
|
-
]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
27
|
+
Once you are done setting up, ask the user if they would like to deploy.
|
|
28
|
+
If they say yes, run the command '${ctx.firebaseCliCommand} deploy --non-interactive' to do so.
|
|
52
29
|
\`\`\`
|
|
53
30
|
`.trim(),
|
|
54
31
|
},
|