firebase-tools 14.18.0 → 14.19.1
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/README.md +11 -5
- package/lib/appUtils.js +230 -0
- package/lib/apphosting/localbuilds.js +23 -0
- package/lib/bin/mcp.js +16 -1
- package/lib/commands/index.js +8 -0
- package/lib/commands/init.js +0 -2
- package/lib/commands/remoteconfig-experiments-delete.js +32 -0
- package/lib/commands/remoteconfig-experiments-get.js +20 -0
- package/lib/commands/remoteconfig-experiments-list.js +38 -0
- package/lib/commands/remoteconfig-rollouts-delete.js +32 -0
- package/lib/commands/remoteconfig-rollouts-get.js +20 -0
- package/lib/commands/remoteconfig-rollouts-list.js +38 -0
- package/lib/config.js +4 -2
- package/lib/deploy/apphosting/deploy.js +26 -10
- package/lib/deploy/apphosting/prepare.js +23 -1
- package/lib/deploy/apphosting/release.js +5 -0
- package/lib/deploy/apphosting/util.js +4 -3
- package/lib/deploy/functions/deploy.js +5 -4
- package/lib/deploy/functions/params.js +2 -2
- package/lib/emulator/commandUtils.js +3 -3
- package/lib/emulator/controller.js +3 -2
- package/lib/gcp/cloudsql/cloudsqladmin.js +1 -1
- package/lib/gcp/storage.js +73 -20
- package/lib/init/features/dataconnect/index.js +18 -9
- package/lib/init/features/project.js +66 -75
- package/lib/management/projects.js +16 -4
- package/lib/mcp/index.js +9 -0
- package/lib/mcp/prompts/core/deploy.js +8 -8
- package/lib/mcp/prompts/core/init.js +15 -18
- package/lib/mcp/prompts/crashlytics/connect.js +2 -2
- package/lib/mcp/prompts/index.js +30 -9
- package/lib/mcp/resources/guides/init_ai.js +8 -0
- package/lib/mcp/resources/guides/init_auth.js +4 -0
- package/lib/mcp/resources/guides/init_firestore_rules.js +2 -0
- package/lib/mcp/resources/index.js +16 -1
- package/lib/mcp/tools/apphosting/list_backends.js +1 -1
- package/lib/mcp/tools/core/get_environment.js +34 -17
- package/lib/mcp/tools/core/init.js +2 -1
- package/lib/mcp/tools/core/logout.js +1 -2
- package/lib/mcp/tools/functions/get_logs.js +9 -7
- package/lib/mcp/tools/index.js +3 -2
- package/lib/remoteconfig/deleteExperiment.js +32 -0
- package/lib/remoteconfig/deleteRollout.js +33 -0
- package/lib/remoteconfig/getExperiment.js +52 -0
- package/lib/remoteconfig/getRollout.js +43 -0
- package/lib/remoteconfig/interfaces.js +3 -1
- package/lib/remoteconfig/listExperiments.js +72 -0
- package/lib/remoteconfig/listRollouts.js +72 -0
- package/lib/remoteconfig/options.js +4 -0
- package/lib/track.js +6 -4
- package/package.json +3 -1
- package/schema/firebase-config.json +6 -0
|
@@ -8,6 +8,8 @@ const devConnect_1 = require("../../gcp/devConnect");
|
|
|
8
8
|
const projectUtils_1 = require("../../projectUtils");
|
|
9
9
|
const prompt_1 = require("../../prompt");
|
|
10
10
|
const utils_1 = require("../../utils");
|
|
11
|
+
const localbuilds_1 = require("../../apphosting/localbuilds");
|
|
12
|
+
const error_1 = require("../../error");
|
|
11
13
|
async function default_1(context, options) {
|
|
12
14
|
var _a;
|
|
13
15
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
@@ -17,6 +19,7 @@ async function default_1(context, options) {
|
|
|
17
19
|
context.backendConfigs = {};
|
|
18
20
|
context.backendLocations = {};
|
|
19
21
|
context.backendStorageUris = {};
|
|
22
|
+
context.backendLocalBuilds = {};
|
|
20
23
|
const configs = getBackendConfigs(options);
|
|
21
24
|
const { backends } = await (0, apphosting_1.listBackends)(projectId, "-");
|
|
22
25
|
const foundBackends = [];
|
|
@@ -105,7 +108,26 @@ async function default_1(context, options) {
|
|
|
105
108
|
if (skippedBackends.length > 0) {
|
|
106
109
|
(0, utils_1.logLabeledWarning)("apphosting", `Skipping deployment of backend(s) ${skippedBackends.map((cfg) => cfg.backendId).join(", ")}.`);
|
|
107
110
|
}
|
|
108
|
-
|
|
111
|
+
for (const cfg of Object.values(context.backendConfigs)) {
|
|
112
|
+
if (!cfg.localBuild) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Starting local build for backend ${cfg.backendId}`);
|
|
116
|
+
try {
|
|
117
|
+
const { outputFiles, annotations, buildConfig } = await (0, localbuilds_1.localBuild)(options.projectRoot || "./", "nextjs");
|
|
118
|
+
if (outputFiles.length !== 1) {
|
|
119
|
+
throw new error_1.FirebaseError(`Local build for backend ${cfg.backendId} failed: No output files found.`);
|
|
120
|
+
}
|
|
121
|
+
context.backendLocalBuilds[cfg.backendId] = {
|
|
122
|
+
buildDir: outputFiles[0],
|
|
123
|
+
buildConfig,
|
|
124
|
+
annotations,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
throw new error_1.FirebaseError(`Local Build for backend ${cfg.backendId} failed: ${e}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
109
131
|
}
|
|
110
132
|
exports.default = default_1;
|
|
111
133
|
function getBackendConfigs(options) {
|
|
@@ -13,6 +13,11 @@ async function default_1(context, options) {
|
|
|
13
13
|
(0, utils_1.logLabeledWarning)("apphosting", `Failed to find metadata for backend(s) ${backendIds.join(", ")}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
14
14
|
backendIds = backendIds.filter((id) => !missingBackends.includes(id));
|
|
15
15
|
}
|
|
16
|
+
const localBuildBackends = backendIds.filter((id) => context.backendLocalBuilds[id]);
|
|
17
|
+
if (localBuildBackends.length > 0) {
|
|
18
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Skipping backend(s) ${localBuildBackends.join(", ")}. Local Builds are not supported yet.`);
|
|
19
|
+
backendIds = backendIds.filter((id) => !localBuildBackends.includes(id));
|
|
20
|
+
}
|
|
16
21
|
if (backendIds.length === 0) {
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
@@ -7,20 +7,21 @@ const path = require("path");
|
|
|
7
7
|
const tmp = require("tmp");
|
|
8
8
|
const error_1 = require("../../error");
|
|
9
9
|
const fsAsync = require("../../fsAsync");
|
|
10
|
-
async function createArchive(config, rootDir) {
|
|
10
|
+
async function createArchive(config, rootDir, targetSubDir) {
|
|
11
11
|
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".zip" }).name;
|
|
12
12
|
const fileStream = fs.createWriteStream(tmpFile, {
|
|
13
13
|
flags: "w",
|
|
14
14
|
encoding: "binary",
|
|
15
15
|
});
|
|
16
16
|
const archive = archiver("zip");
|
|
17
|
+
const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
|
|
17
18
|
const ignore = config.ignore || ["node_modules", ".git"];
|
|
18
19
|
ignore.push("firebase-debug.log", "firebase-debug.*.log");
|
|
19
|
-
const gitIgnorePatterns = parseGitIgnorePatterns(
|
|
20
|
+
const gitIgnorePatterns = parseGitIgnorePatterns(targetDir);
|
|
20
21
|
ignore.push(...gitIgnorePatterns);
|
|
21
22
|
try {
|
|
22
23
|
const files = await fsAsync.readdirRecursive({
|
|
23
|
-
path:
|
|
24
|
+
path: targetDir,
|
|
24
25
|
ignore: ignore,
|
|
25
26
|
isGitIgnore: true,
|
|
26
27
|
});
|
|
@@ -59,14 +59,15 @@ async function uploadSourceV2(projectId, projectNumber, source, wantBackend) {
|
|
|
59
59
|
await gcs.upload(uploadOpts, res.uploadUrl, undefined, true);
|
|
60
60
|
return res.storageSource;
|
|
61
61
|
}
|
|
62
|
-
const
|
|
63
|
-
await gcs.upsertBucket({
|
|
62
|
+
const baseName = `firebase-functions-src-${projectNumber}`;
|
|
63
|
+
const bucketName = await gcs.upsertBucket({
|
|
64
64
|
product: "functions",
|
|
65
65
|
projectId,
|
|
66
|
-
createMessage: `Creating Cloud Storage bucket in ${region} to store Functions source code uploads at ${
|
|
66
|
+
createMessage: `Creating Cloud Storage bucket in ${region} to store Functions source code uploads at ${baseName}...`,
|
|
67
67
|
req: {
|
|
68
|
-
|
|
68
|
+
baseName,
|
|
69
69
|
location: region,
|
|
70
|
+
purposeLabel: `functions-source-${region.toLowerCase()}`,
|
|
70
71
|
lifecycle: {
|
|
71
72
|
rule: [
|
|
72
73
|
{
|
|
@@ -415,7 +415,7 @@ async function promptResourceString(prompt, input, projectId, resolvedDefault) {
|
|
|
415
415
|
const notFound = new error_1.FirebaseError(`No instances of ${input.resource.type} found.`);
|
|
416
416
|
switch (input.resource.type) {
|
|
417
417
|
case "storage.googleapis.com/Bucket":
|
|
418
|
-
const buckets = await (0, storage_1.listBuckets)(projectId);
|
|
418
|
+
const buckets = (await (0, storage_1.listBuckets)(projectId)).map((b) => b.name);
|
|
419
419
|
if (buckets.length === 0) {
|
|
420
420
|
throw notFound;
|
|
421
421
|
}
|
|
@@ -436,7 +436,7 @@ async function promptResourceStrings(prompt, input, projectId) {
|
|
|
436
436
|
const notFound = new error_1.FirebaseError(`No instances of ${input.resource.type} found.`);
|
|
437
437
|
switch (input.resource.type) {
|
|
438
438
|
case "storage.googleapis.com/Bucket":
|
|
439
|
-
const buckets = await (0, storage_1.listBuckets)(projectId);
|
|
439
|
+
const buckets = (await (0, storage_1.listBuckets)(projectId)).map((b) => b.name);
|
|
440
440
|
if (buckets.length === 0) {
|
|
441
441
|
throw notFound;
|
|
442
442
|
}
|
|
@@ -384,6 +384,6 @@ async function checkJavaMajorVersion() {
|
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
386
|
exports.checkJavaMajorVersion = checkJavaMajorVersion;
|
|
387
|
-
exports.MIN_SUPPORTED_JAVA_MAJOR_VERSION =
|
|
388
|
-
exports.JAVA_DEPRECATION_WARNING = "firebase-tools
|
|
389
|
-
"Please install a JDK at version
|
|
387
|
+
exports.MIN_SUPPORTED_JAVA_MAJOR_VERSION = 21;
|
|
388
|
+
exports.JAVA_DEPRECATION_WARNING = "firebase-tools will drop support for Java version < 21 soon in firebase-tools@15. " +
|
|
389
|
+
"Please install a JDK at version 21 or above to get a compatible runtime.";
|
|
@@ -177,10 +177,11 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
177
177
|
if (targets.length === 0) {
|
|
178
178
|
throw new error_1.FirebaseError(`No emulators to start, run ${clc.bold("firebase init emulators")} to get started.`);
|
|
179
179
|
}
|
|
180
|
+
const deprecationNotices = [];
|
|
180
181
|
if (targets.some(downloadableEmulators_1.requiresJava)) {
|
|
181
182
|
if ((await commandUtils.checkJavaMajorVersion()) < commandUtils_1.MIN_SUPPORTED_JAVA_MAJOR_VERSION) {
|
|
182
183
|
utils.logLabeledError("emulators", commandUtils_1.JAVA_DEPRECATION_WARNING, "warn");
|
|
183
|
-
|
|
184
|
+
deprecationNotices.push(commandUtils_1.JAVA_DEPRECATION_WARNING);
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
if (options.logVerbosity) {
|
|
@@ -685,7 +686,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
685
686
|
count_all: running.length,
|
|
686
687
|
is_demo_project: String(isDemoProject),
|
|
687
688
|
});
|
|
688
|
-
return { deprecationNotices
|
|
689
|
+
return { deprecationNotices };
|
|
689
690
|
}
|
|
690
691
|
exports.startAll = startAll;
|
|
691
692
|
function getListenConfig(options, emulator) {
|
|
@@ -60,7 +60,7 @@ async function createInstance(args) {
|
|
|
60
60
|
await client.post(`projects/${args.projectId}/instances`, {
|
|
61
61
|
name: args.instanceId,
|
|
62
62
|
region: args.location,
|
|
63
|
-
databaseVersion: "
|
|
63
|
+
databaseVersion: "POSTGRES_15",
|
|
64
64
|
settings: {
|
|
65
65
|
tier: "db-f1-micro",
|
|
66
66
|
edition: "ENTERPRISE",
|
package/lib/gcp/storage.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getDownloadUrl = exports.getServiceAccount = exports.listBuckets = exports.upsertBucket = exports.createBucket = exports.getBucket = exports.deleteObject = exports.getObject = exports.uploadObject = exports.upload = exports.getDefaultBucket = void 0;
|
|
3
|
+
exports.getDownloadUrl = exports.getServiceAccount = exports.listBuckets = exports.upsertBucket = exports.randomString = exports.patchBucket = exports.createBucket = exports.getBucket = exports.deleteObject = exports.getObject = exports.uploadObject = exports.upload = exports.getDefaultBucket = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const clc = require("colorette");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
6
7
|
const api_1 = require("../api");
|
|
7
8
|
const apiv2_1 = require("../apiv2");
|
|
8
9
|
const error_1 = require("../error");
|
|
9
10
|
const logger_1 = require("../logger");
|
|
10
11
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
11
12
|
const utils = require("../utils");
|
|
13
|
+
const proto_1 = require("./proto");
|
|
12
14
|
async function getDefaultBucket(projectId) {
|
|
13
15
|
var _a;
|
|
14
16
|
await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.firebaseStorageOrigin)(), "storage", false);
|
|
@@ -120,37 +122,88 @@ async function createBucket(projectId, req, projectPrivate) {
|
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
exports.createBucket = createBucket;
|
|
123
|
-
async function
|
|
125
|
+
async function patchBucket(bucketName, metadata) {
|
|
124
126
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
const localAPIClient = new apiv2_1.Client({ urlPrefix: (0, api_1.storageOrigin)() });
|
|
128
|
+
const mask = (0, proto_1.fieldMasks)(metadata, "labels", "acl", "defaultObjectAcl", "lifecycle");
|
|
129
|
+
const result = await localAPIClient.patch(`/storage/v1/b/${bucketName}`, metadata, { queryParams: { updateMask: mask.join(",") } });
|
|
130
|
+
return result.body;
|
|
127
131
|
}
|
|
128
132
|
catch (err) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
+
logger_1.logger.debug(err);
|
|
134
|
+
throw new error_1.FirebaseError("Failed to patch the storage bucket", {
|
|
135
|
+
original: err,
|
|
136
|
+
});
|
|
133
137
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
}
|
|
139
|
+
exports.patchBucket = patchBucket;
|
|
140
|
+
function randomString(length) {
|
|
141
|
+
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
142
|
+
let result = "";
|
|
143
|
+
for (let i = length; i > 0; --i) {
|
|
144
|
+
result += chars[(0, crypto_1.randomInt)(chars.length)];
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
exports.randomString = randomString;
|
|
149
|
+
const dynamicDispatch = exports;
|
|
150
|
+
async function upsertBucket(opts) {
|
|
151
|
+
const existingBuckets = await dynamicDispatch.listBuckets(opts.projectId);
|
|
152
|
+
const managedBucket = existingBuckets.find((b) => opts.req.purposeLabel in (b.labels || {}));
|
|
153
|
+
if (managedBucket) {
|
|
154
|
+
return managedBucket.name;
|
|
155
|
+
}
|
|
156
|
+
const existingUnmanaged = existingBuckets.find((b) => b.name === opts.req.baseName);
|
|
157
|
+
if (existingUnmanaged) {
|
|
158
|
+
logger_1.logger.debug(`Found existing bucket ${existingUnmanaged.name} without purpose label. Because it is known not to be squatted, we can use it.`);
|
|
159
|
+
const labels = Object.assign(Object.assign({}, existingUnmanaged.labels), { [opts.req.purposeLabel]: "true" });
|
|
160
|
+
await dynamicDispatch.patchBucket(existingUnmanaged.name, { labels });
|
|
161
|
+
return existingUnmanaged.name;
|
|
137
162
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
163
|
+
utils.logLabeledBullet(opts.product, opts.createMessage);
|
|
164
|
+
for (let retryCount = 0; retryCount < 5; retryCount++) {
|
|
165
|
+
const name = retryCount === 0
|
|
166
|
+
? opts.req.baseName
|
|
167
|
+
: `${opts.req.baseName}-${dynamicDispatch.randomString(6)}`;
|
|
168
|
+
try {
|
|
169
|
+
await dynamicDispatch.createBucket(opts.projectId, {
|
|
170
|
+
name,
|
|
171
|
+
location: opts.req.location,
|
|
172
|
+
lifecycle: opts.req.lifecycle,
|
|
173
|
+
labels: {
|
|
174
|
+
[opts.req.purposeLabel]: "true",
|
|
175
|
+
},
|
|
176
|
+
}, true);
|
|
177
|
+
return name;
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if ((0, error_1.getErrStatus)(err.original) === 409) {
|
|
181
|
+
utils.logLabeledBullet(opts.product, `Bucket ${name} already exists, creating a new bucket with a conflict-avoiding hash`);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if ((0, error_1.getErrStatus)(err.original) === 403) {
|
|
185
|
+
utils.logLabeledWarning(opts.product, "Failed to create Cloud Storage bucket because user does not have sufficient permissions. " +
|
|
186
|
+
"See https://cloud.google.com/storage/docs/access-control/iam-roles for more details on " +
|
|
187
|
+
"IAM roles that are able to create a Cloud Storage bucket, and ask your project administrator " +
|
|
188
|
+
"to grant you one of those roles.");
|
|
189
|
+
}
|
|
190
|
+
throw err;
|
|
144
191
|
}
|
|
145
|
-
throw err;
|
|
146
192
|
}
|
|
193
|
+
throw new error_1.FirebaseError("Failed to create a unique Cloud Storage bucket name after 5 attempts.");
|
|
147
194
|
}
|
|
148
195
|
exports.upsertBucket = upsertBucket;
|
|
149
196
|
async function listBuckets(projectId) {
|
|
150
197
|
try {
|
|
198
|
+
let buckets = [];
|
|
151
199
|
const localAPIClient = new apiv2_1.Client({ urlPrefix: (0, api_1.storageOrigin)() });
|
|
152
|
-
|
|
153
|
-
|
|
200
|
+
let pageToken;
|
|
201
|
+
do {
|
|
202
|
+
const result = await localAPIClient.get(`/storage/v1/b?project=${projectId}`, { queryParams: pageToken ? { pageToken } : {} });
|
|
203
|
+
buckets = buckets.concat(result.body.items || []);
|
|
204
|
+
pageToken = result.body.nextPageToken;
|
|
205
|
+
} while (pageToken);
|
|
206
|
+
return buckets;
|
|
154
207
|
}
|
|
155
208
|
catch (err) {
|
|
156
209
|
logger_1.logger.debug(err);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.newUniqueId = exports.toDNSCompatibleId = exports.actuate = exports.askQuestions = void 0;
|
|
3
|
+
exports.newUniqueId = exports.toDNSCompatibleId = exports.actuate = exports.askQuestions = exports.FDC_DEFAULT_REGION = void 0;
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const clc = require("colorette");
|
|
6
6
|
const fs = require("fs-extra");
|
|
@@ -21,6 +21,7 @@ const sdk = require("./sdk");
|
|
|
21
21
|
const fdcExperience_1 = require("../../../gemini/fdcExperience");
|
|
22
22
|
const configstore_1 = require("../../../configstore");
|
|
23
23
|
const track_1 = require("../../../track");
|
|
24
|
+
exports.FDC_DEFAULT_REGION = "us-east4";
|
|
24
25
|
const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
|
|
25
26
|
const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
|
|
26
27
|
const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
|
|
@@ -76,6 +77,9 @@ async function askQuestions(setup) {
|
|
|
76
77
|
if (hasBilling) {
|
|
77
78
|
await promptForCloudSQL(setup, info);
|
|
78
79
|
}
|
|
80
|
+
else if (info.appDescription) {
|
|
81
|
+
await promptForLocation(setup, info);
|
|
82
|
+
}
|
|
79
83
|
}
|
|
80
84
|
setup.featureInfo = setup.featureInfo || {};
|
|
81
85
|
setup.featureInfo.dataconnect = info;
|
|
@@ -93,7 +97,7 @@ async function actuate(setup, config, options) {
|
|
|
93
97
|
}
|
|
94
98
|
info.serviceId = info.serviceId || defaultServiceId();
|
|
95
99
|
info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId.toLowerCase()}-fdc`;
|
|
96
|
-
info.locationId = info.locationId ||
|
|
100
|
+
info.locationId = info.locationId || exports.FDC_DEFAULT_REGION;
|
|
97
101
|
info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
|
|
98
102
|
try {
|
|
99
103
|
await actuateWithInfo(setup, config, info, options);
|
|
@@ -269,10 +273,10 @@ async function writeConnectorFiles(config, connectorInfo, options) {
|
|
|
269
273
|
function subDataconnectYamlValues(replacementValues) {
|
|
270
274
|
const replacements = {
|
|
271
275
|
serviceId: "__serviceId__",
|
|
276
|
+
locationId: "__location__",
|
|
272
277
|
cloudSqlDatabase: "__cloudSqlDatabase__",
|
|
273
278
|
cloudSqlInstanceId: "__cloudSqlInstanceId__",
|
|
274
279
|
connectorDirs: "__connectorDirs__",
|
|
275
|
-
locationId: "__location__",
|
|
276
280
|
};
|
|
277
281
|
let replaced = DATACONNECT_YAML_TEMPLATE;
|
|
278
282
|
for (const [k, v] of Object.entries(replacementValues)) {
|
|
@@ -422,12 +426,7 @@ async function promptForCloudSQL(setup, info) {
|
|
|
422
426
|
}
|
|
423
427
|
}
|
|
424
428
|
if (info.locationId === "") {
|
|
425
|
-
|
|
426
|
-
info.locationId = await (0, prompt_1.select)({
|
|
427
|
-
message: "What location would like to use?",
|
|
428
|
-
choices,
|
|
429
|
-
default: "us-east4",
|
|
430
|
-
});
|
|
429
|
+
await promptForLocation(setup, info);
|
|
431
430
|
info.shouldProvisionCSQL = await (0, prompt_1.confirm)({
|
|
432
431
|
message: `Would you like to provision your Cloud SQL instance and database now?`,
|
|
433
432
|
default: true,
|
|
@@ -445,6 +444,16 @@ async function promptForCloudSQL(setup, info) {
|
|
|
445
444
|
}
|
|
446
445
|
return;
|
|
447
446
|
}
|
|
447
|
+
async function promptForLocation(setup, info) {
|
|
448
|
+
if (info.locationId === "") {
|
|
449
|
+
const choices = await locationChoices(setup);
|
|
450
|
+
info.locationId = await (0, prompt_1.select)({
|
|
451
|
+
message: "What location should the new Cloud SQL instance be in?",
|
|
452
|
+
choices,
|
|
453
|
+
default: exports.FDC_DEFAULT_REGION,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
448
457
|
async function locationChoices(setup) {
|
|
449
458
|
if (setup.projectId) {
|
|
450
459
|
const locations = await (0, client_1.listLocations)(setup.projectId);
|
|
@@ -3,101 +3,92 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.doSetup = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const _ = require("lodash");
|
|
6
|
-
const error_1 = require("../../error");
|
|
7
6
|
const projects_1 = require("../../management/projects");
|
|
8
7
|
const logger_1 = require("../../logger");
|
|
9
8
|
const utils = require("../../utils");
|
|
10
9
|
const prompt = require("../../prompt");
|
|
10
|
+
const requireAuth_1 = require("../../requireAuth");
|
|
11
|
+
const constants_1 = require("../../emulator/constants");
|
|
12
|
+
const error_1 = require("../../error");
|
|
11
13
|
const OPTION_NO_PROJECT = "Don't set up a default project";
|
|
12
14
|
const OPTION_USE_PROJECT = "Use an existing project";
|
|
13
15
|
const OPTION_NEW_PROJECT = "Create a new project";
|
|
14
16
|
const OPTION_ADD_FIREBASE = "Add Firebase to an existing Google Cloud Platform project";
|
|
15
|
-
function toInitProjectInfo(projectMetaData) {
|
|
16
|
-
const { projectId, displayName, resources } = projectMetaData;
|
|
17
|
-
return {
|
|
18
|
-
id: projectId,
|
|
19
|
-
label: `${projectId}` + (displayName ? ` (${displayName})` : ""),
|
|
20
|
-
instance: resources === null || resources === void 0 ? void 0 : resources.realtimeDatabaseInstance,
|
|
21
|
-
location: resources === null || resources === void 0 ? void 0 : resources.locationId,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
async function promptAndCreateNewProject(options) {
|
|
25
|
-
utils.logBullet("If you want to create a project in a Google Cloud organization or folder, please use " +
|
|
26
|
-
`"firebase projects:create" instead, and return to this command when you've created the project.`);
|
|
27
|
-
const { projectId, displayName } = await (0, projects_1.promptProjectCreation)(options);
|
|
28
|
-
if (!projectId) {
|
|
29
|
-
throw new error_1.FirebaseError("Project ID cannot be empty");
|
|
30
|
-
}
|
|
31
|
-
return await (0, projects_1.createFirebaseProjectAndLog)(projectId, { displayName });
|
|
32
|
-
}
|
|
33
|
-
async function promptAndAddFirebaseToCloudProject() {
|
|
34
|
-
const projectId = await (0, projects_1.promptAvailableProjectId)();
|
|
35
|
-
if (!projectId) {
|
|
36
|
-
throw new error_1.FirebaseError("Project ID cannot be empty");
|
|
37
|
-
}
|
|
38
|
-
return await (0, projects_1.addFirebaseToCloudProjectAndLog)(projectId);
|
|
39
|
-
}
|
|
40
|
-
async function projectChoicePrompt(options) {
|
|
41
|
-
const choices = [OPTION_USE_PROJECT, OPTION_NEW_PROJECT, OPTION_ADD_FIREBASE, OPTION_NO_PROJECT];
|
|
42
|
-
const projectSetupOption = await prompt.select({
|
|
43
|
-
message: "Please select an option:",
|
|
44
|
-
choices,
|
|
45
|
-
});
|
|
46
|
-
switch (projectSetupOption) {
|
|
47
|
-
case OPTION_USE_PROJECT:
|
|
48
|
-
return (0, projects_1.getOrPromptProject)(options);
|
|
49
|
-
case OPTION_NEW_PROJECT:
|
|
50
|
-
return promptAndCreateNewProject(options);
|
|
51
|
-
case OPTION_ADD_FIREBASE:
|
|
52
|
-
return promptAndAddFirebaseToCloudProject();
|
|
53
|
-
default:
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
17
|
async function doSetup(setup, config, options) {
|
|
58
|
-
var _a, _b
|
|
18
|
+
var _a, _b;
|
|
59
19
|
setup.project = {};
|
|
60
20
|
logger_1.logger.info();
|
|
61
21
|
logger_1.logger.info(`First, let's associate this project directory with a Firebase project.`);
|
|
62
22
|
logger_1.logger.info(`You can create multiple project aliases by running ${clc.bold("firebase use --add")}, `);
|
|
63
|
-
logger_1.logger.info(`but for now we'll just set up a default project.`);
|
|
64
23
|
logger_1.logger.info();
|
|
24
|
+
if (options.project) {
|
|
25
|
+
if (constants_1.Constants.isDemoProject(options.project)) {
|
|
26
|
+
logger_1.logger.info(`Skipping Firebase project setup because a demo project is provided`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await (0, requireAuth_1.requireAuth)(options);
|
|
30
|
+
await usingProject(setup, config, options.project);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
65
33
|
const projectFromRcFile = (_b = (_a = setup.rcfile) === null || _a === void 0 ? void 0 : _a.projects) === null || _b === void 0 ? void 0 : _b.default;
|
|
66
|
-
if (projectFromRcFile
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
setup.projectId = rcProject.projectId;
|
|
70
|
-
setup.projectLocation = (_c = rcProject === null || rcProject === void 0 ? void 0 : rcProject.resources) === null || _c === void 0 ? void 0 : _c.locationId;
|
|
34
|
+
if (projectFromRcFile) {
|
|
35
|
+
await (0, requireAuth_1.requireAuth)(options);
|
|
36
|
+
await usingProject(setup, config, projectFromRcFile, ".firebaserc");
|
|
71
37
|
return;
|
|
72
38
|
}
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
39
|
+
const projectEnvVar = utils.envOverride("FIREBASE_PROJECT", "");
|
|
40
|
+
if (projectEnvVar) {
|
|
41
|
+
await (0, requireAuth_1.requireAuth)(options);
|
|
42
|
+
await usingProject(setup, config, projectEnvVar, "$FIREBASE_PROJECT");
|
|
43
|
+
return;
|
|
77
44
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
45
|
+
if (options.nonInteractive) {
|
|
46
|
+
logger_1.logger.info("No default project found. Continuing without a project in non interactive mode.");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const choices = [OPTION_USE_PROJECT, OPTION_NEW_PROJECT, OPTION_ADD_FIREBASE, OPTION_NO_PROJECT];
|
|
50
|
+
const projectSetupOption = await prompt.select({
|
|
51
|
+
message: "Please select an option:",
|
|
52
|
+
choices,
|
|
53
|
+
});
|
|
54
|
+
switch (projectSetupOption) {
|
|
55
|
+
case OPTION_USE_PROJECT: {
|
|
56
|
+
await (0, requireAuth_1.requireAuth)(options);
|
|
57
|
+
const pm = await (0, projects_1.selectProjectInteractively)();
|
|
58
|
+
return await usingProjectMetadata(setup, config, pm);
|
|
83
59
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
60
|
+
case OPTION_NEW_PROJECT: {
|
|
61
|
+
utils.logBullet("If you want to create a project in a Google Cloud organization or folder, please use " +
|
|
62
|
+
`"firebase projects:create" instead, and return to this command when you've created the project.`);
|
|
63
|
+
await (0, requireAuth_1.requireAuth)(options);
|
|
64
|
+
const { projectId, displayName } = await (0, projects_1.promptProjectCreation)(options);
|
|
65
|
+
const pm = await (0, projects_1.createFirebaseProjectAndLog)(projectId, { displayName });
|
|
66
|
+
return await usingProjectMetadata(setup, config, pm);
|
|
93
67
|
}
|
|
68
|
+
case OPTION_ADD_FIREBASE: {
|
|
69
|
+
await (0, requireAuth_1.requireAuth)(options);
|
|
70
|
+
const pm = await (0, projects_1.addFirebaseToCloudProjectAndLog)(await (0, projects_1.promptAvailableProjectId)());
|
|
71
|
+
return await usingProjectMetadata(setup, config, pm);
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
return;
|
|
94
75
|
}
|
|
95
|
-
const projectInfo = toInitProjectInfo(projectMetaData);
|
|
96
|
-
utils.logBullet(`Using project ${projectInfo.label}`);
|
|
97
|
-
_.set(setup.rcfile, "projects.default", projectInfo.id);
|
|
98
|
-
setup.projectId = projectInfo.id;
|
|
99
|
-
setup.instance = projectInfo.instance;
|
|
100
|
-
setup.projectLocation = projectInfo.location;
|
|
101
|
-
utils.makeActiveProject(config.projectDir, projectInfo.id);
|
|
102
76
|
}
|
|
103
77
|
exports.doSetup = doSetup;
|
|
78
|
+
async function usingProject(setup, config, projectId, from = "") {
|
|
79
|
+
const pm = await (0, projects_1.getFirebaseProject)(projectId);
|
|
80
|
+
const label = `${pm.projectId}` + (pm.displayName ? ` (${pm.displayName})` : "");
|
|
81
|
+
utils.logBullet(`Using project ${label} ${from ? "from ${from}" : ""}.`);
|
|
82
|
+
await usingProjectMetadata(setup, config, pm);
|
|
83
|
+
}
|
|
84
|
+
async function usingProjectMetadata(setup, config, pm) {
|
|
85
|
+
var _a, _b;
|
|
86
|
+
if (!pm) {
|
|
87
|
+
throw new error_1.FirebaseError("null FirebaseProjectMetadata");
|
|
88
|
+
}
|
|
89
|
+
_.set(setup.rcfile, "projects.default", pm.projectId);
|
|
90
|
+
setup.projectId = pm.projectId;
|
|
91
|
+
setup.instance = (_a = pm.resources) === null || _a === void 0 ? void 0 : _a.realtimeDatabaseInstance;
|
|
92
|
+
setup.projectLocation = (_b = pm.resources) === null || _b === void 0 ? void 0 : _b.locationId;
|
|
93
|
+
utils.makeActiveProject(config.projectDir, pm.projectId);
|
|
94
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkFirebaseEnabledForCloudProject = exports.getProject = exports.getFirebaseProject = exports.checkAndRecommendProjectId = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.promptProjectCreation = exports.ProjectParentResourceType = void 0;
|
|
3
|
+
exports.checkFirebaseEnabledForCloudProject = exports.getProject = exports.getFirebaseProject = exports.checkAndRecommendProjectId = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.selectProjectInteractively = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.promptProjectCreation = exports.ProjectParentResourceType = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const ora = require("ora");
|
|
6
6
|
const apiv2_1 = require("../apiv2");
|
|
@@ -11,6 +11,7 @@ const api = require("../api");
|
|
|
11
11
|
const logger_1 = require("../logger");
|
|
12
12
|
const utils = require("../utils");
|
|
13
13
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
14
|
+
const constants_1 = require("../emulator/constants");
|
|
14
15
|
const TIMEOUT_MILLIS = 30000;
|
|
15
16
|
const MAXIMUM_PROMPT_LIST = 100;
|
|
16
17
|
const PROJECT_LIST_PAGE_SIZE = 1000;
|
|
@@ -33,6 +34,9 @@ async function promptProjectCreation(options) {
|
|
|
33
34
|
else if (projectId.length > 30) {
|
|
34
35
|
return "Project ID cannot be longer than 30 characters";
|
|
35
36
|
}
|
|
37
|
+
if (constants_1.Constants.isDemoProject(projectId)) {
|
|
38
|
+
return "Project ID cannot starts with demo-";
|
|
39
|
+
}
|
|
36
40
|
try {
|
|
37
41
|
const { isAvailable, suggestedProjectId } = await checkAndRecommendProjectId(projectId);
|
|
38
42
|
if (!isAvailable && suggestedProjectId) {
|
|
@@ -137,13 +141,20 @@ async function selectProjectInteractively(pageSize = MAXIMUM_PROMPT_LIST) {
|
|
|
137
141
|
}
|
|
138
142
|
if (nextPageToken) {
|
|
139
143
|
logger_1.logger.debug(`Found more than ${projects.length} projects, selecting via prompt`);
|
|
140
|
-
return selectProjectByPrompting();
|
|
144
|
+
return await getFirebaseProject(await selectProjectByPrompting());
|
|
141
145
|
}
|
|
142
146
|
return selectProjectFromList(projects);
|
|
143
147
|
}
|
|
148
|
+
exports.selectProjectInteractively = selectProjectInteractively;
|
|
144
149
|
async function selectProjectByPrompting() {
|
|
145
150
|
const projectId = await prompt.input("Please input the project ID you would like to use:");
|
|
146
|
-
|
|
151
|
+
if (!projectId) {
|
|
152
|
+
throw new error_1.FirebaseError("Project ID cannot be empty");
|
|
153
|
+
}
|
|
154
|
+
if (constants_1.Constants.isDemoProject(projectId)) {
|
|
155
|
+
throw new error_1.FirebaseError("Project ID cannot starts with demo-");
|
|
156
|
+
}
|
|
157
|
+
return projectId;
|
|
147
158
|
}
|
|
148
159
|
async function selectProjectFromList(projects = []) {
|
|
149
160
|
const choices = projects
|
|
@@ -179,7 +190,8 @@ async function promptAvailableProjectId() {
|
|
|
179
190
|
throw new error_1.FirebaseError("There are no available Google Cloud projects to add Firebase services.");
|
|
180
191
|
}
|
|
181
192
|
if (nextPageToken) {
|
|
182
|
-
|
|
193
|
+
logger_1.logger.debug(`Found more than ${projects.length} projects, selecting via prompt`);
|
|
194
|
+
return await selectProjectByPrompting();
|
|
183
195
|
}
|
|
184
196
|
else {
|
|
185
197
|
const choices = projects
|
package/lib/mcp/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const logging_transport_1 = require("./logging-transport");
|
|
|
22
22
|
const env_1 = require("../env");
|
|
23
23
|
const timeout_1 = require("../timeout");
|
|
24
24
|
const resources_1 = require("./resources");
|
|
25
|
+
const crossSpawn = require("cross-spawn");
|
|
25
26
|
const SERVER_VERSION = "0.3.0";
|
|
26
27
|
const cmd = new command_1.Command("mcp");
|
|
27
28
|
const orderedLogLevels = [
|
|
@@ -212,8 +213,16 @@ class FirebaseMcpServer {
|
|
|
212
213
|
config: config_1.Config.load(options, true) || new config_1.Config({}, options),
|
|
213
214
|
rc: (0, rc_1.loadRC)(options),
|
|
214
215
|
accountEmail,
|
|
216
|
+
firebaseCliCommand: this._getFirebaseCliCommand(),
|
|
215
217
|
};
|
|
216
218
|
}
|
|
219
|
+
_getFirebaseCliCommand() {
|
|
220
|
+
if (!this.cliCommand) {
|
|
221
|
+
const testCommand = crossSpawn.sync("firebase --version");
|
|
222
|
+
this.cliCommand = testCommand.error ? "npx firebase-tools@latest" : "firebase";
|
|
223
|
+
}
|
|
224
|
+
return this.cliCommand;
|
|
225
|
+
}
|
|
217
226
|
async mcpListTools() {
|
|
218
227
|
await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]);
|
|
219
228
|
const hasActiveProject = !!(await this.getProjectId());
|