firebase-tools 13.34.0 → 13.35.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/apphosting/config.js +8 -6
  2. package/lib/apphosting/yaml.js +21 -48
  3. package/lib/commands/dataconnect-sdk-generate.js +4 -1
  4. package/lib/commands/dataconnect-sql-diff.js +1 -1
  5. package/lib/commands/dataconnect-sql-setup.js +6 -1
  6. package/lib/commands/functions-artifacts-setpolicy.js +125 -0
  7. package/lib/commands/index.js +2 -0
  8. package/lib/commands/open.js +3 -0
  9. package/lib/dataconnect/build.js +3 -1
  10. package/lib/dataconnect/fileUtils.js +8 -4
  11. package/lib/dataconnect/schemaMigration.js +27 -24
  12. package/lib/defaultCredentials.js +12 -1
  13. package/lib/deploy/dataconnect/prepare.js +1 -1
  14. package/lib/deploy/functions/containerCleaner.js +17 -2
  15. package/lib/deploy/functions/runtimes/discovery/index.js +3 -1
  16. package/lib/deploy/index.js +10 -4
  17. package/lib/emulator/ExpressBasedEmulator.js +1 -1
  18. package/lib/emulator/apphosting/index.js +1 -0
  19. package/lib/emulator/apphosting/serve.js +48 -7
  20. package/lib/emulator/controller.js +1 -0
  21. package/lib/emulator/dataconnect/pgliteServer.js +7 -2
  22. package/lib/emulator/dataconnectEmulator.js +15 -4
  23. package/lib/emulator/downloadableEmulators.js +9 -9
  24. package/lib/emulator/env.js +17 -1
  25. package/lib/emulator/functionsEmulator.js +2 -20
  26. package/lib/extensions/extensionsHelper.js +8 -4
  27. package/lib/frameworks/angular/utils.js +60 -42
  28. package/lib/functions/artifacts.js +104 -0
  29. package/lib/gcp/artifactregistry.js +27 -2
  30. package/lib/gcp/cloudsql/connect.js +17 -5
  31. package/lib/gcp/cloudsql/permissions_setup.js +28 -14
  32. package/lib/init/features/dataconnect/index.js +1 -1
  33. package/lib/init/features/dataconnect/sdk.js +23 -3
  34. package/package.json +1 -1
  35. package/templates/init/dataconnect/connector.yaml +1 -1
@@ -1,16 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.deletePackage = exports.API_VERSION = void 0;
3
+ exports.updateRepository = exports.getRepository = exports.deletePackage = exports.ensureApiEnabled = exports.API_VERSION = void 0;
4
4
  const apiv2_1 = require("../apiv2");
5
5
  const api_1 = require("../api");
6
- exports.API_VERSION = "v1beta2";
6
+ const metaprogramming_1 = require("../metaprogramming");
7
+ const api = require("../ensureApiEnabled");
8
+ const proto = require("./proto");
9
+ exports.API_VERSION = "v1";
7
10
  const client = new apiv2_1.Client({
8
11
  urlPrefix: (0, api_1.artifactRegistryDomain)(),
9
12
  auth: true,
10
13
  apiVersion: exports.API_VERSION,
11
14
  });
15
+ function ensureApiEnabled(projectId) {
16
+ return api.ensure(projectId, (0, api_1.artifactRegistryDomain)(), "artifactregistry", true);
17
+ }
18
+ exports.ensureApiEnabled = ensureApiEnabled;
19
+ (0, metaprogramming_1.assertImplements)();
12
20
  async function deletePackage(name) {
13
21
  const res = await client.delete(name);
14
22
  return res.body;
15
23
  }
16
24
  exports.deletePackage = deletePackage;
25
+ async function getRepository(repoPath) {
26
+ const res = await client.get(repoPath);
27
+ return res.body;
28
+ }
29
+ exports.getRepository = getRepository;
30
+ async function updateRepository(repo) {
31
+ const updateMask = proto.fieldMasks(repo, "cleanupPolicies", "labels");
32
+ if (updateMask.length === 0) {
33
+ const res = await client.get(repo.name);
34
+ return res.body;
35
+ }
36
+ const res = await client.patch(`/${repo.name}`, repo, {
37
+ queryParams: { updateMask: updateMask.join(",") },
38
+ });
39
+ return res.body;
40
+ }
41
+ exports.updateRepository = updateRepository;
@@ -59,25 +59,35 @@ async function execute(sqlStatements, opts) {
59
59
  break;
60
60
  }
