@zenstackhq/cli 3.0.0-alpha.2 → 3.0.0-alpha.21

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.
@@ -7,9 +7,7 @@ import path from 'node:path';
7
7
  export async function run(projectPath: string) {
8
8
  const packages = await getZenStackPackages(projectPath);
9
9
  if (!packages) {
10
- console.error(
11
- 'Unable to locate package.json. Are you in a valid project directory?'
12
- );
10
+ console.error('Unable to locate package.json. Are you in a valid project directory?');
13
11
  return;
14
12
  }
15
13
 
@@ -23,17 +21,11 @@ export async function run(projectPath: string) {
23
21
  }
24
22
 
25
23
  if (versions.size > 1) {
26
- console.warn(
27
- colors.yellow(
28
- 'WARNING: Multiple versions of Zenstack packages detected. This may cause issues.'
29
- )
30
- );
24
+ console.warn(colors.yellow('WARNING: Multiple versions of Zenstack packages detected. This may cause issues.'));
31
25
  }
32
26
  }
33
27
 
34
- async function getZenStackPackages(
35
- projectPath: string
36
- ): Promise<Array<{ pkg: string; version: string | undefined }>> {
28
+ async function getZenStackPackages(projectPath: string): Promise<Array<{ pkg: string; version: string | undefined }>> {
37
29
  let pkgJson: {
38
30
  dependencies: Record<string, unknown>;
39
31
  devDependencies: Record<string, unknown>;
@@ -45,17 +37,16 @@ async function getZenStackPackages(
45
37
  with: { type: 'json' },
46
38
  })
47
39
  ).default;
48
- } catch (err) {
40
+ } catch {
49
41
  return [];
50
42
  }
51
43
 
52
44
  const packages = Array.from(
53
45
  new Set(
54
- [
55
- ...Object.keys(pkgJson.dependencies ?? {}),
56
- ...Object.keys(pkgJson.devDependencies ?? {}),
57
- ].filter((p) => p.startsWith('@zenstackhq/') || p === 'zenstack')
58
- )
46
+ [...Object.keys(pkgJson.dependencies ?? {}), ...Object.keys(pkgJson.devDependencies ?? {})].filter(
47
+ (p) => p.startsWith('@zenstackhq/') || p === 'zenstack',
48
+ ),
49
+ ),
59
50
  ).sort();
60
51
 
61
52
  const result = await Promise.all(
@@ -70,7 +61,7 @@ async function getZenStackPackages(
70
61
  } catch {
71
62
  return { pkg, version: undefined };
72
63
  }
73
- })
64
+ }),
74
65
  );
75
66
 
76
67
  return result;
@@ -28,9 +28,7 @@ export async function run(projectPath: string) {
28
28
  ...(pkg.dev ? [pm.agent === 'yarn' ? '--dev' : '--save-dev'] : []),
29
29
  ]);
30
30
  if (!resolved) {
31
- throw new CliError(
32
- `Unable to determine how to install package "${pkg.name}". Please install it manually.`
33
- );
31
+ throw new CliError(`Unable to determine how to install package "${pkg.name}". Please install it manually.`);
34
32
  }
35
33
 
36
34
  const spinner = ora(`Installing "${pkg.name}"`).start();
@@ -51,32 +49,13 @@ export async function run(projectPath: string) {
51
49
  fs.mkdirSync(path.join(projectPath, generationFolder));
52
50
  }
53
51
 
54
- if (
55
- !fs.existsSync(
56
- path.join(projectPath, generationFolder, 'schema.zmodel')
57
- )
58
- ) {
59
- fs.writeFileSync(
60
- path.join(projectPath, generationFolder, 'schema.zmodel'),
61
- STARTER_ZMODEL
62
- );
52
+ if (!fs.existsSync(path.join(projectPath, generationFolder, 'schema.zmodel'))) {
53
+ fs.writeFileSync(path.join(projectPath, generationFolder, 'schema.zmodel'), STARTER_ZMODEL);
63
54
  } else {
64
- console.log(
65
- colors.yellow(
66
- 'Schema file already exists. Skipping generation of sample.'
67
- )
68
- );
55
+ console.log(colors.yellow('Schema file already exists. Skipping generation of sample.'));
69
56
  }
70
57
 
71
58
  console.log(colors.green('ZenStack project initialized successfully!'));
72
- console.log(
73
- colors.gray(
74
- `See "${generationFolder}/schema.zmodel" for your database schema.`
75
- )
76
- );
77
- console.log(
78
- colors.gray(
79
- 'Run `zenstack generate` to compile the the schema into a TypeScript file.'
80
- )
81
- );
59
+ console.log(colors.gray(`See "${generationFolder}/schema.zmodel" for your database schema.`));
60
+ console.log(colors.gray('Run `zenstack generate` to compile the the schema into a TypeScript file.'));
82
61
  }
