firebase-tools 14.14.0 → 14.15.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 (79) hide show
  1. package/lib/apphosting/backend.js +40 -15
  2. package/lib/auth.js +37 -2
  3. package/lib/command.js +19 -5
  4. package/lib/commands/apphosting-backends-create.js +8 -5
  5. package/lib/commands/dataconnect-sdk-generate.js +3 -5
  6. package/lib/commands/dataconnect-sql-diff.js +2 -2
  7. package/lib/commands/dataconnect-sql-grant.js +2 -2
  8. package/lib/commands/dataconnect-sql-migrate.js +2 -2
  9. package/lib/commands/dataconnect-sql-setup.js +2 -2
  10. package/lib/commands/dataconnect-sql-shell.js +2 -2
  11. package/lib/commands/init.js +11 -0
  12. package/lib/commands/login.js +12 -7
  13. package/lib/crashlytics/addNote.js +27 -0
  14. package/lib/crashlytics/deleteNote.js +23 -0
  15. package/lib/crashlytics/getIssueDetails.js +5 -20
  16. package/lib/crashlytics/getSampleCrash.js +6 -20
  17. package/lib/crashlytics/listNotes.js +29 -0
  18. package/lib/crashlytics/listTopDevices.js +33 -0
  19. package/lib/crashlytics/listTopIssues.js +8 -23
  20. package/lib/crashlytics/listTopOperatingSystems.js +32 -0
  21. package/lib/crashlytics/listTopVersions.js +32 -0
  22. package/lib/crashlytics/updateIssue.js +35 -0
  23. package/lib/crashlytics/utils.js +38 -0
  24. package/lib/dataconnect/appFinder.js +103 -0
  25. package/lib/dataconnect/load.js +105 -6
  26. package/lib/deploy/dataconnect/prepare.js +1 -3
  27. package/lib/emulator/controller.js +2 -2
  28. package/lib/emulator/downloadableEmulatorInfo.json +17 -17
  29. package/lib/experiments.js +5 -0
  30. package/lib/init/features/dataconnect/create_app.js +48 -0
  31. package/lib/init/features/dataconnect/index.js +19 -45
  32. package/lib/init/features/dataconnect/sdk.js +218 -161
  33. package/lib/init/features/index.js +3 -3
  34. package/lib/init/index.js +5 -2
  35. package/lib/management/apps.js +3 -3
  36. package/lib/mcp/prompts/core/deploy.js +51 -8
  37. package/lib/mcp/prompts/crashlytics/common.js +10 -0
  38. package/lib/mcp/prompts/crashlytics/fix_issue.js +89 -0
  39. package/lib/mcp/prompts/crashlytics/index.js +6 -0
  40. package/lib/mcp/prompts/crashlytics/prioritize_issues.js +79 -0
  41. package/lib/mcp/prompts/dataconnect/index.js +9 -0
  42. package/lib/mcp/prompts/dataconnect/schema.js +68 -0
  43. package/lib/mcp/prompts/index.js +5 -3
  44. package/lib/mcp/tools/core/init.js +16 -3
  45. package/lib/mcp/tools/crashlytics/add_note.js +32 -0
  46. package/lib/mcp/tools/crashlytics/constants.js +11 -0
  47. package/lib/mcp/tools/crashlytics/delete_note.js +35 -0
  48. package/lib/mcp/tools/crashlytics/get_issue_details.js +2 -4
  49. package/lib/mcp/tools/crashlytics/get_sample_crash.js +4 -4
  50. package/lib/mcp/tools/crashlytics/index.js +16 -2
  51. package/lib/mcp/tools/crashlytics/list_notes.js +37 -0
  52. package/lib/mcp/tools/crashlytics/list_top_devices.js +33 -0
  53. package/lib/mcp/tools/crashlytics/list_top_issues.js +5 -7
  54. package/lib/mcp/tools/crashlytics/list_top_operating_systems.js +33 -0
  55. package/lib/mcp/tools/crashlytics/list_top_versions.js +33 -0
  56. package/lib/mcp/tools/crashlytics/update_issue.js +37 -0
  57. package/lib/mcp/tools/dataconnect/compile.js +43 -0
  58. package/lib/mcp/tools/dataconnect/execute_graphql.js +4 -4
  59. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +4 -4
  60. package/lib/mcp/tools/dataconnect/execute_mutation.js +4 -4
  61. package/lib/mcp/tools/dataconnect/execute_query.js +4 -4
  62. package/lib/mcp/tools/dataconnect/generate_operation.js +2 -2
  63. package/lib/mcp/tools/dataconnect/get_connector.js +3 -3
  64. package/lib/mcp/tools/dataconnect/get_schema.js +3 -3
  65. package/lib/mcp/tools/dataconnect/index.js +2 -0
  66. package/lib/mcp/tools/firestore/list_collections.js +0 -3
  67. package/lib/mcp/tools/index.js +3 -0
  68. package/lib/mcp/util/dataconnect/compile.js +18 -0
  69. package/lib/mcp/util/dataconnect/content.js +655 -0
  70. package/lib/utils.js +11 -1
  71. package/package.json +1 -1
  72. package/templates/init/dataconnect/connector.yaml +0 -16
  73. package/templates/init/dataconnect/dataconnect.yaml +2 -1
  74. package/templates/init/dataconnect/mutations.gql +29 -29
  75. package/templates/init/dataconnect/queries.gql +73 -73
  76. package/templates/init/dataconnect/schema.gql +48 -48
  77. package/lib/dataconnect/fileUtils.js +0 -168
  78. /package/lib/mcp/{tools → util}/dataconnect/converter.js +0 -0
  79. /package/lib/mcp/{tools → util}/dataconnect/emulator.js +0 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toDNSCompatibleId = exports.postSetup = exports.actuate = exports.askQuestions = void 0;
