firebase-tools 13.7.5 → 13.8.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 (88) hide show
  1. package/lib/api.js +9 -1
  2. package/lib/apiv2.js +19 -13
  3. package/lib/apphosting/app.js +4 -3
  4. package/lib/apphosting/githubConnections.js +1 -1
  5. package/lib/apphosting/index.js +42 -18
  6. package/lib/checkValidTargetFilters.js +8 -1
  7. package/lib/commands/apphosting-backends-create.js +3 -3
  8. package/lib/commands/apphosting-backends-delete.js +24 -17
  9. package/lib/commands/apphosting-backends-list.js +3 -3
  10. package/lib/commands/apphosting-secrets-grantaccess.js +9 -5
  11. package/lib/commands/dataconnect-list.js +64 -0
  12. package/lib/commands/dataconnect-sdk-generate.js +36 -0
  13. package/lib/commands/dataconnect-sql-diff.js +25 -0
  14. package/lib/commands/dataconnect-sql-migrate.js +41 -0
  15. package/lib/commands/deploy.js +27 -1
  16. package/lib/commands/index.js +10 -0
  17. package/lib/commands/init.js +7 -0
  18. package/lib/commands/setup-emulators-dataconnect.js +12 -0
  19. package/lib/config.js +1 -0
  20. package/lib/dataconnect/build.js +23 -0
  21. package/lib/dataconnect/checkIam.js +30 -0
  22. package/lib/dataconnect/client.js +115 -0
  23. package/lib/dataconnect/dataplaneClient.js +16 -0
  24. package/lib/dataconnect/ensureApis.js +12 -0
  25. package/lib/dataconnect/fileUtils.js +89 -0
  26. package/lib/dataconnect/filters.js +45 -0
  27. package/lib/dataconnect/freeTrial.js +23 -0
  28. package/lib/dataconnect/graphqlError.js +13 -0
  29. package/lib/dataconnect/load.js +40 -0
  30. package/lib/dataconnect/names.js +48 -0
  31. package/lib/dataconnect/prompts.js +20 -0
  32. package/lib/dataconnect/provisionCloudSql.js +91 -0
  33. package/lib/dataconnect/schemaMigration.js +137 -0
  34. package/lib/dataconnect/types.js +23 -0
  35. package/lib/deploy/dataconnect/deploy.js +84 -0
  36. package/lib/deploy/dataconnect/index.js +9 -0
  37. package/lib/deploy/dataconnect/prepare.js +30 -0
  38. package/lib/deploy/dataconnect/release.js +67 -0
  39. package/lib/deploy/functions/checkIam.js +4 -34
  40. package/lib/deploy/index.js +2 -0
  41. package/lib/downloadUtils.js +2 -2
  42. package/lib/emulator/constants.js +3 -0
  43. package/lib/emulator/controller.js +38 -12
  44. package/lib/emulator/dataconnectEmulator.js +86 -0
  45. package/lib/emulator/download.js +1 -1
  46. package/lib/emulator/downloadableEmulators.js +42 -3
  47. package/lib/emulator/portUtils.js +3 -2
  48. package/lib/emulator/registry.js +5 -0
  49. package/lib/emulator/types.js +3 -0
  50. package/lib/experiments.js +5 -0
  51. package/lib/extensions/emulator/specHelper.js +5 -39
  52. package/lib/gcp/apphosting.js +6 -1
  53. package/lib/gcp/cloudsql/cloudsqladmin.js +155 -0
  54. package/lib/gcp/cloudsql/connect.js +127 -0
  55. package/lib/gcp/cloudsql/fbToolsAuthClient.js +42 -0
  56. package/lib/gcp/cloudsql/types.js +2 -0
  57. package/lib/gcp/iam.js +33 -1
  58. package/lib/gcp/secretManager.js +1 -1
  59. package/lib/init/features/dataconnect/index.js +124 -0
  60. package/lib/init/features/emulators.js +13 -0
  61. package/lib/init/features/functions/index.js +15 -3
  62. package/lib/init/features/index.js +3 -1
  63. package/lib/init/index.js +1 -0
  64. package/lib/logger.js +22 -2
  65. package/lib/operation-poller.js +7 -1
  66. package/lib/rc.js +10 -1
  67. package/lib/requireAuth.js +1 -0
  68. package/lib/utils.js +51 -4
  69. package/package.json +6 -2
  70. package/schema/connector-yaml.json +54 -0
  71. package/schema/dataconnect-yaml.json +72 -0
  72. package/schema/firebase-config.json +103 -0
  73. package/templates/extensions/javascript/package.lint.json +2 -2
  74. package/templates/extensions/javascript/package.nolint.json +2 -2
  75. package/templates/extensions/typescript/package.lint.json +2 -2
  76. package/templates/extensions/typescript/package.nolint.json +2 -2
  77. package/templates/init/dataconnect/connector.yaml +2 -0
  78. package/templates/init/dataconnect/dataconnect.yaml +10 -0
  79. package/templates/init/dataconnect/mutations.gql +5 -0
  80. package/templates/init/dataconnect/queries.gql +7 -0
  81. package/templates/init/dataconnect/schema.gql +16 -0
  82. package/templates/init/functions/javascript/_gitignore +2 -1
  83. package/templates/init/functions/javascript/package.lint.json +2 -2
  84. package/templates/init/functions/javascript/package.nolint.json +2 -2
  85. package/templates/init/functions/python/_gitignore +1 -0
  86. package/templates/init/functions/typescript/_gitignore +1 -0
  87. package/templates/init/functions/typescript/package.lint.json +2 -2
  88. package/templates/init/functions/typescript/package.nolint.json +2 -2
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setupIAMUser = exports.execute = void 0;
4
+ const pg = require("pg");
5
+ const cloud_sql_connector_1 = require("@google-cloud/cloud-sql-connector");
6
+ const requireAuth_1 = require("../../requireAuth");
7
+ const projectUtils_1 = require("../../projectUtils");
8
+ const cloudSqlAdminClient = require("./cloudsqladmin");
9
+ const utils = require("../../utils");
10
+ const logger_1 = require("../../logger");
11
+ const error_1 = require("../../error");
12
+ const fbToolsAuthClient_1 = require("./fbToolsAuthClient");
13
+ async function execute(sqlStatements, opts) {
14
+ const logFn = opts.silent ? logger_1.logger.debug : logger_1.logger.info;
15
+ const instance = await cloudSqlAdminClient.getInstance(opts.projectId, opts.instanceId);
16
+ const user = await cloudSqlAdminClient.getUser(opts.projectId, opts.instanceId, opts.username);
17
+ const connectionName = instance.connectionName;
18
+ if (!connectionName) {
19
+ throw new error_1.FirebaseError(`Could not get instance conection string for ${opts.instanceId}:${opts.databaseId}`);
20
+ }
21
+ let connector;
22
+ let pool;
23
+ switch (user.type) {
24
+ case "CLOUD_IAM_USER": {
25
+ connector = new cloud_sql_connector_1.Connector({
26
+ auth: new fbToolsAuthClient_1.FBToolsAuthClient(),
27
+ });
28
+ const clientOpts = await connector.getOptions({
29
+ instanceConnectionName: connectionName,
30
+ ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
31
+ authType: cloud_sql_connector_1.AuthTypes.IAM,
32
+ });
33
+ pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId, max: 1 }));
34
+ break;
35
+ }
36
+ case "CLOUD_IAM_SERVICE_ACCOUNT": {
37
+ connector = new cloud_sql_connector_1.Connector();
38
+ const clientOpts = await connector.getOptions({
39
+ instanceConnectionName: connectionName,
40
+ ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
41
+ authType: cloud_sql_connector_1.AuthTypes.IAM,
42
+ });
43
+ pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId, max: 1 }));
44
+ break;
45
+ }
46
+ default: {
47
+ if (!opts.password) {
48
+ throw new error_1.FirebaseError(`Cannot connect as BUILT_IN user without a password.`);
49
+ }
50
+ connector = new cloud_sql_connector_1.Connector({
51
+ auth: new fbToolsAuthClient_1.FBToolsAuthClient(),
52
+ });
53
+ const clientOpts = await connector.getOptions({
54
+ instanceConnectionName: connectionName,
55
+ ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
56
+ });
57
+ pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, password: opts.password, database: opts.databaseId, max: 1 }));
58
+ break;
59
+ }
60
+ }
61
+ for (const s of sqlStatements) {
62
+ logFn(`Executing: '${s}' as ${opts.username}`);
63
+ try {
64
+ await pool.query(s);
65
+ }
66
+ catch (err) {
67
+ throw new error_1.FirebaseError(`Error executing ${err}`);
68
+ }
69
+ }
70
+ await pool.end();
71
+ connector.close();
72
+ }
73
+ exports.execute = execute;
74
+ async function setupIAMUser(instanceId, databaseId, options) {
75
+ const projectId = (0, projectUtils_1.needProjectId)(options);
76
+ const account = await (0, requireAuth_1.requireAuth)(options);
77
+ if (!account) {
78
+ throw new error_1.FirebaseError("No account to set up! Run `firebase login` or set Application Default Credentials");
79
+ }
80
+ const setupUser = "firebasesuperuser";
81
+ const temporaryPassword = utils.generateId(20);
82
+ await cloudSqlAdminClient.createUser(projectId, instanceId, "BUILT_IN", setupUser, temporaryPassword);
83
+ const { user, mode } = toDatabaseUser(account);
84
+ await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
85
+ const grants = [
86
+ `do
87
+ $$
88
+ begin
89
+ if not exists (select FROM pg_catalog.pg_roles
90
+ WHERE rolname = '${firebaseowner(databaseId)}') then
91
+ CREATE ROLE "${firebaseowner(databaseId)}" WITH ADMIN "${setupUser}";
92
+ end if;
93
+ end
94
+ $$
95
+ ;`,
96
+ `GRANT ALL PRIVILEGES ON DATABASE "${databaseId}" TO "${firebaseowner(databaseId)}"`,
97
+ `GRANT cloudsqlsuperuser TO "${firebaseowner(databaseId)}"`,
98
+ `GRANT "${firebaseowner(databaseId)}" TO "${setupUser}"`,
99
+ `GRANT "${firebaseowner(databaseId)}" TO "${user}"`,
100
+ `ALTER SCHEMA public OWNER TO "${firebaseowner(databaseId)}"`,
101
+ `GRANT USAGE ON SCHEMA "public" TO PUBLIC`,
102
+ `GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "public" TO PUBLIC`,
103
+ `GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "public" TO PUBLIC`,
104
+ ];
105
+ await execute(grants, {
106
+ projectId,
107
+ instanceId,
108
+ databaseId,
109
+ username: setupUser,
110
+ password: temporaryPassword,
111
+ silent: true,
112
+ });
113
+ return user;
114
+ }
115
+ exports.setupIAMUser = setupIAMUser;
116
+ function firebaseowner(databaseId) {
117
+ return `firebaseowner_${databaseId}_public`;
118
+ }
119
+ function toDatabaseUser(account) {
120
+ let mode = "CLOUD_IAM_USER";
121
+ let user = account;
122
+ if (account.endsWith(".gserviceaccount.com")) {
123
+ user = account.replace(".gserviceaccount.com", "");
124
+ mode = "CLOUD_IAM_SERVICE_ACCOUNT";
125
+ }
126
+ return { user, mode };
127
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FBToolsAuthClient = void 0;
4
+ const google_auth_library_1 = require("google-auth-library");
5
+ const apiv2 = require("../../apiv2");
6
+ const error_1 = require("../../error");
7
+ class FBToolsAuthClient extends google_auth_library_1.AuthClient {
8
+ async request(opts) {
9
+ var _a;
10
+ if (!opts.url) {
11
+ throw new error_1.FirebaseError("opts.url was undefined");
12
+ }
13
+ const url = new URL(opts.url);
14
+ const client = new apiv2.Client({
15
+ urlPrefix: url.origin,
16
+ auth: true,
17
+ });
18
+ const res = await client.request({
19
+ method: (_a = opts.method) !== null && _a !== void 0 ? _a : "POST",
20
+ path: url.pathname,
21
+ queryParams: opts.params,
22
+ body: opts.data,
23
+ responseType: opts.responseType,
24
+ });
25
+ return {
26
+ config: opts,
27
+ status: res.status,
28
+ statusText: res.response.statusText,
29
+ data: res.body,
30
+ headers: res.response.headers,
31
+ request: {},
32
+ };
33
+ }
34
+ async getAccessToken() {
35
+ return { token: await apiv2.getAccessToken() };
36
+ }
37
+ async getRequestHeaders() {
38
+ const token = await this.getAccessToken();
39
+ return Object.assign(Object.assign({}, apiv2.STANDARD_HEADERS), { Authorization: `Bearer ${token.token}` });
40
+ }
41
+ }
42
+ exports.FBToolsAuthClient = FBToolsAuthClient;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/lib/gcp/iam.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.testIamPermissions = exports.testResourceIamPermissions = exports.getRole = exports.listServiceAccountKeys = exports.deleteServiceAccount = exports.createServiceAccountKey = exports.getServiceAccount = exports.createServiceAccount = exports.getDefaultComputeEngineServiceAgent = exports.getDefaultCloudBuildServiceAgent = void 0;
3
+ exports.printManualIamConfig = exports.mergeBindings = exports.testIamPermissions = exports.testResourceIamPermissions = exports.getRole = exports.listServiceAccountKeys = exports.deleteServiceAccount = exports.createServiceAccountKey = exports.getServiceAccount = exports.createServiceAccount = exports.getDefaultComputeEngineServiceAgent = exports.getDefaultCloudBuildServiceAgent = void 0;
4
4
  const api_1 = require("../api");
5
5
  const logger_1 = require("../logger");
6
6
  const apiv2_1 = require("../apiv2");
7
+ const utils = require("../utils");
7
8
  const apiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.iamOrigin)(), apiVersion: "v1" });
