firebase-tools 14.12.0 → 14.13.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 (80) hide show
  1. package/README.md +1 -1
  2. package/lib/commands/dataconnect-services-list.js +5 -5
  3. package/lib/commands/dataconnect-sql-grant.js +5 -0
  4. package/lib/commands/dataconnect-sql-setup.js +1 -3
  5. package/lib/commands/firestore-databases-create.js +11 -0
  6. package/lib/crashlytics/buildToolsJarHelper.js +1 -2
  7. package/lib/crashlytics/getIssueDetails.js +41 -0
  8. package/lib/crashlytics/getSampleCrash.js +48 -0
  9. package/lib/dataconnect/client.js +23 -15
  10. package/lib/dataconnect/ensureApis.js +5 -9
  11. package/lib/dataconnect/fileUtils.js +5 -6
  12. package/lib/dataconnect/freeTrial.js +16 -39
  13. package/lib/dataconnect/provisionCloudSql.js +67 -70
  14. package/lib/dataconnect/schemaMigration.js +75 -47
  15. package/lib/deploy/dataconnect/deploy.js +9 -11
  16. package/lib/deploy/dataconnect/prepare.js +9 -12
  17. package/lib/deploy/dataconnect/release.js +13 -7
  18. package/lib/deploy/firestore/deploy.js +10 -0
  19. package/lib/deploy/functions/backend.js +8 -2
  20. package/lib/deploy/functions/build.js +23 -1
  21. package/lib/deploy/functions/ensure.js +1 -1
  22. package/lib/deploy/functions/functionsDeployHelper.js +8 -1
  23. package/lib/deploy/functions/prepare.js +6 -4
  24. package/lib/deploy/functions/prepareFunctionsUpload.js +3 -1
  25. package/lib/deploy/functions/pricing.js +12 -5
  26. package/lib/deploy/functions/release/fabricator.js +25 -3
  27. package/lib/emulator/controller.js +2 -1
  28. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  29. package/lib/emulator/functionsEmulator.js +9 -1
  30. package/lib/experiments.js +4 -0
  31. package/lib/extensions/extensionsHelper.js +4 -15
  32. package/lib/extensions/utils.js +1 -12
  33. package/lib/firestore/api-sort.js +96 -3
  34. package/lib/firestore/api-types.js +14 -1
  35. package/lib/firestore/api.js +85 -4
  36. package/lib/firestore/pretty-print.js +7 -0
  37. package/lib/firestore/validator.js +1 -1
  38. package/lib/functional.js +7 -1
  39. package/lib/functions/deprecationWarnings.js +4 -4
  40. package/lib/functions/projectConfig.js +25 -2
  41. package/lib/functions/secrets.js +3 -0
  42. package/lib/gcp/cloudfunctionsv2.js +3 -31
  43. package/lib/gcp/cloudscheduler.js +1 -1
  44. package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
  45. package/lib/gcp/cloudsql/connect.js +2 -2
  46. package/lib/gcp/cloudsql/permissionsSetup.js +13 -15
  47. package/lib/gcp/k8s.js +32 -0
  48. package/lib/gcp/runv2.js +178 -0
  49. package/lib/gemini/fdcExperience.js +5 -3
  50. package/lib/init/features/dataconnect/index.js +266 -162
  51. package/lib/init/features/dataconnect/sdk.js +32 -17
  52. package/lib/init/features/project.js +4 -0
  53. package/lib/management/studio.js +1 -1
  54. package/lib/mcp/index.js +75 -2
  55. package/lib/mcp/prompt.js +10 -0
  56. package/lib/mcp/prompts/core/deploy.js +58 -0
  57. package/lib/mcp/prompts/core/index.js +5 -0
  58. package/lib/mcp/prompts/index.js +45 -0
  59. package/lib/mcp/tools/core/get_sdk_config.js +10 -0
  60. package/lib/mcp/tools/core/init.js +7 -6
  61. package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
  62. package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
  63. package/lib/mcp/tools/crashlytics/index.js +7 -1
  64. package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
  65. package/lib/mcp/tools/database/get_data.js +49 -0
  66. package/lib/mcp/tools/database/get_rules.js +39 -0
  67. package/lib/mcp/tools/database/index.js +8 -0
  68. package/lib/mcp/tools/database/set_data.js +57 -0
  69. package/lib/mcp/tools/database/set_rules.js +41 -0
  70. package/lib/mcp/tools/database/validate_rules.js +41 -0
  71. package/lib/mcp/tools/index.js +4 -1
  72. package/lib/mcp/tools/rules/get_rules.js +1 -1
  73. package/lib/mcp/types.js +2 -0
  74. package/lib/mcp/util.js +2 -0
  75. package/lib/rtdb.js +10 -6
  76. package/lib/utils.js +24 -1
  77. package/package.json +1 -1
  78. package/schema/firebase-config.json +6 -0
  79. package/templates/init/firestore/firestore.indexes.json +26 -1
  80. package/lib/extensions/resolveSource.js +0 -24