3
+ exports.newUniqueId = exports.toDNSCompatibleId = exports.actuate = exports.askQuestions = void 0;
4
4
  const path_1 = require("path");
5
5
  const clc = require("colorette");
6
6
  const fs = require("fs-extra");
@@ -15,9 +15,9 @@ const names_1 = require("../../../dataconnect/names");
15
15
  const logger_1 = require("../../../logger");
16
16
  const templates_1 = require("../../../templates");
17
17
  const utils_1 = require("../../../utils");
18
+ Object.defineProperty(exports, "newUniqueId", { enumerable: true, get: function () { return utils_1.newUniqueId; } });
18
19
  const cloudbilling_1 = require("../../../gcp/cloudbilling");
19
20
  const sdk = require("./sdk");
20
- const fileUtils_1 = require("../../../dataconnect/fileUtils");
21
21
  const fdcExperience_1 = require("../../../gemini/fdcExperience");
22
22
  const configstore_1 = require("../../../configstore");
23
23
  const track_1 = require("../../../track");
@@ -64,7 +64,7 @@ async function askQuestions(setup) {
64
64
  (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");
65
65
  }
66
66
  info.appDescription = await (0, prompt_1.input)({
67
- message: `Describe your app to automatically generate a schema [Enter to skip]:`,
67
+ message: `Describe your app to automatically generate a schema with Gemini [Enter to skip]:`,
68
68
  });
69
69
  if (info.appDescription) {
70
70
  configstore_1.configstore.set("gemini", true);
@@ -77,6 +77,7 @@ async function askQuestions(setup) {
77
77
  }
78
78
  setup.featureInfo = setup.featureInfo || {};
79
79
  setup.featureInfo.dataconnect = info;
80
+ await sdk.askQuestions(setup);
80
81
  }
81
82
  exports.askQuestions = askQuestions;
82
83
  async function actuate(setup, config, options) {
@@ -94,6 +95,7 @@ async function actuate(setup, config, options) {
94
95
  info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
95
96
  try {
96
97
  await actuateWithInfo(setup, config, info, options);
98
+ await sdk.actuate(setup, config);
97
99
  }
98
100
  finally {
99
101
  void (0, track_1.trackGA4)("dataconnect_init", {
@@ -101,6 +103,15 @@ async function actuate(setup, config, options) {
101
103
  flow: info.analyticsFlow,
102
104
  });
103
105
  }
106
+ if (info.appDescription) {
107
+ setup.instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
108
+
109
+ https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
110
+ }
111
+ if (!setup.isBillingEnabled) {
112
+ setup.instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
113
+ }
114
+ setup.instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
104
115
  }
105
116
  exports.actuate = actuate;
106
117
  async function actuateWithInfo(setup, config, info, options) {
@@ -220,34 +231,6 @@ function schemasDeploySequence(projectId, info, schemaFiles, linkToCloudSql) {
220
231
  },
221
232
  ];
222
233
  }
223
- async function postSetup(setup, config, options) {
224
- var _a;
225
- const info = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
226
- if (!info) {
227
- throw new Error("Data Connect feature RequiredInfo is not provided");
228
- }
229
- const instructions = [];
230
- const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
231
- if (cwdPlatformGuess !== types_1.Platform.NONE || (0, utils_1.envOverride)("FDC_CONNECTOR", "")) {
232
- await sdk.doSetup(setup, config, options);
233
- }
234
- else {
235
- instructions.push(`To add the generated SDK to your app, run ${clc.bold("firebase init dataconnect:sdk")}`);
236
- }
237
- if (info.appDescription) {
238
- instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
239
-
240
- https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
241
- }
242
- if (setup.projectId && !setup.isBillingEnabled) {
243
- instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId));
244
- }
245
- logger_1.logger.info(`\n${clc.bold("To get started with Firebase Data Connect:")}`);
246
- for (const i of instructions) {
247
- (0, utils_1.logBullet)(i + "\n");
248
- }
249
- }
250
- exports.postSetup = postSetup;
251
234
  async function writeFiles(config, info, serviceGql, options) {
252
235
  const dir = config.get("dataconnect.source") || "dataconnect";
253
236
  const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: serviceGql.connectors.map((c) => c.path) }));
