firebase-tools 13.9.0 → 13.10.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.
@@ -20,16 +20,12 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
20
20
  configDir = path.resolve(path.join(cwd), configDir);
21
21
  }
22
22
  const serviceInfo = await (0, load_1.load)(projectId, service.location, configDir);
23
- const args = {
24
- projectId,
25
- configDir,
26
- auto_download: true,
27
- rc: options.rc,
28
- locationId: service.location,
29
- };
30
- const dataconnectEmulator = new dataconnectEmulator_1.DataConnectEmulator(args);
31
23
  for (const conn of serviceInfo.connectorInfo) {
32
- const output = await dataconnectEmulator.generate(conn.connectorYaml.connectorId);
24
+ const output = await dataconnectEmulator_1.DataConnectEmulator.generate({
25
+ configDir,
26
+ locationId: service.location,
27
+ connectorId: conn.connectorYaml.connectorId,
28
+ });
33
29
  logger_1.logger.info(output);
34
30
  logger_1.logger.info(`Generated SDKs for ${conn.connectorYaml.connectorId}`);
35
31
  }
@@ -33,7 +33,6 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
33
33
  const diffs = await (0, schemaMigration_1.migrateSchema)({
34
34
  options,
35
35
  schema: serviceInfo.schema,
36
- allowNonInteractiveMigration: true,
37
36
  validateOnly: true,
38
37
  });
