firebase-tools 14.13.0 → 14.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/apphosting/backend.js +40 -15
- package/lib/auth.js +37 -2
- package/lib/commands/apphosting-backends-create.js +8 -5
- package/lib/commands/dataconnect-sdk-generate.js +3 -5
- package/lib/commands/dataconnect-sql-diff.js +2 -2
- package/lib/commands/dataconnect-sql-grant.js +2 -2
- package/lib/commands/dataconnect-sql-migrate.js +2 -2
- package/lib/commands/dataconnect-sql-setup.js +2 -2
- package/lib/commands/dataconnect-sql-shell.js +2 -2
- package/lib/commands/init.js +3 -0
- package/lib/commands/login.js +12 -7
- package/lib/crashlytics/addNote.js +27 -0
- package/lib/crashlytics/deleteNote.js +23 -0
- package/lib/crashlytics/getIssueDetails.js +5 -20
- package/lib/crashlytics/getSampleCrash.js +6 -20
- package/lib/crashlytics/listNotes.js +29 -0
- package/lib/crashlytics/listTopDevices.js +33 -0
- package/lib/crashlytics/listTopIssues.js +8 -23
- package/lib/crashlytics/listTopOperatingSystems.js +32 -0
- package/lib/crashlytics/listTopVersions.js +32 -0
- package/lib/crashlytics/updateIssue.js +35 -0
- package/lib/crashlytics/utils.js +38 -0
- package/lib/dataconnect/appFinder.js +103 -0
- package/lib/dataconnect/errors.js +7 -1
- package/lib/dataconnect/load.js +105 -6
- package/lib/dataconnect/schemaMigration.js +152 -128
- package/lib/deploy/dataconnect/prepare.js +1 -3
- package/lib/deploy/dataconnect/release.js +33 -27
- package/lib/deploy/functions/prepare.js +2 -0
- package/lib/emulator/controller.js +7 -4
- package/lib/emulator/downloadableEmulatorInfo.json +17 -17
- package/lib/emulator/functionsEmulator.js +2 -0
- package/lib/functions/env.js +19 -15
- package/lib/gcp/cloudsql/connect.js +2 -1
- package/lib/gcp/cloudsql/permissionsSetup.js +10 -1
- package/lib/init/features/dataconnect/create_app.js +48 -0
- package/lib/init/features/dataconnect/index.js +20 -31
- package/lib/init/features/dataconnect/sdk.js +218 -161
- package/lib/init/features/index.js +3 -2
- package/lib/init/index.js +5 -1
- package/lib/management/apps.js +3 -3
- package/lib/mcp/prompts/core/deploy.js +51 -8
- package/lib/mcp/prompts/crashlytics/common.js +10 -0
- package/lib/mcp/prompts/crashlytics/fix_issue.js +89 -0
- package/lib/mcp/prompts/crashlytics/index.js +6 -0
- package/lib/mcp/prompts/crashlytics/prioritize_issues.js +79 -0
- package/lib/mcp/prompts/index.js +3 -2
- package/lib/mcp/tools/core/init.js +5 -1
- package/lib/mcp/tools/crashlytics/add_note.js +32 -0
- package/lib/mcp/tools/crashlytics/constants.js +11 -0
- package/lib/mcp/tools/crashlytics/delete_note.js +35 -0
- package/lib/mcp/tools/crashlytics/get_issue_details.js +2 -4
- package/lib/mcp/tools/crashlytics/get_sample_crash.js +4 -4
- package/lib/mcp/tools/crashlytics/index.js +16 -2
- package/lib/mcp/tools/crashlytics/list_notes.js +37 -0
- package/lib/mcp/tools/crashlytics/list_top_devices.js +33 -0
- package/lib/mcp/tools/crashlytics/list_top_issues.js +5 -7
- package/lib/mcp/tools/crashlytics/list_top_operating_systems.js +33 -0
- package/lib/mcp/tools/crashlytics/list_top_versions.js +33 -0
- package/lib/mcp/tools/crashlytics/update_issue.js +37 -0
- package/lib/mcp/tools/dataconnect/execute_graphql.js +2 -2
- package/lib/mcp/tools/dataconnect/execute_graphql_read.js +2 -2
- package/lib/mcp/tools/dataconnect/execute_mutation.js +2 -2
- package/lib/mcp/tools/dataconnect/execute_query.js +2 -2
- package/lib/mcp/tools/dataconnect/generate_operation.js +2 -2
- package/lib/mcp/tools/dataconnect/get_connector.js +2 -2
- package/lib/mcp/tools/dataconnect/get_schema.js +2 -2
- package/lib/utils.js +11 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +3 -0
- package/templates/init/dataconnect/connector.yaml +0 -16
- package/templates/init/dataconnect/dataconnect.yaml +2 -1
- package/templates/init/dataconnect/mutations.gql +29 -29
- package/templates/init/dataconnect/queries.gql +73 -73
- package/templates/init/dataconnect/schema.gql +48 -48
- package/lib/dataconnect/fileUtils.js +0 -168
|
@@ -17,6 +17,7 @@ const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
|
|
|
17
17
|
const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
|
|
18
18
|
const errors = require("./errors");
|
|
19
19
|
const provisionCloudSql_1 = require("./provisionCloudSql");
|
|
20
|
+
const requireAuth_1 = require("../requireAuth");
|
|
20
21
|
async function setupSchemaIfNecessary(instanceId, databaseId, options) {
|
|
21
22
|
try {
|
|
22
23
|
await (0, connect_1.setupIAMUsers)(instanceId, options);
|
|
@@ -24,87 +25,89 @@ async function setupSchemaIfNecessary(instanceId, databaseId, options) {
|
|
|
24
25
|
switch (schemaInfo.setupStatus) {
|
|
25
26
|
case permissionsSetup_1.SchemaSetupStatus.BrownField:
|
|
26
27
|
case permissionsSetup_1.SchemaSetupStatus.GreenField:
|
|
28
|
+
logger_1.logger.debug(`Cloud SQL Database ${instanceId}:${databaseId} is already set up in ${schemaInfo.setupStatus}`);
|
|
27
29
|
return schemaInfo.setupStatus;
|
|
28
30
|
case permissionsSetup_1.SchemaSetupStatus.NotSetup:
|
|
29
31
|
case permissionsSetup_1.SchemaSetupStatus.NotFound:
|
|
32
|
+
(0, utils_1.logLabeledBullet)("dataconnect", "Setting up Cloud SQL Database SQL permissions...");
|
|
30
33
|
return await (0, permissionsSetup_1.setupSQLPermissions)(instanceId, databaseId, schemaInfo, options, true);
|
|
31
34
|
default:
|
|
32
35
|
throw new error_1.FirebaseError(`Unexpected schema setup status: ${schemaInfo.setupStatus}`);
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
catch (err) {
|
|
36
|
-
throw new error_1.FirebaseError(`Cannot setup SQL
|
|
39
|
+
throw new error_1.FirebaseError(`Cannot setup Postgres SQL permissions of Cloud SQL database ${instanceId}:${databaseId}\n${err}`);
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
async function diffSchema(options, schema, schemaValidation) {
|
|
40
|
-
|
|
43
|
+
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "STRICT";
|
|
44
|
+
setSchemaValidationMode(schema, validationMode);
|
|
45
|
+
displayStartSchemaDiff(validationMode);
|
|
41
46
|
const { serviceName, instanceName, databaseId, instanceId } = getIdentifiers(schema);
|
|
42
47
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
43
|
-
let
|
|
44
|
-
setSchemaValidationMode(schema, validationMode);
|
|
45
|
-
let diffs = [];
|
|
48
|
+
let incompatible = undefined;
|
|
46
49
|
try {
|
|
47
50
|
await (0, client_1.upsertSchema)(schema, true);
|
|
48
|
-
|
|
49
|
-
(0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is up to date.`);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
(0, utils_1.logLabeledSuccess)("dataconnect", `database schema of ${instanceId}:${databaseId} is compatible.`);
|
|
53
|
-
}
|
|
51
|
+
displayNoSchemaDiff(instanceId, databaseId, validationMode);
|
|
54
52
|
}
|
|
55
53
|
catch (err) {
|
|
56
54
|
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
57
55
|
throw err;
|
|
58
56
|
}
|
|
57
|
+
incompatible = errors.getIncompatibleSchemaError(err);
|
|
59
58
|
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
60
|
-
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
61
59
|
if (!incompatible && !invalidConnectors.length) {
|
|
60
|
+
const gqlErrs = errors.getGQLErrors(err);
|
|
61
|
+
if (gqlErrs) {
|
|
62
|
+
throw new error_1.FirebaseError(`There are errors in your schema files:\n${gqlErrs}`);
|
|
63
|
+
}
|
|
62
64
|
throw err;
|
|
63
65
|
}
|
|
64
66
|
if (invalidConnectors.length) {
|
|
65
67
|
displayInvalidConnectors(invalidConnectors);
|
|
66
68
|
}
|
|
67
|
-
if (incompatible) {
|
|
68
|
-
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
69
|
-
diffs = incompatible.diffs;
|
|
70
|
-
}
|
|
71
69
|
}
|
|
72
|
-
if (!
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
displaySchemaChanges(incompatible, validationMode, instanceName, databaseId);
|
|
92
|
-
}
|
|
93
|
-
diffs = incompatible.diffs;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
(0, utils_1.logLabeledSuccess)("dataconnect", `no additional optional changes`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
70
|
+
if (!incompatible) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
if (schemaValidation) {
|
|
74
|
+
displaySchemaChanges(incompatible, validationMode);
|
|
75
|
+
return incompatible.diffs;
|
|
76
|
+
}
|
|
77
|
+
const strictIncompatible = incompatible;
|
|
78
|
+
let compatibleIncompatible = undefined;
|
|
79
|
+
validationMode = "COMPATIBLE";
|
|
80
|
+
setSchemaValidationMode(schema, validationMode);
|
|
81
|
+
try {
|
|
82
|
+
displayStartSchemaDiff(validationMode);
|
|
83
|
+
await (0, client_1.upsertSchema)(schema, true);
|
|
84
|
+
displayNoSchemaDiff(instanceId, databaseId, validationMode);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
88
|
+
throw err;
|
|
99
89
|
}
|
|
90
|
+
compatibleIncompatible = errors.getIncompatibleSchemaError(err);
|
|
100
91
|
}
|
|
101
|
-
|
|
92
|
+
if (!compatibleIncompatible) {
|
|
93
|
+
displaySchemaChanges(strictIncompatible, "STRICT");
|
|
94
|
+
}
|
|
95
|
+
else if (diffsEqual(strictIncompatible.diffs, compatibleIncompatible.diffs)) {
|
|
96
|
+
displaySchemaChanges(strictIncompatible, "STRICT");
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
displaySchemaChanges(compatibleIncompatible, "COMPATIBLE");
|
|
100
|
+
displaySchemaChanges(strictIncompatible, "STRICT_AFTER_COMPATIBLE");
|
|
101
|
+
}
|
|
102
|
+
return incompatible.diffs;
|
|
102
103
|
}
|
|
103
104
|
exports.diffSchema = diffSchema;
|
|
104
105
|
async function migrateSchema(args) {
|
|
105
106
|
var _a;
|
|
106
|
-
(0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations...`);
|
|
107
107
|
const { options, schema, validateOnly, schemaValidation } = args;
|
|
108
|
+
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
109
|
+
setSchemaValidationMode(schema, validationMode);
|
|
110
|
+
displayStartSchemaDiff(validationMode);
|
|
108
111
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
109
112
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
110
113
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
|
|
@@ -124,12 +127,10 @@ async function migrateSchema(args) {
|
|
|
124
127
|
return [];
|
|
125
128
|
}
|
|
126
129
|
await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
127
|
-
let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
|
|
128
|
-
setSchemaValidationMode(schema, validationMode);
|
|
129
130
|
let diffs = [];
|
|
130
131
|
try {
|
|
131
132
|
await (0, client_1.upsertSchema)(schema, validateOnly);
|
|
132
|
-
(
|
|
133
|
+
displayNoSchemaDiff(instanceId, databaseId, validationMode);
|
|
133
134
|
}
|
|
134
135
|
catch (err) {
|
|
135
136
|
if ((err === null || err === void 0 ? void 0 : err.status) !== 400) {
|
|
@@ -138,9 +139,13 @@ async function migrateSchema(args) {
|
|
|
138
139
|
const incompatible = errors.getIncompatibleSchemaError(err);
|
|
139
140
|
const invalidConnectors = errors.getInvalidConnectors(err);
|
|
140
141
|
if (!incompatible && !invalidConnectors.length) {
|
|
142
|
+
const gqlErrs = errors.getGQLErrors(err);
|
|
143
|
+
if (gqlErrs) {
|
|
144
|
+
throw new error_1.FirebaseError(`There are errors in your schema files:\n${gqlErrs}`);
|
|
145
|
+
}
|
|
141
146
|
throw err;
|
|
142
147
|
}
|
|
143
|
-
const migrationMode = await promptForSchemaMigration(options,
|
|
148
|
+
const migrationMode = await promptForSchemaMigration(options, instanceId, databaseId, incompatible, validateOnly, validationMode);
|
|
144
149
|
const shouldDeleteInvalidConnectors = await promptForInvalidConnectorError(options, serviceName, invalidConnectors, validateOnly);
|
|
145
150
|
if (incompatible) {
|
|
146
151
|
diffs = await handleIncompatibleSchemaError({
|
|
@@ -173,7 +178,7 @@ async function migrateSchema(args) {
|
|
|
173
178
|
if (!incompatible && !invalidConnectors.length) {
|
|
174
179
|
throw err;
|
|
175
180
|
}
|
|
176
|
-
const migrationMode = await promptForSchemaMigration(options,
|
|
181
|
+
const migrationMode = await promptForSchemaMigration(options, instanceId, databaseId, incompatible, validateOnly, "STRICT_AFTER_COMPATIBLE");
|
|
177
182
|
if (incompatible) {
|
|
178
183
|
const maybeDiffs = await handleIncompatibleSchemaError({
|
|
179
184
|
options,
|
|
@@ -193,16 +198,12 @@ async function grantRoleToUserInSchema(options, schema) {
|
|
|
193
198
|
const role = options.role;
|
|
194
199
|
const email = options.email;
|
|
195
200
|
const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
|
|
196
|
-
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
197
|
-
const { user, mode } = (0, connect_1.toDatabaseUser)(email);
|
|
198
|
-
const fdcSqlRole = permissionsSetup_1.fdcSqlRoleMap[role](databaseId);
|
|
199
201
|
await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
|
|
200
202
|
const schemaSetupStatus = await setupSchemaIfNecessary(instanceId, databaseId, options);
|
|
201
203
|
if (schemaSetupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField && role === "owner") {
|
|
202
204
|
throw new error_1.FirebaseError(`Owner rule isn't available in ${schemaSetupStatus} databases. If you would like Data Connect to manage and own your database schema, run 'firebase dataconnect:sql:setup'`);
|
|
203
205
|
}
|
|
204
|
-
await
|
|
205
|
-
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [`GRANT "${fdcSqlRole}" TO "${user}"`], false);
|
|
206
|
+
await (0, permissionsSetup_1.grantRoleTo)(options, instanceId, databaseId, role, email);
|
|
206
207
|
}
|
|
207
208
|
exports.grantRoleToUserInSchema = grantRoleToUserInSchema;
|
|
208
209
|
function diffsEqual(x, y) {
|
|
@@ -253,11 +254,7 @@ function suggestedCommand(serviceName, invalidConnectorNames) {
|
|
|
253
254
|
}
|
|
254
255
|
async function handleIncompatibleSchemaError(args) {
|
|
255
256
|
const { incompatibleSchemaError, options, instanceId, databaseId, choice } = args;
|
|
256
|
-
|
|
257
|
-
throw new error_1.FirebaseError("This schema migration includes potentially destructive changes. If you'd like to execute it anyway, rerun this command with --force");
|
|
258
|
-
}
|
|
259
|
-
const commandsToExecute = incompatibleSchemaError.diffs
|
|
260
|
-
.filter((d) => {
|
|
257
|
+
const commandsToExecute = incompatibleSchemaError.diffs.filter((d) => {
|
|
261
258
|
switch (choice) {
|
|
262
259
|
case "all":
|
|
263
260
|
return true;
|
|
@@ -266,16 +263,15 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
266
263
|
case "none":
|
|
267
264
|
return false;
|
|
268
265
|
}
|
|
269
|
-
})
|
|
270
|
-
.map((d) => d.sql);
|
|
266
|
+
});
|
|
271
267
|
if (commandsToExecute.length) {
|
|
272
|
-
const commandsToExecuteBySuperUser = commandsToExecute.filter(
|
|
273
|
-
const commandsToExecuteByOwner = commandsToExecute.filter((sql) => !
|
|
268
|
+
const commandsToExecuteBySuperUser = commandsToExecute.filter(requireSuperUser);
|
|
269
|
+
const commandsToExecuteByOwner = commandsToExecute.filter((sql) => !requireSuperUser(sql));
|
|
274
270
|
const userIsCSQLAdmin = await (0, cloudsqladmin_1.iamUserIsCSQLAdmin)(options);
|
|
275
271
|
if (!userIsCSQLAdmin && commandsToExecuteBySuperUser.length) {
|
|
276
272
|
throw new error_1.FirebaseError(`Some SQL commands required for this migration require Admin permissions.\n
|
|
277
273
|
Please ask a user with 'roles/cloudsql.admin' to apply the following commands.\n
|
|
278
|
-
${commandsToExecuteBySuperUser
|
|
274
|
+
${diffsToString(commandsToExecuteBySuperUser)}`);
|
|
279
275
|
}
|
|
280
276
|
const schemaInfo = await (0, permissionsSetup_1.getSchemaMetadata)(instanceId, databaseId, permissions_1.DEFAULT_SCHEMA, options);
|
|
281
277
|
if (schemaInfo.setupStatus !== permissionsSetup_1.SchemaSetupStatus.GreenField) {
|
|
@@ -284,56 +280,67 @@ async function handleIncompatibleSchemaError(args) {
|
|
|
284
280
|
`If you would like Data Connect to manage your database schema, run 'firebase dataconnect:sql:setup'`);
|
|
285
281
|
}
|
|
286
282
|
if (!(await (0, permissionsSetup_1.checkSQLRoleIsGranted)(options, instanceId, databaseId, (0, permissions_1.firebaseowner)(databaseId), (await (0, connect_1.getIAMUser)(options)).user))) {
|
|
287
|
-
|
|
283
|
+
if (!userIsCSQLAdmin) {
|
|
284
|
+
throw new error_1.FirebaseError(`Command aborted. Only users granted firebaseowner SQL role can run migrations.`);
|
|
285
|
+
}
|
|
286
|
+
const account = (await (0, requireAuth_1.requireAuth)(options));
|
|
287
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Granting firebaseowner role to myself ${account}...`);
|
|
288
|
+
await (0, permissionsSetup_1.grantRoleTo)(options, instanceId, databaseId, "owner", account);
|
|
288
289
|
}
|
|
289
290
|
if (commandsToExecuteBySuperUser.length) {
|
|
290
|
-
|
|
291
|
-
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, commandsToExecuteBySuperUser, false);
|
|
291
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Executing admin SQL commands as superuser...`);
|
|
292
|
+
await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, commandsToExecuteBySuperUser.map((d) => d.sql), false);
|
|
292
293
|
}
|
|
293
294
|
if (commandsToExecuteByOwner.length) {
|
|
294
|
-
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [`SET ROLE "${(0, permissions_1.firebaseowner)(databaseId)}"`, ...commandsToExecuteByOwner], false);
|
|
295
|
+
await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [`SET ROLE "${(0, permissions_1.firebaseowner)(databaseId)}"`, ...commandsToExecuteByOwner.map((d) => d.sql)], false);
|
|
295
296
|
return incompatibleSchemaError.diffs;
|
|
296
297
|
}
|
|
297
298
|
}
|
|
298
299
|
return [];
|
|
299
300
|
}
|
|
300
|
-
async function promptForSchemaMigration(options,
|
|
301
|
+
async function promptForSchemaMigration(options, instanceId, databaseId, err, validateOnly, validationMode) {
|
|
301
302
|
if (!err) {
|
|
302
303
|
return "none";
|
|
303
304
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
displaySchemaChanges(err, validationMode, instanceName, databaseId);
|
|
305
|
+
const defaultChoice = validationMode === "STRICT_AFTER_COMPATIBLE" ? "none" : "all";
|
|
306
|
+
displaySchemaChanges(err, validationMode);
|
|
308
307
|
if (!options.nonInteractive) {
|
|
309
308
|
if (validateOnly && options.force) {
|
|
310
|
-
return
|
|
309
|
+
return defaultChoice;
|
|
310
|
+
}
|
|
311
|
+
let choices = [
|
|
312
|
+
{ name: "Execute all", value: "all" },
|
|
313
|
+
];
|
|
314
|
+
if (err.destructive) {
|
|
315
|
+
choices = [{ name: `Execute all ${clc.red("(including destructive)")}`, value: "all" }];
|
|
316
|
+
if (err.diffs.some((d) => !d.destructive)) {
|
|
317
|
+
choices.push({ name: "Execute safe only", value: "safe" });
|
|
318
|
+
}
|
|
311
319
|
}
|
|
312
|
-
const message = validationMode === "STRICT_AFTER_COMPATIBLE"
|
|
313
|
-
? `Would you like to execute these optional changes against ${databaseId} in your CloudSQL instance ${instanceName}?`
|
|
314
|
-
: `Would you like to execute these changes against ${databaseId} in your CloudSQL instance ${instanceName}?`;
|
|
315
|
-
let executeChangePrompt = "Execute changes";
|
|
316
320
|
if (validationMode === "STRICT_AFTER_COMPATIBLE") {
|
|
317
|
-
|
|
321
|
+
choices.push({ name: "Skip them", value: "none" });
|
|
318
322
|
}
|
|
319
|
-
|
|
320
|
-
|
|
323
|
+
else {
|
|
324
|
+
choices.push({ name: "Abort", value: "abort" });
|
|
321
325
|
}
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
const ans = await (0, prompt_1.select)({
|
|
327
|
+
message: `Do you want to execute these SQL against ${instanceId}:${databaseId}?`,
|
|
328
|
+
choices: choices,
|
|
329
|
+
default: defaultChoice,
|
|
330
|
+
});
|
|
331
|
+
if (ans === "abort") {
|
|
332
|
+
throw new error_1.FirebaseError("Command aborted.");
|
|
333
|
+
}
|
|
334
|
+
return ans;
|
|
328
335
|
}
|
|
329
336
|
if (!validateOnly) {
|
|
330
337
|
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");
|
|
331
338
|
}
|
|
332
339
|
else if (options.force) {
|
|
333
|
-
return
|
|
340
|
+
return defaultChoice;
|
|
334
341
|
}
|
|
335
342
|
else if (!err.destructive) {
|
|
336
|
-
return
|
|
343
|
+
return defaultChoice;
|
|
337
344
|
}
|
|
338
345
|
else {
|
|
339
346
|
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");
|
|
@@ -362,7 +369,7 @@ async function deleteInvalidConnectors(invalidConnectors) {
|
|
|
362
369
|
}
|
|
363
370
|
function displayInvalidConnectors(invalidConnectors) {
|
|
364
371
|
const connectorIds = invalidConnectors.map((i) => i.split("/").pop()).join(", ");
|
|
365
|
-
(0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${connectorIds}.`);
|
|
372
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `The schema you are deploying is incompatible with the following existing connectors: ${clc.bold(connectorIds)}.`);
|
|
366
373
|
(0, utils_1.logLabeledWarning)("dataconnect", `This is a ${clc.red("breaking")} change and may break existing apps.`);
|
|
367
374
|
}
|
|
368
375
|
async function ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, linkIfNotConnected) {
|
|
@@ -424,50 +431,61 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceName, dat
|
|
|
424
431
|
}
|
|
425
432
|
}
|
|
426
433
|
exports.ensureServiceIsConnectedToCloudSql = ensureServiceIsConnectedToCloudSql;
|
|
427
|
-
function
|
|
434
|
+
function displayStartSchemaDiff(validationMode) {
|
|
435
|
+
switch (validationMode) {
|
|
436
|
+
case "COMPATIBLE":
|
|
437
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations to be compatible...`);
|
|
438
|
+
break;
|
|
439
|
+
case "STRICT":
|
|
440
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `Generating SQL schema migrations to match exactly...`);
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function displayNoSchemaDiff(instanceId, databaseId, validationMode) {
|
|
445
|
+
switch (validationMode) {
|
|
446
|
+
case "COMPATIBLE":
|
|
447
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema of ${instanceId}:${databaseId} is compatible with Data Connect Schema.`);
|
|
448
|
+
break;
|
|
449
|
+
case "STRICT":
|
|
450
|
+
(0, utils_1.logLabeledSuccess)("dataconnect", `Database schema of ${instanceId}:${databaseId} matches Data Connect Schema exactly.`);
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function displaySchemaChanges(error, validationMode) {
|
|
428
455
|
switch (error.violationType) {
|
|
429
456
|
case "INCOMPATIBLE_SCHEMA":
|
|
430
457
|
{
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
458
|
+
switch (validationMode) {
|
|
459
|
+
case "COMPATIBLE":
|
|
460
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `PostgreSQL schema is incompatible with the Data Connect Schema.
|
|
461
|
+
Those SQL statements will migrate it to be compatible:
|
|
462
|
+
|
|
463
|
+
${diffsToString(error.diffs)}
|
|
464
|
+
`);
|
|
465
|
+
break;
|
|
466
|
+
case "STRICT_AFTER_COMPATIBLE":
|
|
467
|
+
(0, utils_1.logLabeledBullet)("dataconnect", `PostgreSQL schema contains unused SQL objects not part of the Data Connect Schema.
|
|
468
|
+
Those SQL statements will migrate it to match exactly:
|
|
469
|
+
|
|
470
|
+
${diffsToString(error.diffs)}
|
|
471
|
+
`);
|
|
472
|
+
break;
|
|
473
|
+
case "STRICT":
|
|
474
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `PostgreSQL schema does not match the Data Connect Schema.
|
|
475
|
+
Those SQL statements will migrate it to match exactly:
|
|
476
|
+
|
|
477
|
+
${diffsToString(error.diffs)}
|
|
478
|
+
`);
|
|
479
|
+
break;
|
|
441
480
|
}
|
|
442
|
-
else if (validationMode === "STRICT_AFTER_COMPATIBLE") {
|
|
443
|
-
message =
|
|
444
|
-
"Your new application schema is compatible with the schema of your PostgreSQL database " +
|
|
445
|
-
databaseId +
|
|
446
|
-
" in your CloudSQL instance " +
|
|
447
|
-
instanceName +
|
|
448
|
-
", but contains unused tables or columns. " +
|
|
449
|
-
"The following optional SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
450
|
-
error.diffs.map(toString).join("\n");
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
message =
|
|
454
|
-
"Your PostgreSQL database " +
|
|
455
|
-
databaseId +
|
|
456
|
-
" in your CloudSQL instance " +
|
|
457
|
-
instanceName +
|
|
458
|
-
" must be migrated in order to match your application schema. " +
|
|
459
|
-
"The following SQL statements will migrate your database schema to match your new Data Connect schema.\n" +
|
|
460
|
-
error.diffs.map(toString).join("\n");
|
|
461
|
-
}
|
|
462
|
-
(0, utils_1.logLabeledWarning)("dataconnect", message);
|
|
463
481
|
}
|
|
464
482
|
break;
|
|
465
483
|
case "INACCESSIBLE_SCHEMA":
|
|
466
484
|
{
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
485
|
+
(0, utils_1.logLabeledWarning)("dataconnect", `Cannot access CloudSQL database to validate schema.
|
|
486
|
+
Here is the complete expected SQL schema:
|
|
487
|
+
${diffsToString(error.diffs)}
|
|
488
|
+
`);
|
|
471
489
|
(0, utils_1.logLabeledWarning)("dataconnect", "Some SQL resources may already exist.");
|
|
472
490
|
}
|
|
473
491
|
break;
|
|
@@ -475,6 +493,12 @@ function displaySchemaChanges(error, validationMode, instanceName, databaseId) {
|
|
|
475
493
|
throw new error_1.FirebaseError(`Unknown schema violation type: ${error.violationType}, IncompatibleSqlSchemaError: ${error}`);
|
|
476
494
|
}
|
|
477
495
|
}
|
|
478
|
-
function
|
|
496
|
+
function requireSuperUser(diff) {
|
|
497
|
+
return diff.sql.startsWith("CREATE EXTENSION") || diff.sql.startsWith("CREATE SCHEMA");
|
|
498
|
+
}
|
|
499
|
+
function diffsToString(diffs) {
|
|
500
|
+
return diffs.map(diffToString).join("\n\n");
|
|
501
|
+
}
|
|
502
|
+
function diffToString(diff) {
|
|
479
503
|
return `\/** ${diff.destructive ? clc.red("Destructive: ") : ""}${diff.description}*\/\n${(0, sql_formatter_1.format)(diff.sql, { language: "postgresql" })}`;
|
|
480
504
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const clc = require("colorette");
|
|
4
4
|
const load_1 = require("../../dataconnect/load");
|
|
5
|
-
const fileUtils_1 = require("../../dataconnect/fileUtils");
|
|
6
5
|
const logger_1 = require("../../logger");
|
|
7
6
|
const utils = require("../../utils");
|
|
8
7
|
const projectUtils_1 = require("../../projectUtils");
|
|
@@ -26,9 +25,8 @@ async function default_1(context, options) {
|
|
|
26
25
|
}
|
|
27
26
|
await (0, ensureApis_1.ensureApis)(projectId);
|
|
28
27
|
await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
|
|
29
|
-
const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
|
|
30
28
|
const filters = (0, filters_1.getResourceFilters)(options);
|
|
31
|
-
const serviceInfos = await
|
|
29
|
+
const serviceInfos = await (0, load_1.loadAll)(projectId, options.config);
|
|
32
30
|
for (const si of serviceInfos) {
|
|
33
31
|
si.deploymentMetadata = await (0, build_1.build)(options, si.sourceDirectory, options.dryRun);
|
|
34
32
|
}
|
|
@@ -6,6 +6,7 @@ const prompts_1 = require("../../dataconnect/prompts");
|
|
|
6
6
|
const schemaMigration_1 = require("../../dataconnect/schemaMigration");
|
|
7
7
|
const projectUtils_1 = require("../../projectUtils");
|
|
8
8
|
const names_1 = require("../../dataconnect/names");
|
|
9
|
+
const logger_1 = require("../../logger");
|
|
9
10
|
async function default_1(context, options) {
|
|
10
11
|
const project = (0, projectUtils_1.needProjectId)(options);
|
|
11
12
|
const serviceInfos = context.dataconnect.serviceInfos;
|
|
@@ -24,19 +25,7 @@ async function default_1(context, options) {
|
|
|
24
25
|
validationMode: (_d = (_c = (_b = (_a = s.dataConnectYaml) === null || _a === void 0 ? void 0 : _a.schema) === null || _b === void 0 ? void 0 : _b.datasource) === null || _c === void 0 ? void 0 : _c.postgresql) === null || _d === void 0 ? void 0 : _d.schemaValidation,
|
|
25
26
|
});
|
|
26
27
|
});
|
|
27
|
-
|
|
28
|
-
for (const s of wantSchemas) {
|
|
29
|
-
await (0, schemaMigration_1.migrateSchema)({
|
|
30
|
-
options,
|
|
31
|
-
schema: s.schema,
|
|
32
|
-
validateOnly: false,
|
|
33
|
-
schemaValidation: s.validationMode,
|
|
34
|
-
});
|
|
35
|
-
utils.logLabeledSuccess("dataconnect", `Migrated schema ${s.schema.name}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
let wantConnectors = [];
|
|
39
|
-
wantConnectors = wantConnectors.concat(...serviceInfos.map((si) => si.connectorInfo
|
|
28
|
+
const wantConnectors = serviceInfos.flatMap((si) => si.connectorInfo
|
|
40
29
|
.filter((c) => {
|
|
41
30
|
return (!filters ||
|
|
42
31
|
filters.some((f) => {
|
|
@@ -44,22 +33,39 @@ async function default_1(context, options) {
|
|
|
44
33
|
(f.connectorId === c.connectorYaml.connectorId || f.fullService));
|
|
45
34
|
}));
|
|
46
35
|
})
|
|
47
|
-
.map((c) => c.connector))
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
? []
|
|
51
|
-
: haveConnectors.filter((h) => !wantConnectors.some((w) => w.name === h.name));
|
|
52
|
-
if (wantConnectors.length) {
|
|
53
|
-
await Promise.all(wantConnectors.map(async (c) => {
|
|
36
|
+
.map((c) => c.connector));
|
|
37
|
+
const remainingConnectors = await Promise.all(wantConnectors.map(async (c) => {
|
|
38
|
+
try {
|
|
54
39
|
await (0, client_1.upsertConnector)(c);
|
|
55
|
-
utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
|
|
56
|
-
}));
|
|
57
|
-
for (const c of connectorsToDelete) {
|
|
58
|
-
await (0, prompts_1.promptDeleteConnector)(options, c.name);
|
|
59
40
|
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
logger_1.logger.debug("Error pre-deploying connector", c.name, err);
|
|
43
|
+
return c;
|
|
44
|
+
}
|
|
45
|
+
utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
|
|
46
|
+
return undefined;
|
|
47
|
+
}));
|
|
48
|
+
for (const s of wantSchemas) {
|
|
49
|
+
await (0, schemaMigration_1.migrateSchema)({
|
|
50
|
+
options,
|
|
51
|
+
schema: s.schema,
|
|
52
|
+
validateOnly: false,
|
|
53
|
+
schemaValidation: s.validationMode,
|
|
54
|
+
});
|
|
55
|
+
utils.logLabeledSuccess("dataconnect", `Migrated schema ${s.schema.name}`);
|
|
60
56
|
}
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
await Promise.all(remainingConnectors.map(async (c) => {
|
|
58
|
+
if (c) {
|
|
59
|
+
await (0, client_1.upsertConnector)(c);
|
|
60
|
+
utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
const allConnectors = await deployedConnectors(serviceInfos);
|
|
64
|
+
const connectorsToDelete = filters
|
|
65
|
+
? []
|
|
66
|
+
: allConnectors.filter((h) => !wantConnectors.some((w) => w.name === h.name));
|
|
67
|
+
for (const c of connectorsToDelete) {
|
|
68
|
+
await (0, prompts_1.promptDeleteConnector)(options, c.name);
|
|
63
69
|
}
|
|
64
70
|
let consolePath = "/dataconnect";
|
|
65
71
|
if (serviceInfos.length === 1) {
|
|
@@ -73,7 +79,7 @@ async function default_1(context, options) {
|
|
|
73
79
|
return;
|
|
74
80
|
}
|
|
75
81
|
exports.default = default_1;
|
|
76
|
-
async function
|
|
82
|
+
async function deployedConnectors(serviceInfos) {
|
|
77
83
|
let connectors = [];
|
|
78
84
|
for (const si of serviceInfos) {
|
|
79
85
|
connectors = connectors.concat(await (0, client_1.listConnectors)(si.serviceName));
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureAllRequiredAPIsEnabled = exports.warnIfNewGenkitFunctionIsMissingSecrets = exports.loadCodebases = exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
|
+
const proto = require("../../gcp/proto");
|
|
5
6
|
const backend = require("./backend");
|
|
6
7
|
const build = require("./build");
|
|
7
8
|
const ensureApiEnabled = require("../../ensureApiEnabled");
|
|
@@ -76,6 +77,7 @@ async function prepare(context, options, payload) {
|
|
|
76
77
|
projectId: projectId,
|
|
77
78
|
projectAlias: options.projectAlias,
|
|
78
79
|
};
|
|
80
|
+
proto.convertIfPresent(userEnvOpt, config, "configDir", (cd) => options.config.path(cd));
|
|
79
81
|
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
80
82
|
const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
|
|
81
83
|
const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend({
|
|
@@ -5,6 +5,7 @@ const clc = require("colorette");
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
7
|
const fsConfig = require("../firestore/fsConfig");
|
|
8
|
+
const proto = require("../gcp/proto");
|
|
8
9
|
const logger_1 = require("../logger");
|
|
9
10
|
const track_1 = require("../track");
|
|
10
11
|
const utils = require("../utils");
|
|
@@ -43,7 +44,7 @@ const firestoreEmulator_1 = require("./firestoreEmulator");
|
|
|
43
44
|
const hostingEmulator_1 = require("./hostingEmulator");
|
|
44
45
|
const pubsubEmulator_1 = require("./pubsubEmulator");
|
|
45
46
|
const storage_1 = require("./storage");
|
|
46
|
-
const
|
|
47
|
+
const load_1 = require("../dataconnect/load");
|
|
47
48
|
const tasksEmulator_1 = require("./tasksEmulator");
|
|
48
49
|
const apphosting_1 = require("./apphosting");
|
|
49
50
|
const webhook_1 = require("../dataconnect/webhook");
|
|
@@ -344,7 +345,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
344
345
|
if (runtime && !(0, supported_1.isRuntime)(runtime)) {
|
|
345
346
|
throw new error_1.FirebaseError(`Cannot load functions from ${functionsDir} because it has invalid runtime ${runtime}`);
|
|
346
347
|
}
|
|
347
|
-
|
|
348
|
+
const backend = {
|
|
348
349
|
functionsDir,
|
|
349
350
|
runtime,
|
|
350
351
|
codebase: cfg.codebase,
|
|
@@ -353,7 +354,9 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
353
354
|
secretEnv: [],
|
|
354
355
|
predefinedTriggers: options.extDevTriggers,
|
|
355
356
|
ignore: cfg.ignore,
|
|
356
|
-
}
|
|
357
|
+
};
|
|
358
|
+
proto.convertIfPresent(backend, cfg, "configDir", (cd) => path.join(projectDir, cd));
|
|
359
|
+
emulatableBackends.push(backend);
|
|
357
360
|
}
|
|
358
361
|
}
|
|
359
362
|
if (extensionEmulator) {
|
|
@@ -556,7 +559,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
|
|
|
556
559
|
await startEmulator(pubsubEmulator);
|
|
557
560
|
}
|
|
558
561
|
if (listenForEmulator.dataconnect) {
|
|
559
|
-
const config = (0,
|
|
562
|
+
const config = (0, load_1.readFirebaseJson)(options.config);
|
|
560
563
|
if (!config.length) {
|
|
561
564
|
throw new error_1.FirebaseError("No Data Connect service found in firebase.json");
|
|
562
565
|
}
|