firebase-tools 13.22.1 → 13.23.1

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 (34) hide show
  1. package/lib/apiv2.js +2 -2
  2. package/lib/apphosting/backend.js +294 -0
  3. package/lib/apphosting/githubConnections.js +54 -23
  4. package/lib/apphosting/index.js +3 -260
  5. package/lib/apphosting/rollout.js +12 -9
  6. package/lib/bin/firebase.js +2 -2
  7. package/lib/commands/apphosting-backends-create.js +4 -4
  8. package/lib/commands/apphosting-backends-delete.js +4 -14
  9. package/lib/commands/apphosting-repos-create.js +23 -0
  10. package/lib/commands/apphosting-rollouts-create.js +3 -4
  11. package/lib/commands/apphosting-secrets-grantaccess.js +2 -2
  12. package/lib/commands/dataconnect-sql-shell.js +111 -0
  13. package/lib/commands/index.js +3 -0
  14. package/lib/commands/init.js +6 -6
  15. package/lib/dataconnect/schemaMigration.js +2 -1
  16. package/lib/deploy/functions/services/auth.js +40 -15
  17. package/lib/deploy/functions/services/index.js +2 -0
  18. package/lib/emulator/commandUtils.js +2 -2
  19. package/lib/emulator/downloadableEmulators.js +9 -9
  20. package/lib/emulator/functionsEmulator.js +0 -2
  21. package/lib/emulator/loggingEmulator.js +2 -2
  22. package/lib/firestore/delete.js +11 -0
  23. package/lib/frameworks/next/index.js +9 -1
  24. package/lib/frameworks/next/utils.js +7 -4
  25. package/lib/frameworks/vite/index.js +2 -2
  26. package/lib/functions/constants.js +4 -0
  27. package/lib/functions/events/v1.js +9 -2
  28. package/lib/gcp/apphosting.js +1 -1
  29. package/lib/gcp/cloudsql/interactive.js +49 -0
  30. package/lib/gcp/firestore.js +5 -1
  31. package/lib/init/features/apphosting.js +3 -0
  32. package/lib/responseToError.js +5 -2
  33. package/lib/utils.js +2 -2
  34. package/package.json +1 -2
