firebase-tools 14.13.0 → 14.15.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 (76) hide show
  1. package/lib/apphosting/backend.js +40 -15
  2. package/lib/auth.js +37 -2
  3. package/lib/commands/apphosting-backends-create.js +8 -5
  4. package/lib/commands/dataconnect-sdk-generate.js +3 -5
  5. package/lib/commands/dataconnect-sql-diff.js +2 -2
  6. package/lib/commands/dataconnect-sql-grant.js +2 -2
  7. package/lib/commands/dataconnect-sql-migrate.js +2 -2
  8. package/lib/commands/dataconnect-sql-setup.js +2 -2
  9. package/lib/commands/dataconnect-sql-shell.js +2 -2
  10. package/lib/commands/init.js +3 -0
  11. package/lib/commands/login.js +12 -7
  12. package/lib/crashlytics/addNote.js +27 -0
  13. package/lib/crashlytics/deleteNote.js +23 -0
  14. package/lib/crashlytics/getIssueDetails.js +5 -20
  15. package/lib/crashlytics/getSampleCrash.js +6 -20
  16. package/lib/crashlytics/listNotes.js +29 -0
  17. package/lib/crashlytics/listTopDevices.js +33 -0
  18. package/lib/crashlytics/listTopIssues.js +8 -23
  19. package/lib/crashlytics/listTopOperatingSystems.js +32 -0
  20. package/lib/crashlytics/listTopVersions.js +32 -0
  21. package/lib/crashlytics/updateIssue.js +35 -0
  22. package/lib/crashlytics/utils.js +38 -0
  23. package/lib/dataconnect/appFinder.js +103 -0
  24. package/lib/dataconnect/errors.js +7 -1
  25. package/lib/dataconnect/load.js +105 -6
  26. package/lib/dataconnect/schemaMigration.js +152 -128
  27. package/lib/deploy/dataconnect/prepare.js +1 -3
  28. package/lib/deploy/dataconnect/release.js +33 -27
  29. package/lib/deploy/functions/prepare.js +2 -0
  30. package/lib/emulator/controller.js +7 -4
  31. package/lib/emulator/downloadableEmulatorInfo.json +17 -17
  32. package/lib/emulator/functionsEmulator.js +2 -0
  33. package/lib/functions/env.js +19 -15
  34. package/lib/gcp/cloudsql/connect.js +2 -1
  35. package/lib/gcp/cloudsql/permissionsSetup.js +10 -1
  36. package/lib/init/features/dataconnect/create_app.js +48 -0
  37. package/lib/init/features/dataconnect/index.js +20 -31
  38. package/lib/init/features/dataconnect/sdk.js +218 -161
  39. package/lib/init/features/index.js +3 -2
  40. package/lib/init/index.js +5 -1
  41. package/lib/management/apps.js +3 -3
  42. package/lib/mcp/prompts/core/deploy.js +51 -8
  43. package/lib/mcp/prompts/crashlytics/common.js +10 -0
  44. package/lib/mcp/prompts/crashlytics/fix_issue.js +89 -0
  45. package/lib/mcp/prompts/crashlytics/index.js +6 -0
  46. package/lib/mcp/prompts/crashlytics/prioritize_issues.js +79 -0
  47. package/lib/mcp/prompts/index.js +3 -2
  48. package/lib/mcp/tools/core/init.js +5 -1
  49. package/lib/mcp/tools/crashlytics/add_note.js +32 -0
  50. package/lib/mcp/tools/crashlytics/constants.js +11 -0
  51. package/lib/mcp/tools/crashlytics/delete_note.js +35 -0
  52. package/lib/mcp/tools/crashlytics/get_issue_details.js +2 -4
  53. package/lib/mcp/tools/crashlytics/get_sample_crash.js +4 -4
  54. package/lib/mcp/tools/crashlytics/index.js +16 -2
  55. package/lib/mcp/tools/crashlytics/list_notes.js +37 -0
  56. package/lib/mcp/tools/crashlytics/list_top_devices.js +33 -0
  57. package/lib/mcp/tools/crashlytics/list_top_issues.js +5 -7
  58. package/lib/mcp/tools/crashlytics/list_top_operating_systems.js +33 -0
  59. package/lib/mcp/tools/crashlytics/list_top_versions.js +33 -0
  60. package/lib/mcp/tools/crashlytics/update_issue.js +37 -0
  61. package/lib/mcp/tools/dataconnect/execute_graphql.js +2 -2
  62. package/lib/mcp/tools/dataconnect/execute_graphql_read.js +2 -2
  63. package/lib/mcp/tools/dataconnect/execute_mutation.js +2 -2
  64. package/lib/mcp/tools/dataconnect/execute_query.js +2 -2
  65. package/lib/mcp/tools/dataconnect/generate_operation.js +2 -2
  66. package/lib/mcp/tools/dataconnect/get_connector.js +2 -2
  67. package/lib/mcp/tools/dataconnect/get_schema.js +2 -2
  68. package/lib/utils.js +11 -1
  69. package/package.json +1 -1
  70. package/schema/firebase-config.json +3 -0
  71. package/templates/init/dataconnect/connector.yaml +0 -16
  72. package/templates/init/dataconnect/dataconnect.yaml +2 -1
  73. package/templates/init/dataconnect/mutations.gql +29 -29
  74. package/templates/init/dataconnect/queries.gql +73 -73
  75. package/templates/init/dataconnect/schema.gql +48 -48
  76. package/lib/dataconnect/fileUtils.js +0 -168
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.11.2",
57
+ "version": "2.12.0",
58
58
  "expectedSize": 29447008,
