summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Fred K. Schott <fkschott@gmail.com> 2024-03-05 11:44:19 -0800
committerGravatar GitHub <noreply@github.com> 2024-03-05 14:44:19 -0500
commit24bc1690700709392b2c0c60222e885fc54b145a (patch)
tree88bba35edfbf649dfe6a9d4ba47bc6086b1ae130
parent2809d136000cd694c1003392c5cd99441ee26d35 (diff)
downloadastro-24bc1690700709392b2c0c60222e885fc54b145a.tar.gz
astro-24bc1690700709392b2c0c60222e885fc54b145a.tar.zst
astro-24bc1690700709392b2c0c60222e885fc54b145a.zip
add back data loss confirmation handling (#10330)
-rw-r--r--packages/db/package.json1
-rw-r--r--packages/db/src/core/cli/commands/push/index.ts12
-rw-r--r--packages/db/src/core/cli/commands/verify/index.ts27
-rw-r--r--packages/db/src/core/cli/migration-queries.ts38
-rw-r--r--packages/db/test/fixtures/ticketing-example/db/config.ts2
-rw-r--r--pnpm-lock.yaml3
6 files changed, 61 insertions, 22 deletions
diff --git a/packages/db/package.json b/packages/db/package.json
index 2533aed5b..a9341a4d5 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -74,6 +74,7 @@
"open": "^10.0.3",
"ora": "^7.0.1",
"prompts": "^2.4.2",
+ "strip-ansi": "^7.1.0",
"yargs-parser": "^21.1.1",
"zod": "^3.22.4"
},
diff --git a/packages/db/src/core/cli/commands/push/index.ts b/packages/db/src/core/cli/commands/push/index.ts
index 59c3a014c..f7359a8b3 100644
--- a/packages/db/src/core/cli/commands/push/index.ts
+++ b/packages/db/src/core/cli/commands/push/index.ts
@@ -6,9 +6,11 @@ import { getRemoteDatabaseUrl } from '../../../utils.js';
import {
createCurrentSnapshot,
createEmptySnapshot,
+ formatDataLossMessage,
getMigrationQueries,
getProductionCurrentSnapshot,
} from '../../migration-queries.js';
+import { red } from 'kleur/colors';
export async function cmd({
dbConfig,
@@ -24,7 +26,7 @@ export async function cmd({
const productionSnapshot = await getProductionCurrentSnapshot({ appToken: appToken.token });
const currentSnapshot = createCurrentSnapshot(dbConfig);
const isFromScratch = isForceReset || JSON.stringify(productionSnapshot) === '{}';
- const { queries: migrationQueries } = await getMigrationQueries({
+ const { queries: migrationQueries, confirmations } = await getMigrationQueries({
oldSnapshot: isFromScratch ? createEmptySnapshot() : productionSnapshot,
newSnapshot: currentSnapshot,
});
@@ -35,6 +37,14 @@ export async function cmd({
} else {
console.log(`Database schema is out of date.`);
}
+
+ if (isForceReset) {
+ console.log(`Force-pushing to the database. All existing data will be erased.`);
+ } else if (confirmations.length > 0) {
+ console.log('\n' + formatDataLossMessage(confirmations) + '\n');
+ throw new Error('Exiting.');
+ }
+
if (isDryRun) {
console.log('Statements:', JSON.stringify(migrationQueries, undefined, 2));
} else {
diff --git a/packages/db/src/core/cli/commands/verify/index.ts b/packages/db/src/core/cli/commands/verify/index.ts
index 55e45c378..4bf8683b9 100644
--- a/packages/db/src/core/cli/commands/verify/index.ts
+++ b/packages/db/src/core/cli/commands/verify/index.ts
@@ -5,6 +5,7 @@ import type { DBConfig } from '../../../types.js';
import {
createCurrentSnapshot,
createEmptySnapshot,
+ formatDataLossMessage,
getMigrationQueries,
getProductionCurrentSnapshot,
} from '../../migration-queries.js';
@@ -17,21 +18,39 @@ export async function cmd({
dbConfig: DBConfig;
flags: Arguments;
}) {
+ const isJson = flags.json;
const appToken = await getManagedAppTokenOrExit(flags.token);
const productionSnapshot = await getProductionCurrentSnapshot({ appToken: appToken.token });
const currentSnapshot = createCurrentSnapshot(dbConfig);
- const { queries: migrationQueries } = await getMigrationQueries({
+ const { queries: migrationQueries, confirmations } = await getMigrationQueries({
oldSnapshot:
JSON.stringify(productionSnapshot) !== '{}' ? productionSnapshot : createEmptySnapshot(),
newSnapshot: currentSnapshot,
});
+ const result = { exitCode: 0, message: '', code: '', data: undefined as unknown };
if (migrationQueries.length === 0) {
- console.log(`Database schema is up to date.`);
+ result.code = 'MATCH';
+ result.message = `Database schema is up to date.`;
} else {
- console.log(`Database schema is out of date.`);
- console.log(`Run 'astro db push' to push up your latest changes.`);
+ result.code = 'NO_MATCH';
+ result.message = `Database schema is out of date.\nRun 'astro db push' to push up your latest changes.`;
+ }
+
+
+ if (confirmations.length > 0) {
+ result.code = 'DATA_LOSS';
+ result.exitCode = 1;
+ result.data = confirmations;
+ result.message = formatDataLossMessage(confirmations, !isJson);
+ }
+
+ if (isJson) {
+ console.log(JSON.stringify(result));
+ } else {
+ console.log(result.message);
}
await appToken.destroy();
+ process.exit(result.exitCode);
}
diff --git a/packages/db/src/core/cli/migration-queries.ts b/packages/db/src/core/cli/migration-queries.ts
index 09984b8c6..f1b1047cb 100644
--- a/packages/db/src/core/cli/migration-queries.ts
+++ b/packages/db/src/core/cli/migration-queries.ts
@@ -1,3 +1,4 @@
+import stripAnsi from 'strip-ansi';
import deepDiff from 'deep-diff';
import { SQLiteAsyncDialect } from 'drizzle-orm/sqlite-core';
import * as color from 'kleur/colors';
@@ -147,21 +148,12 @@ export async function getCollectionChangeQueries({
if (dataLossCheck.dataLoss) {
const { reason, columnName } = dataLossCheck;
const reasonMsgs: Record<DataLossReason, string> = {
- 'added-required': `New column ${color.bold(
+ 'added-required': `You added new required column '${color.bold(
collectionName + '.' + columnName
- )} is required with no default value.\nThis requires deleting existing data in the ${color.bold(
- collectionName
- )} collection.`,
- 'added-unique': `New column ${color.bold(
+ )}' with no default value.\n This cannot be executed on an existing table.`,
+ 'updated-type': `Updating existing column ${color.bold(
collectionName + '.' + columnName
- )} is marked as unique.\nThis requires deleting existing data in the ${color.bold(
- collectionName
- )} collection.`,
- 'updated-type': `Updated column ${color.bold(
- collectionName + '.' + columnName
- )} cannot convert data to new column data type.\nThis requires deleting existing data in the ${color.bold(
- collectionName
- )} collection.`,
+ )} to a new type that cannot be handled automatically.`,
};
confirmations.push(reasonMsgs[reason]);
}
@@ -319,7 +311,7 @@ function canAlterTableDropColumn(column: DBColumn) {
return true;
}
-type DataLossReason = 'added-required' | 'added-unique' | 'updated-type';
+type DataLossReason = 'added-required' | 'updated-type';
type DataLossResponse =
| { dataLoss: false }
| { dataLoss: true; columnName: string; reason: DataLossReason };
@@ -335,9 +327,6 @@ function canRecreateTableWithoutDataLoss(
if (!a.schema.optional && !hasDefault(a)) {
return { dataLoss: true, columnName, reason: 'added-required' };
}
- if (!a.schema.optional && a.schema.unique) {
- return { dataLoss: true, columnName, reason: 'added-unique' };
- }
}
for (const [columnName, u] of Object.entries(updated)) {
if (u.old.type !== u.new.type && !canChangeTypeWithoutQuery(u.old, u.new)) {
@@ -454,3 +443,18 @@ export function createCurrentSnapshot({ tables = {} }: DBConfig): DBSnapshot {
export function createEmptySnapshot(): DBSnapshot {
return { experimentalVersion: 1, schema: {} };
}
+
+export function formatDataLossMessage(confirmations: string[], isColor = true): string {
+ const messages = [];
+ messages.push(color.red('✖ We found some schema changes that cannot be handled automatically:'));
+ messages.push(``);
+ messages.push(...confirmations.map((m, i) => color.red(` (${i + 1}) `) + m));
+ messages.push(``);
+ messages.push(`To resolve, revert these changes or update your schema, and re-run the command.`);
+ messages.push(`You may also run 'astro db push --force-reset' to ignore all warnings and force-push your local database schema to production instead. All data will be lost and the database will be reset.`);
+ let finalMessage = messages.join('\n');
+ if (!isColor) {
+ finalMessage = stripAnsi(finalMessage);
+ }
+ return finalMessage;
+}
diff --git a/packages/db/test/fixtures/ticketing-example/db/config.ts b/packages/db/test/fixtures/ticketing-example/db/config.ts
index f8148eaed..09ed4d273 100644
--- a/packages/db/test/fixtures/ticketing-example/db/config.ts
+++ b/packages/db/test/fixtures/ticketing-example/db/config.ts
@@ -10,6 +10,8 @@ const Event = defineTable({
ticketPrice: column.number(),
date: column.date(),
location: column.text(),
+ author3: column.text(),
+ author4: column.text(),
},
});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 86f8068c8..9a2a8b6be 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3841,6 +3841,9 @@ importers:
prompts:
specifier: ^2.4.2
version: 2.4.2
+ strip-ansi:
+ specifier: ^7.1.0
+ version: 7.1.0
yargs-parser:
specifier: ^21.1.1
version: 21.1.1