firebase-tools 14.4.0 → 14.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) 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/commands/login.js +8 -3
  5. package/lib/config.js +42 -24
  6. package/lib/dataconnect/cloudAICompanionClient.js +7 -2
  7. package/lib/dataconnect/cloudAICompanionTypes.js +2 -0
  8. package/lib/dataconnect/fileUtils.js +11 -4
  9. package/lib/dataconnect/schemaMigration.js +6 -7
  10. package/lib/deploy/apphosting/deploy.js +3 -0
  11. package/lib/deploy/apphosting/prepare.js +34 -28
  12. package/lib/deploy/apphosting/release.js +3 -0
  13. package/lib/deploy/firestore/deploy.js +50 -4
  14. package/lib/emulator/apphosting/serve.js +1 -1
  15. package/lib/emulator/downloadableEmulatorInfo.json +39 -18
  16. package/lib/emulator/downloadableEmulators.js +15 -59
  17. package/lib/extensions/manifest.js +0 -3
  18. package/lib/frameworks/angular/index.js +1 -1
  19. package/lib/frameworks/angular/utils.js +17 -6
  20. package/lib/gcp/apphosting.js +13 -1
  21. package/lib/gcp/run.js +19 -1
  22. package/lib/init/features/apphosting.js +3 -2
  23. package/lib/init/features/database.js +11 -19
  24. package/lib/init/features/dataconnect/index.js +30 -26
  25. package/lib/init/features/dataconnect/sdk.js +21 -6
  26. package/lib/init/features/emulators.js +4 -3
  27. package/lib/init/features/firestore/index.js +44 -34
  28. package/lib/init/features/firestore/indexes.js +12 -13
  29. package/lib/init/features/firestore/rules.js +8 -15
  30. package/lib/init/features/genkit/index.js +16 -9
  31. package/lib/init/features/hosting/index.js +9 -8
  32. package/lib/init/features/index.js +3 -2
  33. package/lib/init/features/storage.js +31 -8
  34. package/lib/init/index.js +5 -1
  35. package/lib/mcp/index.js +10 -6
  36. package/lib/mcp/tool.js +2 -1
  37. package/lib/mcp/tools/apphosting/fetch_logs.js +69 -0
  38. package/lib/mcp/tools/apphosting/index.js +6 -0
  39. package/lib/mcp/tools/apphosting/list_backends.js +51 -0
  40. package/lib/mcp/tools/core/index.js +2 -0
  41. package/lib/mcp/tools/core/init.js +26 -2
  42. package/lib/mcp/tools/core/list_apps.js +10 -5
  43. package/lib/mcp/tools/core/list_projects.js +45 -0
  44. package/lib/mcp/tools/dataconnect/generate_operation.js +1 -1
  45. package/lib/mcp/tools/firestore/query_collection.js +13 -7
  46. package/lib/mcp/tools/index.js +2 -0
  47. package/lib/mcp/tools/storage/get_download_url.js +1 -1
  48. package/lib/mcp/types.js +1 -0
  49. package/lib/mcp/util.js +137 -1
  50. package/lib/mcp/util.test.js +468 -0
  51. package/lib/prompt.js +1 -1
  52. package/lib/track.js +1 -1
  53. package/package.json +1 -1
  54. package/schema/connector-yaml.json +12 -0
  55. package/schema/extension-yaml.json +17 -4
  56. package/schema/firebase-config.json +3 -0
  57. package/standalone/package.json +1 -1
  58. package/templates/dataconnect-prompts/operation-generation-cursor-windsurf-rule.txt +273 -0
  59. package/templates/dataconnect-prompts/schema-generation-cursor-windsurf-rule.txt +653 -0
  60. package/templates/genkit/firebase.1.0.0.template +5 -0
  61. package/templates/init/firestore/firestore.rules +2 -0
  62. package/templates/init/functions/typescript/package.lint.json +1 -1
  63. package/templates/init/functions/typescript/package.nolint.json +1 -1
@@ -30,83 +30,39 @@ const dataconnectDetails = process.platform === "darwin"
30
30
  : EMULATOR_UPDATE_DETAILS.dataconnect.linux;
