firebase-tools 14.4.0 → 14.5.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/apphosting/backend.js +2 -0
- package/lib/bin/mcp.js +1 -0
- package/lib/commands/init.js +0 -3
- package/lib/config.js +42 -24
- package/lib/dataconnect/cloudAICompanionClient.js +7 -2
- package/lib/dataconnect/cloudAICompanionTypes.js +2 -0
- package/lib/dataconnect/fileUtils.js +11 -4
- package/lib/dataconnect/schemaMigration.js +6 -7
- package/lib/deploy/apphosting/deploy.js +3 -0
- package/lib/deploy/apphosting/prepare.js +34 -28
- package/lib/deploy/apphosting/release.js +3 -0
- package/lib/deploy/firestore/deploy.js +47 -4
- package/lib/emulator/apphosting/serve.js +1 -1
- package/lib/emulator/downloadableEmulatorInfo.json +39 -18
- package/lib/emulator/downloadableEmulators.js +15 -59
- package/lib/extensions/manifest.js +0 -3
- package/lib/frameworks/angular/index.js +1 -1
- package/lib/frameworks/angular/utils.js +17 -6
- package/lib/gcp/apphosting.js +13 -1
- package/lib/gcp/run.js +19 -1
- package/lib/init/features/apphosting.js +3 -2
- package/lib/init/features/database.js +11 -19
- package/lib/init/features/dataconnect/index.js +27 -26
- package/lib/init/features/dataconnect/sdk.js +19 -6
- package/lib/init/features/emulators.js +4 -3
- package/lib/init/features/firestore/index.js +44 -34
- package/lib/init/features/firestore/indexes.js +12 -13
- package/lib/init/features/firestore/rules.js +8 -15
- package/lib/init/features/genkit/index.js +16 -9
- package/lib/init/features/hosting/index.js +9 -8
- package/lib/init/features/index.js +3 -2
- package/lib/init/features/storage.js +31 -8
- package/lib/init/index.js +5 -1
- package/lib/mcp/index.js +10 -6
- package/lib/mcp/tool.js +2 -1
- package/lib/mcp/tools/apphosting/fetch_logs.js +69 -0
- package/lib/mcp/tools/apphosting/index.js +6 -0
- package/lib/mcp/tools/apphosting/list_backends.js +51 -0
- package/lib/mcp/tools/core/index.js +2 -0
- package/lib/mcp/tools/core/init.js +26 -2
- package/lib/mcp/tools/core/list_apps.js +10 -5
- package/lib/mcp/tools/core/list_projects.js +45 -0
- package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
- package/lib/mcp/tools/firestore/query_collection.js +13 -7
- package/lib/mcp/tools/index.js +2 -0
- package/lib/mcp/tools/storage/get_download_url.js +1 -1
- package/lib/mcp/types.js +1 -0
- package/lib/mcp/util.js +137 -1
- package/lib/mcp/util.test.js +468 -0
- package/lib/prompt.js +1 -1
- package/lib/track.js +1 -1
- package/package.json +1 -1
- package/schema/connector-yaml.json +12 -0
- package/schema/extension-yaml.json +17 -4
- package/schema/firebase-config.json +3 -0
- package/standalone/package.json +1 -1
- package/templates/dataconnect-prompts/operation-generation-cursor-windsurf-rule.txt +273 -0
- package/templates/dataconnect-prompts/schema-generation-cursor-windsurf-rule.txt +653 -0
- package/templates/genkit/firebase.1.0.0.template +5 -0
- package/templates/init/firestore/firestore.rules +2 -0
- package/templates/init/functions/typescript/package.lint.json +1 -1
- package/templates/init/functions/typescript/package.nolint.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readInstanceParam = exports.writeExtensionsToFirebaseJson = exports.getInstanceRef = exports.getInstanceTarget = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeLocalSecrets = exports.writeEmptyManifest = exports.writeToManifest = exports.ENV_DIRECTORY = void 0;
|
|
4
|
-
const clc = require("colorette");
|
|
5
4
|
const path = require("path");
|
|
6
5
|
const fs = require("fs-extra");
|
|
7
6
|
const refs = require("./refs");
|
|
@@ -11,7 +10,6 @@ const logger_1 = require("../logger");
|
|
|
11
10
|
const prompt_1 = require("../prompt");
|
|
12
11
|
const paramHelper_1 = require("./paramHelper");
|
|
13
12
|
const error_1 = require("../error");
|
|
14
|
-
const utils = require("../utils");
|
|
15
13
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
16
14
|
const types_1 = require("./types");
|
|
17
15
|
exports.ENV_DIRECTORY = "extensions";
|
|
@@ -151,7 +149,6 @@ function writeExtensionsToFirebaseJson(specs, config) {
|
|
|
151
149
|
}
|
|
152
150
|
config.set("extensions", extensions);
|
|
153
151
|
config.writeProjectFile("firebase.json", config.src);
|
|
154
|
-
utils.logSuccess("Wrote extensions to " + clc.bold("firebase.json") + "...");
|
|
155
152
|
}
|
|
156
153
|
exports.writeExtensionsToFirebaseJson = writeExtensionsToFirebaseJson;
|
|
157
154
|
async function writeEnvFiles(specs, config, force) {
|
|
@@ -15,7 +15,7 @@ exports.support = "preview";
|
|
|
15
15
|
exports.type = 3;
|
|
16
16
|
exports.docsUrl = "https://firebase.google.com/docs/hosting/frameworks/angular";
|
|
17
17
|
const DEFAULT_BUILD_SCRIPT = ["ng build"];
|
|
18
|
-
exports.supportedRange = "16 -
|
|
18
|
+
exports.supportedRange = "16 - 20";
|
|
19
19
|
async function discover(dir) {
|
|
20
20
|
if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
|
|
21
21
|
return;
|
|
@@ -381,8 +381,10 @@ async function getBrowserConfig(sourceDir, configuration) {
|
|
|
381
381
|
architectHost.getOptionsForTarget(buildOrBrowserTarget),
|
|
382
382
|
architectHost.getBuilderNameForTarget(buildOrBrowserTarget),
|
|
383
383
|
]);
|
|
384
|
-
|
|
385
|
-
|
|
384
|
+
const buildOutputPath = typeof (targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.outputPath) === "string"
|
|
385
|
+
? targetOptions.outputPath
|
|
386
|
+
: (0, path_1.join)("dist", buildOrBrowserTarget.project);
|
|
387
|
+
const outputPath = (0, path_1.join)(buildOutputPath, buildTarget && getBuilderType(builderName) === BuilderType.APPLICATION ? "browser" : "");
|
|
386
388
|
return { locales, baseHref, outputPath, defaultLocale };
|
|
387
389
|
}
|
|
388
390
|
exports.getBrowserConfig = getBrowserConfig;
|
|
@@ -394,8 +396,10 @@ async function getServerConfig(sourceDir, configuration) {
|
|
|
394
396
|
throw new assert_1.AssertionError({ message: "expected build or browser target to be defined" });
|
|
395
397
|
}
|
|
396
398
|
const browserTargetOptions = await architectHost.getOptionsForTarget(buildOrBrowserTarget);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
+
const buildOutputPath = typeof (browserTargetOptions === null || browserTargetOptions === void 0 ? void 0 : browserTargetOptions.outputPath) === "string"
|
|
400
|
+
? browserTargetOptions.outputPath
|
|
401
|
+
: (0, path_1.join)("dist", buildOrBrowserTarget.project);
|
|
402
|
+
const browserOutputPath = (0, path_1.join)(buildOutputPath, buildTarget ? "browser" : "")
|
|
399
403
|
.split(path_1.sep)
|
|
400
404
|
.join(path_1.posix.sep);
|
|
401
405
|
const packageJson = JSON.parse(await host.readFile((0, path_1.join)(sourceDir, "package.json")));
|
|
@@ -419,8 +423,15 @@ async function getServerConfig(sourceDir, configuration) {
|
|
|
419
423
|
}
|
|
420
424
|
const { locales: serverLocales, defaultLocale } = await localesForTarget(sourceDir, architectHost, buildOrServerTarget, workspaceProject);
|
|
421
425
|
const serverTargetOptions = await architectHost.getOptionsForTarget(buildOrServerTarget);
|
|
422
|
-
|
|
423
|
-
|
|
426
|
+
if (!serverTargetOptions) {
|
|
427
|
+
throw new assert_1.AssertionError({
|
|
428
|
+
message: `expected "JsonObject" but got "${typeof serverTargetOptions}"`,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
const serverTargetOutputPath = typeof (serverTargetOptions === null || serverTargetOptions === void 0 ? void 0 : serverTargetOptions.outputPath) === "string"
|
|
432
|
+
? serverTargetOptions.outputPath
|
|
433
|
+
: (0, path_1.join)("dist", buildOrServerTarget.project);
|
|
434
|
+
const serverOutputPath = (0, path_1.join)(serverTargetOutputPath, buildTarget ? "server" : "")
|
|
424
435
|
.split(path_1.sep)
|
|
425
436
|
.join(path_1.posix.sep);
|
|
426
437
|
if (serverLocales && !defaultLocale) {
|
package/lib/gcp/apphosting.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getNextRolloutId = exports.ensureApiEnabled = exports.listLocations = exports.updateTraffic = exports.listRollouts = exports.createRollout = exports.createBuild = exports.listBuilds = exports.getBuild = exports.deleteBackend = exports.listBackends = exports.getBackend = exports.createBackend = exports.parseBackendName = exports.serviceAgentEmail = exports.client = exports.API_VERSION = void 0;
|
|
3
|
+
exports.getNextRolloutId = exports.ensureApiEnabled = exports.listLocations = exports.updateTraffic = exports.listRollouts = exports.createRollout = exports.createBuild = exports.listBuilds = exports.getBuild = exports.deleteBackend = exports.listBackends = exports.listDomains = exports.getTraffic = exports.getBackend = exports.createBackend = exports.parseBackendName = exports.serviceAgentEmail = exports.client = exports.API_VERSION = void 0;
|
|
4
4
|
const proto = require("../gcp/proto");
|
|
5
5
|
const apiv2_1 = require("../apiv2");
|
|
6
6
|
const projectUtils_1 = require("../projectUtils");
|
|
@@ -40,6 +40,18 @@ async function getBackend(projectId, location, backendId) {
|
|
|
40
40
|
return res.body;
|
|
41
41
|
}
|
|
42
42
|
exports.getBackend = getBackend;
|
|
43
|
+
async function getTraffic(projectId, location, backendId) {
|
|
44
|
+
const name = `projects/${projectId}/locations/${location}/backends/${backendId}/traffic`;
|
|
45
|
+
const res = await exports.client.get(name);
|
|
46
|
+
return res.body;
|
|
47
|
+
}
|
|
48
|
+
exports.getTraffic = getTraffic;
|
|
49
|
+
async function listDomains(projectId, location, backendId) {
|
|
50
|
+
const name = `projects/${projectId}/locations/${location}/backends/${backendId}/domains`;
|
|
51
|
+
const res = await exports.client.get(name, { queryParams: { pageSize: 100 } });
|
|
52
|
+
return Array.isArray(res.body.domains) ? res.body.domains : [];
|
|
53
|
+
}
|
|
54
|
+
exports.listDomains = listDomains;
|
|
43
55
|
async function listBackends(projectId, location) {
|
|
44
56
|
var _a;
|
|
45
57
|
const name = `projects/${projectId}/locations/${location}/backends`;
|
package/lib/gcp/run.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.replaceService = exports.serviceIsResolved = exports.updateService = exports.getService = exports.gcpIds = exports.LOCATION_LABEL = void 0;
|
|
3
|
+
exports.fetchServiceLogs = exports.setInvokerUpdate = exports.setInvokerCreate = exports.getIamPolicy = exports.setIamPolicy = exports.replaceService = exports.serviceIsResolved = exports.updateService = exports.getService = exports.gcpIds = exports.LOCATION_LABEL = void 0;
|
|
4
4
|
const apiv2_1 = require("../apiv2");
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const api_1 = require("../api");
|
|
7
7
|
const proto = require("./proto");
|
|
8
8
|
const throttler_1 = require("../throttler/throttler");
|
|
9
9
|
const logger_1 = require("../logger");
|
|
10
|
+
const cloudlogging_1 = require("./cloudlogging");
|
|
10
11
|
const API_VERSION = "v1";
|
|
11
12
|
const client = new apiv2_1.Client({
|
|
12
13
|
urlPrefix: (0, api_1.runOrigin)(),
|
|
@@ -155,3 +156,20 @@ async function setInvokerUpdate(projectId, serviceName, invoker, httpClient = cl
|
|
|
155
156
|
await setIamPolicy(serviceName, policy, httpClient);
|
|
156
157
|
}
|
|
157
158
|
exports.setInvokerUpdate = setInvokerUpdate;
|
|
159
|
+
async function fetchServiceLogs(projectId, serviceId) {
|
|
160
|
+
var _a, _b;
|
|
161
|
+
const filter = `resource.type="cloud_run_revision" AND resource.labels.service_name="${serviceId}"`;
|
|
162
|
+
const pageSize = 100;
|
|
163
|
+
const order = "desc";
|
|
164
|
+
try {
|
|
165
|
+
const entries = await (0, cloudlogging_1.listEntries)(projectId, filter, pageSize, order);
|
|
166
|
+
return entries || [];
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
throw new error_1.FirebaseError(`Failed to fetch logs for Cloud Run service ${serviceId}`, {
|
|
170
|
+
original: err,
|
|
171
|
+
status: (_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
exports.fetchServiceLogs = fetchServiceLogs;
|
|
@@ -22,12 +22,14 @@ async function doSetup(setup, config) {
|
|
|
22
22
|
}
|
|
23
23
|
await (0, apphosting_1.ensureApiEnabled)({ projectId });
|
|
24
24
|
await (0, backend_1.ensureRequiredApisEnabled)(projectId);
|
|
25
|
+
const spinner = ora("Checking your App Hosting compute service account...").start();
|
|
25
26
|
try {
|
|
26
27
|
await (0, backend_1.ensureAppHostingComputeServiceAccount)(projectId, "", true);
|
|
28
|
+
spinner.succeed("App Hosting compute Service account is ready");
|
|
27
29
|
}
|
|
28
30
|
catch (err) {
|
|
29
31
|
if (err.status === 400) {
|
|
30
|
-
|
|
32
|
+
spinner.warn("Your App Hosting compute service account is still being provisioned. Please try again in a few moments.");
|
|
31
33
|
}
|
|
32
34
|
throw err;
|
|
33
35
|
}
|
|
@@ -68,7 +70,6 @@ async function doSetup(setup, config) {
|
|
|
68
70
|
message: "Specify your app's root directory relative to your firebase.json directory",
|
|
69
71
|
});
|
|
70
72
|
upsertAppHostingConfig(backendConfig, config);
|
|
71
|
-
utils.logBullet("Writing configuration info to firebase.json...");
|
|
72
73
|
config.writeProjectFile("firebase.json", config.src);
|
|
73
74
|
utils.logBullet("Writing default settings to " + clc.bold("apphosting.yaml") + "...");
|
|
74
75
|
const absRootDir = path.join(process.cwd(), backendConfig.rootDir);
|
|
@@ -5,7 +5,6 @@ const clc = require("colorette");
|
|
|
5
5
|
const prompt_1 = require("../../prompt");
|
|
6
6
|
const logger_1 = require("../../logger");
|
|
7
7
|
const utils = require("../../utils");
|
|
8
|
-
const fsutils = require("../../fsutils");
|
|
9
8
|
const database_1 = require("../../management/database");
|
|
10
9
|
const ora = require("ora");
|
|
11
10
|
const ensureApiEnabled_1 = require("../../ensureApiEnabled");
|
|
@@ -31,9 +30,8 @@ async function getDBRules(instanceDetails) {
|
|
|
31
30
|
}
|
|
32
31
|
return await response.response.text();
|
|
33
32
|
}
|
|
34
|
-
function writeDBRules(rules,
|
|
33
|
+
function writeDBRules(rules, filename, config) {
|
|
35
34
|
config.writeProjectFile(filename, rules);
|
|
36
|
-
utils.logSuccess(`${logMessagePrefix} have been written to ${clc.bold(filename)}.`);
|
|
37
35
|
logger_1.logger.info(`Future modifications to ${clc.bold(filename)} will update Realtime Database Security Rules when you run`);
|
|
38
36
|
logger_1.logger.info(clc.bold("firebase deploy") + ".");
|
|
39
37
|
}
|
|
@@ -83,11 +81,7 @@ async function initializeDatabaseInstance(projectId) {
|
|
|
83
81
|
}
|
|
84
82
|
return null;
|
|
85
83
|
}
|
|
86
|
-
async function askQuestions(setup) {
|
|
87
|
-
let instanceDetails = null;
|
|
88
|
-
if (setup.projectId) {
|
|
89
|
-
instanceDetails = await initializeDatabaseInstance(setup.projectId);
|
|
90
|
-
}
|
|
84
|
+
async function askQuestions(setup, config) {
|
|
91
85
|
logger_1.logger.info();
|
|
92
86
|
logger_1.logger.info("Firebase Realtime Database Security Rules allow you to define how your data should be");
|
|
93
87
|
logger_1.logger.info("structured and when your data can be read from and written to.");
|
|
@@ -104,16 +98,14 @@ async function askQuestions(setup) {
|
|
|
104
98
|
rules: exports.DEFAULT_RULES,
|
|
105
99
|
writeRules: true,
|
|
106
100
|
};
|
|
107
|
-
if (
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
if (info.writeRules && instanceDetails) {
|
|
115
|
-
info.rules = await getDBRules(instanceDetails);
|
|
101
|
+
if (setup.projectId) {
|
|
102
|
+
const instanceDetails = await initializeDatabaseInstance(setup.projectId);
|
|
103
|
+
if (instanceDetails) {
|
|
104
|
+
info.rules = await getDBRules(instanceDetails);
|
|
105
|
+
utils.logBullet(`Downloaded the existing Realtime Database Security Rules of database ${clc.bold(instanceDetails.name)} from the Firebase console`);
|
|
106
|
+
}
|
|
116
107
|
}
|
|
108
|
+
info.writeRules = await config.confirmWriteProjectFile(rulesFilename, info.rules);
|
|
117
109
|
setup.featureInfo = setup.featureInfo || {};
|
|
118
110
|
setup.featureInfo.database = info;
|
|
119
111
|
}
|
|
@@ -129,10 +121,10 @@ async function actuate(setup, config) {
|
|
|
129
121
|
setup.config.database = { rules: info.rulesFilename };
|
|
130
122
|
if (info.writeRules) {
|
|
131
123
|
if (info.rules === exports.DEFAULT_RULES) {
|
|
132
|
-
writeDBRules(info.rules,
|
|
124
|
+
writeDBRules(info.rules, info.rulesFilename, config);
|
|
133
125
|
}
|
|
134
126
|
else {
|
|
135
|
-
writeDBRules(info.rules,
|
|
127
|
+
writeDBRules(info.rules, info.rulesFilename, config);
|
|
136
128
|
}
|
|
137
129
|
}
|
|
138
130
|
else {
|
|
@@ -23,7 +23,7 @@ const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconn
|
|
|
23
23
|
const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
|
|
24
24
|
const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
|
|
25
25
|
const MUTATIONS_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/mutations.gql");
|
|
26
|
-
const serviceEnvVar = () => (0, utils_1.envOverride)("FDC_SERVICE", "");
|
|
26
|
+
const serviceEnvVar = () => (0, utils_1.envOverride)("FDC_CONNECTOR", "") || (0, utils_1.envOverride)("FDC_SERVICE", "");
|
|
27
27
|
const emptyConnector = {
|
|
28
28
|
id: "default",
|
|
29
29
|
path: "./connector",
|
|
@@ -68,7 +68,7 @@ async function askQuestions(setup) {
|
|
|
68
68
|
const shouldConfigureBackend = hasBilling &&
|
|
69
69
|
requiredConfigUnset &&
|
|
70
70
|
(await (0, prompt_1.confirm)({
|
|
71
|
-
message: `Would you like to configure your
|
|
71
|
+
message: `Would you like to configure your Cloud SQL datasource now?`,
|
|
72
72
|
default: true,
|
|
73
73
|
}));
|
|
74
74
|
if (shouldConfigureBackend) {
|
|
@@ -190,30 +190,7 @@ async function promptForExistingServices(setup, info) {
|
|
|
190
190
|
return { service: s, schema: await (0, client_1.getSchema)(s.name) };
|
|
191
191
|
}));
|
|
192
192
|
if (existingServicesAndSchemas.length) {
|
|
193
|
-
|
|
194
|
-
const [serviceLocationFromEnvVar, serviceIdFromEnvVar] = serviceEnvVar().split("/");
|
|
195
|
-
const serviceFromEnvVar = existingServicesAndSchemas.find((s) => {
|
|
196
|
-
const serviceName = (0, names_1.parseServiceName)(s.service.name);
|
|
197
|
-
return (serviceName.serviceId === serviceIdFromEnvVar &&
|
|
198
|
-
serviceName.location === serviceLocationFromEnvVar);
|
|
199
|
-
});
|
|
200
|
-
if (serviceFromEnvVar) {
|
|
201
|
-
choice = serviceFromEnvVar;
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
const choices = existingServicesAndSchemas.map((s) => {
|
|
205
|
-
const serviceName = (0, names_1.parseServiceName)(s.service.name);
|
|
206
|
-
return {
|
|
207
|
-
name: `${serviceName.location}/${serviceName.serviceId}`,
|
|
208
|
-
value: s,
|
|
209
|
-
};
|
|
210
|
-
});
|
|
211
|
-
choices.push({ name: "Create a new service", value: undefined });
|
|
212
|
-
choice = await (0, prompt_1.select)({
|
|
213
|
-
message: "Your project already has existing services. Which would you like to set up local files for?",
|
|
214
|
-
choices,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
193
|
+
const choice = await chooseExistingService(existingServicesAndSchemas);
|
|
217
194
|
if (choice) {
|
|
218
195
|
const serviceName = (0, names_1.parseServiceName)(choice.service.name);
|
|
219
196
|
info.serviceId = serviceName.serviceId;
|
|
@@ -249,6 +226,30 @@ async function promptForExistingServices(setup, info) {
|
|
|
249
226
|
}
|
|
250
227
|
return info;
|
|
251
228
|
}
|
|
229
|
+
async function chooseExistingService(existing) {
|
|
230
|
+
const [serviceLocationFromEnvVar, serviceIdFromEnvVar] = serviceEnvVar().split("/");
|
|
231
|
+
const serviceFromEnvVar = existing.find((s) => {
|
|
232
|
+
const serviceName = (0, names_1.parseServiceName)(s.service.name);
|
|
233
|
+
return (serviceName.serviceId === serviceIdFromEnvVar &&
|
|
234
|
+
serviceName.location === serviceLocationFromEnvVar);
|
|
235
|
+
});
|
|
236
|
+
if (serviceFromEnvVar) {
|
|
237
|
+
(0, utils_1.logBullet)(`Picking up the existing service ${clc.bold(serviceLocationFromEnvVar + "/" + serviceIdFromEnvVar)}.`);
|
|
238
|
+
return serviceFromEnvVar;
|
|
239
|
+
}
|
|
240
|
+
const choices = existing.map((s) => {
|
|
241
|
+
const serviceName = (0, names_1.parseServiceName)(s.service.name);
|
|
242
|
+
return {
|
|
243
|
+
name: `${serviceName.location}/${serviceName.serviceId}`,
|
|
244
|
+
value: s,
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
choices.push({ name: "Create a new service", value: undefined });
|
|
248
|
+
return await (0, prompt_1.select)({
|
|
249
|
+
message: "Your project already has existing services. Which would you like to set up local files for?",
|
|
250
|
+
choices,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
252
253
|
async function promptForCloudSQL(setup, info) {
|
|
253
254
|
if (info.cloudSqlInstanceId === "" && setup.projectId) {
|
|
254
255
|
const instances = await cloudsql.listInstances(setup.projectId);
|
|
@@ -14,6 +14,7 @@ const error_1 = require("../../../error");
|
|
|
14
14
|
const lodash_1 = require("lodash");
|
|
15
15
|
const utils_1 = require("../../../utils");
|
|
16
16
|
const auth_1 = require("../../../auth");
|
|
17
|
+
const connectorEnvVar = () => (0, utils_1.envOverride)("FDC_CONNECTOR", "");
|
|
17
18
|
exports.FDC_APP_FOLDER = "_FDC_APP_FOLDER";
|
|
18
19
|
async function doSetup(setup, config) {
|
|
19
20
|
const sdkInfo = await askQuestions(setup, config);
|
|
@@ -29,7 +30,7 @@ async function askQuestions(setup, config) {
|
|
|
29
30
|
.map((si) => {
|
|
30
31
|
return si.connectorInfo.map((ci) => {
|
|
31
32
|
return {
|
|
32
|
-
name: `${si.dataConnectYaml.serviceId}/${ci.connectorYaml.connectorId}`,
|
|
33
|
+
name: `${si.dataConnectYaml.location}/${si.dataConnectYaml.serviceId}/${ci.connectorYaml.connectorId}`,
|
|
33
34
|
value: ci,
|
|
34
35
|
};
|
|
35
36
|
});
|
|
@@ -68,10 +69,7 @@ async function askQuestions(setup, config) {
|
|
|
68
69
|
else {
|
|
69
70
|
(0, utils_1.logSuccess)(`Detected ${targetPlatform} app in directory ${appDir}`);
|
|
70
71
|
}
|
|
71
|
-
const connectorInfo = await (
|
|
72
|
-
message: "Which connector do you want set up a generated SDK for?",
|
|
73
|
-
choices: connectorChoices,
|
|
74
|
-
});
|
|
72
|
+
const connectorInfo = await chooseExistingConnector(connectorChoices);
|
|
75
73
|
const connectorYaml = JSON.parse(JSON.stringify(connectorInfo.connectorYaml));
|
|
76
74
|
const newConnectorYaml = await generateSdkYaml(targetPlatform, connectorYaml, connectorInfo.directory, appDir);
|
|
77
75
|
if (targetPlatform === types_1.Platform.WEB) {
|
|
@@ -98,6 +96,21 @@ async function askQuestions(setup, config) {
|
|
|
98
96
|
const displayIOSWarning = targetPlatform === types_1.Platform.IOS;
|
|
99
97
|
return { connectorYamlContents, connectorInfo, displayIOSWarning };
|
|
100
98
|
}
|
|
99
|
+
async function chooseExistingConnector(choices) {
|
|
100
|
+
if (choices.length === 1) {
|
|
101
|
+
return choices[0].value;
|
|
102
|
+
}
|
|
103
|
+
const nameFromEnvVar = connectorEnvVar();
|
|
104
|
+
const existingConnector = choices.find((c) => c.name === nameFromEnvVar);
|
|
105
|
+
if (existingConnector) {
|
|
106
|
+
(0, utils_1.logBullet)(`Picking up the existing connector ${clc.bold(nameFromEnvVar)}.`);
|
|
107
|
+
return existingConnector.value;
|
|
108
|
+
}
|
|
109
|
+
return await (0, prompt_1.select)({
|
|
110
|
+
message: "Which connector do you want set up a generated SDK for?",
|
|
111
|
+
choices: choices,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
101
114
|
async function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appDir) {
|
|
102
115
|
if (!connectorYaml.generate) {
|
|
103
116
|
connectorYaml.generate = {};
|
|
@@ -155,7 +168,7 @@ async function actuate(sdkInfo, config) {
|
|
|
155
168
|
var _a, _b;
|
|
156
169
|
const connectorYamlPath = `${sdkInfo.connectorInfo.directory}/connector.yaml`;
|
|
157
170
|
(0, utils_1.logBullet)(`Writing your new SDK configuration to ${connectorYamlPath}`);
|
|
158
|
-
await config.askWriteProjectFile(path.relative(config.projectDir, connectorYamlPath), sdkInfo.connectorYamlContents);
|
|
171
|
+
await config.askWriteProjectFile(path.relative(config.projectDir, connectorYamlPath), sdkInfo.connectorYamlContents, false, true);
|
|
159
172
|
const account = (0, auth_1.getGlobalDefaultAccount)();
|
|
160
173
|
await dataconnectEmulator_1.DataConnectEmulator.generate({
|
|
161
174
|
configDir: sdkInfo.connectorInfo.directory,
|
|
@@ -65,9 +65,10 @@ async function doSetup(setup, config) {
|
|
|
65
65
|
default: true,
|
|
66
66
|
});
|
|
67
67
|
if (ui.enabled) {
|
|
68
|
-
ui.port = await (0, prompt_1.number)(
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
ui.port = await (0, prompt_1.number)({
|
|
69
|
+
message: `Which port do you want to use for the ${clc.underline(uiDesc)} (leave empty to use any available port)?`,
|
|
70
|
+
required: false,
|
|
71
|
+
});
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
selections.download = await (0, prompt_1.confirm)({
|
|
@@ -1,45 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.actuate = exports.askQuestions =
|
|
4
|
-
const logger_1 = require("../../../logger");
|
|
5
|
-
const apiEnabled = require("../../../ensureApiEnabled");
|
|
6
|
-
const requirePermissions_1 = require("../../../requirePermissions");
|
|
7
|
-
const checkDatabaseType_1 = require("../../../firestore/checkDatabaseType");
|
|
3
|
+
exports.actuate = exports.askQuestions = void 0;
|
|
8
4
|
const rules = require("./rules");
|
|
9
5
|
const indexes = require("./indexes");
|
|
10
6
|
const error_1 = require("../../../error");
|
|
11
|
-
const
|
|
7
|
+
const api_1 = require("../../../firestore/api");
|
|
12
8
|
const prompt_1 = require("../../../prompt");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!isFirestoreEnabled) {
|
|
17
|
-
throw firestoreUnusedError;
|
|
18
|
-
}
|
|
19
|
-
info.databaseId = info.databaseId || "(default)";
|
|
20
|
-
let dbType = await (0, checkDatabaseType_1.checkDatabaseType)(setup.projectId, info.databaseId);
|
|
21
|
-
if (dbType === "DATABASE_DOES_NOT_EXIST") {
|
|
22
|
-
info.databaseId = await selectDatabaseByPrompting();
|
|
23
|
-
dbType = await (0, checkDatabaseType_1.checkDatabaseType)(setup.projectId, info.databaseId);
|
|
24
|
-
}
|
|
25
|
-
if (dbType !== "FIRESTORE_NATIVE") {
|
|
26
|
-
logger_1.logger.debug(`firestore database_type: ${dbType}`);
|
|
27
|
-
throw new error_1.FirebaseError(`It looks like this project is using Cloud Datastore or Cloud Firestore in Datastore mode. The Firebase CLI can only manage projects using Cloud Firestore in Native mode. For more information, visit https://cloud.google.com/datastore/docs/firestore-or-datastore`, { exit: 1 });
|
|
28
|
-
}
|
|
29
|
-
await (0, requirePermissions_1.requirePermissions)(Object.assign(Object.assign({}, options), { project: setup.projectId }));
|
|
30
|
-
}
|
|
31
|
-
function selectDatabaseByPrompting() {
|
|
32
|
-
return (0, prompt_1.input)("Please input the name of the Native Firestore database you would like to use:");
|
|
33
|
-
}
|
|
34
|
-
async function doSetup(setup, config, options) {
|
|
35
|
-
await askQuestions(setup, config, options);
|
|
36
|
-
await actuate(setup, config);
|
|
37
|
-
}
|
|
38
|
-
exports.doSetup = doSetup;
|
|
39
|
-
async function askQuestions(setup, config, options) {
|
|
9
|
+
const ensureApiEnabled_1 = require("../../../ensureApiEnabled");
|
|
10
|
+
const api_2 = require("../../../api");
|
|
11
|
+
async function askQuestions(setup, config) {
|
|
40
12
|
const firestore = !Array.isArray(setup.config.firestore) ? setup.config.firestore : undefined;
|
|
41
13
|
const info = {
|
|
42
14
|
databaseId: (firestore === null || firestore === void 0 ? void 0 : firestore.database) || "",
|
|
15
|
+
locationId: (firestore === null || firestore === void 0 ? void 0 : firestore.location) || "",
|
|
43
16
|
rulesFilename: (firestore === null || firestore === void 0 ? void 0 : firestore.rules) || "",
|
|
44
17
|
rules: "",
|
|
45
18
|
writeRules: true,
|
|
@@ -48,7 +21,42 @@ async function askQuestions(setup, config, options) {
|
|
|
48
21
|
writeIndexes: true,
|
|
49
22
|
};
|
|
50
23
|
if (setup.projectId) {
|
|
51
|
-
await
|
|
24
|
+
await (0, ensureApiEnabled_1.ensure)(setup.projectId, (0, api_2.firestoreOrigin)(), "firestore");
|
|
25
|
+
info.databaseId = info.databaseId || "(default)";
|
|
26
|
+
const api = new api_1.FirestoreApi();
|
|
27
|
+
const databases = await api.listDatabases(setup.projectId);
|
|
28
|
+
const nativeDatabaseNames = databases
|
|
29
|
+
.filter((db) => db.type === "FIRESTORE_NATIVE")
|
|
30
|
+
.map((db) => db.name.split("/")[3]);
|
|
31
|
+
if (nativeDatabaseNames.length === 0) {
|
|
32
|
+
if (databases.length > 0) {
|
|
33
|
+
throw new error_1.FirebaseError(`It looks like this project is using Cloud Firestore in ${databases[0].type}. The Firebase CLI can only manage projects using Cloud Firestore in Native mode. For more information, visit https://cloud.google.com/datastore/docs/firestore-or-datastore`, { exit: 1 });
|
|
34
|
+
}
|
|
35
|
+
info.databaseId = "(default)";
|
|
36
|
+
const locations = await api.locations(setup.projectId);
|
|
37
|
+
const choice = await (0, prompt_1.select)({
|
|
38
|
+
message: "Please select the location of your Firestore database:",
|
|
39
|
+
choices: locations.map((location) => location.name.split("/")[3]),
|
|
40
|
+
default: "nam5",
|
|
41
|
+
});
|
|
42
|
+
info.locationId = choice;
|
|
43
|
+
}
|
|
44
|
+
else if (nativeDatabaseNames.length === 1) {
|
|
45
|
+
info.databaseId = nativeDatabaseNames[0];
|
|
46
|
+
info.locationId = databases
|
|
47
|
+
.filter((db) => db.name.endsWith(`databases/${info.databaseId}`))
|
|
48
|
+
.map((db) => db.locationId)[0];
|
|
49
|
+
}
|
|
50
|
+
else if (nativeDatabaseNames.length > 1) {
|
|
51
|
+
const choice = await (0, prompt_1.select)({
|
|
52
|
+
message: "Please select the name of the Native Firestore database you would like to use:",
|
|
53
|
+
choices: nativeDatabaseNames,
|
|
54
|
+
});
|
|
55
|
+
info.databaseId = choice;
|
|
56
|
+
info.locationId = databases
|
|
57
|
+
.filter((db) => db.name.endsWith(`databases/${info.databaseId}`))
|
|
58
|
+
.map((db) => db.locationId)[0];
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
61
|
await rules.initRules(setup, config, info);
|
|
54
62
|
await indexes.initIndexes(setup, config, info);
|
|
@@ -63,12 +71,14 @@ async function actuate(setup, config) {
|
|
|
63
71
|
throw new error_1.FirebaseError("Firestore featureInfo is not found");
|
|
64
72
|
}
|
|
65
73
|
info.databaseId = info.databaseId || "(default)";
|
|
74
|
+
info.locationId = info.locationId || "nam5";
|
|
66
75
|
info.rules = info.rules || rules.getDefaultRules();
|
|
67
76
|
info.rulesFilename = info.rulesFilename || rules.DEFAULT_RULES_FILE;
|
|
68
77
|
info.indexes = info.indexes || indexes.INDEXES_TEMPLATE;
|
|
69
78
|
info.indexesFilename = info.indexesFilename || indexes.DEFAULT_INDEXES_FILE;
|
|
70
79
|
setup.config.firestore = {
|
|
71
80
|
database: info.databaseId,
|
|
81
|
+
location: info.locationId,
|
|
72
82
|
rules: info.rulesFilename,
|
|
73
83
|
indexes: info.indexesFilename,
|
|
74
84
|
};
|
|
@@ -4,8 +4,8 @@ exports.initIndexes = exports.INDEXES_TEMPLATE = exports.DEFAULT_INDEXES_FILE =
|
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const error_1 = require("../../../error");
|
|
6
6
|
const api = require("../../../firestore/api");
|
|
7
|
-
const fsutils = require("../../../fsutils");
|
|
8
7
|
const prompt_1 = require("../../../prompt");
|
|
8
|
+
const utils = require("../../../utils");
|
|
9
9
|
const logger_1 = require("../../../logger");
|
|
10
10
|
const templates_1 = require("../../../templates");
|
|
11
11
|
const indexes = new api.FirestoreApi();
|
|
@@ -24,19 +24,15 @@ async function initIndexes(setup, config, info) {
|
|
|
24
24
|
message: "What file should be used for Firestore indexes?",
|
|
25
25
|
default: exports.DEFAULT_INDEXES_FILE,
|
|
26
26
|
}));
|
|
27
|
-
|
|
28
|
-
const msg = "File " +
|
|
29
|
-
clc.bold(info.indexesFilename) +
|
|
30
|
-
" already exists." +
|
|
31
|
-
" Do you want to overwrite it with the Firestore Indexes from the Firebase Console?";
|
|
32
|
-
if (!(await (0, prompt_1.confirm)(msg))) {
|
|
33
|
-
info.writeIndexes = false;
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
27
|
+
info.indexes = exports.INDEXES_TEMPLATE;
|
|
37
28
|
if (setup.projectId) {
|
|
38
|
-
|
|
29
|
+
const downloadIndexes = await getIndexesFromConsole(setup.projectId, info.databaseId);
|
|
30
|
+
if (downloadIndexes) {
|
|
31
|
+
info.indexes = downloadIndexes;
|
|
32
|
+
utils.logBullet(`Downloaded the existing Firestore indexes from the Firebase console`);
|
|
33
|
+
}
|
|
39
34
|
}
|
|
35
|
+
info.writeRules = await config.confirmWriteProjectFile(info.indexesFilename, info.indexes);
|
|
40
36
|
}
|
|
41
37
|
exports.initIndexes = initIndexes;
|
|
42
38
|
async function getIndexesFromConsole(projectId, databaseId) {
|
|
@@ -47,8 +43,11 @@ async function getIndexesFromConsole(projectId, databaseId) {
|
|
|
47
43
|
return JSON.stringify(indexes.makeIndexSpec(res[0], res[1]), null, 2);
|
|
48
44
|
}
|
|
49
45
|
catch (e) {
|
|
46
|
+
if (e.status === 404) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
50
49
|
if (e.message.indexOf("is not a Cloud Firestore enabled project") >= 0) {
|
|
51
|
-
return
|
|
50
|
+
return null;
|
|
52
51
|
}
|
|
53
52
|
throw new error_1.FirebaseError("Error fetching Firestore indexes", {
|
|
54
53
|
original: e,
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.initRules = exports.getDefaultRules = exports.DEFAULT_RULES_FILE = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const gcp = require("../../../gcp");
|
|
6
|
-
const fsutils = require("../../../fsutils");
|
|
7
6
|
const prompt_1 = require("../../../prompt");
|
|
8
7
|
const logger_1 = require("../../../logger");
|
|
9
8
|
const utils = require("../../../utils");
|
|
@@ -28,28 +27,22 @@ async function initRules(setup, config, info) {
|
|
|
28
27
|
message: "What file should be used for Firestore Rules?",
|
|
29
28
|
default: exports.DEFAULT_RULES_FILE,
|
|
30
29
|
}));
|
|
31
|
-
|
|
32
|
-
const msg = "File " +
|
|
33
|
-
clc.bold(info.rulesFilename) +
|
|
34
|
-
" already exists." +
|
|
35
|
-
" Do you want to overwrite it with the Firestore Rules from the Firebase Console?";
|
|
36
|
-
if (!(await (0, prompt_1.confirm)(msg))) {
|
|
37
|
-
info.writeRules = false;
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
30
|
+
info.rules = getDefaultRules();
|
|
41
31
|
if (setup.projectId) {
|
|
42
|
-
|
|
32
|
+
const downloadedRules = await getRulesFromConsole(setup.projectId);
|
|
33
|
+
if (downloadedRules) {
|
|
34
|
+
info.rules = downloadedRules;
|
|
35
|
+
utils.logBullet(`Downloaded the existing Firestore Security Rules from the Firebase console`);
|
|
36
|
+
}
|
|
43
37
|
}
|
|
38
|
+
info.writeRules = await config.confirmWriteProjectFile(info.rulesFilename, info.rules);
|
|
44
39
|
}
|
|
45
40
|
exports.initRules = initRules;
|
|
46
41
|
async function getRulesFromConsole(projectId) {
|
|
47
42
|
const name = await gcp.rules.getLatestRulesetName(projectId, "cloud.firestore");
|
|
48
43
|
if (!name) {
|
|
49
|
-
|
|
50
|
-
return getDefaultRules();
|
|
44
|
+
return null;
|
|
51
45
|
}
|
|
52
|
-
logger_1.logger.debug("Found ruleset: " + name);
|
|
53
46
|
const rules = await gcp.rules.getRulesetContent(name);
|
|
54
47
|
if (rules.length <= 0) {
|
|
55
48
|
return utils.reject("Ruleset has no files", { exit: 1 });
|