firebase-tools 14.12.1 → 14.14.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 (56) hide show
  1. package/README.md +1 -1
  2. package/lib/commands/dataconnect-services-list.js +5 -5
  3. package/lib/commands/dataconnect-sql-grant.js +5 -0
  4. package/lib/commands/dataconnect-sql-setup.js +1 -3
  5. package/lib/crashlytics/getIssueDetails.js +41 -0
  6. package/lib/crashlytics/getSampleCrash.js +48 -0
  7. package/lib/dataconnect/client.js +23 -15
  8. package/lib/dataconnect/ensureApis.js +5 -9
  9. package/lib/dataconnect/errors.js +7 -1
  10. package/lib/dataconnect/fileUtils.js +5 -6
  11. package/lib/dataconnect/freeTrial.js +16 -39
  12. package/lib/dataconnect/provisionCloudSql.js +67 -70
  13. package/lib/dataconnect/schemaMigration.js +222 -170
  14. package/lib/deploy/dataconnect/deploy.js +9 -11
  15. package/lib/deploy/dataconnect/prepare.js +7 -10
  16. package/lib/deploy/dataconnect/release.js +42 -30
  17. package/lib/deploy/functions/backend.js +8 -2
  18. package/lib/deploy/functions/build.js +23 -1
  19. package/lib/deploy/functions/ensure.js +1 -1
  20. package/lib/deploy/functions/functionsDeployHelper.js +8 -1
  21. package/lib/deploy/functions/prepare.js +8 -4
  22. package/lib/deploy/functions/pricing.js +12 -5
  23. package/lib/deploy/functions/release/fabricator.js +25 -3
  24. package/lib/emulator/controller.js +7 -3
  25. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  26. package/lib/emulator/functionsEmulator.js +11 -1
  27. package/lib/experiments.js +4 -0
  28. package/lib/extensions/extensionsHelper.js +4 -15
  29. package/lib/extensions/utils.js +1 -12
  30. package/lib/firestore/api.js +25 -11
  31. package/lib/firestore/pretty-print.js +7 -0
  32. package/lib/functional.js +7 -1
  33. package/lib/functions/env.js +19 -15
  34. package/lib/functions/projectConfig.js +25 -2
  35. package/lib/functions/secrets.js +3 -0
  36. package/lib/gcp/cloudfunctionsv2.js +3 -31
  37. package/lib/gcp/cloudscheduler.js +1 -1
  38. package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
  39. package/lib/gcp/cloudsql/connect.js +3 -2
  40. package/lib/gcp/cloudsql/permissionsSetup.js +23 -16
  41. package/lib/gcp/k8s.js +32 -0
  42. package/lib/gcp/runv2.js +178 -0
  43. package/lib/gemini/fdcExperience.js +5 -3
  44. package/lib/init/features/dataconnect/index.js +266 -162
  45. package/lib/init/features/dataconnect/sdk.js +36 -20
  46. package/lib/init/features/project.js +4 -0
  47. package/lib/management/studio.js +1 -1
  48. package/lib/mcp/tools/core/init.js +7 -6
  49. package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
  50. package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
  51. package/lib/mcp/tools/crashlytics/index.js +7 -1
  52. package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
  53. package/lib/rtdb.js +1 -1
  54. package/package.json +1 -1
  55. package/schema/firebase-config.json +6 -0
  56. package/lib/extensions/resolveSource.js +0 -24
@@ -20,19 +20,20 @@ const sdk = require("./sdk");
20
20
  const fileUtils_1 = require("../../../dataconnect/fileUtils");
21
21
  const fdcExperience_1 = require("../../../gemini/fdcExperience");
22
22
  const configstore_1 = require("../../../configstore");
23
+ const track_1 = require("../../../track");
23
24
  const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
24
25
  const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
25
26
  const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
26
27
  const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
27
28
  const MUTATIONS_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/mutations.gql");
28
29
  const emptyConnector = {
29
- id: "default",
30
- path: "./connector",
30
+ id: "example",
31
+ path: "./example",
31
32
  files: [],
32
33
  };