31
31
  exports.DownloadDetails = {
32
32
  database: {
33
- downloadPath: path.join(CACHE_DIR, `firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar`),
33
+ downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.database.downloadPathRelativeToCacheDir),
34
34
  version: EMULATOR_UPDATE_DETAILS.database.version,
35
- opts: {
36
- cacheDir: CACHE_DIR,
37
- remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v${EMULATOR_UPDATE_DETAILS.database.version}.jar`,
38
- expectedSize: EMULATOR_UPDATE_DETAILS.database.expectedSize,
39
- expectedChecksum: EMULATOR_UPDATE_DETAILS.database.expectedChecksum,
40
- namePrefix: "firebase-database-emulator",
41
- },
35
+ opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.database), { cacheDir: CACHE_DIR, namePrefix: "firebase-database-emulator" }),
42
36
  },
43
37
  firestore: {
44
- downloadPath: path.join(CACHE_DIR, `cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar`),
38
+ downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.firestore.downloadPathRelativeToCacheDir),
45
39
  version: EMULATOR_UPDATE_DETAILS.firestore.version,
46
- opts: {
47
- cacheDir: CACHE_DIR,
48
- remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v${EMULATOR_UPDATE_DETAILS.firestore.version}.jar`,
49
- expectedSize: EMULATOR_UPDATE_DETAILS.firestore.expectedSize,
50
- expectedChecksum: EMULATOR_UPDATE_DETAILS.firestore.expectedChecksum,
51
- namePrefix: "cloud-firestore-emulator",
52
- },
40
+ opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.firestore), { cacheDir: CACHE_DIR, namePrefix: "cloud-firestore-emulator" }),
53
41
  },
54
42
  storage: {
55
- downloadPath: path.join(CACHE_DIR, `cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar`),
43
+ downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.storage.downloadPathRelativeToCacheDir),
56
44
  version: EMULATOR_UPDATE_DETAILS.storage.version,
57
- opts: {
58
- cacheDir: CACHE_DIR,
59
- remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-storage-rules-runtime-v${EMULATOR_UPDATE_DETAILS.storage.version}.jar`,
60
- expectedSize: EMULATOR_UPDATE_DETAILS.storage.expectedSize,
61
- expectedChecksum: EMULATOR_UPDATE_DETAILS.storage.expectedChecksum,
62
- namePrefix: "cloud-storage-rules-emulator",
63
- },
45
+ opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.storage), { cacheDir: CACHE_DIR, namePrefix: "cloud-storage-rules-emulator" }),
64
46
  },
65
47
  ui: {
66
48
  version: emulatorUiDetails.version,
67
- downloadPath: path.join(CACHE_DIR, `ui-v${emulatorUiDetails.version}.zip`),
49
+ downloadPath: path.join(CACHE_DIR, emulatorUiDetails.downloadPathRelativeToCacheDir),
68
50
  unzipDir: path.join(CACHE_DIR, `ui-v${emulatorUiDetails.version}`),
69
- binaryPath: path.join(CACHE_DIR, `ui-v${emulatorUiDetails.version}`, "server", "server.mjs"),
70
- opts: {
71
- cacheDir: CACHE_DIR,
72
- remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v${emulatorUiDetails.version}.zip`,
73
- expectedSize: emulatorUiDetails.expectedSize,
74
- expectedChecksum: emulatorUiDetails.expectedChecksum,
75
- skipCache: experiments.isEnabled("emulatoruisnapshot"),
76
- skipChecksumAndSize: experiments.isEnabled("emulatoruisnapshot"),
77
- namePrefix: "ui",
78
- },
51
+ binaryPath: path.join(CACHE_DIR, emulatorUiDetails.binaryPathRelativeToCacheDir),
52
+ opts: Object.assign(Object.assign({}, emulatorUiDetails), { cacheDir: CACHE_DIR, skipCache: experiments.isEnabled("emulatoruisnapshot"), skipChecksumAndSize: experiments.isEnabled("emulatoruisnapshot"), namePrefix: "ui" }),
79
53
  },
80
54
  pubsub: {
81
- downloadPath: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip`),
55
+ downloadPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.pubsub.downloadPathRelativeToCacheDir),
82
56
  version: EMULATOR_UPDATE_DETAILS.pubsub.version,
83
57
  unzipDir: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`),
