firebase-tools 13.9.0 → 13.10.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/commands/dataconnect-sdk-generate.js +5 -9
- package/lib/commands/dataconnect-sql-migrate.js +0 -1
- package/lib/dataconnect/build.js +4 -12
- package/lib/dataconnect/client.js +1 -1
- package/lib/dataconnect/errors.js +21 -13
- package/lib/dataconnect/provisionCloudSql.js +24 -13
- package/lib/dataconnect/schemaMigration.js +96 -58
- package/lib/deploy/dataconnect/release.js +0 -1
- package/lib/deploy/functions/runtimes/discovery/index.js +2 -1
- package/lib/emulator/controller.js +1 -3
- package/lib/emulator/dataconnectEmulator.js +20 -15
- package/lib/emulator/downloadableEmulators.js +15 -9
- package/lib/emulator/portUtils.js +11 -2
- package/lib/frameworks/next/utils.js +0 -1
- package/lib/gcp/cloudsql/cloudsqladmin.js +2 -0
- package/lib/gcp/cloudsql/connect.js +9 -6
- package/lib/init/features/dataconnect/index.js +1 -1
- package/lib/requireAuth.js +3 -0
- package/package.json +2 -2
|
@@ -20,16 +20,12 @@ exports.command = new command_1.Command("dataconnect:sdk:generate")
|
|
|
20
20
|
configDir = path.resolve(path.join(cwd), configDir);
|
|
21
21
|
}
|
|
22
22
|
const serviceInfo = await (0, load_1.load)(projectId, service.location, configDir);
|
|
23
|
-
const args = {
|
|
24
|
-
projectId,
|
|
25
|
-
configDir,
|
|
26
|
-
auto_download: true,
|
|
27
|
-
rc: options.rc,
|
|
28
|
-
locationId: service.location,
|
|
29
|
-
};
|
|
30
|
-
const dataconnectEmulator = new dataconnectEmulator_1.DataConnectEmulator(args);
|
|
31
23
|
for (const conn of serviceInfo.connectorInfo) {
|
|
32
|
-
const output = await
|
|
24
|
+
const output = await dataconnectEmulator_1.DataConnectEmulator.generate({
|
|
25
|
+
configDir,
|
|
26
|
+
locationId: service.location,
|
|
27
|
+
connectorId: conn.connectorYaml.connectorId,
|
|
28
|
+
});
|
|
33
29
|
logger_1.logger.info(output);
|
|
34
30
|
logger_1.logger.info(`Generated SDKs for ${conn.connectorYaml.connectorId}`);
|
|
35
31
|
}
|
|
@@ -33,7 +33,6 @@ exports.command = new command_1.Command("dataconnect:sql:migrate [serviceId]")
|
|
|
33
33
|
const diffs = await (0, schemaMigration_1.migrateSchema)({
|
|
34
34
|
options,
|
|
35
35
|
schema: serviceInfo.schema,
|
|
36
|
-
allowNonInteractiveMigration: true,
|
|
37
36
|
validateOnly: true,
|
|
38
37
|
});
|
|
39
38
|
if (diffs.length) {
|
package/lib/dataconnect/build.js
CHANGED
|
@@ -5,19 +5,11 @@ const dataconnectEmulator_1 = require("../emulator/dataconnectEmulator");
|
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
const graphqlError_1 = require("./graphqlError");
|
|
7
7
|
async function build(options, configDir) {
|
|
8
|
-
var _a, _b
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
projectId,
|
|
12
|
-
configDir,
|
|
13
|
-
auto_download: true,
|
|
14
|
-
rc: options.rc,
|
|
15
|
-
};
|
|
16
|
-
const dataconnectEmulator = new dataconnectEmulator_1.DataConnectEmulator(args);
|
|
17
|
-
const buildResult = await dataconnectEmulator.build();
|
|
18
|
-
if ((_b = buildResult === null || buildResult === void 0 ? void 0 : buildResult.errors) === null || _b === void 0 ? void 0 : _b.length) {
|
|
8
|
+
var _a, _b;
|
|
9
|
+
const buildResult = await dataconnectEmulator_1.DataConnectEmulator.build({ configDir });
|
|
10
|
+
if ((_a = buildResult === null || buildResult === void 0 ? void 0 : buildResult.errors) === null || _a === void 0 ? void 0 : _a.length) {
|
|
19
11
|
throw new error_1.FirebaseError(`There are errors in your schema and connector files:\n${buildResult.errors.map(graphqlError_1.prettify).join("\n")}`);
|
|
20
12
|
}
|
|
21
|
-
return (
|
|
13
|
+
return (_b = buildResult === null || buildResult === void 0 ? void 0 : buildResult.metadata) !== null && _b !== void 0 ? _b : {};
|
|
22
14
|
}
|
|
23
15
|
exports.build = build;
|
|
@@ -56,7 +56,7 @@ async function createService(projectId, locationId, serviceId) {
|
|
|
56
56
|
}
|
|
57
57
|
exports.createService = createService;
|
|
58
58
|
async function deleteService(projectId, locationId, serviceId) {
|
|
59
|
-
const op = await dataconnectClient().delete(`projects/${projectId}/locations/${locationId}/services/${serviceId}
|
|
59
|
+
const op = await dataconnectClient().delete(`projects/${projectId}/locations/${locationId}/services/${serviceId}`);
|
|
60
60
|
const pollRes = await operationPoller.pollOperation({
|
|
61
61
|
apiOrigin: (0, api_1.dataconnectOrigin)(),
|
|
62
62
|
apiVersion: DATACONNECT_API_VERSION,
|
|
@@ -5,27 +5,35 @@ const INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING = "IncompatibleSqlSchemaError";
|
|
|
5
5
|
const PRECONDITION_ERROR_TYPESTRING = "type.googleapis.com/google.rpc.PreconditionFailure";
|
|
6
6
|
const INCOMPATIBLE_CONNECTOR_TYPE = "INCOMPATIBLE_CONNECTOR";
|
|
7
7
|
function getIncompatibleSchemaError(err) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
throw err;
|
|
8
|
+
const incompatibles = errorDetails(err, INCOMPATIBLE_SCHEMA_ERROR_TYPESTRING);
|
|
9
|
+
if (incompatibles.length === 0) {
|
|
10
|
+
return undefined;
|
|
12
11
|
}
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
12
|
+
const incompatible = incompatibles[0];
|
|
13
|
+
const preconditionErrs = errorDetails(err, PRECONDITION_ERROR_TYPESTRING);
|
|
14
|
+
const violationTypes = (incompatible.violationType = preconditionErrs
|
|
15
|
+
.flatMap((preCondErr) => preCondErr.violations)
|
|
16
|
+
.flatMap((viol) => viol.type)
|
|
17
|
+
.filter((type) => type === "INACCESSIBLE_SCHEMA" || type === "INCOMPATIBLE_SCHEMA"));
|
|
18
|
+
incompatible.violationType = violationTypes[0];
|
|
19
|
+
return incompatible;
|
|
16
20
|
}
|
|
17
21
|
exports.getIncompatibleSchemaError = getIncompatibleSchemaError;
|
|
18
22
|
function getInvalidConnectors(err) {
|
|
19
|
-
var _a, _b
|
|
23
|
+
var _a, _b;
|
|
24
|
+
const preconditionErrs = errorDetails(err, PRECONDITION_ERROR_TYPESTRING);
|
|
20
25
|
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
26
|
for (const preconditionErr of preconditionErrs) {
|
|
25
|
-
const incompatibleConnViolation = (
|
|
26
|
-
const newConns = (
|
|
27
|
+
const incompatibleConnViolation = (_a = preconditionErr === null || preconditionErr === void 0 ? void 0 : preconditionErr.violations) === null || _a === void 0 ? void 0 : _a.filter((v) => v.type === INCOMPATIBLE_CONNECTOR_TYPE);
|
|
28
|
+
const newConns = (_b = incompatibleConnViolation === null || incompatibleConnViolation === void 0 ? void 0 : incompatibleConnViolation.map((i) => i.subject)) !== null && _b !== void 0 ? _b : [];
|
|
27
29
|
invalidConns.push(...newConns);
|
|
28
30
|
}
|
|
29
31
|
return invalidConns;
|
|
30
32
|
}
|
|
31
33
|
exports.getInvalidConnectors = getInvalidConnectors;
|
|
34
|
+
function errorDetails(err, ofType) {
|
|
35
|
+
var _a, _b;
|
|
36
|
+
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.original);
|
|
37
|
+
const details = original === null || original === void 0 ? void 0 : original.details;
|
|
38
|
+
return (details === null || details === void 0 ? void 0 : details.filter((d) => { var _a; return (_a = d["@type"]) === null || _a === void 0 ? void 0 : _a.includes(ofType); })) || [];
|
|
39
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getUpdateReason = exports.provisionCloudSql = void 0;
|
|
4
4
|
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
5
5
|
const utils = require("../utils");
|
|
6
6
|
const checkIam_1 = require("./checkIam");
|
|
@@ -15,10 +15,12 @@ async function provisionCloudSql(args) {
|
|
|
15
15
|
const existingInstance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
|
|
16
16
|
silent || utils.logLabeledBullet("dataconnect", `Found existing instance ${instanceId}.`);
|
|
17
17
|
connectionName = (existingInstance === null || existingInstance === void 0 ? void 0 : existingInstance.connectionName) || "";
|
|
18
|
-
|
|
18
|
+
const why = getUpdateReason(existingInstance, enableGoogleMlIntegration);
|
|
19
|
+
if (why) {
|
|
19
20
|
silent ||
|
|
20
21
|
utils.logLabeledBullet("dataconnect", `Instance ${instanceId} settings not compatible with Firebase Data Connect. ` +
|
|
21
|
-
`Updating instance
|
|
22
|
+
`Updating instance. This may take a few minutes...` +
|
|
23
|
+
why);
|
|
22
24
|
await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.updateInstanceForDataConnect(existingInstance, enableGoogleMlIntegration), "Updating your instance...");
|
|
23
25
|
silent || utils.logLabeledBullet("dataconnect", "Instance updated");
|
|
24
26
|
}
|
|
@@ -44,10 +46,15 @@ async function provisionCloudSql(args) {
|
|
|
44
46
|
silent || utils.logLabeledBullet("dataconnect", `Found existing database ${databaseId}.`);
|
|
45
47
|
}
|
|
46
48
|
catch (err) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
if (err.status === 404) {
|
|
50
|
+
silent ||
|
|
51
|
+
utils.logLabeledBullet("dataconnect", `Database ${databaseId} not found, creating it now...`);
|
|
52
|
+
await cloudSqlAdminClient.createDatabase(projectId, instanceId, databaseId);
|
|
53
|
+
silent || utils.logLabeledBullet("dataconnect", `Database ${databaseId} created.`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
silent || utils.logLabeledWarning("dataconnect", `Database ${databaseId} is not accessible.`);
|
|
57
|
+
}
|
|
51
58
|
}
|
|
52
59
|
if (enableGoogleMlIntegration) {
|
|
53
60
|
await (0, checkIam_1.grantRolesToCloudSqlServiceAccount)(projectId, instanceId, [GOOGLE_ML_INTEGRATION_ROLE]);
|
|
@@ -55,21 +62,25 @@ async function provisionCloudSql(args) {
|
|
|
55
62
|
return connectionName;
|
|
56
63
|
}
|
|
57
64
|
exports.provisionCloudSql = provisionCloudSql;
|
|
58
|
-
function
|
|
65
|
+
function getUpdateReason(instance, requireGoogleMlIntegration) {
|
|
59
66
|
var _a, _b, _c, _d;
|
|
67
|
+
let reason = "";
|
|
60
68
|
const settings = instance.settings;
|
|
61
69
|
if (!((_a = settings.ipConfiguration) === null || _a === void 0 ? void 0 : _a.ipv4Enabled)) {
|
|
62
|
-
|
|
70
|
+
reason += "\n - to enable public IP.";
|
|
63
71
|
}
|
|
64
72
|
if (requireGoogleMlIntegration) {
|
|
65
73
|
if (!settings.enableGoogleMlIntegration) {
|
|
66
|
-
|
|
74
|
+
reason += "\n - to enable Google ML integration.";
|
|
67
75
|
}
|
|
68
76
|
if (!((_b = settings.databaseFlags) === null || _b === void 0 ? void 0 : _b.some((f) => f.name === "cloudsql.enable_google_ml_integration" && f.value === "on"))) {
|
|
69
|
-
|
|
77
|
+
reason += "\n - to enable Google ML integration database flag.";
|
|
70
78
|
}
|
|
71
79
|
}
|
|
72
80
|
const isIamEnabled = (_d = (_c = settings.databaseFlags) === null || _c === void 0 ? void 0 : _c.some((f) => f.name === "cloudsql.iam_authentication" && f.value === "on")) !== null && _d !== void 0 ? _d : false;
|
|
73
|
-
|
|
81
|
+
if (!isIamEnabled) {
|
|
82
|
+
reason += "\n - to enable IAM authentication database flag.";
|
|
83
|
+
}
|
|
84
|
+
return reason;
|
|
74
85
|
}
|
|
75
|
-
exports.
|
|
86
|
+
exports.getUpdateReason = getUpdateReason;
|
|
@@ -14,50 +14,50 @@ const utils_1 = require("../utils");
|
|
|
14
14
|
const errors = require("./errors");
|
|
15
15
|
async function diffSchema(schema) {
|
|
16
16
|
const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
|
|
17
|
-
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId);
|
|
17
|
+
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
18
18
|
try {
|
|
19
19
|
await (0, client_1.upsertSchema)(schema, true);
|
|
20
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
|
|
20
21
|
}
|
|
21
22
|
catch (err) {
|
|
23
|
+
if (err.status !== 400) {
|
|
24
|
+
throw err;
|
|
25
|
+
}
|
|
22
26
|
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
27
|
+
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
28
|
+
if (!incompatible && !invalidConnectors.length) {
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
23
31
|
if (invalidConnectors.length) {
|
|
24
32
|
displayInvalidConnectors(invalidConnectors);
|
|
25
33
|
}
|
|
26
|
-
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
27
34
|
if (incompatible) {
|
|
28
35
|
displaySchemaChanges(incompatible);
|
|
29
36
|
return incompatible.diffs;
|
|
30
37
|
}
|
|
31
38
|
}
|
|
32
|
-
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema is up to date.`);
|
|
33
39
|
return [];
|
|
34
40
|
}
|
|
35
41
|
exports.diffSchema = diffSchema;
|
|
36
42
|
async function migrateSchema(args) {
|
|
37
|
-
const { options, schema,
|
|
43
|
+
const { options, schema, validateOnly } = args;
|
|
38
44
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
39
|
-
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId);
|
|
45
|
+
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
|
|
40
46
|
try {
|
|
41
47
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
42
48
|
logger_1.logger.debug(`Database schema was up to date for ${instanceId}:${databaseId}`);
|
|
43
49
|
}
|
|
44
50
|
catch (err) {
|
|
51
|
+
if (err.status !== 400) {
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
45
54
|
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
46
55
|
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
47
56
|
if (!incompatible && !invalidConnectors.length) {
|
|
48
57
|
throw err;
|
|
49
58
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const cmd = suggestedCommand(serviceName, invalidConnectors);
|
|
53
|
-
throw new error_1.FirebaseError(`Command aborted. Try deploying compatible connectors first with ${clc.bold(cmd)}`);
|
|
54
|
-
}
|
|
55
|
-
const migrationMode = incompatible
|
|
56
|
-
? await promptForSchemaMigration(options, databaseId, incompatible, allowNonInteractiveMigration)
|
|
57
|
-
: "none";
|
|
58
|
-
if (migrationMode === "none" && incompatible) {
|
|
59
|
-
throw new error_1.FirebaseError("Command aborted.");
|
|
60
|
-
}
|
|
59
|
+
const migrationMode = await promptForSchemaMigration(options, databaseId, incompatible, validateOnly);
|
|
60
|
+
const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
|
|
61
61
|
let diffs = [];
|
|
62
62
|
if (incompatible) {
|
|
63
63
|
diffs = await handleIncompatibleSchemaError({
|
|
@@ -68,10 +68,12 @@ async function migrateSchema(args) {
|
|
|
68
68
|
choice: migrationMode,
|
|
69
69
|
});
|
|
70
70
|
}
|
|
71
|
-
if (
|
|
71
|
+
if (shouldDeleteInvalidConnectors) {
|
|
72
72
|
await deleteInvalidConnectors(invalidConnectors);
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
if (!validateOnly) {
|
|
75
|
+
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
76
|
+
}
|
|
75
77
|
return diffs;
|
|
76
78
|
}
|
|
77
79
|
return [];
|
|
@@ -136,16 +138,22 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
136
138
|
}
|
|
137
139
|
return [];
|
|
138
140
|
}
|
|
139
|
-
async function promptForSchemaMigration(options, databaseName, err,
|
|
141
|
+
async function promptForSchemaMigration(options, databaseName, err, validateOnly) {
|
|
142
|
+
if (!err) {
|
|
143
|
+
return "none";
|
|
144
|
+
}
|
|
140
145
|
displaySchemaChanges(err);
|
|
141
146
|
if (!options.nonInteractive) {
|
|
147
|
+
if (validateOnly && options.force) {
|
|
148
|
+
return "all";
|
|
149
|
+
}
|
|
142
150
|
const choices = err.destructive
|
|
143
151
|
? [
|
|
144
152
|
{ name: "Execute all changes (including destructive changes)", value: "all" },
|
|
145
153
|
{ name: "Abort changes", value: "none" },
|
|
146
154
|
]
|
|
147
155
|
: [
|
|
148
|
-
{ name: "Execute changes", value: "
|
|
156
|
+
{ name: "Execute changes", value: "all" },
|
|
149
157
|
{ name: "Abort changes", value: "none" },
|
|
150
158
|
];
|
|
151
159
|
return await (0, prompt_1.promptOnce)({
|
|
@@ -154,35 +162,39 @@ async function promptForSchemaMigration(options, databaseName, err, allowNonInte
|
|
|
154
162
|
choices,
|
|
155
163
|
});
|
|
156
164
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return "none";
|
|
165
|
+
if (!validateOnly) {
|
|
166
|
+
throw new error_1.FirebaseError("Command aborted. Your database schema is incompatible with your Data Connect schema. Run `firebase dataconnect:sql:migrate` to migrate your database schema");
|
|
160
167
|
}
|
|
161
168
|
else if (options.force) {
|
|
162
169
|
return "all";
|
|
163
170
|
}
|
|
164
171
|
else if (!err.destructive) {
|
|
165
|
-
return "
|
|
172
|
+
return "all";
|
|
166
173
|
}
|
|
167
174
|
else {
|
|
168
|
-
|
|
169
|
-
return "none";
|
|
175
|
+
throw new error_1.FirebaseError("Command aborted. This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
|
|
170
176
|
}
|
|
171
177
|
}
|
|
172
|
-
async function promptForInvalidConnectorError(options, invalidConnectors, validateOnly) {
|
|
178
|
+
async function promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly) {
|
|
173
179
|
if (!invalidConnectors.length) {
|
|
174
180
|
return false;
|
|
175
181
|
}
|
|
176
182
|
displayInvalidConnectors(invalidConnectors);
|
|
177
183
|
if (validateOnly) {
|
|
178
|
-
|
|
184
|
+
if (options.force) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
throw new error_1.FirebaseError(`Command aborted. If you'd like to migrate it anyway, you may override with --force.`);
|
|
179
188
|
}
|
|
180
|
-
|
|
181
|
-
(!options.nonInteractive &&
|
|
182
|
-
(await (0, prompt_1.confirm)(Object.assign(Object.assign({}, options), { message: "Would you like to delete and recreate these connectors?" }))))) {
|
|
189
|
+
if (options.force) {
|
|
183
190
|
return true;
|
|
184
191
|
}
|
|
185
|
-
|
|
192
|
+
if (!options.nonInteractive &&
|
|
193
|
+
(await (0, prompt_1.confirm)(Object.assign(Object.assign({}, options), { message: `Would you like to delete and recreate these connectors? This will cause ${clc.red(`downtime.`)}.` })))) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
const cmd = suggestedCommand(serviceName, invalidConnectors);
|
|
197
|
+
throw new error_1.FirebaseError(`Command aborted. Try deploying those connectors first with ${clc.bold(cmd)}`);
|
|
186
198
|
}
|
|
187
199
|
async function deleteInvalidConnectors(invalidConnectors) {
|
|
188
200
|
return Promise.all(invalidConnectors.map(client_1.deleteConnector));
|
|
@@ -190,39 +202,48 @@ async function deleteInvalidConnectors(invalidConnectors) {
|
|
|
190
202
|
function displayInvalidConnectors(invalidConnectors) {
|
|
191
203
|
const connectorIds = invalidConnectors.map((i) => i.split("/").pop()).join(", ");
|
|
192
204
|
(0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${connectorIds}.`);
|
|
193
|
-
(0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and
|
|
205
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and may break existing apps.`);
|
|
194
206
|
}
|
|
195
|
-
async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, databaseId) {
|
|
207
|
+
async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, databaseId, linkIfNotConnected) {
|
|
196
208
|
let currentSchema;
|
|
197
209
|
try {
|
|
198
210
|
currentSchema = await (0, client_1.getSchema)(serviceName);
|
|
199
211
|
}
|
|
200
212
|
catch (err) {
|
|
201
|
-
if (err.status
|
|
202
|
-
currentSchema = {
|
|
203
|
-
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
204
|
-
source: {
|
|
205
|
-
files: [],
|
|
206
|
-
},
|
|
207
|
-
primaryDatasource: {
|
|
208
|
-
postgresql: {
|
|
209
|
-
database: databaseId,
|
|
210
|
-
cloudSql: {
|
|
211
|
-
instance: instanceId,
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
213
|
+
if (err.status !== 404) {
|
|
218
214
|
throw err;
|
|
219
215
|
}
|
|
216
|
+
if (!linkIfNotConnected) {
|
|
217
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Not yet linked to the Cloud SQL instance.`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Linking the Cloud SQL instance...`);
|
|
221
|
+
currentSchema = {
|
|
222
|
+
name: `${serviceName}/schemas/${types_1.SCHEMA_ID}`,
|
|
223
|
+
source: {
|
|
224
|
+
files: [],
|
|
225
|
+
},
|
|
226
|
+
primaryDatasource: {
|
|
227
|
+
postgresql: {
|
|
228
|
+
database: databaseId,
|
|
229
|
+
cloudSql: {
|
|
230
|
+
instance: instanceId,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const postgresql = currentSchema.primaryDatasource.postgresql;
|
|
237
|
+
if ((postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance) !== instanceId) {
|
|
238
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Cloud SQL instance\nFrom ${postgresql === null || postgresql === void 0 ? void 0 : postgresql.cloudSql.instance}\nTo ${instanceId}`);
|
|
220
239
|
}
|
|
221
|
-
if (
|
|
222
|
-
|
|
240
|
+
if ((postgresql === null || postgresql === void 0 ? void 0 : postgresql.database) !== databaseId) {
|
|
241
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Switching connected Postgres database from ${postgresql === null || postgresql === void 0 ? void 0 : postgresql.database} to ${databaseId}`);
|
|
242
|
+
}
|
|
243
|
+
if (!postgresql || postgresql.schemaValidation === "STRICT") {
|
|
223
244
|
return;
|
|
224
245
|
}
|
|
225
|
-
|
|
246
|
+
postgresql.schemaValidation = "STRICT";
|
|
226
247
|
try {
|
|
227
248
|
await (0, client_1.upsertSchema)(currentSchema, false);
|
|
228
249
|
}
|
|
@@ -234,10 +255,27 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceId, datab
|
|
|
234
255
|
}
|
|
235
256
|
}
|
|
236
257
|
function displaySchemaChanges(error) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
switch (error.violationType) {
|
|
259
|
+
case "INCOMPATIBLE_SCHEMA":
|
|
260
|
+
{
|
|
261
|
+
const message = "Your new schema is incompatible with the schema of your CloudSQL database. " +
|
|
262
|
+
"The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
263
|
+
error.diffs.map(toString).join("\n");
|
|
264
|
+
(0, utils_1.logLabeledWarning)("dataconnect", message);
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
case "INACCESSIBLE_SCHEMA":
|
|
268
|
+
{
|
|
269
|
+
const message = "Cannot access your CloudSQL database to validate schema. " +
|
|
270
|
+
"The following SQL statements can setup a new database schema.\n" +
|
|
271
|
+
error.diffs.map(toString).join("\n");
|
|
272
|
+
(0, utils_1.logLabeledWarning)("dataconnect", message);
|
|
273
|
+
(0, utils_1.logLabeledWarning)("dataconnect", "Some SQL resources may already exist.");
|
|
274
|
+
}
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
throw new error_1.FirebaseError(`Unknown schema violation type: ${error.violationType}, IncompatibleSqlSchemaError: ${error}`);
|
|
278
|
+
}
|
|
241
279
|
}
|
|
242
280
|
function toString(diff) {
|
|
243
281
|
return `\/** ${diff.destructive ? clc.red("Destructive: ") : ""}${diff.description}*\/\n${(0, sql_formatter_1.format)(diff.sql, { language: "postgresql" })}`;
|
|
@@ -11,6 +11,7 @@ const api = require("../../.../../../../api");
|
|
|
11
11
|
const v1alpha1 = require("./v1alpha1");
|
|
12
12
|
const error_1 = require("../../../../error");
|
|
13
13
|
exports.readFileAsync = (0, util_1.promisify)(fs.readFile);
|
|
14
|
+
const TIMEOUT_OVERRIDE_ENV_VAR = "FUNCTIONS_DISCOVERY_TIMEOUT";
|
|
14
15
|
function yamlToBuild(yaml, project, region, runtime) {
|
|
15
16
|
try {
|
|
16
17
|
if (!yaml.specVersion) {
|
|
@@ -50,7 +51,7 @@ async function detectFromPort(port, project, runtime, timeout = 10000) {
|
|
|
50
51
|
const timedOut = new Promise((resolve, reject) => {
|
|
51
52
|
setTimeout(() => {
|
|
52
53
|
reject(new error_1.FirebaseError("User code failed to load. Cannot determine backend specification"));
|
|
53
|
-
}, timeout);
|
|
54
|
+
}, +(process.env[TIMEOUT_OVERRIDE_ENV_VAR] || 0) * 1000 || timeout);
|
|
54
55
|
});
|
|
55
56
|
while (true) {
|
|
56
57
|
try {
|
|
@@ -518,7 +518,6 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
518
518
|
await startEmulator(pubsubEmulator);
|
|
519
519
|
}
|
|
520
520
|
if (listenForEmulator.dataconnect) {
|
|
521
|
-
const dataConnectAddr = legacyGetFirstAddr(types_1.Emulators.DATACONNECT);
|
|
522
521
|
const config = (0, fileUtils_1.readFirebaseJson)(options.config);
|
|
523
522
|
if (!config.length) {
|
|
524
523
|
throw new error_1.FirebaseError("No Data Connect service found in firebase.json");
|
|
@@ -532,8 +531,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
532
531
|
configDir = path.resolve(path.join(cwd), configDir);
|
|
533
532
|
}
|
|
534
533
|
const dataConnectEmulator = new dataconnectEmulator_1.DataConnectEmulator({
|
|
535
|
-
|
|
536
|
-
port: dataConnectAddr.port,
|
|
534
|
+
listen: listenForEmulator.dataconnect,
|
|
537
535
|
projectId,
|
|
538
536
|
auto_download: true,
|
|
539
537
|
configDir,
|
|
@@ -9,16 +9,15 @@ const types_1 = require("./types");
|
|
|
9
9
|
const error_1 = require("../error");
|
|
10
10
|
const emulatorLogger_1 = require("./emulatorLogger");
|
|
11
11
|
const types_2 = require("../dataconnect/types");
|
|
12
|
-
const
|
|
12
|
+
const portUtils_1 = require("./portUtils");
|
|
13
13
|
class DataConnectEmulator {
|
|
14
14
|
constructor(args) {
|
|
15
15
|
this.args = args;
|
|
16
16
|
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT);
|
|
17
17
|
}
|
|
18
18
|
async start() {
|
|
19
|
-
const port = this.args.port || constants_1.Constants.getDefaultPort(types_1.Emulators.DATACONNECT);
|
|
20
19
|
this.logger.log("DEBUG", `Using Postgres connection string: ${this.getLocalConectionString()}`);
|
|
21
|
-
const info = await
|
|
20
|
+
const info = await DataConnectEmulator.build({ configDir: this.args.configDir });
|
|
22
21
|
if ((0, types_2.requiresVector)(info.metadata)) {
|
|
23
22
|
if (constants_1.Constants.isDemoProject(this.args.projectId)) {
|
|
24
23
|
this.logger.logLabeled("WARN", "Data Connect", "Detected a 'demo-' project, but vector embeddings require a real project. Operations that use vector_embed will fail.");
|
|
@@ -27,7 +26,7 @@ class DataConnectEmulator {
|
|
|
27
26
|
this.logger.logLabeled("WARN", "Data Connect", "Operations that use vector_embed will make calls to production Vertex AI");
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
|
-
return (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, Object.assign(Object.assign({}, this.args), {
|
|
29
|
+
return (0, downloadableEmulators_1.start)(types_1.Emulators.DATACONNECT, Object.assign(Object.assign({}, this.args), { listen: (0, portUtils_1.listenSpecsToString)(this.args.listen), config_dir: this.args.configDir, local_connection_string: this.getLocalConectionString(), project_id: this.args.projectId, service_location: this.args.locationId }));
|
|
31
30
|
}
|
|
32
31
|
connect() {
|
|
33
32
|
return Promise.resolve();
|
|
@@ -36,12 +35,11 @@ class DataConnectEmulator {
|
|
|
36
35
|
return (0, downloadableEmulators_1.stop)(types_1.Emulators.DATACONNECT);
|
|
37
36
|
}
|
|
38
37
|
getInfo() {
|
|
39
|
-
const host = this.args.host || constants_1.Constants.getDefaultHost();
|
|
40
|
-
const port = this.args.port || constants_1.Constants.getDefaultPort(types_1.Emulators.DATACONNECT);
|
|
41
38
|
return {
|
|
42
39
|
name: this.getName(),
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
listen: this.args.listen,
|
|
41
|
+
host: this.args.listen[0].address,
|
|
42
|
+
port: this.args.listen[0].port,
|
|
45
43
|
pid: (0, downloadableEmulators_1.getPID)(types_1.Emulators.DATACONNECT),
|
|
46
44
|
timeout: 10000,
|
|
47
45
|
};
|
|
@@ -49,25 +47,32 @@ class DataConnectEmulator {
|
|
|
49
47
|
getName() {
|
|
50
48
|
return types_1.Emulators.DATACONNECT;
|
|
51
49
|
}
|
|
52
|
-
async generate(
|
|
50
|
+
static async generate(args) {
|
|
53
51
|
const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
|
|
54
52
|
const cmd = [
|
|
55
53
|
"generate",
|
|
56
|
-
`--service_location=${
|
|
57
|
-
`--config_dir=${
|
|
58
|
-
`--connector_id=${connectorId}`,
|
|
54
|
+
`--service_location=${args.locationId}`,
|
|
55
|
+
`--config_dir=${args.configDir}`,
|
|
56
|
+
`--connector_id=${args.connectorId}`,
|
|
59
57
|
];
|
|
60
58
|
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
|
|
61
59
|
if (res.error) {
|
|
62
|
-
throw new error_1.FirebaseError(`Error starting up Data Connect
|
|
60
|
+
throw new error_1.FirebaseError(`Error starting up Data Connect generate: ${res.error.message}`, {
|
|
61
|
+
original: res.error,
|
|
62
|
+
});
|
|
63
63
|
}
|
|
64
64
|
return res.stdout;
|
|
65
65
|
}
|
|
66
|
-
async build() {
|
|
66
|
+
static async build(args) {
|
|
67
67
|
var _a;
|
|
68
68
|
const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
|
|
69
|
-
const cmd = ["build", `--config_dir=${
|
|
69
|
+
const cmd = ["build", `--config_dir=${args.configDir}`];
|
|
70
70
|
const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
|
|
71
|
+
if (res.error) {
|
|
72
|
+
throw new error_1.FirebaseError(`Error starting up Data Connect build: ${res.error.message}`, {
|
|
73
|
+
original: res.error,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
71
76
|
if (res.stderr) {
|
|
72
77
|
throw new error_1.FirebaseError(`Unable to build your Data Connect schema and connectors: ${res.stderr}`);
|
|
73
78
|
}
|
|
@@ -35,9 +35,9 @@ const EMULATOR_UPDATE_DETAILS = {
|
|
|
35
35
|
ui: experiments.isEnabled("emulatoruisnapshot")
|
|
36
36
|
? { version: "SNAPSHOT", expectedSize: -1, expectedChecksum: "" }
|
|
37
37
|
: {
|
|
38
|
-
version: "1.
|
|
39
|
-
expectedSize:
|
|
40
|
-
expectedChecksum: "
|
|
38
|
+
version: "1.12.1",
|
|
39
|
+
expectedSize: 3498269,
|
|
40
|
+
expectedChecksum: "a7f4398a00e5ca22abdcd78dc3877d00",
|
|
41
41
|
},
|
|
42
42
|
pubsub: {
|
|
43
43
|
version: "0.8.2",
|
|
@@ -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.18",
|
|
50
|
+
expectedSize: 25836736,
|
|
51
|
+
expectedChecksum: "28a760826968c86cff7e2137b7a487cf",
|
|
52
52
|
}
|
|
53
53
|
: {
|
|
54
54
|
version: "1.1.17",
|
|
55
|
-
expectedSize:
|
|
56
|
-
expectedChecksum: "
|
|
55
|
+
expectedSize: 23247120,
|
|
56
|
+
expectedChecksum: "ff2d10655fd0ad16c0fd07e1ca6dac3e",
|
|
57
57
|
},
|
|
58
58
|
};
|
|
59
59
|
exports.DownloadDetails = {
|
|
@@ -232,7 +232,13 @@ const Commands = {
|
|
|
232
232
|
dataconnect: {
|
|
233
233
|
binary: getExecPath(types_1.Emulators.DATACONNECT),
|
|
234
234
|
args: ["dev"],
|
|
235
|
-
optionalArgs: [
|
|
235
|
+
optionalArgs: [
|
|
236
|
+
"listen",
|
|
237
|
+
"config_dir",
|
|
238
|
+
"local_connection_string",
|
|
239
|
+
"project_id",
|
|
240
|
+
"service_location",
|
|
241
|
+
],
|
|
236
242
|
joinArgs: true,
|
|
237
243
|
shell: true,
|
|
238
244
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveHostAndAssignPorts = exports.waitForPortUsed = exports.checkListenable = void 0;
|
|
3
|
+
exports.listenSpecsToString = exports.resolveHostAndAssignPorts = exports.waitForPortUsed = exports.checkListenable = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const tcpport = require("tcp-port-used");
|
|
6
6
|
const node_net_1 = require("node:net");
|
|
@@ -146,7 +146,7 @@ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
|
|
|
146
146
|
firestore: true,
|
|
147
147
|
"firestore.websocket": true,
|
|
148
148
|
pubsub: true,
|
|
149
|
-
dataconnect:
|
|
149
|
+
dataconnect: false,
|
|
150
150
|
hub: false,
|
|
151
151
|
ui: false,
|
|
152
152
|
auth: true,
|
|
@@ -321,3 +321,12 @@ function listenSpec(lookup, port) {
|
|
|
321
321
|
port: port,
|
|
322
322
|
};
|
|
323
323
|
}
|
|
324
|
+
function listenSpecsToString(specs) {
|
|
325
|
+
return specs
|
|
326
|
+
.map((spec) => {
|
|
327
|
+
const host = spec.family === "IPv4" ? spec.address : `[${spec.address}]`;
|
|
328
|
+
return `${host}:${spec.port}`;
|
|
329
|
+
})
|
|
330
|
+
.join(",");
|
|
331
|
+
}
|
|
332
|
+
exports.listenSpecsToString = listenSpecsToString;
|
|
@@ -16,7 +16,7 @@ async function execute(sqlStatements, opts) {
|
|
|
16
16
|
const user = await cloudSqlAdminClient.getUser(opts.projectId, opts.instanceId, opts.username);
|
|
17
17
|
const connectionName = instance.connectionName;
|
|
18
18
|
if (!connectionName) {
|
|
19
|
-
throw new error_1.FirebaseError(`Could not get instance
|
|
19
|
+
throw new error_1.FirebaseError(`Could not get instance connection string for ${opts.instanceId}:${opts.databaseId}`);
|
|
20
20
|
}
|
|
21
21
|
let connector;
|
|
22
22
|
let pool;
|
|
@@ -30,7 +30,7 @@ async function execute(sqlStatements, opts) {
|
|
|
30
30
|
ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
|
|
31
31
|
authType: cloud_sql_connector_1.AuthTypes.IAM,
|
|
32
32
|
});
|
|
33
|
-
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId
|
|
33
|
+
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId }));
|
|
34
34
|
break;
|
|
35
35
|
}
|
|
36
36
|
case "CLOUD_IAM_SERVICE_ACCOUNT": {
|
|
@@ -40,7 +40,7 @@ async function execute(sqlStatements, opts) {
|
|
|
40
40
|
ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
|
|
41
41
|
authType: cloud_sql_connector_1.AuthTypes.IAM,
|
|
42
42
|
});
|
|
43
|
-
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId
|
|
43
|
+
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, database: opts.databaseId }));
|
|
44
44
|
break;
|
|
45
45
|
}
|
|
46
46
|
default: {
|
|
@@ -54,19 +54,22 @@ async function execute(sqlStatements, opts) {
|
|
|
54
54
|
instanceConnectionName: connectionName,
|
|
55
55
|
ipType: cloud_sql_connector_1.IpAddressTypes.PUBLIC,
|
|
56
56
|
});
|
|
57
|
-
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, password: opts.password, database: opts.databaseId
|
|
57
|
+
pool = new pg.Pool(Object.assign(Object.assign({}, clientOpts), { user: opts.username, password: opts.password, database: opts.databaseId }));
|
|
58
58
|
break;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
const conn = await pool.connect();
|
|
62
|
+
logFn(`Logged in as ${opts.username}`);
|
|
61
63
|
for (const s of sqlStatements) {
|
|
62
|
-
logFn(`Executing: '${s}'
|
|
64
|
+
logFn(`Executing: '${s}'`);
|
|
63
65
|
try {
|
|
64
|
-
await
|
|
66
|
+
await conn.query(s);
|
|
65
67
|
}
|
|
66
68
|
catch (err) {
|
|
67
69
|
throw new error_1.FirebaseError(`Error executing ${err}`);
|
|
68
70
|
}
|
|
69
71
|
}
|
|
72
|
+
conn.release();
|
|
70
73
|
await pool.end();
|
|
71
74
|
connector.close();
|
|
72
75
|
}
|
|
@@ -45,7 +45,7 @@ async function doSetup(setup, config) {
|
|
|
45
45
|
}
|
|
46
46
|
if (instances.length) {
|
|
47
47
|
cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
|
|
48
|
-
message: `Which
|
|
48
|
+
message: `Which CloudSQL instance would you like to use?`,
|
|
49
49
|
type: "list",
|
|
50
50
|
choices,
|
|
51
51
|
});
|
package/lib/requireAuth.js
CHANGED
|
@@ -20,6 +20,9 @@ function getAuthClient(config) {
|
|
|
20
20
|
return authClient;
|
|
21
21
|
}
|
|
22
22
|
async function autoAuth(options, authScopes) {
|
|
23
|
+
if (process.env.MONOSPACE_ENV) {
|
|
24
|
+
throw new error_1.FirebaseError("autoAuth not yet implemented for IDX. Please run 'firebase login'");
|
|
25
|
+
}
|
|
23
26
|
const client = getAuthClient({ scopes: authScopes, projectId: options.project });
|
|
24
27
|
const token = await client.getAccessToken();
|
|
25
28
|
token !== null ? apiv2.setAccessToken(token) : false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.10.1",
|
|
4
4
|
"description": "Command-Line Interface for Firebase",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@google-cloud/cloud-sql-connector": "^1.2.3",
|
|
62
|
-
"@google-cloud/pubsub": "^
|
|
62
|
+
"@google-cloud/pubsub": "^4.4.0",
|
|
63
63
|
"abort-controller": "^3.0.0",
|
|
64
64
|
"ajv": "^6.12.6",
|
|
65
65
|
"archiver": "^5.0.0",
|