firebase-tools 13.19.0 → 13.20.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 (57) hide show
  1. package/lib/apiv2.js +8 -1
  2. package/lib/auth.js +14 -2
  3. package/lib/commands/dataconnect-sdk-generate.js +5 -2
  4. package/lib/commands/emulators-start.js +3 -0
  5. package/lib/commands/init.js +20 -16
  6. package/lib/commands/setup-emulators-dataconnect.js +0 -14
  7. package/lib/dataconnect/dataplaneClient.js +12 -9
  8. package/lib/dataconnect/fileUtils.js +16 -4
  9. package/lib/dataconnect/freeTrial.js +8 -6
  10. package/lib/dataconnect/provisionCloudSql.js +4 -4
  11. package/lib/dataconnect/types.js +1 -0
  12. package/lib/dataconnect/webhook.js +31 -0
  13. package/lib/deploy/dataconnect/deploy.js +2 -0
  14. package/lib/deploy/dataconnect/prepare.js +2 -0
  15. package/lib/deploy/dataconnect/release.js +10 -5
  16. package/lib/deploy/functions/runtimes/node/index.js +6 -1
  17. package/lib/emulator/commandUtils.js +6 -1
  18. package/lib/emulator/constants.js +1 -1
  19. package/lib/emulator/controller.js +17 -3
  20. package/lib/emulator/dataconnect/pg-gateway/auth/base-auth-flow.js +11 -0
  21. package/lib/emulator/dataconnect/pg-gateway/auth/cert.js +69 -0
  22. package/lib/emulator/dataconnect/pg-gateway/auth/index.js +22 -0
  23. package/lib/emulator/dataconnect/pg-gateway/auth/md5.js +135 -0
  24. package/lib/emulator/dataconnect/pg-gateway/auth/password.js +65 -0
  25. package/lib/emulator/dataconnect/pg-gateway/auth/sasl/sasl-mechanism.js +34 -0
  26. package/lib/emulator/dataconnect/pg-gateway/auth/sasl/scram-sha-256.js +298 -0
  27. package/lib/emulator/dataconnect/pg-gateway/auth/trust.js +2 -0
  28. package/lib/emulator/dataconnect/pg-gateway/backend-error.js +75 -0
  29. package/lib/emulator/dataconnect/pg-gateway/buffer-reader.js +55 -0
  30. package/lib/emulator/dataconnect/pg-gateway/buffer-writer.js +79 -0
  31. package/lib/emulator/dataconnect/pg-gateway/connection.js +419 -0
  32. package/lib/emulator/dataconnect/pg-gateway/connection.types.js +8 -0
  33. package/lib/emulator/dataconnect/pg-gateway/crypto.js +40 -0
  34. package/lib/emulator/dataconnect/pg-gateway/duplex.js +53 -0
  35. package/lib/emulator/dataconnect/pg-gateway/index.js +27 -0
  36. package/lib/emulator/dataconnect/pg-gateway/message-buffer.js +96 -0
  37. package/lib/emulator/dataconnect/pg-gateway/message-codes.js +54 -0
  38. package/lib/emulator/dataconnect/pg-gateway/platforms/node/index.js +13 -0
  39. package/lib/emulator/dataconnect/pg-gateway/polyfills/readable-stream-async-iterator.js +36 -0
  40. package/lib/emulator/dataconnect/pg-gateway/utils.js +40 -0
  41. package/lib/emulator/dataconnect/pgliteServer.js +134 -0
  42. package/lib/emulator/dataconnectEmulator.js +55 -73
  43. package/lib/emulator/dataconnectToolkitController.js +44 -0
  44. package/lib/emulator/downloadableEmulators.js +22 -11
  45. package/lib/emulator/hub.js +2 -1
  46. package/lib/emulator/portUtils.js +9 -11
  47. package/lib/emulator/storage/rules/runtime.js +1 -1
  48. package/lib/experiments.js +1 -0
  49. package/lib/init/features/dataconnect/index.js +148 -111
  50. package/lib/init/features/dataconnect/sdk.js +40 -27
  51. package/lib/init/features/emulators.js +2 -14
  52. package/lib/prompt.js +1 -1
  53. package/lib/rc.js +1 -9
  54. package/package.json +3 -1
  55. package/schema/connector-yaml.json +14 -0
  56. package/schema/firebase-config.json +6 -0
  57. package/templates/init/dataconnect/queries.gql +1 -2
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DataConnectToolkitController = void 0;
4
+ const error_1 = require("../error");
5
+ const portUtils = require("./portUtils");
6
+ const utils_1 = require("../utils");
7
+ const dataconnectEmulator_1 = require("./dataconnectEmulator");
8
+ const name = "Data Connect Toolkit";
9
+ class DataConnectToolkitController {
10
+ static async start(args) {
11
+ if (this.isRunning || this.instance) {
12
+ throw new error_1.FirebaseError(`${name} is already running!`, {});
13
+ }
14
+ this.instance = new dataconnectEmulator_1.DataConnectEmulator(args);
15
+ this.isRunning = true;
16
+ await this.instance.start();
17
+ const info = this.instance.getInfo();
18
+ await portUtils.waitForPortUsed(info.port, (0, utils_1.connectableHostname)(info.host), info.timeout);
19
+ }
20
+ static async stop() {
21
+ if (!this.isRunning) {
22
+ return;
23
+ }
24
+ try {
25
+ await this.instance.stop();
26
+ this.isRunning = false;
27
+ }
28
+ catch (e) {
29
+ throw new error_1.FirebaseError(`Data Connect Toolkit failed to stop with error: ${e}`);
30
+ }
31
+ }
32
+ static getInfo() {
33
+ return this.instance.getInfo();
34
+ }
35
+ static getUrl() {
36
+ const info = this.instance.getInfo();
37
+ if (info.host.includes(":")) {
38
+ return `http://[${info.host}]:${info.port}`;
39
+ }
40
+ return `http://${info.host}:${info.port}`;
41
+ }
42
+ }
43
+ exports.DataConnectToolkitController = DataConnectToolkitController;
44
+ DataConnectToolkitController.isRunning = false;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
4
+ const lsofi = require("lsofi");
4
5
  const types_1 = require("./types");
