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.
Files changed (162) hide show
  1. package/lib/api.js +10 -2
  2. package/lib/apphosting/backend.js +74 -18
  3. package/lib/apphosting/rollout.js +2 -2
  4. package/lib/apphosting/secrets/dialogs.js +4 -4
  5. package/lib/apphosting/secrets/index.js +2 -2
  6. package/lib/auth.js +22 -2
  7. package/lib/bin/cli.js +1 -33
  8. package/lib/bin/mcp.js +13 -2
  9. package/lib/checkValidTargetFilters.js +1 -0
  10. package/lib/command.js +3 -2
  11. package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
  12. package/lib/commands/deploy.js +1 -0
  13. package/lib/commands/init.js +1 -4
  14. package/lib/commands/login-use.js +2 -11
  15. package/lib/commands/use.js +9 -8
  16. package/lib/config.js +43 -24
  17. package/lib/crashlytics/listTopIssues.js +44 -0
  18. package/lib/dataconnect/cloudAICompanionClient.js +72 -0
  19. package/lib/dataconnect/cloudAICompanionTypes.js +2 -0
  20. package/lib/dataconnect/fileUtils.js +11 -4
  21. package/lib/dataconnect/schemaMigration.js +6 -7
  22. package/lib/deploy/apphosting/args.js +2 -0
  23. package/lib/deploy/apphosting/deploy.js +77 -0
  24. package/lib/deploy/apphosting/index.js +9 -0
  25. package/lib/deploy/apphosting/prepare.js +147 -0
  26. package/lib/deploy/apphosting/release.js +56 -0
  27. package/lib/deploy/apphosting/util.js +65 -0
  28. package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
  29. package/lib/deploy/firestore/deploy.js +47 -4
  30. package/lib/deploy/functions/checkIam.js +3 -3
  31. package/lib/deploy/functions/ensure.js +2 -1
  32. package/lib/deploy/functions/prepare.js +23 -16
  33. package/lib/deploy/functions/release/fabricator.js +4 -4
  34. package/lib/deploy/functions/release/index.js +1 -1
  35. package/lib/deploy/functions/runtimes/python/index.js +3 -0
  36. package/lib/deploy/functions/runtimes/supported/types.js +17 -11
  37. package/lib/deploy/index.js +2 -0
  38. package/lib/emulator/apphosting/index.js +1 -0
  39. package/lib/emulator/apphosting/serve.js +77 -3
  40. package/lib/emulator/auth/widget_ui.js +2 -1
  41. package/lib/emulator/controller.js +18 -4
  42. package/lib/emulator/dataconnectEmulator.js +9 -2
  43. package/lib/emulator/downloadableEmulatorInfo.json +81 -0
  44. package/lib/emulator/downloadableEmulators.js +28 -108
  45. package/lib/experiments.js +1 -1
  46. package/lib/extensions/manifest.js +2 -5
  47. package/lib/frameworks/angular/index.js +1 -1
  48. package/lib/frameworks/angular/utils.js +17 -6
  49. package/lib/fsAsync.js +9 -2
  50. package/lib/gcp/apphosting.js +13 -1
  51. package/lib/gcp/auth.js +32 -2
  52. package/lib/gcp/cloudbilling.js +12 -1
  53. package/lib/gcp/cloudfunctions.js +1 -2
  54. package/lib/gcp/cloudfunctionsv2.js +1 -2
  55. package/lib/gcp/cloudscheduler.js +2 -2
  56. package/lib/gcp/computeEngine.js +19 -2
  57. package/lib/gcp/devConnect.js +6 -1
  58. package/lib/gcp/firestore.js +24 -1
  59. package/lib/gcp/iam.js +1 -5
  60. package/lib/gcp/run.js +19 -1
  61. package/lib/gcp/storage.js +25 -1
  62. package/lib/index.js +1 -2
  63. package/lib/init/features/apphosting.js +85 -6
  64. package/lib/init/features/database.js +63 -52
  65. package/lib/init/features/dataconnect/index.js +78 -79
  66. package/lib/init/features/dataconnect/sdk.js +19 -6
  67. package/lib/init/features/emulators.js +12 -7
  68. package/lib/init/features/firestore/index.js +80 -39
  69. package/lib/init/features/firestore/indexes.js +29 -31
  70. package/lib/init/features/firestore/rules.js +35 -48
  71. package/lib/init/features/functions/index.js +2 -0
  72. package/lib/init/features/functions/javascript.js +3 -2
  73. package/lib/init/features/functions/typescript.js +3 -2
  74. package/lib/init/features/genkit/index.js +18 -10
  75. package/lib/init/features/hosting/github.js +3 -2
  76. package/lib/init/features/hosting/index.js +9 -8
  77. package/lib/init/features/index.js +10 -5
  78. package/lib/init/features/remoteconfig.js +3 -2
  79. package/lib/init/features/storage.js +31 -8
  80. package/lib/init/index.js +80 -24
  81. package/lib/logger.js +71 -7
  82. package/lib/management/projects.js +24 -1
  83. package/lib/mcp/errors.js +1 -1
  84. package/lib/mcp/index.js +142 -46
  85. package/lib/mcp/tool.js +2 -1
  86. package/lib/mcp/tools/apphosting/fetch_logs.js +69 -0
  87. package/lib/mcp/tools/apphosting/index.js +6 -0
  88. package/lib/mcp/tools/apphosting/list_backends.js +51 -0
  89. package/lib/mcp/tools/auth/get_user.js +16 -7
  90. package/lib/mcp/tools/auth/index.js +8 -1
  91. package/lib/mcp/tools/auth/list_users.js +47 -0
  92. package/lib/mcp/tools/auth/set_claims.js +20 -11
  93. package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
  94. package/lib/mcp/tools/core/consult_assistant.js +1 -1
  95. package/lib/mcp/tools/core/create_android_sha.js +40 -0
  96. package/lib/mcp/tools/core/create_app.js +90 -0
  97. package/lib/mcp/tools/core/create_project.js +68 -0
  98. package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
  99. package/lib/mcp/tools/core/get_environment.js +51 -0
  100. package/lib/mcp/tools/core/get_sdk_config.js +6 -3
  101. package/lib/mcp/tools/core/index.js +21 -2
  102. package/lib/mcp/tools/core/init.js +153 -0
  103. package/lib/mcp/tools/core/list_apps.js +10 -5
  104. package/lib/mcp/tools/core/list_projects.js +45 -0
  105. package/lib/mcp/tools/core/update_environment.js +55 -0
  106. package/lib/mcp/tools/crashlytics/index.js +5 -0
  107. package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
  108. package/lib/mcp/tools/dataconnect/converter.js +13 -1
  109. package/lib/mcp/tools/dataconnect/emulator.js +32 -0
  110. package/lib/mcp/tools/dataconnect/execute_graphql.js +24 -8
  111. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +24 -8
  112. package/lib/mcp/tools/dataconnect/execute_mutation.js +27 -15
  113. package/lib/mcp/tools/dataconnect/execute_query.js +26 -14
  114. package/lib/mcp/tools/dataconnect/generate_operation.js +7 -7
  115. package/lib/mcp/tools/dataconnect/generate_schema.js +1 -1
  116. package/lib/mcp/tools/dataconnect/get_connector.js +7 -7
  117. package/lib/mcp/tools/dataconnect/get_schema.js +5 -5
  118. package/lib/mcp/tools/dataconnect/index.js +1 -5
  119. package/lib/mcp/tools/dataconnect/list_services.js +1 -1
  120. package/lib/mcp/tools/firestore/converter.js +47 -1
  121. package/lib/mcp/tools/firestore/delete_document.js +37 -0
  122. package/lib/mcp/tools/firestore/index.js +12 -2
  123. package/lib/mcp/tools/firestore/list_collections.js +1 -6
  124. package/lib/mcp/tools/firestore/query_collection.js +122 -0
  125. package/lib/mcp/tools/index.js +37 -16
  126. package/lib/mcp/tools/messaging/index.js +5 -0
  127. package/lib/mcp/tools/messaging/send_message.js +42 -0
  128. package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
  129. package/lib/mcp/tools/remoteconfig/index.js +7 -0
  130. package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
  131. package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
  132. package/lib/mcp/tools/rules/get_rules.js +29 -0
  133. package/lib/mcp/tools/rules/validate_rules.js +98 -0
  134. package/lib/mcp/tools/storage/get_download_url.js +8 -5
  135. package/lib/mcp/tools/storage/index.js +7 -2
  136. package/lib/mcp/types.js +10 -1
  137. package/lib/mcp/util.js +165 -2
  138. package/lib/mcp/util.test.js +468 -0
  139. package/lib/messaging/interfaces.js +2 -0
  140. package/lib/messaging/sendMessage.js +48 -0
  141. package/lib/prompt.js +1 -1
  142. package/lib/remoteconfig/publish.js +39 -0
  143. package/lib/requireAuth.js +2 -2
  144. package/lib/track.js +1 -1
  145. package/lib/utils.js +2 -37
  146. package/package.json +2 -1
  147. package/schema/connector-yaml.json +12 -0
  148. package/schema/extension-yaml.json +17 -4
  149. package/schema/firebase-config.json +65 -10
  150. package/standalone/package.json +1 -1
  151. package/templates/dataconnect-prompts/operation-generation-cursor-windsurf-rule.txt +273 -0
  152. package/templates/dataconnect-prompts/schema-generation-cursor-windsurf-rule.txt +653 -0
  153. package/templates/genkit/firebase.1.0.0.template +5 -0
  154. package/templates/init/firestore/firestore.rules +2 -0
  155. package/templates/init/functions/javascript/package.lint.json +1 -1
  156. package/templates/init/functions/javascript/package.nolint.json +1 -1
  157. package/templates/init/functions/typescript/package.lint.json +2 -2
  158. package/templates/init/functions/typescript/package.nolint.json +3 -3
  159. package/lib/mcp/tools/directory/get_project_directory.js +0 -20
  160. package/lib/mcp/tools/directory/index.js +0 -6
  161. package/lib/mcp/tools/directory/set_project_directory.js +0 -33
  162. 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, utils_1.setupLoggers)();
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 utils = require("../../utils");
6
- const templates_1 = require("../../templates");
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
- await (0, cloudbilling_1.checkBillingEnabled)(setup.projectId);
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
- await config.askWriteProjectFile("apphosting.yaml", APPHOSTING_YAML_TEMPLATE);
13
- utils.logSuccess("Create a new App Hosting backend with `firebase apphosting:backends:create`");
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.doSetup = void 0;
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 DEFAULT_RULES = JSON.stringify({ rules: { ".read": "auth != null", ".write": "auth != null" } }, null, 2);
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, logMessagePrefix, filename, config) {
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 doSetup(setup, config) {
70
- setup.config = setup.config || {};
71
- let instanceDetails;
72
- if (setup.projectId) {
73
- await (0, ensureApiEnabled_1.ensure)(setup.projectId, (0, api_1.rtdbManagementOrigin)(), "database", false);
74
- logger_1.logger.info();
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
- setup.config.database = setup.config.database || {};
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
- setup.config.database.rules =
96
- setup.config.database.rules ||
97
- (await (0, prompt_1.input)({
98
- message: "What file should be used for Realtime Database Security Rules?",
99
- default: "database.rules.json",
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
- let writeRules = true;
106
- if (fsutils.fileExistsSync(filename)) {
107
- const rulesDescription = instanceDetails
108
- ? `the Realtime Database Security Rules for ${clc.bold(instanceDetails.name)} from the Firebase console`
109
- : "default rules";
110
- const msg = `File ${clc.bold(filename)} already exists. Do you want to overwrite it with ${rulesDescription}?`;
111
- writeRules = await (0, prompt_1.confirm)(msg);
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
- writeDBRules(await getDBRules(instanceDetails), `Database Rules for ${instanceDetails.name}`, filename, config);
116
- return;
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
- logger_1.logger.info("Skipping overwrite of Realtime Database Security Rules.");
122
- logger_1.logger.info(`The security rules defined in ${clc.bold(filename)} will be published when you run ${clc.bold("firebase deploy")}.`);
123
- return;
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.doSetup = doSetup;
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.doSetup = void 0;
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 doSetup(setup, config) {
48
- const isBillingEnabled = setup.projectId ? await (0, cloudbilling_1.checkBillingEnabled)(setup.projectId) : false;
47
+ async function askQuestions(setup) {
48
+ const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
49
49
  if (setup.projectId) {
50
- isBillingEnabled ? await (0, ensureApis_1.ensureApis)(setup.projectId) : await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
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: [defaultConnector],
78
- schemaGql: [defaultSchema],
59
+ connectors: [],
60
+ schemaGql: [],
79
61
  shouldProvisionCSQL: false,
80
62
  };
81
- info = await promptForExistingServices(setup, info, isBillingEnabled);
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 = isBillingEnabled &&
68
+ const shouldConfigureBackend = hasBilling &&
87
69
  requiredConfigUnset &&
88
70
  (await (0, prompt_1.confirm)({
89
- message: `Would you like to configure your backend resources now?`,
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
- isBillingEnabled &&
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
- else {
104
- const defaultServiceId = toDNSCompatibleId((0, path_1.basename)(process.cwd()));
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
- async function actuate(setup, config, info) {
114
- await writeFiles(config, info);
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 writeFiles(config, info) {
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, false, true);
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, isBillingEnabled) {
183
+ async function promptForExistingServices(setup, info) {
183
184
  var _a, _b, _c, _d;
184
- if (!setup.projectId || !isBillingEnabled) {
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
- let choice;
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 (0, prompt_1.select)({
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
- setup.config.emulators[selected] = setup.config.emulators[selected] || {};
32
- const currentPort = setup.config.emulators[selected].port;
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
- setup.config.emulators[selected].port = await (0, prompt_1.number)({
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
- setup.config.emulators[selected] = Object.assign(Object.assign({}, setup.config.emulators[selected]), additionalOptions);
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)(`Which port do you want to use for the ${clc.underline(uiDesc)} (leave empty to use any available port)?`);
65
- const portNum = Number.parseInt(ui.port);
66
- ui.port = isNaN(portNum) ? undefined : portNum;
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)({