33
34
  const defaultConnector = {
34
- id: "default",
35
- path: "./connector",
35
+ id: "example",
36
+ path: "./example",
36
37
  files: [
37
38
  {
38
39
  path: "queries.gql",
@@ -46,42 +47,33 @@ const defaultConnector = {
46
47
  };
47
48
  const defaultSchema = { path: "schema.gql", content: SCHEMA_TEMPLATE };
48
49
  async function askQuestions(setup) {
49
- const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
50
- if (setup.projectId) {
51
- hasBilling ? await (0, ensureApis_1.ensureApis)(setup.projectId) : await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
52
- }
53
- let info = {
50
+ const info = {
51
+ analyticsFlow: "cli",
52
+ appDescription: "",
54
53
  serviceId: "",
55
54
  locationId: "",
56
55
  cloudSqlInstanceId: "",
57
- isNewInstance: false,
58
56
  cloudSqlDatabase: "",
59
- isNewDatabase: false,
60
- connectors: [],
61
- schemaGql: [],
62
- shouldProvisionCSQL: false,
63
57
  };
64
- info = await promptForExistingServices(setup, info);
65
- const requiredConfigUnset = info.serviceId === "" ||
66
- info.cloudSqlInstanceId === "" ||
67
- info.locationId === "" ||
68
- info.cloudSqlDatabase === "";
69
- const shouldConfigureBackend = hasBilling &&
70
- requiredConfigUnset &&
71
- (await (0, prompt_1.confirm)({
72
- message: `Would you like to configure your Cloud SQL datasource now?`,
73
- default: true,
74
- }));
75
- if (shouldConfigureBackend) {
76
- info = await promptForSchema(setup, info);
77
- info = await promptForCloudSQL(setup, info);
78
- info.shouldProvisionCSQL = !!(setup.projectId &&
79
- (info.isNewInstance || info.isNewDatabase) &&
80
- hasBilling &&
81
- (await (0, prompt_1.confirm)({
82
- message: `Would you like to provision your Cloud SQL instance and database now?${info.isNewInstance ? " This will take several minutes." : ""}.`,
83
- default: true,
84
- })));
58
+ if (setup.projectId) {
59
+ const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
60
+ await (0, ensureApis_1.ensureApis)(setup.projectId);
61
+ await promptForExistingServices(setup, info);
62
+ if (!info.serviceGql) {
63
+ if (!configstore_1.configstore.get("gemini")) {
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
+ }
66
+ info.appDescription = await (0, prompt_1.input)({
67
+ message: `Describe your app to automatically generate a schema [Enter to skip]:`,
68
+ });
69
+ if (info.appDescription) {
70
+ configstore_1.configstore.set("gemini", true);
71
+ await (0, ensureApis_1.ensureGIFApis)(setup.projectId);
72
+ }
73
+ }
74
+ if (hasBilling) {
75
+ await promptForCloudSQL(setup, info);
76
+ }
85
77
  }
86
78
  setup.featureInfo = setup.featureInfo || {};
87
79
  setup.featureInfo.dataconnect = info;
@@ -96,65 +88,192 @@ async function actuate(setup, config, options) {
96
88
  if (!info) {
97
89
  throw new Error("Data Connect feature RequiredInfo is not provided");
98
90
  }
99
- const defaultServiceId = toDNSCompatibleId((0, path_1.basename)(process.cwd()));
100
- info.serviceId = info.serviceId || defaultServiceId;
101
- info.cloudSqlInstanceId =
102
- info.cloudSqlInstanceId || `${info.serviceId.toLowerCase() || "app"}-fdc`;
91
+ info.serviceId = info.serviceId || defaultServiceId();
92
+ info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId.toLowerCase()}-fdc`;
103
93
  info.locationId = info.locationId || `us-central1`;
104
94
  info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
105
- if (!info.schemaGql.length && !info.connectors.flatMap((r) => r.files).length) {
106
- info.schemaGql = [defaultSchema];
107
- info.connectors = [defaultConnector];
108
- }
109
- await writeFiles(config, info, options);
110
- if (setup.projectId && info.shouldProvisionCSQL && (await (0, cloudbilling_1.isBillingEnabled)(setup))) {
111
- await (0, provisionCloudSql_1.provisionCloudSql)({
112
- projectId: setup.projectId,
95
+ try {
96
+ await actuateWithInfo(setup, config, info, options);
97
+ }
98
+ finally {
99
+ void (0, track_1.trackGA4)("dataconnect_init", {
100
+ project_status: setup.projectId ? (setup.isBillingEnabled ? "blaze" : "spark") : "missing",
101
+ flow: info.analyticsFlow,
102
+ });
103
+ }
104
+ }
105
+ exports.actuate = actuate;
106
+ async function actuateWithInfo(setup, config, info, options) {
107
+ const projectId = setup.projectId;
108
+ if (!projectId) {
109
+ info.analyticsFlow += "_save_template";
110
+ return await writeFiles(config, info, { schemaGql: [defaultSchema], connectors: [defaultConnector] }, options);
111
+ }
112
+ const hasBilling = await (0, cloudbilling_1.isBillingEnabled)(setup);
113
+ if (hasBilling) {
114
+ await (0, provisionCloudSql_1.setupCloudSql)({
115
+ projectId: projectId,
113
116
  location: info.locationId,
114
117
  instanceId: info.cloudSqlInstanceId,
115
118
  databaseId: info.cloudSqlDatabase,
116
- enableGoogleMlIntegration: false,
117
- waitForCreation: false,
119
+ requireGoogleMlIntegration: false,
118
120
  });
119
121
  }
122
+ if (!info.appDescription) {
123
+ if (info.serviceGql) {
124
+ info.analyticsFlow += "_save_downloaded";
125
+ return await writeFiles(config, info, info.serviceGql, options);
126
+ }
127
+ info.analyticsFlow += "_save_template";
128
+ return await writeFiles(config, info, { schemaGql: [defaultSchema], connectors: [defaultConnector] }, options);
129
+ }
130
+ const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
131
+ const serviceAlreadyExists = !(await (0, client_1.createService)(projectId, info.locationId, info.serviceId));
132
+ const schemaGql = await (0, utils_1.promiseWithSpinner)(() => (0, fdcExperience_1.generateSchema)(info.appDescription, projectId), "Generating the Data Connect Schema...");
133
+ const schemaFiles = [{ path: "schema.gql", content: schemaGql }];
134
+ if (serviceAlreadyExists) {
135
+ (0, utils_1.logLabeledError)("dataconnect", `Data Connect Service ${serviceName} already exists. Skip saving them...`);
136
+ info.analyticsFlow += "_save_gemini_service_already_exists";
137
+ return await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
138
+ }
139
+ await (0, utils_1.promiseWithSpinner)(async () => {
140
+ const [saveSchemaGql, waitForCloudSQLProvision] = schemasDeploySequence(projectId, info, schemaFiles, hasBilling);
141
+ await (0, client_1.upsertSchema)(saveSchemaGql);
142
+ if (waitForCloudSQLProvision) {
143
+ void (0, client_1.upsertSchema)(waitForCloudSQLProvision);
144
+ }
145
+ }, "Saving the Data Connect Schema...");
146
+ try {
147
+ const [operationGql, seedDataGql] = await (0, utils_1.promiseWithSpinner)(() => Promise.all([
148
+ (0, fdcExperience_1.generateOperation)(fdcExperience_1.PROMPT_GENERATE_CONNECTOR, serviceName, projectId),
149
+ (0, fdcExperience_1.generateOperation)(fdcExperience_1.PROMPT_GENERATE_SEED_DATA, serviceName, projectId),
150
+ ]), "Generating the Data Connect Operations...");
151
+ const connectors = [
152
+ {
153
+ id: "example",
154
+ path: "./example",
155
+ files: [
156
+ {
157
+ path: "queries.gql",
158
+ content: operationGql,
159
+ },
160
+ ],
161
+ },
162
+ ];
163
+ info.analyticsFlow += "_save_gemini";
164
+ await writeFiles(config, info, { schemaGql: schemaFiles, connectors: connectors, seedDataGql: seedDataGql }, options);
165
+ }
166
+ catch (err) {
167
+ (0, utils_1.logLabeledError)("dataconnect", `Operation Generation failed...`);
168
+ info.analyticsFlow += "_save_gemini_operation_error";
169
+ await writeFiles(config, info, { schemaGql: schemaFiles, connectors: [] }, options);
170
+ throw err;
171
+ }
120
172
  }
121
- exports.actuate = actuate;
122
- async function postSetup(setup, config) {
173
+ function schemasDeploySequence(projectId, info, schemaFiles, linkToCloudSql) {
174
+ const serviceName = `projects/${projectId}/locations/${info.locationId}/services/${info.serviceId}`;
175
+ if (!linkToCloudSql) {
176
+ return [
177
+ {
178
+ name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
179
+ datasources: [{ postgresql: {} }],
180
+ source: {
181
+ files: schemaFiles,
182
+ },
183
+ },
184
+ ];
185
+ }
186
+ return [
187
+ {
188
+ name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
189
+ datasources: [
190
+ {
191
+ postgresql: {
192
+ database: info.cloudSqlDatabase,
193
+ cloudSql: {
194
+ instance: `projects/${projectId}/locations/${info.locationId}/instances/${info.cloudSqlInstanceId}`,
195
+ },
196
+ schemaValidation: "NONE",
197
+ },
198
+ },
199
+ ],
200
+ source: {
201
+ files: schemaFiles,
202
+ },
203
+ },
204
+ {
205
+ name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
206
+ datasources: [
207
+ {
208
+ postgresql: {
209
+ database: info.cloudSqlDatabase,
210
+ cloudSql: {
211
+ instance: `projects/${projectId}/locations/${info.locationId}/instances/${info.cloudSqlInstanceId}`,
212
+ },
213
+ schemaMigration: "MIGRATE_COMPATIBLE",
214
+ },
215
+ },
216
+ ],
217
+ source: {
218
+ files: schemaFiles,
219
+ },
220
+ },
221
+ ];
222
+ }
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 = [];
123
230
  const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
124
231
  if (cwdPlatformGuess !== types_1.Platform.NONE || (0, utils_1.envOverride)("FDC_CONNECTOR", "")) {
125
- await sdk.doSetup(setup, config);
232
+ await sdk.doSetup(setup, config, options);
126
233
  }
127
234
  else {
128
- (0, utils_1.logBullet)(`If you'd like to add the generated SDK to your app later, run ${clc.bold("firebase init dataconnect:sdk")}`);
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`);
129
241
  }
130
242
  if (setup.projectId && !setup.isBillingEnabled) {
131
- (0, utils_1.logBullet)((0, freeTrial_1.upgradeInstructions)(setup.projectId));
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");
132
248
  }
133
249
  }
134
250
  exports.postSetup = postSetup;
135
- async function writeFiles(config, info, options) {
251
+ async function writeFiles(config, info, serviceGql, options) {
136
252
  const dir = config.get("dataconnect.source") || "dataconnect";
137
- const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: info.connectors.map((c) => c.path) }));
253
+ const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: serviceGql.connectors.map((c) => c.path) }));
138
254
  config.set("dataconnect", { source: dir });
139
255
  await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml, !!options.force, true);
140
- if (info.schemaGql.length) {
141
- for (const f of info.schemaGql) {
256
+ if (serviceGql.seedDataGql) {
257
+ await config.askWriteProjectFile((0, path_1.join)(dir, "seed_data.gql"), serviceGql.seedDataGql, !!options.force);
258
+ }
259
+ if (serviceGql.schemaGql.length) {
260
+ for (const f of serviceGql.schemaGql) {
142
261
  await config.askWriteProjectFile((0, path_1.join)(dir, "schema", f.path), f.content, !!options.force);
143
262
  }
144
263
  }
145
264
  else {
146
265
  fs.ensureFileSync((0, path_1.join)(dir, "schema", "schema.gql"));
147
266
  }
148
- for (const c of info.connectors) {
149
- await writeConnectorFiles(config, c);
267
+ for (const c of serviceGql.connectors) {
268
+ await writeConnectorFiles(config, c, options);
150
269
  }
151
270
  }
152
- async function writeConnectorFiles(config, connectorInfo) {
271
+ async function writeConnectorFiles(config, connectorInfo, options) {
153
272
  const subbedConnectorYaml = subConnectorYamlValues({ connectorId: connectorInfo.id });
154
273
  const dir = config.get("dataconnect.source") || "dataconnect";
155
- await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml);
274
+ await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force);
156
275
  for (const f of connectorInfo.files) {
157
- await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content);
276
+ await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content, !!options.force);
158
277
  }
159
278
  }
160
279
  function subDataconnectYamlValues(replacementValues) {
@@ -182,53 +301,63 @@ function subConnectorYamlValues(replacementValues) {
182
301
  return replaced;
183
302
  }
184
303
  async function promptForExistingServices(setup, info) {
185
- var _a, _b, _c, _d;
304
+ var _a, _b, _c, _d, _e;
186
305
  if (!setup.projectId) {
187
- return info;
306
+ return;
188
307
  }
189
308
  const existingServices = await (0, client_1.listAllServices)(setup.projectId);
309
+ if (!existingServices.length) {
310
+ return;
311
+ }
190
312
  const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
191
313
  return { service: s, schema: await (0, client_1.getSchema)(s.name) };
192
314
  }));
193
- if (existingServicesAndSchemas.length) {
194
- const choice = await chooseExistingService(existingServicesAndSchemas);
195
- if (choice) {
196
- const serviceName = (0, names_1.parseServiceName)(choice.service.name);
197
- info.serviceId = serviceName.serviceId;
198
- info.locationId = serviceName.location;
199
- info.schemaGql = [];
200
- info.connectors = [emptyConnector];
201
- if (choice.schema) {
202
- const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
203
- if ((_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) {
204
- const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
205
- info.cloudSqlInstanceId = instanceName.instanceId;
206
- }
207
- if ((_b = choice.schema.source.files) === null || _b === void 0 ? void 0 : _b.length) {
208
- info.schemaGql = choice.schema.source.files;
209
- }
210
- info.cloudSqlDatabase = (_d = (_c = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
211
- const connectors = await (0, client_1.listConnectors)(choice.service.name, [
212
- "connectors.name",
213
- "connectors.source.files",
214
- ]);
215
- if (connectors.length) {
216
- info.connectors = connectors.map((c) => {
217
- const id = c.name.split("/").pop();
218
- return {
219
- id,
220
- path: connectors.length === 1 ? "./connector" : `./${id}`,
221
- files: c.source.files || [],
222
- };
223
- });
224
- }
225
- }
315
+ const choice = await chooseExistingService(existingServicesAndSchemas);
316
+ if (!choice) {
317
+ const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
318
+ info.serviceId = newUniqueId(defaultServiceId(), existingServiceIds);
319
+ info.analyticsFlow += "_pick_new_service";
320
+ return;
321
+ }
322
+ info.analyticsFlow += "_pick_existing_service";
323
+ const serviceName = (0, names_1.parseServiceName)(choice.service.name);
324
+ info.serviceId = serviceName.serviceId;
325
+ info.locationId = serviceName.location;
326
+ info.serviceGql = {
327
+ schemaGql: [],
328
+ connectors: [emptyConnector],
329
+ };
330
+ if (choice.schema) {
331
+ const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
332
+ if ((_b = (_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance) {
333
+ const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
334
+ info.cloudSqlInstanceId = instanceName.instanceId;
335
+ }
336
+ if ((_c = choice.schema.source.files) === null || _c === void 0 ? void 0 : _c.length) {
337
+ info.serviceGql.schemaGql = choice.schema.source.files;
338
+ }
339
+ info.cloudSqlDatabase = (_e = (_d = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _d === void 0 ? void 0 : _d.database) !== null && _e !== void 0 ? _e : "";
340
+ const connectors = await (0, client_1.listConnectors)(choice.service.name, [
341
+ "connectors.name",
342
+ "connectors.source.files",
343
+ ]);
344
+ if (connectors.length) {
345
+ info.serviceGql.connectors = connectors.map((c) => {
346
+ const id = c.name.split("/").pop();
347
+ return {
348
+ id,
349
+ path: connectors.length === 1 ? "./connector" : `./${id}`,
350
+ files: c.source.files || [],
351
+ };
352
+ });
226
353
  }
227
354
  }
228
- return info;
355
+ return;
229
356
  }
230
357
  async function chooseExistingService(existing) {
231
- const serviceEnvVar = (0, utils_1.envOverride)("FDC_CONNECTOR", "") || (0, utils_1.envOverride)("FDC_SERVICE", "");
358
+ const fdcConnector = (0, utils_1.envOverride)("FDC_CONNECTOR", "");
359
+ const fdcService = (0, utils_1.envOverride)("FDC_SERVICE", "");
360
+ const serviceEnvVar = fdcConnector || fdcService;
232
361
  if (serviceEnvVar) {
233
362
  const [serviceLocationFromEnvVar, serviceIdFromEnvVar] = serviceEnvVar.split("/");
234
363
  const serviceFromEnvVar = existing.find((s) => {
@@ -240,7 +369,8 @@ async function chooseExistingService(existing) {
240
369
  (0, utils_1.logBullet)(`Picking up the existing service ${clc.bold(serviceLocationFromEnvVar + "/" + serviceIdFromEnvVar)}.`);
241
370
  return serviceFromEnvVar;
242
371
  }
243
- (0, utils_1.logWarning)(`Unable to pick up an existing service based on FDC_SERVICE=${serviceEnvVar}.`);
372
+ const envVarName = fdcConnector ? "FDC_CONNECTOR" : "FDC_SERVICE";
373
+ (0, utils_1.logWarning)(`Unable to pick up an existing service based on ${envVarName}=${serviceEnvVar}.`);
244
374
  }
245
375
  const choices = existing.map((s) => {
246
376
  const serviceName = (0, names_1.parseServiceName)(s.service.name);
@@ -256,7 +386,10 @@ async function chooseExistingService(existing) {
256
386
  });
257
387
  }
258
388
  async function promptForCloudSQL(setup, info) {
259
- if (info.cloudSqlInstanceId === "" && setup.projectId) {
389
+ if (!setup.projectId) {
390
+ return;
391
+ }
392
+ if (info.cloudSqlInstanceId === "") {
260
393
  const instances = await cloudsql.listInstances(setup.projectId);
261
394
  let choices = instances.map((i) => {
262
395
  var _a;
@@ -279,78 +412,37 @@ async function promptForCloudSQL(setup, info) {
279
412
  choices,
280
413
  });
281
414
  if (info.cloudSqlInstanceId !== "") {
415
+ info.analyticsFlow += "_pick_existing_csql";
282
416
  info.locationId = choices.find((c) => c.value === info.cloudSqlInstanceId).location;
283
417
  }
418
+ else {
419
+ info.analyticsFlow += "_pick_new_csql";
420
+ info.cloudSqlInstanceId = await (0, prompt_1.input)({
421
+ message: `What ID would you like to use for your new CloudSQL instance?`,
422
+ default: newUniqueId(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
423
+ });
424
+ }
284
425
  }
285
426
  }
286
- if (info.cloudSqlInstanceId === "") {
287
- info.isNewInstance = true;
288
- info.cloudSqlInstanceId = await (0, prompt_1.input)({
289
- message: `What ID would you like to use for your new CloudSQL instance?`,
290
- default: `${info.serviceId.toLowerCase() || "app"}-fdc`,
291
- });
292
- }
293
427
  if (info.locationId === "") {
294
428
  const choices = await locationChoices(setup);
295
429
  info.locationId = await (0, prompt_1.select)({
296
430
  message: "What location would like to use?",
297
431
  choices,
432
+ default: "us-central1",
298
433
  });
299
434
  }
300
- if (info.cloudSqlDatabase === "" && setup.projectId) {
435
+ if (info.cloudSqlInstanceId !== "" && info.cloudSqlDatabase === "") {
301
436
  try {
302
437
  const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
303
- const choices = dbs.map((d) => {
304
- return { name: d.name, value: d.name };
305
- });
306
- choices.push({ name: "Create a new database", value: "" });
307
- if (dbs.length) {
308
- info.cloudSqlDatabase = await (0, prompt_1.select)({
309
- message: `Which database in ${info.cloudSqlInstanceId} would you like to use?`,
310
- choices,
311
- });
312
- }
438
+ const existing = dbs.map((d) => d.name);
439
+ info.cloudSqlDatabase = newUniqueId("fdcdb", existing);
313
440
  }
314
441
  catch (err) {
315
442
  logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
316
443
  }
317
444
  }
318
- if (info.cloudSqlDatabase === "") {
319
- info.isNewDatabase = true;
320
- info.cloudSqlDatabase = await (0, prompt_1.input)({
321
- message: `What ID would you like to use for your new database in ${info.cloudSqlInstanceId}?`,
322
- default: `fdcdb`,
323
- });
324
- }
325
- return info;
326
- }
327
- async function promptForSchema(setup, info) {
328
- if (info.serviceId === "") {
329
- info.serviceId = await (0, prompt_1.input)({
330
- message: "What ID would you like to use for this service?",
331
- default: (0, path_1.basename)(process.cwd()),
332
- });
333
- if (setup.projectId) {
334
- if (!configstore_1.configstore.get("gemini")) {
335
- (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");
336
- }
337
- if (await (0, prompt_1.confirm)({
338
- message: `Do you want Gemini in Firebase to help generate a schema for your service?`,
339
- default: false,
340
- })) {
341
- configstore_1.configstore.set("gemini", true);
342
- await (0, ensureApis_1.ensureGIFApis)(setup.projectId);
343
- const prompt = await (0, prompt_1.input)({
344
- message: "Describe the app you are building:",
345
- default: "movie rating app",
346
- });
347
- const schema = await (0, utils_1.promiseWithSpinner)(() => (0, fdcExperience_1.generateSchema)(prompt, setup.projectId), "Generating the Data Connect Schema...");
348
- info.schemaGql = [{ path: "schema.gql", content: (0, fdcExperience_1.extractCodeBlock)(schema) }];
349
- info.connectors = [emptyConnector];
350
- }
351
- }
352
- }
353
- return info;
445
+ return;
354
446
  }
355
447
  async function locationChoices(setup) {
356
448
  if (setup.projectId) {
@@ -372,17 +464,29 @@ async function locationChoices(setup) {
372
464
  ];
373
465
  }
374
466
  }
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
+ function defaultServiceId() {
477
+ return toDNSCompatibleId((0, path_1.basename)(process.cwd()));
478
+ }
375
479
  function toDNSCompatibleId(id) {
376
- let defaultServiceId = (0, path_1.basename)(id)
480
+ id = (0, path_1.basename)(id)
377
481
  .toLowerCase()
378
482
  .replaceAll(/[^a-z0-9-]/g, "")
379
483
  .slice(0, 63);
380
- while (defaultServiceId.endsWith("-") && defaultServiceId.length) {
381
- defaultServiceId = defaultServiceId.slice(0, defaultServiceId.length - 1);
484
+ while (id.endsWith("-") && id.length) {
485
+ id = id.slice(0, id.length - 1);
382
486
  }
383
- while (defaultServiceId.startsWith("-") && defaultServiceId.length) {
384
- defaultServiceId = defaultServiceId.slice(1, defaultServiceId.length);
487
+ while (id.startsWith("-") && id.length) {
488
+ id = id.slice(1, id.length);
385
489
  }
386
- return defaultServiceId || "app";
490
+ return id || "app";
387
491
  }
388
492
  exports.toDNSCompatibleId = toDNSCompatibleId;