@@ -271,7 +254,7 @@ async function writeFiles(config, info, serviceGql, options) {
271
254
  async function writeConnectorFiles(config, connectorInfo, options) {
272
255
  const subbedConnectorYaml = subConnectorYamlValues({ connectorId: connectorInfo.id });
273
256
  const dir = config.get("dataconnect.source") || "dataconnect";
274
- await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force);
257
+ await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force, true);
275
258
  for (const f of connectorInfo.files) {
276
259
  await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content, !!options.force);
277
260
  }
@@ -315,7 +298,7 @@ async function promptForExistingServices(setup, info) {
315
298
  const choice = await chooseExistingService(existingServicesAndSchemas);
316
299
  if (!choice) {
317
300
  const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
318
- info.serviceId = newUniqueId(defaultServiceId(), existingServiceIds);
301
+ info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
319
302
  info.analyticsFlow += "_pick_new_service";
320
303
  return;
321
304
  }
@@ -346,7 +329,7 @@ async function promptForExistingServices(setup, info) {
346
329
  const id = c.name.split("/").pop();
347
330
  return {
348
331
  id,
349
- path: connectors.length === 1 ? "./connector" : `./${id}`,
332
+ path: connectors.length === 1 ? "./example" : `./${id}`,
350
333
  files: c.source.files || [],
351
334
  };
352
335
  });