@@ -1,262 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getBackendForAmbiguousLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.setDefaultTrafficPolicy = exports.createBackend = exports.ensureAppHostingComputeServiceAccount = exports.doSetup = void 0;
4
- const clc = require("colorette");
5
- const poller = require("../operation-poller");
6
- const apphosting = require("../gcp/apphosting");
7
- const githubConnections = require("./githubConnections");
8
- const utils_1 = require("../utils");
9
- const api_1 = require("../api");
10
- const apphosting_1 = require("../gcp/apphosting");
11
- const resourceManager_1 = require("../gcp/resourceManager");
12
- const iam = require("../gcp/iam");
13
- const error_1 = require("../error");
14
- const prompt_1 = require("../prompt");
15
- const constants_1 = require("./constants");
16
- const ensureApiEnabled_1 = require("../ensureApiEnabled");
17
- const deploymentTool = require("../deploymentTool");
18
- const app_1 = require("./app");
19
- const ora = require("ora");
20
- const node_fetch_1 = require("node-fetch");
21
- const rollout_1 = require("./rollout");
22
- const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
23
- const apphostingPollerOptions = {
24
- apiOrigin: (0, api_1.apphostingOrigin)(),
25
- apiVersion: apphosting_1.API_VERSION,
26
- masterTimeout: 25 * 60 * 1000,
27
- maxBackoff: 10000,
28
- };
29
- async function tlsReady(url) {
30
- var _a;
31
- try {
32
- await (0, node_fetch_1.default)(url);
33
- return true;
34
- }
35
- catch (err) {
36
- const maybeNodeError = err;
37
- if (/HANDSHAKE_FAILURE/.test((_a = maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.cause) === null || _a === void 0 ? void 0 : _a.code) ||
38
- "EPROTO" === (maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.code)) {
39
- return false;
40
- }
41
- return true;
42
- }
43
- }
44
- async function awaitTlsReady(url) {
45
- let ready;
46
- do {
47
- ready = await tlsReady(url);
48
- if (!ready) {
49
- await (0, utils_1.sleep)(1000);
50
- }
51
- } while (!ready);
52
- }
53
- async function doSetup(projectId, webAppName, location, serviceAccount) {
54
- await Promise.all([
55
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.developerConnectOrigin)(), "apphosting", true),
56
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudbuildOrigin)(), "apphosting", true),
57
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.secretManagerOrigin)(), "apphosting", true),
58
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudRunApiOrigin)(), "apphosting", true),
59
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.artifactRegistryDomain)(), "apphosting", true),
60
- (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.iamOrigin)(), "apphosting", true),
61
- ]);
62
- await ensureAppHostingComputeServiceAccount(projectId, serviceAccount);
63
- const allowedLocations = (await apphosting.listLocations(projectId)).map((loc) => loc.locationId);
64
- if (location) {
65
- if (!allowedLocations.includes(location)) {
66
- throw new error_1.FirebaseError(`Invalid location ${location}. Valid choices are ${allowedLocations.join(", ")}`);
67
- }
68
- }
69
- location =
70
- location || (await promptLocation(projectId, "Select a location to host your backend:\n"));
71
- const gitRepositoryLink = await githubConnections.linkGitHubRepository(projectId, location);
72
- const rootDir = await (0, prompt_1.promptOnce)({
73
- name: "rootDir",
74
- type: "input",
75
- default: "/",
76
- message: "Specify your app's root directory relative to your repository",
77
- });
78
- const branch = await githubConnections.promptGitHubBranch(gitRepositoryLink);
79
- (0, utils_1.logSuccess)(`Repo linked successfully!\n`);
80
- (0, utils_1.logBullet)(`${clc.yellow("===")} Set up your backend`);
81
- const backendId = await promptNewBackendId(projectId, location, {
82
- name: "backendId",
83
- type: "input",
84
- default: "my-web-app",
85
- message: "Provide a name for your backend [1-30 characters]",
86
- });
87
- (0, utils_1.logSuccess)(`Name set to ${backendId}\n`);
88
- const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName, backendId);
89
- if (!webApp) {
90
- (0, utils_1.logWarning)(`Firebase web app not set`);
91
- }
92
- const createBackendSpinner = ora("Creating your new backend...").start();
93
- const backend = await createBackend(projectId, location, backendId, gitRepositoryLink, serviceAccount, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
94
- createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
95
- await setDefaultTrafficPolicy(projectId, location, backendId, branch);
96
- const confirmRollout = await (0, prompt_1.promptOnce)({
97
- type: "confirm",
98
- name: "rollout",
99
- default: true,
100
- message: "Do you want to deploy now?",
101
- });
102
- if (!confirmRollout) {
103
- (0, utils_1.logSuccess)(`Your backend will be deployed at:\n\thttps://${backend.uri}`);
104
- return;
105
- }
106
- const url = `https://${backend.uri}`;
107
- (0, utils_1.logBullet)(`You may also track this rollout at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
108
- const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
109
- await (0, rollout_1.orchestrateRollout)({
110
- projectId,
111
- location,
112
- backendId,
113
- buildInput: {
114
- source: {
115
- codebase: {
116
- branch,
117
- },
118
- },
119
- },
120
- isFirstRollout: true,
121
- });
122
- createRolloutSpinner.succeed("Rollout complete");
123
- if (!(await tlsReady(url))) {
124
- const tlsSpinner = ora("Finalizing your backend's TLS certificate; this may take a few minutes.").start();
125
- await awaitTlsReady(url);
126
- tlsSpinner.succeed("TLS certificate ready");
127
- }
128
- (0, utils_1.logSuccess)(`Your backend is now deployed at:\n\thttps://${backend.uri}`);
129
- }
130
- exports.doSetup = doSetup;
131
- async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount) {
132
- const sa = serviceAccount || defaultComputeServiceAccountEmail(projectId);
133
- const name = `projects/${projectId}/serviceAccounts/${sa}`;
134
- try {
135
- await iam.testResourceIamPermissions((0, api_1.iamOrigin)(), "v1", name, ["iam.serviceAccounts.actAs"], `projects/${projectId}`);
136
- }
137
- catch (err) {
138
- if (!(err instanceof error_1.FirebaseError)) {
139
- throw err;
140
- }
141
- if (err.status === 404) {
142
- await provisionDefaultComputeServiceAccount(projectId);
143
- }
144
- else if (err.status === 403) {
145
- throw new error_1.FirebaseError(`Failed to create backend due to missing delegation permissions for ${sa}. Make sure you have the iam.serviceAccounts.actAs permission.`, { original: err });
146
- }
147
- }
148
- }
149
- exports.ensureAppHostingComputeServiceAccount = ensureAppHostingComputeServiceAccount;
150
- async function promptNewBackendId(projectId, location, prompt) {
151
- while (true) {
152
- const backendId = await (0, prompt_1.promptOnce)(prompt);
153
- try {
154
- await apphosting.getBackend(projectId, location, backendId);
155
- }
156
- catch (err) {
157
- if (err.status === 404) {
158
- return backendId;
159
- }
160
- throw new error_1.FirebaseError(`Failed to check if backend with id ${backendId} already exists in ${location}`, { original: err });
161
- }
162
- (0, utils_1.logWarning)(`Backend with id ${backendId} already exists in ${location}`);
163
- }
164
- }
165
- function defaultComputeServiceAccountEmail(projectId) {
166
- return `${DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME}@${projectId}.iam.gserviceaccount.com`;
167
- }
168
- async function createBackend(projectId, location, backendId, repository, serviceAccount, webAppId, rootDir = "/") {
169
- const defaultServiceAccount = defaultComputeServiceAccountEmail(projectId);
170
- const backendReqBody = {
171
- servingLocality: "GLOBAL_ACCESS",
172
- codebase: {
173
- repository: `${repository.name}`,
174
- rootDirectory: rootDir,
175
- },
176
- labels: deploymentTool.labels(),
177
- serviceAccount: serviceAccount || defaultServiceAccount,
178
- appId: webAppId,
179
- };
180
- async function createBackendAndPoll() {
181
- const op = await apphosting.createBackend(projectId, location, backendReqBody, backendId);
182
- return await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
183
- }
184
- return await createBackendAndPoll();
185
- }
186
- exports.createBackend = createBackend;
187
- async function provisionDefaultComputeServiceAccount(projectId) {
188
- try {
189
- await iam.createServiceAccount(projectId, DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME, "Default service account used to run builds and deploys for Firebase App Hosting", "Firebase App Hosting compute service account");
190
- }
191
- catch (err) {
192
- if (err.status !== 409) {
193
- throw err;
194
- }
195
- }
196
- await (0, resourceManager_1.addServiceAccountToRoles)(projectId, defaultComputeServiceAccountEmail(projectId), [
197
- "roles/firebaseapphosting.computeRunner",
198
- "roles/firebase.sdkAdminServiceAgent",
199
- "roles/developerconnect.readTokenAccessor",
200
- ], true);
201
- }
202
- async function setDefaultTrafficPolicy(projectId, location, backendId, codebaseBranch) {
203
- const traffic = {
204
- rolloutPolicy: {
205
- codebaseBranch: codebaseBranch,
206
- stages: [
207
- {
208
- progression: "IMMEDIATE",
209
- targetPercent: 100,
210
- },
211
- ],
212
- },
213
- };
214
- const op = await apphosting.updateTraffic(projectId, location, backendId, traffic);
215
- await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `updateTraffic-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
216
- }
217
- exports.setDefaultTrafficPolicy = setDefaultTrafficPolicy;
218
- async function deleteBackendAndPoll(projectId, location, backendId) {
219
- const op = await apphosting.deleteBackend(projectId, location, backendId);
220
- await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `delete-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
221
- }
222
- exports.deleteBackendAndPoll = deleteBackendAndPoll;
223
- async function promptLocation(projectId, prompt = "Please select a location:") {
224
- const allowedLocations = (await apphosting.listLocations(projectId)).map((loc) => loc.locationId);
225
- if (allowedLocations.length === 1) {
226
- return allowedLocations[0];
227
- }
228
- const location = (await (0, prompt_1.promptOnce)({
229
- name: "location",
230
- type: "list",
231
- default: constants_1.DEFAULT_LOCATION,
232
- message: prompt,
233
- choices: allowedLocations,
234
- }));
235
- (0, utils_1.logSuccess)(`Location set to ${location}.\n`);
236
- return location;
237
- }
238
- exports.promptLocation = promptLocation;
239
- async function getBackendForAmbiguousLocation(projectId, backendId, locationDisambugationPrompt) {
240
- let { unreachable, backends } = await apphosting.listBackends(projectId, "-");
241
- if (unreachable && unreachable.length !== 0) {
242
- (0, utils_1.logWarning)(`The following locations are currently unreachable: ${unreachable}.\n` +
243
- "If your backend is in one of these regions, please try again later.");
244
- }
245
- backends = backends.filter((backend) => apphosting.parseBackendName(backend.name).id === backendId);
246
- if (backends.length === 0) {
247
- throw new error_1.FirebaseError(`No backend named "${backendId}" found.`);
248
- }
249
- if (backends.length === 1) {
250
- return backends[0];
251
- }
252
- const backendsByLocation = new Map();
253
- backends.forEach((backend) => backendsByLocation.set(apphosting.parseBackendName(backend.name).location, backend));
254
- const location = await (0, prompt_1.promptOnce)({
255
- name: "location",
256
- type: "list",
257
- message: locationDisambugationPrompt,
258
- choices: [...backendsByLocation.keys()],
259
- });
260
- return backendsByLocation.get(location);
261
- }
262
- exports.getBackendForAmbiguousLocation = getBackendForAmbiguousLocation;
3
+ exports.setupBackend = void 0;
4
+ const backend_1 = require("./backend");
5
+ Object.defineProperty(exports, "setupBackend", { enumerable: true, get: function () { return backend_1.doSetup; } });
@@ -7,9 +7,9 @@ const ora = require("ora");
7
7
  const devConnect_1 = require("../gcp/devConnect");