@@ -1,109 +1,107 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
3
  import { execPackage } from '../utils/exec-utils';
3
- import { getSchemaFile } from './action-utils';
4
- import { run as runGenerate } from './generate';
4
+ import { generateTempPrismaSchema, getSchemaFile } from './action-utils';
5
5
 
6
6
  type CommonOptions = {
7
7
  schema?: string;
8
+ migrations?: string;
9
+ };
10
+
11
+ type DevOptions = CommonOptions & {
8
12
  name?: string;
13
+ createOnly?: boolean;
14
+ };
15
+
16
+ type ResetOptions = CommonOptions & {
17
+ force?: boolean;
9
18
  };
10
19
 
20
+ type DeployOptions = CommonOptions;
21
+
22
+ type StatusOptions = CommonOptions;
23
+
11
24
  /**
12
25
  * CLI action for migration-related commands
13
26
  */
14
27
  export async function run(command: string, options: CommonOptions) {
15
28
  const schemaFile = getSchemaFile(options.schema);
29
+ const prismaSchemaDir = options.migrations ? path.dirname(options.migrations) : undefined;
30
+ const prismaSchemaFile = await generateTempPrismaSchema(schemaFile, prismaSchemaDir);
31
+
32
+ try {
33
+ switch (command) {
34
+ case 'dev':
35
+ await runDev(prismaSchemaFile, options as DevOptions);
36
+ break;
16
37
 
17
- // run generate first
18
- await runGenerate({
19
- schema: schemaFile,
20
- silent: true,
21
- });
22
-
23
- const prismaSchemaFile = path.join(
24
- path.dirname(schemaFile),
25
- 'schema.prisma'
26
- );
27
-
28
- switch (command) {
29
- case 'dev':
30
- await runDev(prismaSchemaFile, options);
31
- break;
32
-
33
- case 'reset':
34
- await runReset(prismaSchemaFile, options as any);
35
- break;
36
-
37
- case 'deploy':
38
- await runDeploy(prismaSchemaFile, options);
39
- break;
40
-
41
- case 'status':
42
- await runStatus(prismaSchemaFile, options);
43
- break;
38
+ case 'reset':
39
+ await runReset(prismaSchemaFile, options as ResetOptions);
40
+ break;
41
+
42
+ case 'deploy':
43
+ await runDeploy(prismaSchemaFile, options as DeployOptions);
44
+ break;
45
+
46
+ case 'status':
47
+ await runStatus(prismaSchemaFile, options as StatusOptions);
48
+ break;
49
+ }
50
+ } finally {
51
+ if (fs.existsSync(prismaSchemaFile)) {
52
+ fs.unlinkSync(prismaSchemaFile);
53
+ }
44
54
  }
45
55
  }
46
56
 
47
- async function runDev(prismaSchemaFile: string, _options: unknown) {
57
+ async function runDev(prismaSchemaFile: string, options: DevOptions) {
48
58
  try {
49
- await execPackage(
50
- `prisma migrate dev --schema "${prismaSchemaFile}" --skip-generate`,
51
- {
52
- stdio: 'inherit',
53
- }
54
- );
59
+ const cmd = [
60
+ 'prisma migrate dev',
61
+ ` --schema "${prismaSchemaFile}"`,
62
+ ' --skip-generate',
63
+ options.name ? ` --name ${options.name}` : '',
64
+ options.createOnly ? ' --create-only' : '',
65
+ ].join('');
66
+
67
+ await execPackage(cmd);
55
68
  } catch (err) {
56
69
  handleSubProcessError(err);
57
70
  }
58
71
  }
59
72
 
60
- async function runReset(prismaSchemaFile: string, options: { force: boolean }) {
73
+ async function runReset(prismaSchemaFile: string, options: ResetOptions) {
61
74
  try {
62
- await execPackage(
63
- `prisma migrate reset --schema "${prismaSchemaFile}"${
64
- options.force ? ' --force' : ''
65
- }`,
66
- {
67
- stdio: 'inherit',
68
- }
75
+ const cmd = ['prisma migrate reset', ` --schema "${prismaSchemaFile}"`, options.force ? ' --force' : ''].join(
76
+ '',
69
77
  );
78
+
79
+ await execPackage(cmd);
70
80
  } catch (err) {
71
81
  handleSubProcessError(err);
72
82
  }
73
83
  }
74
84
 
75
- async function runDeploy(prismaSchemaFile: string, _options: unknown) {
85
+ async function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
76
86
  try {
77
- await execPackage(
78
- `prisma migrate deploy --schema "${prismaSchemaFile}"`,
79
- {
80
- stdio: 'inherit',
81
- }
82
- );
87
+ const cmd = ['prisma migrate deploy', ` --schema "${prismaSchemaFile}"`].join('');
88
+
89
+ await execPackage(cmd);
83
90
  } catch (err) {
84
91
  handleSubProcessError(err);
85
92
  }
86
93
  }
87
94
 
88
- async function runStatus(prismaSchemaFile: string, _options: unknown) {
95
+ async function runStatus(prismaSchemaFile: string, _options: StatusOptions) {
89
96
  try {
90
- await execPackage(
91
- `prisma migrate status --schema "${prismaSchemaFile}"`,
92
- {
93
- stdio: 'inherit',
94
- }
95
- );
97
+ await execPackage(`prisma migrate status --schema "${prismaSchemaFile}"`);
96
98
  } catch (err) {
97
99
  handleSubProcessError(err);
98
100
  }
99
101
  }
100
102
 
101
103
  function handleSubProcessError(err: unknown) {
102
- if (
103
- err instanceof Error &&
104
- 'status' in err &&
105
- typeof err.status === 'number'
106
- ) {
104
+ if (err instanceof Error && 'status' in err && typeof err.status === 'number') {
107
105
  process.exit(err.status);
108
106
  } else {
109
107
  process.exit(1);
@@ -27,10 +27,16 @@ model Post {
27
27
  `;
28
28
 
29
29
  export const STARTER_MAIN_TS = `import { ZenStackClient } from '@zenstackhq/runtime';
30
+ import SQLite from 'better-sqlite3';
31
+ import { SqliteDialect } from 'kysely';
30
32
  import { schema } from './zenstack/schema';
31
33
 
32
34
  async function main() {
33
- const client = new ZenStackClient(schema);
35
+ const client = new ZenStackClient(schema, {
36
+ dialect: new SqliteDialect({
37
+ database: new SQLite('./zenstack/dev.db'),
38
+ }),
39
+ });
34
40
  const user = await client.user.create({
35
41
  data: {
36
42
  email: 'test@zenstack.dev',
@@ -0,0 +1,22 @@
1
+ import colors from 'colors';
2
+ import { getSchemaFile, loadSchemaDocument } from './action-utils';
3
+
4
+ type Options = {
5
+ schema?: string;
6
+ };
7
+
8
+ /**
9
+ * CLI action for validating schema without generation
10
+ */
11
+ export async function run(options: Options) {
12
+ const schemaFile = getSchemaFile(options.schema);
13
+
14
+ try {
15
+ await loadSchemaDocument(schemaFile);
16
+ console.log(colors.green('✓ Schema validation completed successfully.'));
17
+ } catch (error) {
18
+ console.error(colors.red('✗ Schema validation failed.'));
19
+ // Re-throw to maintain CLI exit code behavior
20
+ throw error;
21
+ }
22
+ }
package/src/index.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import { ZModelLanguageMetaData } from '@zenstackhq/language';
2
2
  import colors from 'colors';
3
- import { Command, Option } from 'commander';
3
+ import { Command, CommanderError, Option } from 'commander';
4
4
  import * as actions from './actions';
5
+ import { CliError } from './cli-error';
5
6
  import { getVersion } from './utils/version-utils';
6
7
 
7
- const generateAction = async (
8
- options: Parameters<typeof actions.generate>[0]
9
- ): Promise<void> => {
8
+ const generateAction = async (options: Parameters<typeof actions.generate>[0]): Promise<void> => {
10
9
  await actions.generate(options);
11
10
  };
12
11
 
@@ -26,6 +25,10 @@ const initAction = async (projectPath: string): Promise<void> => {
26
25
  await actions.init(projectPath);
27
26
  };
28
27
 
28
+ const validateAction = async (options: Parameters<typeof actions.validate>[0]): Promise<void> => {
29
+ await actions.validate(options);
30
+ };
31
+
29
32
  export function createProgram() {
30
33
  const program = new Command('zenstack');
31
34
 
@@ -36,92 +39,78 @@ export function createProgram() {
36
39
  program
37
40
  .description(
38
41
  `${colors.bold.blue(
39
- 'ζ'
40
- )} ZenStack is a Prisma power pack for building full-stack apps.\n\nDocumentation: https://zenstack.dev.`
42
+ 'ζ',
43
+ )} ZenStack is a database access toolkit for TypeScript apps.\n\nDocumentation: https://zenstack.dev.`,
41
44
  )
42
45
  .showHelpAfterError()
43
46
  .showSuggestionAfterError();
44
47
 
45
48
  const schemaOption = new Option(
46
49
  '--schema <file>',
47
- `schema file (with extension ${schemaExtensions}). Defaults to "schema.zmodel" unless specified in package.json.`
50
+ `schema file (with extension ${schemaExtensions}). Defaults to "schema.zmodel" unless specified in package.json.`,
48
51
  );
49
52
 
50
53
  program
51
54
  .command('generate')
52
55
  .description('Run code generation.')
53
56
  .addOption(schemaOption)
57
+ .addOption(new Option('--silent', 'do not print any output'))
54
58
  .addOption(
55
59
  new Option(
56
- '-o, --output <path>',
57
- 'default output directory for core plugins'
58
- )
60
+ '--save-prisma-schema [path]',
61
+ 'save a Prisma schema file, by default into the output directory',
62
+ ),
59
63
  )
64
+ .addOption(new Option('-o, --output <path>', 'default output directory for core plugins'))
60
65
  .action(generateAction);
61
66
 
62
- const migrateCommand = program
63
- .command('migrate')
64
- .description('Update the database schema with migrations.');
67
+ const migrateCommand = program.command('migrate').description('Update the database schema with migrations.');
68
+ const migrationsOption = new Option('--migrations <path>', 'path for migrations');
65
69
 
66
70
  migrateCommand
67
71
  .command('dev')
68
72
  .addOption(schemaOption)
69
73
  .addOption(new Option('-n, --name <name>', 'migration name'))
70
- .addOption(
71
- new Option('--create-only', 'only create migration, do not apply')
72
- )
73
- .description(
74
- 'Create a migration from changes in schema and apply it to the database.'
75
- )
74
+ .addOption(new Option('--create-only', 'only create migration, do not apply'))
75
+ .addOption(migrationsOption)
76
+ .description('Create a migration from changes in schema and apply it to the database.')
76
77
  .action((options) => migrateAction('dev', options));
77
78
 
78
79
  migrateCommand
79
80
  .command('reset')
80
81
  .addOption(schemaOption)
81
82
  .addOption(new Option('--force', 'skip the confirmation prompt'))
82
- .description(
83
- 'Reset your database and apply all migrations, all data will be lost.'
84
- )
83
+ .addOption(migrationsOption)
84
+ .description('Reset your database and apply all migrations, all data will be lost.')
85
85
  .action((options) => migrateAction('reset', options));
86
86
 
87
87
  migrateCommand
88
88
  .command('deploy')
89
89
  .addOption(schemaOption)
90
- .description(
91
- 'Deploy your pending migrations to your production/staging database.'
92
- )
90
+ .addOption(migrationsOption)
91
+ .description('Deploy your pending migrations to your production/staging database.')
93
92
  .action((options) => migrateAction('deploy', options));
94
93
 
95
94
  migrateCommand
96
95
  .command('status')
97
96
  .addOption(schemaOption)
97
+ .addOption(migrationsOption)
98
98
  .description('check the status of your database migrations.')
99
99
  .action((options) => migrateAction('status', options));
100
100
 
101
- const dbCommand = program
102
- .command('db')
103
- .description('Manage your database schema during development.');
101
+ const dbCommand = program.command('db').description('Manage your database schema during development.');
104
102
 
105
103
  dbCommand
106
104
  .command('push')
107
105
  .description('Push the state from your schema to your database')
108
106
  .addOption(schemaOption)
109
- .addOption(
110
- new Option('--accept-data-loss', 'ignore data loss warnings')
111
- )
112
- .addOption(
113
- new Option(
114
- '--force-reset',
115
- 'force a reset of the database before push'
116
- )
117
- )
107
+ .addOption(new Option('--accept-data-loss', 'ignore data loss warnings'))
108
+ .addOption(new Option('--force-reset', 'force a reset of the database before push'))
118
109
  .action((options) => dbAction('push', options));
119
110
 
120
111
  program
121
112
  .command('info')
122
- .description(
123
- 'Get information of installed ZenStack and related packages.'
124
- )
113
+ .description('Get information of installed ZenStack and related packages.')
125
114
  .argument('[path]', 'project path', '.')
126
115
  .action(infoAction);
127
116
 
@@ -131,8 +120,23 @@ export function createProgram() {
131
120
  .argument('[path]', 'project path', '.')
132
121
  .action(initAction);
133
122
 
123
+ program.command('validate').description('Validate a ZModel schema.').addOption(schemaOption).action(validateAction);
124
+
134
125
  return program;
135
126
  }
136
127
 
137
128
  const program = createProgram();
138
- program.parse(process.argv);
129
+
130
+ program.parseAsync().catch((err) => {
131
+ if (err instanceof CliError) {
132
+ console.error(colors.red(err.message));
133
+ process.exit(1);
134
+ } else if (err instanceof CommanderError) {
135
+ // errors are already reported, just exit
136
+ process.exit(err.exitCode);
137
+ } else {
138
+ console.error(colors.red('An unexpected error occurred:'));
139
+ console.error(err);
140
+ process.exit(1);
141
+ }
142
+ });
@@ -3,10 +3,7 @@ import { execSync as _exec, type ExecSyncOptions } from 'child_process';
3
3
  /**
4
4
  * Utility for executing command synchronously and prints outputs on current console
5
5
  */
6
- export function execSync(
7
- cmd: string,
8
- options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> }
9
- ): void {
6
+ export function execSync(cmd: string, options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> }): void {
10
7
  const { env, ...restOptions } = options ?? {};
11
8
  const mergedEnv = env ? { ...process.env, ...env } : undefined;
12
9
  _exec(cmd, {
@@ -22,7 +19,7 @@ export function execSync(
22
19
  */
23
20
  export function execPackage(
24
21
  cmd: string,
25
- options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> }
22
+ options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> },
26
23
  ): void {
27
24
  const packageManager = process?.versions?.['bun'] ? 'bunx' : 'npx';
28
25
  execSync(`${packageManager} ${cmd}`, options);
@@ -1,13 +1,13 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- export function getVersion(): string | undefined {
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export function getVersion() {
3
6
  try {
4
- return require('../package.json').version;
7
+ // isomorphic __dirname
8
+ const _dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
9
+ return JSON.parse(fs.readFileSync(path.join(_dirname, '../package.json'), 'utf8')).version;
5
10
  } catch {
6
- try {
7
- // dev environment
8
- return require('../../package.json').version;
9
- } catch {
10
- return undefined;
11
- }
11
+ return undefined;
12
12
  }
13
13
  }
@@ -0,0 +1,18 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { createProject, runCli } from './utils';
5
+
6
+ const model = `
7
+ model User {
8
+ id String @id @default(cuid())
9
+ }
10
+ `;
11
+
12
+ describe('CLI db commands test', () => {
13
+ it('should generate a database with db push', () => {
14
+ const workDir = createProject(model);
15
+ runCli('db push', workDir);
16
+ expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
17
+ });
18
+ });
@@ -0,0 +1,59 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { createProject, runCli } from './utils';
5
+
6
+ const model = `
7
+ model User {
8
+ id String @id @default(cuid())
9
+ }
10
+ `;
11
+
12
+ describe('CLI generate command test', () => {
13
+ it('should generate a TypeScript schema', () => {
14
+ const workDir = createProject(model);
15
+ runCli('generate', workDir);
16
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
17
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.prisma'))).toBe(false);
18
+ });
19
+
20
+ it('should respect custom output directory', () => {
21
+ const workDir = createProject(model);
22
+ runCli('generate --output ./zen', workDir);
23
+ expect(fs.existsSync(path.join(workDir, 'zen/schema.ts'))).toBe(true);
24
+ });
25
+
26
+ it('should respect custom schema location', () => {
27
+ const workDir = createProject(model);
28
+ fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'zenstack/foo.zmodel'));
29
+ runCli('generate --schema ./zenstack/foo.zmodel', workDir);
30
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
31
+ });
32
+
33
+ it('should respect save prisma schema option', () => {
34
+ const workDir = createProject(model);
35
+ runCli('generate --save-prisma-schema', workDir);
36
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.prisma'))).toBe(true);
37
+ });
38
+
39
+ it('should respect save prisma schema custom path option', () => {
40
+ const workDir = createProject(model);
41
+ runCli('generate --save-prisma-schema "../prisma/schema.prisma"', workDir);
42
+ expect(fs.existsSync(path.join(workDir, 'prisma/schema.prisma'))).toBe(true);
43
+ });
44
+
45
+ it('should respect package.json config', () => {
46
+ const workDir = createProject(model);
47
+ fs.mkdirSync(path.join(workDir, 'foo'));
48
+ fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'foo/schema.zmodel'));
49
+ fs.rmdirSync(path.join(workDir, 'zenstack'));
50
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
51
+ pkgJson.zenstack = {
52
+ schema: './foo/schema.zmodel',
53
+ output: './bar',
54
+ };
55
+ fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
56
+ runCli('generate', workDir);
57
+ expect(fs.existsSync(path.join(workDir, 'bar/schema.ts'))).toBe(true);
58
+ });
59
+ });
@@ -0,0 +1,13 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import tmp from 'tmp';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { runCli } from './utils';
6
+
7
+ describe('Cli init command tests', () => {
8
+ it('should create a new project', () => {
9
+ const { name: workDir } = tmp.dirSync({ unsafeCleanup: true });
10
+ runCli('init', workDir);
11
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.zmodel'))).toBe(true);
12
+ });
13
+ });
@@ -0,0 +1,41 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { createProject, runCli } from './utils';
5
+
6
+ const model = `
7
+ model User {
8
+ id String @id @default(cuid())
9
+ }
10
+ `;
11
+
12
+ describe('CLI migrate commands test', () => {
13
+ it('should generate a database with migrate dev', () => {
14
+ const workDir = createProject(model);
15
+ runCli('migrate dev --name init', workDir);
16
+ expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
17
+ expect(fs.existsSync(path.join(workDir, 'zenstack/migrations'))).toBe(true);
18
+ });
19
+
20
+ it('should reset the database with migrate reset', () => {
21
+ const workDir = createProject(model);
22
+ runCli('db push', workDir);
23
+ expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
24
+ runCli('migrate reset --force', workDir);
25
+ expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
26
+ });
27
+
28
+ it('should reset the database with migrate deploy', () => {
29
+ const workDir = createProject(model);
30
+ runCli('migrate dev --name init', workDir);
31
+ fs.rmSync(path.join(workDir, 'zenstack/dev.db'));
32
+ runCli('migrate deploy', workDir);
33
+ expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
34
+ });
35
+
36
+ it('supports migrate status', () => {
37
+ const workDir = createProject(model);
38
+ runCli('migrate dev --name init', workDir);
39
+ runCli('migrate status', workDir);
40
+ });
41
+ });