61
61
  }
62
+ const cleanUpFn = async () => {
63
+ conn.release();
64
+ await pool.end();
65
+ connector.close();
66
+ };
62
67
  const conn = await pool.connect();
63
68
  const results = [];
64
69
  logFn(`Logged in as ${opts.username}`);
70
+ if (opts.transaction) {
71
+ sqlStatements.unshift("BEGIN;");
72
+ sqlStatements.push("COMMIT;");
73
+ }
65
74
  for (const s of sqlStatements) {
66
75
  logFn(`Executing: '${s}'`);
67
76
  try {
68
77
  results.push(await conn.query(s));
69
78
  }
70
79
  catch (err) {
80
+ logFn(`Rolling back transaction due to error ${err}}`);
81
+ await conn.query("ROLLBACK;");
82
+ await cleanUpFn();
71
83
  throw new error_1.FirebaseError(`Error executing ${err}`);
72
84
  }
73
85
  }
74
- conn.release();
75
- await pool.end();
76
- connector.close();
86
+ await cleanUpFn();
77
87
  return results;
78
88
  }
79
89
  exports.execute = execute;
80
- async function executeSqlCmdsAsIamUser(options, instanceId, databaseId, cmds, silent = false) {
90
+ async function executeSqlCmdsAsIamUser(options, instanceId, databaseId, cmds, silent = false, transaction = false) {
81
91
  const projectId = (0, projectUtils_1.needProjectId)(options);
82
92
  const { user: iamUser } = await getIAMUser(options);
83
93
  return await execute(cmds, {
@@ -86,10 +96,11 @@ async function executeSqlCmdsAsIamUser(options, instanceId, databaseId, cmds, si
86
96
  databaseId,
87
97
  username: iamUser,
88
98
  silent: silent,
99
+ transaction: transaction,
89
100
  });
90
101
  }
91
102
  exports.executeSqlCmdsAsIamUser = executeSqlCmdsAsIamUser;
92
- async function executeSqlCmdsAsSuperUser(options, instanceId, databaseId, cmds, silent = false) {
103
+ async function executeSqlCmdsAsSuperUser(options, instanceId, databaseId, cmds, silent = false, transaction = false) {
93
104
  const projectId = (0, projectUtils_1.needProjectId)(options);
94
105
  const superuser = "firebasesuperuser";
95
106
  const temporaryPassword = utils.generateId(20);
@@ -101,6 +112,7 @@ async function executeSqlCmdsAsSuperUser(options, instanceId, databaseId, cmds,
101
112
  username: superuser,
102
113
  password: temporaryPassword,
103
114
  silent: silent,
115
+ transaction: transaction,
104
116
  });
105
117
  }
106
118
  exports.executeSqlCmdsAsSuperUser = executeSqlCmdsAsSuperUser;
@@ -66,23 +66,24 @@ async function checkSQLRoleIsGranted(options, instanceId, databaseId, grantedRol
66
66
  exports.checkSQLRoleIsGranted = checkSQLRoleIsGranted;
67
67
  async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options, silent = false) {
68
68
  const schema = schemaInfo.name;
69
- logger_1.logger.info(`Attempting to Setup SQL schema "${schema}".`);
69
+ logger_1.logger.info(`Detected schema "${schema}" setup status is ${schemaInfo.setupStatus}. Running setup...`);
70
70
  const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
71
71
  if (!userIsCSQLAdmin) {
72
72
  throw new error_1.FirebaseError(`Missing required IAM permission to setup SQL schemas. SQL schema setup requires 'roles/cloudsql.admin' or an equivalent role.`);
73
73
  }
74
74
  await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
75
+ let runGreenfieldSetup = false;
75
76
  if (schemaInfo.setupStatus === SchemaSetupStatus.GreenField) {
76
- logger_1.logger.info(`Database ${databaseId} has already been setup. Rerunning setup to repair any missing permissions.`);
77
- await greenFieldSchemaSetup(instanceId, databaseId, schema, options, silent);
78
- return SchemaSetupStatus.GreenField;
79
- }
80
- else {
81
- logger_1.logger.info(`Detected schema "${schema}" setup status is ${schemaInfo.setupStatus}.`);
77
+ runGreenfieldSetup = true;
78
+ logger_1.logger.info(`Database ${databaseId} has already been setup as greenfield project. Rerunning setup to repair any missing permissions.`);
82
79
  }
83
80
  if (schemaInfo.tables.length === 0) {
81
+ runGreenfieldSetup = true;
84
82
  logger_1.logger.info(`Found no tables in schema "${schema}", assuming greenfield project.`);
85
- await greenFieldSchemaSetup(instanceId, databaseId, schema, options, silent);
83
+ }
84
+ if (runGreenfieldSetup) {
85
+ const greenfieldSetupCmds = await greenFieldSchemaSetup(instanceId, databaseId, schema, options);
86
+ await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, greenfieldSetupCmds, silent, true);
86
87
  logger_1.logger.info(clc.green("Database setup complete."));
87
88
  return SchemaSetupStatus.GreenField;
88
89
  }
@@ -99,6 +100,8 @@ async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options,
99
100
  });
