firebase-tools 14.19.0 → 14.20.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 (54) hide show
  1. package/lib/appUtils.js +4 -4
  2. package/lib/apphosting/localbuilds.js +23 -0
  3. package/lib/command.js +1 -0
  4. package/lib/commands/apps-init.js +7 -7
  5. package/lib/commands/dataconnect-execute.js +229 -0
  6. package/lib/commands/firestore-backups-delete.js +1 -6
  7. package/lib/commands/firestore-backups-get.js +2 -8
  8. package/lib/commands/firestore-backups-list.js +4 -10
  9. package/lib/commands/firestore-backups-schedules-create.js +1 -6
  10. package/lib/commands/firestore-backups-schedules-delete.js +1 -6
  11. package/lib/commands/firestore-backups-schedules-list.js +1 -7
  12. package/lib/commands/firestore-backups-schedules-update.js +1 -6
  13. package/lib/commands/firestore-bulkdelete.js +7 -13
  14. package/lib/commands/firestore-databases-create.js +5 -10
  15. package/lib/commands/firestore-databases-delete.js +1 -6
  16. package/lib/commands/firestore-databases-get.js +1 -7
  17. package/lib/commands/firestore-databases-list.js +1 -7
  18. package/lib/commands/firestore-databases-restore.js +5 -10
  19. package/lib/commands/firestore-databases-update.js +1 -6
  20. package/lib/commands/firestore-locations.js +1 -7
  21. package/lib/commands/firestore-operations-cancel.js +3 -9
  22. package/lib/commands/firestore-operations-describe.js +2 -8
  23. package/lib/commands/firestore-operations-list.js +2 -8
  24. package/lib/commands/index.js +1 -0
  25. package/lib/dataconnect/build.js +16 -2
  26. package/lib/dataconnect/load.js +21 -1
  27. package/lib/dataconnect/names.js +6 -1
  28. package/lib/dataconnect/provisionCloudSql.js +38 -11
  29. package/lib/dataconnect/schemaMigration.js +16 -3
  30. package/lib/dataconnect/types.js +1 -10
  31. package/lib/deploy/apphosting/deploy.js +18 -5
  32. package/lib/deploy/apphosting/prepare.js +23 -1
  33. package/lib/deploy/apphosting/release.js +5 -0
  34. package/lib/deploy/apphosting/util.js +4 -3
  35. package/lib/deploy/dataconnect/context.js +26 -0
  36. package/lib/deploy/dataconnect/deploy.js +13 -4
  37. package/lib/deploy/dataconnect/prepare.js +11 -8
  38. package/lib/deploy/dataconnect/release.js +10 -2
  39. package/lib/deploy/index.js +39 -20
  40. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  41. package/lib/gcp/cloudsql/cloudsqladmin.js +4 -3
  42. package/lib/init/features/dataconnect/index.js +22 -6
  43. package/lib/init/features/dataconnect/sdk.js +40 -22
  44. package/lib/management/apps.js +24 -24
  45. package/lib/mcp/prompts/core/consult.js +2 -3
  46. package/lib/mcp/prompts/core/init.js +3 -4
  47. package/lib/mcp/resources/index.js +0 -4
  48. package/lib/mcp/tools/dataconnect/execute.js +0 -1
  49. package/lib/mcp/util/dataconnect/converter.js +5 -4
  50. package/lib/mcp/util/dataconnect/emulator.js +0 -1
  51. package/lib/responseToError.js +7 -6
  52. package/package.json +3 -1
  53. package/schema/firebase-config.json +6 -0
  54. package/lib/dataconnect/appFinder.js +0 -103
@@ -26,6 +26,7 @@ const prepare_1 = require("./hosting/prepare");
26
26
  const github_1 = require("../init/features/hosting/github");
27
27
  const deploy_1 = require("../commands/deploy");
28
28
  const requirePermissions_1 = require("../requirePermissions");