@@ -419,7 +402,7 @@ async function promptForCloudSQL(setup, info) {
419
402
  info.analyticsFlow += "_pick_new_csql";
420
403
  info.cloudSqlInstanceId = await (0, prompt_1.input)({
421
404
  message: `What ID would you like to use for your new CloudSQL instance?`,
422
- default: newUniqueId(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
405
+ default: (0, utils_1.newUniqueId)(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
423
406
  });
424
407
  }
425
408
  }
@@ -436,7 +419,7 @@ async function promptForCloudSQL(setup, info) {
436
419
  try {
437
420
  const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
438
421
  const existing = dbs.map((d) => d.name);
439
- info.cloudSqlDatabase = newUniqueId("fdcdb", existing);
422
+ info.cloudSqlDatabase = (0, utils_1.newUniqueId)("fdcdb", existing);
440
423
  }
441
424
  catch (err) {
442
425
  logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
@@ -464,15 +447,6 @@ async function locationChoices(setup) {
464
447
  ];
465
448
  }
466
449
  }
467
- function newUniqueId(recommended, existingIDs) {
468
- let id = recommended;
469
- let i = 1;
470
- while (existingIDs.includes(id)) {
471
- id = `${recommended}-${i}`;
472
- i++;
473
- }
474
- return id;
475
- }
476
450
  function defaultServiceId() {
477
451
  return toDNSCompatibleId((0, path_1.basename)(process.cwd()));
478
452
  }
@@ -1,116 +1,187 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.actuate = exports.generateSdkYaml = exports.doSetup = exports.FDC_SDK_PLATFORM_ENV = exports.FDC_SDK_FRAMEWORKS_ENV = exports.FDC_APP_FOLDER = void 0;
3
+ exports.addSdkGenerateToConnectorYaml = exports.actuate = 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
- const fsutils_1 = require("../../../fsutils");
7
+ const cwd = process.cwd();
8
8
  const prompt_1 = require("../../../prompt");
9
- const fileUtils_1 = require("../../../dataconnect/fileUtils");
9
+ const appFinder_1 = require("../../../dataconnect/appFinder");
10
10
  const load_1 = require("../../../dataconnect/load");
11
11
  const types_1 = require("../../../dataconnect/types");
12
- const dataconnectEmulator_1 = require("../../../emulator/dataconnectEmulator");
13
12
  const error_1 = require("../../../error");
14
13
  const lodash_1 = require("lodash");
15
14
  const utils_1 = require("../../../utils");
15
+ const dataconnectEmulator_1 = require("../../../emulator/dataconnectEmulator");
16
16
  const auth_1 = require("../../../auth");
17
+ const create_app_1 = require("./create_app");
18
+ const track_1 = require("../../../track");
19
+ const fsutils_1 = require("../../../fsutils");
17
20
  exports.FDC_APP_FOLDER = "FDC_APP_FOLDER";
18
21
  exports.FDC_SDK_FRAMEWORKS_ENV = "FDC_SDK_FRAMEWORKS";
19
22
  exports.FDC_SDK_PLATFORM_ENV = "FDC_SDK_PLATFORM";
20
- async function doSetup(setup, config, options) {
21
- const sdkInfo = await askQuestions(setup, config, options);
22
- await actuate(sdkInfo, config);
23
- (0, utils_1.logSuccess)(`If you'd like to add more generated SDKs to your app your later, run ${clc.bold("firebase init dataconnect:sdk")} again`);
23
+ async function askQuestions(setup) {
24
+ const info = {
25
+ apps: [],
26
+ };
27
+ info.apps = await chooseApp();
28
+ if (!info.apps.length) {
29
+ const existingFilesAndDirs = (0, fsutils_1.listFiles)(cwd);
30
+ const webAppId = (0, utils_1.newUniqueId)("web-app", existingFilesAndDirs);
31
+ const choice = await (0, prompt_1.select)({
32
+ message: `Do you want to create an app template?`,
33
+ choices: [
34
+ { name: "React", value: "react" },
35
+ { name: "Next.JS", value: "next" },
36
+ { name: "no", value: "no" },
37
+ ],
38
+ });
39
+ switch (choice) {
40
+ case "react":
41
+ await (0, create_app_1.createReactApp)(webAppId);
42
+ break;
43
+ case "next":
44
+ await (0, create_app_1.createNextApp)(webAppId);
45
+ break;
46
+ case "no":
47
+ break;
48
+ }
49
+ }
50
+ setup.featureInfo = setup.featureInfo || {};
51
+ setup.featureInfo.dataconnectSdk = info;
24
52
  }
25
- exports.doSetup = doSetup;
26
- async function askQuestions(setup, config, options) {
27
- var _a;
28
- const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(config);
29
- const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(setup.projectId || "", config, c.source)));
30
- const connectorChoices = serviceInfos
31
- .map((si) => {
32
- return si.connectorInfo.map((ci) => {
53
+ exports.askQuestions = askQuestions;
54
+ async function chooseApp() {
55
+ let apps = await (0, appFinder_1.detectApps)(cwd);
56
+ if (apps.length) {
57
+ (0, utils_1.logLabeledSuccess)("dataconnect", `Detected existing apps ${apps.map((a) => (0, appFinder_1.appDescription)(a)).join(", ")}`);
58
+ }
59
+ else {
60
+ (0, utils_1.logLabeledWarning)("dataconnect", "No app exists in the current directory.");
61
+ }
62
+ const envAppFolder = (0, utils_1.envOverride)(exports.FDC_APP_FOLDER, "");
63
+ const envPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, types_1.Platform.NONE);
64
+ const envFrameworks = (0, utils_1.envOverride)(exports.FDC_SDK_FRAMEWORKS_ENV, "")
65
+ .split(",")
66
+ .map((f) => f);
67
+ if (envAppFolder && envPlatform !== types_1.Platform.NONE) {
68
+ const envAppRelDir = path.relative(cwd, path.resolve(cwd, envAppFolder));
69
+ const matchedApps = apps.filter((app) => app.directory === envAppRelDir && (!app.platform || app.platform === envPlatform));
70
+ if (matchedApps.length) {
71
+ for (const a of matchedApps) {
72
+ a.frameworks = [...(a.frameworks || []), ...envFrameworks];
73
+ }
74
+ return matchedApps;
75
+ }
76
+ return [
77
+ {
78
+ platform: envPlatform,
79
+ directory: envAppRelDir,
80
+ frameworks: envFrameworks,
81
+ },
82
+ ];
83
+ }
84
+ if (apps.length >= 2) {
85
+ const choices = apps.map((a) => {
33
86
  return {
34
- name: `${si.dataConnectYaml.location}/${si.dataConnectYaml.serviceId}/${ci.connectorYaml.connectorId}`,
35
- value: ci,
87
+ name: (0, appFinder_1.appDescription)(a),
88
+ value: a,
89
+ checked: a.directory === ".",
36
90
  };
37
91
  });
38
- })
39
- .flat();
40
- if (!connectorChoices.length) {
41
- throw new error_1.FirebaseError(`Your config has no connectors to set up SDKs for. Run ${clc.bold("firebase init dataconnect")} to set up a service and connectors.`);
42
- }
43
- let appDir = process.env[exports.FDC_APP_FOLDER] || process.cwd();
44
- let targetPlatform = (0, utils_1.envOverride)(exports.FDC_SDK_PLATFORM_ENV, (await (0, fileUtils_1.getPlatformFromFolder)(appDir)) || types_1.Platform.NONE);
45
- if (options.nonInteractive && targetPlatform === types_1.Platform.NONE) {
46
- throw new error_1.FirebaseError(`In non-interactive mode, the target platform and app directory must be specified using environment variables if they cannot be automatically detected.
47
- Please set the ${exports.FDC_SDK_PLATFORM_ENV} and ${exports.FDC_APP_FOLDER} environment variables.
48
- For example:
49
- ${clc.bold(`${exports.FDC_SDK_PLATFORM_ENV}=WEB ${exports.FDC_APP_FOLDER}=app-dir ${exports.FDC_SDK_FRAMEWORKS_ENV}=react firebase init dataconnect:sdk --non-interactive`)}`);
50
- }
51
- if (targetPlatform === types_1.Platform.NONE && !((_a = process.env[exports.FDC_APP_FOLDER]) === null || _a === void 0 ? void 0 : _a.length)) {
52
- appDir = await (0, utils_1.promptForDirectory)({
53
- config,
54
- message: "Where is your app directory? Leave blank to set up a generated SDK in your current directory.",
92
+ const pickedApps = await (0, prompt_1.checkbox)({
93
+ message: "Which apps do you want to set up Data Connect SDKs in?",
94
+ choices,
55
95
  });
56
- targetPlatform = await (0, fileUtils_1.getPlatformFromFolder)(appDir);
96
+ if (!pickedApps.length) {
97
+ throw new error_1.FirebaseError("Command Aborted. Please choose at least one app.");
98
+ }
99
+ apps = pickedApps;
100
+ }
101
+ return apps;
102
+ }
103
+ async function actuate(setup, config) {
104
+ var _a, _b;
105
+ const fdcInfo = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
106
+ const sdkInfo = (_b = setup.featureInfo) === null || _b === void 0 ? void 0 : _b.dataconnectSdk;
107
+ if (!sdkInfo) {
108
+ throw new Error("Data Connect SDK feature RequiredInfo is not provided");
109
+ }
110
+ try {
111
+ await actuateWithInfo(setup, config, sdkInfo);
57
112
  }
58
- if (targetPlatform === types_1.Platform.NONE || targetPlatform === types_1.Platform.MULTIPLE) {
59
- if (targetPlatform === types_1.Platform.NONE) {
60
- (0, utils_1.logBullet)(`Couldn't automatically detect app your in directory ${appDir}.`);
113
+ finally {
114
+ let flow = "no_app";
115
+ if (sdkInfo.apps.length) {
116
+ const platforms = sdkInfo.apps.map((a) => a.platform.toLowerCase()).sort();
117
+ flow = `${platforms.join("_")}_app`;
118
+ }
119
+ if (fdcInfo) {
120
+ fdcInfo.analyticsFlow += `_${flow}`;
61
121
  }
62
122
  else {
63
- (0, utils_1.logSuccess)(`Detected multiple app platforms in directory ${appDir}`);
123
+ void (0, track_1.trackGA4)("dataconnect_init", {
124
+ project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
125
+ flow: `cli_sdk_${flow}`,
126
+ });
64
127
  }
65
- const platforms = [
66
- { name: "iOS (Swift)", value: types_1.Platform.IOS },
67
- { name: "Web (JavaScript)", value: types_1.Platform.WEB },
68
- { name: "Android (Kotlin)", value: types_1.Platform.ANDROID },
69
- { name: "Flutter (Dart)", value: types_1.Platform.FLUTTER },
70
- ];
71
- targetPlatform = await (0, prompt_1.select)({
72
- message: "Which platform do you want to set up a generated SDK for?",
73
- choices: platforms,
74
- });
75
128
  }
76
- else {
77
- (0, utils_1.logSuccess)(`Detected ${targetPlatform} app in directory ${appDir}`);
129
+ }
130
+ exports.actuate = actuate;
131
+ async function actuateWithInfo(setup, config, info) {
132
+ if (!info.apps.length) {
133
+ info.apps = await (0, appFinder_1.detectApps)(cwd);
134
+ if (!info.apps.length) {
135
+ (0, utils_1.logLabeledBullet)("dataconnect", "No apps to setup Data Connect Generated SDKs");
136
+ return;
137
+ }
78
138
  }
79
- const connectorInfo = await chooseExistingConnector(connectorChoices);
139
+ const apps = info.apps;
140
+ const connectorInfo = await chooseExistingConnector(setup, config);
80
141
  const connectorYaml = JSON.parse(JSON.stringify(connectorInfo.connectorYaml));
81
- const newConnectorYaml = await generateSdkYaml(targetPlatform, connectorYaml, connectorInfo.directory, appDir);
82
- if (targetPlatform === types_1.Platform.WEB) {
83
- const unusedFrameworks = fileUtils_1.SUPPORTED_FRAMEWORKS.filter((framework) => { var _a; return !((_a = newConnectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk[framework]); });
84
- if (unusedFrameworks.length > 0) {
85
- let additionalFrameworks = [];
86
- if (options.nonInteractive) {
87
- additionalFrameworks = (0, utils_1.envOverride)(exports.FDC_SDK_FRAMEWORKS_ENV, "")
88
- .split(",")
89
- .filter((f) => f);
90
- }
91
- else {
92
- additionalFrameworks = await (0, prompt_1.checkbox)({
93
- message: "Which frameworks would you like to generate SDKs for in addition to the TypeScript SDK? Press Enter to skip.\n",
94
- choices: fileUtils_1.SUPPORTED_FRAMEWORKS.map((frameworkStr) => {
95
- var _a, _b;
96
- return ({
97
- value: frameworkStr,
98
- checked: (_b = (_a = newConnectorYaml === null || newConnectorYaml === void 0 ? void 0 : newConnectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk) === null || _b === void 0 ? void 0 : _b[frameworkStr],
99
- });
100
- }),
101
- });
102
- }
103
- for (const framework of additionalFrameworks) {
104
- newConnectorYaml.generate.javascriptSdk[framework] = true;
105
- }
142
+ for (const app of apps) {
143
+ if (!(0, fsutils_1.dirExistsSync)(app.directory)) {
144
+ (0, utils_1.logLabeledWarning)("dataconnect", `App directory ${app.directory} does not exist`);
106
145
  }
146
+ addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app);
147
+ }
148
+ const connectorYamlContents = yaml.stringify(connectorYaml);
149
+ connectorInfo.connectorYaml = connectorYaml;
150
+ const connectorYamlPath = `${connectorInfo.directory}/connector.yaml`;
151
+ config.writeProjectFile(path.relative(config.projectDir, connectorYamlPath), connectorYamlContents);
152
+ (0, utils_1.logLabeledBullet)("dataconnect", `Installing the generated SDKs ...`);
153
+ const account = (0, auth_1.getGlobalDefaultAccount)();
154
+ await dataconnectEmulator_1.DataConnectEmulator.generate({
155
+ configDir: connectorInfo.directory,
156
+ connectorId: connectorInfo.connectorYaml.connectorId,
157
+ account,
158
+ });
159
+ (0, utils_1.logLabeledSuccess)("dataconnect", `Installed generated SDKs for ${clc.bold(apps.map((a) => (0, appFinder_1.appDescription)(a)).join(", "))}`);
160
+ if (apps.some((a) => a.platform === types_1.Platform.IOS)) {
161
+ (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"));
162
+ }
163
+ if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes("react"); })) {
164
+ (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");
165
+ }
166
+ if (apps.some((a) => { var _a; return (_a = a.frameworks) === null || _a === void 0 ? void 0 : _a.includes("angular"); })) {
167
+ (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");
107
168
  }
108
- const connectorYamlContents = yaml.stringify(newConnectorYaml);
109
- connectorInfo.connectorYaml = newConnectorYaml;
110
- const displayIOSWarning = targetPlatform === types_1.Platform.IOS;
111
- return { connectorYamlContents, connectorInfo, displayIOSWarning };
112
169
  }
113
- async function chooseExistingConnector(choices) {
170
+ async function chooseExistingConnector(setup, config) {
171
+ const serviceInfos = await (0, load_1.loadAll)(setup.projectId || "", config);
172
+ const choices = serviceInfos
173
+ .map((si) => {
174
+ return si.connectorInfo.map((ci) => {
175
+ return {
176
+ name: `${si.dataConnectYaml.location}/${si.dataConnectYaml.serviceId}/${ci.connectorYaml.connectorId}`,
177
+ value: ci,
178
+ };
179
+ });
180
+ })
181
+ .flat();
182
+ if (!choices.length) {
183
+ throw new error_1.FirebaseError(`No Firebase Data Connect workspace found. Run ${clc.bold("firebase init dataconnect")} to set up a service and connector.`);
184
+ }
114
185
  if (choices.length === 1) {
115
186
  return choices[0].value;
116
187
  }
@@ -123,93 +194,79 @@ async function chooseExistingConnector(choices) {
123
194
  }
124
195
  (0, utils_1.logWarning)(`Unable to pick up an existing connector based on FDC_CONNECTOR=${connectorEnvVar}.`);
125
196
  }
126
- return await (0, prompt_1.select)({
127
- message: "Which connector do you want set up a generated SDK for?",
128
- choices: choices,
129
- });
197
+ (0, utils_1.logWarning)(`Pick up the first connector ${clc.bold(connectorEnvVar)}. Use FDC_CONNECTOR to override it`);
198
+ return choices[0].value;
130
199
  }
131
- async function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appDir) {
200
+ function addSdkGenerateToConnectorYaml(connectorInfo, connectorYaml, app) {
201
+ const connectorDir = connectorInfo.directory;
202
+ const appDir = app.directory;
132
203
  if (!connectorYaml.generate) {
133
204
  connectorYaml.generate = {};
134
205
  }
135
- if (targetPlatform === types_1.Platform.IOS) {
136
- const swiftSdk = {
137
- outputDir: path.relative(connectorDir, path.join(appDir, `dataconnect-generated/swift`)),
138
- package: "DataConnectGenerated",
139
- };
140
- connectorYaml.generate.swiftSdk = swiftSdk;
141
- }
142
- if (targetPlatform === types_1.Platform.WEB) {
143
- const pkg = `${connectorYaml.connectorId}-connector`;
144
- const packageJsonDir = path.relative(connectorDir, appDir);
145
- const javascriptSdk = {
146
- outputDir: path.relative(connectorDir, path.join(appDir, `dataconnect-generated/js/${pkg}`)),
147
- package: `@dataconnect/generated`,
148
- packageJsonDir,
149
- };
150
- const packageJson = await (0, fileUtils_1.resolvePackageJson)(appDir);
151
- if (packageJson) {
152
- const frameworksUsed = (0, fileUtils_1.getFrameworksFromPackageJson)(packageJson);
153
- for (const framework of frameworksUsed) {
154
- (0, utils_1.logBullet)(`Detected ${framework} app. Enabling ${framework} generated SDKs.`);
155
- javascriptSdk[framework] = true;
206
+ const generate = connectorYaml.generate;
207
+ switch (app.platform) {
208
+ case types_1.Platform.WEB: {
209
+ const javascriptSdk = {
210
+ outputDir: path.relative(connectorDir, path.join(appDir, `src/dataconnect-generated`)),
211
+ package: `@dataconnect/generated`,
212
+ packageJsonDir: path.relative(connectorDir, appDir),
213
+ react: false,
214
+ angular: false,
215
+ };
216
+ for (const f of app.frameworks || []) {
217
+ javascriptSdk[f] = true;
218
+ }
219
+ if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.javascriptSdk)) {
220
+ generate.javascriptSdk = generate.javascriptSdk ? [generate.javascriptSdk] : [];
221
+ }
222
+ if (!generate.javascriptSdk.some((s) => s.outputDir === javascriptSdk.outputDir)) {
223
+ generate.javascriptSdk.push(javascriptSdk);
224
+ }
225
+ break;
226
+ }
227
+ case types_1.Platform.FLUTTER: {
228
+ const dartSdk = {
229
+ outputDir: path.relative(connectorDir, path.join(appDir, `lib/dataconnect_generated`)),
230
+ package: "dataconnect_generated",
231
+ };
232
+ if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.dartSdk)) {
233
+ generate.dartSdk = generate.dartSdk ? [generate.dartSdk] : [];
234
+ }
235
+ if (!generate.dartSdk.some((s) => s.outputDir === dartSdk.outputDir)) {
236
+ generate.dartSdk.push(dartSdk);
156
237
  }
238
+ break;
157
239
  }
158
- connectorYaml.generate.javascriptSdk = javascriptSdk;
159
- }
160
- if (targetPlatform === types_1.Platform.FLUTTER) {
161
- const pkg = `${(0, lodash_1.snakeCase)(connectorYaml.connectorId)}_connector`;
162
- const dartSdk = {
163
- outputDir: path.relative(connectorDir, path.join(appDir, `dataconnect-generated/dart/${pkg}`)),
164
- package: "dataconnect_generated",
165
- };
166
- connectorYaml.generate.dartSdk = dartSdk;
167
- }
168
- if (targetPlatform === types_1.Platform.ANDROID) {
169
- const kotlinSdk = {
170
- outputDir: path.relative(connectorDir, path.join(appDir, `dataconnect-generated/kotlin`)),
171
- package: `com.google.firebase.dataconnect.generated`,
172
- };
173
- for (const candidateSubdir of ["app/src/main/java", "app/src/main/kotlin"]) {
174
- const candidateDir = path.join(appDir, candidateSubdir);
175
- if ((0, fsutils_1.dirExistsSync)(candidateDir)) {
176
- kotlinSdk.outputDir = path.relative(connectorDir, candidateDir);
240
+ case types_1.Platform.ANDROID: {
241
+ const kotlinSdk = {
242
+ outputDir: path.relative(connectorDir, path.join(appDir, `src/main/kotlin`)),
243
+ package: `com.google.firebase.dataconnect.generated`,
244
+ };
245
+ if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.kotlinSdk)) {
246
+ generate.kotlinSdk = generate.kotlinSdk ? [generate.kotlinSdk] : [];
177
247
  }
248
+ if (!generate.kotlinSdk.some((s) => s.outputDir === kotlinSdk.outputDir)) {
249
+ generate.kotlinSdk.push(kotlinSdk);
250
+ }
251
+ break;
178
252
  }
179
- connectorYaml.generate.kotlinSdk = kotlinSdk;
180
- }
181
- return connectorYaml;
182
- }
183
- exports.generateSdkYaml = generateSdkYaml;
184
- async function actuate(sdkInfo, config) {
185
- var _a, _b;
186
- const connectorYamlPath = `${sdkInfo.connectorInfo.directory}/connector.yaml`;
187
- (0, utils_1.logBullet)(`Writing your new SDK configuration to ${connectorYamlPath}`);
188
- config.writeProjectFile(path.relative(config.projectDir, connectorYamlPath), sdkInfo.connectorYamlContents);
189
- const account = (0, auth_1.getGlobalDefaultAccount)();
190
- await dataconnectEmulator_1.DataConnectEmulator.generate({
191
- configDir: sdkInfo.connectorInfo.directory,
192
- connectorId: sdkInfo.connectorInfo.connectorYaml.connectorId,
193
- account,
194
- });
195
- (0, utils_1.logBullet)(`Generated SDK code for ${sdkInfo.connectorInfo.connectorYaml.connectorId}`);
196
- if (((_a = sdkInfo.connectorInfo.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.swiftSdk) && sdkInfo.displayIOSWarning) {
197
- (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"));
198
- }
199
- if ((_b = sdkInfo.connectorInfo.connectorYaml.generate) === null || _b === void 0 ? void 0 : _b.javascriptSdk) {
200
- for (const framework of fileUtils_1.SUPPORTED_FRAMEWORKS) {
201
- if (sdkInfo.connectorInfo.connectorYaml.generate.javascriptSdk[framework]) {
202
- logInfoForFramework(framework);
253
+ case types_1.Platform.IOS: {
254
+ const swiftSdk = {
255
+ outputDir: path.relative(connectorDir, path.join(app.directory, `../FirebaseDataConnectGenerated`)),
256
+ package: "DataConnectGenerated",
257
+ };
258
+ if (!(0, lodash_1.isArray)(generate === null || generate === void 0 ? void 0 : generate.swiftSdk)) {
259
+ generate.swiftSdk = generate.swiftSdk ? [generate.swiftSdk] : [];
260
+ }
261
+ if (!generate.swiftSdk.some((s) => s.outputDir === swiftSdk.outputDir)) {
262
+ generate.swiftSdk.push(swiftSdk);
203
263
  }
264
+ break;
204
265
  }
266
+ default:
267
+ throw new error_1.FirebaseError(`Unsupported platform ${app.platform} for Data Connect SDK generation. Supported platforms are: ${Object.values(types_1.Platform)
268
+ .filter((p) => p !== types_1.Platform.NONE && p !== types_1.Platform.MULTIPLE)
269
+ .join(", ")}\n${JSON.stringify(app)}`);
205
270
  }
206
271
  }
207
- exports.actuate = actuate;
208
- function logInfoForFramework(framework) {
209
- if (framework === "react") {
210
- (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");
211
- }
212
- else if (framework === "angular") {
213
- (0, utils_1.logBullet)("Run `npm i --save @angular/fire @tanstack-query-firebase/angular @tanstack/angular-query-experimental` 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");
214
- }
215
- }
272
+ exports.addSdkGenerateToConnectorYaml = addSdkGenerateToConnectorYaml;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdk = exports.dataconnectPostSetup = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hosting = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
3
+ exports.aitools = exports.apptestingAcutate = exports.apptestingAskQuestions = exports.genkit = exports.apphosting = exports.dataconnectSdkActuate = exports.dataconnectSdkAskQuestions = exports.dataconnectActuate = exports.dataconnectAskQuestions = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storageActuate = exports.storageAskQuestions = exports.hosting = exports.functions = exports.firestoreActuate = exports.firestoreAskQuestions = exports.databaseActuate = exports.databaseAskQuestions = exports.account = void 0;
4
4
  var account_1 = require("./account");
5
5
  Object.defineProperty(exports, "account", { enumerable: true, get: function () { return account_1.doSetup; } });
6
6
  var database_1 = require("./database");
@@ -29,9 +29,9 @@ Object.defineProperty(exports, "hostingGithub", { enumerable: true, get: functio
29
29
  var dataconnect_1 = require("./dataconnect");
30
30
  Object.defineProperty(exports, "dataconnectAskQuestions", { enumerable: true, get: function () { return dataconnect_1.askQuestions; } });
31
31
  Object.defineProperty(exports, "dataconnectActuate", { enumerable: true, get: function () { return dataconnect_1.actuate; } });
32
- Object.defineProperty(exports, "dataconnectPostSetup", { enumerable: true, get: function () { return dataconnect_1.postSetup; } });
33
32
  var sdk_1 = require("./dataconnect/sdk");
34
- Object.defineProperty(exports, "dataconnectSdk", { enumerable: true, get: function () { return sdk_1.doSetup; } });
33
+ Object.defineProperty(exports, "dataconnectSdkAskQuestions", { enumerable: true, get: function () { return sdk_1.askQuestions; } });
34
+ Object.defineProperty(exports, "dataconnectSdkActuate", { enumerable: true, get: function () { return sdk_1.actuate; } });
35
35
  var apphosting_1 = require("./apphosting");
36
36
  Object.defineProperty(exports, "apphosting", { enumerable: true, get: function () { return apphosting_1.doSetup; } });
37
37
  var genkit_1 = require("./genkit");
package/lib/init/index.js CHANGED
@@ -23,9 +23,12 @@ const featuresList = [
23
23
  name: "dataconnect",
24
24
  askQuestions: features.dataconnectAskQuestions,
25
25
  actuate: features.dataconnectActuate,
26
- postSetup: features.dataconnectPostSetup,
27
26
  },
28
- { name: "dataconnect:sdk", doSetup: features.dataconnectSdk },
27
+ {
28
+ name: "dataconnect:sdk",
29
+ askQuestions: features.dataconnectSdkAskQuestions,
30
+ actuate: features.dataconnectSdkActuate,
31
+ },
29
32
  { name: "functions", doSetup: features.functions },
30
33
  { name: "hosting", doSetup: features.hosting },
31
34
  {
@@ -13,7 +13,7 @@ const types_1 = require("../dataconnect/types");
13
13
  const projectUtils_1 = require("../projectUtils");
14
14
  const prompt = require("../prompt");
15
15
  const projects_1 = require("./projects");
16
- const fileUtils_1 = require("../dataconnect/fileUtils");
16
+ const appFinder_1 = require("../dataconnect/appFinder");
17
17
  const utils_1 = require("../utils");
18
18
  const TIMEOUT_MILLIS = 30000;
19
19
  exports.APP_LIST_PAGE_SIZE = 100;
@@ -22,14 +22,14 @@ async function getDisplayName() {
22
22
  return await prompt.input("What would you like to call your app?");
23
23
  }
24
24
  async function getPlatform(appDir, config) {
25
- let targetPlatform = await (0, fileUtils_1.getPlatformFromFolder)(appDir);
25
+ let targetPlatform = await (0, appFinder_1.getPlatformFromFolder)(appDir);
26
26
  if (targetPlatform === types_1.Platform.NONE) {
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, fileUtils_1.getPlatformFromFolder)(appDir);
32
+ targetPlatform = await (0, appFinder_1.getPlatformFromFolder)(appDir);
33
33
  }
34
34
  if (targetPlatform === types_1.Platform.NONE || targetPlatform === types_1.Platform.MULTIPLE) {
35
35
  if (targetPlatform === types_1.Platform.NONE) {