firebase-tools 13.17.0 → 13.18.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 (35) hide show
  1. package/lib/commands/dataconnect-services-list.js +4 -3
  2. package/lib/commands/dataconnect-sql-grant.js +37 -0
  3. package/lib/commands/dataconnect-sql-migrate.js +2 -1
  4. package/lib/commands/deploy.js +2 -0
  5. package/lib/commands/index.js +1 -0
  6. package/lib/dataconnect/client.js +1 -1
  7. package/lib/dataconnect/dataplaneClient.js +1 -1
  8. package/lib/dataconnect/ensureApis.js +7 -1
  9. package/lib/dataconnect/load.js +3 -1
  10. package/lib/dataconnect/provisionCloudSql.js +34 -21
  11. package/lib/dataconnect/schemaMigration.js +126 -73
  12. package/lib/deploy/dataconnect/deploy.js +16 -13
  13. package/lib/deploy/dataconnect/prepare.js +36 -0
  14. package/lib/deploy/dataconnect/release.js +9 -2
  15. package/lib/deploy/extensions/prepare.js +22 -9
  16. package/lib/deploy/firestore/prepare.js +10 -0
  17. package/lib/deploy/firestore/release.js +3 -6
  18. package/lib/deploy/functions/checkIam.js +7 -2
  19. package/lib/deploy/functions/ensure.js +10 -2
  20. package/lib/deploy/functions/prepare.js +2 -2
  21. package/lib/deploy/index.js +9 -5
  22. package/lib/emulator/downloadableEmulators.js +9 -9
  23. package/lib/firestore/checkDatabaseType.js +10 -3
  24. package/lib/frameworks/angular/index.js +15 -3
  25. package/lib/frameworks/next/utils.js +1 -1
  26. package/lib/gcp/cloudsql/permissions.js +6 -1
  27. package/lib/gcp/secretManager.js +12 -5
  28. package/lib/init/features/dataconnect/index.js +24 -11
  29. package/lib/init/features/firestore/index.js +20 -1
  30. package/lib/init/features/firestore/indexes.js +4 -4
  31. package/package.json +1 -1
  32. package/schema/connector-yaml.json +43 -17
  33. package/templates/init/dataconnect/connector.yaml +0 -1
  34. package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +12 -0
  35. package/templates/init/dataconnect/dataconnect.yaml +1 -1
@@ -11,8 +11,13 @@ const build_1 = require("../../dataconnect/build");
11
11
  const ensureApis_1 = require("../../dataconnect/ensureApis");
12
12
  const requireTosAcceptance_1 = require("../../requireTosAcceptance");
13
13
  const firedata_1 = require("../../gcp/firedata");
14
+ const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
15
+ const names_1 = require("../../dataconnect/names");
14
16
  const error_1 = require("../../error");
17
+ const types_1 = require("../../dataconnect/types");
18
+ const schemaMigration_1 = require("../../dataconnect/schemaMigration");
15
19
  async function default_1(context, options) {
20
+ var _a;
16
21
  const projectId = (0, projectUtils_1.needProjectId)(options);
17
22
  await (0, ensureApis_1.ensureApis)(projectId);
18
23
  await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
@@ -41,6 +46,37 @@ async function default_1(context, options) {
41
46
  filters,
42
47
  };
43
48
  utils.logLabeledBullet("dataconnect", `Successfully prepared schema and connectors`);
49
+ if (options.dryRun) {
50
+ for (const si of serviceInfos) {
51
+ await (0, schemaMigration_1.diffSchema)(si.schema, (_a = si.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation);
52
+ }
53
+ utils.logLabeledBullet("dataconnect", "Checking for CloudSQL resources...");
54
+ await Promise.all(serviceInfos
55
+ .filter((si) => {
56
+ return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
57
+ })
58
+ .map(async (s) => {
59
+ var _a, _b;
60
+ const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
61
+ if (postgresDatasource) {
62
+ const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
63
+ const databaseId = (_b = postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database;
64
+ if (!instanceId || !databaseId) {
65
+ return Promise.resolve();
66
+ }
67
+ const enableGoogleMlIntegration = (0, types_1.requiresVector)(s.deploymentMetadata);
68
+ return (0, provisionCloudSql_1.provisionCloudSql)({
69
+ projectId,
70
+ locationId: (0, names_1.parseServiceName)(s.serviceName).location,
71
+ instanceId,
72
+ databaseId,
73
+ enableGoogleMlIntegration,
74
+ waitForCreation: true,
75
+ dryRun: options.dryRun,
76
+ });
77
+ }
78
+ }));
79
+ }
44
80
  logger_1.logger.debug(JSON.stringify(context.dataconnect, null, 2));
45
81
  return;
46
82
  }
@@ -14,14 +14,21 @@ async function default_1(context, options) {
14
14
  return f.serviceId === si.dataConnectYaml.serviceId && (f.schemaOnly || f.fullService);
15
15
  }));
