Skip to content

Commit 1ac89c1

Browse files
ymc9Copilot
andauthored
feat: add "migrate resolve" command to CLI (#171)
* feat: add "migrate resolve" command to CLI * Update packages/cli/src/index.ts Co-authored-by: Copilot <[email protected]> * add test * update test --------- Co-authored-by: Copilot <[email protected]>
1 parent 904a8aa commit 1ac89c1

File tree

4 files changed

+71
-2
lines changed

4 files changed

+71
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ npm install -D @types/better-sqlite3
109109
For Postgres:
110110

111111
```bash
112-
npm install pg pg-connection-string
112+
npm install pg
113113
npm install -D @types/pg
114114
```
115115

packages/cli/src/actions/migrate.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs from 'node:fs';
22
import path from 'node:path';
3+
import { CliError } from '../cli-error';
34
import { execPackage } from '../utils/exec-utils';
45
import { generateTempPrismaSchema, getSchemaFile } from './action-utils';
56

@@ -21,6 +22,11 @@ type DeployOptions = CommonOptions;
2122

2223
type StatusOptions = CommonOptions;
2324

25+
type ResolveOptions = CommonOptions & {
26+
applied?: string;
27+
rolledBack?: string;
28+
};
29+
2430
/**
2531
* CLI action for migration-related commands
2632
*/
@@ -46,6 +52,10 @@ export async function run(command: string, options: CommonOptions) {
4652
case 'status':
4753
await runStatus(prismaSchemaFile, options as StatusOptions);
4854
break;
55+
56+
case 'resolve':
57+
await runResolve(prismaSchemaFile, options as ResolveOptions);
58+
break;
4959
}
5060
} finally {
5161
if (fs.existsSync(prismaSchemaFile)) {
@@ -100,6 +110,25 @@ async function runStatus(prismaSchemaFile: string, _options: StatusOptions) {
100110
}
101111
}
102112

113+
async function runResolve(prismaSchemaFile: string, options: ResolveOptions) {
114+
if (!options.applied && !options.rolledBack) {
115+
throw new CliError('Either --applied or --rolled-back option must be provided');
116+
}
117+
118+
try {
119+
const cmd = [
120+
'prisma migrate resolve',
121+
` --schema "${prismaSchemaFile}"`,
122+
options.applied ? ` --applied ${options.applied}` : '',
123+
options.rolledBack ? ` --rolled-back ${options.rolledBack}` : '',
124+
].join('');
125+
126+
await execPackage(cmd);
127+
} catch (err) {
128+
handleSubProcessError(err);
129+
}
130+
}
131+
103132
function handleSubProcessError(err: unknown) {
104133
if (err instanceof Error && 'status' in err && typeof err.status === 'number') {
105134
process.exit(err.status);

packages/cli/src/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,18 @@ export function createProgram() {
9595
.command('status')
9696
.addOption(schemaOption)
9797
.addOption(migrationsOption)
98-
.description('check the status of your database migrations.')
98+
.description('Check the status of your database migrations.')
9999
.action((options) => migrateAction('status', options));
100100

101+
migrateCommand
102+
.command('resolve')
103+
.addOption(schemaOption)
104+
.addOption(migrationsOption)
105+
.addOption(new Option('--applied <migration>', 'record a specific migration as applied'))
106+
.addOption(new Option('--rolled-back <migration>', 'record a specific migration as rolled back'))
107+
.description('Resolve issues with database migrations in deployment databases')
108+
.action((options) => migrateAction('resolve', options));
109+
101110
const dbCommand = program.command('db').description('Manage your database schema during development.');
102111

103112
dbCommand

packages/cli/test/migrate.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,35 @@ describe('CLI migrate commands test', () => {
3838
runCli('migrate dev --name init', workDir);
3939
runCli('migrate status', workDir);
4040
});
41+
42+
it('supports migrate resolve', () => {
43+
const workDir = createProject(model);
44+
runCli('migrate dev --name init', workDir);
45+
46+
// find the migration record "timestamp_init"
47+
const migrationRecords = fs.readdirSync(path.join(workDir, 'zenstack/migrations'));
48+
const migration = migrationRecords.find((f) => f.endsWith('_init'));
49+
50+
// force a migration failure
51+
fs.writeFileSync(path.join(workDir, 'zenstack/migrations', migration!, 'migration.sql'), 'invalid content');
52+
53+
// redeploy the migration, which will fail
54+
fs.rmSync(path.join(workDir, 'zenstack/dev.db'), { force: true });
55+
try {
56+
runCli('migrate deploy', workDir);
57+
} catch {
58+
// noop
59+
}
60+
61+
// --rolled-back
62+
runCli(`migrate resolve --rolled-back ${migration}`, workDir);
63+
64+
// --applied
65+
runCli(`migrate resolve --applied ${migration}`, workDir);
66+
});
67+
68+
it('should throw error when neither applied nor rolled-back is provided', () => {
69+
const workDir = createProject(model);
70+
expect(() => runCli('migrate resolve', workDir)).toThrow();
71+
});
4172
});

0 commit comments

Comments
 (0)