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