100
101
  if (shouldSetupGreenfield) {
101
102
  await setupBrownfieldAsGreenfield(instanceId, databaseId, schemaInfo, options, silent);
103
+ logger_1.logger.info(clc.green("Database setup complete."));
104
+ logger_1.logger.info(clc.yellow("IMPORTANT: please uncomment 'schemaValidation: \"COMPATIBLE\"' in your dataconnect.yaml file to avoid dropping any existing tables by mistake."));
102
105
  return SchemaSetupStatus.GreenField;
103
106
  }
104
107
  else {
@@ -110,7 +113,7 @@ async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options,
110
113
  }
111
114
  }
112
115
  exports.setupSQLPermissions = setupSQLPermissions;
113
- async function greenFieldSchemaSetup(instanceId, databaseId, schema, options, silent = false) {
116
+ async function greenFieldSchemaSetup(instanceId, databaseId, schema, options) {
114
117
  const revokes = [];
115
118
  if (await checkSQLRoleIsGranted(options, instanceId, databaseId, "cloudsqlsuperuser", (0, permissions_1.firebaseowner)(databaseId))) {
116
119
  logger_1.logger.warn("Detected cloudsqlsuperuser was previously given to firebase owner, revoking to improve database security.");
@@ -120,7 +123,7 @@ async function greenFieldSchemaSetup(instanceId, databaseId, schema, options, si
120
123
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
121
124
  const { user: fdcP4SAUser } = (0, connect_3.toDatabaseUser)((0, connect_3.getDataConnectP4SA)(projectNumber));
122
125
  const sqlRoleSetupCmds = (0, lodash_1.concat)(revokes, [`CREATE SCHEMA IF NOT EXISTS "${schema}"`], (0, permissions_1.ownerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), (0, permissions_1.writerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), (0, permissions_1.readerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), `GRANT "${(0, permissions_1.firebaseowner)(databaseId, schema)}" TO "${user}"`, `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${fdcP4SAUser}"`, (0, permissions_1.defaultPermissions)(databaseId, schema, (0, permissions_1.firebaseowner)(databaseId, schema)));
123
- await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, sqlRoleSetupCmds, silent);
126
+ return sqlRoleSetupCmds;
124
127
  }
125
128
  exports.greenFieldSchemaSetup = greenFieldSchemaSetup;
126
129
  async function getSchemaMetadata(instanceId, databaseId, schema, options) {
@@ -172,18 +175,28 @@ async function getSchemaMetadata(instanceId, databaseId, schema, options) {
172
175
  exports.getSchemaMetadata = getSchemaMetadata;
173
176
  async function setupBrownfieldAsGreenfield(instanceId, databaseId, schemaInfo, options, silent = false) {
174
177
  const schema = schemaInfo.name;
175
- await greenFieldSchemaSetup(instanceId, databaseId, schema, options, silent);
176
178
  const firebaseOwnerRole = (0, permissions_1.firebaseowner)(databaseId, schema);
177
179
  const nonFirebasetablesOwners = [...new Set(schemaInfo.tables.map((t) => t.owner))].filter((owner) => owner !== firebaseOwnerRole);
180
+ const grantOwnersToSuperuserCmds = nonFirebasetablesOwners.map((owner) => `GRANT "${owner}" TO "${permissions_1.FIREBASE_SUPER_USER}"`);
181
+ const revokeOwnersFromSuperuserCmds = nonFirebasetablesOwners.map((owner) => `REVOKE "${owner}" FROM "${permissions_1.FIREBASE_SUPER_USER}"`);
182
+ const greenfieldSetupCmds = await greenFieldSchemaSetup(instanceId, databaseId, schema, options);
178
183
  const grantCmds = nonFirebasetablesOwners.map((owner) => `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${owner}"`);
179
184
  const alterTableCmds = schemaInfo.tables.map((table) => `ALTER TABLE "${schema}"."${table.name}" OWNER TO "${firebaseOwnerRole}";`);
180
- await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [...grantCmds, ...alterTableCmds], silent);
185
+ const setupCmds = [
186
+ ...grantOwnersToSuperuserCmds,
187
+ ...greenfieldSetupCmds,
188
+ ...grantCmds,
189
+ ...alterTableCmds,
190
+ ...revokeOwnersFromSuperuserCmds,
191
+ ];
192
+ await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, setupCmds, silent, true);
181
193
  }
182
194
  exports.setupBrownfieldAsGreenfield = setupBrownfieldAsGreenfield;
183
195
  async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, silent = false) {
184
196
  const schema = schemaInfo.name;
185
197
  const uniqueTablesOwners = [...new Set(schemaInfo.tables.map((t) => t.owner))];
186
- const grantOwnersToFirebasesuperuser = uniqueTablesOwners.map((owner) => `GRANT ${owner} TO ${permissions_1.FIREBASE_SUPER_USER}`);
198
+ const grantOwnersToFirebasesuperuser = uniqueTablesOwners.map((owner) => `GRANT "${owner}" TO "${permissions_1.FIREBASE_SUPER_USER}"`);
199
+ const revokeOwnersFromFirebasesuperuser = uniqueTablesOwners.map((owner) => `REVOKE "${owner}" FROM "${permissions_1.FIREBASE_SUPER_USER}"`);
187
200
  const iamUser = (await (0, connect_2.getIAMUser)(options)).user;
188
201
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
189
202
  const { user: fdcP4SAUser } = (0, connect_3.toDatabaseUser)((0, connect_3.getDataConnectP4SA)(projectNumber));
@@ -195,7 +208,8 @@ async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, s
195
208
  `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${iamUser}"`,
196
209
  `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${fdcP4SAUser}"`,
197
210
  ...firebaseDefaultPermissions,
211
+ ...revokeOwnersFromFirebasesuperuser,
198
212
  ];
199
- await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, brownfieldSetupCmds, silent);
213
+ await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, brownfieldSetupCmds, silent, true);
200
214
  }