29
+ const context_1 = require("./dataconnect/context");
29
30
  const TARGETS = {
30
31
  hosting: HostingTarget,
31
32
  database: DatabaseTarget,
@@ -63,7 +64,7 @@ const isDeployingWebFramework = (options) => {
63
64
  };
64
65
  exports.isDeployingWebFramework = isDeployingWebFramework;
65
66
  const deploy = async function (targetNames, options, customContext = {}) {
66
- var _a, _b, _c;
67
+ var _a, _b, _c, _d;
67
68
  const projectId = (0, projectUtils_1.needProjectId)(options);
68
69
  const payload = {};
69
70
  const context = Object.assign({ projectId }, customContext);
@@ -115,34 +116,52 @@ const deploy = async function (targetNames, options, customContext = {}) {
115
116
  logger_1.logger.info((0, colorette_1.bold)((0, colorette_1.white)("===") + " Deploying to '" + projectId + "'..."));
116
117
  logger_1.logger.info();
117
118
  (0, utils_1.logBullet)("deploying " + (0, colorette_1.bold)(targetNames.join(", ")));
118
- await chain(predeploys, context, options, payload);
119
- await chain(prepares, context, options, payload);
120
- await chain(deploys, context, options, payload);
121
- await chain(releases, context, options, payload);
122
- await chain(postdeploys, context, options, payload);
123
- const duration = Date.now() - startTime;
124
- const analyticsParams = {
125
- interactive: options.nonInteractive ? "false" : "true",
126
- };
127
- Object.keys(TARGETS).reduce((accum, t) => {
128
- accum[t] = "false";
129
- return accum;
130
- }, analyticsParams);
131
- for (const t of targetNames) {
132
- analyticsParams[t] = "true";
119
+ let result = "predeploys_error";
120
+ try {
121
+ await chain(predeploys, context, options, payload);
122
+ result = "prepares_error";
123
+ await chain(prepares, context, options, payload);
124
+ result = "deploys_error";
125
+ await chain(deploys, context, options, payload);
126
+ result = "releases_error";
127
+ await chain(releases, context, options, payload);
128
+ result = "postdeploys_error";
129
+ await chain(postdeploys, context, options, payload);
130
+ result = "success";
131
+ }
132
+ finally {
133
+ const baseParams = {
134
+ interactive: options.nonInteractive ? "false" : "true",
135
+ dry_run: options.dryRun ? "true" : "false",
136
+ result: result,
137
+ };
138
+ const duration = Date.now() - startTime;
139
+ const params = Object.assign({}, baseParams);
140
+ Object.keys(TARGETS).reduce((accum, t) => {
141
+ accum[t] = "false";
142
+ return accum;
143
+ }, params);
144
+ for (const t of targetNames) {
145
+ params[t] = "true";
146
+ }
147
+ void (0, track_1.trackGA4)("product_deploy", params, duration);
148
+ const stats = (_a = context === null || context === void 0 ? void 0 : context.dataconnect) === null || _a === void 0 ? void 0 : _a.deployStats;
149
+ if (stats) {
150
+ const fdcParams = (0, context_1.deployStatsParams)(stats);
151
+ void (0, track_1.trackGA4)("dataconnect_deploy", Object.assign(Object.assign({}, fdcParams), baseParams), duration);
152
+ }
133
153
  }
134
- await (0, track_1.trackGA4)("product_deploy", analyticsParams, duration);
135
154
  const successMessage = options.dryRun ? "Dry run complete!" : "Deploy complete!";
136
155
  logger_1.logger.info();
137
156
  (0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)(successMessage)));
138
157
  logger_1.logger.info();
139
158
  const deployedHosting = (0, lodash_1.includes)(targetNames, "hosting");
140
- logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)((_a = options.project) !== null && _a !== void 0 ? _a : "_", "/overview"));
159
+ logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)((_b = options.project) !== null && _b !== void 0 ? _b : "_", "/overview"));
141
160
  if (deployedHosting) {
142
- (0, lodash_1.each)((_b = context.hosting) === null || _b === void 0 ? void 0 : _b.deploys, (deploy) => {
161
+ (0, lodash_1.each)((_c = context.hosting) === null || _c === void 0 ? void 0 : _c.deploys, (deploy) => {
143
162
  logger_1.logger.info((0, colorette_1.bold)("Hosting URL:"), (0, utils_1.addSubdomain)((0, api_1.hostingOrigin)(), deploy.config.site));
144
163
  });
145
- const versionNames = (_c = context.hosting) === null || _c === void 0 ? void 0 : _c.deploys.map((deploy) => deploy.version);
164
+ const versionNames = (_d = context.hosting) === null || _d === void 0 ? void 0 : _d.deploys.map((deploy) => deploy.version);
146
165
  return { hosting: (versionNames === null || versionNames === void 0 ? void 0 : versionNames.length) === 1 ? versionNames[0] : versionNames };
147
166
  }
148
167
  else {
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.14.0",
58
- "expectedSize": 29602656,
59
- "expectedChecksum": "4570cfde37b7def2ac9ead63752dda34",
60
- "expectedChecksumSHA256": "88d3e13e860be59bc3520fe685b695be51341d13203838d48e7502c62504acb8",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.14.0",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.14.0"
57
+ "version": "2.15.0",
58
+ "expectedSize": 29610848,
59
+ "expectedChecksum": "182ddca17f4974ec081fd5a769f8eeac",
60
+ "expectedChecksumSHA256": "3bcdc39b7f149f8c2d96f397188ca003daf1b43a1ce845780146b79217970a52",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.15.0",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0"
63
63
  },
64
64
  "win32": {
65
- "version": "2.14.0",
66
- "expectedSize": 30091264,
67
- "expectedChecksum": "43338765d5f7ec2fabb6fe8f212bab82",
68
- "expectedChecksumSHA256": "1de9ce32f4958c6e1e56ca7a0a7f5f4364d6cd17de77d08f76d3638830bc3c16",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.14.0",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.14.0.exe"
65
+ "version": "2.15.0",
66
+ "expectedSize": 30099456,
67
+ "expectedChecksum": "5dd4efd368c3987925c41784e21c7af6",
68
+ "expectedChecksumSHA256": "a664f29cd21f826dd4936f8b95f0673e0ea603ab0d85a426d8231f1aaa8947f9",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.15.0",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.14.0",
74
- "expectedSize": 29524152,
75
- "expectedChecksum": "711dd1e6d97bbeabc070efcdf0a22ee4",
76
- "expectedChecksumSHA256": "bc468019bfb87b846adf02f21f65e5243854dbeee72258fe98f5e3201d1b1363",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.14.0",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.14.0"
73
+ "version": "2.15.0",
74
+ "expectedSize": 29532344,
75
+ "expectedChecksum": "38baf423fbfc9c67a89975a27f0a3974",
76
+ "expectedChecksumSHA256": "d3c5b26470639fedce9e3ac667948fa07473e77f6c2d0b361d78c5f2e856a1ec",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.15.0",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0"
79
79
  }
80
80
  }
81
81
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.listUsers = exports.deleteUser = exports.getUser = exports.createUser = exports.deleteDatabase = exports.createDatabase = exports.getDatabase = exports.listDatabases = exports.updateInstanceForDataConnect = exports.createInstance = exports.instanceConsoleLink = exports.getInstance = exports.listInstances = exports.iamUserIsCSQLAdmin = void 0;
3
+ exports.listUsers = exports.deleteUser = exports.getUser = exports.createUser = exports.deleteDatabase = exports.createDatabase = exports.getDatabase = exports.listDatabases = exports.updateInstanceForDataConnect = exports.createInstance = exports.DEFAULT_DATABASE_VERSION = exports.instanceConsoleLink = exports.getInstance = exports.listInstances = exports.iamUserIsCSQLAdmin = void 0;
4
4
  const apiv2_1 = require("../../apiv2");
5
5
  const api_1 = require("../../api");
6
6
  const clc = require("colorette");
@@ -51,6 +51,7 @@ function instanceConsoleLink(projectId, instanceId) {
51
51
  return `https://console.cloud.google.com/sql/instances/${instanceId}/overview?project=${projectId}`;
52
52
  }
53
53
  exports.instanceConsoleLink = instanceConsoleLink;
54
+ exports.DEFAULT_DATABASE_VERSION = "POSTGRES_15";
54
55
  async function createInstance(args) {
55
56
  const databaseFlags = [{ name: "cloudsql.iam_authentication", value: "on" }];
56
57
  if (args.enableGoogleMlIntegration) {
@@ -60,7 +61,7 @@ async function createInstance(args) {
60
61
  await client.post(`projects/${args.projectId}/instances`, {
61
62
  name: args.instanceId,
62
63
  region: args.location,
63
- databaseVersion: "POSTGRES_17",
64
+ databaseVersion: exports.DEFAULT_DATABASE_VERSION,
64
65
  settings: {
65
66
  tier: "db-f1-micro",
66
67
  edition: "ENTERPRISE",
@@ -70,7 +71,7 @@ async function createInstance(args) {
70
71
  enableGoogleMlIntegration: args.enableGoogleMlIntegration,
71
72
  databaseFlags,
72
73
  storageAutoResize: false,
73
- userLabels: { "firebase-data-connect": args.freeTrial ? "ft" : "nt" },
74
+ userLabels: { "firebase-data-connect": args.freeTrialLabel },
74
75
  insightsConfig: {
75
76
  queryInsightsEnabled: true,
76
77
  queryPlansPerMinute: 5,
@@ -66,12 +66,22 @@ async function askQuestions(setup) {
66
66
  if (!configstore_1.configstore.get("gemini")) {
67
67
  (0, utils_1.logBullet)("Learn more about Gemini in Firebase and how it uses your data: https://firebase.google.com/docs/gemini-in-firebase#how-gemini-in-firebase-uses-your-data");
68
68
  }
69
- info.appDescription = await (0, prompt_1.input)({
70
- message: `Describe your app to automatically generate a schema with Gemini [Enter to use a template]:`,
69
+ const wantToGenerate = await (0, prompt_1.confirm)({
70
+ message: "Do you want to generate schema and queries with Gemini?",
71
+ default: false,
71
72
  });
72
- if (info.appDescription) {
73
+ if (wantToGenerate) {
73
74
  configstore_1.configstore.set("gemini", true);
74
75
  await (0, ensureApis_1.ensureGIFApiTos)(setup.projectId);
76
+ info.appDescription = await (0, prompt_1.input)({
77
+ message: `Describe your app idea:`,
78
+ validate: async (s) => {
79
+ if (s.length > 0) {
80
+ return true;
81
+ }
82
+ return "Please enter a description for your app idea.";
83
+ },
84
+ });
75
85
  }
76
86
  }
77
87
  if (hasBilling) {
@@ -105,9 +115,14 @@ async function actuate(setup, config, options) {
105
115
  }
106
116
  finally {
107
117
  void (0, track_1.trackGA4)("dataconnect_init", {
108
- project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
109
118
  flow: info.analyticsFlow,
110
- provision_cloud_sql: String(info.shouldProvisionCSQL),
119
+ project_status: setup.projectId
120
+ ? setup.isBillingEnabled
121
+ ? info.shouldProvisionCSQL
122
+ ? "blaze_provisioned_csql"
123
+ : "blaze"
124
+ : "spark"
125
+ : "missing",
111
126
  });
112
127
  }
113
128
  if (info.appDescription) {
@@ -136,6 +151,7 @@ async function actuateWithInfo(setup, config, info, options) {
136
151
  instanceId: info.cloudSqlInstanceId,
137
152
  databaseId: info.cloudSqlDatabase,
138
153
  requireGoogleMlIntegration: false,
154
+ source: info.analyticsFlow.startsWith("mcp") ? "mcp_init" : "init",
139
155
  });
140
156
  }
141
157
  const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
@@ -448,7 +464,7 @@ async function promptForLocation(setup, info) {
448
464
  if (info.locationId === "") {
449
465
  const choices = await locationChoices(setup);
450
466
  info.locationId = await (0, prompt_1.select)({
451
- message: "What location should the new Cloud SQL instance be in?",
467
+ message: "What location would you like to use?",
452
468
  choices,
453
469
  default: exports.FDC_DEFAULT_REGION,
454
470
  });
@@ -1,17 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addSdkGenerateToConnectorYaml = exports.actuate = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
3
+ exports.addSdkGenerateToConnectorYaml = exports.actuate = exports.chooseApp = exports.askQuestions = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
4
4
  const yaml = require("yaml");
5
5
  const clc = require("colorette");
6
6
  const path = require("path");
7
7
  const cwd = process.cwd();
8
8
  const prompt_1 = require("../../../prompt");
9
- const appFinder_1 = require("../../../dataconnect/appFinder");
10
9
  const load_1 = require("../../../dataconnect/load");
11
- const types_1 = require("../../../dataconnect/types");
12
10
  const error_1 = require("../../../error");
13
11
  const lodash_1 = require("lodash");
14
12
  const utils_1 = require("../../../utils");
13
+ const appUtils_1 = require("../../../appUtils");
15
14
  const dataconnectEmulator_1 = require("../../../emulator/dataconnectEmulator");
16
15
  const auth_1 = require("../../../auth");
17
16
  const create_app_1 = require("./create_app");
@@ -65,19 +64,20 @@ async function askQuestions(setup) {
65
64
  }
66
65
  exports.askQuestions = askQuestions;
67
66
  async function chooseApp() {
68
- let apps = await (0, appFinder_1.detectApps)(cwd);
67
+ let apps = dedupeAppsByPlatformAndDirectory(await (0, appUtils_1.detectApps)(cwd));
69
68
  if (apps.length) {
70
- (0, utils_1.logLabeledSuccess)("dataconnect", `Detected existing apps ${apps.map((a) => (0, appFinder_1.appDescription)(a)).join(", ")}`);
69
+ (0, utils_1.logLabeledSuccess)("dataconnect", `Detected existing apps ${apps.map((a) => (0, appUtils_1.appDescription)(a)).join(", ")}`);
71
70
  }
72
71
  else {
73
72
  (0, utils_1.logLabeledWarning)("dataconnect", "No app exists in the current directory.");
74
73
  }
75
74
  const envAppFolder = (0, utils_1.envOverride)(exports.FDC_APP_FOLDER, "");
76
- const envPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, types_1.Platform.NONE);
75
+ const envPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, "");
77
76
  const envFrameworks = (0, utils_1.envOverride)(exports.FDC_SDK_FRAMEWORKS_ENV, "")
78
77
  .split(",")
78
+ .filter((f) => !!f)
79
79
  .map((f) => f);
80
- if (envAppFolder && envPlatform !== types_1.Platform.NONE) {
80
+ if (envAppFolder && envPlatform) {
81
81
  const envAppRelDir = path.relative(cwd, path.resolve(cwd, envAppFolder));
82
82
  const matchedApps = apps.filter((app) => app.directory === envAppRelDir && (!app.platform || app.platform === envPlatform));
83
83
  if (matchedApps.length) {
@@ -97,7 +97,7 @@ async function chooseApp() {
97
97
  if (apps.length >= 2) {
98
98
  const choices = apps.map((a) => {
99
99
  return {
100
- name: (0, appFinder_1.appDescription)(a),
100
+ name: (0, appUtils_1.appDescription)(a),
101
101
  value: a,
102
102
  checked: a.directory === ".",
103
103
  };
@@ -106,13 +106,14 @@ async function chooseApp() {
106
106
  message: "Which apps do you want to set up Data Connect SDKs in?",
107
107
  choices,
108
108
  });
109
- if (!pickedApps.length) {
109
+ if (!pickedApps || !pickedApps.length) {
110
110
  throw new error_1.FirebaseError("Command Aborted. Please choose at least one app.");
111
111
  }
112
112
  apps = pickedApps;
113
113
  }
114
114
  return apps;
115
115
  }
116
+ exports.chooseApp = chooseApp;
116
117
  async function actuate(setup, config) {
117
118
  var _a, _b;
118
119
  const fdcInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
@@ -143,13 +144,13 @@ async function actuate(setup, config) {
143
144
  exports.actuate = actuate;
144
145
  async function actuateWithInfo(setup, config, info) {
145
146
  if (!info.apps.length) {
146
- info.apps = await (0, appFinder_1.detectApps)(cwd);
147
+ info.apps = await (0, appUtils_1.detectApps)(cwd);
147
148
  if (!info.apps.length) {
148
149
  (0, utils_1.logLabeledBullet)("dataconnect", "No apps to setup Data Connect Generated SDKs");
149
150
  return;
150
151
  }
151
152
  }
152
- const apps = info.apps;
153
+ const apps = dedupeAppsByPlatformAndDirectory(info.apps);
153
154
  const connectorInfo = await chooseExistingConnector(setup, config);
154
155
  const connectorYaml = JSON.parse(JSON.stringify(connectorInfo.connectorYaml));
155
156
  for (const app of apps) {
@@ -173,14 +174,14 @@ async function actuateWithInfo(setup, config, info) {
173
174
  catch (e) {
174
175
  (0, utils_1.logLabeledError)("dataconnect", `Failed to generate Data Connect SDKs\n${e === null || e === void 0 ? void 0 : e.message}`);
175
176
  }
176
- (0, utils_1.logLabeledSuccess)("dataconnect", `Installed generated SDKs for ${clc.bold(apps.map((a) => (0, appFinder_1.appDescription)(a)).join(", "))}`);
177
- if (apps.some((a) => a.platform === types_1.Platform.IOS)) {
177
+ (0, utils_1.logLabeledSuccess)("dataconnect", `Installed generated SDKs for ${clc.bold(apps.map((a) => (0, appUtils_1.appDescription)(a)).join(", "))}`);
178
+ if (apps.some((a) => a.platform === appUtils_1.Platform.IOS)) {
178
179
  (0, utils_1.logBullet)(clc.bold("Please follow the instructions here to add your generated sdk to your XCode project:\n\thttps://firebase.google.com/docs/data-connect/ios-sdk#set-client"));
179
180
  }
180
- if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes("react"); })) {
181
+ if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes(appUtils_1.Framework.REACT); })) {
181
182
  (0, utils_1.logBullet)("Visit https://firebase.google.com/docs/data-connect/web-sdk#react for more information on how to set up React Generated SDKs for Firebase Data Connect");
182
183
  }
183
- if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes("angular"); })) {
184
+ if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes(appUtils_1.Framework.ANGULAR); })) {
184
185
  (0, utils_1.logBullet)("Run `ng add @angular/fire` to install angular sdk dependencies.\nVisit https://github.com/invertase/tanstack-query-firebase/tree/main/packages/angular for more information on how to set up Angular Generated SDKs for Firebase Data Connect");
185
186
  }
186
187
  }
@@ -222,7 +223,7 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
222
223
  }
223
224
  const generate = connectorYaml.generate;
224
225
  switch (app.platform) {
225
- case types_1.Platform.WEB: {
226
+ case appUtils_1.Platform.WEB: {
226
227
  const javascriptSdk = {
227
228
  outputDir: path.relative(connectorDir, path.join(appDir, `src/dataconnect-generated`)),
228
229
  package: `@dataconnect/generated`,
@@ -241,7 +242,7 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
241
242
  }
242
243
  break;
243
244
  }
244
- case types_1.Platform.FLUTTER: {
245
+ case appUtils_1.Platform.FLUTTER: {
245
246
  const dartSdk = {
246
247
  outputDir: path.relative(connectorDir, path.join(appDir, `lib/dataconnect_generated`)),
247
248
  package: "dataconnect_generated",
@@ -254,7 +255,7 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
254
255
  }
255
256
  break;
256
257
  }
257
- case types_1.Platform.ANDROID: {
258
+ case appUtils_1.Platform.ANDROID: {
258
259
  const kotlinSdk = {
259
260
  outputDir: path.relative(connectorDir, path.join(appDir, `src/main/kotlin`)),
260
261
  package: `com.google.firebase.dataconnect.generated`,
@@ -267,7 +268,7 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
267
268
  }
268
269
  break;
269
270
  }
270
- case types_1.Platform.IOS: {
271
+ case appUtils_1.Platform.IOS: {
271
272
  const swiftSdk = {
272
273
  outputDir: path.relative(connectorDir, path.join(app.directory, `../FirebaseDataConnectGenerated`)),
273
274
  package: "DataConnectGenerated",
@@ -281,9 +282,26 @@ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
281
282
  break;
282
283
  }
283
284
  default:
284
- throw new error_1.FirebaseError(`Unsupported platform ${app.platform} for Data Connect SDK generation. Supported platforms are: ${Object.values(types_1.Platform)
285
- .filter((p) => p !== types_1.Platform.NONE && p !== types_1.Platform.MULTIPLE)
286
- .join(", ")}\n${JSON.stringify(app)}`);
285
+ throw new error_1.FirebaseError(`Unsupported platform ${app.platform} for Data Connect SDK generation. Supported platforms are: ${Object.values(appUtils_1.Platform).join(", ")}\n${JSON.stringify(app)}`);
287
286
  }
288
287
  }
289
288
  exports.addSdkGenerateToConnectorYaml = addSdkGenerateToConnectorYaml;
289
+ function dedupeAppsByPlatformAndDirectory(apps) {
290
+ var _a;
291
+ const uniqueApps = new Map();
292
+ for (const app of apps) {
293
+ const frameworkKey = app.frameworks ? [...app.frameworks].sort().join(",") : "";
294
+ const key = `${app.platform}:${app.directory}:${frameworkKey}`;
295
+ if (!uniqueApps.has(key)) {
296
+ const minifiedApp = {
297
+ platform: app.platform,
298
+ directory: app.directory,
299
+ };
300
+ if ((_a = app.frameworks) === null || _a === void 0 ? void 0 : _a.length) {
301
+ minifiedApp.frameworks = [...app.frameworks];
302
+ }
303
+ uniqueApps.set(key, minifiedApp);
304
+ }
305
+ }
306
+ return Array.from(uniqueApps.values());
307
+ }
@@ -9,46 +9,49 @@ const api_1 = require("../api");
9
9
  const error_1 = require("../error");
10
10
  const logger_1 = require("../logger");
11
11
  const operation_poller_1 = require("../operation-poller");
12
- const types_1 = require("../dataconnect/types");
13
12
  const projectUtils_1 = require("../projectUtils");
14
- const prompt = require("../prompt");
13
+ const prompt_1 = require("../prompt");
15
14
  const projects_1 = require("./projects");
16
- const appFinder_1 = require("../dataconnect/appFinder");
15
+ const appUtils_1 = require("../appUtils");
17
16
  const utils_1 = require("../utils");
18
17
  const TIMEOUT_MILLIS = 30000;
19
18
  exports.APP_LIST_PAGE_SIZE = 100;
20
19
  const CREATE_APP_API_REQUEST_TIMEOUT_MILLIS = 15000;
21
20
  async function getDisplayName() {
22
- return await prompt.input("What would you like to call your app?");
21
+ return await (0, prompt_1.input)("What would you like to call your app?");
23
22
  }
24
23
  async function getPlatform(appDir, config) {
25
- let targetPlatform = await (0, appFinder_1.getPlatformFromFolder)(appDir);
26
- if (targetPlatform === types_1.Platform.NONE) {
24
+ let targetPlatforms = await (0, appUtils_1.getPlatformsFromFolder)(appDir);
25
+ let targetPlatform;
26
+ if (targetPlatforms.length === 0) {
27
27
  appDir = await (0, utils_1.promptForDirectory)({
28
28
  config,
29
29
  relativeTo: appDir,
30
30
  message: "We couldn't determine what kind of app you're using. Where is your app directory?",
31
31
  });
32
- targetPlatform = await (0, appFinder_1.getPlatformFromFolder)(appDir);
32
+ targetPlatforms = await (0, appUtils_1.getPlatformsFromFolder)(appDir);
33
33
  }
34
- if (targetPlatform === types_1.Platform.NONE || targetPlatform === types_1.Platform.MULTIPLE) {
35
- if (targetPlatform === types_1.Platform.NONE) {
36
- (0, utils_1.logBullet)(`Couldn't automatically detect app your in directory ${appDir}.`);
34
+ if (targetPlatforms.length !== 1) {
35
+ if (targetPlatforms.length === 0) {
36
+ (0, utils_1.logBullet)(`Couldn't automatically detect your app in directory ${appDir}.`);
37
37
  }
38
38
  else {
39
39
  (0, utils_1.logSuccess)(`Detected multiple app platforms in directory ${appDir}`);
40
40
  }
41
41
  const platforms = [
42
- { name: "iOS (Swift)", value: types_1.Platform.IOS },
43
- { name: "Web (JavaScript)", value: types_1.Platform.WEB },
44
- { name: "Android (Kotlin)", value: types_1.Platform.ANDROID },
42
+ { name: "iOS (Swift)", value: appUtils_1.Platform.IOS },
43
+ { name: "Web (JavaScript)", value: appUtils_1.Platform.WEB },
44
+ { name: "Android (Kotlin)", value: appUtils_1.Platform.ANDROID },
45
45
  ];
46
- targetPlatform = await prompt.select({
46
+ targetPlatform = await (0, prompt_1.select)({
47
47
  message: "Which platform do you want to set up an SDK for? Note: We currently do not support automatically setting up C++ or Unity projects.",
48
48
  choices: platforms,
49
49
  });
50
50
  }
51
- else if (targetPlatform === types_1.Platform.FLUTTER) {
51
+ else {
52
+ targetPlatform = targetPlatforms[0];
53
+ }
54
+ if (targetPlatform === appUtils_1.Platform.FLUTTER) {
52
55
  (0, utils_1.logWarning)(`Detected ${targetPlatform} app in directory ${appDir}`);
53
56
  throw new error_1.FirebaseError(`Flutter is not supported by apps:configure.
54
57
  Please follow the link below to set up firebase for your Flutter app:
@@ -58,18 +61,15 @@ https://firebase.google.com/docs/flutter/setup
58
61
  else {
59
62
  (0, utils_1.logSuccess)(`Detected ${targetPlatform} app in directory ${appDir}`);
60
63
  }
61
- return targetPlatform === types_1.Platform.MULTIPLE
62
- ? AppPlatform.PLATFORM_UNSPECIFIED
63
- : targetPlatform;
64
+ return targetPlatform;
64
65
  }
65
66
  exports.getPlatform = getPlatform;
66
67
  async function initiateIosAppCreation(options) {
67
68
  if (!options.nonInteractive) {
68
69
  options.displayName = options.displayName || (await getDisplayName());
69
- options.bundleId =
70
- options.bundleId || (await prompt.input("Please specify your iOS app bundle ID:"));
70
+ options.bundleId = options.bundleId || (await (0, prompt_1.input)("Please specify your iOS app bundle ID:"));
71
71
  options.appStoreId =
72
- options.appStoreId || (await prompt.input("Please specify your iOS app App Store ID:"));
72
+ options.appStoreId || (await (0, prompt_1.input)("Please specify your iOS app App Store ID:"));
73
73
  }
74
74
  if (!options.bundleId) {
75
75
  throw new error_1.FirebaseError("Bundle ID for iOS app cannot be empty");
@@ -93,7 +93,7 @@ async function initiateAndroidAppCreation(options) {
93
93
  if (!options.nonInteractive) {
94
94
  options.displayName = options.displayName || (await getDisplayName());
95
95
  options.packageName =
96
- options.packageName || (await prompt.input("Please specify your Android app package name:"));
96
+ options.packageName || (await (0, prompt_1.input)("Please specify your Android app package name:"));
97
97
  }
98
98
  if (!options.packageName) {
99
99
  throw new error_1.FirebaseError("Package name for Android app cannot be empty");
@@ -179,7 +179,7 @@ async function selectAppInteractively(apps, appPlatform) {
179
179
  value: app,
180
180
  };
181
181
  });
182
- return await prompt.select({
182
+ return await (0, prompt_1.select)({
183
183
  message: `Select the ${appPlatform === AppPlatform.ANY ? "" : appPlatform + " "}` +
184
184
  "app to get the configuration data:",
185
185
  choices,
@@ -416,7 +416,7 @@ async function writeConfigToFile(filename, nonInteractive, fileContents) {
416
416
  if (nonInteractive) {
417
417
  throw new error_1.FirebaseError(`${filename} already exists`);
418
418
  }
419
- const overwrite = await prompt.confirm(`${filename} already exists. Do you want to overwrite?`);
419
+ const overwrite = await (0, prompt_1.confirm)(`${filename} already exists. Do you want to overwrite?`);
420
420
  if (!overwrite) {
421
421
  return false;
422
422
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.consult = void 0;
4
- const appFinder_1 = require("../../../dataconnect/appFinder");
4
+ const appUtils_1 = require("../../../appUtils");
5
5
  const fdcExperience_1 = require("../../../gemini/fdcExperience");
6
6
  const errors_1 = require("../../errors");
7
7
  const prompt_1 = require("../../prompt");
@@ -32,8 +32,7 @@ exports.consult = (0, prompt_1.prompt)({
32
32
  },
33
33
  ];
34
34
  }
35
- const apps = await (0, appFinder_1.detectApps)(config.projectDir);
36
- const platforms = apps.map((a) => a.platform);
35
+ const platforms = await (0, appUtils_1.getPlatformsFromFolder)(config.projectDir);
37
36
  const gifPrompt = `I am using a coding agent to build with Firebase and I have a specific question that I would like answered. Provide a robust and detailed response that will help the coding agent act on my behalf in a local workspace.
38
37
 
39
38
  App Platform(s): ${platforms.join(", ")}
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.init = void 0;
4
- const appFinder_1 = require("../../../dataconnect/appFinder");
5
- const types_1 = require("../../../dataconnect/types");
4
+ const appUtils_1 = require("../../../appUtils");
6
5
  const prompt_1 = require("../../prompt");
7
6
  exports.init = (0, prompt_1.prompt)({
8
7
  name: "init",
@@ -12,7 +11,7 @@ exports.init = (0, prompt_1.prompt)({
12
11
  },
13
12
  }, async (_, mcp) => {
14
13
  const { config, projectId, accountEmail, firebaseCliCommand } = mcp;
15
- const platform = await (0, appFinder_1.getPlatformFromFolder)(config.projectDir);
14
+ const platforms = await (0, appUtils_1.getPlatformsFromFolder)(config.projectDir);
16
15
  return [
17
16
  {
18
17
  role: "user",
@@ -29,7 +28,7 @@ Your goal is to help the user setup Firebase services in this workspace. Firebas
29
28
 
30
29
  Use this information to determine which Firebase services the user is already using (if any).
31
30
 
32
- Workspace platform: ${[types_1.Platform.NONE, types_1.Platform.MULTIPLE].includes(platform) ? "<UNABLE TO DETECT>" : platform}
31
+ Workspace platform(s): ${platforms.length > 0 ? platforms.join(", ") : "<UNABLE TO DETECT>"}
33
32
  Active user: ${accountEmail || "<NONE>"}
34
33
  Active project: ${projectId || "<NONE>"}
35
34
 
@@ -5,19 +5,15 @@ const docs_1 = require("./docs");
5
5
  const init_ai_1 = require("./guides/init_ai");
6
6
  const init_auth_1 = require("./guides/init_auth");
7
7
  const init_backend_1 = require("./guides/init_backend");
8
- const init_data_connect_1 = require("./guides/init_data_connect");
9
8
  const init_firestore_1 = require("./guides/init_firestore");
10
9
  const init_firestore_rules_1 = require("./guides/init_firestore_rules");
11
10
  const init_hosting_1 = require("./guides/init_hosting");
12
- const init_rtdb_1 = require("./guides/init_rtdb");
13
11
  const track_1 = require("../../track");
14
12
  exports.resources = [
15
13
  init_backend_1.init_backend,
16
14
  init_ai_1.init_ai,
17
- init_data_connect_1.init_data_connect,
18
15
  init_firestore_1.init_firestore,
19
16
  init_firestore_rules_1.init_firestore_rules,
20
- init_rtdb_1.init_rtdb,
21
17
  init_auth_1.init_auth,
22
18
  init_hosting_1.init_hosting,
23
19
  ];
@@ -56,7 +56,6 @@ You can find candidate service_id in \`dataconnect.yaml\`
56
56
  executeGraphQL = dataplane.executeGraphQLRead;
57
57
  }
58
58
  const response = await executeGraphQL(apiClient, serviceInfo.serviceName, {
59
- name: "",
60
59
  query,
61
60
  variables: (0, converter_1.parseVariables)(unparsedVariables),
62
61
  extensions: {
@@ -49,14 +49,15 @@ function graphqlResponseToToolResponse(g) {
49
49
  }
50
50
  exports.graphqlResponseToToolResponse = graphqlResponseToToolResponse;
51
51
  function parseVariables(unparsedVariables) {
52
+ let obj;
52
53
  try {
53
- const variables = JSON.parse(unparsedVariables || "{}");
54
- if (typeof variables !== "object")
55
- throw new Error("not an object");
56
- return variables;
54
+ obj = JSON.parse(unparsedVariables || "{}");
57
55
  }
58
56
  catch (e) {
59
57
  throw new Error("Provided variables string `" + unparsedVariables + "` is not valid JSON.");
60
58
  }
59
+ if (typeof obj !== "object" || obj == null)
60
+ throw new Error("not an object");
61
+ return obj;
61
62
  }
62
63
  exports.parseVariables = parseVariables;
@@ -9,7 +9,6 @@ async function getDataConnectEmulatorClient(host) {
9
9
  const apiClient = new apiv2_1.Client({
10
10
  urlPrefix: emulatorUrl,
11
11
  apiVersion: dataplaneClient_1.DATACONNECT_API_VERSION,
12
- auth: false,
13
12
  });
14
13
  return apiClient;
15
14
  }