firebase-tools 13.20.2 → 13.22.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.
- package/lib/apphosting/githubConnections.js +19 -1
- package/lib/apphosting/index.js +12 -46
- package/lib/apphosting/rollout.js +127 -0
- package/lib/command.js +10 -3
- package/lib/commands/apphosting-rollouts-create.js +13 -13
- package/lib/commands/emulators-start.js +1 -1
- package/lib/commands/init.js +6 -0
- package/lib/config.js +14 -4
- package/lib/dataconnect/fileUtils.js +7 -43
- package/lib/dataconnect/freeTrial.js +44 -10
- package/lib/dataconnect/provisionCloudSql.js +4 -4
- package/lib/dataconnect/types.js +3 -2
- package/lib/dataconnect/webhook.js +2 -1
- package/lib/deploy/dataconnect/prepare.js +5 -0
- package/lib/emulator/apphosting/config.js +45 -0
- package/lib/emulator/apphosting/index.js +9 -11
- package/lib/emulator/apphosting/serve.js +16 -10
- package/lib/emulator/apphosting/utils.js +18 -0
- package/lib/emulator/controller.js +6 -1
- package/lib/emulator/downloadableEmulators.js +12 -12
- package/lib/emulator/eventarcEmulator.js +1 -1
- package/lib/emulator/extensionsEmulator.js +38 -6
- package/lib/emulator/functionsEmulator.js +85 -41
- package/lib/emulator/functionsEmulatorShared.js +2 -1
- package/lib/error.js +24 -1
- package/lib/extensions/emulator/triggerHelper.js +1 -1
- package/lib/extensions/extensionsApi.js +11 -8
- package/lib/extensions/runtimes/common.js +11 -19
- package/lib/extensions/runtimes/node.js +25 -22
- package/lib/extensions/types.js +16 -1
- package/lib/gcp/cloudmonitoring.js +3 -3
- package/lib/gcp/devConnect.js +38 -1
- package/lib/init/features/apphosting.js +12 -0
- package/lib/init/features/dataconnect/index.js +75 -85
- package/lib/init/features/dataconnect/sdk.js +69 -86
- package/lib/init/features/index.js +1 -1
- package/lib/init/index.js +1 -0
- package/lib/init/spawn.js +2 -1
- package/lib/requireAuth.js +9 -4
- package/package.json +1 -1
- package/templates/init/apphosting/apphosting.yaml +23 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fetchRepositoryCloneUris = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.listValidInstallations = exports.promptGitHubInstallation = exports.getConnectionForInstallation = exports.linkGitHubRepository = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
|
|
3
|
+
exports.getGitHubCommit = exports.getGitHubBranch = exports.fetchRepositoryCloneUris = exports.listAppHostingConnections = exports.getOrCreateRepository = exports.getOrCreateConnection = exports.createConnection = exports.ensureSecretManagerAdminGrant = exports.promptGitHubBranch = exports.getOrCreateOauthConnection = exports.listValidInstallations = exports.promptGitHubInstallation = exports.getConnectionForInstallation = exports.linkGitHubRepository = exports.generateRepositoryId = exports.extractRepoSlugFromUri = exports.parseConnectionName = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const devConnect = require("../gcp/devConnect");
|
|
6
6
|
const rm = require("../gcp/resourceManager");
|
|
@@ -12,6 +12,8 @@ const getProjectNumber_1 = require("../getProjectNumber");
|
|
|
12
12
|
const api_1 = require("../api");
|
|
13
13
|
const fuzzy = require("fuzzy");
|
|
14
14
|
const inquirer = require("inquirer");
|
|
15
|
+
const apiv2_1 = require("../apiv2");
|
|
16
|
+
const githubApiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.githubApiOrigin)(), auth: false });
|
|
15
17
|
const APPHOSTING_CONN_PATTERN = /.+\/apphosting-github-conn-.+$/;
|
|
16
18
|
const APPHOSTING_OAUTH_CONN_NAME = "firebase-app-hosting-github-oauth";
|
|
17
19
|
const CONNECTION_NAME_REGEX = /^projects\/(?<projectId>[^\/]+)\/locations\/(?<location>[^\/]+)\/connections\/(?<id>[^\/]+)$/;
|
|
@@ -346,3 +348,19 @@ async function fetchRepositoryCloneUris(projectId, connection) {
|
|
|
346
348
|
return cloneUris;
|
|
347
349
|
}
|
|
348
350
|
exports.fetchRepositoryCloneUris = fetchRepositoryCloneUris;
|
|
351
|
+
async function getGitHubBranch(owner, repo, branch, readToken) {
|
|
352
|
+
const headers = { Authorization: `Bearer ${readToken}`, "User-Agent": "Firebase CLI" };
|
|
353
|
+
const { body } = await githubApiClient.get(`/repos/${owner}/${repo}/branches/${branch}`, {
|
|
354
|
+
headers,
|
|
355
|
+
});
|
|
356
|
+
return body;
|
|
357
|
+
}
|
|
358
|
+
exports.getGitHubBranch = getGitHubBranch;
|
|
359
|
+
async function getGitHubCommit(owner, repo, ref, readToken) {
|
|
360
|
+
const headers = { Authorization: `Bearer ${readToken}`, "User-Agent": "Firebase CLI" };
|
|
361
|
+
const { body } = await githubApiClient.get(`/repos/${owner}/${repo}/commits/${ref}`, {
|
|
362
|
+
headers,
|
|
363
|
+
});
|
|
364
|
+
return body;
|
|
365
|
+
}
|
|
366
|
+
exports.getGitHubCommit = getGitHubCommit;
|
package/lib/apphosting/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getBackendForAmbiguousLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.
|
|
3
|
+
exports.getBackendForAmbiguousLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.setDefaultTrafficPolicy = exports.createBackend = exports.ensureAppHostingComputeServiceAccount = exports.doSetup = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const poller = require("../operation-poller");
|
|
6
6
|
const apphosting = require("../gcp/apphosting");
|
|
@@ -18,6 +18,7 @@ const deploymentTool = require("../deploymentTool");
|
|
|
18
18
|
const app_1 = require("./app");
|
|
19
19
|
const ora = require("ora");
|
|
20
20
|
const node_fetch_1 = require("node-fetch");
|
|
21
|
+
const rollout_1 = require("./rollout");
|
|
21
22
|
const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
|
|
22
23
|
const apphostingPollerOptions = {
|
|
23
24
|
apiOrigin: (0, api_1.apphostingOrigin)(),
|
|
@@ -105,12 +106,18 @@ async function doSetup(projectId, webAppName, location, serviceAccount) {
|
|
|
105
106
|
const url = `https://${backend.uri}`;
|
|
106
107
|
(0, utils_1.logBullet)(`You may also track this rollout at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
|
|
107
108
|
const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
|
|
108
|
-
await
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
await (0, rollout_1.orchestrateRollout)({
|
|
110
|
+
projectId,
|
|
111
|
+
location,
|
|
112
|
+
backendId,
|
|
113
|
+
buildInput: {
|
|
114
|
+
source: {
|
|
115
|
+
codebase: {
|
|
116
|
+
branch,
|
|
117
|
+
},
|
|
112
118
|
},
|
|
113
119
|
},
|
|
120
|
+
isFirstRollout: true,
|
|
114
121
|
});
|
|
115
122
|
createRolloutSpinner.succeed("Rollout complete");
|
|
116
123
|
if (!(await tlsReady(url))) {
|
|
@@ -208,47 +215,6 @@ async function setDefaultTrafficPolicy(projectId, location, backendId, codebaseB
|
|
|
208
215
|
await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `updateTraffic-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
|
|
209
216
|
}
|
|
210
217
|
exports.setDefaultTrafficPolicy = setDefaultTrafficPolicy;
|
|
211
|
-
async function orchestrateRollout(projectId, location, backendId, buildInput) {
|
|
212
|
-
const buildId = await apphosting.getNextRolloutId(projectId, location, backendId, 1);
|
|
213
|
-
const buildOp = await apphosting.createBuild(projectId, location, backendId, buildId, buildInput);
|
|
214
|
-
const rolloutBody = {
|
|
215
|
-
build: `projects/${projectId}/locations/${location}/backends/${backendId}/builds/${buildId}`,
|
|
216
|
-
};
|
|
217
|
-
let tries = 0;
|
|
218
|
-
let done = false;
|
|
219
|
-
while (!done) {
|
|
220
|
-
tries++;
|
|
221
|
-
try {
|
|
222
|
-
const validateOnly = true;
|
|
223
|
-
await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody, validateOnly);
|
|
224
|
-
done = true;
|
|
225
|
-
}
|
|
226
|
-
catch (err) {
|
|
227
|
-
if (err instanceof error_1.FirebaseError && err.status === 400) {
|
|
228
|
-
if (tries >= 5) {
|
|
229
|
-
throw err;
|
|
230
|
-
}
|
|
231
|
-
await (0, utils_1.sleep)(1000);
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
throw err;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const rolloutOp = await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody);
|
|
239
|
-
const rolloutPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-rollout-${buildId}`, operationResourceName: rolloutOp.name }));
|
|
240
|
-
const buildPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-build-${buildId}`, operationResourceName: buildOp.name }));
|
|
241
|
-
const [rollout, build] = await Promise.all([rolloutPoll, buildPoll]);
|
|
242
|
-
if (build.state !== "READY") {
|
|
243
|
-
if (!build.buildLogsUri) {
|
|
244
|
-
throw new error_1.FirebaseError("Failed to build your app, but failed to get build logs as well. " +
|
|
245
|
-
"This is an internal error and should be reported");
|
|
246
|
-
}
|
|
247
|
-
throw new error_1.FirebaseError(`Failed to build your app. Please inspect the build logs at ${build.buildLogsUri}.`, { children: [build.error] });
|
|
248
|
-
}
|
|
249
|
-
return { rollout, build };
|
|
250
|
-
}
|
|
251
|
-
exports.orchestrateRollout = orchestrateRollout;
|
|
252
218
|
async function deleteBackendAndPoll(projectId, location, backendId) {
|
|
253
219
|
const op = await apphosting.deleteBackend(projectId, location, backendId);
|
|
254
220
|
await poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `delete-${projectId}-${location}-${backendId}`, operationResourceName: op.name }));
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.orchestrateRollout = exports.createRollout = void 0;
|
|
4
|
+
const apphosting = require("../gcp/apphosting");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const ora = require("ora");
|
|
7
|
+
const devConnect_1 = require("../gcp/devConnect");
|
|
8
|
+
const githubConnections_1 = require("../apphosting/githubConnections");
|
|
9
|
+
const poller = require("../operation-poller");
|
|
10
|
+
const prompt_1 = require("../prompt");
|
|
11
|
+
const utils_1 = require("../utils");
|
|
12
|
+
const api_1 = require("../api");
|
|
13
|
+
const apphostingPollerOptions = {
|
|
14
|
+
apiOrigin: (0, api_1.apphostingOrigin)(),
|
|
15
|
+
apiVersion: apphosting.API_VERSION,
|
|
16
|
+
masterTimeout: 25 * 60 * 1000,
|
|
17
|
+
maxBackoff: 10000,
|
|
18
|
+
};
|
|
19
|
+
const GIT_COMMIT_SHA_REGEX = /^(?:[0-9a-f]{40}|[0-9a-f]{7})$/;
|
|
20
|
+
async function createRollout(backendId, projectId, location, branch, commit, force) {
|
|
21
|
+
const backend = await apphosting.getBackend(projectId, location, backendId);
|
|
22
|
+
if (!backend.codebase.repository) {
|
|
23
|
+
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
|
+
}
|
|
25
|
+
const { repoLink, owner, repo, readToken } = await (0, devConnect_1.getRepoDetailsFromBackend)(projectId, location, backend.codebase.repository);
|
|
26
|
+
let targetCommit;
|
|
27
|
+
if (branch) {
|
|
28
|
+
const branches = await (0, devConnect_1.listAllBranches)(repoLink.name);
|
|
29
|
+
if (!branches.has(branch)) {
|
|
30
|
+
throw new error_1.FirebaseError(`Unrecognized git branch ${branch}. Please double-check your branch name and try again.`);
|
|
31
|
+
}
|
|
32
|
+
const branchInfo = await (0, githubConnections_1.getGitHubBranch)(owner, repo, branch, readToken.token);
|
|
33
|
+
targetCommit = branchInfo.commit;
|
|
34
|
+
}
|
|
35
|
+
else if (commit) {
|
|
36
|
+
if (!GIT_COMMIT_SHA_REGEX.test(commit)) {
|
|
37
|
+
throw new error_1.FirebaseError(`Invalid git commit ${commit}. Must be a valid SHA1 hash.`);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const commitInfo = await (0, githubConnections_1.getGitHubCommit)(owner, repo, commit, readToken.token);
|
|
41
|
+
targetCommit = commitInfo;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err.status === 422) {
|
|
45
|
+
throw new error_1.FirebaseError(`Unrecognized git commit ${commit}. Please double-check your commit hash and try again.`);
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
branch = await (0, githubConnections_1.promptGitHubBranch)(repoLink);
|
|
52
|
+
const branchInfo = await (0, githubConnections_1.getGitHubBranch)(owner, repo, branch, readToken.token);
|
|
53
|
+
targetCommit = branchInfo.commit;
|
|
54
|
+
}
|
|
55
|
+
(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
|
+
(0, utils_1.logBullet)(`You may also track this rollout at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
|
|
64
|
+
const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
|
|
65
|
+
try {
|
|
66
|
+
await orchestrateRollout({
|
|
67
|
+
projectId,
|
|
68
|
+
location,
|
|
69
|
+
backendId,
|
|
70
|
+
buildInput: {
|
|
71
|
+
source: {
|
|
72
|
+
codebase: {
|
|
73
|
+
commit: targetCommit.sha,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
createRolloutSpinner.fail("Rollout failed.");
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
createRolloutSpinner.succeed("Successfully created a new rollout!");
|
|
84
|
+
}
|
|
85
|
+
exports.createRollout = createRollout;
|
|
86
|
+
async function orchestrateRollout(args) {
|
|
87
|
+
const { projectId, location, backendId, buildInput, isFirstRollout } = args;
|
|
88
|
+
const buildId = await apphosting.getNextRolloutId(projectId, location, backendId, isFirstRollout ? 1 : undefined);
|
|
89
|
+
const buildOp = await apphosting.createBuild(projectId, location, backendId, buildId, buildInput);
|
|
90
|
+
const rolloutBody = {
|
|
91
|
+
build: `projects/${projectId}/locations/${location}/backends/${backendId}/builds/${buildId}`,
|
|
92
|
+
};
|
|
93
|
+
let tries = 0;
|
|
94
|
+
let done = false;
|
|
95
|
+
while (!done) {
|
|
96
|
+
tries++;
|
|
97
|
+
try {
|
|
98
|
+
const validateOnly = true;
|
|
99
|
+
await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody, validateOnly);
|
|
100
|
+
done = true;
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
if (err instanceof error_1.FirebaseError && err.status === 400) {
|
|
104
|
+
if (tries >= 5) {
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
await (0, utils_1.sleep)(1000);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const rolloutOp = await apphosting.createRollout(projectId, location, backendId, buildId, rolloutBody);
|
|
115
|
+
const rolloutPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-rollout-${buildId}`, operationResourceName: rolloutOp.name }));
|
|
116
|
+
const buildPoll = poller.pollOperation(Object.assign(Object.assign({}, apphostingPollerOptions), { pollerName: `create-${projectId}-${location}-backend-${backendId}-build-${buildId}`, operationResourceName: buildOp.name }));
|
|
117
|
+
const [rollout, build] = await Promise.all([rolloutPoll, buildPoll]);
|
|
118
|
+
if (build.state !== "READY") {
|
|
119
|
+
if (!build.buildLogsUri) {
|
|
120
|
+
throw new error_1.FirebaseError("Failed to build your app, but failed to get build logs as well. " +
|
|
121
|
+
"This is an internal error and should be reported");
|
|
122
|
+
}
|
|
123
|
+
throw new error_1.FirebaseError(`Failed to build your app. Please inspect the build logs at ${build.buildLogsUri}.`, { children: [build.error] });
|
|
124
|
+
}
|
|
125
|
+
return { rollout, build };
|
|
126
|
+
}
|
|
127
|
+
exports.orchestrateRollout = orchestrateRollout;
|
package/lib/command.js
CHANGED
|
@@ -182,15 +182,18 @@ class Command {
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
applyRC(options) {
|
|
185
|
+
var _a, _b;
|
|
185
186
|
const rc = (0, rc_1.loadRC)(options);
|
|
186
187
|
options.rc = rc;
|
|
187
|
-
options.
|
|
188
|
-
|
|
188
|
+
const activeProject = options.projectRoot
|
|
189
|
+
? ((_a = configstore_1.configstore.get("activeProjects")) !== null && _a !== void 0 ? _a : {})[options.projectRoot]
|
|
190
|
+
: undefined;
|
|
191
|
+
options.project = (_b = options.project) !== null && _b !== void 0 ? _b : activeProject;
|
|
189
192
|
if (options.config && !options.project) {
|
|
190
193
|
options.project = options.config.defaults.project;
|
|
191
194
|
}
|
|
192
195
|
const aliases = rc.projects;
|
|
193
|
-
const rcProject =
|
|
196
|
+
const rcProject = options.project ? aliases[options.project] : undefined;
|
|
194
197
|
if (rcProject) {
|
|
195
198
|
options.projectAlias = options.project;
|
|
196
199
|
options.project = rcProject;
|
|
@@ -199,6 +202,10 @@ class Command {
|
|
|
199
202
|
options.projectAlias = (0, lodash_1.head)((0, lodash_1.keys)(aliases));
|
|
200
203
|
options.project = (0, lodash_1.head)((0, lodash_1.values)(aliases));
|
|
201
204
|
}
|
|
205
|
+
else if (!options.project && aliases["default"]) {
|
|
206
|
+
options.projectAlias = "default";
|
|
207
|
+
options.project = aliases["default"];
|
|
208
|
+
}
|
|
202
209
|
}
|
|
203
210
|
async resolveProjectIdentifiers(options) {
|
|
204
211
|
var _a;
|
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.command = void 0;
|
|
4
4
|
const apphosting = require("../gcp/apphosting");
|
|
5
|
-
const logger_1 = require("../logger");
|
|
6
5
|
const command_1 = require("../command");
|
|
7
6
|
const projectUtils_1 = require("../projectUtils");
|
|
8
|
-
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const rollout_1 = require("../apphosting/rollout");
|
|
9
|
+
exports.command = new command_1.Command("apphosting:rollouts:create <backendId>")
|
|
9
10
|
.description("create a rollout using a build for an App Hosting backend")
|
|
10
11
|
.option("-l, --location <location>", "specify the region of the backend", "us-central1")
|
|
11
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)")
|
|
15
|
+
.withForce("Skip confirmation before creating rollout")
|
|
12
16
|
.before(apphosting.ensureApiEnabled)
|
|
13
|
-
.action(async (backendId,
|
|
17
|
+
.action(async (backendId, options) => {
|
|
14
18
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
15
19
|
const location = options.location;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
logger_1.logger.info(`Started a rollout for backend ${backendId} with build ${buildId}.`);
|
|
23
|
-
logger_1.logger.info("Check status by running:");
|
|
24
|
-
logger_1.logger.info(`\tfirebase apphosting:rollouts:list --location ${location}`);
|
|
25
|
-
return op;
|
|
20
|
+
const branch = options.gitBranch;
|
|
21
|
+
const commit = options.gitCommit;
|
|
22
|
+
if (branch && commit) {
|
|
23
|
+
throw new error_1.FirebaseError("Cannot specify both a branch and commit to deploy. Please specify either --git-branch or --git-commit.");
|
|
24
|
+
}
|
|
25
|
+
await (0, rollout_1.createRollout)(backendId, projectId, location, branch, commit, options.force);
|
|
26
26
|
});
|
|
@@ -103,7 +103,7 @@ function printEmulatorOverview(options) {
|
|
|
103
103
|
let extensionsTable = "";
|
|
104
104
|
if (registry_1.EmulatorRegistry.isRunning(types_1.Emulators.EXTENSIONS)) {
|
|
105
105
|
const extensionsEmulatorInstance = registry_1.EmulatorRegistry.get(types_1.Emulators.EXTENSIONS);
|
|
106
|
-
extensionsTable = extensionsEmulatorInstance.extensionsInfoTable(
|
|
106
|
+
extensionsTable = extensionsEmulatorInstance.extensionsInfoTable();
|
|
107
107
|
}
|
|
108
108
|
logger_1.logger.info(`\n${successMessageTable}
|
|
109
109
|
|
package/lib/commands/init.js
CHANGED
|
@@ -79,6 +79,12 @@ let choices = [
|
|
|
79
79
|
checked: false,
|
|
80
80
|
hidden: true,
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
value: "apphosting",
|
|
84
|
+
name: "App Hosting: Configure an apphosting.yaml file for App Hosting",
|
|
85
|
+
checked: false,
|
|
86
|
+
hidden: false,
|
|
87
|
+
},
|
|
82
88
|
];
|
|
83
89
|
if ((0, experiments_1.isEnabled)("genkit")) {
|
|
84
90
|
choices = [
|
package/lib/config.js
CHANGED
|
@@ -165,21 +165,31 @@ class Config {
|
|
|
165
165
|
deleteProjectFile(p) {
|
|
166
166
|
fs.removeSync(this.path(p));
|
|
167
167
|
}
|
|
168
|
-
askWriteProjectFile(p, content, force) {
|
|
168
|
+
askWriteProjectFile(p, content, force, confirmByDefault) {
|
|
169
169
|
const writeTo = this.path(p);
|
|
170
170
|
let next;
|
|
171
|
-
if (
|
|
171
|
+
if (typeof content !== "string") {
|
|
172
|
+
content = JSON.stringify(content, null, 2) + "\n";
|
|
173
|
+
}
|
|
174
|
+
let existingContent;
|
|
175
|
+
if (fsutils.fileExistsSync(writeTo)) {
|
|
176
|
+
existingContent = fsutils.readFile(writeTo);
|
|
177
|
+
}
|
|
178
|
+
if (existingContent && existingContent !== content && !force) {
|
|
172
179
|
next = (0, prompt_1.promptOnce)({
|
|
173
180
|
type: "confirm",
|
|
174
181
|
message: "File " + clc.underline(p) + " already exists. Overwrite?",
|
|
175
|
-
default:
|
|
182
|
+
default: !!confirmByDefault,
|
|
176
183
|
});
|
|
177
184
|
}
|
|
178
185
|
else {
|
|
179
186
|
next = Promise.resolve(true);
|
|
180
187
|
}
|
|
181
188
|
return next.then((result) => {
|
|
182
|
-
if (
|
|
189
|
+
if (existingContent === content) {
|
|
190
|
+
utils.logBullet(clc.bold(p) + " is unchanged");
|
|
191
|
+
}
|
|
192
|
+
else if (result) {
|
|
183
193
|
this.writeProjectFile(p, content);
|
|
184
194
|
utils.logSuccess("Wrote " + clc.bold(p));
|
|
185
195
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getPlatformFromFolder = exports.pickService = exports.readGQLFiles = exports.readConnectorYaml = exports.readDataConnectYaml = exports.readFirebaseJson = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const error_1 = require("../error");
|
|
@@ -112,7 +112,10 @@ async function getPlatformFromFolder(dirPath) {
|
|
|
112
112
|
IOS_POSTFIX_INDICATORS.some((indicator) => cleanedFileName.endsWith(indicator)));
|
|
113
113
|
hasDart || (hasDart = DART_INDICATORS.some((indicator) => indicator === cleanedFileName));
|
|
114
114
|
}
|
|
115
|
-
if (hasWeb && !hasAndroid && !hasIOS && !hasDart) {
|
|
115
|
+
if (!hasWeb && !hasAndroid && !hasIOS && !hasDart) {
|
|
116
|
+
return types_1.Platform.NONE;
|
|
117
|
+
}
|
|
118
|
+
else if (hasWeb && !hasAndroid && !hasIOS && !hasDart) {
|
|
116
119
|
return types_1.Platform.WEB;
|
|
117
120
|
}
|
|
118
121
|
else if (hasAndroid && !hasWeb && !hasIOS && !hasDart) {
|
|
@@ -122,47 +125,8 @@ async function getPlatformFromFolder(dirPath) {
|
|
|
122
125
|
return types_1.Platform.IOS;
|
|
123
126
|
}
|
|
124
127
|
else if (hasDart && !hasWeb && !hasIOS && !hasAndroid) {
|
|
125
|
-
return types_1.Platform.
|
|
128
|
+
return types_1.Platform.FLUTTER;
|
|
126
129
|
}
|
|
127
|
-
return types_1.Platform.
|
|
130
|
+
return types_1.Platform.MULTIPLE;
|
|
128
131
|
}
|
|
129
132
|
exports.getPlatformFromFolder = getPlatformFromFolder;
|
|
130
|
-
async function directoryHasPackageJson(dirPath) {
|
|
131
|
-
const fileNames = await fs.readdir(dirPath);
|
|
132
|
-
return fileNames.some((f) => f.toLowerCase() === "package.json");
|
|
133
|
-
}
|
|
134
|
-
exports.directoryHasPackageJson = directoryHasPackageJson;
|
|
135
|
-
function generateSdkYaml(platform, connectorYaml, connectorYamlFolder, appFolder) {
|
|
136
|
-
const relPath = path.relative(connectorYamlFolder, appFolder);
|
|
137
|
-
const outputDir = path.join(relPath, "dataconnect-generated");
|
|
138
|
-
if (!connectorYaml.generate) {
|
|
139
|
-
connectorYaml.generate = {};
|
|
140
|
-
}
|
|
141
|
-
if (platform === types_1.Platform.WEB) {
|
|
142
|
-
connectorYaml.generate.javascriptSdk = {
|
|
143
|
-
outputDir,
|
|
144
|
-
package: `@firebasegen/${connectorYaml.connectorId}`,
|
|
145
|
-
packageJsonDir: appFolder,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (platform === types_1.Platform.IOS) {
|
|
149
|
-
connectorYaml.generate.swiftSdk = {
|
|
150
|
-
outputDir,
|
|
151
|
-
package: connectorYaml.connectorId,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
if (platform === types_1.Platform.ANDROID) {
|
|
155
|
-
connectorYaml.generate.kotlinSdk = {
|
|
156
|
-
outputDir,
|
|
157
|
-
package: `connectors.${connectorYaml.connectorId}`,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
if (platform === types_1.Platform.DART) {
|
|
161
|
-
connectorYaml.generate.dartSdk = {
|
|
162
|
-
outputDir,
|
|
163
|
-
package: connectorYaml.connectorId,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return connectorYaml;
|
|
167
|
-
}
|
|
168
|
-
exports.generateSdkYaml = generateSdkYaml;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.printFreeTrialUnavailable = exports.
|
|
3
|
+
exports.upgradeInstructions = exports.printFreeTrialUnavailable = exports.getFreeTrialInstanceId = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
|
|
4
|
+
const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
|
|
4
5
|
const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
|
|
5
6
|
const utils = require("../utils");
|
|
6
7
|
const clc = require("colorette");
|
|
@@ -8,18 +9,51 @@ function freeTrialTermsLink() {
|
|
|
8
9
|
return "https://firebase.google.com/pricing";
|
|
9
10
|
}
|
|
10
11
|
exports.freeTrialTermsLink = freeTrialTermsLink;
|
|
11
|
-
|
|
12
|
+
const FREE_TRIAL_METRIC = "sqladmin.googleapis.com/fdc_lifetime_free_trial_per_project";
|
|
13
|
+
async function checkFreeTrialInstanceUsed(projectId) {
|
|
14
|
+
const past7d = new Date();
|
|
15
|
+
past7d.setDate(past7d.getDate() - 7);
|
|
16
|
+
const query = {
|
|
17
|
+
filter: `metric.type="serviceruntime.googleapis.com/quota/allocation/usage" AND metric.label.quota_metric = "${FREE_TRIAL_METRIC}"`,
|
|
18
|
+
"interval.endTime": new Date().toJSON(),
|
|
19
|
+
"interval.startTime": past7d.toJSON(),
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
const ts = await (0, cloudmonitoring_1.queryTimeSeries)(query, projectId);
|
|
23
|
+
if (ts.length) {
|
|
24
|
+
return ts[0].points.some((p) => p.value.int64Value);
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.checkFreeTrialInstanceUsed = checkFreeTrialInstanceUsed;
|
|
33
|
+
async function getFreeTrialInstanceId(projectId) {
|
|
12
34
|
var _a;
|
|
13
35
|
const instances = await (0, cloudsqladmin_1.listInstances)(projectId);
|
|
14
36
|
return (_a = instances.find((i) => { var _a; return ((_a = i.settings.userLabels) === null || _a === void 0 ? void 0 : _a["firebase-data-connect"]) === "ft"; })) === null || _a === void 0 ? void 0 : _a.name;
|
|
15
37
|
}
|
|
16
|
-
exports.
|
|
17
|
-
function printFreeTrialUnavailable(projectId,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
utils.
|
|
38
|
+
exports.getFreeTrialInstanceId = getFreeTrialInstanceId;
|
|
39
|
+
function printFreeTrialUnavailable(projectId, configYamlPath, instanceId) {
|
|
40
|
+
if (!instanceId) {
|
|
41
|
+
utils.logLabeledError("data connect", "The CloudSQL free trial has already been used on this project.");
|
|
42
|
+
utils.logLabeledError("data connect", `You may create or use a paid CloudSQL instance by visiting https://console.cloud.google.com/sql/instances`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
utils.logLabeledError("data connect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`);
|
|
46
|
+
const reuseHint = `To use a different database in the same instance, ${clc.bold(`change the ${clc.blue("instanceId")} to "${instanceId}"`)} and update ${clc.blue("location")} in ` +
|
|
47
|
+
`${clc.green(configYamlPath)}.`;
|
|
48
|
+
utils.logLabeledError("data connect", reuseHint);
|
|
49
|
+
utils.logLabeledError("data connect", `Alternatively, you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`);
|
|
24
50
|
}
|
|
25
51
|
exports.printFreeTrialUnavailable = printFreeTrialUnavailable;
|
|
52
|
+
function upgradeInstructions(projectId) {
|
|
53
|
+
return `If you'd like to provision a CloudSQL Postgres instance on the Firebase Data Connect no-cost trial:
|
|
54
|
+
1. Please upgrade to the pay-as-you-go (Blaze) billing plan. Visit the following page:
|
|
55
|
+
https://console.firebase.google.com/project/${projectId}/usage/details
|
|
56
|
+
2. Run ${clc.bold("firebase init dataconnect")} again to configure the Cloud SQL instance.
|
|
57
|
+
3. Run ${clc.bold("firebase deploy --only dataconnect")} to deploy your Data Connect service.`;
|
|
58
|
+
}
|
|
59
|
+
exports.upgradeInstructions = upgradeInstructions;
|
|
@@ -33,10 +33,10 @@ async function provisionCloudSql(args) {
|
|
|
33
33
|
if (err.status !== 404) {
|
|
34
34
|
throw err;
|
|
35
35
|
}
|
|
36
|
-
const freeTrialInstanceId = await (0, freeTrial_1.
|
|
37
|
-
if (
|
|
38
|
-
(0, freeTrial_1.printFreeTrialUnavailable)(projectId,
|
|
39
|
-
throw new error_1.FirebaseError("
|
|
36
|
+
const freeTrialInstanceId = await (0, freeTrial_1.getFreeTrialInstanceId)(projectId);
|
|
37
|
+
if (await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId)) {
|
|
38
|
+
(0, freeTrial_1.printFreeTrialUnavailable)(projectId, configYamlPath, freeTrialInstanceId);
|
|
39
|
+
throw new error_1.FirebaseError("No-cost Cloud SQL trial has already been used on this project.");
|
|
40
40
|
}
|
|
41
41
|
const cta = dryRun ? "It will be created on your next deploy" : "Creating it now.";
|
|
42
42
|
silent ||
|
package/lib/dataconnect/types.js
CHANGED
|
@@ -9,11 +9,12 @@ function requiresVector(dm) {
|
|
|
9
9
|
exports.requiresVector = requiresVector;
|
|
10
10
|
var Platform;
|
|
11
11
|
(function (Platform) {
|
|
12
|
+
Platform["NONE"] = "NONE";
|
|
12
13
|
Platform["ANDROID"] = "ANDROID";
|
|
13
14
|
Platform["WEB"] = "WEB";
|
|
14
15
|
Platform["IOS"] = "IOS";
|
|
15
|
-
Platform["
|
|
16
|
-
Platform["
|
|
16
|
+
Platform["FLUTTER"] = "FLUTTER";
|
|
17
|
+
Platform["MULTIPLE"] = "MULTIPLE";
|
|
17
18
|
})(Platform = exports.Platform || (exports.Platform = {}));
|
|
18
19
|
function toDatasource(projectId, locationId, ds) {
|
|
19
20
|
if (ds.postgresql) {
|
|
@@ -22,10 +22,11 @@ async function sendVSCodeMessage(body) {
|
|
|
22
22
|
"x-mantle-admin": "all",
|
|
23
23
|
},
|
|
24
24
|
body: jsonBody,
|
|
25
|
+
signal: AbortSignal.timeout(3000),
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
catch (e) {
|
|
28
|
-
logger_1.logger.debug(`Could not find VSCode notification endpoint: ${e}
|
|
29
|
+
logger_1.logger.debug(`Could not find VSCode notification endpoint: ${e}. If you are not running the Firebase Data Connect VSCode extension, this is expected and not an issue.`);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
exports.sendVSCodeMessage = sendVSCodeMessage;
|
|
@@ -12,14 +12,19 @@ const ensureApis_1 = require("../../dataconnect/ensureApis");
|
|
|
12
12
|
const requireTosAcceptance_1 = require("../../requireTosAcceptance");
|
|
13
13
|
const firedata_1 = require("../../gcp/firedata");
|
|
14
14
|
const provisionCloudSql_1 = require("../../dataconnect/provisionCloudSql");
|
|
15
|
+
const cloudbilling_1 = require("../../gcp/cloudbilling");
|
|
15
16
|
const names_1 = require("../../dataconnect/names");
|
|
16
17
|
const error_1 = require("../../error");
|
|
17
18
|
const types_1 = require("../../dataconnect/types");
|
|
18
19
|
const schemaMigration_1 = require("../../dataconnect/schemaMigration");
|
|
19
20
|
const node_path_1 = require("node:path");
|
|
21
|
+
const freeTrial_1 = require("../../dataconnect/freeTrial");
|
|
20
22
|
async function default_1(context, options) {
|
|
21
23
|
var _a;
|
|
22
24
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
25
|
+
if (!(await (0, cloudbilling_1.checkBillingEnabled)(projectId))) {
|
|
26
|
+
throw new error_1.FirebaseError((0, freeTrial_1.upgradeInstructions)(projectId));
|
|
27
|
+
}
|
|
23
28
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
24
29
|
await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
|
|
25
30
|
const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getLocalAppHostingConfiguration = exports.loadAppHostingYaml = void 0;
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const fs_extra_1 = require("fs-extra");
|
|
6
|
+
const utils_1 = require("../../utils");
|
|
7
|
+
const utils_2 = require("./utils");
|
|
8
|
+
const types_1 = require("../types");
|
|
9
|
+
const APPHOSTING_YAML = "apphosting.yaml";
|
|
10
|
+
const APPHOSTING_LOCAL_YAML = "apphosting.local.yaml";
|
|
11
|
+
async function loadAppHostingYaml(sourceDirectory, fileName) {
|
|
12
|
+
const file = await (0, utils_1.readFileFromDirectory)(sourceDirectory, fileName);
|
|
13
|
+
const apphostingYaml = await (0, utils_1.wrappedSafeLoad)(file.source);
|
|
14
|
+
const environmentVariables = {};
|
|
15
|
+
const secrets = {};
|
|
16
|
+
if (apphostingYaml.env) {
|
|
17
|
+
for (const env of apphostingYaml.env) {
|
|
18
|
+
if (env.value) {
|
|
19
|
+
environmentVariables[env.variable] = env.value;
|
|
20
|
+
}
|
|
21
|
+
if (env.secret) {
|
|
22
|
+
secrets[env.variable] = env.secret;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { environmentVariables, secrets };
|
|
27
|
+
}
|
|
28
|
+
exports.loadAppHostingYaml = loadAppHostingYaml;
|
|
29
|
+
async function getLocalAppHostingConfiguration(sourceDirectory) {
|
|
30
|
+
let apphostingBaseConfig = {};
|
|
31
|
+
let apphostingLocalConfig = {};
|
|
32
|
+
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(sourceDirectory, APPHOSTING_YAML))) {
|
|
33
|
+
utils_2.logger.logLabeled("SUCCESS", types_1.Emulators.APPHOSTING, `${APPHOSTING_YAML} found, loading configuration`);
|
|
34
|
+
apphostingBaseConfig = await loadAppHostingYaml(sourceDirectory, APPHOSTING_YAML);
|
|
35
|
+
}
|
|
36
|
+
if (await (0, fs_extra_1.pathExists)((0, path_1.join)(sourceDirectory, APPHOSTING_LOCAL_YAML))) {
|
|
37
|
+
utils_2.logger.logLabeled("SUCCESS", types_1.Emulators.APPHOSTING, `${APPHOSTING_LOCAL_YAML} found, loading configuration`);
|
|
38
|
+
apphostingLocalConfig = await loadAppHostingYaml(sourceDirectory, APPHOSTING_LOCAL_YAML);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
environmentVariables: Object.assign(Object.assign({}, apphostingBaseConfig.environmentVariables), apphostingLocalConfig.environmentVariables),
|
|
42
|
+
secrets: Object.assign(Object.assign({}, apphostingBaseConfig.secrets), apphostingLocalConfig.secrets),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
exports.getLocalAppHostingConfiguration = getLocalAppHostingConfiguration;
|