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.
Files changed (62) hide show
  1. package/lib/apphosting/backend.js +2 -0
  2. package/lib/bin/mcp.js +1 -0
  3. package/lib/commands/init.js +0 -3
  4. package/lib/config.js +42 -24
  5. package/lib/dataconnect/cloudAICompanionClient.js +7 -2
  6. package/lib/dataconnect/cloudAICompanionTypes.js +2 -0
  7. package/lib/dataconnect/fileUtils.js +11 -4
  8. package/lib/dataconnect/schemaMigration.js +6 -7
  9. package/lib/deploy/apphosting/deploy.js +3 -0
  10. package/lib/deploy/apphosting/prepare.js +34 -28
  11. package/lib/deploy/apphosting/release.js +3 -0
  12. package/lib/deploy/firestore/deploy.js +47 -4
  13. package/lib/emulator/apphosting/serve.js +1 -1
  14. package/lib/emulator/downloadableEmulatorInfo.json +39 -18
  15. package/lib/emulator/downloadableEmulators.js +15 -59
  16. package/lib/extensions/manifest.js +0 -3
  17. package/lib/frameworks/angular/index.js +1 -1
  18. package/lib/frameworks/angular/utils.js +17 -6
  19. package/lib/gcp/apphosting.js +13 -1
  20. package/lib/gcp/run.js +19 -1
  21. package/lib/init/features/apphosting.js +3 -2
  22. package/lib/init/features/database.js +11 -19
  23. package/lib/init/features/dataconnect/index.js +27 -26
  24. package/lib/init/features/dataconnect/sdk.js +19 -6
  25. package/lib/init/features/emulators.js +4 -3
  26. package/lib/init/features/firestore/index.js +44 -34
  27. package/lib/init/features/firestore/indexes.js +12 -13
  28. package/lib/init/features/firestore/rules.js +8 -15
  29. package/lib/init/features/genkit/index.js +16 -9
  30. package/lib/init/features/hosting/index.js +9 -8
  31. package/lib/init/features/index.js +3 -2
  32. package/lib/init/features/storage.js +31 -8
  33. package/lib/init/index.js +5 -1
  34. package/lib/mcp/index.js +10 -6
  35. package/lib/mcp/tool.js +2 -1
  36. package/lib/mcp/tools/apphosting/fetch_logs.js +69 -0
  37. package/lib/mcp/tools/apphosting/index.js +6 -0
  38. package/lib/mcp/tools/apphosting/list_backends.js +51 -0
  39. package/lib/mcp/tools/core/index.js +2 -0
  40. package/lib/mcp/tools/core/init.js +26 -2
  41. package/lib/mcp/tools/core/list_apps.js +10 -5
  42. package/lib/mcp/tools/core/list_projects.js +45 -0
  43. package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
  44. package/lib/mcp/tools/firestore/query_collection.js +13 -7
  45. package/lib/mcp/tools/index.js +2 -0
  46. package/lib/mcp/tools/storage/get_download_url.js +1 -1
  47. package/lib/mcp/types.js +1 -0
  48. package/lib/mcp/util.js +137 -1
  49. package/lib/mcp/util.test.js +468 -0
  50. package/lib/prompt.js +1 -1
  51. package/lib/track.js +1 -1
  52. package/package.json +1 -1
  53. package/schema/connector-yaml.json +12 -0
  54. package/schema/extension-yaml.json +17 -4
  55. package/schema/firebase-config.json +3 -0
  56. package/standalone/package.json +1 -1
  57. package/templates/dataconnect-prompts/operation-generation-cursor-windsurf-rule.txt +273 -0
  58. package/templates/dataconnect-prompts/schema-generation-cursor-windsurf-rule.txt +653 -0
  59. package/templates/genkit/firebase.1.0.0.template +5 -0
  60. package/templates/init/firestore/firestore.rules +2 -0
  61. package/templates/init/functions/typescript/package.lint.json +1 -1
  62. 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 - 19";
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
- (0, utils_2.assertIsString)(targetOptions === null || targetOptions === void 0 ? void 0 : targetOptions.outputPath);
385
- const outputPath = (0, path_1.join)(targetOptions.outputPath, buildTarget && getBuilderType(builderName) === BuilderType.APPLICATION ? "browser" : "");
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
- (0, utils_2.assertIsString)(browserTargetOptions === null || browserTargetOptions === void 0 ? void 0 : browserTargetOptions.outputPath);
398
- const browserOutputPath = (0, path_1.join)(browserTargetOptions.outputPath, buildTarget ? "browser" : "")
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
- (0, utils_2.assertIsString)(serverTargetOptions === null || serverTargetOptions === void 0 ? void 0 : serverTargetOptions.outputPath);
423
- const serverOutputPath = (0, path_1.join)(serverTargetOptions.outputPath, buildTarget ? "server" : "")
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) {
@@ -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
- utils.logWarning("Your App Hosting compute service account is still being provisioned. Please try again in a few moments.");
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, logMessagePrefix, filename, config) {
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 (fsutils.fileExistsSync(rulesFilename)) {
108
- const rulesDescription = instanceDetails
109
- ? `the Realtime Database Security Rules for ${clc.bold(instanceDetails.name)} from the Firebase console`
110
- : "default rules";
111
- const msg = `File ${clc.bold(rulesFilename)} already exists. Do you want to overwrite it with ${rulesDescription}?`;
112
- info.writeRules = await (0, prompt_1.confirm)(msg);
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, `Default rules for ${setup.projectId}`, info.rulesFilename, config);
124
+ writeDBRules(info.rules, info.rulesFilename, config);
133
125
  }
134
126
  else {
135
- writeDBRules(info.rules, `Database Rules for ${setup.projectId}`, info.rulesFilename, config);
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 backend resources now?`,
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
- let choice;
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 (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,
@@ -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)(`Which port do you want to use for the ${clc.underline(uiDesc)} (leave empty to use any available port)?`);
69
- const portNum = Number.parseInt(ui.port);
70
- 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
+ });
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 = exports.doSetup = void 0;
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 clc = require("colorette");
7
+ const api_1 = require("../../../firestore/api");
12
8
  const prompt_1 = require("../../../prompt");
13
- async function checkProjectSetup(setup, options, info) {
14
- const firestoreUnusedError = new error_1.FirebaseError(`It looks like you haven't used Cloud Firestore in this project before. Go to ${clc.bold(clc.underline(`https://console.firebase.google.com/project/${setup.projectId}/firestore`))} to create your Cloud Firestore database.`, { exit: 1 });
15
- const isFirestoreEnabled = await apiEnabled.check(setup.projectId, "firestore.googleapis.com", "", true);
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 checkProjectSetup(setup, options, info);
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
- if (fsutils.fileExistsSync(info.indexesFilename)) {
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
- info.indexes = await getIndexesFromConsole(setup.projectId, info.databaseId);
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 exports.INDEXES_TEMPLATE;
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
- if (fsutils.fileExistsSync(info.rulesFilename)) {
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
- info.rules = await getRulesFromConsole(setup.projectId);
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
- logger_1.logger.debug("No rulesets found, using default.");
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 });