From 2412b4b070b314b95ba228ad5147a0d6095e62bb Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 15 Aug 2025 09:51:19 +0800 Subject: [PATCH 1/4] feat: add "migrate resolve" command to CLI --- README.md | 2 +- packages/cli/src/actions/migrate.ts | 29 +++++++++++++++++++++++++++++ packages/cli/src/index.ts | 11 ++++++++++- packages/cli/test/migrate.test.ts | 16 ++++++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 77bdfe1f..db969d96 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ npm install -D @types/better-sqlite3 For Postgres: ```bash -npm install pg pg-connection-string +npm install pg npm install -D @types/pg ``` diff --git a/packages/cli/src/actions/migrate.ts b/packages/cli/src/actions/migrate.ts index 6a667a16..896cc991 100644 --- a/packages/cli/src/actions/migrate.ts +++ b/packages/cli/src/actions/migrate.ts @@ -1,5 +1,6 @@ import fs from 'node:fs'; import path from 'node:path'; +import { CliError } from '../cli-error'; import { execPackage } from '../utils/exec-utils'; import { generateTempPrismaSchema, getSchemaFile } from './action-utils'; @@ -21,6 +22,11 @@ type DeployOptions = CommonOptions; type StatusOptions = CommonOptions; +type ResolveOptions = CommonOptions & { + applied?: string; + rolledBack?: string; +}; + /** * CLI action for migration-related commands */ @@ -46,6 +52,10 @@ export async function run(command: string, options: CommonOptions) { case 'status': await runStatus(prismaSchemaFile, options as StatusOptions); break; + + case 'resolve': + await runResolve(prismaSchemaFile, options as ResolveOptions); + break; } } finally { if (fs.existsSync(prismaSchemaFile)) { @@ -100,6 +110,25 @@ async function runStatus(prismaSchemaFile: string, _options: StatusOptions) { } } +async function runResolve(prismaSchemaFile: string, options: ResolveOptions) { + if (!options.applied && !options.rolledBack) { + throw new CliError('Either --applied or --rolled-back option must be provided'); + } + + try { + const cmd = [ + 'prisma migrate resolve', + ` --schema "${prismaSchemaFile}"`, + options.applied ? ` --applied ${options.applied}` : '', + options.rolledBack ? ` --rolled-back ${options.rolledBack}` : '', + ].join(''); + + await execPackage(cmd); + } catch (err) { + handleSubProcessError(err); + } +} + function handleSubProcessError(err: unknown) { if (err instanceof Error && 'status' in err && typeof err.status === 'number') { process.exit(err.status); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index fc154121..e078c0a4 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -95,7 +95,16 @@ export function createProgram() { .command('status') .addOption(schemaOption) .addOption(migrationsOption) - .description('check the status of your database migrations.') + .description('Check the status of your database migrations.') + .action((options) => migrateAction('status', options)); + + migrateCommand + .command('resolve') + .addOption(schemaOption) + .addOption(migrationsOption) + .addOption(new Option('--applied ', 'record a specific migration as applied')) + .addOption(new Option('--rolled-back ', 'record a specific migration as rolled back')) + .description('Resolve issues with database migrations in deployment databases') .action((options) => migrateAction('status', options)); const dbCommand = program.command('db').description('Manage your database schema during development.'); diff --git a/packages/cli/test/migrate.test.ts b/packages/cli/test/migrate.test.ts index 85c2a928..e5e5fdc6 100644 --- a/packages/cli/test/migrate.test.ts +++ b/packages/cli/test/migrate.test.ts @@ -38,4 +38,20 @@ describe('CLI migrate commands test', () => { runCli('migrate dev --name init', workDir); runCli('migrate status', workDir); }); + + it('supports migrate resolve', () => { + const workDir = createProject(model); + runCli('migrate dev --name init', workDir); + + // find the migration record "timestamp_init" + const migrationRecords = fs.readdirSync(path.join(workDir, 'zenstack/migrations')); + const migration = migrationRecords.find((f) => f.endsWith('_init')); + expect(migration).toBeDefined(); + + // --rolled-back + runCli(`migrate resolve --rolled-back ${migration}`, workDir); + + // --applied + runCli(`migrate resolve --applied ${migration}`, workDir); + }); }); From eff9c80b516b4eea96f60b06fc99dbd04c091eda Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Fri, 15 Aug 2025 15:53:06 +0800 Subject: [PATCH 2/4] Update packages/cli/src/index.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/cli/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e078c0a4..fd5ad01e 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -105,7 +105,7 @@ export function createProgram() { .addOption(new Option('--applied ', 'record a specific migration as applied')) .addOption(new Option('--rolled-back ', 'record a specific migration as rolled back')) .description('Resolve issues with database migrations in deployment databases') - .action((options) => migrateAction('status', options)); + .action((options) => migrateAction('resolve', options)); const dbCommand = program.command('db').description('Manage your database schema during development.'); From e8a4c693c4c7bc2b1334f11d99f0ee9222ef1a9f Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 15 Aug 2025 15:56:18 +0800 Subject: [PATCH 3/4] add test --- packages/cli/test/migrate.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/cli/test/migrate.test.ts b/packages/cli/test/migrate.test.ts index e5e5fdc6..1a16c363 100644 --- a/packages/cli/test/migrate.test.ts +++ b/packages/cli/test/migrate.test.ts @@ -54,4 +54,9 @@ describe('CLI migrate commands test', () => { // --applied runCli(`migrate resolve --applied ${migration}`, workDir); }); + + it('should throw error when neither applied nor rolled-back is provided', () => { + const workDir = createProject(model); + expect(() => runCli('migrate resolve', workDir)).toThrow(); + }); }); From 372b6984edaad503dd96afae06602c72a91f3d1a Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 15 Aug 2025 16:53:57 +0800 Subject: [PATCH 4/4] update test --- packages/cli/test/migrate.test.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/cli/test/migrate.test.ts b/packages/cli/test/migrate.test.ts index 1a16c363..56a0fec8 100644 --- a/packages/cli/test/migrate.test.ts +++ b/packages/cli/test/migrate.test.ts @@ -46,7 +46,17 @@ describe('CLI migrate commands test', () => { // find the migration record "timestamp_init" const migrationRecords = fs.readdirSync(path.join(workDir, 'zenstack/migrations')); const migration = migrationRecords.find((f) => f.endsWith('_init')); - expect(migration).toBeDefined(); + + // force a migration failure + fs.writeFileSync(path.join(workDir, 'zenstack/migrations', migration!, 'migration.sql'), 'invalid content'); + + // redeploy the migration, which will fail + fs.rmSync(path.join(workDir, 'zenstack/dev.db'), { force: true }); + try { + runCli('migrate deploy', workDir); + } catch { + // noop + } // --rolled-back runCli(`migrate resolve --rolled-back ${migration}`, workDir);