firebase-tools 13.8.0 → 13.8.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.
- package/lib/apphosting/app.js +35 -56
- package/lib/apphosting/index.js +37 -9
- package/lib/commands/apphosting-backends-create.js +4 -3
- package/lib/commands/dataconnect-sdk-generate.js +2 -1
- package/lib/commands/dataconnect-sql-migrate.js +6 -1
- package/lib/dataconnect/provisionCloudSql.js +1 -18
- package/lib/dataconnect/schemaMigration.js +70 -36
- package/lib/deploy/dataconnect/release.js +7 -9
- package/lib/deploy/functions/release/fabricator.js +7 -2
- package/lib/emulator/controller.js +1 -0
- package/lib/emulator/dataconnectEmulator.js +3 -1
- package/lib/emulator/downloadableEmulators.js +6 -6
- package/lib/emulator/portUtils.js +1 -2
- package/lib/emulator/registry.js +1 -1
- package/lib/experiments.js +7 -5
- package/lib/frameworks/next/index.js +3 -1
- package/lib/frameworks/next/utils.js +1 -1
- package/lib/gcp/cloudsql/connect.js +2 -1
- package/lib/gcp/firedata.js +26 -0
- package/lib/hosting/interactive.js +4 -0
- package/lib/init/features/dataconnect/index.js +52 -16
- package/lib/operation-poller.js +1 -1
- package/lib/requireTosAcceptance.js +21 -0
- package/lib/utils.js +6 -2
- package/package.json +1 -1
- package/templates/init/dataconnect/mutations.gql +0 -1
- package/templates/init/dataconnect/queries.gql +0 -1
- package/templates/init/dataconnect/schema.gql +0 -1
package/lib/apphosting/app.js
CHANGED
|
@@ -1,88 +1,67 @@
|
|
|
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
15
|
async function getOrCreateWebApp(projectId, firebaseWebAppName, backendId) {
|
|
18
16
|
const webAppsInProject = await (0, apps_1.listFirebaseApps)(projectId, apps_1.AppPlatform.WEB);
|
|
19
|
-
|
|
20
|
-
const { displayName, appId } = await createFirebaseWebApp(projectId, {
|
|
21
|
-
displayName: backendId,
|
|
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
|
-
}));
|
|
17
|
+
const existingUserProjectWebApps = firebaseAppsToMap(webAppsInProject);
|
|
32
18
|
if (firebaseWebAppName) {
|
|
33
19
|
if (existingUserProjectWebApps.get(firebaseWebAppName) === undefined) {
|
|
34
20
|
throw new error_1.FirebaseError(`The web app '${firebaseWebAppName}' does not exist in project ${projectId}`);
|
|
35
21
|
}
|
|
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;
|
|
22
|
+
return {
|
|
23
|
+
name: firebaseWebAppName,
|
|
24
|
+
id: existingUserProjectWebApps.get(firebaseWebAppName),
|
|
25
|
+
};
|
|
70
26
|
}
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
async function createFirebaseWebApp(projectId, options) {
|
|
27
|
+
const webAppName = await generateWebAppName(projectId, backendId);
|
|
74
28
|
try {
|
|
75
|
-
|
|
29
|
+
const app = await (0, apps_1.createWebApp)(projectId, { displayName: webAppName });
|
|
30
|
+
return { name: app.displayName, id: app.appId };
|
|
76
31
|
}
|
|
77
32
|
catch (e) {
|
|
78
33
|
if (isQuotaError(e)) {
|
|
79
|
-
|
|
34
|
+
(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. ");
|
|
35
|
+
return;
|
|
80
36
|
}
|
|
81
37
|
throw new error_1.FirebaseError("Unable to create a Firebase web app", {
|
|
82
38
|
original: e instanceof Error ? e : undefined,
|
|
83
39
|
});
|
|
84
40
|
}
|
|
85
41
|
}
|
|
42
|
+
async function generateWebAppName(projectId, backendId) {
|
|
43
|
+
const webAppsInProject = await (0, apps_1.listFirebaseApps)(projectId, apps_1.AppPlatform.WEB);
|
|
44
|
+
const appsMap = firebaseAppsToMap(webAppsInProject);
|
|
45
|
+
if (!appsMap.get(backendId)) {
|
|
46
|
+
return backendId;
|
|
47
|
+
}
|
|
48
|
+
let uniqueId = 1;
|
|
49
|
+
let webAppName = `${backendId}_${uniqueId}`;
|
|
50
|
+
while (appsMap.get(webAppName)) {
|
|
51
|
+
uniqueId += 1;
|
|
52
|
+
webAppName = `${backendId}_${uniqueId}`;
|
|
53
|
+
}
|
|
54
|
+
return webAppName;
|
|
55
|
+
}
|
|
56
|
+
function firebaseAppsToMap(apps) {
|
|
57
|
+
return new Map(apps.map((obj) => {
|
|
58
|
+
var _a;
|
|
59
|
+
return [
|
|
60
|
+
(_a = obj.displayName) !== null && _a !== void 0 ? _a : obj.appId,
|
|
61
|
+
obj.appId,
|
|
62
|
+
];
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
86
65
|
function isQuotaError(error) {
|
|
87
66
|
var _a, _b, _c, _d, _e;
|
|
88
67
|
const original = error.original;
|
package/lib/apphosting/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
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 repo = require("./repo");
|
|
5
4
|
const poller = require("../operation-poller");
|
|
6
5
|
const apphosting = require("../gcp/apphosting");
|
|
7
6
|
const githubConnections = require("./githubConnections");
|
|
@@ -17,6 +16,7 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
|
17
16
|
const deploymentTool = require("../deploymentTool");
|
|
18
17
|
const app_1 = require("./app");
|
|
19
18
|
const ora = require("ora");
|
|
19
|
+
const node_fetch_1 = require("node-fetch");
|
|
20
20
|
const DEFAULT_COMPUTE_SERVICE_ACCOUNT_NAME = "firebase-app-hosting-compute";
|
|
21
21
|
const apphostingPollerOptions = {
|
|
22
22
|
apiOrigin: (0, api_1.apphostingOrigin)(),
|
|
@@ -24,7 +24,30 @@ const apphostingPollerOptions = {
|
|
|
24
24
|
masterTimeout: 25 * 60 * 1000,
|
|
25
25
|
maxBackoff: 10000,
|
|
26
26
|
};
|
|
27
|
-
async function
|
|
27
|
+
async function tlsReady(url) {
|
|
28
|
+
var _a;
|
|
29
|
+
try {
|
|
30
|
+
await (0, node_fetch_1.default)(url);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const maybeNodeError = err;
|
|
35
|
+
if (/HANDSHAKE_FAILURE/.test((_a = maybeNodeError === null || maybeNodeError === void 0 ? void 0 : maybeNodeError.cause) === null || _a === void 0 ? void 0 : _a.code)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function awaitTlsReady(url) {
|
|
42
|
+
let ready;
|
|
43
|
+
do {
|
|
44
|
+
ready = await tlsReady(url);
|
|
45
|
+
if (!ready) {
|
|
46
|
+
await (0, utils_1.sleep)(1000);
|
|
47
|
+
}
|
|
48
|
+
} while (!ready);
|
|
49
|
+
}
|
|
50
|
+
async function doSetup(projectId, webAppName, location, serviceAccount) {
|
|
28
51
|
await Promise.all([
|
|
29
52
|
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.developerConnectOrigin)(), "apphosting", true),
|
|
30
53
|
(0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.cloudbuildOrigin)(), "apphosting", true),
|
|
@@ -52,14 +75,12 @@ async function doSetup(projectId, webAppName, location, serviceAccount, withClou
|
|
|
52
75
|
});
|
|
53
76
|
const webApp = await app_1.webApps.getOrCreateWebApp(projectId, webAppName, backendId);
|
|
54
77
|
if (webApp) {
|
|
55
|
-
(0, utils_1.logSuccess)(`Firebase web app
|
|
78
|
+
(0, utils_1.logSuccess)(`Created a new Firebase web app named "${webApp.name}"`);
|
|
56
79
|
}
|
|
57
80
|
else {
|
|
58
81
|
(0, utils_1.logWarning)(`Firebase web app not set`);
|
|
59
82
|
}
|
|
60
|
-
const gitRepositoryConnection =
|
|
61
|
-
? await repo.linkGitHubRepository(projectId, location)
|
|
62
|
-
: await githubConnections.linkGitHubRepository(projectId, location);
|
|
83
|
+
const gitRepositoryConnection = await githubConnections.linkGitHubRepository(projectId, location);
|
|
63
84
|
const rootDir = await (0, prompt_1.promptOnce)({
|
|
64
85
|
name: "rootDir",
|
|
65
86
|
type: "input",
|
|
@@ -86,8 +107,9 @@ async function doSetup(projectId, webAppName, location, serviceAccount, withClou
|
|
|
86
107
|
(0, utils_1.logSuccess)(`Your backend will be deployed at:\n\thttps://${backend.uri}`);
|
|
87
108
|
return;
|
|
88
109
|
}
|
|
110
|
+
const url = `https://${backend.uri}`;
|
|
89
111
|
(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
|
|
112
|
+
const createRolloutSpinner = ora("Starting a new rollout; this may take a few minutes. It's safe to exit now.").start();
|
|
91
113
|
await orchestrateRollout(projectId, location, backendId, {
|
|
92
114
|
source: {
|
|
93
115
|
codebase: {
|
|
@@ -95,7 +117,13 @@ async function doSetup(projectId, webAppName, location, serviceAccount, withClou
|
|
|
95
117
|
},
|
|
96
118
|
},
|
|
97
119
|
});
|
|
98
|
-
createRolloutSpinner.succeed(
|
|
120
|
+
createRolloutSpinner.succeed("Rollout complete");
|
|
121
|
+
if (!(await tlsReady(url))) {
|
|
122
|
+
const tlsSpinner = ora("Finalizing your backend's TLS certificate; this may take a few minutes.").start();
|
|
123
|
+
await awaitTlsReady(url);
|
|
124
|
+
tlsSpinner.succeed("TLS certificate ready");
|
|
125
|
+
}
|
|
126
|
+
(0, utils_1.logSuccess)(`Your backend is now deployed at:\n\thttps://${backend.uri}`);
|
|
99
127
|
}
|
|
100
128
|
exports.doSetup = doSetup;
|
|
101
129
|
async function ensureAppHostingComputeServiceAccount(projectId, serviceAccount) {
|
|
@@ -205,7 +233,7 @@ async function orchestrateRollout(projectId, location, backendId, buildInput) {
|
|
|
205
233
|
if (tries >= 5) {
|
|
206
234
|
throw err;
|
|
207
235
|
}
|
|
208
|
-
await
|
|
236
|
+
await (0, utils_1.sleep)(1000);
|
|
209
237
|
}
|
|
210
238
|
else {
|
|
211
239
|
throw err;
|
|
@@ -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
13
|
.option("-a, --app <webApp>", "specify an existing Firebase web app 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
21
|
const webApp = 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, webApp, 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
|
}
|
|
@@ -30,7 +30,12 @@ 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
|
logger_1.logger.info(`Schema sucessfully migrated! Run 'firebase deploy' to deploy your new schema to your Data Connect service.`);
|
|
36
41
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.checkInstanceConfig = exports.
|
|
3
|
+
exports.checkInstanceConfig = exports.provisionCloudSql = void 0;
|
|
4
4
|
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
5
|
-
const connect_1 = require("../gcp/cloudsql/connect");
|
|
6
5
|
const utils = require("../utils");
|
|
7
6
|
const checkIam_1 = require("./checkIam");
|
|
8
7
|
const GOOGLE_ML_INTEGRATION_ROLE = "roles/aiplatform.user";
|
|
@@ -55,22 +54,6 @@ async function provisionCloudSql(args) {
|
|
|
55
54
|
return connectionName;
|
|
56
55
|
}
|
|
57
56
|
exports.provisionCloudSql = provisionCloudSql;
|
|
58
|
-
exports.REQUIRED_EXTENSIONS_COMMANDS = [
|
|
59
|
-
`CREATE SCHEMA IF NOT EXISTS "public"`,
|
|
60
|
-
`CREATE EXTENSION IF NOT EXISTS "uuid-ossp" with SCHEMA public`,
|
|
61
|
-
`CREATE EXTENSION IF NOT EXISTS "vector" with SCHEMA public`,
|
|
62
|
-
`CREATE EXTENSION IF NOT EXISTS "google_ml_integration" with SCHEMA public CASCADE`,
|
|
63
|
-
];
|
|
64
|
-
async function installRequiredExtensions(projectId, instanceId, databaseId, username) {
|
|
65
|
-
await (0, connect_1.execute)(exports.REQUIRED_EXTENSIONS_COMMANDS, {
|
|
66
|
-
projectId,
|
|
67
|
-
instanceId,
|
|
68
|
-
databaseId,
|
|
69
|
-
username,
|
|
70
|
-
silent: true,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
exports.installRequiredExtensions = installRequiredExtensions;
|
|
74
57
|
function checkInstanceConfig(instance, requireGoogleMlIntegration) {
|
|
75
58
|
var _a, _b, _c, _d;
|
|
76
59
|
const settings = instance.settings;
|
|
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.migrateSchema = exports.diffSchema = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const sql_formatter_1 = require("sql-formatter");
|
|
6
|
+
const types_1 = require("./types");
|
|
6
7
|
const client_1 = require("./client");
|
|
7
8
|
const connect_1 = require("../gcp/cloudsql/connect");
|
|
8
9
|
const prompt_1 = require("../prompt");
|
|
9
10
|
const logger_1 = require("../logger");
|
|
10
11
|
const error_1 = require("../error");
|
|
11
|
-
const provisionCloudSql_1 = require("./provisionCloudSql");
|
|
12
12
|
const projectUtils_1 = require("../projectUtils");
|
|
13
13
|
const utils_1 = require("../utils");
|
|
14
14
|
const IMCOMPATIBLE_SCHEMA_ERROR_TYPESTRING = "type.googleapis.com/google.firebase.dataconnect.v1main.IncompatibleSqlSchemaError";
|
|
@@ -20,6 +20,8 @@ async function diffSchema(schema) {
|
|
|
20
20
|
throw new error_1.FirebaseError(`tried to diff schema but ${instanceName} was undefined`);
|
|
21
21
|
}
|
|
22
22
|
try {
|
|
23
|
+
const serviceName = schema.name.replace(`/schemas/${types_1.SCHEMA_ID}`, "");
|
|
24
|
+
await ensureServiceIsConnectedToCloudSql(serviceName);
|
|
23
25
|
await (0, client_1.upsertSchema)(schema, true);
|
|
24
26
|
}
|
|
25
27
|
catch (err) {
|
|
@@ -34,9 +36,9 @@ async function diffSchema(schema) {
|
|
|
34
36
|
return [];
|
|
35
37
|
}
|
|
36
38
|
exports.diffSchema = diffSchema;
|
|
37
|
-
async function migrateSchema(
|
|
39
|
+
async function migrateSchema(args) {
|
|
38
40
|
var _a, _b;
|
|
39
|
-
const
|
|
41
|
+
const { schema, validateOnly } = args;
|
|
40
42
|
const databaseId = (_a = schema.primaryDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.database;
|
|
41
43
|
if (!databaseId) {
|
|
42
44
|
throw new error_1.FirebaseError("Schema is missing primaryDatasource.postgresql?.database, cannot migrate");
|
|
@@ -45,46 +47,57 @@ async function migrateSchema(options, schema, allowNonInteractiveMigration) {
|
|
|
45
47
|
if (!instanceId) {
|
|
46
48
|
throw new error_1.FirebaseError(`tried to migrate schema but ${instanceId} was undefined`);
|
|
47
49
|
}
|
|
48
|
-
const iamUser = await (0, connect_1.setupIAMUser)(instanceId, databaseId, options);
|
|
49
50
|
try {
|
|
50
|
-
|
|
51
|
+
const serviceName = schema.name.replace(`/schemas/${types_1.SCHEMA_ID}`, "");
|
|
52
|
+
await ensureServiceIsConnectedToCloudSql(serviceName);
|
|
53
|
+
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
54
|
+
logger_1.logger.debug(`Database schema was up to date for ${instanceId}:${databaseId}`);
|
|
55
|
+
return [];
|
|
51
56
|
}
|
|
52
57
|
catch (err) {
|
|
53
58
|
const incompatible = getIncompatibleSchemaError(err);
|
|
54
|
-
if (incompatible) {
|
|
55
|
-
|
|
56
|
-
const commandsToExecute = incompatible.diffs
|
|
57
|
-
.filter((d) => {
|
|
58
|
-
switch (choice) {
|
|
59
|
-
case "all":
|
|
60
|
-
return true;
|
|
61
|
-
case "safe":
|
|
62
|
-
return !d.destructive;
|
|
63
|
-
case "none":
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
.map((d) => d.sql);
|
|
68
|
-
if (commandsToExecute.length) {
|
|
69
|
-
await (0, connect_1.execute)([
|
|
70
|
-
...provisionCloudSql_1.REQUIRED_EXTENSIONS_COMMANDS,
|
|
71
|
-
...commandsToExecute,
|
|
72
|
-
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "public" TO PUBLIC`,
|
|
73
|
-
], {
|
|
74
|
-
projectId,
|
|
75
|
-
instanceId,
|
|
76
|
-
databaseId,
|
|
77
|
-
username: iamUser,
|
|
78
|
-
});
|
|
79
|
-
return incompatible.diffs;
|
|
80
|
-
}
|
|
59
|
+
if (!incompatible) {
|
|
60
|
+
throw err;
|
|
81
61
|
}
|
|
82
|
-
|
|
62
|
+
const diffs = await handleIncompatibleSchemaError(Object.assign(Object.assign({}, args), { incompatibleSchemaError: incompatible, instanceId,
|
|
63
|
+
databaseId }));
|
|
64
|
+
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
65
|
+
return diffs;
|
|
83
66
|
}
|
|
84
|
-
logger_1.logger.debug(`Schema was up to date for ${instanceId}:${databaseId}`);
|
|
85
|
-
return [];
|
|
86
67
|
}
|
|
87
68
|
exports.migrateSchema = migrateSchema;
|
|
69
|
+
async function handleIncompatibleSchemaError(args) {
|
|
70
|
+
const { incompatibleSchemaError, options, instanceId, databaseId, allowNonInteractiveMigration } = args;
|
|
71
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
72
|
+
const iamUser = await (0, connect_1.setupIAMUser)(instanceId, databaseId, options);
|
|
73
|
+
const choice = await promptForSchemaMigration(options, databaseId, incompatibleSchemaError, allowNonInteractiveMigration);
|
|
74
|
+
const commandsToExecute = incompatibleSchemaError.diffs
|
|
75
|
+
.filter((d) => {
|
|
76
|
+
switch (choice) {
|
|
77
|
+
case "all":
|
|
78
|
+
return true;
|
|
79
|
+
case "safe":
|
|
80
|
+
return !d.destructive;
|
|
81
|
+
case "none":
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
.map((d) => d.sql);
|
|
86
|
+
if (commandsToExecute.length) {
|
|
87
|
+
await (0, connect_1.execute)([
|
|
88
|
+
`SET ROLE "${(0, connect_1.firebaseowner)(databaseId)}"`,
|
|
89
|
+
...commandsToExecute,
|
|
90
|
+
`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "public" TO PUBLIC`,
|
|
91
|
+
], {
|
|
92
|
+
projectId,
|
|
93
|
+
instanceId,
|
|
94
|
+
databaseId,
|
|
95
|
+
username: iamUser,
|
|
96
|
+
});
|
|
97
|
+
return incompatibleSchemaError.diffs;
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
88
101
|
async function promptForSchemaMigration(options, databaseName, err, allowNonInteractiveMigration) {
|
|
89
102
|
displaySchemaChanges(err);
|
|
90
103
|
if (!options.nonInteractive) {
|
|
@@ -119,6 +132,24 @@ async function promptForSchemaMigration(options, databaseName, err, allowNonInte
|
|
|
119
132
|
return "none";
|
|
120
133
|
}
|
|
121
134
|
}
|
|
135
|
+
async function ensureServiceIsConnectedToCloudSql(serviceName) {
|
|
136
|
+
let currentSchema;
|
|
137
|
+
try {
|
|
138
|
+
currentSchema = await (0, client_1.getSchema)(serviceName);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
if (err.status === 404) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
if (!currentSchema.primaryDatasource.postgresql ||
|
|
147
|
+
currentSchema.primaryDatasource.postgresql.schemaValidation === "STRICT") {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
currentSchema.primaryDatasource.postgresql.schemaValidation = "STRICT";
|
|
151
|
+
await (0, client_1.upsertSchema)(currentSchema, false);
|
|
152
|
+
}
|
|
122
153
|
function displaySchemaChanges(error) {
|
|
123
154
|
const message = "Your new schema is incompatible with the schema of your CloudSQL database. " +
|
|
124
155
|
"The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
@@ -130,7 +161,10 @@ function toString(diff) {
|
|
|
130
161
|
}
|
|
131
162
|
function getIncompatibleSchemaError(err) {
|
|
132
163
|
var _a;
|
|
133
|
-
const original = (_a = err.context) === null || _a === void 0 ? void 0 : _a.body.error;
|
|
164
|
+
const original = ((_a = err.context) === null || _a === void 0 ? void 0 : _a.body.error) || err.orignal;
|
|
165
|
+
if (!original) {
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
134
168
|
const details = original.details;
|
|
135
169
|
const incompatibles = details.filter((d) => d["@type"] === IMCOMPATIBLE_SCHEMA_ERROR_TYPESTRING);
|
|
136
170
|
return incompatibles[0];
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const utils = require("../../utils");
|
|
4
4
|
const client_1 = require("../../dataconnect/client");
|
|
5
5
|
const prompts_1 = require("../../dataconnect/prompts");
|
|
6
|
-
const error_1 = require("../../error");
|
|
7
6
|
const schemaMigration_1 = require("../../dataconnect/schemaMigration");
|
|
8
7
|
async function default_1(context, options) {
|
|
9
8
|
const serviceInfos = context.dataconnect.serviceInfos;
|
|
@@ -17,15 +16,14 @@ async function default_1(context, options) {
|
|
|
17
16
|
})
|
|
18
17
|
.map((s) => s.schema);
|
|
19
18
|
if (wantSchemas.length) {
|
|
20
|
-
utils.logLabeledBullet("dataconnect", "
|
|
19
|
+
utils.logLabeledBullet("dataconnect", "Releasing Data Connect schemas...");
|
|
21
20
|
for (const s of wantSchemas) {
|
|
22
|
-
await (0, schemaMigration_1.migrateSchema)(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
throw new error_1.FirebaseError(`Errors while updating your schemas:\n ${failedSchemas.map((f) => f.reason).join("\n")}`);
|
|
21
|
+
await (0, schemaMigration_1.migrateSchema)({
|
|
22
|
+
options,
|
|
23
|
+
schema: s,
|
|
24
|
+
allowNonInteractiveMigration: false,
|
|
25
|
+
validateOnly: false,
|
|
26
|
+
});
|
|
29
27
|
}
|
|
30
28
|
utils.logLabeledBullet("dataconnect", "Schemas released.");
|
|
31
29
|
}
|
|
@@ -16,6 +16,7 @@ const deploymentTool = require("../../../deploymentTool");
|
|
|
16
16
|
const gcf = require("../../../gcp/cloudfunctions");
|
|
17
17
|
const gcfV2 = require("../../../gcp/cloudfunctionsv2");
|
|
18
18
|
const eventarc = require("../../../gcp/eventarc");
|
|
19
|
+
const experiments = require("../../../experiments");
|
|
19
20
|
const helper = require("../functionsDeployHelper");
|
|
20
21
|
const poller = require("../../../operation-poller");
|
|
21
22
|
const pubsub = require("../../../gcp/pubsub");
|
|
@@ -276,7 +277,9 @@ class Fabricator {
|
|
|
276
277
|
while (!resultFunction) {
|
|
277
278
|
resultFunction = await this.functionExecutor
|
|
278
279
|
.run(async () => {
|
|
279
|
-
|
|
280
|
+
if (experiments.isEnabled("functionsv2deployoptimizations")) {
|
|
281
|
+
apiFunction.buildConfig.sourceToken = await scraper.getToken();
|
|
282
|
+
}
|
|
280
283
|
const op = await gcfV2.createFunction(apiFunction);
|
|
281
284
|
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
|
|
282
285
|
})
|
|
@@ -384,7 +387,9 @@ class Fabricator {
|
|
|
384
387
|
}
|
|
385
388
|
const resultFunction = await this.functionExecutor
|
|
386
389
|
.run(async () => {
|
|
387
|
-
|
|
390
|
+
if (experiments.isEnabled("functionsv2deployoptimizations")) {
|
|
391
|
+
apiFunction.buildConfig.sourceToken = await scraper.getToken();
|
|
392
|
+
}
|
|
388
393
|
const op = await gcfV2.updateFunction(apiFunction);
|
|
389
394
|
return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
|
|
390
395
|
}, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
|
|
@@ -26,7 +26,7 @@ class DataConnectEmulator {
|
|
|
26
26
|
this.logger.logLabeled("WARN", "Data Connect", "Operations that use vector_embed will make calls to production Vertex AI");
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
return (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, Object.assign(Object.assign({}, this.args), { http_port: port, grpc_port: port + 1, config_dir: this.args.configDir, local_connection_string: this.getLocalConectionString(), project_id: this.args.projectId }));
|
|
29
|
+
return (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, Object.assign(Object.assign({}, this.args), { http_port: port, grpc_port: port + 1, config_dir: this.args.configDir, local_connection_string: this.getLocalConectionString(), project_id: this.args.projectId, service_location: this.args.locationId }));
|
|
30
30
|
}
|
|
31
31
|
connect() {
|
|
32
32
|
return Promise.resolve();
|
|
@@ -42,6 +42,7 @@ class DataConnectEmulator {
|
|
|
42
42
|
host,
|
|
43
43
|
port,
|
|
44
44
|
pid: (0, downloadableEmulators_1.getPID)(types_1.Emulators.DATACONNECT),
|
|
45
|
+
timeout: 10000,
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
getName() {
|
|
@@ -51,6 +52,7 @@ class DataConnectEmulator {
|
|
|
51
52
|
const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
|
|
52
53
|
const cmd = [
|
|
53
54
|
"generate",
|
|
55
|
+
`--service_location=${this.args.locationId}`,
|
|
54
56
|
`--config_dir=${this.args.configDir}`,
|
|
55
57
|
`--connector_id=${connectorId}`,
|
|
56
58
|
];
|
|
@@ -46,14 +46,14 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
46
46
|
},
|
|
47
47
|
dataconnect: process.platform === "darwin"
|
|
48
48
|
? {
|
|
49
|
-
version: "1.1.
|
|
50
|
-
expectedSize:
|
|
51
|
-
expectedChecksum: "
|
|
49
|
+
version: "1.1.15",
|
|
50
|
+
expectedSize: 25600896,
|
|
51
|
+
expectedChecksum: "36dcf9be7273b9ba6052faf0b3c0347f",
|
|
52
52
|
}
|
|
53
53
|
: {
|
|
54
|
-
version: "1.1.
|
|
55
|
-
expectedSize:
|
|
56
|
-
expectedChecksum: "
|
|
54
|
+
version: "1.1.15",
|
|
55
|
+
expectedSize: 23036688,
|
|
56
|
+
expectedChecksum: "e42203947bf984993f295976ee3ba2be",
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
59
|
exports.DownloadDetails = {
|
|
@@ -131,9 +131,8 @@ async function checkListenable(arg1, port) {
|
|
|
131
131
|
});
|
|
132
132
|
}
|
|
133
133
|
exports.checkListenable = checkListenable;
|
|
134
|
-
async function waitForPortUsed(port, host) {
|
|
134
|
+
async function waitForPortUsed(port, host, timeout = 60000) {
|
|
135
135
|
const interval = 200;
|
|
136
|
-
const timeout = 5000;
|
|
137
136
|
try {
|
|
138
137
|
await tcpport.waitUntilUsedOnHost(port, host, interval, timeout);
|
|
139
138
|
}
|
package/lib/emulator/registry.js
CHANGED
|
@@ -19,7 +19,7 @@ class EmulatorRegistry {
|
|
|
19
19
|
await instance.start();
|
|
20
20
|
if (instance.getName() !== types_1.Emulators.EXTENSIONS) {
|
|
21
21
|
const info = instance.getInfo();
|
|
22
|
-
await portUtils.waitForPortUsed(info.port, (0, utils_1.connectableHostname)(info.host));
|
|
22
|
+
await portUtils.waitForPortUsed(info.port, (0, utils_1.connectableHostname)(info.host), info.timeout);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
static async stop(name) {
|
package/lib/experiments.js
CHANGED
|
@@ -20,11 +20,13 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
20
20
|
rtdbmanagement: {
|
|
21
21
|
shortDescription: "Use new endpoint to administer realtime database instances",
|
|
22
22
|
},
|
|
23
|
-
|
|
24
|
-
shortDescription: "
|
|
25
|
-
fullDescription: "
|
|
26
|
-
"
|
|
27
|
-
"
|
|
23
|
+
functionsv2deployoptimizations: {
|
|
24
|
+
shortDescription: "Optimize deployments of v2 firebase functions",
|
|
25
|
+
fullDescription: "Reuse build images across funtions to increase performance and reliaibility " +
|
|
26
|
+
"of deploys. This has been made an experiment due to backend bugs that are " +
|
|
27
|
+
"temporarily causing failures in some regions with this optimization enabled",
|
|
28
|
+
public: true,
|
|
29
|
+
default: false,
|
|
28
30
|
},
|
|
29
31
|
deletegcfartifacts: {
|
|
30
32
|
shortDescription: `Add the ${(0, colorette_1.bold)("functions:deletegcfartifacts")} command to purge docker build images`,
|
|
@@ -400,7 +400,9 @@ async function ɵcodegenFunctionsDirectory(sourceDir, destDir, target, context)
|
|
|
400
400
|
(0, utils_2.getProductionDistDirFiles)(sourceDir, distDir),
|
|
401
401
|
(0, fs_extra_1.mkdirp)((0, path_1.join)(destDir, distDir)),
|
|
402
402
|
]);
|
|
403
|
-
await Promise.all(productionDistDirfiles.map((file) => (0, fs_extra_1.copy)(
|
|
403
|
+
await Promise.all(productionDistDirfiles.map((file) => (0, fs_extra_1.copy)((0, path_1.join)(sourceDir, distDir, file), (0, path_1.join)(destDir, distDir, file), {
|
|
404
|
+
recursive: true,
|
|
405
|
+
})));
|
|
404
406
|
return { packageJson, frameworksEntry: "next.js", dotEnv };
|
|
405
407
|
}
|
|
406
408
|
exports.ɵcodegenFunctionsDirectory = ɵcodegenFunctionsDirectory;
|
|
@@ -230,7 +230,7 @@ async function getProductionDistDirFiles(sourceDir, distDir) {
|
|
|
230
230
|
ignore: [(0, path_1.join)("cache", "webpack", "*-development", "**"), (0, path_1.join)("cache", "eslint", "**")],
|
|
231
231
|
cwd: (0, path_1.join)(sourceDir, distDir),
|
|
232
232
|
nodir: true,
|
|
233
|
-
absolute:
|
|
233
|
+
absolute: false,
|
|
234
234
|
realpath: utils_2.IS_WINDOWS,
|
|
235
235
|
}, (err, matches) => {
|
|
236
236
|
if (err)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setupIAMUser = exports.execute = void 0;
|
|
3
|
+
exports.firebaseowner = exports.setupIAMUser = exports.execute = void 0;
|
|
4
4
|
const pg = require("pg");
|
|
5
5
|
const cloud_sql_connector_1 = require("@google-cloud/cloud-sql-connector");
|
|
6
6
|
const requireAuth_1 = require("../../requireAuth");
|
|
@@ -116,6 +116,7 @@ exports.setupIAMUser = setupIAMUser;
|
|
|
116
116
|
function firebaseowner(databaseId) {
|
|
117
117
|
return `firebaseowner_${databaseId}_public`;
|
|
118
118
|
}
|
|
119
|
+
exports.firebaseowner = firebaseowner;
|
|
119
120
|
function toDatabaseUser(account) {
|
|
120
121
|
let mode = "CLOUD_IAM_USER";
|
|
121
122
|
let user = account;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isProductTosAccepted = exports.getAcceptanceStatus = exports.getTosStatus = exports.APP_CHECK_TOS_ID = exports.APPHOSTING_TOS_ID = void 0;
|
|
4
|
+
const apiv2_1 = require("../apiv2");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const client = new apiv2_1.Client({ urlPrefix: (0, api_1.firedataOrigin)(), auth: true, apiVersion: "v1" });
|
|
8
|
+
exports.APPHOSTING_TOS_ID = "APP_HOSTING_TOS";
|
|
9
|
+
exports.APP_CHECK_TOS_ID = "APP_CHECK";
|
|
10
|
+
async function getTosStatus() {
|
|
11
|
+
const res = await client.get("accessmanagement/tos:getStatus");
|
|
12
|
+
return res.body;
|
|
13
|
+
}
|
|
14
|
+
exports.getTosStatus = getTosStatus;
|
|
15
|
+
function getAcceptanceStatus(response, tosId) {
|
|
16
|
+
const perServiceStatus = response.perServiceStatus.find((tosStatus) => tosStatus.tosId === tosId);
|
|
17
|
+
if (perServiceStatus === undefined) {
|
|
18
|
+
throw new error_1.FirebaseError(`Missing terms of service status for product: ${tosId}`);
|
|
19
|
+
}
|
|
20
|
+
return perServiceStatus.serviceStatus.status;
|
|
21
|
+
}
|
|
22
|
+
exports.getAcceptanceStatus = getAcceptanceStatus;
|
|
23
|
+
function isProductTosAccepted(response, tosId) {
|
|
24
|
+
return getAcceptanceStatus(response, tosId) === "ACCEPTED";
|
|
25
|
+
}
|
|
26
|
+
exports.isProductTosAccepted = isProductTosAccepted;
|
|
@@ -43,6 +43,7 @@ async function interactiveCreateHostingSite(siteId, appId, options) {
|
|
|
43
43
|
if (options.nonInteractive) {
|
|
44
44
|
throw err;
|
|
45
45
|
}
|
|
46
|
+
id = "";
|
|
46
47
|
suggestion = getSuggestionFromError(err);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -71,5 +72,8 @@ function getSuggestionFromError(err) {
|
|
|
71
72
|
return match[1];
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
else {
|
|
76
|
+
(0, utils_1.logWarning)(err.message);
|
|
77
|
+
}
|
|
74
78
|
return;
|
|
75
79
|
}
|
|
@@ -5,6 +5,7 @@ const path_1 = require("path");
|
|
|
5
5
|
const prompt_1 = require("../../../prompt");
|
|
6
6
|
const fs_1 = require("fs");
|
|
7
7
|
const provisionCloudSql_1 = require("../../../dataconnect/provisionCloudSql");
|
|
8
|
+
const cloudsql = require("../../../gcp/cloudsql/cloudsqladmin");
|
|
8
9
|
const ensureApis_1 = require("../../../dataconnect/ensureApis");
|
|
9
10
|
const client_1 = require("../../../dataconnect/client");
|
|
10
11
|
const TEMPLATE_ROOT = (0, path_1.resolve)(__dirname, "../../../../templates/init/dataconnect/");
|
|
@@ -49,26 +50,60 @@ async function doSetup(setup, config) {
|
|
|
49
50
|
type: "input",
|
|
50
51
|
default: "my-connector",
|
|
51
52
|
});
|
|
52
|
-
const dir = config.get("dataconnect.source") ||
|
|
53
|
-
(await (0, prompt_1.promptOnce)({
|
|
54
|
-
message: "What directory should be used for DataConnect config and schema?",
|
|
55
|
-
type: "input",
|
|
56
|
-
default: "dataconnect",
|
|
57
|
-
}));
|
|
53
|
+
const dir = config.get("dataconnect.source") || "dataconnect";
|
|
58
54
|
if (!config.has("dataconnect")) {
|
|
59
55
|
config.set("dataconnect.source", dir);
|
|
60
56
|
config.set("dataconnect.location", locationId);
|
|
61
57
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
let cloudSqlInstanceId = "";
|
|
59
|
+
let newInstance = false;
|
|
60
|
+
if (setup.projectId) {
|
|
61
|
+
const instances = await cloudsql.listInstances(setup.projectId);
|
|
62
|
+
const instancesInLocation = instances.filter((i) => i.region === locationId);
|
|
63
|
+
const choices = instancesInLocation.map((i) => {
|
|
64
|
+
return { name: i.name, value: i.name };
|
|
65
|
+
});
|
|
66
|
+
choices.push({ name: "Create a new instance", value: "" });
|
|
67
|
+
if (instancesInLocation.length) {
|
|
68
|
+
cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
|
|
69
|
+
message: `Which CloudSSQL in ${locationId} would you like to use?`,
|
|
70
|
+
type: "list",
|
|
71
|
+
choices,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (cloudSqlInstanceId === "") {
|
|
76
|
+
newInstance = true;
|
|
77
|
+
cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
|
|
78
|
+
message: `What ID would you like to use for your new CloudSQL instance?`,
|
|
79
|
+
type: "input",
|
|
80
|
+
default: `dataconnect-test`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
let cloudSqlDatabase = "";
|
|
84
|
+
let newDB = false;
|
|
85
|
+
if (!newInstance && setup.projectId) {
|
|
86
|
+
const dbs = await cloudsql.listDatabases(setup.projectId, cloudSqlInstanceId);
|
|
87
|
+
const choices = dbs.map((d) => {
|
|
88
|
+
return { name: d.name, value: d.name };
|
|
89
|
+
});
|
|
90
|
+
choices.push({ name: "Create a new database", value: "" });
|
|
91
|
+
if (dbs.length) {
|
|
92
|
+
cloudSqlDatabase = await (0, prompt_1.promptOnce)({
|
|
93
|
+
message: `Which database in ${cloudSqlInstanceId} would you like to use?`,
|
|
94
|
+
type: "list",
|
|
95
|
+
choices,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (cloudSqlDatabase === "") {
|
|
100
|
+
newDB = true;
|
|
101
|
+
cloudSqlDatabase = await (0, prompt_1.promptOnce)({
|
|
102
|
+
message: `What ID would you like to use for your new database in ${cloudSqlInstanceId}?`,
|
|
103
|
+
type: "input",
|
|
104
|
+
default: `dataconnect`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
72
107
|
const defaultConnectionString = (_c = (_b = (_a = setup.rcfile.dataconnectEmulatorConfig) === null || _a === void 0 ? void 0 : _a.postgres) === null || _b === void 0 ? void 0 : _b.localConnectionString) !== null && _c !== void 0 ? _c : "postgresql://localhost:5432?sslmode=disable";
|
|
73
108
|
const localConnectionString = await (0, prompt_1.promptOnce)({
|
|
74
109
|
type: "input",
|
|
@@ -95,6 +130,7 @@ async function doSetup(setup, config) {
|
|
|
95
130
|
await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "queries.gql"), QUERIES_TEMPLATE);
|
|
96
131
|
await config.askWriteProjectFile((0, path_1.join)(dir, "connector", "mutations.gql"), MUTATIONS_TEMPLATE);
|
|
97
132
|
if (setup.projectId &&
|
|
133
|
+
(newInstance || newDB) &&
|
|
98
134
|
(await (0, prompt_1.confirm)({
|
|
99
135
|
message: "Would you like to provision your CloudSQL instance and database now? This will take a few minutes.",
|
|
100
136
|
default: true,
|
package/lib/operation-poller.js
CHANGED
|
@@ -21,7 +21,7 @@ class OperationPoller {
|
|
|
21
21
|
if (error) {
|
|
22
22
|
throw error instanceof error_1.FirebaseError
|
|
23
23
|
? error
|
|
24
|
-
: new error_1.FirebaseError(error.message, { status: error.code });
|
|
24
|
+
: new error_1.FirebaseError(error.message, { status: error.code, original: error });
|
|
25
25
|
}
|
|
26
26
|
return response;
|
|
27
27
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requireTosAcceptance = void 0;
|
|
4
|
+
const error_1 = require("./error");
|
|
5
|
+
const firedata_1 = require("./gcp/firedata");
|
|
6
|
+
const api_1 = require("./api");
|
|
7
|
+
const consoleLandingPage = new Map([
|
|
8
|
+
[firedata_1.APPHOSTING_TOS_ID, `${(0, api_1.consoleOrigin)()}/project/_/apphosting`],
|
|
9
|
+
]);
|
|
10
|
+
function requireTosAcceptance(tosId) {
|
|
11
|
+
return () => requireTos(tosId);
|
|
12
|
+
}
|
|
13
|
+
exports.requireTosAcceptance = requireTosAcceptance;
|
|
14
|
+
async function requireTos(tosId) {
|
|
15
|
+
const res = await (0, firedata_1.getTosStatus)();
|
|
16
|
+
if ((0, firedata_1.isProductTosAccepted)(res, tosId)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const console = consoleLandingPage.get(tosId) || (0, api_1.consoleOrigin)();
|
|
20
|
+
throw new error_1.FirebaseError(`Your account has not accepted the required Terms of Service for this action. Please accept the Terms of Service and try again. ${console}`);
|
|
21
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = void 0;
|
|
3
|
+
exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isVSCodeExtension = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
|
|
4
|
+
exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = exports.openInBrowserPopup = exports.openInBrowser = void 0;
|
|
5
5
|
const fs = require("fs-extra");
|
|
6
6
|
const tty = require("tty");
|
|
7
7
|
const path = require("node:path");
|
|
@@ -342,6 +342,10 @@ async function promiseWithSpinner(action, message) {
|
|
|
342
342
|
return data;
|
|
343
343
|
}
|
|
344
344
|
exports.promiseWithSpinner = promiseWithSpinner;
|
|
345
|
+
function sleep(ms) {
|
|
346
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
347
|
+
}
|
|
348
|
+
exports.sleep = sleep;
|
|
345
349
|
function createDestroyer(server) {
|
|
346
350
|
const connections = new Set();
|
|
347
351
|
server.on("connection", (conn) => {
|
package/package.json
CHANGED