8
8
  const githubConnections_1 = require("../apphosting/githubConnections");
9
9
  const poller = require("../operation-poller");
10
- const prompt_1 = require("../prompt");
11
10
  const utils_1 = require("../utils");
12
11
  const api_1 = require("../api");
12
+ const backend_1 = require("./backend");
13
13
  const apphostingPollerOptions = {
14
14
  apiOrigin: (0, api_1.apphostingOrigin)(),
15
15
  apiVersion: apphosting.API_VERSION,
@@ -18,7 +18,14 @@ const apphostingPollerOptions = {
18
18
  };
19
19
  const GIT_COMMIT_SHA_REGEX = /^(?:[0-9a-f]{40}|[0-9a-f]{7})$/;
20
20
  async function createRollout(backendId, projectId, location, branch, commit, force) {
21
- const backend = await apphosting.getBackend(projectId, location, backendId);
21
+ let backend;
22
+ if (location === "-" || location === "") {
23
+ backend = await (0, backend_1.getBackendForAmbiguousLocation)(projectId, backendId, "Please select the location of the backend you'd like to roll out:", force);
24
+ location = apphosting.parseBackendName(backend.name).location;
25
+ }
26
+ else {
27
+ backend = await (0, backend_1.getBackendForLocation)(projectId, location, backendId);
28
+ }
22
29
  if (!backend.codebase.repository) {
23
30
  throw new error_1.FirebaseError(`Backend ${backendId} is misconfigured due to missing a connected repository. You can delete and recreate your backend using 'firebase apphosting:backends:delete' and 'firebase apphosting:backends:create'.`);
24
31
  }
@@ -48,18 +55,14 @@ async function createRollout(backendId, projectId, location, branch, commit, for
48
55
  }
49
56
  }
50
57
  else {
58
+ if (force) {
59
+ throw new error_1.FirebaseError(`Failed to create rollout with --force option because no target branch or commit was specified. Please specify which branch or commit to roll out with the --git-branch or --git-commit flag.`);
60
+ }
51
61
  branch = await (0, githubConnections_1.promptGitHubBranch)(repoLink);
52
62
  const branchInfo = await (0, githubConnections_1.getGitHubBranch)(owner, repo, branch, readToken.token);
53
63
  targetCommit = branchInfo.commit;
54
64
  }
55
65
  (0, utils_1.logBullet)(`You are about to deploy [${targetCommit.sha.substring(0, 7)}]: ${targetCommit.commit.message}`);
56
- const confirmRollout = await (0, prompt_1.confirm)({
57
- force: !!force,
58
- message: "Do you want to continue?",
59
- });
60
- if (!confirmRollout) {
61
- return;
62
- }
63
66
  (0, utils_1.logBullet)(`You may also track this rollout at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
64
67
  const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
65
68
  try {
@@ -16,7 +16,7 @@ const marked_1 = require("marked");
16
16
  marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
17
17
  const node_path_1 = require("node:path");
18
18
  const triple_beam_1 = require("triple-beam");
19
- const stripAnsi = require("strip-ansi");
19
+ const node_util_1 = require("node:util");
20
20
  const fs = require("node:fs");
21
21
  const configstore_1 = require("../configstore");
22
22
  const errorOut_1 = require("../errorOut");
@@ -58,7 +58,7 @@ logger_1.logger.add(new winston.transports.File({
58
58
  filename: logFilename,
59
59
  format: winston.format.printf((info) => {
60
60
  const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(utils.tryStringify);
61
- return `[${info.level}] ${stripAnsi(segments.join(" "))}`;
61
+ return `[${info.level}] ${(0, node_util_1.stripVTControlCharacters)(segments.join(" "))}`;
62
62
  }),
63
63
  }));
64
64
  logger_1.logger.debug("-".repeat(70));
@@ -4,8 +4,8 @@ exports.command = void 0;
4
4
  const command_1 = require("../command");
5
5
  const projectUtils_1 = require("../projectUtils");
6
6
  const requireInteractive_1 = require("../requireInteractive");
7
- const apphosting_1 = require("../apphosting");
8
- const apphosting_2 = require("../gcp/apphosting");
7
+ const backend_1 = require("../apphosting/backend");
8
+ const apphosting_1 = require("../gcp/apphosting");
9
9
  const firedata_1 = require("../gcp/firedata");
10
10
  const requireTosAcceptance_1 = require("../requireTosAcceptance");
11
11
  exports.command = new command_1.Command("apphosting:backends:create")
@@ -13,7 +13,7 @@ exports.command = new command_1.Command("apphosting:backends:create")
13
13
  .option("-a, --app <webAppId>", "specify an existing Firebase web app's ID to associate your App Hosting backend with")
14
14
  .option("-l, --location <location>", "specify the location of the backend", "")
15
15
  .option("-s, --service-account <serviceAccount>", "specify the service account used to run the server", "")
16
- .before(apphosting_2.ensureApiEnabled)
16
+ .before(apphosting_1.ensureApiEnabled)
17
17
  .before(requireInteractive_1.default)
18
18
  .before((0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.APPHOSTING_TOS_ID))
19
19
  .action(async (options) => {
@@ -21,5 +21,5 @@ exports.command = new command_1.Command("apphosting:backends:create")
21
21
  const webAppId = options.app;
22
22
  const location = options.location;
23
23
  const serviceAccount = options.serviceAccount;
24
- await (0, apphosting_1.doSetup)(projectId, webAppId, location, serviceAccount);
24
+ await (0, backend_1.doSetup)(projectId, webAppId, location, serviceAccount);
25
25
  });
@@ -8,7 +8,7 @@ const prompt_1 = require("../prompt");
8
8
  const utils = require("../utils");
9
9
  const apphosting = require("../gcp/apphosting");
10
10
  const apphosting_backends_list_1 = require("./apphosting-backends-list");
11
- const apphosting_1 = require("../apphosting");
11
+ const backend_1 = require("../apphosting/backend");
12
12
  const ora = require("ora");
13
13
  exports.command = new command_1.Command("apphosting:backends:delete <backend>")
14
14
  .description("delete a Firebase App Hosting backend")
@@ -20,11 +20,11 @@ exports.command = new command_1.Command("apphosting:backends:delete <backend>")
20
20
  let location = options.location;
21
21
  let backend;
22
22
  if (location === "-" || location === "") {
23
- backend = await (0, apphosting_1.getBackendForAmbiguousLocation)(projectId, backendId, "Please select the location of the backend you'd like to delete:");
23
+ backend = await (0, backend_1.getBackendForAmbiguousLocation)(projectId, backendId, "Please select the location of the backend you'd like to delete:");
24
24
  location = apphosting.parseBackendName(backend.name).location;
25
25
  }
26
26
  else {
27
- backend = await getBackendForLocation(projectId, location, backendId);
27
+ backend = await (0, backend_1.getBackendForLocation)(projectId, location, backendId);
28
28
  }
29
29
  utils.logWarning("You are about to permanently delete this backend:");
30
30
  (0, apphosting_backends_list_1.printBackendsTable)([backend]);
@@ -39,7 +39,7 @@ exports.command = new command_1.Command("apphosting:backends:delete <backend>")
39
39
  }
40
40
  const spinner = ora("Deleting backend...").start();
41
41
  try {
42
- await (0, apphosting_1.deleteBackendAndPoll)(projectId, location, backendId);
42
+ await (0, backend_1.deleteBackendAndPoll)(projectId, location, backendId);
43
43
  spinner.succeed(`Successfully deleted the backend: ${backendId}`);
44
44
  }
45
45
  catch (err) {
@@ -47,13 +47,3 @@ exports.command = new command_1.Command("apphosting:backends:delete <backend>")
47
47
  throw new error_1.FirebaseError(`Failed to delete backend: ${backendId}.`, { original: err });
48
48
  }
49
49
  });
50
- async function getBackendForLocation(projectId, location, backendId) {
51
- try {
52
- return await apphosting.getBackend(projectId, location, backendId);
53
- }
54
- catch (err) {
55
- throw new error_1.FirebaseError(`No backend named "${backendId}" found in ${location}.`, {
56
- original: err,
57
- });
58
- }
59
- }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const projectUtils_1 = require("../projectUtils");
6
+ const requireInteractive_1 = require("../requireInteractive");
7
+ const backend_1 = require("../apphosting/backend");
8
+ const apphosting_1 = require("../gcp/apphosting");
9
+ const firedata_1 = require("../gcp/firedata");
10
+ const requireTosAcceptance_1 = require("../requireTosAcceptance");
11
+ exports.command = new command_1.Command("apphosting:repos:create")
12
+ .description("create a Firebase App Hosting Developer Connect Git Repository Link")
13
+ .option("-l, --location <location>", "specify the location of the backend", "")
14
+ .option("-g, --gitconnection <connection>", "id of the connection", "")
15
+ .before(apphosting_1.ensureApiEnabled)
16
+ .before(requireInteractive_1.default)
17
+ .before((0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.APPHOSTING_TOS_ID))
18
+ .action(async (options) => {
19
+ const projectId = (0, projectUtils_1.needProjectId)(options);
20
+ const location = options.location;
21
+ const connection = options.gitconnection;
22
+ await (0, backend_1.createGitRepoLink)(projectId, location, connection);
23
+ });
@@ -8,10 +8,9 @@ const error_1 = require("../error");
8
8
  const rollout_1 = require("../apphosting/rollout");
9
9
  exports.command = new command_1.Command("apphosting:rollouts:create <backendId>")
10
10
  .description("create a rollout using a build for an App Hosting backend")
11
- .option("-l, --location <location>", "specify the region of the backend", "us-central1")
12
- .option("-i, --id <rolloutId>", "id of the rollout (defaults to autogenerating a random id)", "")
13
- .option("-gb, --git-branch <gitBranch>", "repository branch to deploy (mutually exclusive with -gc)")
14
- .option("-gc, --git-commit <gitCommit>", "git commit to deploy (mutually exclusive with -gb)")
11
+ .option("-l, --location <location>", "specify the region of the backend", "-")
12
+ .option("-b, --git-branch <gitBranch>", "repository branch to deploy (mutually exclusive with -g)")
13
+ .option("-g, --git-commit <gitCommit>", "git commit to deploy (mutually exclusive with -b)")
15
14
  .withForce("Skip confirmation before creating rollout")
16
15
  .before(apphosting.ensureApiEnabled)
17
16
  .action(async (backendId, options) => {
@@ -9,7 +9,7 @@ const secretManager = require("../gcp/secretManager");
9
9
  const requirePermissions_1 = require("../requirePermissions");
10
10
  const apphosting = require("../gcp/apphosting");
11
11
  const secrets = require("../apphosting/secrets");
12
- const apphosting_1 = require("../apphosting");
12
+ const backend_1 = require("../apphosting/backend");
13
13
  exports.command = new command_1.Command("apphosting:secrets:grantaccess <secretName>")
14
14
  .description("grant service accounts permissions to the provided secret")
15
15
  .option("-l, --location <location>", "backend location", "-")
@@ -39,7 +39,7 @@ exports.command = new command_1.Command("apphosting:secrets:grantaccess <secretN
39
39
  const location = options.location;
40
40
  let backend;
41
41
  if (location === "" || location === "-") {
42
- backend = await (0, apphosting_1.getBackendForAmbiguousLocation)(projectId, backendId, "Please select the location of your backend:");
42
+ backend = await (0, backend_1.getBackendForAmbiguousLocation)(projectId, backendId, "Please select the location of your backend:");
43
43
  }
44
44
  else {
45
45
  backend = await apphosting.getBackend(projectId, location, backendId);
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const pg = require("pg");
5
+ const clc = require("colorette");
6
+ const cloud_sql_connector_1 = require("@google-cloud/cloud-sql-connector");
7
+ const command_1 = require("../command");
8
+ const projectUtils_1 = require("../projectUtils");
9
+ const ensureApis_1 = require("../dataconnect/ensureApis");
10
+ const requirePermissions_1 = require("../requirePermissions");
11
+ const fileUtils_1 = require("../dataconnect/fileUtils");
12
+ const schemaMigration_1 = require("../dataconnect/schemaMigration");
13
+ const requireAuth_1 = require("../requireAuth");
14
+ const connect_1 = require("../gcp/cloudsql/connect");
15
+ const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
16
+ const prompt_1 = require("../prompt");
17
+ const logger_1 = require("../logger");
18
+ const error_1 = require("../error");
19
+ const fbToolsAuthClient_1 = require("../gcp/cloudsql/fbToolsAuthClient");
20
+ const interactive_1 = require("../gcp/cloudsql/interactive");
21
+ const sqlKeywords = [
22
+ "SELECT",
23
+ "FROM",
24
+ "WHERE",
25
+ "INSERT",
26
+ "UPDATE",
27
+ "DELETE",
28
+ "JOIN",
29
+ "GROUP",
30
+ "ORDER",
31
+ "LIMIT",
32
+ "GRANT",
33
+ "CREATE",
34
+ "DROP",
35
+ ];
36
+ async function promptForQuery() {
37
+ let query = "";
38
+ let line = "";
39
+ do {
40
+ const question = {
41
+ type: "input",
42
+ name: "line",
43
+ message: query ? "> " : "Enter your SQL query (or '.exit'):",
44
+ transformer: (input) => {
45
+ return input
46
+ .split(" ")
47
+ .map((word) => (sqlKeywords.includes(word.toUpperCase()) ? clc.cyan(word) : word))
48
+ .join(" ");
49
+ },
50
+ };
51
+ ({ line } = await (0, prompt_1.prompt)({ nonInteractive: false }, [question]));
52
+ line = line.trimEnd();
53
+ if (line.toLowerCase() === ".exit") {
54
+ return ".exit";
55
+ }
56
+ query += (query ? "\n" : "") + line;
57
+ } while (line !== "" && !query.endsWith(";"));
58
+ return query;
59
+ }
60
+ async function mainShellLoop(conn) {
61
+ while (true) {
62
+ const query = await promptForQuery();
63
+ if (query.toLowerCase() === ".exit") {
64
+ break;
65
+ }
66
+ if (query === "") {
67
+ continue;
68
+ }
69
+ if (await (0, interactive_1.confirmDangerousQuery)(query)) {
70
+ await (0, interactive_1.interactiveExecuteQuery)(query, conn);
71
+ }
72
+ else {
73
+ logger_1.logger.info(clc.yellow("Query cancelled."));
74
+ }
75
+ }
76
+ }
77
+ exports.command = new command_1.Command("dataconnect:sql:shell [serviceId]")
78
+ .description("Starts a shell connected directly to your dataconnect cloudsql instance.")
79
+ .before(requirePermissions_1.requirePermissions, ["firebasedataconnect.services.list", "cloudsql.instances.connect"])
80
+ .before(requireAuth_1.requireAuth)
81
+ .action(async (serviceId, options) => {
82
+ const projectId = (0, projectUtils_1.needProjectId)(options);
83
+ await (0, ensureApis_1.ensureApis)(projectId);
84
+ const serviceInfo = await (0, fileUtils_1.pickService)(projectId, options.config, serviceId);
85
+ const { instanceId, databaseId } = (0, schemaMigration_1.getIdentifiers)(serviceInfo.schema);
86
+ const { user: username } = await (0, connect_1.getIAMUser)(options);
87
+ const instance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
88
+ const connectionName = instance.connectionName;
89
+ if (!connectionName) {
90
+ throw new error_1.FirebaseError(`Could not get instance connection string for ${options.instanceId}:${options.databaseId}`);
91
+ }
92
+ const connector = new cloud_sql_connector_1.Connector({
93
+ auth: new fbToolsAuthClient_1.FBToolsAuthClient(),
94
+ });
95
+ const clientOpts = await connector.getOptions({
96
+ instanceConnectionName: connectionName,
97
+ ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
98
+ authType: cloud_sql_connector_1.AuthTypes.IAM,
99
+ });
100
+ const pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: username, database: databaseId }));
101
+ const conn = await pool.connect();
102
+ logger_1.logger.info(`Logged in as ${username}`);
103
+ logger_1.logger.info(clc.cyan("Welcome to Data Connect Cloud SQL Shell"));
104
+ logger_1.logger.info(clc.gray("Type your your SQL query or '.exit' to quit, queries should end with ';' or add empty line to execute."));
105
+ await mainShellLoop(conn);
106
+ logger_1.logger.info(clc.yellow("Exiting shell..."));
107
+ conn.release();
108
+ await pool.end();
109
+ connector.close();
110
+ return { projectId, serviceId };
111
+ });
@@ -173,6 +173,8 @@ function load(client) {
173
173
  client.apphosting.builds = {};
174
174
  client.apphosting.builds.get = loadCommand("apphosting-builds-get");
175
175
  client.apphosting.builds.create = loadCommand("apphosting-builds-create");
176
+ client.apphosting.repos = {};
177
+ client.apphosting.repos.create = loadCommand("apphosting-repos-create");
176
178
  client.apphosting.rollouts = {};
177
179
  client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
178
180
  client.apphosting.rollouts.list = loadCommand("apphosting-rollouts-list");
@@ -210,6 +212,7 @@ function load(client) {
210
212
  client.dataconnect.sql.diff = loadCommand("dataconnect-sql-diff");
211
213
  client.dataconnect.sql.migrate = loadCommand("dataconnect-sql-migrate");
212
214
  client.dataconnect.sql.grant = loadCommand("dataconnect-sql-grant");
215
+ client.dataconnect.sql.shell = loadCommand("dataconnect-sql-shell");
213
216
  client.dataconnect.sdk = {};
214
217
  client.dataconnect.sdk.generate = loadCommand("dataconnect-sdk-generate");
215
218
  client.target = loadCommand("target");
@@ -37,6 +37,12 @@ let choices = [
37
37
  name: "Functions: Configure a Cloud Functions directory and its files",
38
38
  checked: false,
39
39
  },
40
+ {
41
+ value: "apphosting",
42
+ name: "App Hosting: Configure an apphosting.yaml file for App Hosting",
43
+ checked: false,
44
+ hidden: false,
45
+ },
40
46
  {
41
47
  value: "hosting",
42
48
  name: "Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys",
@@ -79,12 +85,6 @@ let choices = [
79
85
  checked: false,
80
86
  hidden: true,
81
87
  },
82
- {
83
- value: "apphosting",
84
- name: "App Hosting: Configure an apphosting.yaml file for App Hosting",
85
- checked: false,
86
- hidden: false,
87
- },
88
88
  ];
89
89
  if ((0, experiments_1.isEnabled)("genkit")) {
90
90
  choices = [
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.grantRoleToUserInSchema = exports.migrateSchema = exports.diffSchema = void 0;
3
+ exports.getIdentifiers = exports.grantRoleToUserInSchema = exports.migrateSchema = exports.diffSchema = void 0;
4
4
  const clc = require("colorette");
5
5
  const sql_formatter_1 = require("sql-formatter");
6
6
  const types_1 = require("./types");
@@ -212,6 +212,7 @@ function getIdentifiers(schema) {
212
212
  serviceName,
213
213
  };
214
214
  }
215
+ exports.getIdentifiers = getIdentifiers;
215
216
  function suggestedCommand(serviceName, invalidConnectorNames) {
216
217
  const serviceId = serviceName.split("/")[5];
217
218
  const connectorIds = invalidConnectorNames.map((i) => i.split("/")[7]);