8
9
  function getDefaultCloudBuildServiceAgent(projectNumber) {
9
10
  return `${projectNumber}@cloudbuild.gserviceaccount.com`;
@@ -82,3 +83,34 @@ async function testIamPermissions(projectId, permissions) {
82
83
  return testResourceIamPermissions((0, api_1.resourceManagerOrigin)(), "v1", `projects/${projectId}`, permissions, `projects/${projectId}`);
83
84
  }
84
85
  exports.testIamPermissions = testIamPermissions;
86
+ function mergeBindings(policy, requiredBindings) {
87
+ let updated = false;
88
+ for (const requiredBinding of requiredBindings) {
89
+ const match = policy.bindings.find((b) => b.role === requiredBinding.role);
90
+ if (!match) {
91
+ updated = true;
92
+ policy.bindings.push(requiredBinding);
93
+ continue;
94
+ }
95
+ for (const requiredMember of requiredBinding.members) {
96
+ if (!match.members.find((m) => m === requiredMember)) {
97
+ updated = true;
98
+ match.members.push(requiredMember);
99
+ }
100
+ }
101
+ }
102
+ return updated;
103
+ }
104
+ exports.mergeBindings = mergeBindings;
105
+ function printManualIamConfig(requiredBindings, projectId, prefix) {
106
+ utils.logLabeledBullet(prefix, "Failed to verify the project has the correct IAM bindings for a successful deployment.", "warn");
107
+ utils.logLabeledBullet(prefix, "You can either re-run this command as a project owner or manually run the following set of `gcloud` commands:", "warn");
108
+ for (const binding of requiredBindings) {
109
+ for (const member of binding.members) {
110
+ utils.logLabeledBullet(prefix, `\`gcloud projects add-iam-policy-binding ${projectId} ` +
111
+ `--member=${member} ` +
112
+ `--role=${binding.role}\``, "warn");
113
+ }
114
+ }
115
+ }
116
+ exports.printManualIamConfig = printManualIamConfig;
@@ -51,7 +51,7 @@ async function getSecretMetadata(projectId, secretName, version) {
51
51
  const secretInfo = {};
52
52
  try {
53
53
  secretInfo.secret = await getSecret(projectId, secretName);
54
- secretInfo.secretVersion = getSecretVersion(projectId, secretName, version);
54
+ secretInfo.secretVersion = await getSecretVersion(projectId, secretName, version);
55
55
  }
56
56
  catch (err) {
57
57
  if (err.status !== 404) {
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.doSetup = void 0;
4
+ const path_1 = require("path");
5
+ const prompt_1 = require("../../../prompt");
6
+ const fs_1 = require("fs");
7
+ const provisionCloudSql_1 = require("../../../dataconnect/provisionCloudSql");
8
+ const ensureApis_1 = require("../../../dataconnect/ensureApis");
9
+ const client_1 = require("../../../dataconnect/client");
10
+ const TEMPLATE_ROOT = (0, path_1.resolve)(__dirname, "../../../../templates/init/dataconnect/");
11
+ const DATACONNECT_YAML_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "dataconnect.yaml"), "utf8");
12
+ const CONNECTOR_YAML_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "connector.yaml"), "utf8");
13
+ const SCHEMA_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "schema.gql"), "utf8");
14
+ const QUERIES_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "queries.gql"), "utf8");
15
+ const MUTATIONS_TEMPLATE = (0, fs_1.readFileSync)((0, path_1.join)(TEMPLATE_ROOT, "mutations.gql"), "utf8");
16
+ async function doSetup(setup, config) {
17
+ var _a, _b, _c;
18
+ if (setup.projectId) {
19
+ await (0, ensureApis_1.ensureApis)(setup.projectId);
20
+ }
21
+ const serviceId = await (0, prompt_1.promptOnce)({
22
+ message: "What ID would you like to use for this service?",
23
+ type: "input",
24
+ default: "dataconnect",
25
+ });
26
+ let locationOptions = [
27
+ { name: "us-central1", value: "us-central1" },
28
+ { name: "europe-north1", value: "europe-north1" },
29
+ { name: "europe-central2", value: "europe-central2" },
30
+ { name: "europe-west1", value: "europe-west1" },
31
+ { name: "southamerica-west1", value: "southamerica-west1" },
32
+ { name: "us-east4", value: "us-east4" },
33
+ { name: "us-west1", value: "us-west1" },
34
+ { name: "asia-southeast1", value: "asia-southeast1" },
35
+ ];
36
+ if (setup.projectId) {
37
+ const locations = await (0, client_1.listLocations)(setup.projectId);
38
+ locationOptions = locations.map((l) => {
39
+ return { name: l, value: l };
40
+ });
41
+ }
42
+ const locationId = await (0, prompt_1.promptOnce)({
43
+ message: "What location would you like to deploy this service into?",
44
+ type: "list",
45
+ choices: locationOptions,
46
+ });
47
+ const connectorId = await (0, prompt_1.promptOnce)({
48
+ message: "What ID would you like to use for your connector?",
49
+ type: "input",
50
+ default: "my-connector",
51
+ });
52
+ const dir = config.get("dataconnect.source") ||
53
+ (await (0, prompt_1.promptOnce)({
54
+ message: "What directory should be used for DataConnect config and schema?",
55
+ type: "input",
56
+ default: "dataconnect",
57
+ }));
58
+ if (!config.has("dataconnect")) {
59
+ config.set("dataconnect.source", dir);
60
+ config.set("dataconnect.location", locationId);
61
+ }
62
+ const cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
63
+ message: `What CloudSQL instance would you like to use? Please enter the ID of an existing instance in ${locationId}`,
64
+ type: "input",
65
+ default: `dataconnect-test`,
66
+ });
67
+ const cloudSqlDatabase = await (0, prompt_1.promptOnce)({
68
+ message: `Which database would you like to use from ${cloudSqlInstanceId}?`,
69
+ type: "input",
70
+ default: `dataconnect-test`,
71
+ });
72
+ const defaultConnectionString = (_c = (_b = (_a = setup.rcfile.dataconnectEmulatorConfig) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.localConnectionString) !== null && _c !== void 0 ? _c : "postgresql://localhost:5432?sslmode=disable";
73
+ const localConnectionString = await (0, prompt_1.promptOnce)({
74
+ type: "input",
75
+ name: "localConnectionString",
76
+ message: `What is the connection string of the local Postgres instance you would like to use with the Data Connect emulator?`,
77
+ default: defaultConnectionString,
78
+ });
79
+ setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
80
+ const subbedDataconnectYaml = subValues(DATACONNECT_YAML_TEMPLATE, {
81
+ serviceId,
82
+ cloudSqlInstanceId,
83
+ cloudSqlDatabase,
84
+ connectorId,
85
+ });
86
+ const subbedConnectorYaml = subValues(CONNECTOR_YAML_TEMPLATE, {
87
+ serviceId,
88
+ cloudSqlInstanceId,
89
+ cloudSqlDatabase,
90
+ connectorId,
91
+ });
92
+ await config.askWriteProjectFile((0, path_1.join)(dir, "dataconnect.yaml"), subbedDataconnectYaml);
93
+ await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "connector.yaml"), subbedConnectorYaml);
94
+ await config.askWriteProjectFile((0, path_1.join)(dir, "schema", "schema.gql"), SCHEMA_TEMPLATE);
95
+ await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "queries.gql"), QUERIES_TEMPLATE);
96
+ await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "mutations.gql"), MUTATIONS_TEMPLATE);
97
+ if (setup.projectId &&
98
+ (await (0, prompt_1.confirm)({
99
+ message: "Would you like to provision your CloudSQL instance and database now? This will take a few minutes.",
100
+ default: true,
101
+ }))) {
102
+ await (0, provisionCloudSql_1.provisionCloudSql)({
103
+ projectId: setup.projectId,
104
+ locationId,
105
+ instanceId: cloudSqlInstanceId,
106
+ databaseId: cloudSqlDatabase,
107
+ enableGoogleMlIntegration: false,
108
+ });
109
+ }
110
+ }
111
+ exports.doSetup = doSetup;
112
+ function subValues(template, replacementValues) {
113
+ const replacements = {
114
+ serviceId: "__serviceId__",
115
+ cloudSqlDatabase: "__cloudSqlDatabase__",
116
+ cloudSqlInstanceId: "__cloudSqlInstanceId__",
117
+ connectorId: "__connectorId__",
118
+ };
119
+ let replaced = template;
120
+ for (const [k, v] of Object.entries(replacementValues)) {
121
+ replaced = replaced.replace(replacements[k], v);
122
+ }
123
+ return replaced;
124
+ }
@@ -9,6 +9,7 @@ const types_1 = require("../../emulator/types");
9
9
  const constants_1 = require("../../emulator/constants");