201
215
  exports.brownfieldSqlSetup = brownfieldSqlSetup;
@@ -59,7 +59,7 @@ async function doSetup(setup, config) {
59
59
  await sdk.doSetup(setup, config);
60
60
  }
61
61
  else {
62
- (0, utils_1.logBullet)(`If you'd like to add the generated SDK to your app your, run ${clc.bold("firebase init dataconnect:sdk")}`);
62
+ (0, utils_1.logBullet)(`If you'd like to add the generated SDK to your app later, run ${clc.bold("firebase init dataconnect:sdk")}`);
63
63
  }
64
64
  if (setup.projectId && !isBillingEnabled) {
65
65
  (0, utils_1.logBullet)((0, freeTrial_1.upgradeInstructions)(setup.projectId));
@@ -14,6 +14,7 @@ const dataconnectEmulator_1 = require("../../../emulator/dataconnectEmulator");
14
14
  const error_1 = require("../../../error");
15
15
  const lodash_1 = require("lodash");
16
16
  const utils_1 = require("../../../utils");
17
+ const auth_1 = require("../../../auth");
17
18
  exports.FDC_APP_FOLDER = "_FDC_APP_FOLDER";
18
19
  async function doSetup(setup, config) {
19
20
  const sdkInfo = await askQuestions(setup, config);
@@ -78,12 +79,13 @@ async function askQuestions(setup, config) {
78
79
  const newConnectorYaml = await generateSdkYaml(targetPlatform, connectorYaml, connectorInfo.directory, appDir);
79
80
  if (targetPlatform === types_1.Platform.WEB) {
80
81
  const unusedFrameworks = fileUtils_1.SUPPORTED_FRAMEWORKS.filter((framework) => { var _a; return !((_a = newConnectorYaml.generate) === null || _a === void 0 ? void 0 : _a.javascriptSdk[framework]); });
82
+ const hasFrameworkEnabled = unusedFrameworks.length < fileUtils_1.SUPPORTED_FRAMEWORKS.length;
81
83
  if (unusedFrameworks.length > 0) {
82
84
  const additionalFrameworks = await (0, prompt_1.prompt)(setup, [
83
85
  {
84
86
  type: "checkbox",
85
87
  name: "fdcFrameworks",
86
- message: "Which framework would you like to generate SDKs for? " +
88
+ message: `Which ${hasFrameworkEnabled && "additional "}frameworks would you like to generate SDKs for? ` +
87
89
  "Press Space to select features, then Enter to confirm your choices.",
88
90
  choices: unusedFrameworks.map((frameworkStr) => ({
89
91
  value: frameworkStr,
@@ -125,6 +127,7 @@ async function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appD
125
127
  if (packageJson) {
126
128
  const frameworksUsed = (0, fileUtils_1.getFrameworksFromPackageJson)(packageJson);
127
129
  for (const framework of frameworksUsed) {
130
+ (0, utils_1.logBullet)(`Detected ${framework} app. Enabling ${framework} generated SDKs.`);
128
131
  javascriptSdk[framework] = true;
129
132
  }
130
133
  }
@@ -155,17 +158,34 @@ async function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appD
155
158
  }
156
159
  exports.generateSdkYaml = generateSdkYaml;
157
160
  async function actuate(sdkInfo) {
158
- var _a;
161
+ var _a, _b;
159
162
  const connectorYamlPath = `${sdkInfo.connectorInfo.directory}/connector.yaml`;
160
163
  fs.writeFileSync(connectorYamlPath, sdkInfo.connectorYamlContents, "utf8");
161
164
  (0, utils_1.logBullet)(`Wrote new config to ${connectorYamlPath}`);
165
+ const account = (0, auth_1.getGlobalDefaultAccount)();
162
166
  await dataconnectEmulator_1.DataConnectEmulator.generate({
163
167
  configDir: sdkInfo.connectorInfo.directory,
164
168
  connectorId: sdkInfo.connectorInfo.connectorYaml.connectorId,
169
+ account,
165
170
  });
166
171
  (0, utils_1.logBullet)(`Generated SDK code for ${sdkInfo.connectorInfo.connectorYaml.connectorId}`);
167
172
  if (((_a = sdkInfo.connectorInfo.connectorYaml.generate) === null || _a === void 0 ? void 0 : _a.swiftSdk) && sdkInfo.displayIOSWarning) {
168
- (0, utils_1.logBullet)(clc.bold("Please follow the instructions here to add your generated sdk to your XCode project:\n\thttps://firebase.google.com/docs/data-connect/gp/ios-sdk#set-client"));
173
+ (0, utils_1.logBullet)(clc.bold("Please follow the instructions here to add your generated sdk to your XCode project:\n\thttps://firebase.google.com/docs/data-connect/ios-sdk#set-client"));
174
+ }
175
+ if ((_b = sdkInfo.connectorInfo.connectorYaml.generate) === null || _b === void 0 ? void 0 : _b.javascriptSdk) {
176
+ for (const framework of fileUtils_1.SUPPORTED_FRAMEWORKS) {
177
+ if (sdkInfo.connectorInfo.connectorYaml.generate.javascriptSdk[framework]) {
178
+ logInfoForFramework(framework);
179
+ }
180
+ }
169
181
  }
170
182
  }
171
183
  exports.actuate = actuate;
184
+ function logInfoForFramework(framework) {
185
+ if (framework === "react") {
186
+ (0, utils_1.logBullet)("Visit https://firebase.google.com/docs/data-connect/web-sdk#react for more information on how to set up React Generated SDKs for Firebase Data Connect");
187
+ }
188
+ else if (framework === "angular") {
189
+ (0, utils_1.logBullet)("Run `npm i --save @angular/fire @tanstack-query-firebase/angular @tanstack/angular-query-experimental` to install angular sdk dependencies.\nVisit https://github.com/invertase/tanstack-query-firebase/tree/main/packages/angular for more information on how to set up Angular Generated SDKs for Firebase Data Connect");
190
+ }
191
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.34.0",
3
+ "version": "13.35.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@ connectorId: __connectorId__
8
8
  # packageJsonDir: < Optional. Path to your Javascript app's package.json>
9
9
  # swiftSdk:
10
10
  # outputDir: <Path where you want the generated SDK to be written to, relative to this file>
11
- # package: "firebasegen/default"
11
+ # package: DefaultConnector
12
12
  # kotlinSdk:
13
13
  # outputDir: <Path where you want the generated SDK to be written to, relative to this file>
14
14
  # package: connectors.default