16
16
  })
17
- .map((s) => s.schema);
17
+ .map((s) => {
18
+ var _a;
19
+ return ({
20
+ schema: s.schema,
21
+ validationMode: (_a = s.dataConnectYaml.schema.datasource.postgresql) === null || _a === void 0 ? void 0 : _a.schemaValidation,
22
+ });
23
+ });
18
24
  if (wantSchemas.length) {
19
25
  utils.logLabeledBullet("dataconnect", "Releasing Data Connect schemas...");
20
26
  for (const s of wantSchemas) {
21
27
  await (0, schemaMigration_1.migrateSchema)({
22
28
  options,
23
- schema: s,
29
+ schema: s.schema,
24
30
  validateOnly: false,
31
+ schemaValidation: s.validationMode,
25
32
  });
26
33
  }
27
34
  utils.logLabeledBullet("dataconnect", "Schemas released.");
@@ -81,14 +81,19 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
81
81
  }
82
82
  if (payload.instancesToDelete.length) {
83
83
  logger_1.logger.info(deploymentSummary.deletesSummary(payload.instancesToDelete));
84
- if (!(await prompt.confirm({
85
- message: `Would you like to delete ${payload.instancesToDelete
86
- .map((i) => i.instanceId)
87
- .join(", ")}?`,
88
- default: false,
89
- nonInteractive: options.nonInteractive,
90
- force: options.force,
91
- }))) {
84
+ if (options.dryRun) {
85
+ logger_1.logger.info("On your next deploy, these you will be asked if you want to delete these instances.");
86
+ logger_1.logger.info("If you deploy --force, they will be deleted.");
87
+ }
88
+ if (!options.dryRun &&
89
+ !(await prompt.confirm({
90
+ message: `Would you like to delete ${payload.instancesToDelete
91
+ .map((i) => i.instanceId)
92
+ .join(", ")}?`,
93
+ default: false,
94
+ nonInteractive: options.nonInteractive,
95
+ force: options.force,
96
+ }))) {
92
97
  payload.instancesToDelete = [];
93
98
  }
94
99
  else {
@@ -96,7 +101,15 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
96
101
  }
97
102
  }
98
103
  await (0, requirePermissions_1.requirePermissions)(options, permissionsNeeded);
99
- await (0, tos_1.acceptLatestAppDeveloperTOS)(options, projectId, context.want.map((i) => i.instanceId));
104
+ if (options.dryRun) {
105
+ const appDevTos = await (0, tos_1.getAppDeveloperTOSStatus)(projectId);
106
+ if (!appDevTos.lastAcceptedVersion) {
107
+ logger_1.logger.info("On your next deploy, you will be asked to accept the Firebase Extensions App Developer Terms of Service");
108
+ }
109
+ }
110
+ else {
111
+ await (0, tos_1.acceptLatestAppDeveloperTOS)(options, projectId, context.want.map((i) => i.instanceId));
112
+ }
100
113
  }
