firebase-tools 14.12.1 → 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 (54) 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/crashlytics/getIssueDetails.js +41 -0
  6. package/lib/crashlytics/getSampleCrash.js +48 -0
  7. package/lib/dataconnect/client.js +23 -15
  8. package/lib/dataconnect/ensureApis.js +5 -9
  9. package/lib/dataconnect/fileUtils.js +5 -6
  10. package/lib/dataconnect/freeTrial.js +16 -39
  11. package/lib/dataconnect/provisionCloudSql.js +67 -70
  12. package/lib/dataconnect/schemaMigration.js +75 -47
  13. package/lib/deploy/dataconnect/deploy.js +9 -11
  14. package/lib/deploy/dataconnect/prepare.js +7 -10
  15. package/lib/deploy/dataconnect/release.js +11 -5
  16. package/lib/deploy/functions/backend.js +8 -2
  17. package/lib/deploy/functions/build.js +23 -1
  18. package/lib/deploy/functions/ensure.js +1 -1
  19. package/lib/deploy/functions/functionsDeployHelper.js +8 -1
  20. package/lib/deploy/functions/prepare.js +6 -4
  21. package/lib/deploy/functions/pricing.js +12 -5
  22. package/lib/deploy/functions/release/fabricator.js +25 -3
  23. package/lib/emulator/controller.js +2 -1
  24. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  25. package/lib/emulator/functionsEmulator.js +9 -1
  26. package/lib/experiments.js +4 -0
  27. package/lib/extensions/extensionsHelper.js +4 -15
  28. package/lib/extensions/utils.js +1 -12
  29. package/lib/firestore/api.js +25 -11
  30. package/lib/firestore/pretty-print.js +7 -0
  31. package/lib/functional.js +7 -1
  32. package/lib/functions/projectConfig.js +25 -2
  33. package/lib/functions/secrets.js +3 -0
  34. package/lib/gcp/cloudfunctionsv2.js +3 -31
  35. package/lib/gcp/cloudscheduler.js +1 -1
  36. package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
  37. package/lib/gcp/cloudsql/connect.js +1 -1
  38. package/lib/gcp/cloudsql/permissionsSetup.js +13 -15
  39. package/lib/gcp/k8s.js +32 -0
  40. package/lib/gcp/runv2.js +178 -0
  41. package/lib/gemini/fdcExperience.js +5 -3
  42. package/lib/init/features/dataconnect/index.js +266 -162
  43. package/lib/init/features/dataconnect/sdk.js +32 -16
  44. package/lib/init/features/project.js +4 -0
  45. package/lib/management/studio.js +1 -1
  46. package/lib/mcp/tools/core/init.js +7 -6
  47. package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
  48. package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
  49. package/lib/mcp/tools/crashlytics/index.js +7 -1
  50. package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
  51. package/lib/rtdb.js +1 -1
  52. package/package.json +1 -1
  53. package/schema/firebase-config.json +3 -0
  54. package/lib/extensions/resolveSource.js +0 -24
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Firebase CLI [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm]
1
+ # Firebase CLI [![Actions Status][gh-actions-badge]][gh-actions] [![Node Version][node-badge]][npm] [![NPM version][npm-badge]][npm] [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=firebase&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImZpcmViYXNlLXRvb2xzIiwiZXhwZXJpbWVudGFsOm1jcCIsIi0tZGlyIiwiLiJdfQ==)
2
2
 
3
3
  The Firebase Command Line Interface (CLI) Tools can be used to test, manage, and deploy your Firebase project from the command line.
4
4
 
@@ -17,7 +17,7 @@ exports.command = new command_1.Command("dataconnect:services:list")
17
17
  "dataconnect.connectors.list",
18
18
  ])