5
6
  const constants_1 = require("./constants");
6
7
  const error_1 = require("../error");
@@ -14,6 +15,7 @@ const os = require("os");
14
15
  const registry_1 = require("./registry");
15
16
  const download_1 = require("../emulator/download");
16
17
  const experiments = require("../experiments");
18
+ const process = require("process");
17
19
  const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
18
20
  const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
19
21
  const EMULATOR_UPDATE_DETAILS = {
@@ -46,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
46
48
  },
47
49
  dataconnect: process.platform === "darwin"
48
50
  ? {
49
- version: "1.3.8",
50
- expectedSize: 25027328,
51
- expectedChecksum: "903f0e8dd8212fec575dc2eba34be6e7",
51
+ version: "1.4.3",
52
+ expectedSize: 25125632,
53
+ expectedChecksum: "1edc7180b101b1b3653429ecb8312d2d",
52
54
  }
53
55
  : process.platform === "win32"
54
56
  ? {
55
- version: "1.3.8",
56
- expectedSize: 25450496,
57
- expectedChecksum: "e09999deda7301eba06f88face5e1744",
57
+ version: "1.4.3",
58
+ expectedSize: 25548800,
59
+ expectedChecksum: "16b31831577778e8842c8715f35b4faa",
58
60
  }
59
61
  : {
60
- version: "1.3.8",
61
- expectedSize: 24940696,
62
- expectedChecksum: "69aeb9755d4ec2e77bdadbc40236b306",
62
+ version: "1.4.3",
63
+ expectedSize: 25034904,
64
+ expectedChecksum: "c959f7bd2ed3221c509cb7ad22956de3",
63
65
  },
64
66
  };
65
67
  exports.DownloadDetails = {
@@ -295,6 +297,7 @@ function _getCommand(emulator, args) {
295
297
  optionalArgs: baseCmd.optionalArgs,
296
298
  joinArgs: baseCmd.joinArgs,
297
299
  shell: baseCmd.shell,
300
+ port: args.port,
298
301
  };
299
302
  }
300
303
  exports._getCommand = _getCommand;
@@ -308,11 +311,15 @@ async function _fatal(emulator, errorMsg) {
308
311
  process.exit(1);
309
312
  }
310
313
  }
