firebase-tools 14.3.1 → 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/api.js +10 -2
- package/lib/apphosting/backend.js +74 -18
- package/lib/apphosting/rollout.js +2 -2
- package/lib/apphosting/secrets/dialogs.js +4 -4
- package/lib/apphosting/secrets/index.js +2 -2
- package/lib/auth.js +22 -2
- package/lib/bin/cli.js +1 -33
- package/lib/bin/mcp.js +13 -2
- package/lib/checkValidTargetFilters.js +1 -0
- package/lib/command.js +3 -2
- package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
- package/lib/commands/deploy.js +1 -0
- package/lib/commands/init.js +1 -4
- package/lib/commands/login-use.js +2 -11
- package/lib/commands/use.js +9 -8
- package/lib/config.js +43 -24
- package/lib/crashlytics/listTopIssues.js +44 -0
- package/lib/dataconnect/cloudAICompanionClient.js +72 -0
- 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/args.js +2 -0
- package/lib/deploy/apphosting/deploy.js +77 -0
- package/lib/deploy/apphosting/index.js +9 -0
- package/lib/deploy/apphosting/prepare.js +147 -0
- package/lib/deploy/apphosting/release.js +56 -0
- package/lib/deploy/apphosting/util.js +65 -0
- package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
- package/lib/deploy/firestore/deploy.js +47 -4
- package/lib/deploy/functions/checkIam.js +3 -3
- package/lib/deploy/functions/ensure.js +2 -1
- package/lib/deploy/functions/prepare.js +23 -16
- package/lib/deploy/functions/release/fabricator.js +4 -4
- package/lib/deploy/functions/release/index.js +1 -1
- package/lib/deploy/functions/runtimes/python/index.js +3 -0
- package/lib/deploy/functions/runtimes/supported/types.js +17 -11
- package/lib/deploy/index.js +2 -0
- package/lib/emulator/apphosting/index.js +1 -0
- package/lib/emulator/apphosting/serve.js +77 -3
- package/lib/emulator/auth/widget_ui.js +2 -1
- package/lib/emulator/controller.js +18 -4
- package/lib/emulator/dataconnectEmulator.js +9 -2
- package/lib/emulator/downloadableEmulatorInfo.json +81 -0
- package/lib/emulator/downloadableEmulators.js +28 -108
- package/lib/experiments.js +1 -1
- package/lib/extensions/manifest.js +2 -5
- package/lib/frameworks/angular/index.js +1 -1
- package/lib/frameworks/angular/utils.js +17 -6
- package/lib/fsAsync.js +9 -2
- package/lib/gcp/apphosting.js +13 -1
- package/lib/gcp/auth.js +32 -2
- package/lib/gcp/cloudbilling.js +12 -1
- package/lib/gcp/cloudfunctions.js +1 -2
- package/lib/gcp/cloudfunctionsv2.js +1 -2
- package/lib/gcp/cloudscheduler.js +2 -2
- package/lib/gcp/computeEngine.js +19 -2
- package/lib/gcp/devConnect.js +6 -1
- package/lib/gcp/firestore.js +24 -1
- package/lib/gcp/iam.js +1 -5
- package/lib/gcp/run.js +19 -1
- package/lib/gcp/storage.js +25 -1
- package/lib/index.js +1 -2
- package/lib/init/features/apphosting.js +85 -6
- package/lib/init/features/database.js +63 -52
- package/lib/init/features/dataconnect/index.js +78 -79
- package/lib/init/features/dataconnect/sdk.js +19 -6
- package/lib/init/features/emulators.js +12 -7
- package/lib/init/features/firestore/index.js +80 -39
- package/lib/init/features/firestore/indexes.js +29 -31
- package/lib/init/features/firestore/rules.js +35 -48
- package/lib/init/features/functions/index.js +2 -0
- package/lib/init/features/functions/javascript.js +3 -2
- package/lib/init/features/functions/typescript.js +3 -2
- package/lib/init/features/genkit/index.js +18 -10
- package/lib/init/features/hosting/github.js +3 -2
- package/lib/init/features/hosting/index.js +9 -8
- package/lib/init/features/index.js +10 -5
- package/lib/init/features/remoteconfig.js +3 -2
- package/lib/init/features/storage.js +31 -8
- package/lib/init/index.js +80 -24
- package/lib/logger.js +71 -7
- package/lib/management/projects.js +24 -1
- package/lib/mcp/errors.js +1 -1
- package/lib/mcp/index.js +142 -46
- 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/auth/get_user.js +16 -7
- package/lib/mcp/tools/auth/index.js +8 -1
- package/lib/mcp/tools/auth/list_users.js +47 -0
- package/lib/mcp/tools/auth/set_claims.js +20 -11
- package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
- package/lib/mcp/tools/core/consult_assistant.js +1 -1
- package/lib/mcp/tools/core/create_android_sha.js +40 -0
- package/lib/mcp/tools/core/create_app.js +90 -0
- package/lib/mcp/tools/core/create_project.js +68 -0
- package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
- package/lib/mcp/tools/core/get_environment.js +51 -0
- package/lib/mcp/tools/core/get_sdk_config.js +6 -3
- package/lib/mcp/tools/core/index.js +21 -2
- package/lib/mcp/tools/core/init.js +153 -0
- package/lib/mcp/tools/core/list_apps.js +10 -5
- package/lib/mcp/tools/core/list_projects.js +45 -0
- package/lib/mcp/tools/core/update_environment.js +55 -0
- package/lib/mcp/tools/crashlytics/index.js +5 -0
- package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
- package/lib/mcp/tools/dataconnect/converter.js +13 -1
- package/lib/mcp/tools/dataconnect/emulator.js +32 -0
- package/lib/mcp/tools/dataconnect/execute_graphql.js +24 -8
- package/lib/mcp/tools/dataconnect/execute_graphql_read.js +24 -8
- package/lib/mcp/tools/dataconnect/execute_mutation.js +27 -15
- package/lib/mcp/tools/dataconnect/execute_query.js +26 -14
- package/lib/mcp/tools/dataconnect/generate_operation.js +7 -7
- package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
- package/lib/mcp/tools/dataconnect/get_connector.js +7 -7
- package/lib/mcp/tools/dataconnect/get_schema.js +5 -5
- package/lib/mcp/tools/dataconnect/index.js +1 -5
- package/lib/mcp/tools/dataconnect/list_services.js +1 -1
- package/lib/mcp/tools/firestore/converter.js +47 -1
- package/lib/mcp/tools/firestore/delete_document.js +37 -0
- package/lib/mcp/tools/firestore/index.js +12 -2
- package/lib/mcp/tools/firestore/list_collections.js +1 -6
- package/lib/mcp/tools/firestore/query_collection.js +122 -0
- package/lib/mcp/tools/index.js +37 -16
- package/lib/mcp/tools/messaging/index.js +5 -0
- package/lib/mcp/tools/messaging/send_message.js +42 -0
- package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
- package/lib/mcp/tools/remoteconfig/index.js +7 -0
- package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
- package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
- package/lib/mcp/tools/rules/get_rules.js +29 -0
- package/lib/mcp/tools/rules/validate_rules.js +98 -0
- package/lib/mcp/tools/storage/get_download_url.js +8 -5
- package/lib/mcp/tools/storage/index.js +7 -2
- package/lib/mcp/types.js +10 -1
- package/lib/mcp/util.js +165 -2
- package/lib/mcp/util.test.js +468 -0
- package/lib/messaging/interfaces.js +2 -0
- package/lib/messaging/sendMessage.js +48 -0
- package/lib/prompt.js +1 -1
- package/lib/remoteconfig/publish.js +39 -0
- package/lib/requireAuth.js +2 -2
- package/lib/track.js +1 -1
- package/lib/utils.js +2 -37
- package/package.json +2 -1
- package/schema/connector-yaml.json +12 -0
- package/schema/extension-yaml.json +17 -4
- package/schema/firebase-config.json +65 -10
- 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/javascript/package.lint.json +1 -1
- package/templates/init/functions/javascript/package.nolint.json +1 -1
- package/templates/init/functions/typescript/package.lint.json +2 -2
- package/templates/init/functions/typescript/package.nolint.json +3 -3
- package/lib/mcp/tools/directory/get_project_directory.js +0 -20
- package/lib/mcp/tools/directory/index.js +0 -6
- package/lib/mcp/tools/directory/set_project_directory.js +0 -33
- package/lib/mcp/tools/firestore/get_rules.js +0 -26
package/lib/index.js
CHANGED
|
@@ -3,7 +3,6 @@ const program = require("commander");
|
|
|
3
3
|
const clc = require("colorette");
|
|
4
4
|
const leven = require("leven");
|
|
5
5
|
const logger_1 = require("./logger");
|
|
6
|
-
const utils_1 = require("./utils");
|
|
7
6
|
const pkg = require("../package.json");
|
|
8
7
|
program.version(pkg.version);
|
|
9
8
|
program.option("-P, --project <alias_or_project_id>", "the Firebase project to use for this command");
|
|
@@ -54,7 +53,7 @@ const RENAMED_COMMANDS = {
|
|
|
54
53
|
"prefs:token": "login:ci",
|
|
55
54
|
};
|
|
56
55
|
program.action((_, args) => {
|
|
57
|
-
(0,
|
|
56
|
+
(0, logger_1.useConsoleLoggers)();
|
|
58
57
|
const cmd = args[0];
|
|
59
58
|
logger_1.logger.error(clc.bold(clc.red("Error:")), clc.bold(cmd), "is not a Firebase command");
|
|
60
59
|
if (RENAMED_COMMANDS[cmd]) {
|
|
@@ -1,15 +1,94 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.doSetup = void 0;
|
|
3
|
+
exports.upsertAppHostingConfig = exports.doSetup = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const ora = require("ora");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const app_1 = require("../../apphosting/app");
|
|
9
|
+
const backend_1 = require("../../apphosting/backend");
|
|
10
|
+
const error_1 = require("../../error");
|
|
11
|
+
const apphosting_1 = require("../../gcp/apphosting");
|
|
7
12
|
const cloudbilling_1 = require("../../gcp/cloudbilling");
|
|
13
|
+
const prompt_1 = require("../../prompt");
|
|
14
|
+
const templates_1 = require("../../templates");
|
|
15
|
+
const utils = require("../../utils");
|
|
16
|
+
const utils_1 = require("../../utils");
|
|
8
17
|
const APPHOSTING_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/apphosting/apphosting.yaml");
|
|
9
18
|
async function doSetup(setup, config) {
|
|
10
|
-
|
|
19
|
+
const projectId = setup.projectId;
|
|
20
|
+
if (!(await (0, cloudbilling_1.isBillingEnabled)(setup))) {
|
|
21
|
+
throw new error_1.FirebaseError(`Firebase App Hosting requires billing to be enabled on your project. To upgrade, visit the following URL: https://console.firebase.google.com/project/${projectId}/usage/details`);
|
|
22
|
+
}
|
|
23
|
+
await (0, apphosting_1.ensureApiEnabled)({ projectId });
|
|
24
|
+
await (0, backend_1.ensureRequiredApisEnabled)(projectId);
|
|
25
|
+
const spinner = ora("Checking your App Hosting compute service account...").start();
|
|
26
|
+
try {
|
|
27
|
+
await (0, backend_1.ensureAppHostingComputeServiceAccount)(projectId, "", true);
|
|
28
|
+
spinner.succeed("App Hosting compute Service account is ready");
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
if (err.status === 400) {
|
|
32
|
+
spinner.warn("Your App Hosting compute service account is still being provisioned. Please try again in a few moments.");
|
|
33
|
+
}
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
utils.logBullet("This command links your local project to Firebase App Hosting. You will be able to deploy your web app with `firebase deploy` after setup.");
|
|
37
|
+
const backendConfig = {
|
|
38
|
+
backendId: "",
|
|
39
|
+
rootDir: "",
|
|
40
|
+
ignore: ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log", "functions"],
|
|
41
|
+
};
|
|
42
|
+
const createOrLink = await (0, prompt_1.select)({
|
|
43
|
+
default: "Create a new backend",
|
|
44
|
+
message: "Please select an option",
|
|
45
|
+
choices: [
|
|
46
|
+
{ name: "Create a new backend", value: "create" },
|
|
47
|
+
{ name: "Link to an existing backend", value: "link" },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
if (createOrLink === "link") {
|
|
51
|
+
backendConfig.backendId = await (0, backend_1.promptExistingBackend)(projectId, "Which backend would you like to link?");
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
(0, utils_1.logBullet)(`${clc.yellow("===")} Set up your backend`);
|
|
55
|
+
const location = await (0, backend_1.promptLocation)(projectId, "Select a primary region to host your backend:\n");
|
|
56
|
+
const backendId = await (0, backend_1.promptNewBackendId)(projectId, location);
|
|
57
|
+
utils.logSuccess(`Name set to ${backendId}\n`);
|
|
58
|
+
backendConfig.backendId = backendId;
|
|
59
|
+
const webApp = await app_1.webApps.getOrCreateWebApp(projectId, null, backendId);
|
|
60
|
+
if (!webApp) {
|
|
61
|
+
utils.logWarning(`Firebase web app not set`);
|
|
62
|
+
}
|
|
63
|
+
const createBackendSpinner = ora("Creating your new backend...").start();
|
|
64
|
+
const backend = await (0, backend_1.createBackend)(projectId, location, backendId, null, undefined, webApp === null || webApp === void 0 ? void 0 : webApp.id);
|
|
65
|
+
createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
|
|
66
|
+
}
|
|
67
|
+
(0, utils_1.logBullet)(`${clc.yellow("===")} Deploy local source setup`);
|
|
68
|
+
backendConfig.rootDir = await (0, prompt_1.input)({
|
|
69
|
+
default: "/",
|
|
70
|
+
message: "Specify your app's root directory relative to your firebase.json directory",
|
|
71
|
+
});
|
|
72
|
+
upsertAppHostingConfig(backendConfig, config);
|
|
73
|
+
config.writeProjectFile("firebase.json", config.src);
|
|
11
74
|
utils.logBullet("Writing default settings to " + clc.bold("apphosting.yaml") + "...");
|
|
12
|
-
|
|
13
|
-
|
|
75
|
+
const absRootDir = path.join(process.cwd(), backendConfig.rootDir);
|
|
76
|
+
if (!(0, fs_1.existsSync)(absRootDir)) {
|
|
77
|
+
throw new error_1.FirebaseError(`Failed to write apphosting.yaml file because app root directory ${absRootDir} does not exist. Please try again with a valid directory.`);
|
|
78
|
+
}
|
|
79
|
+
await config.askWriteProjectFile(path.join(absRootDir, "apphosting.yaml"), APPHOSTING_YAML_TEMPLATE);
|
|
80
|
+
utils.logSuccess("Firebase initialization complete!");
|
|
14
81
|
}
|
|
15
82
|
exports.doSetup = doSetup;
|
|
83
|
+
function upsertAppHostingConfig(backendConfig, config) {
|
|
84
|
+
if (!config.src.apphosting) {
|
|
85
|
+
config.set("apphosting", backendConfig);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (Array.isArray(config.src.apphosting)) {
|
|
89
|
+
config.set("apphosting", [...config.src.apphosting, backendConfig]);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
config.set("apphosting", [config.src.apphosting, backendConfig]);
|
|
93
|
+
}
|
|
94
|
+
exports.upsertAppHostingConfig = upsertAppHostingConfig;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.actuate = exports.askQuestions = exports.DEFAULT_RULES = void 0;
|
|
4
4
|
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");
|
|
@@ -13,10 +12,11 @@ const getDefaultDatabaseInstance_1 = require("../../getDefaultDatabaseInstance")
|
|
|
13
12
|
const error_1 = require("../../error");
|
|
14
13
|
const apiv2_1 = require("../../apiv2");
|
|
15
14
|
const api_1 = require("../../api");
|
|
16
|
-
const
|
|
15
|
+
const DEFAULT_RULES_FILENAME = "database.rules.json";
|
|
16
|
+
exports.DEFAULT_RULES = JSON.stringify({ rules: { ".read": "auth != null", ".write": "auth != null" } }, null, 2);
|
|
17
17
|
async function getDBRules(instanceDetails) {
|
|
18
18
|
if (!instanceDetails || !instanceDetails.name) {
|
|
19
|
-
return DEFAULT_RULES;
|
|
19
|
+
return exports.DEFAULT_RULES;
|
|
20
20
|
}
|
|
21
21
|
const client = new apiv2_1.Client({ urlPrefix: instanceDetails.databaseUrl });
|
|
22
22
|
const response = await client.request({
|
|
@@ -30,9 +30,8 @@ async function getDBRules(instanceDetails) {
|
|
|
30
30
|
}
|
|
31
31
|
return await response.response.text();
|
|
32
32
|
}
|
|
33
|
-
function writeDBRules(rules,
|
|
33
|
+
function writeDBRules(rules, filename, config) {
|
|
34
34
|
config.writeProjectFile(filename, rules);
|
|
35
|
-
utils.logSuccess(`${logMessagePrefix} have been written to ${clc.bold(filename)}.`);
|
|
36
35
|
logger_1.logger.info(`Future modifications to ${clc.bold(filename)} will update Realtime Database Security Rules when you run`);
|
|
37
36
|
logger_1.logger.info(clc.bold("firebase deploy") + ".");
|
|
38
37
|
}
|
|
@@ -66,60 +65,72 @@ async function createDefaultDatabaseInstance(project) {
|
|
|
66
65
|
throw err;
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
|
-
async function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
setup.instance =
|
|
76
|
-
setup.instance || (await (0, getDefaultDatabaseInstance_1.getDefaultDatabaseInstance)({ project: setup.projectId }));
|
|
77
|
-
if (setup.instance !== "") {
|
|
78
|
-
instanceDetails = await (0, database_1.getDatabaseInstanceDetails)(setup.projectId, setup.instance);
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
const createDefault = await (0, prompt_1.confirm)({
|
|
82
|
-
message: "It seems like you haven’t initialized Realtime Database in your project yet. Do you want to set it up?",
|
|
83
|
-
default: true,
|
|
84
|
-
});
|
|
85
|
-
if (createDefault) {
|
|
86
|
-
instanceDetails = await createDefaultDatabaseInstance(setup.projectId);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
68
|
+
async function initializeDatabaseInstance(projectId) {
|
|
69
|
+
await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.rtdbManagementOrigin)(), "database", false);
|
|
70
|
+
logger_1.logger.info();
|
|
71
|
+
const instance = await (0, getDefaultDatabaseInstance_1.getDefaultDatabaseInstance)({ project: projectId });
|
|
72
|
+
if (instance !== "") {
|
|
73
|
+
return await (0, database_1.getDatabaseInstanceDetails)(projectId, instance);
|
|
89
74
|
}
|
|
90
|
-
|
|
75
|
+
const createDefault = await (0, prompt_1.confirm)({
|
|
76
|
+
message: "It seems like you haven’t initialized Realtime Database in your project yet. Do you want to set it up?",
|
|
77
|
+
default: true,
|
|
78
|
+
});
|
|
79
|
+
if (createDefault) {
|
|
80
|
+
return await createDefaultDatabaseInstance(projectId);
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
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.");
|
|
94
88
|
logger_1.logger.info();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}));
|
|
101
|
-
const filename = setup.config.database.rules;
|
|
102
|
-
if (!filename) {
|
|
89
|
+
const rulesFilename = await (0, prompt_1.input)({
|
|
90
|
+
message: "What file should be used for Realtime Database Security Rules?",
|
|
91
|
+
default: DEFAULT_RULES_FILENAME,
|
|
92
|
+
});
|
|
93
|
+
if (!rulesFilename) {
|
|
103
94
|
throw new error_1.FirebaseError("Must specify location for Realtime Database rules file.");
|
|
104
95
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
if (writeRules) {
|
|
96
|
+
const info = {
|
|
97
|
+
rulesFilename,
|
|
98
|
+
rules: exports.DEFAULT_RULES,
|
|
99
|
+
writeRules: true,
|
|
100
|
+
};
|
|
101
|
+
if (setup.projectId) {
|
|
102
|
+
const instanceDetails = await initializeDatabaseInstance(setup.projectId);
|
|
114
103
|
if (instanceDetails) {
|
|
115
|
-
|
|
116
|
-
|
|
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`);
|
|
117
106
|
}
|
|
118
|
-
writeDBRules(DEFAULT_RULES, "Default rules", filename, config);
|
|
119
|
-
return;
|
|
120
107
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
108
|
+
info.writeRules = await config.confirmWriteProjectFile(rulesFilename, info.rules);
|
|
109
|
+
setup.featureInfo = setup.featureInfo || {};
|
|
110
|
+
setup.featureInfo.database = info;
|
|
111
|
+
}
|
|
112
|
+
exports.askQuestions = askQuestions;
|
|
113
|
+
async function actuate(setup, config) {
|
|
114
|
+
var _a;
|
|
115
|
+
const info = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.database;
|
|
116
|
+
if (!info) {
|
|
117
|
+
throw new error_1.FirebaseError("No database RequiredInfo found in setup actuate.");
|
|
118
|
+
}
|
|
119
|
+
info.rules = info.rules || exports.DEFAULT_RULES;
|
|
120
|
+
info.rulesFilename = info.rulesFilename || "database.rules.json";
|
|
121
|
+
setup.config.database = { rules: info.rulesFilename };
|
|
122
|
+
if (info.writeRules) {
|
|
123
|
+
if (info.rules === exports.DEFAULT_RULES) {
|
|
124
|
+
writeDBRules(info.rules, info.rulesFilename, config);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
writeDBRules(info.rules, info.rulesFilename, config);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
logger_1.logger.info("Skipping overwrite of Realtime Database Security Rules.");
|
|
132
|
+
logger_1.logger.info(`The security rules defined in ${clc.bold(info.rulesFilename)} will be published when you run ${clc.bold("firebase deploy")}.`);
|
|
133
|
+
}
|
|
134
|
+
return Promise.resolve();
|
|
124
135
|
}
|
|
125
|
-
exports.
|
|
136
|
+
exports.actuate = actuate;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toDNSCompatibleId = exports.actuate = exports.
|
|
3
|
+
exports.toDNSCompatibleId = exports.postSetup = exports.actuate = exports.askQuestions = void 0;
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const clc = require("colorette");
|
|
6
6
|
const fs = require("fs-extra");
|
|
@@ -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",
|
|
@@ -44,29 +44,11 @@ const defaultConnector = {
|
|
|
44
44
|
],
|
|
45
45
|
};
|
|
46
46
|
const defaultSchema = { path: "schema.gql", content: SCHEMA_TEMPLATE };
|
|
47
|
-
async function
|
|
48
|
-
const
|
|
47
|
+
async function askQuestions(setup) {
|
|
48
|
+
const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
|
|
49
49
|
if (setup.projectId) {
|
|
50
|
-
|
|
50
|
+
hasBilling ? await (0, ensureApis_1.ensureApis)(setup.projectId) : await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
|
|
51
51
|
}
|
|
52
|
-
const info = await askQuestions(setup, isBillingEnabled);
|
|
53
|
-
const dir = config.get("dataconnect.source", "dataconnect");
|
|
54
|
-
const dataDir = config.get("emulators.dataconnect.dataDir", `${dir}/.dataconnect/pgliteData`);
|
|
55
|
-
config.set("emulators.dataconnect.dataDir", dataDir);
|
|
56
|
-
await actuate(setup, config, info);
|
|
57
|
-
const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
|
|
58
|
-
if (cwdPlatformGuess !== types_1.Platform.NONE) {
|
|
59
|
-
await sdk.doSetup(setup, config);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
(0, utils_1.logBullet)(`If you'd like to add the generated SDK to your app later, run ${clc.bold("firebase init dataconnect:sdk")}`);
|
|
63
|
-
}
|
|
64
|
-
if (setup.projectId && !isBillingEnabled) {
|
|
65
|
-
(0, utils_1.logBullet)((0, freeTrial_1.upgradeInstructions)(setup.projectId));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
exports.doSetup = doSetup;
|
|
69
|
-
async function askQuestions(setup, isBillingEnabled) {
|
|
70
52
|
let info = {
|
|
71
53
|
serviceId: "",
|
|
72
54
|
locationId: "",
|
|
@@ -74,19 +56,19 @@ async function askQuestions(setup, isBillingEnabled) {
|
|
|
74
56
|
isNewInstance: false,
|
|
75
57
|
cloudSqlDatabase: "",
|
|
76
58
|
isNewDatabase: false,
|
|
77
|
-
connectors: [
|
|
78
|
-
schemaGql: [
|
|
59
|
+
connectors: [],
|
|
60
|
+
schemaGql: [],
|
|
79
61
|
shouldProvisionCSQL: false,
|
|
80
62
|
};
|
|
81
|
-
info = await promptForExistingServices(setup, info
|
|
63
|
+
info = await promptForExistingServices(setup, info);
|
|
82
64
|
const requiredConfigUnset = info.serviceId === "" ||
|
|
83
65
|
info.cloudSqlInstanceId === "" ||
|
|
84
66
|
info.locationId === "" ||
|
|
85
67
|
info.cloudSqlDatabase === "";
|
|
86
|
-
const shouldConfigureBackend =
|
|
68
|
+
const shouldConfigureBackend = hasBilling &&
|
|
87
69
|
requiredConfigUnset &&
|
|
88
70
|
(await (0, prompt_1.confirm)({
|
|
89
|
-
message: `Would you like to configure your
|
|
71
|
+
message: `Would you like to configure your Cloud SQL datasource now?`,
|
|
90
72
|
default: true,
|
|
91
73
|
}));
|
|
92
74
|
if (shouldConfigureBackend) {
|
|
@@ -94,24 +76,36 @@ async function askQuestions(setup, isBillingEnabled) {
|
|
|
94
76
|
info = await promptForCloudSQL(setup, info);
|
|
95
77
|
info.shouldProvisionCSQL = !!(setup.projectId &&
|
|
96
78
|
(info.isNewInstance || info.isNewDatabase) &&
|
|
97
|
-
|
|
79
|
+
hasBilling &&
|
|
98
80
|
(await (0, prompt_1.confirm)({
|
|
99
81
|
message: `Would you like to provision your Cloud SQL instance and database now?${info.isNewInstance ? " This will take several minutes." : ""}.`,
|
|
100
82
|
default: true,
|
|
101
83
|
})));
|
|
102
84
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
info.serviceId = info.serviceId || defaultServiceId;
|
|
106
|
-
info.cloudSqlInstanceId =
|
|
107
|
-
info.cloudSqlInstanceId || `${info.serviceId.toLowerCase() || "app"}-fdc`;
|
|
108
|
-
info.locationId = info.locationId || `us-central1`;
|
|
109
|
-
info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
|
|
110
|
-
}
|
|
111
|
-
return info;
|
|
85
|
+
setup.featureInfo = setup.featureInfo || {};
|
|
86
|
+
setup.featureInfo.dataconnect = info;
|
|
112
87
|
}
|
|
113
|
-
|
|
114
|
-
|
|
88
|
+
exports.askQuestions = askQuestions;
|
|
89
|
+
async function actuate(setup, config, options) {
|
|
90
|
+
var _a;
|
|
91
|
+
const dir = config.get("dataconnect.source", "dataconnect");
|
|
92
|
+
const dataDir = config.get("emulators.dataconnect.dataDir", `${dir}/.dataconnect/pgliteData`);
|
|
93
|
+
config.set("emulators.dataconnect.dataDir", dataDir);
|
|
94
|
+
const info = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
|
|
95
|
+
if (!info) {
|
|
96
|
+
throw new Error("Data Connect feature RequiredInfo is not provided");
|
|
97
|
+
}
|
|
98
|
+
const defaultServiceId = toDNSCompatibleId((0, path_1.basename)(process.cwd()));
|
|
99
|
+
info.serviceId = info.serviceId || defaultServiceId;
|
|
100
|
+
info.cloudSqlInstanceId =
|
|
101
|
+
info.cloudSqlInstanceId || `${info.serviceId.toLowerCase() || "app"}-fdc`;
|
|
102
|
+
info.locationId = info.locationId || `us-central1`;
|
|
103
|
+
info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
|
|
104
|
+
if (!info.schemaGql.length && !info.connectors.flatMap((r) => r.files).length) {
|
|
105
|
+
info.schemaGql = [defaultSchema];
|
|
106
|
+
info.connectors = [defaultConnector];
|
|
107
|
+
}
|
|
108
|
+
await writeFiles(config, info, options);
|
|
115
109
|
if (setup.projectId && info.shouldProvisionCSQL) {
|
|
116
110
|
await (0, provisionCloudSql_1.provisionCloudSql)({
|
|
117
111
|
projectId: setup.projectId,
|
|
@@ -124,20 +118,27 @@ async function actuate(setup, config, info) {
|
|
|
124
118
|
}
|
|
125
119
|
}
|
|
126
120
|
exports.actuate = actuate;
|
|
127
|
-
async function
|
|
121
|
+
async function postSetup(setup, config) {
|
|
122
|
+
const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
|
|
123
|
+
if (cwdPlatformGuess !== types_1.Platform.NONE) {
|
|
124
|
+
await sdk.doSetup(setup, config);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
(0, utils_1.logBullet)(`If you'd like to add the generated SDK to your app later, run ${clc.bold("firebase init dataconnect:sdk")}`);
|
|
128
|
+
}
|
|
129
|
+
if (setup.projectId && !setup.isBillingEnabled) {
|
|
130
|
+
(0, utils_1.logBullet)((0, freeTrial_1.upgradeInstructions)(setup.projectId));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.postSetup = postSetup;
|
|
134
|
+
async function writeFiles(config, info, options) {
|
|
128
135
|
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
129
136
|
const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: info.connectors.map((c) => c.path) }));
|
|
130
|
-
if (!config.get("dataconnect.source")) {
|
|
131
|
-
if (!info.schemaGql.length && !info.connectors.flatMap((r) => r.files).length) {
|
|
132
|
-
info.schemaGql = [defaultSchema];
|
|
133
|
-
info.connectors = [defaultConnector];
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
137
|
config.set("dataconnect", { source: dir });
|
|
137
|
-
await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml,
|
|
138
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml, !!options.force, true);
|
|
138
139
|
if (info.schemaGql.length) {
|
|
139
140
|
for (const f of info.schemaGql) {
|
|
140
|
-
await config.askWriteProjectFile((0, path_1.join)(dir, "schema", f.path), f.content);
|
|
141
|
+
await config.askWriteProjectFile((0, path_1.join)(dir, "schema", f.path), f.content, !!options.force);
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
else {
|
|
@@ -179,43 +180,17 @@ function subConnectorYamlValues(replacementValues) {
|
|
|
179
180
|
}
|
|
180
181
|
return replaced;
|
|
181
182
|
}
|
|
182
|
-
async function promptForExistingServices(setup, info
|
|
183
|
+
async function promptForExistingServices(setup, info) {
|
|
183
184
|
var _a, _b, _c, _d;
|
|
184
|
-
if (!setup.projectId
|
|
185
|
+
if (!setup.projectId) {
|
|
185
186
|
return info;
|
|
186
187
|
}
|
|
187
188
|
const existingServices = await (0, client_1.listAllServices)(setup.projectId);
|
|
188
189
|
const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
|
|
189
|
-
return {
|
|
190
|
-
service: s,
|
|
191
|
-
schema: await (0, client_1.getSchema)(s.name),
|
|
192
|
-
};
|
|
190
|
+
return { service: s, schema: await (0, client_1.getSchema)(s.name) };
|
|
193
191
|
}));
|
|
194
192
|
if (existingServicesAndSchemas.length) {
|
|
195
|
-
|
|
196
|
-
const [serviceLocationFromEnvVar, serviceIdFromEnvVar] = serviceEnvVar().split("/");
|
|
197
|
-
const serviceFromEnvVar = existingServicesAndSchemas.find((s) => {
|
|
198
|
-
const serviceName = (0, names_1.parseServiceName)(s.service.name);
|
|
199
|
-
return (serviceName.serviceId === serviceIdFromEnvVar &&
|
|
200
|
-
serviceName.location === serviceLocationFromEnvVar);
|
|
201
|
-
});
|
|
202
|
-
if (serviceFromEnvVar) {
|
|
203
|
-
choice = serviceFromEnvVar;
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
const choices = existingServicesAndSchemas.map((s) => {
|
|
207
|
-
const serviceName = (0, names_1.parseServiceName)(s.service.name);
|
|
208
|
-
return {
|
|
209
|
-
name: `${serviceName.location}/${serviceName.serviceId}`,
|
|
210
|
-
value: s,
|
|
211
|
-
};
|
|
212
|
-
});
|
|
213
|
-
choices.push({ name: "Create a new service", value: undefined });
|
|
214
|
-
choice = await (0, prompt_1.select)({
|
|
215
|
-
message: "Your project already has existing services. Which would you like to set up local files for?",
|
|
216
|
-
choices,
|
|
217
|
-
});
|
|
218
|
-
}
|
|
193
|
+
const choice = await chooseExistingService(existingServicesAndSchemas);
|
|
219
194
|
if (choice) {
|
|
220
195
|
const serviceName = (0, names_1.parseServiceName)(choice.service.name);
|
|
221
196
|
info.serviceId = serviceName.serviceId;
|
|
@@ -251,6 +226,30 @@ async function promptForExistingServices(setup, info, isBillingEnabled) {
|
|
|
251
226
|
}
|
|
252
227
|
return info;
|
|
253
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
|
+
}
|
|
254
253
|
async function promptForCloudSQL(setup, info) {
|
|
255
254
|
if (info.cloudSqlInstanceId === "" && setup.projectId) {
|
|
256
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,
|
|
@@ -27,23 +27,27 @@ async function doSetup(setup, config) {
|
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
setup.config.emulators = setup.config.emulators || {};
|
|
30
|
+
const emulators = setup.config.emulators || {};
|
|
30
31
|
for (const selected of selections.emulators) {
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
if (selected === "extensions")
|
|
33
|
+
continue;
|
|
34
|
+
const selectedEmulator = emulators[selected] || {};
|
|
35
|
+
const currentPort = selectedEmulator.port;
|
|
33
36
|
if (currentPort) {
|
|
34
37
|
utils.logBullet(`Port for ${selected} already configured: ${clc.cyan(currentPort)}`);
|
|
35
38
|
}
|
|
36
39
|
else {
|
|
37
|
-
|
|
40
|
+
selectedEmulator.port = await (0, prompt_1.number)({
|
|
38
41
|
message: `Which port do you want to use for the ${clc.underline(selected)} emulator?`,
|
|
39
42
|
default: constants_1.Constants.getDefaultPort(selected),
|
|
40
43
|
});
|
|
41
44
|
}
|
|
45
|
+
emulators[selected] = selectedEmulator;
|
|
42
46
|
const additionalInitFn = initEmulators_1.AdditionalInitFns[selected];
|
|
43
47
|
if (additionalInitFn) {
|
|
44
48
|
const additionalOptions = await additionalInitFn(config);
|
|
45
49
|
if (additionalOptions) {
|
|
46
|
-
|
|
50
|
+
emulators[selected] = Object.assign(Object.assign({}, setup.config.emulators[selected]), additionalOptions);
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -61,9 +65,10 @@ async function doSetup(setup, config) {
|
|
|
61
65
|
default: true,
|
|
62
66
|
});
|
|
63
67
|
if (ui.enabled) {
|
|
64
|
-
ui.port = await (0, prompt_1.number)(
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
});
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
selections.download = await (0, prompt_1.confirm)({
|