101
114
  async function prepareDynamicExtensions(context, options, payload, builds) {
102
115
  const filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
@@ -5,6 +5,7 @@ const loadCJSON_1 = require("../../loadCJSON");
5
5
  const rulesDeploy_1 = require("../../rulesDeploy");
6
6
  const utils = require("../../utils");
7
7
  const fsConfig = require("../../firestore/fsConfig");
8
+ const logger_1 = require("../../logger");
8
9
  function prepareRules(context, rulesDeploy, databaseId, rulesFile) {
9
10
  rulesDeploy.addFile(rulesFile);
10
11
  context.firestore.rules.push({
@@ -23,6 +24,7 @@ function prepareIndexes(context, options, databaseId, indexesFileName) {
23
24
  });
24
25
  }
25
26
  async function default_1(context, options) {
27
+ var _a;
26
28
  if (options.only) {
27
29
  const targets = options.only.split(",");
28
30
  const excludeRules = targets.indexOf("firestore:indexes") >= 0;
@@ -57,5 +59,13 @@ async function default_1(context, options) {
57
59
  if (context.firestore.rules.length > 0) {
58
60
  await rulesDeploy.compile();
59
61
  }
62
+ const rulesContext = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rules;
63
+ for (const ruleContext of rulesContext) {
64
+ const databaseId = ruleContext.databaseId;
65
+ const rulesFile = ruleContext.rulesFile;
66
+ if (!rulesFile) {
67
+ logger_1.logger.error(`Invalid firestore config for ${databaseId} database: ${JSON.stringify(options.config.src.firestore)}`);
68
+ }
69
+ }
60
70
  }
61
71
  exports.default = default_1;
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const rulesDeploy_1 = require("../../rulesDeploy");
4
- const logger_1 = require("../../logger");
5
- async function default_1(context, options) {
4
+ async function default_1(context) {
6
5
  var _a, _b;
7
6
  const rulesDeploy = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rulesDeploy;
8
7
  if (!context.firestoreRules || !rulesDeploy) {
@@ -12,11 +11,9 @@ async function default_1(context, options) {
12
11
  await Promise.all(rulesContext.map(async (ruleContext) => {
13
12
  const databaseId = ruleContext.databaseId;
14
13
  const rulesFile = ruleContext.rulesFile;
15
- if (!rulesFile) {
16
- logger_1.logger.error(`Invalid firestore config for ${databaseId} database: ${JSON.stringify(options.config.src.firestore)}`);
17
- return;
14
+ if (rulesFile) {
15
+ return rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE, databaseId);
18
16
  }
19
- return rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE, databaseId);
20
17
  }));
21
18
  }
22
19
  exports.default = default_1;
@@ -102,7 +102,7 @@ function obtainDefaultComputeServiceAgentBindings(projectNumber) {
102
102
  return [runInvokerBinding, eventarcEventReceiverBinding];
103
103
  }
104
104
  exports.obtainDefaultComputeServiceAgentBindings = obtainDefaultComputeServiceAgentBindings;
105
- async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
105
+ async function ensureServiceAgentRoles(projectId, projectNumber, want, have, dryRun) {
106
106
  const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
107
107
  const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
108
108
  const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
@@ -138,7 +138,12 @@ async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
138
138
  return;
139
139
  }
140
140
  try {
141
- await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
141
+ if (dryRun) {
142
+ logger_1.logger.info(`On your next deploy, the following required roles will be granted: ${requiredBindings.map((b) => `${b.members.join(", ")}: ${(0, colorette_1.bold)(b.role)}`)}`);
143
+ }
144
+ else {
145
+ await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
146
+ }
142
147
  }
143
148
  catch (err) {
144
149
  iam.printManualIamConfig(requiredBindings, projectId, "functions");
@@ -79,11 +79,19 @@ async function secretsToServiceAccounts(b) {
79
79
  }
80
80
  return secretsToSa;
81
81
  }
82
- async function secretAccess(projectId, wantBackend, haveBackend) {
82
+ async function secretAccess(projectId, wantBackend, haveBackend, dryRun) {
83
83
  var _a, _b;
84
84
  const ensureAccess = async (secret, serviceAccounts) => {
85
85
  (0, utils_1.logLabeledBullet)("functions", `ensuring ${clc.bold(serviceAccounts.join(", "))} access to secret ${clc.bold(secret)}.`);
86
- await (0, secretManager_1.ensureServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
86
+ if (dryRun) {
87
+ const check = await (0, secretManager_1.checkServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
88
+ if (check.length) {
89
+ (0, utils_1.logLabeledBullet)("functions", `On your next deploy, ${clc.bold(serviceAccounts.join(", "))} will be granted access to secret ${clc.bold(secret)}.`);
90
+ }
91
+ }
92
+ else {
93
+ await (0, secretManager_1.ensureServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
94
+ }
87
95
  (0, utils_1.logLabeledSuccess)("functions", `ensured ${clc.bold(serviceAccounts.join(", "))} access to ${clc.bold(secret)}.`);
88
96
  };
89
97
  const wantSecrets = await secretsToServiceAccounts(wantBackend);
@@ -183,9 +183,9 @@ async function prepare(context, options, payload) {
183
183
  await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
184
184
  await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
185
185
  await backend.checkAvailability(context, matchingBackend);
186
- await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend);
187
186
  await validate.secretsAreValid(projectId, matchingBackend);
188
- await ensure.secretAccess(projectId, matchingBackend, haveBackend);
187
+ await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend, options.dryRun);
188
+ await ensure.secretAccess(projectId, matchingBackend, haveBackend, options.dryRun);
189
189
  updateEndpointTargetedStatus(wantBackends, context.filters || []);
190
190
  (0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
191
191
  }
@@ -41,6 +41,7 @@ const chain = async function (fns, context, options, payload) {
41
41
  }
42
42
  };
43
43
  const deploy = async function (targetNames, options, customContext = {}) {
44
+ var _a;
44
45
  const projectId = (0, projectUtils_1.needProjectId)(options);
45
46
  const payload = {};
46
47
  const context = Object.assign({ projectId }, customContext);
@@ -85,9 +86,11 @@ const deploy = async function (targetNames, options, customContext = {}) {
85
86
  }
86
87
  predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
87
88
  prepares.push(target.prepare);
88
- deploys.push(target.deploy);
89
- releases.push(target.release);
90
- postdeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "postdeploy"));
89
+ if (!options.dryRun) {
90
+ deploys.push(target.deploy);
91
+ releases.push(target.release);
92
+ postdeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "postdeploy"));
93
+ }
91
94
  }
92
95
  logger_1.logger.info();
93
96
  logger_1.logger.info((0, colorette_1.bold)((0, colorette_1.white)("===") + " Deploying to '" + projectId + "'..."));
@@ -110,11 +113,12 @@ const deploy = async function (targetNames, options, customContext = {}) {
110
113
  analyticsParams[t] = "true";
111
114
  }
112
115
  await (0, track_1.trackGA4)("product_deploy", analyticsParams, duration);
116
+ const successMessage = options.dryRun ? "Dry run complete!" : "Deploy complete!";
113
117
  logger_1.logger.info();
114
- (0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)("Deploy complete!")));
118
+ (0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)(successMessage)));
115
119
  logger_1.logger.info();
116
120
  const deployedHosting = (0, lodash_1.includes)(targetNames, "hosting");
117
- logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)(options.project, "/overview"));
121
+ logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)((_a = options.project) !== null && _a !== void 0 ? _a : "_", "/overview"));
118
122
  if (deployedHosting) {
119
123
  (0, lodash_1.each)(context.hosting.deploys, (deploy) => {
120
124
  logger_1.logger.info((0, colorette_1.bold)("Hosting URL:"), (0, utils_1.addSubdomain)((0, api_1.hostingOrigin)(), deploy.config.site));
@@ -46,20 +46,20 @@ const EMULATOR_UPDATE_DETAILS = {
46
46
  },
47
47
  dataconnect: process.platform === "darwin"
48
48
  ? {
49
- version: "1.3.6",
50
- expectedSize: 24867584,
51
- expectedChecksum: "b924d31e3620d7ed4486a95e22629fc8",
49
+ version: "1.3.7",
50
+ expectedSize: 25019136,
51
+ expectedChecksum: "648ca3e289db8209c2d555bb381e5e5e",
52
52
  }
53
53
  : process.platform === "win32"
54
54
  ? {
55
- version: "1.3.6",
56
- expectedSize: 25292288,
57
- expectedChecksum: "45025491b43b55a94f4e4db8df903250",
55
+ version: "1.3.7",
56
+ expectedSize: 25441792,
57
+ expectedChecksum: "16081147aa94f1b8691329d5b9430b69",
58
58
  }
59
59
  : {
60
- version: "1.3.6",
61
- expectedSize: 24785048,
62
- expectedChecksum: "6ae5820c0470c5a954540ad97838ec01",
60
+ version: "1.3.7",
61
+ expectedSize: 24928408,
62
+ expectedChecksum: "8749930f3a43f616e3ae231af8c787a8",
63
63
  },
64
64
  };
65
65
  exports.DownloadDetails = {
@@ -4,14 +4,21 @@ exports.checkDatabaseType = void 0;
4
4
  const api_1 = require("../api");
5
5
  const apiv2_1 = require("../apiv2");
6
6
  const logger_1 = require("../logger");
7
- async function checkDatabaseType(projectId) {
7
+ const error_1 = require("../error");
8
+ async function checkDatabaseType(projectId, databaseId = "(default)") {
8
9
  try {
9
10
  const client = new apiv2_1.Client({ urlPrefix: (0, api_1.firestoreOrigin)(), apiVersion: "v1" });
10
- const resp = await client.get(`/projects/${projectId}/databases/(default)`);
11
+ const resp = await client.get(`/projects/${projectId}/databases/${databaseId}`);
11
12
  return resp.body.type;
12
13
  }
13
14
  catch (err) {
14
- logger_1.logger.debug("error getting database type", err);
15
+ logger_1.logger.debug("error getting database type: ", err);
16
+ if (err instanceof error_1.FirebaseError) {
17
+ if (err.status === 404) {
18
+ logger_1.logger.info(`${databaseId} does not exist in project ${projectId}.`);
19
+ return "DATABASE_DOES_NOT_EXIST";
20
+ }
21
+ }
15
22
  return undefined;
16
23
  }
17
24
  }
@@ -15,7 +15,7 @@ exports.support = "preview";
15
15
  exports.type = 3;
16
16
  exports.docsUrl = "https://firebase.google.com/docs/hosting/frameworks/angular";
17
17
  const DEFAULT_BUILD_SCRIPT = ["ng build"];
18
- exports.supportedRange = "14 - 17";
18
+ exports.supportedRange = "16 - 18";
19
19
  async function discover(dir) {
20
20
  if (!(await (0, fs_extra_1.pathExists)((0, path_1.join)(dir, "package.json"))))
21
21
  return;
@@ -181,9 +181,21 @@ exports.handle = function(req,res) {
181
181
  };\n`;
182
182
  }
183
183
  else if (serverOutputPath) {
184
- bootstrapScript = `const app = ${(serverEntry === null || serverEntry === void 0 ? void 0 : serverEntry.endsWith(".mjs"))
184
+ bootstrapScript = `
185
+ const app = new Promise((resolve) => {
186
+ setTimeout(() => {
187
+ const port = process.env.PORT;
188
+ const socket = 'express.sock';
189
+ process.env.PORT = socket;
190
+
191
+ ${(serverEntry === null || serverEntry === void 0 ? void 0 : serverEntry.endsWith(".mjs"))
185
192
  ? `import(\`./${serverOutputPath}/${serverEntry}\`)`
186
- : `Promise.resolve(require('./${serverOutputPath}/${serverEntry}'))`}.then(server => server.app());
193
+ : `Promise.resolve(require('./${serverOutputPath}/${serverEntry}'))`}.then(({ app }) => {
194
+ process.env.PORT = port;
195
+ resolve(app());
196
+ });
197
+ }, 0);
198
+ });
187
199
  exports.handle = (req,res) => app.then(it => it(req,res));\n`;
188
200
  }
189
201
  else {
@@ -265,7 +265,7 @@ exports.findEsbuildPath = findEsbuildPath;
265
265
  function getGlobalEsbuildVersion(binPath) {
266
266
  var _a;
267
267
  try {
268
- const versionOutput = (_a = (0, child_process_1.execSync)(`${binPath} --version`, { encoding: "utf8" })) === null || _a === void 0 ? void 0 : _a.trim();
268
+ const versionOutput = (_a = (0, child_process_1.execSync)(`"${binPath}" --version`, { encoding: "utf8" })) === null || _a === void 0 ? void 0 : _a.trim();
269
269
  if (!versionOutput) {
270
270
  return null;
271
271
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setupSQLPermissions = exports.iamUserIsCSQLAdmin = exports.checkSQLRoleIsGranted = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = void 0;
3
+ exports.setupSQLPermissions = exports.iamUserIsCSQLAdmin = exports.checkSQLRoleIsGranted = exports.fdcSqlRoleMap = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = void 0;
4
4
  const projectUtils_1 = require("../../projectUtils");
5
5
  const connect_1 = require("./connect");
6
6
  const iam_1 = require("../iam");
@@ -19,6 +19,11 @@ function firebasewriter(databaseId) {
19
19
  return `firebasewriter_${databaseId}_public`;
20
20
  }
21
21
  exports.firebasewriter = firebasewriter;
22
+ exports.fdcSqlRoleMap = {
23
+ owner: firebaseowner,
24
+ writer: firebasewriter,
25
+ reader: firebasereader,
26
+ };
22
27
  async function checkSQLRoleIsGranted(options, instanceId, databaseId, grantedRole, granteeRole) {
23
28
  const checkCmd = `
24
29
  DO $$
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.labels = exports.ensureApi = exports.isAppHostingManaged = exports.isFunctionsManaged = exports.FIREBASE_MANAGED = exports.ensureServiceAgentRole = exports.setIamPolicy = exports.getIamPolicy = exports.addVersion = exports.deleteSecret = exports.patchSecret = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.destroySecretVersion = exports.accessSecretVersion = exports.getSecretVersion = exports.listSecretVersions = exports.getSecretMetadata = exports.listSecrets = exports.getSecret = exports.secretManagerConsoleUri = void 0;
3
+ exports.labels = exports.ensureApi = exports.isAppHostingManaged = exports.isFunctionsManaged = exports.FIREBASE_MANAGED = exports.checkServiceAgentRole = exports.ensureServiceAgentRole = exports.setIamPolicy = exports.getIamPolicy = exports.addVersion = exports.deleteSecret = exports.patchSecret = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.destroySecretVersion = exports.accessSecretVersion = exports.getSecretVersion = exports.listSecretVersions = exports.getSecretMetadata = exports.listSecrets = exports.getSecret = exports.secretManagerConsoleUri = void 0;
4
4
  const utils_1 = require("../utils");
5
5
  const error_1 = require("../error");
6
6
  const apiv2_1 = require("../apiv2");
@@ -207,6 +207,14 @@ async function setIamPolicy(secret, bindings) {
207
207
  }
208
208
  exports.setIamPolicy = setIamPolicy;
209
209
  async function ensureServiceAgentRole(secret, serviceAccountEmails, role) {
210
+ const bindings = await checkServiceAgentRole(secret, serviceAccountEmails, role);
211
+ if (bindings.length) {
212
+ await module.exports.setIamPolicy(secret, bindings);
213
+ }
214
+ (0, utils_1.logLabeledSuccess)("secretmanager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmails.join(", ")}`);
215
+ }
216
+ exports.ensureServiceAgentRole = ensureServiceAgentRole;
217
+ async function checkServiceAgentRole(secret, serviceAccountEmails, role) {
210
218
  const policy = await module.exports.getIamPolicy(secret);
211
219
  const bindings = policy.bindings || [];
212
220
  let binding = bindings.find((b) => b.role === role);
@@ -222,11 +230,10 @@ async function ensureServiceAgentRole(secret, serviceAccountEmails, role) {
222
230
  }
223
231
  }
224
232
  if (shouldShortCircuit)
225
- return;
226
- await module.exports.setIamPolicy(secret, bindings);
227
- (0, utils_1.logLabeledSuccess)("secretmanager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmails.join(", ")}`);
233
+ return [];
234
+ return bindings;
228
235
  }
229
- exports.ensureServiceAgentRole = ensureServiceAgentRole;
236
+ exports.checkServiceAgentRole = checkServiceAgentRole;
230
237
  exports.FIREBASE_MANAGED = "firebase-managed";
231
238
  function isFunctionsManaged(secret) {
232
239
  return (secret.labels[exports.FIREBASE_MANAGED] === "true" || secret.labels[exports.FIREBASE_MANAGED] === "functions");
@@ -8,13 +8,16 @@ const provisionCloudSql_1 = require("../../../dataconnect/provisionCloudSql");
8
8
  const freeTrial_1 = require("../../../dataconnect/freeTrial");
9
9
  const cloudsql = require("../../../gcp/cloudsql/cloudsqladmin");
10
10
  const ensureApis_1 = require("../../../dataconnect/ensureApis");
11
+ const experiments = require("../../../experiments");
11
12
  const client_1 = require("../../../dataconnect/client");
12
13
  const emulators_1 = require("../emulators");
13
14
  const names_1 = require("../../../dataconnect/names");
14
15
  const logger_1 = require("../../../logger");
15
16
  const templates_1 = require("../../../templates");
16
17
  const utils_1 = require("../../../utils");
18
+ const cloudbilling_1 = require("../../../gcp/cloudbilling");
17
19
  const DATACONNECT_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect.yaml");
20
+ const DATACONNECT_YAML_COMPAT_EXPERIMENT_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/dataconnect-fdccompatiblemode.yaml");
18
21
  const CONNECTOR_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/connector.yaml");
19
22
  const SCHEMA_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/schema.gql");
20
23
  const QUERIES_TEMPLATE = (0, templates_1.readTemplateSync)("init/dataconnect/queries.gql");
@@ -53,7 +56,8 @@ async function askQuestions(setup, config) {
53
56
  schemaGql: [],
54
57
  shouldProvisionCSQL: false,
55
58
  };
56
- info = await promptForService(setup, info);
59
+ const isBillingEnabled = setup.projectId ? await (0, cloudbilling_1.checkBillingEnabled)(setup.projectId) : false;
60
+ info = await promptForService(setup, info, isBillingEnabled);
57
61
  if (info.cloudSqlInstanceId === "") {
58
62
  info = await promptForCloudSQLInstance(setup, info);
59
63
  }
@@ -70,8 +74,9 @@ async function askQuestions(setup, config) {
70
74
  setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
71
75
  info.shouldProvisionCSQL = !!(setup.projectId &&
72
76
  (info.isNewInstance || info.isNewDatabase) &&
77
+ isBillingEnabled &&
73
78
  (await (0, prompt_1.confirm)({
74
- message: "Would you like to provision your CloudSQL instance and database now? This will take a few minutes.",
79
+ message: `Would you like to provision your Cloud SQL instance and database now?${info.isNewInstance ? " This will take several minutes." : ""}.`,
75
80
  default: true,
76
81
  })));
77
82
  return info;
@@ -125,7 +130,9 @@ function subDataconnectYamlValues(replacementValues) {
125
130
  connectorDirs: "__connectorDirs__",
126
131
  locationId: "__location__",
127
132
  };
128
- let replaced = DATACONNECT_YAML_TEMPLATE;
133
+ let replaced = experiments.isEnabled("fdccompatiblemode")
134
+ ? DATACONNECT_YAML_COMPAT_EXPERIMENT_TEMPLATE
135
+ : DATACONNECT_YAML_TEMPLATE;
129
136
  for (const [k, v] of Object.entries(replacementValues)) {
130
137
  replaced = replaced.replace(replacements[k], JSON.stringify(v));
131
138
  }
@@ -141,10 +148,15 @@ function subConnectorYamlValues(replacementValues) {
141
148
  }
142
149
  return replaced;
143
150
  }
144
- async function promptForService(setup, info) {
145
- var _a, _b, _c, _d;
151
+ async function promptForService(setup, info, isBillingEnabled) {
152
+ var _a, _b, _c;
146
153
  if (setup.projectId) {
147
- await (0, ensureApis_1.ensureApis)(setup.projectId);
154
+ if (isBillingEnabled) {
155
+ await (0, ensureApis_1.ensureApis)(setup.projectId);
156
+ }
157
+ else {
158
+ await (0, ensureApis_1.ensureSparkApis)(setup.projectId);
159
+ }
148
160
  const existingServices = await (0, client_1.listAllServices)(setup.projectId);
149
161
  const existingServicesAndSchemas = await Promise.all(existingServices.map(async (s) => {
150
162
  return {
@@ -171,14 +183,15 @@ async function promptForService(setup, info) {
171
183
  info.serviceId = serviceName.serviceId;
172
184
  info.locationId = serviceName.location;
173
185
  if (choice.schema) {
174
- if ((_a = choice.schema.primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) {
175
- const instanceName = (0, names_1.parseCloudSQLInstanceName)((_b = choice.schema.primaryDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.cloudSql.instance);
186
+ const primaryDatasource = choice.schema.datasources.find((d) => d.postgresql);
187
+ if ((_a = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) {
188
+ const instanceName = (0, names_1.parseCloudSQLInstanceName)(primaryDatasource.postgresql.cloudSql.instance);
176
189
  info.cloudSqlInstanceId = instanceName.instanceId;
177
190
  }
178
191
  if (choice.schema.source.files) {
179
192
  info.schemaGql = choice.schema.source.files;
180
193
  }
181
- info.cloudSqlDatabase = (_d = (_c = choice.schema.primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
194
+ info.cloudSqlDatabase = (_c = (_b = primaryDatasource === null || primaryDatasource === void 0 ? void 0 : primaryDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database) !== null && _c !== void 0 ? _c : "";
182
195
  const connectors = await (0, client_1.listConnectors)(choice.service.name, [
183
196
  "connectors.name",
184
197
  "connectors.source.files",
@@ -201,7 +214,7 @@ async function promptForService(setup, info) {
201
214
  info.serviceId = await (0, prompt_1.promptOnce)({
202
215
  message: "What ID would you like to use for this service?",
203
216
  type: "input",
204
- default: "my-service",
217
+ default: "app",
205
218
  });
206
219
  }
207
220
  return info;
@@ -233,7 +246,7 @@ async function promptForCloudSQLInstance(setup, info) {
233
246
  info.cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
234
247
  message: `What ID would you like to use for your new CloudSQL instance?`,
235
248
  type: "input",
236
- default: `fdc-sql`,
249
+ default: `${info.serviceId || "app"}-fdc`,
237
250
  });
238
251
  }
239
252
  if (info.locationId === "") {
@@ -9,13 +9,14 @@ const rules = require("./rules");
9
9
  const indexes = require("./indexes");
10
10
  const error_1 = require("../../../error");
11
11
  const clc = require("colorette");
12
+ const prompt_1 = require("../../../prompt");
12
13
  async function checkProjectSetup(setup, config, options) {
13
14
  const firestoreUnusedError = new error_1.FirebaseError(`It looks like you haven't used Cloud Firestore in this project before. Go to ${clc.bold(clc.underline(`https://console.firebase.google.com/project/${setup.projectId}/firestore`))} to create your Cloud Firestore database.`, { exit: 1 });
14
15
  const isFirestoreEnabled = await apiEnabled.check(setup.projectId, "firestore.googleapis.com", "", true);
15
16
  if (!isFirestoreEnabled) {
16
17
  throw firestoreUnusedError;
17
18
  }
18
- const dbType = await (0, checkDatabaseType_1.checkDatabaseType)(setup.projectId);
19
+ const dbType = await getDatabaseType(setup);
19
20
  logger_1.logger.debug(`database_type: ${dbType}`);
20
21
  if (!dbType) {
21
22
  throw firestoreUnusedError;
@@ -25,6 +26,24 @@ async function checkProjectSetup(setup, config, options) {
25
26
  }
26
27
  await (0, requirePermissions_1.requirePermissions)(Object.assign(Object.assign({}, options), { project: setup.projectId }));
27
28
  }
29
+ async function getDatabaseType(setup) {
30
+ const dbType = await (0, checkDatabaseType_1.checkDatabaseType)(setup.projectId, setup.databaseId);
31
+ logger_1.logger.debug(`database_type: ${dbType}`);
32
+ if (dbType === "DATABASE_DOES_NOT_EXIST") {
33
+ setup.databaseId = await selectDatabaseByPrompting();
34
+ return await getDatabaseType(setup);
35
+ }
36
+ else {
37
+ return dbType;
38
+ }
39
+ }
40
+ async function selectDatabaseByPrompting() {
41
+ const database = await (0, prompt_1.promptOnce)({
42
+ type: "input",
43
+ message: "Please input the name of the Native Firestore database you would like to use:",
44
+ });
45
+ return database;
46
+ }
28
47
  async function doSetup(setup, config, options) {
29
48
  if (setup.projectId) {
30
49
  await checkProjectSetup(setup, config, options);
@@ -47,15 +47,15 @@ function initIndexes(setup, config) {
47
47
  if (!setup.projectId) {
48
48
  return config.writeProjectFile(setup.config.firestore.indexes, INDEXES_TEMPLATE);
49
49
  }
50
- return getIndexesFromConsole(setup.projectId).then((contents) => {
50
+ return getIndexesFromConsole(setup.projectId, setup.databaseId).then((contents) => {
51
51
  return config.writeProjectFile(setup.config.firestore.indexes, contents);
52
52
  });
53
53
  });
54
54
  }
55
55
  exports.initIndexes = initIndexes;
56
- function getIndexesFromConsole(projectId) {
57
- const indexesPromise = indexes.listIndexes(projectId);
58
- const fieldOverridesPromise = indexes.listFieldOverrides(projectId);
56
+ function getIndexesFromConsole(projectId, databaseId) {
57
+ const indexesPromise = indexes.listIndexes(projectId, databaseId);
58
+ const fieldOverridesPromise = indexes.listFieldOverrides(projectId, databaseId);
59
59
  return Promise.all([indexesPromise, fieldOverridesPromise])
60
60
  .then((res) => {
61
61
  return indexes.makeIndexSpec(res[0], res[1]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.17.0",
3
+ "version": "13.18.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {