firebase-tools 13.8.0 → 13.8.2
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/app.js +41 -60
- package/lib/apphosting/githubConnections.js +6 -8
- package/lib/apphosting/index.js +56 -27
- package/lib/apphosting/repo.js +2 -2
- package/lib/commands/apphosting-backends-create.js +6 -5
- package/lib/commands/dataconnect-sdk-generate.js +2 -1
- package/lib/commands/{dataconnect-list.js → dataconnect-services-list.js} +23 -6
- package/lib/commands/dataconnect-sql-migrate.js +9 -4
- package/lib/commands/index.js +2 -1
- package/lib/commands/init.js +3 -3
- package/lib/dataconnect/client.js +2 -2
- package/lib/dataconnect/errors.js +31 -0
- package/lib/dataconnect/fileUtils.js +10 -7
- package/lib/dataconnect/freeTrial.js +1 -1
- package/lib/dataconnect/graphqlError.js +2 -2
- package/lib/dataconnect/provisionCloudSql.js +5 -21
- package/lib/dataconnect/schemaMigration.js +166 -59
- package/lib/deploy/dataconnect/deploy.js +4 -14
- package/lib/deploy/dataconnect/prepare.js +3 -0
- package/lib/deploy/dataconnect/release.js +7 -9
- package/lib/deploy/functions/release/fabricator.js +7 -2
- package/lib/emulator/constants.js +1 -1
- package/lib/emulator/controller.js +1 -0
- package/lib/emulator/dataconnectEmulator.js +4 -1
- package/lib/emulator/downloadableEmulators.js +9 -9
- package/lib/emulator/portUtils.js +1 -2
- package/lib/emulator/registry.js +1 -1
- package/lib/experiments.js +12 -5
- package/lib/frameworks/next/index.js +3 -1
- package/lib/frameworks/next/utils.js +1 -1
- package/lib/gcp/cloudsql/cloudsqladmin.js +39 -19
- package/lib/gcp/cloudsql/connect.js +2 -1
- package/lib/gcp/firedata.js +27 -0
- package/lib/hosting/interactive.js +4 -0
- package/lib/init/features/dataconnect/index.js +79 -37
- package/lib/init/features/emulators.js +9 -10
- package/lib/init/features/functions/index.js +4 -0
- package/lib/init/features/functions/npm-dependencies.js +12 -23
- package/lib/init/features/genkit.js +54 -0
- package/lib/init/features/index.js +3 -1
- package/lib/init/index.js +1 -0
- package/lib/init/spawn.js +23 -0
- package/lib/operation-poller.js +1 -1
- package/lib/requireTosAcceptance.js +22 -0
- package/lib/utils.js +6 -2
- package/package.json +1 -1
- package/templates/init/dataconnect/mutations.gql +29 -4
- package/templates/init/dataconnect/queries.gql +49 -5
- package/templates/init/dataconnect/schema.gql +23 -11
package/lib/apphosting/app.js
CHANGED
|
@@ -1,88 +1,69 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.webApps = void 0;
|
|
4
|
-
const fuzzy = require("fuzzy");
|
|
5
|
-
const inquirer = require("inquirer");
|
|
6
4
|
const apps_1 = require("../management/apps");
|
|
7
|
-
const prompt_1 = require("../prompt");
|
|
8
5
|
const error_1 = require("../error");
|
|
6
|
+
const utils_1 = require("../utils");
|
|
9
7
|
const CREATE_NEW_FIREBASE_WEB_APP = "CREATE_NEW_WEB_APP";
|
|
10
8
|
const CONTINUE_WITHOUT_SELECTING_FIREBASE_WEB_APP = "CONTINUE_WITHOUT_SELECTING_FIREBASE_WEB_APP";
|
|
11
9
|
exports.webApps = {
|
|
12
10
|
CREATE_NEW_FIREBASE_WEB_APP,
|
|
13
11
|
CONTINUE_WITHOUT_SELECTING_FIREBASE_WEB_APP,
|
|
14
12
|
getOrCreateWebApp,
|
|
15
|
-
|
|
13
|
+
generateWebAppName,
|
|
16
14
|
};
|
|
17
|
-
async function getOrCreateWebApp(projectId,
|
|
15
|
+
async function getOrCreateWebApp(projectId, firebaseWebAppId, backendId) {
|
|
16
|
+
var _a;
|
|
18
17
|
const webAppsInProject = await (0, apps_1.listFirebaseApps)(projectId, apps_1.AppPlatform.WEB);
|
|
19
|
-
if (
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return { name: displayName, id: appId };
|
|
24
|
-
}
|
|
25
|
-
const existingUserProjectWebApps = new Map(webAppsInProject.map((obj) => {
|
|
26
|
-
var _a;
|
|
27
|
-
return [
|
|
28
|
-
(_a = obj.displayName) !== null && _a !== void 0 ? _a : obj.appId,
|
|
29
|
-
obj.appId,
|
|
30
|
-
];
|
|
31
|
-
}));
|
|
32
|
-
if (firebaseWebAppName) {
|
|
33
|
-
if (existingUserProjectWebApps.get(firebaseWebAppName) === undefined) {
|
|
34
|
-
throw new error_1.FirebaseError(`The web app '${firebaseWebAppName}' does not exist in project ${projectId}`);
|
|
18
|
+
if (firebaseWebAppId) {
|
|
19
|
+
const webApp = webAppsInProject.find((app) => app.appId === firebaseWebAppId);
|
|
20
|
+
if (webApp === undefined) {
|
|
21
|
+
throw new error_1.FirebaseError(`The web app '${firebaseWebAppId}' does not exist in project ${projectId}`);
|
|
35
22
|
}
|
|
36
|
-
return {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
async function promptFirebaseWebApp(projectId, backendId, existingUserProjectWebApps) {
|
|
41
|
-
const existingWebAppKeys = Array.from(existingUserProjectWebApps.keys());
|
|
42
|
-
const firebaseWebAppName = await (0, prompt_1.promptOnce)({
|
|
43
|
-
type: "autocomplete",
|
|
44
|
-
name: "app",
|
|
45
|
-
message: "Which of the following Firebase web apps would you like to associate your backend with?",
|
|
46
|
-
source: (_, input = "") => {
|
|
47
|
-
return new Promise((resolve) => resolve([
|
|
48
|
-
new inquirer.Separator(),
|
|
49
|
-
{
|
|
50
|
-
name: "Create a new Firebase web app.",
|
|
51
|
-
value: CREATE_NEW_FIREBASE_WEB_APP,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
name: "Continue without a Firebase web app.",
|
|
55
|
-
value: CONTINUE_WITHOUT_SELECTING_FIREBASE_WEB_APP,
|
|
56
|
-
},
|
|
57
|
-
new inquirer.Separator(),
|
|
58
|
-
...fuzzy.filter(input, existingWebAppKeys).map((result) => {
|
|
59
|
-
return result.original;
|
|
60
|
-
}),
|
|
61
|
-
]));
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
if (firebaseWebAppName === CREATE_NEW_FIREBASE_WEB_APP) {
|
|
65
|
-
const newFirebaseWebApp = await createFirebaseWebApp(projectId, { displayName: backendId });
|
|
66
|
-
return { name: newFirebaseWebApp.displayName, id: newFirebaseWebApp.appId };
|
|
67
|
-
}
|
|
68
|
-
else if (firebaseWebAppName === CONTINUE_WITHOUT_SELECTING_FIREBASE_WEB_APP) {
|
|
69
|
-
return;
|
|
23
|
+
return {
|
|
24
|
+
name: (_a = webApp.displayName) !== null && _a !== void 0 ? _a : webApp.appId,
|
|
25
|
+
id: webApp.appId,
|
|
26
|
+
};
|
|
70
27
|
}
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
async function createFirebaseWebApp(projectId, options) {
|
|
28
|
+
const webAppName = await generateWebAppName(projectId, backendId);
|
|
74
29
|
try {
|
|
75
|
-
|
|
30
|
+
const app = await (0, apps_1.createWebApp)(projectId, { displayName: webAppName });
|
|
31
|
+
(0, utils_1.logSuccess)(`Created a new Firebase web app named "${webAppName}"`);
|
|
32
|
+
return { name: app.displayName, id: app.appId };
|
|
76
33
|
}
|
|
77
34
|
catch (e) {
|
|
78
35
|
if (isQuotaError(e)) {
|
|
79
|
-
|
|
36
|
+
(0, utils_1.logWarning)("Unable to create a new web app, the project has reached the quota for Firebase apps. Navigate to your Firebase console to manage or delete a Firebase app to continue. ");
|
|
37
|
+
return;
|
|
80
38
|
}
|
|
81
39
|
throw new error_1.FirebaseError("Unable to create a Firebase web app", {
|
|
82
40
|
original: e instanceof Error ? e : undefined,
|
|
83
41
|
});
|
|
84
42
|
}
|
|
85
43
|
}
|
|
44
|
+
async function generateWebAppName(projectId, backendId) {
|
|
45
|
+
const webAppsInProject = await (0, apps_1.listFirebaseApps)(projectId, apps_1.AppPlatform.WEB);
|
|
46
|
+
const appsMap = firebaseAppsToMap(webAppsInProject);
|
|
47
|
+
if (!appsMap.get(backendId)) {
|
|
48
|
+
return backendId;
|
|
49
|
+
}
|
|
50
|
+
let uniqueId = 1;
|
|
51
|
+
let webAppName = `${backendId}_${uniqueId}`;
|
|
52
|
+
while (appsMap.get(webAppName)) {
|
|
53
|
+
uniqueId += 1;
|
|
54
|
+
webAppName = `${backendId}_${uniqueId}`;
|
|
55
|
+
}
|
|
56
|
+
return webAppName;
|
|
57
|
+
}
|
|
58
|
+
function firebaseAppsToMap(apps) {
|
|
59
|
+
return new Map(apps.map((obj) => {
|
|
60
|
+
var _a;
|
|
61
|
+
return [
|
|
62
|
+
(_a = obj.displayName) !== null && _a !== void 0 ? _a : obj.appId,
|
|
63
|
+
obj.appId,
|
|
64
|
+
];
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
86
67
|
function isQuotaError(error) {
|
|
87
68
|
var _a, _b, _c, _d, _e;
|
|
88
69
|
const original = error.original;
|
|
@@ -54,7 +54,7 @@ function generateConnectionId() {
|
|
|
54
54
|
const ADD_CONN_CHOICE = "@ADD_CONN";
|
|
55
55
|
async function linkGitHubRepository(projectId, location) {
|
|
56
56
|
var _a, _b;
|
|
57
|
-
utils.logBullet(clc.bold(`${clc.yellow("===")}
|
|
57
|
+
utils.logBullet(clc.bold(`${clc.yellow("===")} Import a GitHub repository`));
|
|
58
58
|
const oauthConn = await getOrCreateOauthConnection(projectId, location);
|
|
59
59
|
const existingConns = await listAppHostingConnections(projectId);
|
|
60
60
|
if (existingConns.length === 0) {
|
|
@@ -76,8 +76,6 @@ async function linkGitHubRepository(projectId, location) {
|
|
|
76
76
|
appInstallationId: (_b = connection.githubConfig) === null || _b === void 0 ? void 0 : _b.appInstallationId,
|
|
77
77
|
});
|
|
78
78
|
const repo = await getOrCreateRepository(projectId, location, connectionId, repoCloneUri);
|
|
79
|
-
utils.logSuccess(`Successfully linked GitHub repository at remote URI`);
|
|
80
|
-
utils.logSuccess(`\t${repo.cloneUri}\n`);
|
|
81
79
|
return repo;
|
|
82
80
|
}
|
|
83
81
|
exports.linkGitHubRepository = linkGitHubRepository;
|
|
@@ -115,18 +113,18 @@ async function getOrCreateOauthConnection(projectId, location) {
|
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
while (conn.installationState.stage === "PENDING_USER_OAUTH") {
|
|
118
|
-
utils.logBullet("
|
|
119
|
-
utils.logBullet("Sign in to GitHub and authorize Firebase GitHub app:");
|
|
116
|
+
utils.logBullet("Please authorize the Firebase GitHub app by visiting this url:");
|
|
120
117
|
const { url, cleanup } = await utils.openInBrowserPopup(conn.installationState.actionUri, "Authorize the GitHub app");
|
|
121
118
|
utils.logBullet(`\t${url}`);
|
|
122
119
|
await (0, prompt_1.promptOnce)({
|
|
123
120
|
type: "input",
|
|
124
|
-
message: "Press Enter once you have authorized the
|
|
121
|
+
message: "Press Enter once you have authorized the GitHub App.",
|
|
125
122
|
});
|
|
126
123
|
cleanup();
|
|
127
124
|
const { projectId, location, id } = parseConnectionName(conn.name);
|
|
128
125
|
conn = await devConnect.getConnection(projectId, location, id);
|
|
129
126
|
}
|
|
127
|
+
utils.logSuccess("Connected with GitHub successfully\n");
|
|
130
128
|
return conn;
|
|
131
129
|
}
|
|
132
130
|
exports.getOrCreateOauthConnection = getOrCreateOauthConnection;
|
|
@@ -135,7 +133,7 @@ async function promptCloneUri(projectId, connections) {
|
|
|
135
133
|
const cloneUri = await (0, prompt_1.promptOnce)({
|
|
136
134
|
type: "autocomplete",
|
|
137
135
|
name: "cloneUri",
|
|
138
|
-
message: "Which
|
|
136
|
+
message: "Which GitHub repo do you want to deploy?",
|
|
139
137
|
source: (_, input = "") => {
|
|
140
138
|
return new Promise((resolve) => resolve([
|
|
141
139
|
new inquirer.Separator(),
|
|
@@ -192,7 +190,7 @@ async function ensureSecretManagerAdminGrant(projectId) {
|
|
|
192
190
|
throw e;
|
|
193
191
|
}
|
|
194
192
|
}
|
|
195
|
-
utils.logSuccess("Successfully granted the required role to the Developer Connect Service Agent
|
|
193
|
+
utils.logSuccess("Successfully granted the required role to the Developer Connect Service Agent!\n");
|
|
196
194
|
}
|
|
197
195
|
exports.ensureSecretManagerAdminGrant = ensureSecretManagerAdminGrant;
|
|
198
196
|
async function createConnection(projectId, location, connectionId, githubConfig) {
|
package/lib/apphosting/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getBackendForAmbiguousLocation = exports.promptLocation = exports.deleteBackendAndPoll = exports.orchestrateRollout = exports.setDefaultTrafficPolicy = exports.createBackend = exports.ensureAppHostingComputeServiceAccount = exports.doSetup = void 0;
|
|
4
|
-
const
|
|
4
|
+
const clc = require("colorette");
|
|
5
5
|
const poller = require("../operation-poller");
|
|
6
6
|
const apphosting = require("../gcp/apphosting");
|
|
7
7
|
const githubConnections = require("./githubConnections");
|
|
@@ -17,6 +17,7 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
|
17
17
|
const deploymentTool = require("../deploymentTool");
|
|
18
18
|
const app_1 = require("./app");
|
|
19
19
|
const ora = require("ora");
|
|
20
|
+
const node_fetch_1 = require("node-fetch");
|
|
20
21
|
const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
|
|
21
22
|
const apphostingPollerOptions = {
|
|
22
23
|
apiOrigin: (0, api_1.apphostingOrigin)(),
|
|
@@ -24,7 +25,30 @@ const apphostingPollerOptions = {
|
|
|
24
25
|
masterTimeout: 25 * 60 * 1000,
|
|
25
26
|
maxBackoff: 10000,
|
|
26
27
|
};
|
|
27
|
-
async function
|
|
28
|
+
async function tlsReady(url) {
|
|
29
|
+
var _a;
|
|
30
|
+
try {
|
|
31
|
+
await (0, node_fetch_1.default)(url);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const maybeNodeError = err;
|
|
36
|
+
if (/HANDSHAKE_FAILURE/.test((_a = maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.cause) === null || _a === void 0 ? void 0 : _a.code)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function awaitTlsReady(url) {
|
|
43
|
+
let ready;
|
|
44
|
+
do {
|
|
45
|
+
ready = await tlsReady(url);
|
|
46
|
+
if (!ready) {
|
|
47
|
+
await (0, utils_1.sleep)(1000);
|
|
48
|
+
}
|
|
49
|
+
} while (!ready);
|
|
50
|
+
}
|
|
51
|
+
async function doSetup(projectId, webAppName, location, serviceAccount) {
|
|
28
52
|
await Promise.all([
|
|
29
53
|
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.developerConnectOrigin)(), "apphosting", true),
|
|
30
54
|
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudbuildOrigin)(), "apphosting", true),
|
|
@@ -33,7 +57,6 @@ async function doSetup(projectId, webAppName, location, serviceAccount, withClou
|
|
|
33
57
|
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.artifactRegistryDomain)(), "apphosting", true),
|
|
34
58
|
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.iamOrigin)(), "apphosting", true),
|
|
35
59
|
]);
|
|
36
|
-
(0, utils_1.logBullet)("First we need a few details to create your backend.\n");
|
|
37
60
|
await ensureAppHostingComputeServiceAccount(projectId, serviceAccount);
|
|
38
61
|
const allowedLocations = (await apphosting.listLocations(projectId)).map((loc) => loc.locationId);
|
|
39
62
|
if (location) {
|
|
@@ -43,38 +66,35 @@ async function doSetup(projectId, webAppName, location, serviceAccount, withClou
|
|
|
43
66
|
}
|
|
44
67
|
location =
|
|
45
68
|
location || (await promptLocation(projectId, "Select a location to host your backend:\n"));
|
|
46
|
-
|
|
47
|
-
const backendId = await promptNewBackendId(projectId, location, {
|
|
48
|
-
name: "backendId",
|
|
49
|
-
type: "input",
|
|
50
|
-
default: "my-web-app",
|
|
51
|
-
message: "Create a name for your backend [1-30 characters]",
|
|
52
|
-
});
|
|
53
|
-
const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName, backendId);
|
|
54
|
-
if (webApp) {
|
|
55
|
-
(0, utils_1.logSuccess)(`Firebase web app set to ${webApp.name}.\n`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
(0, utils_1.logWarning)(`Firebase web app not set`);
|
|
59
|
-
}
|
|
60
|
-
const gitRepositoryConnection = withCloudBuildRepos
|
|
61
|
-
? await repo.linkGitHubRepository(projectId, location)
|
|
62
|
-
: await githubConnections.linkGitHubRepository(projectId, location);
|
|
69
|
+
const gitRepositoryConnection = await githubConnections.linkGitHubRepository(projectId, location);
|
|
63
70
|
const rootDir = await (0, prompt_1.promptOnce)({
|
|
64
71
|
name: "rootDir",
|
|
65
72
|
type: "input",
|
|
66
73
|
default: "/",
|
|
67
74
|
message: "Specify your app's root directory relative to your repository",
|
|
68
75
|
});
|
|
69
|
-
const createBackendSpinner = ora("Creating your new backend...").start();
|
|
70
|
-
const backend = await createBackend(projectId, location, backendId, gitRepositoryConnection, serviceAccount, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
|
|
71
|
-
createBackendSpinner.succeed(`Successfully created backend:\n\t${backend.name}\n`);
|
|
72
76
|
const branch = await (0, prompt_1.promptOnce)({
|
|
73
77
|
name: "branch",
|
|
74
78
|
type: "input",
|
|
75
79
|
default: "main",
|
|
76
80
|
message: "Pick a branch for continuous deployment",
|
|
77
81
|
});
|
|
82
|
+
(0, utils_1.logSuccess)(`Repo linked successfully!\n`);
|
|
83
|
+
(0, utils_1.logBullet)(`${clc.yellow("===")} Set up your backend`);
|
|
84
|
+
const backendId = await promptNewBackendId(projectId, location, {
|
|
85
|
+
name: "backendId",
|
|
86
|
+
type: "input",
|
|
87
|
+
default: "my-web-app",
|
|
88
|
+
message: "Provide a name for your backend [1-30 characters]",
|
|
89
|
+
});
|
|
90
|
+
(0, utils_1.logSuccess)(`Name set to ${backendId}\n`);
|
|
91
|
+
const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName, backendId);
|
|
92
|
+
if (!webApp) {
|
|
93
|
+
(0, utils_1.logWarning)(`Firebase web app not set`);
|
|
94
|
+
}
|
|
95
|
+
const createBackendSpinner = ora("Creating your new backend...").start();
|
|
96
|
+
const backend = await createBackend(projectId, location, backendId, gitRepositoryConnection, serviceAccount, webApp === null || webApp === void 0 ? void 0 : webApp.id, rootDir);
|
|
97
|
+
createBackendSpinner.succeed(`Successfully created backend!\n\t${backend.name}\n`);
|
|
78
98
|
await setDefaultTrafficPolicy(projectId, location, backendId, branch);
|
|
79
99
|
const confirmRollout = await (0, prompt_1.promptOnce)({
|
|
80
100
|
type: "confirm",
|
|
@@ -86,8 +106,9 @@ async function doSetup(projectId, webAppName, location, serviceAccount, withClou
|
|
|
86
106
|
(0, utils_1.logSuccess)(`Your backend will be deployed at:\n\thttps://${backend.uri}`);
|
|
87
107
|
return;
|
|
88
108
|
}
|
|
109
|
+
const url = `https://${backend.uri}`;
|
|
89
110
|
(0, utils_1.logBullet)(`You may also track this rollout at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
|
|
90
|
-
const createRolloutSpinner = ora("Starting a new rollout
|
|
111
|
+
const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
|
|
91
112
|
await orchestrateRollout(projectId, location, backendId, {
|
|
92
113
|
source: {
|
|
93
114
|
codebase: {
|
|
@@ -95,7 +116,13 @@ async function doSetup(projectId, webAppName, location, serviceAccount, withClou
|
|
|
95
116
|
},
|
|
96
117
|
},
|
|
97
118
|
});
|
|
98
|
-
createRolloutSpinner.succeed(
|
|
119
|
+
createRolloutSpinner.succeed("Rollout complete");
|
|
120
|
+
if (!(await tlsReady(url))) {
|
|
121
|
+
const tlsSpinner = ora("Finalizing your backend's TLS certificate; this may take a few minutes.").start();
|
|
122
|
+
await awaitTlsReady(url);
|
|
123
|
+
tlsSpinner.succeed("TLS certificate ready");
|
|
124
|
+
}
|
|
125
|
+
(0, utils_1.logSuccess)(`Your backend is now deployed at:\n\thttps://${backend.uri}`);
|
|
99
126
|
}
|
|
100
127
|
exports.doSetup = doSetup;
|
|
101
128
|
async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount) {
|
|
@@ -205,7 +232,7 @@ async function orchestrateRollout(projectId, location, backendId, buildInput) {
|
|
|
205
232
|
if (tries >= 5) {
|
|
206
233
|
throw err;
|
|
207
234
|
}
|
|
208
|
-
await
|
|
235
|
+
await (0, utils_1.sleep)(1000);
|
|
209
236
|
}
|
|
210
237
|
else {
|
|
211
238
|
throw err;
|
|
@@ -236,13 +263,15 @@ async function promptLocation(projectId, prompt = "Please select a location:") {
|
|
|
236
263
|
if (allowedLocations.length === 1) {
|
|
237
264
|
return allowedLocations[0];
|
|
238
265
|
}
|
|
239
|
-
|
|
266
|
+
const location = (await (0, prompt_1.promptOnce)({
|
|
240
267
|
name: "location",
|
|
241
268
|
type: "list",
|
|
242
269
|
default: constants_1.DEFAULT_LOCATION,
|
|
243
270
|
message: prompt,
|
|
244
271
|
choices: allowedLocations,
|
|
245
272
|
}));
|
|
273
|
+
(0, utils_1.logSuccess)(`Location set to ${location}.\n`);
|
|
274
|
+
return location;
|
|
246
275
|
}
|
|
247
276
|
exports.promptLocation = promptLocation;
|
|
248
277
|
async function getBackendForAmbiguousLocation(projectId, backendId, locationDisambugationPrompt) {
|
package/lib/apphosting/repo.js
CHANGED
|
@@ -52,7 +52,7 @@ function generateConnectionId() {
|
|
|
52
52
|
const ADD_CONN_CHOICE = "@ADD_CONN";
|
|
53
53
|
async function linkGitHubRepository(projectId, location) {
|
|
54
54
|
var _a, _b;
|
|
55
|
-
utils.logBullet(clc.bold(`${clc.yellow("===")}
|
|
55
|
+
utils.logBullet(clc.bold(`${clc.yellow("===")} Import a GitHub repository`));
|
|
56
56
|
const oauthConn = await getOrCreateOauthConnection(projectId, location);
|
|
57
57
|
const existingConns = await listAppHostingConnections(projectId);
|
|
58
58
|
if (existingConns.length === 0) {
|
|
@@ -132,7 +132,7 @@ async function promptRepositoryUri(projectId, connections) {
|
|
|
132
132
|
const remoteUri = await (0, prompt_1.promptOnce)({
|
|
133
133
|
type: "autocomplete",
|
|
134
134
|
name: "remoteUri",
|
|
135
|
-
message: "Which
|
|
135
|
+
message: "Which GitHub repo do you want to deploy?",
|
|
136
136
|
source: (_, input = "") => {
|
|
137
137
|
return new Promise((resolve) => resolve([
|
|
138
138
|
new inquirer.Separator(),
|
|
@@ -6,19 +6,20 @@ const projectUtils_1 = require("../projectUtils");
|
|
|
6
6
|
const requireInteractive_1 = require("../requireInteractive");
|
|
7
7
|
const apphosting_1 = require("../apphosting");
|
|
8
8
|
const apphosting_2 = require("../gcp/apphosting");
|
|
9
|
+
const firedata_1 = require("../gcp/firedata");
|
|
10
|
+
const requireTosAcceptance_1 = require("../requireTosAcceptance");
|
|
9
11
|
exports.command = new command_1.Command("apphosting:backends:create")
|
|
10
12
|
.description("create a Firebase App Hosting backend")
|
|
11
|
-
.option("-a, --app <
|
|
13
|
+
.option("-a, --app <webAppId>", "specify an existing Firebase web app's ID to associate your App Hosting backend with")
|
|
12
14
|
.option("-l, --location <location>", "specify the location of the backend", "")
|
|
13
15
|
.option("-s, --service-account <serviceAccount>", "specify the service account used to run the server", "")
|
|
14
|
-
.option("-w, --with-cloud-build-repos", "use Cloud Build Repositories flow instead of the Developer Connect flow")
|
|
15
16
|
.before(apphosting_2.ensureApiEnabled)
|
|
16
17
|
.before(requireInteractive_1.default)
|
|
18
|
+
.before((0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.APPHOSTING_TOS_ID))
|
|
17
19
|
.action(async (options) => {
|
|
18
20
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
19
|
-
const
|
|
21
|
+
const webAppId = options.app;
|
|
20
22
|
const location = options.location;
|
|
21
23
|
const serviceAccount = options.serviceAccount;
|
|
22
|
-
|
|
23
|
-
await (0, apphosting_1.doSetup)(projectId, webApp, location, serviceAccount, withCloudBuildRepos);
|
|
24
|
+
await (0, apphosting_1.doSetup)(projectId, webAppId, location, serviceAccount);
|
|
24
25
|
});
|
|
@@ -25,11 +25,12 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
|
|
|
25
25
|
configDir,
|
|
26
26
|
auto_download: true,
|
|
27
27
|
rc: options.rc,
|
|
28
|
+
locationId: service.location,
|
|
28
29
|
};
|
|
29
30
|
const dataconnectEmulator = new dataconnectEmulator_1.DataConnectEmulator(args);
|
|
30
31
|
for (const conn of serviceInfo.connectorInfo) {
|
|
31
32
|
const output = await dataconnectEmulator.generate(conn.connectorYaml.connectorId);
|
|
32
|
-
|
|
33
|
+
logger_1.logger.info(output);
|
|
33
34
|
logger_1.logger.info(`Generated SDKs for ${conn.connectorYaml.connectorId}`);
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -9,7 +9,7 @@ const logger_1 = require("../logger");
|
|
|
9
9
|
const requirePermissions_1 = require("../requirePermissions");
|
|
10
10
|
const ensureApis_1 = require("../dataconnect/ensureApis");
|
|
11
11
|
const Table = require("cli-table");
|
|
12
|
-
exports.command = new command_1.Command("dataconnect:list")
|
|
12
|
+
exports.command = new command_1.Command("dataconnect:services:list")
|
|
13
13
|
.description("list all deployed services in your Firebase project")
|
|
14
14
|
.before(requirePermissions_1.requirePermissions, [
|
|
15
15
|
"dataconnect.services.list",
|
|
@@ -17,7 +17,7 @@ exports.command = new command_1.Command("dataconnect:list")
|
|
|
17
17
|
"dataconnect.connectors.list",
|
|
18
18
|
])
|
|
19
19
|
.action(async (options) => {
|
|
20
|
-
var _a, _b, _c, _d, _e;
|
|
20
|
+
var _a, _b, _c, _d, _e, _f;
|
|
21
21
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
22
22
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
23
23
|
const services = await client.listAllServices(projectId);
|
|
@@ -34,14 +34,31 @@ exports.command = new command_1.Command("dataconnect:list")
|
|
|
34
34
|
});
|
|
35
35
|
const jsonOutput = { services: [] };
|
|
36
36
|
for (const service of services) {
|
|
37
|
-
|
|
37
|
+
let schema = {
|
|
38
|
+
name: "",
|
|
39
|
+
primaryDatasource: {},
|
|
40
|
+
source: { files: [] },
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
schema = await client.getSchema(service.name);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
logger_1.logger.debug(`Error fetching schema: ${err}`);
|
|
47
|
+
}
|
|
38
48
|
const connectors = await client.listConnectors(service.name);
|
|
39
49
|
const serviceName = names.parseServiceName(service.name);
|
|
40
50
|
const instanceName = (_b = (_a = schema === null || schema === void 0 ? void 0 : schema.primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance) !== null && _b !== void 0 ? _b : "";
|
|
41
51
|
const instanceId = instanceName.split("/").pop();
|
|
42
52
|
const dbId = (_d = (_c = schema === null || schema === void 0 ? void 0 : schema.primaryDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database) !== null && _d !== void 0 ? _d : "";
|
|
43
|
-
const dbName = `CloudSQL Instance: ${instanceId}
|
|
44
|
-
table.push([
|
|
53
|
+
const dbName = `CloudSQL Instance: ${instanceId}\nDatabase:${dbId}`;
|
|
54
|
+
table.push([
|
|
55
|
+
serviceName.serviceId,
|
|
56
|
+
serviceName.location,
|
|
57
|
+
dbName,
|
|
58
|
+
(_e = schema === null || schema === void 0 ? void 0 : schema.updateTime) !== null && _e !== void 0 ? _e : "",
|
|
59
|
+
"",
|
|
60
|
+
"",
|
|
61
|
+
]);
|
|
45
62
|
const serviceJson = {
|
|
46
63
|
serviceId: serviceName.serviceId,
|
|
47
64
|
location: serviceName.location,
|
|
@@ -54,7 +71,7 @@ exports.command = new command_1.Command("dataconnect:list")
|
|
|
54
71
|
table.push(["", "", "", "", connectorName.connectorId, conn.updateTime]);
|
|
55
72
|
serviceJson.connectors.push({
|
|
56
73
|
connectorId: connectorName.connectorId,
|
|
57
|
-
connectorLastUpdated: (
|
|
74
|
+
connectorLastUpdated: (_f = conn.updateTime) !== null && _f !== void 0 ? _f : "",
|
|
58
75
|
});
|
|
59
76
|
}
|
|
60
77
|
jsonOutput.services.push(serviceJson);
|
|
@@ -4,12 +4,12 @@ exports.command = void 0;
|
|
|
4
4
|
const command_1 = require("../command");
|
|
5
5
|
const projectUtils_1 = require("../projectUtils");
|
|
6
6
|
const fileUtils_1 = require("../dataconnect/fileUtils");
|
|
7
|
-
const logger_1 = require("../logger");
|
|
8
7
|
const error_1 = require("../error");
|
|
9
8
|
const schemaMigration_1 = require("../dataconnect/schemaMigration");
|
|
10
9
|
const requireAuth_1 = require("../requireAuth");
|
|
11
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
12
11
|
const ensureApis_1 = require("../dataconnect/ensureApis");
|
|
12
|
+
const utils_1 = require("../utils");
|
|
13
13
|
exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
|
|
14
14
|
.description("migrates your CloudSQL database's schema to match your local DataConnect schema")
|
|
15
15
|
.before(requirePermissions_1.requirePermissions, [
|
|
@@ -30,12 +30,17 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
|
|
|
30
30
|
if (!instanceId) {
|
|
31
31
|
throw new error_1.FirebaseError("dataconnect.yaml is missing field schema.datasource.postgresql.cloudsql.instanceId");
|
|
32
32
|
}
|
|
33
|
-
const diffs = await (0, schemaMigration_1.migrateSchema)(
|
|
33
|
+
const diffs = await (0, schemaMigration_1.migrateSchema)({
|
|
34
|
+
options,
|
|
35
|
+
schema: serviceInfo.schema,
|
|
36
|
+
allowNonInteractiveMigration: true,
|
|
37
|
+
validateOnly: true,
|
|
38
|
+
});
|
|
34
39
|
if (diffs.length) {
|
|
35
|
-
|
|
40
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema sucessfully migrated! Run 'firebase deploy' to deploy your new schema to your Data Connect service.`);
|
|
36
41
|
}
|
|
37
42
|
else {
|
|
38
|
-
|
|
43
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", "Database schema is already up to date!");
|
|
39
44
|
}
|
|
40
45
|
return { projectId, serviceId, diffs };
|
|
41
46
|
});
|
package/lib/commands/index.js
CHANGED
|
@@ -203,7 +203,8 @@ function load(client) {
|
|
|
203
203
|
if (experiments.isEnabled("dataconnect")) {
|
|
204
204
|
client.dataconnect = {};
|
|
205
205
|
client.setup.emulators.dataconnect = loadCommand("setup-emulators-dataconnect");
|
|
206
|
-
client.dataconnect.
|
|
206
|
+
client.dataconnect.services = {};
|
|
207
|
+
client.dataconnect.services.list = loadCommand("dataconnect-services-list");
|
|
207
208
|
client.dataconnect.sql = {};
|
|
208
209
|
client.dataconnect.sql.diff = loadCommand("dataconnect-sql-diff");
|
|
209
210
|
client.dataconnect.sql.migrate = loadCommand("dataconnect-sql-migrate");
|
package/lib/commands/init.js
CHANGED
|
@@ -69,10 +69,10 @@ const choices = [
|
|
|
69
69
|
checked: false,
|
|
70
70
|
},
|
|
71
71
|
];
|
|
72
|
-
if ((0, experiments_1.isEnabled)("
|
|
72
|
+
if ((0, experiments_1.isEnabled)("genkit")) {
|
|
73
73
|
choices.push({
|
|
74
|
-
value: "
|
|
75
|
-
name: "
|
|
74
|
+
value: "genkit",
|
|
75
|
+
name: "Genkit: Setup a new Genkit project with Firebase",
|
|
76
76
|
checked: false,
|
|
77
77
|
});
|
|
78
78
|
}
|
|
@@ -21,7 +21,7 @@ exports.listLocations = listLocations;
|
|
|
21
21
|
async function listAllServices(projectId) {
|
|
22
22
|
const locations = await listLocations(projectId);
|
|
23
23
|
let services = [];
|
|
24
|
-
|
|
24
|
+
await Promise.all(locations.map(async (l) => {
|
|
25
25
|
try {
|
|
26
26
|
const locationServices = await listServices(projectId, l);
|
|
27
27
|
services = services.concat(locationServices);
|
|
@@ -29,7 +29,7 @@ async function listAllServices(projectId) {
|
|
|
29
29
|
catch (err) {
|
|
30
30
|
logger_1.logger.debug(`Unable to listServices in ${l}: ${err}`);
|
|
31
31
|
}
|
|
32
|
-
}
|
|
32
|
+
}));
|
|
33
33
|
return services;
|
|
34
34
|
}
|
|
35
35
|
exports.listAllServices = listAllServices;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getInvalidConnectors = exports.getIncompatibleSchemaError = void 0;
|
|
4
|
+
const INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING = "IncompatibleSqlSchemaError";
|
|
5
|
+
const PRECONDITION_ERROR_TYPESTRING = "type.googleapis.com/google.rpc.PreconditionFailure";
|
|
6
|
+
const INCOMPATIBLE_CONNECTOR_TYPE = "INCOMPATIBLE_CONNECTOR";
|
|
7
|
+
function getIncompatibleSchemaError(err) {
|
|
8
|
+
var _a, _b;
|
|
9
|
+
const original = ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) || err.orignal;
|
|
10
|
+
if (!original) {
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
const details = original.details;
|
|
14
|
+
const incompatibles = details.filter((d) => { var _a; return (_a = d["@type"]) === null || _a === void 0 ? void 0 : _a.includes(INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING); });
|
|
15
|
+
return incompatibles[0];
|
|
16
|
+
}
|
|
17
|
+
exports.getIncompatibleSchemaError = getIncompatibleSchemaError;
|
|
18
|
+
function getInvalidConnectors(err) {
|
|
19
|
+
var _a, _b, _c, _d;
|
|
20
|
+
const invalidConns = [];
|
|
21
|
+
const original = ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body) === null || _b === void 0 ? void 0 : _b.error) || (err === null || err === void 0 ? void 0 : err.orignal);
|
|
22
|
+
const details = original === null || original === void 0 ? void 0 : original.details;
|
|
23
|
+
const preconditionErrs = details === null || details === void 0 ? void 0 : details.filter((d) => { var _a; return (_a = d["@type"]) === null || _a === void 0 ? void 0 : _a.includes(PRECONDITION_ERROR_TYPESTRING); });
|
|
24
|
+
for (const preconditionErr of preconditionErrs) {
|
|
25
|
+
const incompatibleConnViolation = (_c = preconditionErr === null || preconditionErr === void 0 ? void 0 : preconditionErr.violations) === null || _c === void 0 ? void 0 : _c.filter((v) => v.type === INCOMPATIBLE_CONNECTOR_TYPE);
|
|
26
|
+
const newConns = (_d = incompatibleConnViolation === null || incompatibleConnViolation === void 0 ? void 0 : incompatibleConnViolation.map((i) => i.subject)) !== null && _d !== void 0 ? _d : [];
|
|
27
|
+
invalidConns.push(...newConns);
|
|
28
|
+
}
|
|
29
|
+
return invalidConns;
|
|
30
|
+
}
|
|
31
|
+
exports.getInvalidConnectors = getInvalidConnectors;
|
|
@@ -51,17 +51,20 @@ function validateConnectorYaml(unvalidated) {
|
|
|
51
51
|
}
|
|
52
52
|
async function readGQLFiles(sourceDir) {
|
|
53
53
|
const files = await fs.readdir(sourceDir);
|
|
54
|
-
return files
|
|
54
|
+
return files
|
|
55
|
+
.filter((f) => f.endsWith(".gql") || f.endsWith(".graphql"))
|
|
56
|
+
.map((f) => toFile(sourceDir, f));
|
|
55
57
|
}
|
|
56
58
|
exports.readGQLFiles = readGQLFiles;
|
|
57
|
-
function toFile(
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
function toFile(sourceDir, relPath) {
|
|
60
|
+
const fullPath = path.join(sourceDir, relPath);
|
|
61
|
+
if (!fs.existsSync(fullPath)) {
|
|
62
|
+
throw new error_1.FirebaseError(`file ${fullPath} not found`);
|
|
60
63
|
}
|
|
61
|
-
const
|
|
64
|
+
const content = fs.readFileSync(fullPath).toString();
|
|
62
65
|
return {
|
|
63
|
-
path:
|
|
64
|
-
content
|
|
66
|
+
path: relPath,
|
|
67
|
+
content,
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
async function pickService(projectId, config, serviceId) {
|
|
@@ -4,7 +4,7 @@ exports.printFreeTrialUnavailable = exports.checkForFreeTrialInstance = exports.
|
|
|
4
4
|
const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
|
|
5
5
|
const utils = require("../utils");
|
|
6
6
|
function freeTrialTermsLink() {
|
|
7
|
-
return "";
|
|
7
|
+
return "https://firebase.google.com/pricing";
|
|
8
8
|
}
|
|
9
9
|
exports.freeTrialTermsLink = freeTrialTermsLink;
|
|
10
10
|
async function checkForFreeTrialInstance(projectId) {
|
|
@@ -5,8 +5,8 @@ function prettify(err) {
|
|
|
5
5
|
var _a;
|
|
6
6
|
const message = err.message;
|
|
7
7
|
let header = (_a = err.extensions.file) !== null && _a !== void 0 ? _a : "";
|
|
8
|
-
|
|
9
|
-
header +=
|
|
8
|
+
if (err.locations) {
|
|
9
|
+
header += `:${err.locations[0].line}`;
|
|
10
10
|
}
|
|
11
11
|
return header.length ? `${header}: ${message}` : message;
|
|
12
12
|
}
|