84
- binaryPath: path.join(CACHE_DIR, `pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}`, `pubsub-emulator/bin/cloud-pubsub-emulator${process.platform === "win32" ? ".bat" : ""}`),
85
- opts: {
86
- cacheDir: CACHE_DIR,
87
- remoteUrl: `https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-${EMULATOR_UPDATE_DETAILS.pubsub.version}.zip`,
88
- expectedSize: EMULATOR_UPDATE_DETAILS.pubsub.expectedSize,
89
- expectedChecksum: EMULATOR_UPDATE_DETAILS.pubsub.expectedChecksum,
90
- namePrefix: "pubsub-emulator",
91
- },
58
+ binaryPath: path.join(CACHE_DIR, EMULATOR_UPDATE_DETAILS.pubsub.binaryPathRelativeToCacheDir),
59
+ opts: Object.assign(Object.assign({}, EMULATOR_UPDATE_DETAILS.pubsub), { cacheDir: CACHE_DIR, namePrefix: "pubsub-emulator" }),
92
60
  },
93
61
  dataconnect: {
94
- downloadPath: path.join(CACHE_DIR, `dataconnect-emulator-${dataconnectDetails.version}${process.platform === "win32" ? ".exe" : ""}`),
62
+ downloadPath: path.join(CACHE_DIR, dataconnectDetails.downloadPathRelativeToCacheDir),
95
63
  version: dataconnectDetails.version,
96
- binaryPath: path.join(CACHE_DIR, `dataconnect-emulator-${dataconnectDetails.version}${process.platform === "win32" ? ".exe" : ""}`),
97
- opts: {
98
- cacheDir: CACHE_DIR,
99
- remoteUrl: process.platform === "darwin"
100
- ? `https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v${dataconnectDetails.version}`
101
- : process.platform === "win32"
102
- ? `https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v${dataconnectDetails.version}`
103
- : `https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v${dataconnectDetails.version}`,
104
- expectedSize: dataconnectDetails.expectedSize,
105
- expectedChecksum: dataconnectDetails.expectedChecksum,
106
- skipChecksumAndSize: false,
107
- namePrefix: "dataconnect-emulator",
108
- auth: false,
109
- },
64
+ binaryPath: path.join(CACHE_DIR, dataconnectDetails.downloadPathRelativeToCacheDir),
65
+ opts: Object.assign(Object.assign({}, dataconnectDetails), { cacheDir: CACHE_DIR, skipChecksumAndSize: false, namePrefix: "dataconnect-emulator", auth: false }),
110
66
  },
111
67
  };