@@ -16,26 +16,33 @@ const utils_1 = require("../utils");
16
16
  const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
17
17
  const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
18
18
  const errors = require("./errors");
19
+ const provisionCloudSql_1 = require("./provisionCloudSql");
19
20
  async function setupSchemaIfNecessary(instanceId, databaseId, options) {
20
- await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
21
- const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
22
- if (schemaInfo.setupStatus !== permissionsSetup_1.SchemaSetupStatus.BrownField &&
23
- schemaInfo.setupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField) {
24
- return await (0, permissionsSetup_1.setupSQLPermissions)(instanceId, databaseId, schemaInfo, options, true);
21
+ try {
22
+ await (0, connect_1.setupIAMUsers)(instanceId, options);
23
+ const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
24
+ switch (schemaInfo.setupStatus) {
25
+ case permissionsSetup_1.SchemaSetupStatus.BrownField:
26
+ case permissionsSetup_1.SchemaSetupStatus.GreenField:
27
+ return schemaInfo.setupStatus;
28
+ case permissionsSetup_1.SchemaSetupStatus.NotSetup:
29
+ case permissionsSetup_1.SchemaSetupStatus.NotFound:
30
+ return await (0, permissionsSetup_1.setupSQLPermissions)(instanceId, databaseId, schemaInfo, options, true);
31
+ default:
32
+ throw new error_1.FirebaseError(`Unexpected schema setup status: ${schemaInfo.setupStatus}`);
33
+ }
25
34
  }
26
- else {
27
- logger_1.logger.debug(`Detected schema "${schemaInfo.name}" is setup in ${schemaInfo.setupStatus} mode. Skipping Setup.`);
35
+ catch (err) {
36
+ throw new error_1.FirebaseError(`Cannot setup SQL schema permissions of ${instanceId}:${databaseId}\n${err}`);
28
37
  }
29
- return schemaInfo.setupStatus;
30
38
  }
31
39
  async function diffSchema(options, schema, schemaValidation) {
32
- (0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
40
+ (0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations...`);
33
41
  const { serviceName, instanceName, databaseId, instanceId } = getIdentifiers(schema);
34
42
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
35
- let diffs = [];
36
- await setupSchemaIfNecessary(instanceId, databaseId, options);
37
43
  let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
38
44
  setSchemaValidationMode(schema, validationMode);
45
+ let diffs = [];
39
46
  try {
40
47
  await (0, client_1.upsertSchema)(schema, true);
41
48
  if (validationMode === "STRICT") {
@@ -95,18 +102,34 @@ async function diffSchema(options, schema, schemaValidation) {
95
102
  }
96
103
  exports.diffSchema = diffSchema;
97
104
  async function migrateSchema(args) {
98
- (0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
105
+ var _a;
106
+ (0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations...`);
99
107
  const { options, schema, validateOnly, schemaValidation } = args;
108
+ const projectId = (0, projectUtils_1.needProjectId)(options);
100
109
  const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
101
110
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
102
- await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
103
- let diffs = [];
111
+ const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
112
+ if (existingInstance.state === "PENDING_CREATE") {
113
+ const postgresql = (_a = schema.datasources.find((d) => d.postgresql)) === null || _a === void 0 ? void 0 : _a.postgresql;
114
+ if (!postgresql) {
115
+ 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)}`);
116
+ }
117
+ postgresql.schemaValidation = "NONE";
118
+ postgresql.schemaMigration = undefined;
119
+ await (0, client_1.upsertSchema)(schema, validateOnly);
120
+ postgresql.schemaValidation = undefined;
121
+ postgresql.schemaMigration = "MIGRATE_COMPATIBLE";
122
+ await (0, client_1.upsertSchema)(schema, validateOnly, true);
123
+ (0, utils_1.logLabeledWarning)("dataconnect", `Skip SQL schema migration because Cloud SQL is still being created`);
124
+ return [];
125
+ }
104
126
  await setupSchemaIfNecessary(instanceId, databaseId, options);
105
127
  let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
106
128
  setSchemaValidationMode(schema, validationMode);
129
+ let diffs = [];
107
130
  try {
108
131
  await (0, client_1.upsertSchema)(schema, validateOnly);
109
- (0, utils_1.logLabeledBullet)("dataconnect", `database schema of ${instanceId}:${databaseId} is up to date.`);
132
+ (0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is up to date.`);
110
133
  }
111
134
  catch (err) {
112
135
  if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
@@ -169,19 +192,14 @@ exports.migrateSchema = migrateSchema;
169
192
  async function grantRoleToUserInSchema(options, schema) {
170
193
  const role = options.role;
171
194
  const email = options.email;
172
- const { instanceId, databaseId } = getIdentifiers(schema);
195
+ const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
173
196
  const projectId = (0, projectUtils_1.needProjectId)(options);
174
197
  const { user, mode } = (0, connect_1.toDatabaseUser)(email);
175
198
  const fdcSqlRole = permissionsSetup_1.fdcSqlRoleMap[role](databaseId);
176
- await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
177
- const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
178
- if (!userIsCSQLAdmin) {
179
- throw new error_1.FirebaseError(`Only users with 'roles/cloudsql.admin' can grant SQL roles. If you do not have this role, ask your database administrator to run this command or manually grant ${fdcSqlRole} to ${user}`);
180
- }
199
+ await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
181
200
  const schemaSetupStatus = await setupSchemaIfNecessary(instanceId, databaseId, options);
182
- if (schemaSetupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField &&
183
- fdcSqlRole === (0, permissions_1.firebaseowner)(databaseId, permissions_1.DEFAULT_SCHEMA)) {
184
- throw new error_1.FirebaseError(`Owner rule isn't available in brownfield databases. If you would like Data Connect to manage and own your database schema, run 'firebase dataconnect:sql:setup'`);
201
+ if (schemaSetupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField && role === "owner") {
202
+ throw new error_1.FirebaseError(`Owner rule isn't available in ${schemaSetupStatus} databases. If you would like Data Connect to manage and own your database schema, run 'firebase dataconnect:sql:setup'`);
185
203
  }
186
204
  await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
187
205
  await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [`GRANT "${fdcSqlRole}" TO "${user}"`], false);
@@ -207,15 +225,15 @@ function setSchemaValidationMode(schema, schemaValidation) {
207
225
  }
208
226
  }
209
227
  function getIdentifiers(schema) {
210
- var _a, _b;
228
+ var _a, _b, _c;
211
229
  const postgresDatasource = schema.datasources.find((d) => d.postgresql);
212
230
  const databaseId = (_a = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.database;
213
231
  if (!databaseId) {
214
- throw new error_1.FirebaseError("Service does not have a postgres datasource, cannot migrate");
232
+ throw new error_1.FirebaseError("Data Connect schema must have a postgres datasource with a database name.");
215
233
  }
216
- const instanceName = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance;
234
+ const instanceName = (_c = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql) === null || _c === void 0 ? void 0 : _c.instance;
217
235
  if (!instanceName) {
218
- throw new error_1.FirebaseError("tried to migrate schema but instance name was not provided in dataconnect.yaml");
236
+ throw new error_1.FirebaseError("Data Connect schema must have a postgres datasource with a CloudSQL instance.");
219
237
  }
220
238
  const instanceId = instanceName.split("/").pop();
221
239
  const serviceName = schema.name.replace(`/schemas/${types_1.SCHEMA_ID}`, "");
@@ -347,14 +365,24 @@ function displayInvalidConnectors(invalidConnectors) {
347
365
  (0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${connectorIds}.`);
348
366
  (0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and may break existing apps.`);
349
367
  }
350
- async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, databaseId, linkIfNotConnected) {
368
+ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, linkIfNotConnected) {
369
+ var _a, _b, _c, _d;
351
370
  let currentSchema = await (0, client_1.getSchema)(serviceName);
352
- if (!currentSchema) {
371
+ let postgresql = (_b = (_a = currentSchema === null || currentSchema === void 0 ? void 0 : currentSchema.datasources) === null || _a === void 0 ? void 0 : _a.find((d) => d.postgresql)) === null || _b === void 0 ? void 0 : _b.postgresql;
372
+ if ((currentSchema === null || currentSchema === void 0 ? void 0 : currentSchema.reconciling) &&
373
+ (postgresql === null || postgresql === void 0 ? void 0 : postgresql.ephemeral) &&
374
+ ((_c = postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql) === null || _c === void 0 ? void 0 : _c.instance) &&
375
+ (postgresql === null || postgresql === void 0 ? void 0 : postgresql.schemaValidation) === "NONE") {
376
+ const [, , , , , serviceId] = serviceName.split("/");
377
+ const [, projectId, , , , instanceId] = postgresql.cloudSql.instance.split("/");
378
+ throw new error_1.FirebaseError(`While checking the service ${serviceId}, ` + (0, provisionCloudSql_1.cloudSQLBeingCreated)(projectId, instanceId));
379
+ }
380
+ if (!currentSchema || !postgresql) {
353
381
  if (!linkIfNotConnected) {
354
382
  (0, utils_1.logLabeledWarning)("dataconnect", `Not yet linked to the Cloud SQL instance.`);
355
383
  return;
356
384
  }
357
- (0, utils_1.logLabeledBullet)("dataconnect", `linking the Cloud SQL instance...`);
385
+ (0, utils_1.logLabeledBullet)("dataconnect", `Linking the Cloud SQL instance...`);
358
386
  currentSchema = {
359
387
  name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
360
388
  source: {
@@ -362,37 +390,37 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
362
390
  },
363
391
  datasources: [
364
392
  {
365
- postgresql: {
366
- database: databaseId,
367
- schemaValidation: "NONE",
368
- cloudSql: {
369
- instance: instanceId,
370
- },
371
- },
393
+ postgresql: { ephemeral: true },
372
394
  },
373
395
  ],
374
396
  };
375
397
  }
376
- const postgresDatasource = currentSchema.datasources.find((d) => d.postgresql);
377
- const postgresql = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql;
378
- if ((postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance) !== instanceId) {
379
- (0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Cloud SQL instance\nFrom ${postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance}\nTo ${instanceId}`);
398
+ if (!postgresql) {
399
+ postgresql = currentSchema.datasources[0].postgresql;
400
+ }
401
+ let alreadyConnected = !postgresql.ephemeral || false;
402
+ if (((_d = postgresql.cloudSql) === null || _d === void 0 ? void 0 : _d.instance) && postgresql.cloudSql.instance !== instanceName) {
403
+ alreadyConnected = false;
404
+ (0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Cloud SQL instance\n From ${postgresql.cloudSql.instance}\n To ${instanceName}`);
380
405
  }
381
- if ((postgresql === null || postgresql === void 0 ? void 0 : postgresql.database) !== databaseId) {
382
- (0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Postgres database from ${postgresql === null || postgresql === void 0 ? void 0 : postgresql.database} to ${databaseId}`);
406
+ if (postgresql.database && postgresql.database !== databaseId) {
407
+ alreadyConnected = false;
408
+ (0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Postgres database from ${postgresql.database} to ${databaseId}`);
383
409
  }
384
- if (!postgresql || postgresql.schemaValidation !== "NONE") {
410
+ if (alreadyConnected) {
385
411
  return;
386
412
  }
387
- postgresql.schemaValidation = "STRICT";
388
413
  try {
414
+ postgresql.schemaValidation = "STRICT";
415
+ postgresql.database = databaseId;
416
+ postgresql.cloudSql = { instance: instanceName };
389
417
  await (0, client_1.upsertSchema)(currentSchema, false);
390
418
  }
391
419
  catch (err) {
392
420
  if ((err === null || err === void 0 ? void 0 : err.status) >= 500) {
393
421
  throw err;
394
422
  }
395
- logger_1.logger.debug(err);
423
+ logger_1.logger.debug(`Failed to ensure service is connected to Cloud SQL: ${err.message}`);
396
424
  }
397
425
  }
398
426
  exports.ensureServiceIsConnectedToCloudSql = ensureServiceIsConnectedToCloudSql;
@@ -33,12 +33,12 @@ async function default_1(context, options) {
33
33
  utils.logLabeledSuccess("dataconnect", `Created service ${s.serviceName}`);
34
34
  }));
35
35
  if (servicesToDelete.length) {
36
+ const serviceToDeleteList = servicesToDelete.map((s) => " - " + s.name).join("\n");
36
37
  if (await (0, prompt_1.confirm)({
37
- force: options.force,
38
+ force: false,
38
39
  nonInteractive: options.nonInteractive,
39
- message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${servicesToDelete
40
- .map((s) => s.name)
41
- .join("\n")}\nWould you like to delete these services?`,
40
+ message: `The following services exist on ${projectId} but are not listed in your 'firebase.json'\n${serviceToDeleteList}\nWould you like to delete these services?`,
41
+ default: false,
42
42
  })) {
43
43
  await Promise.all(servicesToDelete.map(async (s) => {
44
44
  await client.deleteService(s.name);
@@ -52,22 +52,20 @@ async function default_1(context, options) {
52
52
  return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
53
53
  })
54
54
  .map(async (s) => {
55
- var _a, _b;
55
+ var _a, _b, _c;
56
56
  const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
57
57
  if (postgresDatasource) {
58
- const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
59
- const databaseId = (_b = postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database;
58
+ const instanceId = (_b = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance.split("/").pop();
59
+ const databaseId = (_c = postgresDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database;
60
60
  if (!instanceId || !databaseId) {
61
61
  return Promise.resolve();
62
62
  }
63
- const enableGoogleMlIntegration = (0, types_1.requiresVector)(s.deploymentMetadata);
64
- return (0, provisionCloudSql_1.provisionCloudSql)({
63
+ return (0, provisionCloudSql_1.setupCloudSql)({
65
64
  projectId,
66
65
  location: (0, names_1.parseServiceName)(s.serviceName).location,
67
66
  instanceId,
68
67
  databaseId,
69
- enableGoogleMlIntegration,
70
- waitForCreation: true,
68
+ requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
71
69
  });
72
70
  }
73
71
  }));
@@ -19,7 +19,7 @@ const types_1 = require("../../dataconnect/types");
19
19
  const schemaMigration_1 = require("../../dataconnect/schemaMigration");
20
20
  const freeTrial_1 = require("../../dataconnect/freeTrial");
21
21
  async function default_1(context, options) {
22
- var _a;
22
+ var _a, _b, _c;
23
23
  const projectId = (0, projectUtils_1.needProjectId)(options);
24
24
  if (!(await (0, cloudbilling_1.checkBillingEnabled)(projectId))) {
25
25
  throw new error_1.FirebaseError((0, freeTrial_1.upgradeInstructions)(projectId));
@@ -27,7 +27,6 @@ async function default_1(context, options) {
27
27
  await (0, ensureApis_1.ensureApis)(projectId);
28
28
  await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
29
29
  const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
30
- utils.logLabeledBullet("dataconnect", `Preparing to deploy`);
31
30
  const filters = (0, filters_1.getResourceFilters)(options);
32
31
  const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, options.config, c.source)));
33
32
  for (const si of serviceInfos) {
@@ -50,10 +49,10 @@ async function default_1(context, options) {
50
49
  serviceInfos,
51
50
  filters,
52
51
  };
53
- utils.logLabeledBullet("dataconnect", `Successfully prepared schema and connectors`);
52
+ utils.logLabeledBullet("dataconnect", `Successfully compiled schema and connectors`);
54
53
  if (options.dryRun) {
55
54
  for (const si of serviceInfos) {
56
- await (0, schemaMigration_1.diffSchema)(options, si.schema, (_a = si.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation);
55
+ await (0, schemaMigration_1.diffSchema)(options, si.schema, (_c = (_b = (_a = si.dataConnectYaml.schema) === null || _a === void 0 ? void 0 : _a.datasource) === null || _b === void 0 ? void 0 : _b.postgresql) === null || _c === void 0 ? void 0 : _c.schemaValidation);
57
56
  }
58
57
  utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
59
58
  await Promise.all(serviceInfos
@@ -61,23 +60,21 @@ async function default_1(context, options) {
61
60
  return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
62
61
  })
63
62
  .map(async (s) => {
64
- var _a, _b;
63
+ var _a, _b, _c;
65
64
  const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
66
65
  if (postgresDatasource) {
67
- const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
68
- const databaseId = (_b = postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database;
66
+ const instanceId = (_b = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance.split("/").pop();
67
+ const databaseId = (_c = postgresDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database;
69
68
  if (!instanceId || !databaseId) {
70
69
  return Promise.resolve();
71
70
  }
72
- const enableGoogleMlIntegration = (0, types_1.requiresVector)(s.deploymentMetadata);
73
- return (0, provisionCloudSql_1.provisionCloudSql)({
71
+ return (0, provisionCloudSql_1.setupCloudSql)({
74
72
  projectId,
75
73
  location: (0, names_1.parseServiceName)(s.serviceName).location,
76
74
  instanceId,
77
75
  databaseId,
78
- enableGoogleMlIntegration,
79
- waitForCreation: true,
80
- dryRun: options.dryRun,
76
+ requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
77
+ dryRun: true,
81
78
  });
82
79
  }
83
80
  }));
@@ -5,6 +5,7 @@ const client_1 = require("../../dataconnect/client");
5
5
  const prompts_1 = require("../../dataconnect/prompts");
6
6
  const schemaMigration_1 = require("../../dataconnect/schemaMigration");
7
7
  const projectUtils_1 = require("../../projectUtils");
8
+ const names_1 = require("../../dataconnect/names");
8
9
  async function default_1(context, options) {
9
10
  const project = (0, projectUtils_1.needProjectId)(options);
10
11
  const serviceInfos = context.dataconnect.serviceInfos;
@@ -17,14 +18,13 @@ async function default_1(context, options) {
17
18
  }));
18
19
  })
19
20
  .map((s) => {
20
- var _a;
21
+ var _a, _b, _c, _d;
21
22
  return ({
22
23
  schema: s.schema,
23
- validationMode: (_a = s.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation,
24
+ validationMode: (_d = (_c = (_b = (_a = s.dataConnectYaml) === null || _a === void 0 ? void 0 : _a.schema) === null || _b === void 0 ? void 0 : _b.datasource) === null || _c === void 0 ? void 0 : _c.postgresql) === null || _d === void 0 ? void 0 : _d.schemaValidation,
24
25
  });
25
26
  });
26
27
  if (wantSchemas.length) {
27
- utils.logLabeledBullet("dataconnect", "Deploying Data Connect schemas...");
28
28
  for (const s of wantSchemas) {
29
29
  await (0, schemaMigration_1.migrateSchema)({
30
30
  options,
@@ -32,8 +32,8 @@ async function default_1(context, options) {
32
32
  validateOnly: false,
33
33
  schemaValidation: s.validationMode,
34
34
  });
35
+ utils.logLabeledSuccess("dataconnect", `Migrated schema ${s.schema.name}`);
35
36
  }
36
- utils.logLabeledBullet("dataconnect", "Schemas deployed.");
37
37
  }
38
38
  let wantConnectors = [];
39
39
  wantConnectors = wantConnectors.concat(...serviceInfos.map((si) => si.connectorInfo
@@ -50,7 +50,6 @@ async function default_1(context, options) {
50
50
  ? []
51
51
  : haveConnectors.filter((h) => !wantConnectors.some((w) => w.name === h.name));
52
52
  if (wantConnectors.length) {
53
- utils.logLabeledBullet("dataconnect", "Deploying connectors...");
54
53
  await Promise.all(wantConnectors.map(async (c) => {
55
54
  await (0, client_1.upsertConnector)(c);
56
55
  utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
@@ -58,12 +57,19 @@ async function default_1(context, options) {
58
57
  for (const c of connectorsToDelete) {
59
58
  await (0, prompts_1.promptDeleteConnector)(options, c.name);
60
59
  }
61
- utils.logLabeledBullet("dataconnect", "Connectors deployed.");
62
60
  }
63
61
  else {
64
62
  utils.logLabeledBullet("dataconnect", "No connectors to deploy.");
65
63
  }
66
- utils.logLabeledSuccess("dataconnect", `Deployment complete! View your deployed schema and connectors at ${utils.consoleUrl(project, "/dataconnect")}`);
64
+ let consolePath = "/dataconnect";
65
+ if (serviceInfos.length === 1) {
66
+ const sn = (0, names_1.parseServiceName)(serviceInfos[0].serviceName);
67
+ consolePath += `/locations/${sn.location}/services/${sn.serviceId}/schema`;
68
+ }
69
+ utils.logLabeledSuccess("dataconnect", `Deployment complete! View your deployed schema and connectors at
70
+
71
+ ${utils.consoleUrl(project, consolePath)}
72
+ `);
67
73
  return;
68
74
  }
69
75
  exports.default = default_1;
@@ -22,6 +22,15 @@ async function createDatabase(context, options) {
22
22
  if (!firestoreCfg.database) {
23
23
  firestoreCfg.database = "(default)";
24
24
  }
25
+ let edition = types.DatabaseEdition.STANDARD;
26
+ if (firestoreCfg.edition) {
27
+ const upperEdition = firestoreCfg.edition.toUpperCase();
28
+ if (upperEdition !== types.DatabaseEdition.STANDARD &&
29
+ upperEdition !== types.DatabaseEdition.ENTERPRISE) {
30
+ throw new error_1.FirebaseError(`Invalid edition specified for database in firebase.json: ${firestoreCfg.edition}`);
31
+ }
32
+ edition = upperEdition;
33
+ }
25
34
  const api = new api_1.FirestoreApi();
26
35
  try {
27
36
  await api.getDatabase(options.projectId, firestoreCfg.database);
@@ -34,6 +43,7 @@ async function createDatabase(context, options) {
34
43
  databaseId: firestoreCfg.database,
35
44
  locationId: firestoreCfg.location || "nam5",
36
45
  type: types.DatabaseType.FIRESTORE_NATIVE,
46
+ databaseEdition: edition,
37
47
  deleteProtectionState: types.DatabaseDeleteProtectionState.DISABLED,
38
48
  pointInTimeRecoveryEnablement: types.PointInTimeRecoveryEnablement.DISABLED,
39
49
  };
@@ -94,7 +94,7 @@ function secretVersionName(s) {
94
94
  return `projects/${s.projectId}/secrets/${s.secret}/versions/${(_a = s.version) !== null && _a !== void 0 ? _a : "latest"}`;
95
95
  }
96
96
  exports.secretVersionName = secretVersionName;
97
- exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2"];
97
+ exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"];
98
98
  function isHttpsTriggered(triggered) {
99
99
  return {}.hasOwnProperty.call(triggered, "httpsTrigger");
100
100
  }
@@ -184,6 +184,7 @@ async function loadExistingBackend(ctx) {
184
184
  ctx.unreachableRegions = {
185
185
  gcfV1: [],
186
186
  gcfV2: [],
187
+ run: [],
187
188
  };
188
189
  const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
189
190
  for (const apiFunction of gcfV1Results.functions) {
@@ -212,7 +213,7 @@ async function loadExistingBackend(ctx) {
212
213
  }
213
214
  }
214
215
  async function checkAvailability(context, want) {
215
- var _a, _b, _c, _d;
216
+ var _a, _b, _c, _d, _e;
216
217
  if (!context.loadedExistingBackend) {
217
218
  await loadExistingBackend(context);
218
219
  }
@@ -248,6 +249,11 @@ async function checkAvailability(context, want) {
248
249
  context.unreachableRegions.gcfV2.join("\n") +
249
250
  "\nCloud Functions in these regions won't be deleted.");
250
251
  }
252
+ if ((_e = context.unreachableRegions) === null || _e === void 0 ? void 0 : _e.run.length) {
253
+ utils.logLabeledWarning("functions", "The following Cloud Run regions are currently unreachable:\n" +
254
+ context.unreachableRegions.run.join("\n") +
255
+ "\nCloud Run services in these regions won't be deleted.");
256
+ }
251
257
  }
252
258
  exports.checkAvailability = checkAvailability;
253
259
  function allEndpoints(backend) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toBackend = exports.envWithTypes = exports.resolveBackend = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.AllFunctionsPlatforms = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.of = exports.empty = void 0;
3
+ exports.applyPrefix = exports.toBackend = exports.envWithTypes = exports.resolveBackend = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.AllFunctionsPlatforms = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.of = exports.empty = void 0;
4
4
  const backend = require("./backend");
5
5
  const proto = require("../../gcp/proto");
6
6
  const api = require("../../.../../api");
@@ -324,3 +324,25 @@ function discoverTrigger(endpoint, region, r) {
324
324
  }
325
325
  (0, functional_1.assertExhaustive)(endpoint);
326
326
  }
327
+ function applyPrefix(build, prefix) {
328
+ if (!prefix) {
329
+ return;
330
+ }
331
+ const newEndpoints = {};
332
+ for (const [id, endpoint] of Object.entries(build.endpoints)) {
333
+ const newId = `${prefix}-${id}`;
334
+ if (newId.length > 63) {
335
+ throw new error_1.FirebaseError(`Function id '${newId}' exceeds 63 characters after applying prefix '${prefix}'. Please shorten the prefix or function name.`);
336
+ }
337
+ const fnIdRegex = /^[a-zA-Z][a-zA-Z0-9_-]{0,62}$/;
338
+ if (!fnIdRegex.test(newId)) {
339
+ throw new error_1.FirebaseError(`Function id '${newId}' is invalid after applying prefix '${prefix}'. Function names must start with a letter and can contain letters, numbers, underscores, and hyphens, with a maximum length of 63 characters.`);
340
+ }
341
+ newEndpoints[newId] = endpoint;
342
+ if (endpoint.secretEnvironmentVariables) {
343
+ endpoint.secretEnvironmentVariables = endpoint.secretEnvironmentVariables.map((secret) => (Object.assign(Object.assign({}, secret), { secret: `${prefix}-${secret.secret}` })));
344
+ }
345
+ }
346
+ build.endpoints = newEndpoints;
347
+ }
348
+ exports.applyPrefix = applyPrefix;
@@ -23,7 +23,7 @@ async function defaultServiceAccount(e) {
23
23
  if (e.platform === "gcfv1") {
24
24
  return `${metadata.projectId}@appspot.gserviceaccount.com`;
25
25
  }
26
- else if (e.platform === "gcfv2") {
26
+ else if (e.platform === "gcfv2" || e.platform === "run") {
27
27
  return await (0, computeEngine_1.getDefaultServiceAccount)(metadata.projectNumber);
28
28
  }
29
29
  (0, functional_1.assertExhaustive)(e.platform);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isEndpointFiltered = exports.isCodebaseFiltered = exports.groupEndpointsByCodebase = exports.targetCodebases = exports.getFunctionLabel = exports.getHumanFriendlyPlatformName = exports.getEndpointFilters = exports.parseFunctionSelector = exports.endpointMatchesFilter = exports.endpointMatchesAnyFilter = void 0;
4
4
  const backend = require("./backend");
5
5
  const projectConfig_1 = require("../../functions/projectConfig");
6
+ const functional_1 = require("../../functional");
6
7
  function endpointMatchesAnyFilter(endpoint, filters) {
7
8
  if (!filters) {
8
9
  return true;
@@ -71,7 +72,13 @@ function getHumanFriendlyPlatformName(platform) {
71
72
  if (platform === "gcfv1") {
72
73
  return "1st Gen";
73
74
  }
74
- return "2nd Gen";
75
+ else if (platform === "gcfv2") {
76
+ return "2nd Gen";
77
+ }
78
+ else if (platform === "run") {
79
+ return "Cloud Run";
80
+ }
81
+ (0, functional_1.assertExhaustive)(platform);
75
82
  }
76
83
  exports.getHumanFriendlyPlatformName = getHumanFriendlyPlatformName;
77
84
  function getFunctionLabel(fn) {
@@ -99,12 +99,12 @@ async function prepare(context, options, payload) {
99
99
  }
100
100
  }
101
101
  for (const endpoint of backend.allEndpoints(wantBackend)) {
102
- endpoint.environmentVariables = Object.assign({}, wantBackend.environmentVariables) || {};
102
+ endpoint.environmentVariables = Object.assign({}, (wantBackend.environmentVariables || {}));
103
103
  let resource;
104
104
  if (endpoint.platform === "gcfv1") {
105
105
  resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`;
106
106
  }
107
- else if (endpoint.platform === "gcfv2") {
107
+ else if (endpoint.platform === "gcfv2" || endpoint.platform === "run") {
108
108
  resource = `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.id}`;
109
109
  }
110
110
  else {
@@ -309,8 +309,10 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
309
309
  await runtimeDelegate.build();
310
310
  const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
311
311
  (0, utils_1.logLabeledBullet)("functions", `Loading and analyzing source code for codebase ${codebase} to determine what to deploy`);
312
- wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
313
- wantBuilds[codebase].runtime = codebaseConfig.runtime;
312
+ const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
313
+ discoveredBuild.runtime = codebaseConfig.runtime;
314
+ build.applyPrefix(discoveredBuild, codebaseConfig.prefix || "");
315
+ wantBuilds[codebase] = discoveredBuild;
314
316
  }
315
317
  return wantBuilds;
316
318
  }
@@ -74,7 +74,9 @@ async function packageSource(sourceDir, config, runtimeConfig) {
74
74
  name: CONFIG_DEST_FILE,
75
75
  mode: 420,
76
76
  });
77
- (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
77
+ if (Object.keys(runtimeConfig).some((k) => k !== "firebase")) {
78
+ (0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
79
+ }
78
80
  }
79
81
  await pipeAsync(archive, fileStream);
80
82
  }
@@ -130,6 +130,7 @@ function monthlyMinInstanceCost(endpoints) {
130
130
  const usage = {
131
131
  gcfv1: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
132
132
  gcfv2: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
133
+ run: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
133
134
  };
134
135
  for (const endpoint of endpoints) {
135
136
  if (endpoint.minInstances === undefined || endpoint.minInstances === null) {
@@ -147,10 +148,10 @@ function monthlyMinInstanceCost(endpoints) {
147
148
  }
148
149
  else {
149
150
  const tier = V2_REGION_TO_TIER[endpoint.region];
150
- usage["gcfv2"][tier].ram =
151
- usage["gcfv2"][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
152
- usage["gcfv2"][tier].cpu =
153
- usage["gcfv2"][tier].cpu +
151
+ usage[endpoint.platform][tier].ram =
152
+ usage[endpoint.platform][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
153
+ usage[endpoint.platform][tier].cpu =
154
+ usage[endpoint.platform][tier].cpu +
154
155
  endpoint.cpu * SECONDS_PER_MONTH * endpoint.minInstances;
155
156
  }
156
157
  }
@@ -166,6 +167,12 @@ function monthlyMinInstanceCost(endpoints) {
166
167
  let v2CpuBill = usage["gcfv2"][1].cpu * exports.V2_RATES.idleVCpu[1] + usage["gcfv2"][2].cpu * exports.V2_RATES.idleVCpu[2];
167
168
  v2CpuBill -= exports.V2_FREE_TIER.vCpu * exports.V2_RATES.vCpu[1];
168
169
  v2CpuBill = Math.max(v2CpuBill, 0);
169
- return v1MemoryBill + v1CpuBill + v2MemoryBill + v2CpuBill;
170
+ let runMemoryBill = usage["run"][1].ram * exports.V2_RATES.memoryGb[1] + usage["run"][2].ram * exports.V2_RATES.memoryGb[2];
171
+ runMemoryBill -= exports.V2_FREE_TIER.memoryGb * exports.V2_RATES.memoryGb[1];
172
+ runMemoryBill = Math.max(runMemoryBill, 0);
173
+ let runCpuBill = usage["run"][1].cpu * exports.V2_RATES.idleVCpu[1] + usage["run"][2].cpu * exports.V2_RATES.idleVCpu[2];
174
+ runCpuBill -= exports.V2_FREE_TIER.vCpu * exports.V2_RATES.vCpu[1];
175
+ runCpuBill = Math.max(runCpuBill, 0);
176
+ return v1MemoryBill + v1CpuBill + v2MemoryBill + v2CpuBill + runMemoryBill + runCpuBill;
170
177
  }
171
178
  exports.monthlyMinInstanceCost = monthlyMinInstanceCost;