311
- async function handleEmulatorProcessError(emulator, err) {
314
+ async function handleEmulatorProcessError(emulator, err, port) {
312
315
  const description = constants_1.Constants.description(emulator);
313
316
  if (err.path === "java" && err.code === "ENOENT") {
314
317
  await _fatal(emulator, `${description} has exited because java is not installed, you can install it from https://openjdk.java.net/install/`);
315
318
  }
319
+ else if (err.code === "EADDRINUSE") {
320
+ const ps = port ? await lsofi(port) : false;
321
+ await _fatal(emulator, `${description} has exited because its configured port is already in use${ps ? ` by process number ${ps}` : ""}. Are you running another copy of the emulator suite?`);
322
+ }
316
323
  else {
317
324
  await _fatal(emulator, `${description} has exited: ${err}`);
318
325
  }
@@ -366,9 +373,13 @@ async function _runBinary(emulator, command, extraEnv) {
366
373
  if (data.toString().includes("java.lang.UnsupportedClassVersionError")) {
367
374
  logger.logLabeled("WARN", emulator.name, "Unsupported java version, make sure java --version reports 1.8 or higher.");
368
375
  }
376
+ if (data.toString().includes("address already in use")) {
377
+ const message = `${description} has exited because its configured port ${command.port} is already in use. Are you running another copy of the emulator suite?`;
378
+ logger.logLabeled("ERROR", emulator.name, message);
379
+ }
369
380
  });
370
381
  emulator.instance.on("error", (err) => {
371
- handleEmulatorProcessError(emulator.name, err);
382
+ void handleEmulatorProcessError(emulator.name, err, command.port);
372
383
  });
373
384
  emulator.instance.once("exit", async (code, signal) => {
374
385
  if (signal) {
@@ -10,6 +10,7 @@ const types_1 = require("./types");
10
10
  const hubExport_1 = require("./hubExport");
11
11
  const registry_1 = require("./registry");
12
12
  const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
13
+ const utils_1 = require("../utils");
13
14
  const pkg = require("../../package.json");
14
15
  class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
15
16
  static readLocatorFile(projectId) {
@@ -19,7 +20,7 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
19
20
  }
20
21
  const data = fs.readFileSync(locatorPath, "utf8").toString();
21
22
  const locator = JSON.parse(data);
22
- if (locator.version !== this.CLI_VERSION) {
23
+ if (!utils_1.isVSCodeExtension && locator.version !== this.CLI_VERSION) {
23
24
  logger_1.logger.debug(`Found locator with mismatched version, ignoring: ${JSON.stringify(locator)}`);
24
25
  return undefined;
25
26
  }
@@ -11,7 +11,6 @@ const types_1 = require("./types");
11
11
  const constants_1 = require("./constants");
12
12
  const emulatorLogger_1 = require("./emulatorLogger");
13
13
  const node_child_process_1 = require("node:child_process");
14
- const dataconnectEmulator_1 = require("./dataconnectEmulator");
15
14
  const RESTRICTED_PORTS = new Set([
16
15
  1,
17
16
  7,
@@ -147,6 +146,7 @@ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
147
146
  firestore: true,
148
147
  "firestore.websocket": true,
149
148
  pubsub: true,
149
+ "dataconnect.postgres": true,
150
150
  dataconnect: false,
151
151
  hub: false,
152
152
  ui: false,
@@ -185,7 +185,11 @@ async function resolveHostAndAssignPorts(listenConfig) {
185
185
  lookupForHost.set(host, lookup);
186
186
  }
187
187
  const findAddrs = lookup.then(async (addrs) => {
188
- const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket" ? types_1.Emulators.FIRESTORE : name);
188
+ const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket"
189
+ ? types_1.Emulators.FIRESTORE
190
+ : name === "dataconnect.postgres"
191
+ ? types_1.Emulators.DATACONNECT
192
+ : name);
189
193
  if (addrs.some((addr) => addr.address === dns_1.IPV6_UNSPECIFIED.address)) {
190
194
  if (!addrs.some((addr) => addr.address === dns_1.IPV4_UNSPECIFIED.address)) {
191
195
  emuLogger.logLabeled("DEBUG", name, `testing listening on IPv4 wildcard in addition to IPv6. To listen on IPv6 only, use "::0" instead.`);
@@ -222,14 +226,6 @@ async function resolveHostAndAssignPorts(listenConfig) {
222
226
  available.push(listen);
223
227
  }
224
228
  else {
225
- if (/^dataconnect/i.exec(name)) {
226
- const alreadyRunning = await (0, dataconnectEmulator_1.checkIfDataConnectEmulatorRunningOnAddress)(listen);
227
- if (alreadyRunning) {
228
- emuLogger.logLabeled("DEBUG", "dataconnect", `Detected already running emulator on ${listen.address}:${listen.port}. Will attempt to reuse it.`);
229
- }
230
- available.push(listen);
231
- continue;
232
- }
233
229
  if (!portFixed) {
234
230
  if (i > 0) {
235
231
  emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`);
@@ -281,7 +277,9 @@ exports.resolveHostAndAssignPorts = resolveHostAndAssignPorts;
281
277
  function portDescription(name) {
282
278
  return name === "firestore.websocket"
283
279
  ? `websocket server for ${types_1.Emulators.FIRESTORE}`
284
- : constants_1.Constants.description(name);
280
+ : name === "dataconnect.postgres"
281
+ ? `postgres server for ${types_1.Emulators.DATACONNECT}`
282
+ : constants_1.Constants.description(name);
285
283
  }
286
284
  function warnPartiallyAvailablePort(emuLogger, port, available, unavailable) {
287
285
  emuLogger.logLabeled("WARN", `Port ${port} is available on ` +
@@ -109,7 +109,7 @@ class StorageRulesRuntime {
109
109
  };
110
110
  });
111
111
  this._childprocess.on("error", (err) => {
112
- (0, downloadableEmulators_1.handleEmulatorProcessError)(types_2.Emulators.STORAGE, err);
112
+ void (0, downloadableEmulators_1.handleEmulatorProcessError)(types_2.Emulators.STORAGE, err);
113
113
  });
114
114
  (_a = this._childprocess.stderr) === null || _a === void 0 ? void 0 : _a.on("data", (buf) => {
115
115
  const error = buf.toString();
@@ -109,6 +109,7 @@ exports.ALL_EXPERIMENTS = experiments({
109
109
  fdccompatiblemode: {
110
110
  shortDescription: "Enable Data Connect schema migrations in Compatible Mode",
111
111
  fullDescription: "Enable Data Connect schema migrations in Compatible Mode",
112
+ default: true,
112
113
  public: false,
113
114
  },
114
115
  });
@@ -10,12 +10,14 @@ const cloudsql = require("../../../gcp/cloudsql/cloudsqladmin");
10
10
  const ensureApis_1 = require("../../../dataconnect/ensureApis");
11
11
  const experiments = require("../../../experiments");
12
12
  const client_1 = require("../../../dataconnect/client");
13
- const emulators_1 = require("../emulators");
13
+ const types_1 = require("../../../dataconnect/types");
14
14
  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
18
  const cloudbilling_1 = require("../../../gcp/cloudbilling");
19
+ const sdk = require("./sdk");
20
+ const fileUtils_1 = require("../../../dataconnect/fileUtils");
19
21
  const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
20
22
  const DATACONNECT_YAML_COMPAT_EXPERIMENT_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect-fdccompatiblemode.yaml");
21
23
  const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
@@ -37,14 +39,28 @@ const defaultConnector = {
37
39
  ],
38
40
  };
39
41
  async function doSetup(setup, config) {
40
- const info = await askQuestions(setup, config);
42
+ const info = await askQuestions(setup);
41
43
  await actuate(setup, config, info);
44
+ const cwdPlatformGuess = await (0, fileUtils_1.getPlatformFromFolder)(process.cwd());
45
+ if (cwdPlatformGuess !== types_1.Platform.UNDETERMINED) {
46
+ await sdk.doSetup(setup, config);
47
+ }
48
+ else {
49
+ const promptForSDKGeneration = await (0, prompt_1.confirm)({
50
+ message: `Would you like to configure generated SDKs now?`,
51
+ default: false,
52
+ });
53
+ if (promptForSDKGeneration) {
54
+ await sdk.doSetup(setup, config);
55
+ }
56
+ else {
57
+ (0, utils_1.logBullet)(`If you'd like to generate an SDK for your new connector later, run ${clc.bold("firebase init dataconnect:sdk")}`);
58
+ }
59
+ }
42
60
  logger_1.logger.info("");
43
- (0, utils_1.logSuccess)(`If you'd like to generate an SDK for your new connector, run ${clc.bold("firebase init dataconnect:sdk")}`);
44
61
  }
45
62
  exports.doSetup = doSetup;
46
- async function askQuestions(setup, config) {
47
- var _a, _b, _c;
63
+ async function askQuestions(setup) {
48
64
  let info = {
49
65
  serviceId: "",
50
66
  locationId: "",
@@ -57,28 +73,42 @@ async function askQuestions(setup, config) {
57
73
  shouldProvisionCSQL: false,
58
74
  };
59
75
  const isBillingEnabled = setup.projectId ? await (0, cloudbilling_1.checkBillingEnabled)(setup.projectId) : false;
60
- info = await promptForService(setup, info, isBillingEnabled);
61
- if (info.cloudSqlInstanceId === "") {
76
+ if (setup.projectId) {
77
+ isBillingEnabled ? await (0, ensureApis_1.ensureApis)(setup.projectId) : await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
78
+ }
79
+ info = await checkExistingInstances(setup, info, isBillingEnabled);
80
+ const requiredConfigUnset = info.serviceId === "" ||
81
+ info.cloudSqlInstanceId === "" ||
82
+ info.locationId === "" ||
83
+ info.cloudSqlDatabase === "";
84
+ const shouldConfigureBackend = isBillingEnabled && requiredConfigUnset
85
+ ? await (0, prompt_1.confirm)({
86
+ message: `Would you like to configure your backend resources now?`,
87
+ default: false,
88
+ })
89
+ : false;
90
+ if (shouldConfigureBackend) {
91
+ info = await promptForService(info);
62
92
  info = await promptForCloudSQLInstance(setup, info);
93
+ info = await promptForDatabase(info);
94
+ info.shouldProvisionCSQL = !!(setup.projectId &&
95
+ (info.isNewInstance || info.isNewDatabase) &&
96
+ isBillingEnabled &&
97
+ (await (0, prompt_1.confirm)({
98
+ message: `Would you like to provision your Cloud SQL instance and database now?${info.isNewInstance ? " This will take several minutes." : ""}.`,
99
+ default: true,
100
+ })));
63
101
  }
64
- if (info.cloudSqlDatabase === "") {
65
- info = await promptForDatabase(setup, config, info);
102
+ else {
103
+ if (requiredConfigUnset) {
104
+ (0, utils_1.logBullet)(`Setting placeholder values in dataconnect.yaml. You can edit these before you deploy to specify different IDs or regions.`);
105
+ }
106
+ info.serviceId = info.serviceId !== "" ? info.serviceId : (0, path_1.basename)(process.cwd());
107
+ info.cloudSqlInstanceId =
108
+ info.cloudSqlInstanceId !== "" ? info.cloudSqlInstanceId : `${info.serviceId || "app"}-fdc`;
109
+ info.locationId = info.locationId !== "" ? info.locationId : `us-central1`;
110
+ info.cloudSqlDatabase = info.cloudSqlDatabase !== "" ? info.cloudSqlDatabase : `fdcdb`;
66
111
  }
67
- const defaultConnectionString = (_c = (_b = (_a = setup.rcfile.dataconnectEmulatorConfig) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.localConnectionString) !== null && _c !== void 0 ? _c : emulators_1.DEFAULT_POSTGRES_CONNECTION;
68
- const localConnectionString = await (0, prompt_1.promptOnce)({
69
- type: "input",
70
- name: "localConnectionString",
71
- message: `What is the connection string of the local Postgres instance you would like to use with the Data Connect emulator?`,
72
- default: defaultConnectionString,
73
- });
74
- setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
75
- info.shouldProvisionCSQL = !!(setup.projectId &&
76
- (info.isNewInstance || info.isNewDatabase) &&
77
- isBillingEnabled &&
78
- (await (0, prompt_1.confirm)({
79
- message: `Would you like to provision your Cloud SQL instance and database now?${info.isNewInstance ? " This will take several minutes." : ""}.`,
80
- default: true,
81
- })));
82
112
  return info;
83
113
  }
84
114
  async function actuate(setup, config, info) {
@@ -89,6 +119,7 @@ async function actuate(setup, config, info) {
89
119
  locationId: info.locationId,
90
120
  instanceId: info.cloudSqlInstanceId,
91
121
  databaseId: info.cloudSqlDatabase,
122
+ configYamlPath: (0, path_1.join)(config.get("dataconnect.source"), "dataconnect.yaml"),
92
123
  enableGoogleMlIntegration: false,
93
124
  waitForCreation: false,
94
125
  });
@@ -97,7 +128,6 @@ async function actuate(setup, config, info) {
97
128
  exports.actuate = actuate;
98
129
  async function writeFiles(config, info) {
99
130
  const dir = config.get("dataconnect.source") || "dataconnect";
100
- console.log(dir);
101
131
  const subbedDataconnectYaml = subDataconnectYamlValues(Object.assign(Object.assign({}, info), { connectorDirs: info.connectors.map((c) => c.path) }));
102
132
  config.set("dataconnect", { source: dir });
103
133
  await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml);
@@ -148,79 +178,67 @@ function subConnectorYamlValues(replacementValues) {
148
178
  }
149
179
  return replaced;
150
180
  }
151
- async function promptForService(setup, info, isBillingEnabled) {
181
+ async function checkExistingInstances(setup, info, isBillingEnabled) {
152
182
  var _a, _b, _c;
153
- if (setup.projectId) {
154
- if (isBillingEnabled) {
155
- await (0, ensureApis_1.ensureApis)(setup.projectId);
156
- const existingServices = await (0, client_1.listAllServices)(setup.projectId);
157
- const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
158
- return {
159
- service: s,
160
- schema: await (0, client_1.getSchema)(s.name),
161
- };
162
- }));
163
- if (existingServicesAndSchemas.length) {
164
- const choices = existingServicesAndSchemas.map((s) => {
165
- const serviceName = (0, names_1.parseServiceName)(s.service.name);
166
- return {
167
- name: `${serviceName.location}/${serviceName.serviceId}`,
168
- value: s,
169
- };
170
- });
171
- choices.push({ name: "Create a new service", value: undefined });
172
- const choice = await (0, prompt_1.promptOnce)({
173
- message: "Your project already has existing services. Which would you like to set up local files for?",
174
- type: "list",
175
- choices,
176
- });
177
- if (choice) {
178
- const serviceName = (0, names_1.parseServiceName)(choice.service.name);
179
- info.serviceId = serviceName.serviceId;
180
- info.locationId = serviceName.location;
181
- if (choice.schema) {
182
- const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
183
- if ((_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) {
184
- const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
185
- info.cloudSqlInstanceId = instanceName.instanceId;
186
- }
187
- if (choice.schema.source.files) {
188
- info.schemaGql = choice.schema.source.files;
189
- }
190
- info.cloudSqlDatabase = (_c = (_b = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database) !== null && _c !== void 0 ? _c : "";
191
- const connectors = await (0, client_1.listConnectors)(choice.service.name, [
192
- "connectors.name",
193
- "connectors.source.files",
194
- ]);
195
- if (connectors.length) {
196
- info.connectors = connectors.map((c) => {
197
- const id = c.name.split("/").pop();
198
- return {
199
- id,
200
- path: connectors.length === 1 ? "./connector" : `./${id}`,
201
- files: c.source.files || [],
202
- };
203
- });
204
- }
205
- }
183
+ if (!setup.projectId || !isBillingEnabled) {
184
+ return info;
185
+ }
186
+ const existingServices = await (0, client_1.listAllServices)(setup.projectId);
187
+ const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
188
+ return {
189
+ service: s,
190
+ schema: await (0, client_1.getSchema)(s.name),
191
+ };
192
+ }));
193
+ if (existingServicesAndSchemas.length) {
194
+ const choices = existingServicesAndSchemas.map((s) => {
195
+ const serviceName = (0, names_1.parseServiceName)(s.service.name);
196
+ return {
197
+ name: `${serviceName.location}/${serviceName.serviceId}`,
198
+ value: s,
199
+ };
200
+ });
201
+ choices.push({ name: "Create a new service", value: undefined });
202
+ const choice = await (0, prompt_1.promptOnce)({
203
+ message: "Your project already has existing services. Which would you like to set up local files for?",
204
+ type: "list",
205
+ choices,
206
+ });
207
+ if (choice) {
208
+ const serviceName = (0, names_1.parseServiceName)(choice.service.name);
209
+ info.serviceId = serviceName.serviceId;
210
+ info.locationId = serviceName.location;
211
+ if (choice.schema) {
212
+ const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
213
+ if ((_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) {
214
+ const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
215
+ info.cloudSqlInstanceId = instanceName.instanceId;
216
+ }
217
+ if (choice.schema.source.files) {
218
+ info.schemaGql = choice.schema.source.files;
219
+ }
220
+ info.cloudSqlDatabase = (_c = (_b = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database) !== null && _c !== void 0 ? _c : "";
221
+ const connectors = await (0, client_1.listConnectors)(choice.service.name, [
222
+ "connectors.name",
223
+ "connectors.source.files",
224
+ ]);
225
+ if (connectors.length) {
226
+ info.connectors = connectors.map((c) => {
227
+ const id = c.name.split("/").pop();
228
+ return {
229
+ id,
230
+ path: connectors.length === 1 ? "./connector" : `./${id}`,
231
+ files: c.source.files || [],
232
+ };
233
+ });
206
234
  }
207
235
  }
208
236
  }
209
237
  else {
210
- await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
238
+ info = await promptForService(info);
211
239
  }
212
240
  }
213
- if (info.serviceId === "") {
214
- info.serviceId = await (0, prompt_1.promptOnce)({
215
- message: "What ID would you like to use for this service?",
216
- type: "input",
217
- default: "app",
218
- });
219
- }
220
- return info;
221
- }
222
- async function promptForCloudSQLInstance(setup, info) {
223
- if (setup.projectId) {
241
+ if (info.cloudSqlInstanceId === "") {
224
242
  const instances = await cloudsql.listInstances(setup.projectId);
225
243
  let choices = instances.map((i) => {
226
244
  return { name: i.name, value: i.name, location: i.region };
@@ -239,8 +257,46 @@ async function promptForCloudSQLInstance(setup, info) {
239
257
  if (info.cloudSqlInstanceId !== "") {
240
258
  info.locationId = choices.find((c) => c.value === info.cloudSqlInstanceId).location;
241
259
  }
260
+ else {
261
+ info = await promptForCloudSQLInstance(setup, info);
262
+ }
263
+ }
264
+ }
265
+ if (info.cloudSqlDatabase === "" && info.cloudSqlInstanceId !== "") {
266
+ try {
267
+ const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
268
+ const choices = dbs.map((d) => {
269
+ return { name: d.name, value: d.name };
270
+ });
271
+ choices.push({ name: "Create a new database", value: "" });
272
+ if (dbs.length) {
273
+ info.cloudSqlDatabase = await (0, prompt_1.promptOnce)({
274
+ message: `Which database in ${info.cloudSqlInstanceId} would you like to use?`,
275
+ type: "list",
276
+ choices,
277
+ });
278
+ if (info.cloudSqlDatabase === "") {
279
+ info = await promptForDatabase(info);
280
+ }
281
+ }
242
282
  }
283
+ catch (err) {
284
+ logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
285
+ }
286
+ }
287
+ return info;
288
+ }
289
+ async function promptForService(info) {
290
+ if (info.serviceId === "") {
291
+ info.serviceId = await (0, prompt_1.promptOnce)({
292
+ message: "What ID would you like to use for this service?",
293
+ type: "input",
294
+ default: (0, path_1.basename)(process.cwd()),
295
+ });
243
296
  }
297
+ return info;
298
+ }
299
+ async function promptForCloudSQLInstance(setup, info) {
244
300
  if (info.cloudSqlInstanceId === "") {
245
301
  info.isNewInstance = true;
246
302
  info.cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
@@ -279,26 +335,7 @@ async function locationChoices(setup) {
279
335
  ];
280
336
  }
281
337
  }
282
- async function promptForDatabase(setup, config, info) {
283
- if (!info.isNewInstance && setup.projectId) {
284
- try {
285
- const dbs = await cloudsql.listDatabases(setup.projectId, info.cloudSqlInstanceId);
286
- const choices = dbs.map((d) => {
287
- return { name: d.name, value: d.name };
288
- });
289
- choices.push({ name: "Create a new database", value: "" });
290
- if (dbs.length) {
291
- info.cloudSqlDatabase = await (0, prompt_1.promptOnce)({
292
- message: `Which database in ${info.cloudSqlInstanceId} would you like to use?`,
293
- type: "list",
294
- choices,
295
- });
296
- }
297
- }
298
- catch (err) {
299
- logger_1.logger.debug(`[dataconnect] Cannot list databases during init: ${err}`);
300
- }
301
- }
338
+ async function promptForDatabase(info) {
302
339
  if (info.cloudSqlDatabase === "") {
303
340
  info.isNewDatabase = true;
304
341
  info.cloudSqlDatabase = await (0, prompt_1.promptOnce)({