112
68
  const EmulatorDetails = {
@@ -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,6 @@ 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", "");
27
26
  const emptyConnector = {
28
27
  id: "default",
29
28
  path: "./connector",
@@ -68,7 +67,7 @@ async function askQuestions(setup) {
68
67
  const shouldConfigureBackend = hasBilling &&
69
68
  requiredConfigUnset &&
70
69
  (await (0, prompt_1.confirm)({
71
- message: `Would you like to configure your backend resources now?`,
70
+ message: `Would you like to configure your Cloud SQL datasource now?`,
72
71
  default: true,
73
72
  }));
74
73
  if (shouldConfigureBackend) {
@@ -190,30 +189,7 @@ async function promptForExistingServices(setup, info) {
190
189
  return { service: s, schema: await (0, client_1.getSchema)(s.name) };
191
190
  }));
192
191
  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
- }
192
+ const choice = await chooseExistingService(existingServicesAndSchemas);
217
193
  if (choice) {
218
194
  const serviceName = (0, names_1.parseServiceName)(choice.service.name);
219
195
  info.serviceId = serviceName.serviceId;
@@ -249,6 +225,34 @@ async function promptForExistingServices(setup, info) {
249
225
  }
250
226
  return info;
251
227
  }
228
+ async function chooseExistingService(existing) {
229
+ const serviceEnvVar = (0, utils_1.envOverride)("FDC_CONNECTOR", "") || (0, utils_1.envOverride)("FDC_SERVICE", "");
230
+ if (serviceEnvVar) {
231
+ const [serviceLocationFromEnvVar, serviceIdFromEnvVar] = serviceEnvVar.split("/");
232
+ const serviceFromEnvVar = existing.find((s) => {
233
+ const serviceName = (0, names_1.parseServiceName)(s.service.name);
234
+ return (serviceName.serviceId === serviceIdFromEnvVar &&
235
+ serviceName.location === serviceLocationFromEnvVar);
236
+ });
237
+ if (serviceFromEnvVar) {
238
+ (0, utils_1.logBullet)(`Picking up the existing service ${clc.bold(serviceLocationFromEnvVar + "/" + serviceIdFromEnvVar)}.`);
239
+ return serviceFromEnvVar;
240
+ }
241
+ (0, utils_1.logWarning)(`Unable to pick up an existing service based on FDC_SERVICE=${serviceEnvVar}.`);
242
+ }
243
+ const choices = existing.map((s) => {
244
+ const serviceName = (0, names_1.parseServiceName)(s.service.name);
245
+ return {
246
+ name: `${serviceName.location}/${serviceName.serviceId}`,
247
+ value: s,
248
+ };
249
+ });
250
+ choices.push({ name: "Create a new service", value: undefined });
251
+ return await (0, prompt_1.select)({
252
+ message: "Your project already has existing services. Which would you like to set up local files for?",
253
+ choices,
254
+ });
255
+ }
252
256
  async function promptForCloudSQL(setup, info) {
253
257
  if (info.cloudSqlInstanceId === "" && setup.projectId) {
254
258
  const instances = await cloudsql.listInstances(setup.projectId);
@@ -29,7 +29,7 @@ async function askQuestions(setup, config) {
29
29
  .map((si) => {
30
30
  return si.connectorInfo.map((ci) => {
31
31
  return {
32
- name: `${si.dataConnectYaml.serviceId}/${ci.connectorYaml.connectorId}`,
32
+ name: `${si.dataConnectYaml.location}/${si.dataConnectYaml.serviceId}/${ci.connectorYaml.connectorId}`,
33
33
  value: ci,
34
34
  };
35
35
  });
@@ -68,10 +68,7 @@ async function askQuestions(setup, config) {
68
68
  else {
69
69
  (0, utils_1.logSuccess)(`Detected ${targetPlatform} app in directory ${appDir}`);
70
70
  }
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
- });
71
+ const connectorInfo = await chooseExistingConnector(connectorChoices);
75
72
  const connectorYaml = JSON.parse(JSON.stringify(connectorInfo.connectorYaml));
76
73
  const newConnectorYaml = await generateSdkYaml(targetPlatform, connectorYaml, connectorInfo.directory, appDir);
77
74
  if (targetPlatform === types_1.Platform.WEB) {
@@ -98,6 +95,24 @@ async function askQuestions(setup, config) {
98
95
  const displayIOSWarning = targetPlatform === types_1.Platform.IOS;
99
96
  return { connectorYamlContents, connectorInfo, displayIOSWarning };
100
97
  }
98
+ async function chooseExistingConnector(choices) {
99
+ if (choices.length === 1) {
100
+ return choices[0].value;
101
+ }
102
+ const connectorEnvVar = (0, utils_1.envOverride)("FDC_CONNECTOR", "");
103
+ if (connectorEnvVar) {
104
+ const existingConnector = choices.find((c) => c.name === connectorEnvVar);
105
+ if (existingConnector) {
106
+ (0, utils_1.logBullet)(`Picking up the existing connector ${clc.bold(connectorEnvVar)}.`);
107
+ return existingConnector.value;
108
+ }
109
+ (0, utils_1.logWarning)(`Unable to pick up an existing connector based on FDC_CONNECTOR=${connectorEnvVar}.`);
110
+ }
111
+ return await (0, prompt_1.select)({
112
+ message: "Which connector do you want set up a generated SDK for?",
113
+ choices: choices,
114
+ });
115
+ }
101
116
  async function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appDir) {
102
117
  if (!connectorYaml.generate) {
103
118
  connectorYaml.generate = {};
@@ -155,7 +170,7 @@ async function actuate(sdkInfo, config) {
155
170
  var _a, _b;
156
171
  const connectorYamlPath = `${sdkInfo.connectorInfo.directory}/connector.yaml`;
157
172
  (0, utils_1.logBullet)(`Writing your new SDK configuration to ${connectorYamlPath}`);
158
- await config.askWriteProjectFile(path.relative(config.projectDir, connectorYamlPath), sdkInfo.connectorYamlContents);
173
+ await config.askWriteProjectFile(path.relative(config.projectDir, connectorYamlPath), sdkInfo.connectorYamlContents, false, true);
159
174
  const account = (0, auth_1.getGlobalDefaultAccount)();
160
175
  await dataconnectEmulator_1.DataConnectEmulator.generate({
161
176
  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
  };