59
- "expectedChecksum": "13bc7d3bb0a0bbfe601991361e4413c2",
60
- "expectedChecksumSHA256": "e3b029eb461f0fe6f0c825c7a71d42c7a09c2b8ee4fac10c3e187d78fe5083f6",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.2",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
59
+ "expectedChecksum": "73a09e7f54b642c0232ce89e9a4dd14a",
60
+ "expectedChecksumSHA256": "5464803fec776eac553e1431474f3d4fcc7cc2f4fe43a58e8c1ce9d7ede28acd",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.12.0",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.12.0"
63
63
  },
64
64
  "win32": {
65
- "version": "2.11.2",
66
- "expectedSize": 29934592,
67
- "expectedChecksum": "032a0749781fc338b446d753dd543bf5",
68
- "expectedChecksumSHA256": "2d0498b3ef94b4e777d6fc1d526279376caf3842549f39f13a8ca327393bf810",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.2",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2.exe"
65
+ "version": "2.12.0",
66
+ "expectedSize": 29936128,
67
+ "expectedChecksum": "b4077d6d298024c9c454b8eb9ad9bf69",
68
+ "expectedChecksumSHA256": "37be096ab8d4d29fa902ca12df97327997b1dc06d4b5026e11aa11d65f183a71",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.12.0",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.12.0.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.11.2",
74
- "expectedSize": 29368504,
75
- "expectedChecksum": "065a7523f881952040ac678a9a1e9323",
76
- "expectedChecksumSHA256": "41ca6561cf77107b5d1d829233844d21c46a5e4bb823c150b1b32591dc2463e0",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.2",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
73
+ "version": "2.12.0",
74
+ "expectedSize": 29372600,
75
+ "expectedChecksum": "04fb5bc9ef194170a5ba0966247eaa79",
76
+ "expectedChecksumSHA256": "42397dd1b23094944aaf20044bc1ec0a5b400c79b536d15803eb4fa6b2bba025",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.12.0",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.12.0"
79
79
  }
80
80
  }
81
81
  }
@@ -329,6 +329,7 @@ class FunctionsEmulator {
329
329
  projectId: this.args.projectId,
330
330
  projectAlias: this.args.projectAlias,
331
331
  isEmulator: true,
332
+ configDir: emulatableBackend.configDir,
332
333
  };
333
334
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
334
335
  const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
