firebase-tools 14.19.1 → 14.20.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 (48) hide show
  1. package/lib/appUtils.js +4 -4
  2. package/lib/command.js +1 -0
  3. package/lib/commands/apps-init.js +7 -7
  4. package/lib/commands/dataconnect-execute.js +229 -0
  5. package/lib/commands/firestore-backups-delete.js +1 -6
  6. package/lib/commands/firestore-backups-get.js +2 -8
  7. package/lib/commands/firestore-backups-list.js +4 -10
  8. package/lib/commands/firestore-backups-schedules-create.js +1 -6
  9. package/lib/commands/firestore-backups-schedules-delete.js +1 -6
  10. package/lib/commands/firestore-backups-schedules-list.js +1 -7
  11. package/lib/commands/firestore-backups-schedules-update.js +1 -6
  12. package/lib/commands/firestore-bulkdelete.js +7 -13
  13. package/lib/commands/firestore-databases-create.js +5 -10
  14. package/lib/commands/firestore-databases-delete.js +1 -6
  15. package/lib/commands/firestore-databases-get.js +1 -7
  16. package/lib/commands/firestore-databases-list.js +1 -7
  17. package/lib/commands/firestore-databases-restore.js +5 -10
  18. package/lib/commands/firestore-databases-update.js +1 -6
  19. package/lib/commands/firestore-locations.js +1 -7
  20. package/lib/commands/firestore-operations-cancel.js +3 -9
  21. package/lib/commands/firestore-operations-describe.js +2 -8
  22. package/lib/commands/firestore-operations-list.js +2 -8
  23. package/lib/commands/index.js +1 -0
  24. package/lib/dataconnect/build.js +16 -2
  25. package/lib/dataconnect/load.js +21 -1
  26. package/lib/dataconnect/names.js +6 -1
  27. package/lib/dataconnect/provisionCloudSql.js +38 -11
  28. package/lib/dataconnect/schemaMigration.js +16 -3
  29. package/lib/dataconnect/types.js +1 -10
  30. package/lib/deploy/dataconnect/context.js +26 -0
  31. package/lib/deploy/dataconnect/deploy.js +13 -4
  32. package/lib/deploy/dataconnect/prepare.js +11 -8
  33. package/lib/deploy/dataconnect/release.js +10 -2
  34. package/lib/deploy/index.js +39 -20
  35. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  36. package/lib/gcp/cloudsql/cloudsqladmin.js +4 -3
  37. package/lib/init/features/dataconnect/index.js +22 -6
  38. package/lib/init/features/dataconnect/sdk.js +40 -22
  39. package/lib/management/apps.js +24 -24
  40. package/lib/mcp/prompts/core/consult.js +2 -3
  41. package/lib/mcp/prompts/core/init.js +3 -4
  42. package/lib/mcp/resources/index.js +0 -4
  43. package/lib/mcp/tools/dataconnect/execute.js +0 -1
  44. package/lib/mcp/util/dataconnect/converter.js +5 -4
  45. package/lib/mcp/util/dataconnect/emulator.js +0 -1
  46. package/lib/responseToError.js +7 -6
  47. package/package.json +1 -1
  48. package/lib/dataconnect/appFinder.js +0 -103
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const command_1 = require("../command");
5
5
  const fsi = require("../firestore/api");
6
- const logger_1 = require("../logger");
7
6
  const types_1 = require("../emulator/types");
8
7
  const commandUtils_1 = require("../emulator/commandUtils");
9
8
  const pretty_print_1 = require("../firestore/pretty-print");
@@ -18,12 +17,7 @@ exports.command = new command_1.Command("firestore:operations:list")
18
17
  const limit = options.limit === undefined ? 100 : Number(options.limit);
19
18
  const api = new fsi.FirestoreApi();
20
19
  const { operations } = await api.listOperations(options.project, databaseId, limit);
21
- if (options.json) {
22
- logger_1.logger.info(JSON.stringify(operations, undefined, 2));
23
- }
24
- else {
25
- const printer = new pretty_print_1.PrettyPrint();
26
- printer.prettyPrintOperations(operations);
27
- }
20
+ const printer = new pretty_print_1.PrettyPrint();
21
+ printer.prettyPrintOperations(operations);
28
22
  return operations;
29
23
  });