10
10
  const downloadableEmulators_1 = require("../../emulator/downloadableEmulators");
11
11
  async function doSetup(setup, config) {
12
+ var _a, _b, _c;
12
13
  const choices = types_1.ALL_SERVICE_EMULATORS.map((e) => {
13
14
  return {
14
15
  value: e,
@@ -76,6 +77,18 @@ async function doSetup(setup, config) {
76
77
  ui.port = isNaN(portNum) ? undefined : portNum;
77
78
  }
78
79
  }
80
+ if (selections.emulators.includes(types_1.Emulators.DATACONNECT)) {
81
+ const defaultConnectionString = (_c = (_b = (_a = setup.rcfile.dataconnectEmulatorConfig) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.localConnectionString) !== null && _c !== void 0 ? _c : "postgresql://localhost:5432";
82
+ const localConnectionString = await (0, prompt_1.prompt)(setup.config.emulators[types_1.Emulators.DATACONNECT], [
83
+ {
84
+ type: "input",
85
+ name: "localConnectionString",
86
+ message: `What is the connection string of the local Postgres instance you would like to use with the Data Connect emulator?`,
87
+ default: defaultConnectionString,
88
+ },
89
+ ]);
90
+ setup.rcfile.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
91
+ }
79
92
  await (0, prompt_1.prompt)(selections, [
80
93
  {
81
94
  name: "download",
@@ -153,13 +153,25 @@ async function languageSetup(setup, config) {
153
153
  const cbconfig = (0, projectConfig_1.configForCodebase)(setup.config.functions, setup.functions.codebase);
154
154
  switch (language) {
155
155
  case "javascript":
156
- cbconfig.ignore = ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"];
156
+ cbconfig.ignore = [
157
+ "node_modules",
158
+ ".git",
159
+ "firebase-debug.log",
160
+ "firebase-debug.*.log",
161
+ "*.local",
162
+ ];
157
163
  break;
158
164
  case "typescript":
159
- cbconfig.ignore = ["node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log"];
165
+ cbconfig.ignore = [
166
+ "node_modules",
167
+ ".git",
168
+ "firebase-debug.log",
169
+ "firebase-debug.*.log",
170
+ "*.local",
171
+ ];
160
172
  break;
161
173
  case "python":
162
- cbconfig.ignore = ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log"];
174
+ cbconfig.ignore = ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log", "*.local"];
163
175
  break;
164
176
  }
165
177
  return require("./" + language).setup(setup, config);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apphosting = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storage = exports.hosting = exports.functions = exports.firestore = exports.database = exports.account = void 0;
3
+ exports.apphosting = exports.dataconnect = exports.hostingGithub = exports.remoteconfig = exports.project = exports.extensions = exports.emulators = exports.storage = exports.hosting = exports.functions = exports.firestore = exports.database = exports.account = void 0;
4
4
  var account_1 = require("./account");
5
5
  Object.defineProperty(exports, "account", { enumerable: true, get: function () { return account_1.doSetup; } });
6
6
  var database_1 = require("./database");
@@ -23,5 +23,7 @@ var remoteconfig_1 = require("./remoteconfig");
23
23
  Object.defineProperty(exports, "remoteconfig", { enumerable: true, get: function () { return remoteconfig_1.doSetup; } });
24
24
  var github_1 = require("./hosting/github");
25
25
  Object.defineProperty(exports, "hostingGithub", { enumerable: true, get: function () { return github_1.initGitHub; } });
26
+ var dataconnect_1 = require("./dataconnect");
27
+ Object.defineProperty(exports, "dataconnect", { enumerable: true, get: function () { return dataconnect_1.doSetup; } });
26
28
  var apphosting_1 = require("../../apphosting");
27
29
  Object.defineProperty(exports, "apphosting", { enumerable: true, get: function () { return apphosting_1.doSetup; } });
package/lib/init/index.js CHANGED
@@ -10,6 +10,7 @@ const featureFns = new Map([
10
10
  ["account", features.account],
11
11
  ["database", features.database],
12
12
  ["firestore", features.firestore],
13
+ ["dataconnect", features.dataconnect],
13
14
  ["functions", features.functions],
14
15
  ["hosting", features.hosting],
15
16
  ["storage", features.storage],
package/lib/logger.js CHANGED
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.logger = void 0;
3
+ exports.logger = exports.vsceLogEmitter = void 0;
4
4
  const winston = require("winston");
5
+ const utils_1 = require("./utils");
6
+ const events_1 = require("events");
7
+ exports.vsceLogEmitter = new events_1.EventEmitter();
5
8
  function expandErrors(logger) {
6
9
  const oldLogFunc = logger.log.bind(logger);
7
10
  const newLogFunc = function (levelOrEntry, message, ...meta) {
@@ -25,7 +28,24 @@ function annotateDebugLines(logger) {
25
28
  logger.debug = newDebug;
26
29
  return logger;
27
30
  }
31
+ function maybeUseVSCodeLogger(logger) {
32
+ if (!(0, utils_1.isVSCodeExtension)()) {
33
+ return logger;
34
+ }
35
+ const oldLogFunc = logger.log.bind(logger);
36
+ const vsceLogger = function (levelOrEntry, message, ...meta) {
37
+ if (message) {
38
+ exports.vsceLogEmitter.emit("log", { level: levelOrEntry, message });
39
+ }
40
+ else {
41
+ exports.vsceLogEmitter.emit("log", levelOrEntry);
42
+ }
43
+ return oldLogFunc(levelOrEntry, message, ...meta);
44
+ };
45
+ logger.log = vsceLogger;
46
+ return logger;
47
+ }
28
48
  const rawLogger = winston.createLogger();
29
49
  rawLogger.add(new winston.transports.Console({ silent: true }));
30
50
  rawLogger.exitOnError = false;
31
- exports.logger = annotateDebugLines(expandErrors(rawLogger));
51
+ exports.logger = maybeUseVSCodeLogger(annotateDebugLines(expandErrors(rawLogger)));
@@ -45,7 +45,13 @@ class OperationPoller {
45
45
  if (options.onPoll) {
46
46
  options.onPoll(res.body);
47
47
  }
48
- if (!res.body.done) {
48
+ if (options.doneFn) {
49
+ const done = options.doneFn(res.body);
50
+ if (!done) {
51
+ throw new Error("Polling incomplete, should trigger retry with backoff");
52
+ }
53
+ }
54
+ else if (!res.body.done) {
49
55
  throw new Error("Polling incomplete, should trigger retry with backoff");
50
56
  }
51
57
  return res.body;
package/lib/rc.js CHANGED
@@ -37,7 +37,7 @@ class RC {
37
37
  }
38
38
  constructor(rcpath, data) {
39
39
  this.path = rcpath;
40
- this.data = Object.assign({ projects: {}, targets: {}, etags: {} }, data);
40
+ this.data = Object.assign({ projects: {}, targets: {}, etags: {}, dataconnectEmulatorConfig: {} }, data);
41
41
  }
42
42
  set(key, value) {
43
43
  _.set(this.data, key, value);
@@ -152,6 +152,15 @@ class RC {
152
152
  this.data.etags[projectId][resourceType] = etagData;
153
153
  this.save();
154
154
  }
155
+ getDataconnect() {
156
+ var _a;
157
+ return (_a = this.data.dataconnectEmulatorConfig) !== null && _a !== void 0 ? _a : {};
158
+ }
159
+ setDataconnect(localConnectionString) {
160
+ if (!this.data.dataconnectEmulatorConfig) {
161
+ this.data.dataconnectEmulatorConfig = { postgres: { localConnectionString } };
162
+ }
163
+ }
155
164
  save() {
156
165
  if (this.path) {
157
166
  fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2), {
@@ -77,5 +77,6 @@ async function requireAuth(options) {
77
77
  throw new error_1.FirebaseError(AUTH_ERROR_MESSAGE);
78
78
  }
79
79
  (0, auth_1.setActiveAccount)(options, { user, tokens });
80
+ return user.email;
80
81
  }
81
82
  exports.requireAuth = requireAuth;
package/lib/utils.js CHANGED
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getHostnameFromUrl = exports.openInBrowserPopup = exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isVSCodeExtension = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = exports.IS_WINDOWS = void 0;
4
- exports.readSecretValue = void 0;
5
- const fs = require("node:fs");
3
+ exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isVSCodeExtension = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
4
+ exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = void 0;
5
+ const fs = require("fs-extra");
6
6
  const tty = require("tty");
7
7
  const path = require("node:path");
8
+ const yaml = require("yaml");
8
9
  const _ = require("lodash");
9
10
  const url = require("url");
10
11
  const http = require("http");
@@ -28,6 +29,7 @@ const WARNING_CHAR = exports.IS_WINDOWS ? "!" : "⚠";
28
29
  const ERROR_CHAR = exports.IS_WINDOWS ? "!!" : "⬢";
29
30
  const THIRTY_DAYS_IN_MILLISECONDS = 30 * 24 * 60 * 60 * 1000;
30
31
  exports.envOverrides = [];
32
+ exports.vscodeEnvVars = {};
31
33
  function consoleUrl(project, path) {
32
34
  const api = require("./api");
33
35
  return `${api.consoleOrigin()}/project/${project}${path}`;
@@ -43,8 +45,12 @@ function getInheritedOption(options, key) {
43
45
  }
44
46
  }
45
47
  exports.getInheritedOption = getInheritedOption;
48
+ function setVSCodeEnvVars(envVar, value) {
49
+ exports.vscodeEnvVars[envVar] = value;
50
+ }
51
+ exports.setVSCodeEnvVars = setVSCodeEnvVars;
46
52
  function envOverride(envname, value, coerce) {
47
- const currentEnvValue = process.env[envname];
53
+ const currentEnvValue = isVSCodeExtension() && exports.vscodeEnvVars[envname] ? exports.vscodeEnvVars[envname] : process.env[envname];
48
54
  if (currentEnvValue && currentEnvValue.length) {
49
55
  exports.envOverrides.push(envname);
50
56
  if (coerce) {
@@ -527,6 +533,47 @@ function getHostnameFromUrl(url) {
527
533
  }
528
534
  }
529
535
  exports.getHostnameFromUrl = getHostnameFromUrl;
536
+ function readFileFromDirectory(directory, file) {
537
+ return new Promise((resolve, reject) => {
538
+ fs.readFile(path.resolve(directory, file), "utf8", (err, data) => {
539
+ if (err) {
540
+ if (err.code === "ENOENT") {
541
+ return reject(new error_1.FirebaseError(`Could not find "${file}" in "${directory}"`, { original: err }));
542
+ }
543
+ reject(new error_1.FirebaseError(`Failed to read file "${file}" in "${directory}"`, { original: err }));
544
+ }
545
+ else {
546
+ resolve(data);
547
+ }
548
+ });
549
+ }).then((source) => {
550
+ return {
551
+ source,
552
+ sourceDirectory: directory,
553
+ };
554
+ });
555
+ }
556
+ exports.readFileFromDirectory = readFileFromDirectory;
557
+ function wrappedSafeLoad(source) {
558
+ try {
559
+ return yaml.parse(source);
560
+ }
561
+ catch (err) {
562
+ throw new error_1.FirebaseError(`YAML Error: ${err.message}`, { original: err });
563
+ }
564
+ }
565
+ exports.wrappedSafeLoad = wrappedSafeLoad;
566
+ function generateId(n = 6) {
567
+ const letters = "abcdefghijklmnopqrstuvwxyz";
568
+ const allChars = "01234567890-abcdefghijklmnopqrstuvwxyz";
569
+ let id = letters[Math.floor(Math.random() * letters.length)];
570
+ for (let i = 1; i < n; i++) {
571
+ const idx = Math.floor(Math.random() * allChars.length);
572
+ id += allChars[idx];
573
+ }
574
+ return id;
575
+ }
576
+ exports.generateId = generateId;
530
577
  function readSecretValue(prompt, dataFile) {
531
578
  if ((!dataFile || dataFile === "-") && tty.isatty(0)) {
532
579
  return (0, prompt_1.promptOnce)({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.7.5",
3
+ "version": "13.8.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -58,6 +58,7 @@
58
58
  ]
59
59
  },
60
60
  "dependencies": {
61
+ "@google-cloud/cloud-sql-connector": "^1.2.3",
61
62
  "@google-cloud/pubsub": "^3.0.1",
62
63
  "abort-controller": "^3.0.0",
63
64
  "ajv": "^6.12.6",
@@ -82,8 +83,9 @@
82
83
  "form-data": "^4.0.0",
83
84
  "fs-extra": "^10.1.0",
84
85
  "fuzzy": "^0.1.3",
86
+ "gaxios": "^6.1.1",
85
87
  "glob": "^7.1.2",
86
- "google-auth-library": "^7.11.0",
88
+ "google-auth-library": "^9.7.0",
87
89
  "inquirer": "^8.2.6",
88
90
  "inquirer-autocomplete-prompt": "^2.0.1",
89
91
  "jsonwebtoken": "^9.0.0",
@@ -99,12 +101,14 @@
99
101
  "open": "^6.3.0",
100
102
  "ora": "^5.4.1",
101
103
  "p-limit": "^3.0.1",
104
+ "pg": "^8.11.3",
102
105
  "portfinder": "^1.0.32",
103
106
  "progress": "^2.0.3",
104
107
  "proxy-agent": "^6.3.0",
105
108
  "retry": "^0.13.1",
106
109
  "rimraf": "^3.0.0",
107
110
  "semver": "^7.5.2",
111
+ "sql-formatter": "^15.3.0",
108
112
  "stream-chain": "^2.2.4",
109
113
  "stream-json": "^1.7.3",
110
114
  "strip-ansi": "^6.0.1",