@@ -879,6 +880,7 @@ class FunctionsEmulator {
879
880
  getUserEnvs(backend) {
880
881
  const projectInfo = {
881
882
  functionsSource: backend.functionsDir,
883
+ configDir: backend.configDir,
882
884
  projectId: this.args.projectId,
883
885
  projectAlias: this.args.projectAlias,
884
886
  isEmulator: true,
@@ -140,7 +140,7 @@ function parseStrict(data) {
140
140
  return envs;
141
141
  }
142
142
  exports.parseStrict = parseStrict;
143
- function findEnvfiles(functionsSource, projectId, projectAlias, isEmulator) {
143
+ function findEnvfiles(configDir, projectId, projectAlias, isEmulator) {
144
144
  const files = [".env"];
145
145
  files.push(`.env.${projectId}`);
146
146
  if (projectAlias) {
@@ -150,26 +150,28 @@ function findEnvfiles(functionsSource, projectId, projectAlias, isEmulator) {
150
150
  files.push(FUNCTIONS_EMULATOR_DOTENV);
151
151
  }
152
152
  return files
153
- .map((f) => path.join(functionsSource, f))
153
+ .map((f) => path.join(configDir, f))
154
154
  .filter(fs.existsSync)
155
155
  .map((p) => path.basename(p));
156
156
  }
157
- function hasUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
158
- return findEnvfiles(functionsSource, projectId, projectAlias, isEmulator).length > 0;
157
+ function hasUserEnvs(opts) {
158
+ const configDir = opts.configDir || opts.functionsSource;
159
+ return findEnvfiles(configDir, opts.projectId, opts.projectAlias, opts.isEmulator).length > 0;
159
160
  }
160
161
  exports.hasUserEnvs = hasUserEnvs;
161
162
  function writeUserEnvs(toWrite, envOpts) {
162
163
  if (Object.keys(toWrite).length === 0) {
163
164
  return;
164
165
  }
165
- const { functionsSource, projectId, projectAlias, isEmulator } = envOpts;
166
- const allEnvFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
166
+ const { projectId, projectAlias, isEmulator } = envOpts;
167
+ const configDir = envOpts.configDir || envOpts.functionsSource;
168
+ const allEnvFiles = findEnvfiles(configDir, projectId, projectAlias, isEmulator);
167
169
  const targetEnvFile = envOpts.isEmulator
168
170
  ? FUNCTIONS_EMULATOR_DOTENV
169
171
  : `.env.${envOpts.projectId}`;
170
172
  const targetEnvFileExists = allEnvFiles.includes(targetEnvFile);
171
173
  if (!targetEnvFileExists) {
172
- fs.writeFileSync(path.join(envOpts.functionsSource, targetEnvFile), "", { flag: "wx" });
174
+ fs.writeFileSync(path.join(configDir, targetEnvFile), "", { flag: "wx" });
173
175
  (0, utils_1.logBullet)(clc.yellow(clc.bold("functions: ")) +
174
176
  `Created new local file ${targetEnvFile} to store param values. We suggest explicitly adding or excluding this file from version control.`);
175
177
  }
@@ -186,7 +188,7 @@ function writeUserEnvs(toWrite, envOpts) {
186
188
  for (const k of Object.keys(toWrite)) {
187
189
  lines += formatUserEnvForWrite(k, toWrite[k]);
188
190
  }
189
- fs.appendFileSync(path.join(functionsSource, targetEnvFile), lines);
191
+ fs.appendFileSync(path.join(configDir, targetEnvFile), lines);
190
192
  }
191
193
  exports.writeUserEnvs = writeUserEnvs;
192
194
  function checkForDuplicateKeys(isEmulator, keys, fullEnv, envsWithoutLocal) {
@@ -210,22 +212,24 @@ function formatUserEnvForWrite(key, value) {
210
212
  }
211
213
  return `${key}=${escapedValue}\n`;
212
214
  }
213
- function loadUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
215
+ function loadUserEnvs(opts) {
214
216
  var _a;
215
- const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
217
+ const configDir = opts.configDir || opts.functionsSource;
218
+ const envFiles = findEnvfiles(configDir, opts.projectId, opts.projectAlias, opts.isEmulator);
216
219
  if (envFiles.length === 0) {
217
220
  return {};
218
221
  }
219
- if (projectAlias) {
220
- if (envFiles.includes(`.env.${projectId}`) && envFiles.includes(`.env.${projectAlias}`)) {
221
- throw new error_1.FirebaseError(`Can't have both dotenv files with projectId (env.${projectId}) ` +
222
- `and projectAlias (.env.${projectAlias}) as extensions.`);
222
+ if (opts.projectAlias) {
223
+ if (envFiles.includes(`.env.${opts.projectId}`) &&
224
+ envFiles.includes(`.env.${opts.projectAlias}`)) {
225
+ throw new error_1.FirebaseError(`Can't have both dotenv files with projectId (env.${opts.projectId}) ` +
226
+ `and projectAlias (.env.${opts.projectAlias}) as extensions.`);
223
227
  }
224
228
  }
225
229
  let envs = {};
226
230
  for (const f of envFiles) {
227
231
  try {
228
- const data = fs.readFileSync(path.join(functionsSource, f), "utf8");
232
+ const data = fs.readFileSync(path.join(configDir, f), "utf8");
229
233
  envs = Object.assign(Object.assign({}, envs), parseStrict(data));
230
234
  }
231
235
  catch (err) {
@@ -72,7 +72,7 @@ async function execute(sqlStatements, opts) {
72
72
  sqlStatements.push("COMMIT;");
73
73
  }
74
74
  for (const s of sqlStatements) {
75
- logFn(`Executing: '${s}'`);
75
+ logFn(`> ${s}`);
76
76
  try {
77
77
  results.push(await conn.query(s));
78
78
  }
@@ -84,6 +84,7 @@ async function execute(sqlStatements, opts) {
84
84
  }
85
85
  }
86
86
  await cleanUpFn();
87
+ logFn(``);
87
88
  return results;
88
89
  }
89
90
  exports.execute = execute;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.brownfieldSqlSetup = exports.setupBrownfieldAsGreenfield = exports.getSchemaMetadata = exports.greenFieldSchemaSetup = exports.setupSQLPermissions = exports.checkSQLRoleIsGranted = exports.fdcSqlRoleMap = exports.SchemaSetupStatus = void 0;
3
+ exports.grantRoleTo = exports.brownfieldSqlSetup = exports.setupBrownfieldAsGreenfield = exports.getSchemaMetadata = exports.greenFieldSchemaSetup = exports.setupSQLPermissions = exports.checkSQLRoleIsGranted = exports.fdcSqlRoleMap = exports.SchemaSetupStatus = void 0;
4
4
  const clc = require("colorette");
5
5
  const permissions_1 = require("./permissions");
6
6
  const cloudsqladmin_1 = require("./cloudsqladmin");
@@ -12,6 +12,7 @@ const connect_1 = require("./connect");
12
12
  const lodash_1 = require("lodash");
13
13
  const connect_2 = require("./connect");
14
14
  const utils = require("../../utils");
15
+ const cloudSqlAdminClient = require("./cloudsqladmin");
15
16
  var SchemaSetupStatus;
16
17
  (function (SchemaSetupStatus) {
17
18
  SchemaSetupStatus["NotSetup"] = "not-setup";
@@ -220,3 +221,11 @@ async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, s
220
221
  await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, brownfieldSetupCmds, silent, true);
221
222
  }
222
223
  exports.brownfieldSqlSetup = brownfieldSqlSetup;
224
+ async function grantRoleTo(options, instanceId, databaseId, role, email) {
225
+ const projectId = (0, projectUtils_1.needProjectId)(options);
226
+ const { user, mode } = (0, connect_2.toDatabaseUser)(email);
227
+ await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
228
+ const fdcSqlRole = exports.fdcSqlRoleMap[role](databaseId);
229
+ await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [`GRANT "${fdcSqlRole}" TO "${user}"`], false);
230
+ }
231
+ exports.grantRoleTo = grantRoleTo;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createNextApp = exports.createReactApp = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const clc = require("colorette");
6
+ const utils_1 = require("../../../utils");
7
+ async function createReactApp(webAppId) {
8
+ const args = ["create", "vite@latest", webAppId, "--", "--template", "react"];
9
+ await executeCommand("npm", args);
10
+ }
11
+ exports.createReactApp = createReactApp;
12
+ async function createNextApp(webAppId) {
13
+ const args = [
14
+ "create-next-app@latest",
15
+ webAppId,
16
+ "--ts",
17
+ "--eslint",
18
+ "--tailwind",
19
+ "--src-dir",
20
+ "--app",
21
+ "--turbopack",
22
+ "--import-alias",
23
+ '"@/*"',
24
+ "--skip-install",
25
+ ];
26
+ await executeCommand("npx", args);
27
+ }
28
+ exports.createNextApp = createNextApp;
29
+ async function executeCommand(command, args) {
30
+ (0, utils_1.logLabeledBullet)("dataconnect", `Running ${clc.bold(`${command} ${args.join(" ")}`)}`);
31
+ return new Promise((resolve, reject) => {
32
+ const childProcess = (0, child_process_1.spawn)(command, args, {
33
+ stdio: "inherit",
34
+ shell: true,
35
+ });
36
+ childProcess.on("close", (code) => {
37
+ if (code === 0) {
38
+ resolve();
39
+ }
40
+ else {
41
+ reject(new Error(`Command failed with exit code ${code}`));
42
+ }
43
+ });
44
+ childProcess.on("error", (err) => {
45
+ reject(err);
46
+ });
47
+ });
48
+ }
@@ -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.postSetup = 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", {
@@ -154,7 +156,7 @@ async function actuateWithInfo(setup, config, info, options) {
154
156
  path: "./example",
155
157
  files: [
156
158
  {
157
- path: "queries",
159
+ path: "queries.gql",
158
160
  content: operationGql,
159
161
  },
160
162
  ],
@@ -220,31 +222,27 @@ function schemasDeploySequence(projectId, info, schemaFiles, linkToCloudSql) {
220
222
  },
221
223
  ];
222
224
  }
223
- async function postSetup(setup, config, options) {
225
+ async function postSetup(setup) {
224
226
  var _a;
225
227
  const info = (_a = setup.featureInfo) === null || _a === void 0 ? void 0 : _a.dataconnect;
226
228
  if (!info) {
227
229
  throw new Error("Data Connect feature RequiredInfo is not provided");
228
230
  }
229
231
  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
232
  if (info.appDescription) {
238
233
  instructions.push(`You can visualize the Data Connect Schema in Firebase Console:
239
234
 
240
235
  https://console.firebase.google.com/project/${setup.projectId}/dataconnect/locations/${info.locationId}/services/${info.serviceId}/schema`);
241
236
  }
242
- if (setup.projectId && !setup.isBillingEnabled) {
243
- instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId));
237
+ if (!setup.isBillingEnabled) {
238
+ instructions.push((0, freeTrial_1.upgradeInstructions)(setup.projectId || "your-firebase-project"));
244
239
  }
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");
240
+ instructions.push(`Install the Data Connect VS Code Extensions. You can explore Data Connect Query on local pgLite and Cloud SQL Postgres Instance.`);
241
+ if (instructions.length) {
242
+ logger_1.logger.info(`\n${clc.bold("To get started with Firebase Data Connect:")}`);
243
+ for (const i of instructions) {
244
+ (0, utils_1.logBullet)(i + "\n");
245
+ }
248
246
  }
249
247
  }
250
248
  exports.postSetup = postSetup;
@@ -271,7 +269,7 @@ async function writeFiles(config, info, serviceGql, options) {
271
269
  async function writeConnectorFiles(config, connectorInfo, options) {
272
270
  const subbedConnectorYaml = subConnectorYamlValues({ connectorId: connectorInfo.id });
273
271
  const dir = config.get("dataconnect.source") || "dataconnect";
274
- await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force);
272
+ await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, "connector.yaml"), subbedConnectorYaml, !!options.force, true);
275
273
  for (const f of connectorInfo.files) {
276
274
  await config.askWriteProjectFile((0, path_1.join)(dir, connectorInfo.path, f.path), f.content, !!options.force);
277
275
  }
@@ -315,7 +313,7 @@ async function promptForExistingServices(setup, info) {
315
313
  const choice = await chooseExistingService(existingServicesAndSchemas);
316
314
  if (!choice) {
317
315
  const existingServiceIds = existingServices.map((s) => s.name.split("/").pop());
318
- info.serviceId = newUniqueId(defaultServiceId(), existingServiceIds);
316
+ info.serviceId = (0, utils_1.newUniqueId)(defaultServiceId(), existingServiceIds);
319
317
  info.analyticsFlow += "_pick_new_service";
320
318
  return;
321
319
  }
@@ -346,7 +344,7 @@ async function promptForExistingServices(setup, info) {
346
344
  const id = c.name.split("/").pop();
347
345
  return {
348
346
  id,
349
- path: connectors.length === 1 ? "./connector" : `./${id}`,
347
+ path: connectors.length === 1 ? "./example" : `./${id}`,
350
348
  files: c.source.files || [],
351
349
  };
352
350
  });
@@ -419,7 +417,7 @@ async function promptForCloudSQL(setup, info) {
419
417
  info.analyticsFlow += "_pick_new_csql";
420
418
  info.cloudSqlInstanceId = await (0, prompt_1.input)({
421
419
  message: `What ID would you like to use for your new CloudSQL instance?`,
422
- default: newUniqueId(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
420
+ default: (0, utils_1.newUniqueId)(`${defaultServiceId().toLowerCase()}-fdc`, instances.map((i) => i.name)),
423
421
  });
424
422
  }
425
423
  }
@@ -436,7 +434,7 @@ async function promptForCloudSQL(setup, info) {
436
434
  try {
437
435
  const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
438
436
  const existing = dbs.map((d) => d.name);
439
- info.cloudSqlDatabase = newUniqueId("fdcdb", existing);
437
+ info.cloudSqlDatabase = (0, utils_1.newUniqueId)("fdcdb", existing);
440
438
  }
441
439
  catch (err) {
442
440
  logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
@@ -464,15 +462,6 @@ async function locationChoices(setup) {
464
462
  ];
465
463
  }
466
464
  }
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
465
  function defaultServiceId() {
477
466
  return toDNSCompatibleId((0, path_1.basename)(process.cwd()));
478
467
  }