@@ -226,6 +226,7 @@ function load(client) {
226
226
  client.setup.emulators.ui = loadCommand("setup-emulators-ui");
227
227
  client.dataconnect = {};
228
228
  client.setup.emulators.dataconnect = loadCommand("setup-emulators-dataconnect");
229
+ client.dataconnect.execute = loadCommand("dataconnect-execute");
229
230
  client.dataconnect.services = {};
230
231
  client.dataconnect.services.list = loadCommand("dataconnect-services-list");
231
232
  client.dataconnect.sql = {};
@@ -7,7 +7,7 @@ const prompt_1 = require("../prompt");
7
7
  const utils = require("../utils");
8
8
  const graphqlError_1 = require("./graphqlError");
9
9
  const auth_1 = require("../auth");
10
- async function build(options, configDir, dryRun) {
10
+ async function build(options, configDir, deployStats) {
11
11
  var _a, _b;
12
12
  const account = (0, auth_1.getProjectDefaultAccount)(options.projectRoot);
13
13
  const args = { configDir, account };
@@ -16,7 +16,21 @@ async function build(options, configDir, dryRun) {
16
16
  }
17
17
  const buildResult = await dataconnectEmulator_1.DataConnectEmulator.build(args);
18
18
  if ((_a = buildResult === null || buildResult === void 0 ? void 0 : buildResult.errors) === null || _a === void 0 ? void 0 : _a.length) {
19
- await handleBuildErrors(buildResult.errors, options.nonInteractive, options.force, dryRun);
19
+ buildResult.errors.forEach((e) => {
20
+ var _a, _b;
21
+ if ((_a = e.extensions) === null || _a === void 0 ? void 0 : _a.warningLevel) {
22
+ let key = e.extensions.warningLevel.toLowerCase();
23
+ const msgSp = e.message.split(": ");
24
+ if (msgSp.length >= 2) {
25
+ key += `_${msgSp[0].toLowerCase()}`;
26
+ }
27
+ deployStats.numBuildWarnings.set(key, ((_b = deployStats.numBuildWarnings.get(key)) !== null && _b !== void 0 ? _b : 0) + 1);
28
+ }
29
+ else {
30
+ deployStats.numBuildErrors += 1;
31
+ }
32
+ });
33
+ await handleBuildErrors(buildResult.errors, options.nonInteractive, options.force, options.dryRun);
20
34
  }
21
35
  return (_b = buildResult === null || buildResult === void 0 ? void 0 : buildResult.metadata) !== null && _b !== void 0 ? _b : {};
22
36
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = exports.load = exports.loadAll = exports.pickService = void 0;
3
+ exports.squashGraphQL = exports.readGQLFiles = exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = exports.load = exports.loadAll = exports.pickService = void 0;
4
4
  const path = require("path");
5
5
  const fs = require("fs-extra");
6
6
  const clc = require("colorette");
@@ -130,6 +130,7 @@ async function readGQLFiles(sourceDir) {
130
130
  const files = await (0, glob_1.glob)("**/*.{gql,graphql}", { cwd: sourceDir, absolute: true, nodir: true });
131
131
  return files.map((f) => toFile(sourceDir, f));
132
132
  }
133
+ exports.readGQLFiles = readGQLFiles;
133
134
  function toFile(sourceDir, fullPath) {
134
135
  const relPath = path.relative(sourceDir, fullPath);
135
136
  if (!fs.existsSync(fullPath)) {
@@ -141,3 +142,22 @@ function toFile(sourceDir, fullPath) {
141
142
  content,
142
143
  };
143
144
  }
145
+ function squashGraphQL(source) {
146
+ if (!source.files || !source.files.length) {
147
+ return "";
148
+ }
149
+ if (source.files.length === 1) {
150
+ return source.files[0].content;
151
+ }
152
+ let query = "";
153
+ for (const f of source.files) {
154
+ if (!f.content || !/\S/.test(f.content)) {
155
+ continue;
156
+ }
157
+ query += `### Begin file ${f.path}\n`;
158
+ query += f.content;
159
+ query += `### End file ${f.path}\n`;
160
+ }
161
+ return query;
162
+ }
163
+ exports.squashGraphQL = squashGraphQL;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseCloudSQLInstanceName = exports.parseConnectorName = exports.parseServiceName = void 0;
3
+ exports.isGraphqlName = exports.parseCloudSQLInstanceName = exports.parseConnectorName = exports.parseServiceName = void 0;
4
4
  const error_1 = require("../error");
5
5
  const serviceNameRegex = /projects\/(?<projectId>[^\/]+)\/locations\/(?<location>[^\/]+)\/services\/(?<serviceId>[^\/]+)/;
6
6
  function parseServiceName(serviceName) {
@@ -67,3 +67,8 @@ function parseCloudSQLInstanceName(cloudSQLInstanceName) {
67
67
  };
68
68
  }
69
69
  exports.parseCloudSQLInstanceName = parseCloudSQLInstanceName;
70
+ const graphqlNameRegex = /^[A-Za-z_][A-Za-z0-9_]*$/;
71
+ function isGraphqlName(name) {
72
+ return graphqlNameRegex.test(name);
73
+ }
74
+ exports.isGraphqlName = isGraphqlName;
@@ -1,27 +1,50 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getUpdateReason = exports.cloudSQLBeingCreated = exports.setupCloudSql = void 0;
4
- const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
5
- const utils = require("../utils");
6
4
  const clc = require("colorette");
7
- const checkIam_1 = require("./checkIam");
8
- const utils_1 = require("../utils");
5
+ const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
9
6
  const logger_1 = require("../logger");
7
+ const checkIam_1 = require("./checkIam");
10
8
  const freeTrial_1 = require("./freeTrial");
9
+ const utils_1 = require("../utils");
10
+ const track_1 = require("../track");
11
+ const utils = require("../utils");
11
12
  const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
12
13
  async function setupCloudSql(args) {
13
- await upsertInstance(Object.assign({}, args));
14
+ var _a;
14
15
  const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
16
+ const startTime = Date.now();
17
+ const stats = { action: "get" };
18
+ let success = false;
19
+ try {
20
+ await upsertInstance(stats, Object.assign({}, args));
21
+ success = true;
22
+ }
23
+ finally {
24
+ if (!dryRun) {
25
+ await (0, track_1.trackGA4)("dataconnect_cloud_sql", {
26
+ source: args.source,
27
+ action: success ? stats.action : `${stats.action}_error`,
28
+ location: args.location,
29
+ enable_google_ml_integration: args.requireGoogleMlIntegration.toString(),
30
+ database_version: ((_a = stats.databaseVersion) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || "unknown",
31
+ dataconnect_label: stats.dataconnectLabel || "unknown",
32
+ }, Date.now() - startTime);
33
+ }
34
+ }
15
35
  if (requireGoogleMlIntegration && !dryRun) {
16
36
  await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
17
37
  }
18
38
  }
19
39
  exports.setupCloudSql = setupCloudSql;
20
- async function upsertInstance(args) {
40
+ async function upsertInstance(stats, args) {
41
+ var _a, _b;
21
42
  const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
22
43
  try {
23
44
  const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
24
45
  utils.logLabeledBullet("dataconnect", `Found existing Cloud SQL instance ${clc.bold(instanceId)}.`);
46
+ stats.databaseVersion = existingInstance.databaseVersion;
47
+ stats.dataconnectLabel = (_b = (_a = existingInstance.settings) === null || _a === void 0 ? void 0 : _a.userLabels) === null || _b === void 0 ? void 0 : _b["firebase-data-connect"];
25
48
  const why = getUpdateReason(existingInstance, requireGoogleMlIntegration);
26
49
  if (why) {
27
50
  if (dryRun) {
@@ -32,6 +55,7 @@ async function upsertInstance(args) {
32
55
  else {
33
56
  utils.logLabeledBullet("dataconnect", `Cloud SQL instance ${clc.bold(instanceId)} settings are not compatible with Firebase Data Connect. ` +
34
57
  why);
58
+ stats.action = "update";
35
59
  await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.updateInstanceForDataConnect(existingInstance, requireGoogleMlIntegration), "Updating your Cloud SQL instance...");
36
60
  }
37
61
  }
@@ -41,12 +65,15 @@ async function upsertInstance(args) {
41
65
  if (err.status !== 404) {
42
66
  throw err;
43
67
  }
44
- await createInstance(Object.assign({}, args));
68
+ stats.action = "create";
69
+ stats.databaseVersion = cloudSqlAdminClient.DEFAULT_DATABASE_VERSION;
70
+ const freeTrialUsed = await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId);
71
+ stats.dataconnectLabel = freeTrialUsed ? "nt" : "ft";
72
+ await createInstance(Object.assign(Object.assign({}, args), { freeTrialLabel: stats.dataconnectLabel }));
45
73
  }
46
74
  }
47
75
  async function createInstance(args) {
48
- const { projectId, location, instanceId, requireGoogleMlIntegration, dryRun } = args;
49
- const freeTrialUsed = await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId);
76
+ const { projectId, location, instanceId, requireGoogleMlIntegration, dryRun, freeTrialLabel } = args;
50
77
  if (dryRun) {
51
78
  utils.logLabeledBullet("dataconnect", `Cloud SQL Instance ${clc.bold(instanceId)} not found. It will be created on your next deploy.`);
52
79
  }
@@ -56,9 +83,9 @@ async function createInstance(args) {
56
83
  location,
57
84
  instanceId,
58
85
  enableGoogleMlIntegration: requireGoogleMlIntegration,
59
- freeTrial: !freeTrialUsed,
86
+ freeTrialLabel,
60
87
  });
61
- utils.logLabeledBullet("dataconnect", cloudSQLBeingCreated(projectId, instanceId, !freeTrialUsed));
88
+ utils.logLabeledBullet("dataconnect", cloudSQLBeingCreated(projectId, instanceId, freeTrialLabel === "ft"));
62
89
  }
63
90
  }
64
91
  function cloudSQLBeingCreated(projectId, instanceId, includeFreeTrialToS) {
@@ -104,7 +104,7 @@ async function diffSchema(options, schema, schemaValidation) {
104
104
  exports.diffSchema = diffSchema;
105
105
  async function migrateSchema(args) {
106
106
  var _a;
107
- const { options, schema, validateOnly, schemaValidation } = args;
107
+ const { options, schema, validateOnly, schemaValidation, stats } = args;
108
108
  let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
109
109
  setSchemaValidationMode(schema, validationMode);
110
110
  displayStartSchemaDiff(validationMode);
@@ -113,6 +113,9 @@ async function migrateSchema(args) {
113
113
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
114
114
  const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
115
115
  if (existingInstance.state === "PENDING_CREATE") {
116
+ if (stats) {
117
+ stats.numSchemaSkippedDueToPendingCreate++;
118
+ }
116
119
  const postgresql = (_a = schema.datasources.find((d) => d.postgresql)) === null || _a === void 0 ? void 0 : _a.postgresql;
117
120
  if (!postgresql) {
118
121
  throw new error_1.FirebaseError(`Cannot find Postgres datasource in the schema to deploy: ${serviceName}/schemas/${types_1.SCHEMA_ID}.\nIts datasources: ${JSON.stringify(schema.datasources)}`);
@@ -145,6 +148,14 @@ async function migrateSchema(args) {
145
148
  }
146
149
  throw err;
147
150
  }
151
+ if (stats) {
152
+ if (incompatible) {
153
+ stats.numSchemaSqlDiffs += incompatible.diffs.length;
154
+ }
155
+ if (invalidConnectors.length) {
156
+ stats.numSchemaInvalidConnectors += invalidConnectors.length;
157
+ }
158
+ }
148
159
  const migrationMode = await promptForSchemaMigration(options, instanceId, databaseId, incompatible, validateOnly, validationMode);
149
160
  const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
150
161
  if (incompatible) {
@@ -174,10 +185,12 @@ async function migrateSchema(args) {
174
185
  throw err;
175
186
  }
176
187
  const incompatible = errors.getIncompatibleSchemaError(err);
177
- const invalidConnectors = errors.getInvalidConnectors(err);
178
- if (!incompatible && !invalidConnectors.length) {
188
+ if (!incompatible) {
179
189
  throw err;
180
190
  }
191
+ if (stats && incompatible) {
192
+ stats.numSchemaSqlDiffs += incompatible.diffs.length;
193
+ }
181
194
  const migrationMode = await promptForSchemaMigration(options, instanceId, databaseId, incompatible, validateOnly, "STRICT_AFTER_COMPATIBLE");
182
195
  if (incompatible) {
183
196
  const maybeDiffs = await handleIncompatibleSchemaError({
@@ -1,21 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isGraphQLResponseError = exports.isGraphQLResponse = exports.toDatasource = exports.Platform = exports.requiresVector = exports.SCHEMA_ID = void 0;
3
+ exports.isGraphQLResponseError = exports.isGraphQLResponse = exports.toDatasource = exports.requiresVector = exports.SCHEMA_ID = void 0;
4
4
  exports.SCHEMA_ID = "main";
5
5
  function requiresVector(dm) {
6
6
  var _a, _b, _c, _d;
7
7
  return (_d = (_c = (_b = (_a = dm === null || dm === void 0 ? void 0 : dm.primaryDataSource) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.requiredExtensions) === null || _c === void 0 ? void 0 : _c.includes("vector")) !== null && _d !== void 0 ? _d : false;
8
8
  }
9
9
  exports.requiresVector = requiresVector;
10
- var Platform;
11
- (function (Platform) {
12
- Platform["NONE"] = "NONE";
13
- Platform["ANDROID"] = "ANDROID";
14
- Platform["WEB"] = "WEB";
15
- Platform["IOS"] = "IOS";
16
- Platform["FLUTTER"] = "FLUTTER";
17
- Platform["MULTIPLE"] = "MULTIPLE";
18
- })(Platform = exports.Platform || (exports.Platform = {}));
19
10
  function toDatasource(projectId, locationId, ds) {
20
11
  if (ds === null || ds === void 0 ? void 0 : ds.postgresql) {
21
12
  return {
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deployStatsParams = exports.initDeployStats = void 0;
4
+ function initDeployStats() {
5
+ return {
6
+ numBuildErrors: 0,
7
+ numBuildWarnings: new Map(),
8
+ numServiceCreated: 0,
9
+ numServiceDeleted: 0,
10
+ numSchemaMigrated: 0,
11
+ numConnectorUpdatedBeforeSchema: 0,
12
+ numConnectorUpdatedAfterSchema: 0,
13
+ numSchemaSkippedDueToPendingCreate: 0,
14
+ numSchemaSqlDiffs: 0,
15
+ numSchemaInvalidConnectors: 0,
16
+ };
17
+ }
18
+ exports.initDeployStats = initDeployStats;
19
+ function deployStatsParams(stats) {
20
+ const buildWarnings = {};
21
+ for (const [type, num] of stats.numBuildWarnings.entries()) {
22
+ buildWarnings[`num_build_warnings_${type}`] = num;
23
+ }
24
+ return Object.assign({ missing_billing: (!!stats.missingBilling).toString(), num_service_created: stats.numServiceCreated, num_service_deleted: stats.numServiceDeleted, num_schema_migrated: stats.numSchemaMigrated, num_connector_updated_before_schema: stats.numConnectorUpdatedBeforeSchema, num_connector_updated_after_schema: stats.numConnectorUpdatedAfterSchema, num_schema_skipped_due_to_pending_create: stats.numSchemaSkippedDueToPendingCreate, num_schema_sql_diffs: stats.numSchemaSqlDiffs, num_schema_invalid_connectors: stats.numSchemaInvalidConnectors, num_build_errors: stats.numBuildErrors }, buildWarnings);
25
+ }
26
+ exports.deployStatsParams = deployStatsParams;
@@ -10,10 +10,14 @@ const api_1 = require("../../api");
10
10
  const ensureApiEnabled = require("../../ensureApiEnabled");
11
11
  const prompt_1 = require("../../prompt");
12
12
  async function default_1(context, options) {
13
+ const dataconnect = context.dataconnect;
14
+ if (!dataconnect) {
15
+ throw new Error("dataconnect.prepare must be run before dataconnect.deploy");
16
+ }
13
17
  const projectId = (0, projectUtils_1.needProjectId)(options);
14
- const serviceInfos = context.dataconnect.serviceInfos;
18
+ const serviceInfos = dataconnect.serviceInfos;
15
19
  const services = await client.listAllServices(projectId);
16
- const filters = context.dataconnect.filters;
20
+ const filters = dataconnect.filters;
17
21
  if (serviceInfos.some((si) => {
18
22
  return (0, types_1.requiresVector)(si.deploymentMetadata);
19
23
  })) {
@@ -22,11 +26,14 @@ async function default_1(context, options) {
22
26
  const servicesToCreate = serviceInfos
23
27
  .filter((si) => !services.some((s) => matches(si, s)))
24
28
  .filter((si) => {
25
- return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
29
+ return (!filters ||
30
+ (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId)));
26
31
  });
32
+ dataconnect.deployStats.numServiceCreated = servicesToCreate.length;
27
33
  const servicesToDelete = filters
28
34
  ? []
29
35
  : services.filter((s) => !serviceInfos.some((si) => matches(si, s)));
36
+ dataconnect.deployStats.numServiceDeleted = servicesToDelete.length;
30
37
  await Promise.all(servicesToCreate.map(async (s) => {
31
38
  const { projectId, locationId, serviceId } = splitName(s.serviceName);
32
39
  await client.createService(projectId, locationId, serviceId);
@@ -49,7 +56,8 @@ async function default_1(context, options) {
49
56
  utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
50
57
  await Promise.all(serviceInfos
51
58
  .filter((si) => {
52
- return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
59
+ return (!filters ||
60
+ (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId)));
53
61
  })
54
62
  .map(async (s) => {
55
63
  var _a, _b, _c;
@@ -66,6 +74,7 @@ async function default_1(context, options) {
66
74
  instanceId,
67
75
  databaseId,
68
76
  requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
77
+ source: "deploy",
69
78
  });
70
79
  }
71
80
  }));
@@ -17,18 +17,24 @@ const error_1 = require("../../error");
17
17
  const types_1 = require("../../dataconnect/types");
18
18
  const schemaMigration_1 = require("../../dataconnect/schemaMigration");
19
19
  const freeTrial_1 = require("../../dataconnect/freeTrial");
20
+ const context_1 = require("./context");
20
21
  async function default_1(context, options) {
21
22
  var _a, _b, _c;
22
23
  const projectId = (0, projectUtils_1.needProjectId)(options);
24
+ await (0, ensureApis_1.ensureApis)(projectId);
25
+ context.dataconnect = {
26
+ serviceInfos: await (0, load_1.loadAll)(projectId, options.config),
27
+ filters: (0, filters_1.getResourceFilters)(options),
28
+ deployStats: (0, context_1.initDeployStats)(),
29
+ };
30
+ const { serviceInfos, filters, deployStats } = context.dataconnect;
23
31
  if (!(await (0, cloudbilling_1.checkBillingEnabled)(projectId))) {
32
+ deployStats.missingBilling = true;
24
33
  throw new error_1.FirebaseError((0, freeTrial_1.upgradeInstructions)(projectId));
25
34
  }
26
- await (0, ensureApis_1.ensureApis)(projectId);
27
35
  await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
28
- const filters = (0, filters_1.getResourceFilters)(options);
29
- const serviceInfos = await (0, load_1.loadAll)(projectId, options.config);
30
36
  for (const si of serviceInfos) {
31
- si.deploymentMetadata = await (0, build_1.build)(options, si.sourceDirectory, options.dryRun);
37
+ si.deploymentMetadata = await (0, build_1.build)(options, si.sourceDirectory, deployStats);
32
38
  }
33
39
  const unmatchedFilters = filters === null || filters === void 0 ? void 0 : filters.filter((f) => {
34
40
  const serviceMatched = serviceInfos.some((s) => s.dataConnectYaml.serviceId === f.serviceId);
@@ -43,10 +49,6 @@ async function default_1(context, options) {
43
49
  if (unmatchedFilters === null || unmatchedFilters === void 0 ? void 0 : unmatchedFilters.length) {
44
50
  throw new error_1.FirebaseError(`The following filters were specified in --only but didn't match anything in this project: ${unmatchedFilters.map(filters_1.toString).map(clc.bold).join(", ")}`);
45
51
  }
46
- context.dataconnect = {
47
- serviceInfos,
48
- filters,
49
- };
50
52
  utils.logLabeledBullet("dataconnect", `Successfully compiled schema and connectors`);
51
53
  if (options.dryRun) {
52
54
  for (const si of serviceInfos) {
@@ -73,6 +75,7 @@ async function default_1(context, options) {
73
75
  databaseId,
74
76
  requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
75
77
  dryRun: true,
78
+ source: "deploy",
76
79
  });
77
80
  }
78
81
  }));
@@ -8,9 +8,13 @@ const projectUtils_1 = require("../../projectUtils");
8
8
  const names_1 = require("../../dataconnect/names");
9
9
  const logger_1 = require("../../logger");
10
10
  async function default_1(context, options) {
11
+ const dataconnect = context.dataconnect;
12
+ if (!dataconnect) {
13
+ throw new Error("dataconnect.prepare must be run before dataconnect.release");
14
+ }
11
15
  const project = (0, projectUtils_1.needProjectId)(options);
12
- const serviceInfos = context.dataconnect.serviceInfos;
13
- const filters = context.dataconnect.filters;
16
+ const serviceInfos = dataconnect.serviceInfos;
17
+ const filters = dataconnect.filters;
14
18
  const wantSchemas = serviceInfos
15
19
  .filter((si) => {
16
20
  return (!filters ||
@@ -43,6 +47,7 @@ async function default_1(context, options) {
43
47
  return c;
44
48
  }
45
49
  utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
50
+ dataconnect.deployStats.numConnectorUpdatedBeforeSchema++;
46
51
  return undefined;
47
52
  }));
48
53
  for (const s of wantSchemas) {
@@ -51,13 +56,16 @@ async function default_1(context, options) {
51
56
  schema: s.schema,
52
57
  validateOnly: false,
53
58
  schemaValidation: s.validationMode,
59
+ stats: dataconnect.deployStats,
54
60
  });
55
61
  utils.logLabeledSuccess("dataconnect", `Migrated schema ${s.schema.name}`);
62
+ dataconnect.deployStats.numSchemaMigrated++;
56
63
  }
57
64
  await Promise.all(remainingConnectors.map(async (c) => {
58
65
  if (c) {
59
66
  await (0, client_1.upsertConnector)(c);
60
67
  utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
68
+ dataconnect.deployStats.numConnectorUpdatedAfterSchema++;
61
69
  }
62
70
  }));
63
71
  const allConnectors = await deployedConnectors(serviceInfos);
@@ -26,6 +26,7 @@ const prepare_1 = require("./hosting/prepare");
26
26
  const github_1 = require("../init/features/hosting/github");
27
27
  const deploy_1 = require("../commands/deploy");
28
28
  const requirePermissions_1 = require("../requirePermissions");
29
+ const context_1 = require("./dataconnect/context");
29
30
  const TARGETS = {
30
31
  hosting: HostingTarget,
31
32
  database: DatabaseTarget,
@@ -63,7 +64,7 @@ const isDeployingWebFramework = (options) => {
63
64
  };
64
65
  exports.isDeployingWebFramework = isDeployingWebFramework;
65
66
  const deploy = async function (targetNames, options, customContext = {}) {
66
- var _a, _b, _c;
67
+ var _a, _b, _c, _d;
67
68
  const projectId = (0, projectUtils_1.needProjectId)(options);
68
69
  const payload = {};
69
70
  const context = Object.assign({ projectId }, customContext);
@@ -115,34 +116,52 @@ const deploy = async function (targetNames, options, customContext = {}) {
115
116
  logger_1.logger.info((0, colorette_1.bold)((0, colorette_1.white)("===") + " Deploying to '" + projectId + "'..."));
116
117
  logger_1.logger.info();
117
118
  (0, utils_1.logBullet)("deploying " + (0, colorette_1.bold)(targetNames.join(", ")));
118
- await chain(predeploys, context, options, payload);
119
- await chain(prepares, context, options, payload);
120
- await chain(deploys, context, options, payload);
121
- await chain(releases, context, options, payload);
122
- await chain(postdeploys, context, options, payload);
123
- const duration = Date.now() - startTime;
124
- const analyticsParams = {
125
- interactive: options.nonInteractive ? "false" : "true",
126
- };
127
- Object.keys(TARGETS).reduce((accum, t) => {
128
- accum[t] = "false";
129
- return accum;
130
- }, analyticsParams);
131
- for (const t of targetNames) {
132
- analyticsParams[t] = "true";
119
+ let result = "predeploys_error";
120
+ try {
121
+ await chain(predeploys, context, options, payload);
122
+ result = "prepares_error";
123
+ await chain(prepares, context, options, payload);
124
+ result = "deploys_error";
125
+ await chain(deploys, context, options, payload);
126
+ result = "releases_error";
127
+ await chain(releases, context, options, payload);
128
+ result = "postdeploys_error";
129
+ await chain(postdeploys, context, options, payload);
130
+ result = "success";
131
+ }
132
+ finally {
133
+ const baseParams = {
134
+ interactive: options.nonInteractive ? "false" : "true",
135
+ dry_run: options.dryRun ? "true" : "false",
136
+ result: result,
137
+ };
138
+ const duration = Date.now() - startTime;
139
+ const params = Object.assign({}, baseParams);
140
+ Object.keys(TARGETS).reduce((accum, t) => {
141
+ accum[t] = "false";
142
+ return accum;
143
+ }, params);
144
+ for (const t of targetNames) {
145
+ params[t] = "true";
146
+ }
147
+ void (0, track_1.trackGA4)("product_deploy", params, duration);
148
+ const stats = (_a = context === null || context === void 0 ? void 0 : context.dataconnect) === null || _a === void 0 ? void 0 : _a.deployStats;
149
+ if (stats) {
150
+ const fdcParams = (0, context_1.deployStatsParams)(stats);
151
+ void (0, track_1.trackGA4)("dataconnect_deploy", Object.assign(Object.assign({}, fdcParams), baseParams), duration);
152
+ }
133
153
  }
134
- await (0, track_1.trackGA4)("product_deploy", analyticsParams, duration);
135
154
  const successMessage = options.dryRun ? "Dry run complete!" : "Deploy complete!";
136
155
  logger_1.logger.info();
137
156
  (0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)(successMessage)));
138
157
  logger_1.logger.info();
139
158
  const deployedHosting = (0, lodash_1.includes)(targetNames, "hosting");
140
- logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)((_a = options.project) !== null && _a !== void 0 ? _a : "_", "/overview"));
159
+ logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)((_b = options.project) !== null && _b !== void 0 ? _b : "_", "/overview"));
141
160
  if (deployedHosting) {
142
- (0, lodash_1.each)((_b = context.hosting) === null || _b === void 0 ? void 0 : _b.deploys, (deploy) => {
161
+ (0, lodash_1.each)((_c = context.hosting) === null || _c === void 0 ? void 0 : _c.deploys, (deploy) => {
143
162
  logger_1.logger.info((0, colorette_1.bold)("Hosting URL:"), (0, utils_1.addSubdomain)((0, api_1.hostingOrigin)(), deploy.config.site));
144
163
  });
145
- const versionNames = (_c = context.hosting) === null || _c === void 0 ? void 0 : _c.deploys.map((deploy) => deploy.version);
164
+ const versionNames = (_d = context.hosting) === null || _d === void 0 ? void 0 : _d.deploys.map((deploy) => deploy.version);
146
165
  return { hosting: (versionNames === null || versionNames === void 0 ? void 0 : versionNames.length) === 1 ? versionNames[0] : versionNames };
147
166
  }
148
167
  else {
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.14.0",
58
- "expectedSize": 29602656,
59
- "expectedChecksum": "4570cfde37b7def2ac9ead63752dda34",
60
- "expectedChecksumSHA256": "88d3e13e860be59bc3520fe685b695be51341d13203838d48e7502c62504acb8",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.14.0",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.14.0"
57
+ "version": "2.15.0",
58
+ "expectedSize": 29610848,
59
+ "expectedChecksum": "182ddca17f4974ec081fd5a769f8eeac",
60
+ "expectedChecksumSHA256": "3bcdc39b7f149f8c2d96f397188ca003daf1b43a1ce845780146b79217970a52",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.15.0",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0"
63
63
  },
64
64
  "win32": {
65
- "version": "2.14.0",
66
- "expectedSize": 30091264,
67
- "expectedChecksum": "43338765d5f7ec2fabb6fe8f212bab82",
68
- "expectedChecksumSHA256": "1de9ce32f4958c6e1e56ca7a0a7f5f4364d6cd17de77d08f76d3638830bc3c16",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.14.0",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.14.0.exe"
65
+ "version": "2.15.0",
66
+ "expectedSize": 30099456,
67
+ "expectedChecksum": "5dd4efd368c3987925c41784e21c7af6",
68
+ "expectedChecksumSHA256": "a664f29cd21f826dd4936f8b95f0673e0ea603ab0d85a426d8231f1aaa8947f9",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.15.0",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.14.0",
74
- "expectedSize": 29524152,
75
- "expectedChecksum": "711dd1e6d97bbeabc070efcdf0a22ee4",
76
- "expectedChecksumSHA256": "bc468019bfb87b846adf02f21f65e5243854dbeee72258fe98f5e3201d1b1363",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.14.0",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.14.0"
73
+ "version": "2.15.0",
74
+ "expectedSize": 29532344,
75
+ "expectedChecksum": "38baf423fbfc9c67a89975a27f0a3974",
76
+ "expectedChecksumSHA256": "d3c5b26470639fedce9e3ac667948fa07473e77f6c2d0b361d78c5f2e856a1ec",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.15.0",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.15.0"
79
79
  }
80
80
  }
81
81
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.listUsers = exports.deleteUser = exports.getUser = exports.createUser = exports.deleteDatabase = exports.createDatabase = exports.getDatabase = exports.listDatabases = exports.updateInstanceForDataConnect = exports.createInstance = exports.instanceConsoleLink = exports.getInstance = exports.listInstances = exports.iamUserIsCSQLAdmin = void 0;
3
+ exports.listUsers = exports.deleteUser = exports.getUser = exports.createUser = exports.deleteDatabase = exports.createDatabase = exports.getDatabase = exports.listDatabases = exports.updateInstanceForDataConnect = exports.createInstance = exports.DEFAULT_DATABASE_VERSION = exports.instanceConsoleLink = exports.getInstance = exports.listInstances = exports.iamUserIsCSQLAdmin = void 0;
4
4
  const apiv2_1 = require("../../apiv2");
5
5
  const api_1 = require("../../api");
6
6
  const clc = require("colorette");
@@ -51,6 +51,7 @@ function instanceConsoleLink(projectId, instanceId) {
51
51
  return `https://console.cloud.google.com/sql/instances/${instanceId}/overview?project=${projectId}`;
52
52
  }
53
53
  exports.instanceConsoleLink = instanceConsoleLink;
54
+ exports.DEFAULT_DATABASE_VERSION = "POSTGRES_15";
54
55
  async function createInstance(args) {
55
56
  const databaseFlags = [{ name: "cloudsql.iam_authentication", value: "on" }];
56
57
  if (args.enableGoogleMlIntegration) {
@@ -60,7 +61,7 @@ async function createInstance(args) {
60
61
  await client.post(`projects/${args.projectId}/instances`, {
61
62
  name: args.instanceId,
62
63
  region: args.location,
63
- databaseVersion: "POSTGRES_15",
64
+ databaseVersion: exports.DEFAULT_DATABASE_VERSION,
64
65
  settings: {
65
66
  tier: "db-f1-micro",
66
67
  edition: "ENTERPRISE",
@@ -70,7 +71,7 @@ async function createInstance(args) {
70
71
  enableGoogleMlIntegration: args.enableGoogleMlIntegration,
71
72
  databaseFlags,
72
73
  storageAutoResize: false,
73
- userLabels: { "firebase-data-connect": args.freeTrial ? "ft" : "nt" },
74
+ userLabels: { "firebase-data-connect": args.freeTrialLabel },
74
75
  insightsConfig: {
75
76
  queryInsightsEnabled: true,
76
77
  queryPlansPerMinute: 5,