39
38
  if (diffs.length) {
@@ -5,19 +5,11 @@ const dataconnectEmulator_1 = require("../emulator/dataconnectEmulator");
5
5
  const error_1 = require("../error");
6
6
  const graphqlError_1 = require("./graphqlError");
7
7
  async function build(options, configDir) {
8
- var _a, _b, _c;
9
- const projectId = (_a = options.project) !== null && _a !== void 0 ? _a : "demo-test";
10
- const args = {
11
- projectId,
12
- configDir,
13
- auto_download: true,
14
- rc: options.rc,
15
- };
16
- const dataconnectEmulator = new dataconnectEmulator_1.DataConnectEmulator(args);
17
- const buildResult = await dataconnectEmulator.build();
18
- if ((_b = buildResult === null || buildResult === void 0 ? void 0 : buildResult.errors) === null || _b === void 0 ? void 0 : _b.length) {
8
+ var _a, _b;
9
+ const buildResult = await dataconnectEmulator_1.DataConnectEmulator.build({ configDir });
10
+ if ((_a = buildResult === null || buildResult === void 0 ? void 0 : buildResult.errors) === null || _a === void 0 ? void 0 : _a.length) {
19
11
  throw new error_1.FirebaseError(`There are errors in your schema and connector files:\n${buildResult.errors.map(graphqlError_1.prettify).join("\n")}`);
20
12
  }
21
- return (_c = buildResult === null || buildResult === void 0 ? void 0 : buildResult.metadata) !== null && _c !== void 0 ? _c : {};
13
+ return (_b = buildResult === null || buildResult === void 0 ? void 0 : buildResult.metadata) !== null && _b !== void 0 ? _b : {};
22
14
  }
23
15
  exports.build = build;
@@ -56,7 +56,7 @@ async function createService(projectId, locationId, serviceId) {
56
56
  }
57
57
  exports.createService = createService;
58
58
  async function deleteService(projectId, locationId, serviceId) {
59
- const op = await dataconnectClient().delete(`projects/${projectId}/locations/${locationId}/services/${serviceId}?force=true`);
59
+ const op = await dataconnectClient().delete(`projects/${projectId}/locations/${locationId}/services/${serviceId}`);
60
60
  const pollRes = await operationPoller.pollOperation({
61
61
  apiOrigin: (0, api_1.dataconnectOrigin)(),
62
62
  apiVersion: DATACONNECT_API_VERSION,
@@ -5,27 +5,35 @@ const INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING = "IncompatibleSqlSchemaError";
5
5
  const PRECONDITION_ERROR_TYPESTRING = "type.googleapis.com/google.rpc.PreconditionFailure";
6
6
  const INCOMPATIBLE_CONNECTOR_TYPE = "INCOMPATIBLE_CONNECTOR";
7
7
  function getIncompatibleSchemaError(err) {
8
- var _a, _b;
9
- const original = ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) || err.orignal;
10
- if (!original) {
11
- throw err;
8
+ const incompatibles = errorDetails(err, INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING);
9
+ if (incompatibles.length === 0) {
10
+ return undefined;
12
11
  }
13
- const details = original.details;
14
- const incompatibles = details.filter((d) => { var _a; return (_a = d["@type"]) === null || _a === void 0 ? void 0 : _a.includes(INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING); });
15
- return incompatibles[0];
12
+ const incompatible = incompatibles[0];
13
+ const preconditionErrs = errorDetails(err, PRECONDITION_ERROR_TYPESTRING);
14
+ const violationTypes = (incompatible.violationType = preconditionErrs
15
+ .flatMap((preCondErr) => preCondErr.violations)
16
+ .flatMap((viol) => viol.type)
17
+ .filter((type) => type === "INACCESSIBLE_SCHEMA" || type === "INCOMPATIBLE_SCHEMA"));
18
+ incompatible.violationType = violationTypes[0];
19
+ return incompatible;
16
20
  }
17
21
  exports.getIncompatibleSchemaError = getIncompatibleSchemaError;
18
22
  function getInvalidConnectors(err) {
19
- var _a, _b, _c, _d;
23
+ var _a, _b;
24
+ const preconditionErrs = errorDetails(err, PRECONDITION_ERROR_TYPESTRING);
20
25
  const invalidConns = [];
21
- const original = ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) || (err === null || err === void 0 ? void 0 : err.orignal);
22
- const details = original === null || original === void 0 ? void 0 : original.details;
23
- const preconditionErrs = details === null || details === void 0 ? void 0 : details.filter((d) => { var _a; return (_a = d["@type"]) === null || _a === void 0 ? void 0 : _a.includes(PRECONDITION_ERROR_TYPESTRING); });
24
26
  for (const preconditionErr of preconditionErrs) {
25
- const incompatibleConnViolation = (_c = preconditionErr === null || preconditionErr === void 0 ? void 0 : preconditionErr.violations) === null || _c === void 0 ? void 0 : _c.filter((v) => v.type === INCOMPATIBLE_CONNECTOR_TYPE);
26
- const newConns = (_d = incompatibleConnViolation === null || incompatibleConnViolation === void 0 ? void 0 : incompatibleConnViolation.map((i) => i.subject)) !== null && _d !== void 0 ? _d : [];
27
+ const incompatibleConnViolation = (_a = preconditionErr === null || preconditionErr === void 0 ? void 0 : preconditionErr.violations) === null || _a === void 0 ? void 0 : _a.filter((v) => v.type === INCOMPATIBLE_CONNECTOR_TYPE);
28
+ const newConns = (_b = incompatibleConnViolation === null || incompatibleConnViolation === void 0 ? void 0 : incompatibleConnViolation.map((i) => i.subject)) !== null && _b !== void 0 ? _b : [];
27
29
  invalidConns.push(...newConns);
28
30
  }
29
31
  return invalidConns;
30
32
  }
31
33
  exports.getInvalidConnectors = getInvalidConnectors;
34
+ function errorDetails(err, ofType) {
35
+ var _a, _b;
36
+ const original = ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) || (err === null || err === void 0 ? void 0 : err.original);
37
+ const details = original === null || original === void 0 ? void 0 : original.details;
38
+ return (details === null || details === void 0 ? void 0 : details.filter((d) => { var _a; return (_a = d["@type"]) === null || _a === void 0 ? void 0 : _a.includes(ofType); })) || [];
39
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkInstanceConfig = exports.provisionCloudSql = void 0;
3
+ exports.getUpdateReason = exports.provisionCloudSql = void 0;
4
4
  const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
5
5
  const utils = require("../utils");
6
6
  const checkIam_1 = require("./checkIam");
@@ -15,10 +15,12 @@ async function provisionCloudSql(args) {
15
15
  const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
16
16
  silent || utils.logLabeledBullet("dataconnect", `Found existing instance ${instanceId}.`);
17
17
  connectionName = (existingInstance === null || existingInstance === void 0 ? void 0 : existingInstance.connectionName) || "";
18
- if (!checkInstanceConfig(existingInstance, enableGoogleMlIntegration)) {
18
+ const why = getUpdateReason(existingInstance, enableGoogleMlIntegration);
19
+ if (why) {
19
20
  silent ||
20
21
  utils.logLabeledBullet("dataconnect", `Instance ${instanceId} settings not compatible with Firebase Data Connect. ` +
21
- `Updating instance to enable Cloud IAM authentication and public IP. This may take a few minutes...`);
22
+ `Updating instance. This may take a few minutes...` +
23
+ why);
22
24
  await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.updateInstanceForDataConnect(existingInstance, enableGoogleMlIntegration), "Updating your instance...");
23
25
  silent || utils.logLabeledBullet("dataconnect", "Instance updated");
24
26
  }
@@ -44,10 +46,15 @@ async function provisionCloudSql(args) {
44
46
  silent || utils.logLabeledBullet("dataconnect", `Found existing database ${databaseId}.`);
45
47
  }
46
48
  catch (err) {
47
- silent ||
48
- utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found, creating it now...`);
49
- await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
50
- silent || utils.logLabeledBullet("dataconnect", `Database ${databaseId} created.`);
49
+ if (err.status === 404) {
50
+ silent ||
51
+ utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found, creating it now...`);
52
+ await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
53
+ silent || utils.logLabeledBullet("dataconnect", `Database ${databaseId} created.`);
54
+ }
55
+ else {
56
+ silent || utils.logLabeledWarning("dataconnect", `Database ${databaseId} is not accessible.`);
57
+ }
51
58
  }
52
59
  if (enableGoogleMlIntegration) {
53
60
  await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
@@ -55,21 +62,25 @@ async function provisionCloudSql(args) {
55
62
  return connectionName;
56
63
  }
57
64
  exports.provisionCloudSql = provisionCloudSql;
58
- function checkInstanceConfig(instance, requireGoogleMlIntegration) {
65
+ function getUpdateReason(instance, requireGoogleMlIntegration) {
59
66
  var _a, _b, _c, _d;
67
+ let reason = "";
60
68
  const settings = instance.settings;
61
69
  if (!((_a = settings.ipConfiguration) === null || _a === void 0 ? void 0 : _a.ipv4Enabled)) {
62
- return false;
70
+ reason += "\n - to enable public IP.";
63
71
  }
64
72
  if (requireGoogleMlIntegration) {
65
73
  if (!settings.enableGoogleMlIntegration) {
66
- return false;
74
+ reason += "\n - to enable Google ML integration.";
67
75
  }
68
76
  if (!((_b = settings.databaseFlags) === null || _b === void 0 ? void 0 : _b.some((f) => f.name === "cloudsql.enable_google_ml_integration" && f.value === "on"))) {
69
- return false;
77
+ reason += "\n - to enable Google ML integration database flag.";
70
78
  }
71
79
  }
72
80
  const isIamEnabled = (_d = (_c = settings.databaseFlags) === null || _c === void 0 ? void 0 : _c.some((f) => f.name === "cloudsql.iam_authentication" && f.value === "on")) !== null && _d !== void 0 ? _d : false;
73
- return isIamEnabled;
81
+ if (!isIamEnabled) {
82
+ reason += "\n - to enable IAM authentication database flag.";
83
+ }
84
+ return reason;
74
85
  }
75
- exports.checkInstanceConfig = checkInstanceConfig;
86
+ exports.getUpdateReason = getUpdateReason;
@@ -14,50 +14,50 @@ const utils_1 = require("../utils");
14
14
  const errors = require("./errors");
15
15
  async function diffSchema(schema) {
16
16
  const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
17
- await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId);
17
+ await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
18
18
  try {
19
19
  await (0, client_1.upsertSchema)(schema, true);
20
+ (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
20
21
  }
21
22
  catch (err) {
23
+ if (err.status !== 400) {
24
+ throw err;
25
+ }
22
26
  const invalidConnectors = errors.getInvalidConnectors(err);
27
+ const incompatible = errors.getIncompatibleSchemaError(err);
28
+ if (!incompatible && !invalidConnectors.length) {
29
+ throw err;
30
+ }
23
31
  if (invalidConnectors.length) {
24
32
  displayInvalidConnectors(invalidConnectors);
25
33
  }
26
- const incompatible = errors.getIncompatibleSchemaError(err);
27
34
  if (incompatible) {
28
35
  displaySchemaChanges(incompatible);
29
36
  return incompatible.diffs;
30
37
  }
31
38
  }
32
- (0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
33
39
  return [];
34
40
  }
35
41
  exports.diffSchema = diffSchema;
36
42
  async function migrateSchema(args) {
37
- const { options, schema, allowNonInteractiveMigration, validateOnly } = args;
43
+ const { options, schema, validateOnly } = args;
38
44
  const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
39
- await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId);
45
+ await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
40
46
  try {
41
47
  await (0, client_1.upsertSchema)(schema, validateOnly);
42
48
  logger_1.logger.debug(`Database schema was up to date for ${instanceId}:${databaseId}`);
43
49
  }
44
50
  catch (err) {
51
+ if (err.status !== 400) {
52
+ throw err;
53
+ }
45
54
  const incompatible = errors.getIncompatibleSchemaError(err);
46
55
  const invalidConnectors = errors.getInvalidConnectors(err);
47
56
  if (!incompatible && !invalidConnectors.length) {
48
57
  throw err;
49
58
  }
50
- const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, invalidConnectors, validateOnly);
51
- if (!shouldDeleteInvalidConnectors && invalidConnectors.length) {
52
- const cmd = suggestedCommand(serviceName, invalidConnectors);
53
- throw new error_1.FirebaseError(`Command aborted. Try deploying compatible connectors first with ${clc.bold(cmd)}`);
54
- }
55
- const migrationMode = incompatible
56
- ? await promptForSchemaMigration(options, databaseId, incompatible, allowNonInteractiveMigration)
57
- : "none";
58
- if (migrationMode === "none" && incompatible) {
59
- throw new error_1.FirebaseError("Command aborted.");
60
- }
59
+ const migrationMode = await promptForSchemaMigration(options, databaseId, incompatible, validateOnly);
60
+ const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
61
61
  let diffs = [];
62
62
  if (incompatible) {
63
63
  diffs = await handleIncompatibleSchemaError({
@@ -68,10 +68,12 @@ async function migrateSchema(args) {
68
68
  choice: migrationMode,
69
69
  });
70
70
  }
71
- if (invalidConnectors.length) {
71
+ if (shouldDeleteInvalidConnectors) {
72
72
  await deleteInvalidConnectors(invalidConnectors);
73
73
  }
74
- await (0, client_1.upsertSchema)(schema, validateOnly);
74
+ if (!validateOnly) {
75
+ await (0, client_1.upsertSchema)(schema, validateOnly);
76
+ }
75
77
  return diffs;
76
78
  }
77
79
  return [];
@@ -136,16 +138,22 @@ async function handleIncompatibleSchemaError(args) {
136
138
  }
137
139
  return [];
138
140
  }
139
- async function promptForSchemaMigration(options, databaseName, err, allowNonInteractiveMigration) {
141
+ async function promptForSchemaMigration(options, databaseName, err, validateOnly) {
142
+ if (!err) {
143
+ return "none";
144
+ }
140
145
  displaySchemaChanges(err);
141
146
  if (!options.nonInteractive) {
147
+ if (validateOnly && options.force) {
148
+ return "all";
149
+ }
142
150
  const choices = err.destructive
143
151
  ? [
144
152
  { name: "Execute all changes (including destructive changes)", value: "all" },
145
153
  { name: "Abort changes", value: "none" },
146
154
  ]
147
155
  : [
148
- { name: "Execute changes", value: "safe" },
156
+ { name: "Execute changes", value: "all" },
149
157
  { name: "Abort changes", value: "none" },
150
158
  ];
151
159
  return await (0, prompt_1.promptOnce)({
@@ -154,35 +162,39 @@ async function promptForSchemaMigration(options, databaseName, err, allowNonInte
154
162
  choices,
155
163
  });
156
164
  }
157
- else if (!allowNonInteractiveMigration) {
158
- logger_1.logger.error("Your database schema is incompatible with your Data Connect schema. Run `firebase dataconnect:sql:migrate` to migrate your database schema");
159
- return "none";
165
+ if (!validateOnly) {
166
+ throw new error_1.FirebaseError("Command aborted. Your database schema is incompatible with your Data Connect schema. Run `firebase dataconnect:sql:migrate` to migrate your database schema");
160
167
  }
161
168
  else if (options.force) {
162
169
  return "all";
163
170
  }
164
171
  else if (!err.destructive) {
165
- return "safe";
172
+ return "all";
166
173
  }
167
174
  else {
168
- logger_1.logger.error("This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
169
- return "none";
175
+ throw new error_1.FirebaseError("Command aborted. This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
170
176
  }
171
177
  }
172
- async function promptForInvalidConnectorError(options, invalidConnectors, validateOnly) {
178
+ async function promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly) {
173
179
  if (!invalidConnectors.length) {
174
180
  return false;
175
181
  }
176
182
  displayInvalidConnectors(invalidConnectors);
177
183
  if (validateOnly) {
178
- return false;
184
+ if (options.force) {
185
+ return false;
186
+ }
187
+ throw new error_1.FirebaseError(`Command aborted. If you'd like to migrate it anyway, you may override with --force.`);
179
188
  }
180
- else if (options.force ||
181
- (!options.nonInteractive &&
182
- (await (0, prompt_1.confirm)(Object.assign(Object.assign({}, options), { message: "Would you like to delete and recreate these connectors?" }))))) {
189
+ if (options.force) {
183
190
  return true;
184
191
  }
185
- return false;
192
+ if (!options.nonInteractive &&
193
+ (await (0, prompt_1.confirm)(Object.assign(Object.assign({}, options), { message: `Would you like to delete and recreate these connectors? This will cause ${clc.red(`downtime.`)}.` })))) {
194
+ return true;
195
+ }
196
+ const cmd = suggestedCommand(serviceName, invalidConnectors);
197
+ throw new error_1.FirebaseError(`Command aborted. Try deploying those connectors first with ${clc.bold(cmd)}`);
186
198
  }
187
199
  async function deleteInvalidConnectors(invalidConnectors) {
188
200
  return Promise.all(invalidConnectors.map(client_1.deleteConnector));
@@ -190,39 +202,48 @@ async function deleteInvalidConnectors(invalidConnectors) {
190
202
  function displayInvalidConnectors(invalidConnectors) {
191
203
  const connectorIds = invalidConnectors.map((i) => i.split("/").pop()).join(", ");
192
204
  (0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${connectorIds}.`);
193
- (0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and will cause a brief downtime.`);
205
+ (0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and may break existing apps.`);
194
206
  }
195
- async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, databaseId) {
207
+ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, databaseId, linkIfNotConnected) {
196
208
  let currentSchema;
197
209
  try {
198
210
  currentSchema = await (0, client_1.getSchema)(serviceName);
199
211
  }
200
212
  catch (err) {
201
- if (err.status === 404) {
202
- currentSchema = {
203
- name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
204
- source: {
205
- files: [],
206
- },
207
- primaryDatasource: {
208
- postgresql: {
209
- database: databaseId,
210
- cloudSql: {
211
- instance: instanceId,
212
- },
213
- },
214
- },
215
- };
216
- }
217
- else {
213
+ if (err.status !== 404) {
218
214
  throw err;
219
215
  }
216
+ if (!linkIfNotConnected) {
217
+ (0, utils_1.logLabeledWarning)("dataconnect", `Not yet linked to the Cloud SQL instance.`);
218
+ return;
219
+ }
220
+ (0, utils_1.logLabeledBullet)("dataconnect", `Linking the Cloud SQL instance...`);
221
+ currentSchema = {
222
+ name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
223
+ source: {
224
+ files: [],
225
+ },
226
+ primaryDatasource: {
227
+ postgresql: {
228
+ database: databaseId,
229
+ cloudSql: {
230
+ instance: instanceId,
231
+ },
232
+ },
233
+ },
234
+ };
235
+ }
236
+ const postgresql = currentSchema.primaryDatasource.postgresql;
237
+ if ((postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance) !== instanceId) {
238
+ (0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Cloud SQL instance\nFrom ${postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance}\nTo ${instanceId}`);
220
239
  }
221
- if (!currentSchema.primaryDatasource.postgresql ||
222
- currentSchema.primaryDatasource.postgresql.schemaValidation === "STRICT") {
240
+ if ((postgresql === null || postgresql === void 0 ? void 0 : postgresql.database) !== databaseId) {
241
+ (0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Postgres database from ${postgresql === null || postgresql === void 0 ? void 0 : postgresql.database} to ${databaseId}`);
242
+ }
243
+ if (!postgresql || postgresql.schemaValidation === "STRICT") {
223
244
  return;
224
245
  }
225
- currentSchema.primaryDatasource.postgresql.schemaValidation = "STRICT";
246
+ postgresql.schemaValidation = "STRICT";
226
247
  try {
227
248
  await (0, client_1.upsertSchema)(currentSchema, false);
228
249
  }
@@ -234,10 +255,27 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
234
255
  }
235
256
  }
236
257
  function displaySchemaChanges(error) {
237
- const message = "Your new schema is incompatible with the schema of your CloudSQL database. " +
238
- "The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
239
- error.diffs.map(toString).join("\n");
240
- (0, utils_1.logLabeledWarning)("dataconnect", message);
258
+ switch (error.violationType) {
259
+ case "INCOMPATIBLE_SCHEMA":
260
+ {
261
+ const message = "Your new schema is incompatible with the schema of your CloudSQL database. " +
262
+ "The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
263
+ error.diffs.map(toString).join("\n");
264
+ (0, utils_1.logLabeledWarning)("dataconnect", message);
265
+ }
266
+ break;
267
+ case "INACCESSIBLE_SCHEMA":
268
+ {
269
+ const message = "Cannot access your CloudSQL database to validate schema. " +
270
+ "The following SQL statements can setup a new database schema.\n" +
271
+ error.diffs.map(toString).join("\n");
272
+ (0, utils_1.logLabeledWarning)("dataconnect", message);
273
+ (0, utils_1.logLabeledWarning)("dataconnect", "Some SQL resources may already exist.");
274
+ }
275
+ break;
276
+ default:
277
+ throw new error_1.FirebaseError(`Unknown schema violation type: ${error.violationType}, IncompatibleSqlSchemaError: ${error}`);
278
+ }
241
279
  }
242
280
  function toString(diff) {
243
281
  return `\/** ${diff.destructive ? clc.red("Destructive: ") : ""}${diff.description}*\/\n${(0, sql_formatter_1.format)(diff.sql, { language: "postgresql" })}`;
@@ -21,7 +21,6 @@ async function default_1(context, options) {
21
21
  await (0, schemaMigration_1.migrateSchema)({
22
22
  options,
23
23
  schema: s,
24
- allowNonInteractiveMigration: false,
25
24
  validateOnly: false,
26
25
  });
27
26
  }
@@ -11,6 +11,7 @@ const api = require("../../.../../../../api");
11
11
  const v1alpha1 = require("./v1alpha1");
12
12
  const error_1 = require("../../../../error");
13
13
  exports.readFileAsync = (0, util_1.promisify)(fs.readFile);
14
+ const TIMEOUT_OVERRIDE_ENV_VAR = "FUNCTIONS_DISCOVERY_TIMEOUT";
14
15
  function yamlToBuild(yaml, project, region, runtime) {
15
16
  try {
16
17
  if (!yaml.specVersion) {
@@ -50,7 +51,7 @@ async function detectFromPort(port, project, runtime, timeout = 10000) {
50
51
  const timedOut = new Promise((resolve, reject) => {
51
52
  setTimeout(() => {
52
53
  reject(new error_1.FirebaseError("User code failed to load. Cannot determine backend specification"));
53
- }, timeout);
54
+ }, +(process.env[TIMEOUT_OVERRIDE_ENV_VAR] || 0) * 1000 || timeout);
54
55
  });
55
56
  while (true) {
56
57
  try {
@@ -518,7 +518,6 @@ async function startAll(options, showUI = true, runningTestScript = false) {
518
518
  await startEmulator(pubsubEmulator);
519
519
  }
520
520
  if (listenForEmulator.dataconnect) {
521
- const dataConnectAddr = legacyGetFirstAddr(types_1.Emulators.DATACONNECT);
522
521
  const config = (0, fileUtils_1.readFirebaseJson)(options.config);
523
522
  if (!config.length) {
524
523
  throw new error_1.FirebaseError("No Data Connect service found in firebase.json");
@@ -532,8 +531,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
532
531
  configDir = path.resolve(path.join(cwd), configDir);
533
532
  }
534
533
  const dataConnectEmulator = new dataconnectEmulator_1.DataConnectEmulator({
535
- host: dataConnectAddr.host,
536
- port: dataConnectAddr.port,
534
+ listen: listenForEmulator.dataconnect,
537
535
  projectId,
538
536
  auto_download: true,
539
537
  configDir,
@@ -9,16 +9,15 @@ const types_1 = require("./types");
9
9
  const error_1 = require("../error");
10
10
  const emulatorLogger_1 = require("./emulatorLogger");
11
11
  const types_2 = require("../dataconnect/types");
12
- const grpcDefaultPort = 9510;
12
+ const portUtils_1 = require("./portUtils");
13
13
  class DataConnectEmulator {
14
14
  constructor(args) {
15
15
  this.args = args;
16
16
  this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT);
17
17
  }
18
18
  async start() {
19
- const port = this.args.port || constants_1.Constants.getDefaultPort(types_1.Emulators.DATACONNECT);
20
19
  this.logger.log("DEBUG", `Using Postgres connection string: ${this.getLocalConectionString()}`);
21
- const info = await this.build();
20
+ const info = await DataConnectEmulator.build({ configDir: this.args.configDir });
22
21
  if ((0, types_2.requiresVector)(info.metadata)) {
23
22
  if (constants_1.Constants.isDemoProject(this.args.projectId)) {
24
23
  this.logger.logLabeled("WARN", "Data Connect", "Detected a 'demo-' project, but vector embeddings require a real project. Operations that use vector_embed will fail.");
@@ -27,7 +26,7 @@ class DataConnectEmulator {
27
26
  this.logger.logLabeled("WARN", "Data Connect", "Operations that use vector_embed will make calls to production Vertex AI");
28
27
  }
29
28
  }
30
- return (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, Object.assign(Object.assign({}, this.args), { http_port: port, grpc_port: grpcDefaultPort, config_dir: this.args.configDir, local_connection_string: this.getLocalConectionString(), project_id: this.args.projectId, service_location: this.args.locationId }));
29
+ return (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, Object.assign(Object.assign({}, this.args), { listen: (0, portUtils_1.listenSpecsToString)(this.args.listen), config_dir: this.args.configDir, local_connection_string: this.getLocalConectionString(), project_id: this.args.projectId, service_location: this.args.locationId }));
31
30
  }
32
31
  connect() {
33
32
  return Promise.resolve();
@@ -36,12 +35,11 @@ class DataConnectEmulator {
36
35
  return (0, downloadableEmulators_1.stop)(types_1.Emulators.DATACONNECT);
37
36
  }
38
37
  getInfo() {
39
- const host = this.args.host || constants_1.Constants.getDefaultHost();
40
- const port = this.args.port || constants_1.Constants.getDefaultPort(types_1.Emulators.DATACONNECT);
41
38
  return {
42
39
  name: this.getName(),
43
- host,
44
- port,
40
+ listen: this.args.listen,
41
+ host: this.args.listen[0].address,
42
+ port: this.args.listen[0].port,
45
43
  pid: (0, downloadableEmulators_1.getPID)(types_1.Emulators.DATACONNECT),
46
44
  timeout: 10000,
47
45
  };
@@ -49,25 +47,32 @@ class DataConnectEmulator {
49
47
  getName() {
50
48
  return types_1.Emulators.DATACONNECT;
51
49
  }
52
- async generate(connectorId) {
50
+ static async generate(args) {
53
51
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
54
52
  const cmd = [
55
53
  "generate",
56
- `--service_location=${this.args.locationId}`,
57
- `--config_dir=${this.args.configDir}`,
58
- `--connector_id=${connectorId}`,
54
+ `--service_location=${args.locationId}`,
55
+ `--config_dir=${args.configDir}`,
56
+ `--connector_id=${args.connectorId}`,
59
57
  ];
60
58
  const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
61
59
  if (res.error) {
62
- throw new error_1.FirebaseError(`Error starting up Data Connect emulator: ${res.error}`);
60
+ throw new error_1.FirebaseError(`Error starting up Data Connect generate: ${res.error.message}`, {
61
+ original: res.error,
62
+ });
63
63
  }
64
64
  return res.stdout;
65
65
  }
66
- async build() {
66
+ static async build(args) {
67
67
  var _a;
68
68
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
69
- const cmd = ["build", `--config_dir=${this.args.configDir}`];
69
+ const cmd = ["build", `--config_dir=${args.configDir}`];
70
70
  const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
71
+ if (res.error) {
72
+ throw new error_1.FirebaseError(`Error starting up Data Connect build: ${res.error.message}`, {
73
+ original: res.error,
74
+ });
75
+ }
71
76
  if (res.stderr) {
72
77
  throw new error_1.FirebaseError(`Unable to build your Data Connect schema and connectors: ${res.stderr}`);
73
78
  }
@@ -35,9 +35,9 @@ const EMULATOR_UPDATE_DETAILS = {
35
35
  ui: experiments.isEnabled("emulatoruisnapshot")
36
36
  ? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" }
37
37
  : {
38
- version: "1.11.8",
39
- expectedSize: 3523907,
40
- expectedChecksum: "49f6dc1911dda9d10df62a6c09aaf9a0",
38
+ version: "1.12.1",
39
+ expectedSize: 3498269,
40
+ expectedChecksum: "a7f4398a00e5ca22abdcd78dc3877d00",
41
41
  },
42
42
  pubsub: {
43
43
  version: "0.8.2",
@@ -46,14 +46,14 @@ const EMULATOR_UPDATE_DETAILS = {
46
46
  },
47
47
  dataconnect: process.platform === "darwin"
48
48
  ? {
49
- version: "1.1.17",
50
- expectedSize: 25602224,
51
- expectedChecksum: "1f9e3dd040a0ac4d1cb4d9dde4a3c0b0",
49
+ version: "1.1.18",
50
+ expectedSize: 25836736,
51
+ expectedChecksum: "28a760826968c86cff7e2137b7a487cf",
52
52
  }
53
53
  : {
54
54
  version: "1.1.17",
55
- expectedSize: 23036912,
56
- expectedChecksum: "a0ec0517108f842ed06fea14fe7c7e56",
55
+ expectedSize: 23247120,
56
+ expectedChecksum: "ff2d10655fd0ad16c0fd07e1ca6dac3e",
57
57
  },
58
58
  };
59
59
  exports.DownloadDetails = {
@@ -232,7 +232,13 @@ const Commands = {
232
232
  dataconnect: {
233
233
  binary: getExecPath(types_1.Emulators.DATACONNECT),
234
234
  args: ["dev"],
235
- optionalArgs: ["http_port", "grpc_port", "config_dir", "local_connection_string", "project_id"],
235
+ optionalArgs: [
236
+ "listen",
237
+ "config_dir",
238
+ "local_connection_string",
239
+ "project_id",
240
+ "service_location",
241
+ ],
236
242
  joinArgs: true,
237
243
  shell: true,
238
244
  },
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveHostAndAssignPorts = exports.waitForPortUsed = exports.checkListenable = void 0;
3
+ exports.listenSpecsToString = exports.resolveHostAndAssignPorts = exports.waitForPortUsed = exports.checkListenable = void 0;
4
4
  const clc = require("colorette");
5
5
  const tcpport = require("tcp-port-used");
6
6
  const node_net_1 = require("node:net");
@@ -146,7 +146,7 @@ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
146
146
  firestore: true,
147
147
  "firestore.websocket": true,
148
148
  pubsub: true,
149
- dataconnect: true,
149
+ dataconnect: false,
150
150
  hub: false,
151
151
  ui: false,
152
152
  auth: true,
@@ -321,3 +321,12 @@ function listenSpec(lookup, port) {
321
321
  port: port,
322
322
  };
323
323
  }
324
+ function listenSpecsToString(specs) {
325
+ return specs
326
+ .map((spec) => {
327
+ const host = spec.family === "IPv4" ? spec.address : `[${spec.address}]`;
328
+ return `${host}:${spec.port}`;
329
+ })
330
+ .join(",");
331
+ }
332
+ exports.listenSpecsToString = listenSpecsToString;
@@ -231,7 +231,6 @@ async function getProductionDistDirFiles(sourceDir, distDir) {
231
231
  cwd: (0, path_1.join)(sourceDir, distDir),
232
232
  nodir: true,
233
233
  absolute: false,
234
- realpath: utils_2.IS_WINDOWS,
235
234
  }, (err, matches) => {
236
235
  if (err)
237
236
  reject(err);
@@ -42,6 +42,8 @@ async function createInstance(projectId, location, instanceId, enableGoogleMlInt
42
42
  userLabels: { "firebase-data-connect": "ft" },
43
43
  insightsConfig: {
44
44
  queryInsightsEnabled: true,
45
+ queryPlansPerMinute: 5,
46
+ queryStringLength: 1024,
45
47
  },
46
48
  },
47
49
  });
@@ -16,7 +16,7 @@ async function execute(sqlStatements, opts) {
16
16
  const user = await cloudSqlAdminClient.getUser(opts.projectId, opts.instanceId, opts.username);
17
17
  const connectionName = instance.connectionName;
18
18
  if (!connectionName) {
19
- throw new error_1.FirebaseError(`Could not get instance conection string for ${opts.instanceId}:${opts.databaseId}`);
19
+ throw new error_1.FirebaseError(`Could not get instance connection string for ${opts.instanceId}:${opts.databaseId}`);
20
20
  }
21
21
  let connector;
22
22
  let pool;
@@ -30,7 +30,7 @@ async function execute(sqlStatements, opts) {
30
30
  ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
31
31
  authType: cloud_sql_connector_1.AuthTypes.IAM,
32
32
  });
33
- pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId, max: 1 }));
33
+ pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId }));
34
34
  break;
35
35
  }
36
36
  case "CLOUD_IAM_SERVICE_ACCOUNT": {
@@ -40,7 +40,7 @@ async function execute(sqlStatements, opts) {
40
40
  ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
41
41
  authType: cloud_sql_connector_1.AuthTypes.IAM,
42
42
  });
43
- pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId, max: 1 }));
43
+ pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId }));
44
44
  break;
45
45
  }
46
46
  default: {
@@ -54,19 +54,22 @@ async function execute(sqlStatements, opts) {
54
54
  instanceConnectionName: connectionName,
55
55
  ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
56
56
  });
57
- pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, password: opts.password, database: opts.databaseId, max: 1 }));
57
+ pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, password: opts.password, database: opts.databaseId }));
58
58
  break;
59
59
  }
60
60
  }
61
+ const conn = await pool.connect();
62
+ logFn(`Logged in as ${opts.username}`);
61
63
  for (const s of sqlStatements) {
62
- logFn(`Executing: '${s}' as ${opts.username}`);
64
+ logFn(`Executing: '${s}'`);
63
65
  try {
64
- await pool.query(s);
66
+ await conn.query(s);
65
67
  }
66
68
  catch (err) {
67
69
  throw new error_1.FirebaseError(`Error executing ${err}`);
68
70
  }
69
71
  }
72
+ conn.release();
70
73
  await pool.end();
71
74
  connector.close();
72
75
  }
@@ -45,7 +45,7 @@ async function doSetup(setup, config) {
45
45
  }
46
46
  if (instances.length) {
47
47
  cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
48
- message: `Which CloudSSQL instance would you like to use?`,
48
+ message: `Which CloudSQL instance would you like to use?`,
49
49
  type: "list",
50
50
  choices,
51
51
  });
@@ -20,6 +20,9 @@ function getAuthClient(config) {
20
20
  return authClient;
21
21
  }
22
22
  async function autoAuth(options, authScopes) {
23
+ if (process.env.MONOSPACE_ENV) {
24
+ throw new error_1.FirebaseError("autoAuth not yet implemented for IDX. Please run 'firebase login'");
25
+ }
23
26
  const client = getAuthClient({ scopes: authScopes, projectId: options.project });
24
27
  const token = await client.getAccessToken();
25
28
  token !== null ? apiv2.setAccessToken(token) : false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.9.0",
3
+ "version": "13.10.1",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@google-cloud/cloud-sql-connector": "^1.2.3",
62
- "@google-cloud/pubsub": "^3.0.1",
62
+ "@google-cloud/pubsub": "^4.4.0",
63
63
  "abort-controller": "^3.0.0",
64
64
  "ajv": "^6.12.6",
65
65
  "archiver": "^5.0.0",