19
19
  .action(async (options) => {
20
- var _a, _b, _c, _d, _e, _f, _g;
20
+ var _a, _b, _c, _d, _e, _f, _g, _h;
21
21
  const projectId = (0, projectUtils_1.needProjectId)(options);
22
22
  await (0, ensureApis_1.ensureApis)(projectId);
23
23
  const services = await client.listAllServices(projectId);
@@ -42,15 +42,15 @@ exports.command = new command_1.Command("dataconnect:services:list")
42
42
  const connectors = await client.listConnectors(service.name);
43
43
  const serviceName = names.parseServiceName(service.name);
44
44
  const postgresDatasource = schema === null || schema === void 0 ? void 0 : schema.datasources.find((d) => d.postgresql);
45
- const instanceName = (_c = (_b = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance) !== null && _c !== void 0 ? _c : "";
45
+ const instanceName = (_d = (_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) !== null && _d !== void 0 ? _d : "";
46
46
  const instanceId = instanceName.split("/").pop();
47
- const dbId = (_e = (_d = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _d === void 0 ? void 0 : _d.database) !== null && _e !== void 0 ? _e : "";
47
+ const dbId = (_f = (_e = postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) === null || _e === void 0 ? void 0 : _e.database) !== null && _f !== void 0 ? _f : "";
48
48
  const dbName = `CloudSQL Instance: ${instanceId}\nDatabase: ${dbId}`;
49
49
  table.push([
50
50
  serviceName.serviceId,
51
51
  serviceName.location,
52
52
  dbName,
53
- (_f = schema === null || schema === void 0 ? void 0 : schema.updateTime) !== null && _f !== void 0 ? _f : "",
53
+ (_g = schema === null || schema === void 0 ? void 0 : schema.updateTime) !== null && _g !== void 0 ? _g : "",
54
54
  "",
55
55
  "",
56
56
  ]);
@@ -66,7 +66,7 @@ exports.command = new command_1.Command("dataconnect:services:list")
66
66
  table.push(["", "", "", "", connectorName.connectorId, conn.updateTime]);
67
67
  serviceJson.connectors.push({
68
68
  connectorId: connectorName.connectorId,
69
- connectorLastUpdated: (_g = conn.updateTime) !== null && _g !== void 0 ? _g : "",
69
+ connectorLastUpdated: (_h = conn.updateTime) !== null && _h !== void 0 ? _h : "",
70
70
  });
71
71
  }
72
72
  jsonOutput.services.push(serviceJson);
@@ -10,6 +10,7 @@ const schemaMigration_1 = require("../dataconnect/schemaMigration");
10
10
  const requireAuth_1 = require("../requireAuth");
11
11
  const error_1 = require("../error");
12
12
  const permissionsSetup_1 = require("../gcp/cloudsql/permissionsSetup");
13
+ const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
13
14
  const allowedRoles = Object.keys(permissionsSetup_1.fdcSqlRoleMap);
14
15
  exports.command = new command_1.Command("dataconnect:sql:grant [serviceId]")
15
16
  .description("grants the SQL role <role> to the provided user or service account <email>")
@@ -29,6 +30,10 @@ exports.command = new command_1.Command("dataconnect:sql:grant [serviceId]")
29
30
  if (!allowedRoles.includes(role.toLowerCase())) {
30
31
  throw new error_1.FirebaseError(`Role should be one of ${allowedRoles.join(" | ")}.`);
31
32
  }
33
+ const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
34
+ if (!userIsCSQLAdmin) {
35
+ 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 ${role} to ${email}`);
36
+ }
32
37
  const projectId = (0, projectUtils_1.needProjectId)(options);
33
38
  await (0, ensureApis_1.ensureApis)(projectId);
34
39
  const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
@@ -12,7 +12,6 @@ const permissionsSetup_1 = require("../gcp/cloudsql/permissionsSetup");
12
12
  const permissions_1 = require("../gcp/cloudsql/permissions");
13
13
  const schemaMigration_1 = require("../dataconnect/schemaMigration");
14
14
  const connect_1 = require("../gcp/cloudsql/connect");
15
- const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
16
15
  exports.command = new command_1.Command("dataconnect:sql:setup [serviceId]")
17
16
  .description("set up your CloudSQL database")
18
17
  .before(requirePermissions_1.requirePermissions, [
@@ -33,8 +32,7 @@ exports.command = new command_1.Command("dataconnect:sql:setup [serviceId]")
33
32
  }
34
33
  const { serviceName, instanceName, databaseId } = (0, schemaMigration_1.getIdentifiers)(serviceInfo.schema);
35
34
  await (0, schemaMigration_1.ensureServiceIsConnectedToCloudSql)(serviceName, instanceName, databaseId, true);
36
- const { user, mode } = await (0, connect_1.getIAMUser)(options);
37
- await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
35
+ await (0, connect_1.setupIAMUsers)(instanceId, options);
38
36
  const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
39
37
  await (0, permissionsSetup_1.setupSQLPermissions)(instanceId, databaseId, schemaInfo, options);
40
38
  });
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getIssueDetails = void 0;
4
+ const apiv2_1 = require("../apiv2");
5
+ const logger_1 = require("../logger");
6
+ const error_1 = require("../error");
7
+ const api_1 = require("../api");
8
+ const TIMEOUT = 10000;
9
+ const apiClient = new apiv2_1.Client({
10
+ urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
11
+ apiVersion: "v1alpha",
12
+ });
13
+ async function getIssueDetails(appId, issueId) {
14
+ try {
15
+ const requestProjectNumber = parseProjectNumber(appId);
16
+ if (requestProjectNumber === undefined) {
17
+ throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
18
+ }
19
+ const response = await apiClient.request({
20
+ method: "GET",
21
+ headers: {
22
+ "Content-Type": "application/json",
23
+ },
24
+ path: `/projects/${requestProjectNumber}/apps/${appId}/issues/${issueId}`,
25
+ timeout: TIMEOUT,
26
+ });
27
+ return response.body;
28
+ }
29
+ catch (err) {
30
+ logger_1.logger.debug(err.message);
31
+ throw new error_1.FirebaseError(`Failed to fetch the issue details for the Firebase AppId ${appId}, IssueId ${issueId}. Error: ${err}.`, { original: err });
32
+ }
33
+ }
34
+ exports.getIssueDetails = getIssueDetails;
35
+ function parseProjectNumber(appId) {
36
+ const appIdParts = appId.split(":");
37
+ if (appIdParts.length > 1) {
38
+ return appIdParts[1];
39
+ }
40
+ return undefined;
41
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSampleCrash = void 0;
4
+ const apiv2_1 = require("../apiv2");
5
+ const logger_1 = require("../logger");
6
+ const error_1 = require("../error");
7
+ const api_1 = require("../api");
8
+ const TIMEOUT = 10000;
9
+ const apiClient = new apiv2_1.Client({
10
+ urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
11
+ apiVersion: "v1alpha",
12
+ });
13
+ async function getSampleCrash(appId, issueId, sampleCount, variantId) {
14
+ try {
15
+ const queryParams = new URLSearchParams();
16
+ queryParams.set("filter.issue.id", issueId);
17
+ queryParams.set("page_size", String(sampleCount));
18
+ if (variantId) {
19
+ queryParams.set("filter.issue.variant_id", variantId);
20
+ }
21
+ const requestProjectNumber = parseProjectNumber(appId);
22
+ if (requestProjectNumber === undefined) {
23
+ throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
24
+ }
25
+ const response = await apiClient.request({
26
+ method: "GET",
27
+ headers: {
28
+ "Content-Type": "application/json",
29
+ },
30
+ path: `/projects/${requestProjectNumber}/apps/${appId}/events`,
31
+ queryParams: queryParams,
32
+ timeout: TIMEOUT,
33
+ });
34
+ return response.body;
35
+ }
36
+ catch (err) {
37
+ logger_1.logger.debug(err.message);
38
+ throw new error_1.FirebaseError(`Failed to fetch the same crash for the Firebase AppId ${appId}, IssueId ${issueId}. Error: ${err}.`, { original: err });
39
+ }
40
+ }
41
+ exports.getSampleCrash = getSampleCrash;
42
+ function parseProjectNumber(appId) {
43
+ const appIdParts = appId.split(":");
44
+ if (appIdParts.length > 1) {
45
+ return appIdParts[1];
46
+ }
47
+ return undefined;
48
+ }
@@ -30,19 +30,27 @@ async function listAllServices(projectId) {
30
30
  }
31
31
  exports.listAllServices = listAllServices;
32
32
  async function createService(projectId, locationId, serviceId) {
33
- const op = await dataconnectClient().post(`/projects/${projectId}/locations/${locationId}/services`, {
34
- name: `projects/${projectId}/locations/${locationId}/services/${serviceId}`,
35
- }, {
36
- queryParams: {
37
- service_id: serviceId,
38
- },
39
- });
40
- const pollRes = await operationPoller.pollOperation({
41
- apiOrigin: (0, api_1.dataconnectOrigin)(),
42
- apiVersion: DATACONNECT_API_VERSION,
43
- operationResourceName: op.body.name,
44
- });
45
- return pollRes;
33
+ try {
34
+ const op = await dataconnectClient().post(`/projects/${projectId}/locations/${locationId}/services`, {
35
+ name: `projects/${projectId}/locations/${locationId}/services/${serviceId}`,
36
+ }, {
37
+ queryParams: {
38
+ service_id: serviceId,
39
+ },
40
+ });
41
+ const pollRes = await operationPoller.pollOperation({
42
+ apiOrigin: (0, api_1.dataconnectOrigin)(),
43
+ apiVersion: DATACONNECT_API_VERSION,
44
+ operationResourceName: op.body.name,
45
+ });
46
+ return pollRes;
47
+ }
48
+ catch (err) {
49
+ if (err.status !== 409) {
50
+ throw err;
51
+ }
52
+ return undefined;
53
+ }
46
54
  }
47
55
  exports.createService = createService;
48
56
  async function deleteService(serviceName) {
@@ -89,14 +97,14 @@ async function listSchemas(serviceName, fields = []) {
89
97
  return schemas;
90
98
  }
91
99
  exports.listSchemas = listSchemas;
92
- async function upsertSchema(schema, validateOnly = false) {
100
+ async function upsertSchema(schema, validateOnly = false, async = false) {
93
101
  const op = await dataconnectClient().patch(`${schema.name}`, schema, {
94
102
  queryParams: {
95
103
  allowMissing: "true",
96
104
  validateOnly: validateOnly ? "true" : "false",
97
105
  },
98
106
  });
99
- if (validateOnly) {
107
+ if (validateOnly || async) {
100
108
  return;
101
109
  }
102
110
  return operationPoller.pollOperation({
@@ -1,20 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureGIFApis = exports.ensureSparkApis = exports.ensureApis = void 0;
3
+ exports.ensureGIFApis = exports.ensureApis = void 0;
4
4
  const api = require("../api");
5
5
  const ensureApiEnabled_1 = require("../ensureApiEnabled");
6
6
  const prefix = "dataconnect";
7
7
  async function ensureApis(projectId) {
8
- await (0, ensureApiEnabled_1.ensure)(projectId, api.dataconnectOrigin(), prefix);
9
- await (0, ensureApiEnabled_1.ensure)(projectId, api.cloudSQLAdminOrigin(), prefix);
10
- await (0, ensureApiEnabled_1.ensure)(projectId, api.computeOrigin(), prefix);
8
+ await Promise.all([
9
+ (0, ensureApiEnabled_1.ensure)(projectId, api.dataconnectOrigin(), prefix),
10
+ (0, ensureApiEnabled_1.ensure)(projectId, api.cloudSQLAdminOrigin(), prefix),
11
+ ]);
11
12
  }
12
13
  exports.ensureApis = ensureApis;
13
- async function ensureSparkApis(projectId) {
14
- await (0, ensureApiEnabled_1.ensure)(projectId, api.cloudSQLAdminOrigin(), prefix);
15
- await (0, ensureApiEnabled_1.ensure)(projectId, api.dataconnectOrigin(), prefix);
16
- }
17
- exports.ensureSparkApis = ensureSparkApis;
18
14
  async function ensureGIFApis(projectId) {
19
15
  await (0, ensureApiEnabled_1.ensure)(projectId, api.cloudAiCompanionOrigin(), prefix);
20
16
  }
@@ -4,6 +4,7 @@ exports.getFrameworksFromPackageJson = exports.frameworksMap = exports.SUPPORTED
4
4
  const fs = require("fs-extra");
5
5
  const path = require("path");
6
6
  const clc = require("colorette");
7
+ const glob_1 = require("glob");
7
8
  const error_1 = require("../error");
8
9
  const types_1 = require("./types");
9
10
  const utils_1 = require("../utils");
@@ -57,14 +58,12 @@ async function readGQLFiles(sourceDir) {
57
58
  if (!fs.existsSync(sourceDir)) {
58
59
  return [];
59
60
  }
60
- const files = await fs.readdir(sourceDir);
61
- return files
62
- .filter((f) => f.endsWith(".gql") || f.endsWith(".graphql"))
63
- .map((f) => toFile(sourceDir, f));
61
+ const files = await (0, glob_1.glob)("**/*.{gql,graphql}", { cwd: sourceDir, absolute: true, nodir: true });
62
+ return files.map((f) => toFile(sourceDir, f));
64
63
  }
65
64
  exports.readGQLFiles = readGQLFiles;
66
- function toFile(sourceDir, relPath) {
67
- const fullPath = path.join(sourceDir, relPath);
65
+ function toFile(sourceDir, fullPath) {
66
+ const relPath = path.relative(sourceDir, fullPath);
68
67
  if (!fs.existsSync(fullPath)) {
69
68
  throw new error_1.FirebaseError(`file ${fullPath} not found`);
70
69
  }
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.upgradeInstructions = exports.printFreeTrialUnavailable = exports.isFreeTrialError = exports.getFreeTrialInstanceId = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
3
+ exports.upgradeInstructions = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
4
4
  const clc = require("colorette");
5
5
  const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
6
- const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
7
6
  const utils = require("../utils");
8
7
  function freeTrialTermsLink() {
9
8
  return "https://firebase.google.com/pricing";
@@ -11,7 +10,6 @@ function freeTrialTermsLink() {
11
10
  exports.freeTrialTermsLink = freeTrialTermsLink;
12
11
  const FREE_TRIAL_METRIC = "sqladmin.googleapis.com/fdc_lifetime_free_trial_per_project";
13
12
  async function checkFreeTrialInstanceUsed(projectId) {
14
- utils.logLabeledBullet("dataconnect", "Checking Cloud SQL no cost trial eligibility...");
15
13
  const past7d = new Date();
16
14
  past7d.setDate(past7d.getDate() - 7);
17
15
  const query = {
@@ -19,53 +17,32 @@ async function checkFreeTrialInstanceUsed(projectId) {
19
17
  "interval.endTime": new Date().toJSON(),
20
18
  "interval.startTime": past7d.toJSON(),
21
19
  };
20
+ let used = true;
22
21
  try {
23
22
  const ts = await (0, cloudmonitoring_1.queryTimeSeries)(query, projectId);
24
- let used = true;
25
23
  if (ts.length) {
26
24
  used = ts[0].points.some((p) => p.value.int64Value);
27
25
  }
28
- if (used) {
29
- utils.logLabeledWarning("dataconnect", "CloudSQL no cost trial has already been used on this project.");
30
- }
31
- return used;
32
26
  }
33
27
  catch (err) {
28
+ used = false;
29
+ }
30
+ if (used) {
31
+ utils.logLabeledWarning("dataconnect", "CloudSQL no cost trial has already been used on this project.");
32
+ }
33
+ else {
34
34
  utils.logLabeledSuccess("dataconnect", "CloudSQL no cost trial available!");
35
- return false;
36
35
  }
36
+ return used;
37
37
  }
38
38
  exports.checkFreeTrialInstanceUsed = checkFreeTrialInstanceUsed;
39
- async function getFreeTrialInstanceId(projectId) {
40
- var _a;
41
- const instances = await (0, cloudsqladmin_1.listInstances)(projectId);
42
- return (_a = instances.find((i) => { var _a; return ((_a = i.settings.userLabels) === null || _a === void 0 ? void 0 : _a["firebase-data-connect"]) === "ft"; })) === null || _a === void 0 ? void 0 : _a.name;
43
- }
44
- exports.getFreeTrialInstanceId = getFreeTrialInstanceId;
45
- async function isFreeTrialError(err, projectId) {
46
- return err.message.includes("Quota Exhausted") && (await checkFreeTrialInstanceUsed(projectId))
47
- ? true
48
- : false;
49
- }
50
- exports.isFreeTrialError = isFreeTrialError;
51
- function printFreeTrialUnavailable(projectId, configYamlPath, instanceId) {
52
- if (!instanceId) {
53
- utils.logLabeledError("dataconnect", "The CloudSQL free trial has already been used on this project.");
54
- utils.logLabeledError("dataconnect", `You may create or use a paid CloudSQL instance by visiting https://console.cloud.google.com/sql/instances`);
55
- return;
56
- }
57
- utils.logLabeledError("dataconnect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`);
58
- const reuseHint = `To use a different database in the same instance, ${clc.bold(`change the ${clc.blue("instanceId")} to "${instanceId}"`)} and update ${clc.blue("location")} in ` +
59
- `${clc.green(configYamlPath)}.`;
60
- utils.logLabeledError("dataconnect", reuseHint);
61
- utils.logLabeledError("dataconnect", `Alternatively, you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`);
62
- }
63
- exports.printFreeTrialUnavailable = printFreeTrialUnavailable;
64
39
  function upgradeInstructions(projectId) {
65
- return `If you'd like to provision a CloudSQL Postgres instance on the Firebase Data Connect no-cost trial:
66
- 1. Please upgrade to the pay-as-you-go (Blaze) billing plan. Visit the following page:
67
- https://console.firebase.google.com/project/${projectId}/usage/details
68
- 2. Run ${clc.bold("firebase init dataconnect")} again to configure the Cloud SQL instance.
69
- 3. Run ${clc.bold("firebase deploy --only dataconnect")} to deploy your Data Connect service.`;
40
+ return `To provision a CloudSQL Postgres instance on the Firebase Data Connect no-cost trial:
41
+
42
+ 1. Please upgrade to the pay-as-you-go (Blaze) billing plan. Visit the following page:
43
+
44
+ https://console.firebase.google.com/project/${projectId}/usage/details
45
+
46
+ 2. Run ${clc.bold("firebase deploy --only dataconnect")} to deploy your Data Connect service.`;
70
47
  }
71
48
  exports.upgradeInstructions = upgradeInstructions;
@@ -1,95 +1,98 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUpdateReason = exports.provisionCloudSql = void 0;
3
+ exports.getUpdateReason = exports.cloudSQLBeingCreated = exports.setupCloudSql = void 0;
4
4
  const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
5
5
  const utils = require("../utils");
6
6
  const checkIam_1 = require("./checkIam");
7
7
  const utils_1 = require("../utils");
8
8
  const logger_1 = require("../logger");
9
- const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
10
9
  const freeTrial_1 = require("./freeTrial");
11
- async function provisionCloudSql(args) {
12
- let connectionName = "";
13
- const { projectId, location, instanceId, databaseId, enableGoogleMlIntegration, waitForCreation, silent, dryRun, } = args;
10
+ const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
11
+ async function setupCloudSql(args) {
12
+ await upsertInstance(Object.assign({}, args));
13
+ const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
14
+ if (requireGoogleMlIntegration && !dryRun) {
15
+ await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
16
+ }
17
+ }
18
+ exports.setupCloudSql = setupCloudSql;
19
+ async function upsertInstance(args) {
20
+ const { projectId, instanceId, requireGoogleMlIntegration, dryRun } = args;
14
21
  try {
15
22
  const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
16
- silent || utils.logLabeledBullet("dataconnect", `Found existing instance ${instanceId}.`);
17
- connectionName = (existingInstance === null || existingInstance === void 0 ? void 0 : existingInstance.connectionName) || "";
18
- const why = getUpdateReason(existingInstance, enableGoogleMlIntegration);
23
+ utils.logLabeledBullet("dataconnect", `Found existing Cloud SQL instance ${instanceId}.`);
24
+ const why = getUpdateReason(existingInstance, requireGoogleMlIntegration);
19
25
  if (why) {
20
- const cta = dryRun
21
- ? `It will be updated on your next deploy.`
22
- : `Updating instance. This may take a few minutes...`;
23
- silent ||
24
- utils.logLabeledBullet("dataconnect", `Instance ${instanceId} settings not compatible with Firebase Data Connect. ` + cta + why);
25
- if (!dryRun) {
26
- await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.updateInstanceForDataConnect(existingInstance, enableGoogleMlIntegration), "Updating your instance...");
27
- silent || utils.logLabeledBullet("dataconnect", "Instance updated");
26
+ if (dryRun) {
27
+ utils.logLabeledBullet("dataconnect", `Cloud SQL instance ${instanceId} settings not compatible with Firebase Data Connect. ` +
28
+ `It will be updated on your next deploy.` +
29
+ why);
30
+ }
31
+ else {
32
+ utils.logLabeledBullet("dataconnect", `Cloud SQL instance ${instanceId} settings not compatible with Firebase Data Connect. ` +
33
+ why);
34
+ await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.updateInstanceForDataConnect(existingInstance, requireGoogleMlIntegration), "Updating your Cloud SQL instance...");
28
35
  }
29
36
  }
37
+ await upsertDatabase(Object.assign({}, args));
30
38
  }
31
39
  catch (err) {
32
40
  if (err.status !== 404) {
33
41
  throw err;
34
42
  }
35
- cmekWarning();
36
- const cta = dryRun ? "It will be created on your next deploy" : "Creating it now.";
37
- const freeTrialUsed = await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId);
38
- silent ||
39
- utils.logLabeledBullet("dataconnect", `CloudSQL instance '${instanceId}' not found.` + cta + freeTrialUsed
40
- ? ""
41
- : `\nThis instance is provided under the terms of the Data Connect no-cost trial ${(0, freeTrial_1.freeTrialTermsLink)()}` +
42
- dryRun
43
- ? `\nMonitor the progress at ${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}`
44
- : "");
45
- if (!dryRun) {
46
- const newInstance = await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.createInstance({
47
- projectId,
48
- location,
49
- instanceId,
50
- enableGoogleMlIntegration,
51
- waitForCreation,
52
- freeTrial: !freeTrialUsed,
53
- }), "Creating your instance...");
54
- if (newInstance) {
55
- silent || utils.logLabeledBullet("dataconnect", "Instance created");
56
- connectionName = (newInstance === null || newInstance === void 0 ? void 0 : newInstance.connectionName) || "";
57
- }
58
- else {
59
- silent ||
60
- utils.logLabeledBullet("dataconnect", "Cloud SQL instance creation started - it should be ready shortly. Database and users will be created on your next deploy.");
61
- return connectionName;
62
- }
63
- }
43
+ await createInstance(Object.assign({}, args));
44
+ }
45
+ }
46
+ async function createInstance(args) {
47
+ const { projectId, location, instanceId, requireGoogleMlIntegration, dryRun } = args;
48
+ const freeTrialUsed = await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId);
49
+ if (dryRun) {
50
+ utils.logLabeledBullet("dataconnect", `Cloud SQL Instance ${instanceId} not found. It will be created on your next deploy.`);
51
+ }
52
+ else {
53
+ await cloudSqlAdminClient.createInstance({
54
+ projectId,
55
+ location,
56
+ instanceId,
57
+ enableGoogleMlIntegration: requireGoogleMlIntegration,
58
+ freeTrial: !freeTrialUsed,
59
+ });
60
+ utils.logLabeledBullet("dataconnect", cloudSQLBeingCreated(projectId, instanceId, !freeTrialUsed));
64
61
  }
62
+ }
63
+ function cloudSQLBeingCreated(projectId, instanceId, includeFreeTrialToS) {
64
+ return (`Cloud SQL Instance ${instanceId} is being created.` +
65
+ (includeFreeTrialToS
66
+ ? `\nThis instance is provided under the terms of the Data Connect no-cost trial ${(0, freeTrial_1.freeTrialTermsLink)()}`
67
+ : "") +
68
+ `
69
+ Meanwhile, your data are saved in a temporary database and will be migrated once complete. Monitor its progress at
70
+
71
+ ${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}
72
+ `);
73
+ }
74
+ exports.cloudSQLBeingCreated = cloudSQLBeingCreated;
75
+ async function upsertDatabase(args) {
76
+ const { projectId, instanceId, databaseId, dryRun } = args;
65
77
  try {
66
78
  await cloudSqlAdminClient.getDatabase(projectId, instanceId, databaseId);
67
- silent || utils.logLabeledBullet("dataconnect", `Found existing database ${databaseId}.`);
79
+ utils.logLabeledBullet("dataconnect", `Found existing Postgres Database ${databaseId}.`);
68
80
  }
69
81
  catch (err) {
70
- if (err.status === 404) {
71
- if (dryRun) {
72
- silent ||
73
- utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found. It will be created on your next deploy.`);
74
- }
75
- else {
76
- silent ||
77
- utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found, creating it now...`);
78
- await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
79
- silent || utils.logLabeledBullet("dataconnect", `Database ${databaseId} created.`);
80
- }
82
+ if (err.status !== 404) {
83
+ logger_1.logger.debug(`Unexpected error from Cloud SQL: ${err}`);
84
+ utils.logLabeledWarning("dataconnect", `Postgres Database ${databaseId} is not accessible.`);
85
+ return;
86
+ }
87
+ if (dryRun) {
88
+ utils.logLabeledBullet("dataconnect", `Postgres Database ${databaseId} not found. It will be created on your next deploy.`);
81
89
  }
82
90
  else {
83
- logger_1.logger.debug(`Unexpected error from CloudSQL: ${err}`);
84
- silent || utils.logLabeledWarning("dataconnect", `Database ${databaseId} is not accessible.`);
91
+ await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
92
+ utils.logLabeledBullet("dataconnect", `Postgres Database ${databaseId} created.`);
85
93
  }
86
94
  }
87
- if (enableGoogleMlIntegration && !dryRun) {
88
- await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
89
- }
90
- return connectionName;
91
95
  }
92
- exports.provisionCloudSql = provisionCloudSql;
93
96
  function getUpdateReason(instance, requireGoogleMlIntegration) {
94
97
  var _a, _b, _c, _d;
95
98
  let reason = "";
@@ -112,9 +115,3 @@ function getUpdateReason(instance, requireGoogleMlIntegration) {
112
115
  return reason;
113
116
  }
114
117
  exports.getUpdateReason = getUpdateReason;
115
- function cmekWarning() {
116
- const message = "Cloud SQL instances created via the Firebase CLI do not support customer managed encryption keys.\n" +
117
- "If you'd like to use a CMEK to encrypt your data, first create a CMEK encrypted instance (https://cloud.google.com/sql/docs/postgres/configure-cmek#createcmekinstance).\n" +
118
- "Then, edit your `dataconnect.yaml` file to use the encrypted instance and redeploy.";
119
- utils.logLabeledWarning("dataconnect", message);
120
- }