@zenstackhq/cli 3.0.0-beta.3 → 3.0.0-beta.30

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.
@@ -13,6 +13,8 @@ type Options = {
13
13
  schema?: string;
14
14
  output?: string;
15
15
  silent: boolean;
16
+ lite: boolean;
17
+ liteOnly: boolean;
16
18
  };
17
19
 
18
20
  /**
@@ -33,7 +35,7 @@ export async function run(options: Options) {
33
35
  console.log(`You can now create a ZenStack client with it.
34
36
 
35
37
  \`\`\`ts
36
- import { ZenStackClient } from '@zenstackhq/runtime';
38
+ import { ZenStackClient } from '@zenstackhq/orm';
37
39
  import { schema } from '${outputPath}/schema';
38
40
 
39
41
  const client = new ZenStackClient(schema, {
@@ -64,7 +66,7 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
64
66
  for (const plugin of plugins) {
65
67
  const provider = getPluginProvider(plugin);
66
68
 
67
- let cliPlugin: CliPlugin;
69
+ let cliPlugin: CliPlugin | undefined;
68
70
  if (provider.startsWith('@core/')) {
69
71
  cliPlugin = (corePlugins as any)[provider.slice('@core/'.length)];
70
72
  if (!cliPlugin) {
@@ -78,18 +80,37 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
78
80
  }
79
81
  try {
80
82
  cliPlugin = (await import(moduleSpec)).default as CliPlugin;
81
- } catch (error) {
82
- throw new CliError(`Failed to load plugin ${provider}: ${error}`);
83
+ } catch {
84
+ // plugin may not export a generator so we simply ignore the error here
83
85
  }
84
86
  }
85
87
 
86
- processedPlugins.push({ cliPlugin, pluginOptions: getPluginOptions(plugin) });
88
+ if (cliPlugin) {
89
+ const pluginOptions = getPluginOptions(plugin);
90
+
91
+ // merge CLI options
92
+ if (provider === '@core/typescript') {
93
+ if (pluginOptions['lite'] === undefined) {
94
+ pluginOptions['lite'] = options.lite;
95
+ }
96
+ if (pluginOptions['liteOnly'] === undefined) {
97
+ pluginOptions['liteOnly'] = options.liteOnly;
98
+ }
99
+ }
100
+
101
+ processedPlugins.push({ cliPlugin, pluginOptions });
102
+ }
87
103
  }
88
104
 
89
- const defaultPlugins = [corePlugins['typescript']].reverse();
90
- defaultPlugins.forEach((d) => {
91
- if (!processedPlugins.some((p) => p.cliPlugin === d)) {
92
- processedPlugins.push({ cliPlugin: d, pluginOptions: {} });
105
+ const defaultPlugins = [
106
+ {
107
+ plugin: corePlugins['typescript'],
108
+ options: { lite: options.lite, liteOnly: options.liteOnly },
109
+ },
110
+ ];
111
+ defaultPlugins.forEach(({ plugin, options }) => {
112
+ if (!processedPlugins.some((p) => p.cliPlugin === plugin)) {
113
+ processedPlugins.push({ cliPlugin: plugin, pluginOptions: options });
93
114
  }
94
115
  });
95
116
 
@@ -1,8 +1,10 @@
1
+ import { run as check } from './check';
1
2
  import { run as db } from './db';
3
+ import { run as format } from './format';
2
4
  import { run as generate } from './generate';
3
5
  import { run as info } from './info';
4
6
  import { run as init } from './init';
5
7
  import { run as migrate } from './migrate';
6
- import { run as check } from './check';
8
+ import { run as seed } from './seed';
7
9
 
8
- export { db, generate, info, init, migrate, check };
10
+ export { check, db, format, generate, info, init, migrate, seed };
@@ -13,7 +13,7 @@ import { STARTER_ZMODEL } from './templates';
13
13
  export async function run(projectPath: string) {
14
14
  const packages = [
15
15
  { name: '@zenstackhq/cli@next', dev: true },
16
- { name: '@zenstackhq/runtime@next', dev: false },
16
+ { name: '@zenstackhq/orm@next', dev: false },
17
17
  ];
18
18
  let pm = await detect();
19
19
  if (!pm) {
@@ -1,12 +1,14 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { CliError } from '../cli-error';
4
- import { execPackage } from '../utils/exec-utils';
5
- import { generateTempPrismaSchema, getSchemaFile } from './action-utils';
4
+ import { execPrisma } from '../utils/exec-utils';
5
+ import { generateTempPrismaSchema, getSchemaFile, requireDataSourceUrl } from './action-utils';
6
+ import { run as runSeed } from './seed';
6
7
 
7
8
  type CommonOptions = {
8
9
  schema?: string;
9
10
  migrations?: string;
11
+ skipSeed?: boolean;
10
12
  };
11
13
 
12
14
  type DevOptions = CommonOptions & {
@@ -32,6 +34,10 @@ type ResolveOptions = CommonOptions & {
32
34
  */
33
35
  export async function run(command: string, options: CommonOptions) {
34
36
  const schemaFile = getSchemaFile(options.schema);
37
+
38
+ // validate datasource url exists
39
+ await requireDataSourceUrl(schemaFile);
40
+
35
41
  const prismaSchemaDir = options.migrations ? path.dirname(options.migrations) : undefined;
36
42
  const prismaSchemaFile = await generateTempPrismaSchema(schemaFile, prismaSchemaDir);
37
43
 
@@ -64,17 +70,17 @@ export async function run(command: string, options: CommonOptions) {
64
70
  }
65
71
  }
66
72
 
67
- async function runDev(prismaSchemaFile: string, options: DevOptions) {
73
+ function runDev(prismaSchemaFile: string, options: DevOptions) {
68
74
  try {
69
75
  const cmd = [
70
- 'prisma migrate dev',
76
+ 'migrate dev',
71
77
  ` --schema "${prismaSchemaFile}"`,
72
78
  ' --skip-generate',
73
- options.name ? ` --name ${options.name}` : '',
79
+ ' --skip-seed',
80
+ options.name ? ` --name "${options.name}"` : '',
74
81
  options.createOnly ? ' --create-only' : '',
75
82
  ].join('');
76
-
77
- await execPackage(cmd);
83
+ execPrisma(cmd);
78
84
  } catch (err) {
79
85
  handleSubProcessError(err);
80
86
  }
@@ -82,48 +88,53 @@ async function runDev(prismaSchemaFile: string, options: DevOptions) {
82
88
 
83
89
  async function runReset(prismaSchemaFile: string, options: ResetOptions) {
84
90
  try {
85
- const cmd = ['prisma migrate reset', ` --schema "${prismaSchemaFile}"`, options.force ? ' --force' : ''].join(
86
- '',
87
- );
88
-
89
- await execPackage(cmd);
91
+ const cmd = [
92
+ 'migrate reset',
93
+ ` --schema "${prismaSchemaFile}"`,
94
+ ' --skip-generate',
95
+ ' --skip-seed',
96
+ options.force ? ' --force' : '',
97
+ ].join('');
98
+ execPrisma(cmd);
90
99
  } catch (err) {
91
100
  handleSubProcessError(err);
92
101
  }
102
+
103
+ if (!options.skipSeed) {
104
+ await runSeed({ noWarnings: true, printStatus: true }, []);
105
+ }
93
106
  }
94
107
 
95
- async function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
108
+ function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
96
109
  try {
97
- const cmd = ['prisma migrate deploy', ` --schema "${prismaSchemaFile}"`].join('');
98
-
99
- await execPackage(cmd);
110
+ const cmd = ['migrate deploy', ` --schema "${prismaSchemaFile}"`].join('');
111
+ execPrisma(cmd);
100
112
  } catch (err) {
101
113
  handleSubProcessError(err);
102
114
  }
103
115
  }
104
116
 
105
- async function runStatus(prismaSchemaFile: string, _options: StatusOptions) {
117
+ function runStatus(prismaSchemaFile: string, _options: StatusOptions) {
106
118
  try {
107
- await execPackage(`prisma migrate status --schema "${prismaSchemaFile}"`);
119
+ execPrisma(`migrate status --schema "${prismaSchemaFile}"`);
108
120
  } catch (err) {
109
121
  handleSubProcessError(err);
110
122
  }
111
123
  }
112
124
 
113
- async function runResolve(prismaSchemaFile: string, options: ResolveOptions) {
125
+ function runResolve(prismaSchemaFile: string, options: ResolveOptions) {
114
126
  if (!options.applied && !options.rolledBack) {
115
127
  throw new CliError('Either --applied or --rolled-back option must be provided');
116
128
  }
117
129
 
118
130
  try {
119
131
  const cmd = [
120
- 'prisma migrate resolve',
132
+ 'migrate resolve',
121
133
  ` --schema "${prismaSchemaFile}"`,
122
- options.applied ? ` --applied ${options.applied}` : '',
123
- options.rolledBack ? ` --rolled-back ${options.rolledBack}` : '',
134
+ options.applied ? ` --applied "${options.applied}"` : '',
135
+ options.rolledBack ? ` --rolled-back "${options.rolledBack}"` : '',
124
136
  ].join('');
125
-
126
- await execPackage(cmd);
137
+ execPrisma(cmd);
127
138
  } catch (err) {
128
139
  handleSubProcessError(err);
129
140
  }
@@ -0,0 +1,38 @@
1
+ import colors from 'colors';
2
+ import { execaCommand } from 'execa';
3
+ import { CliError } from '../cli-error';
4
+ import { getPkgJsonConfig } from './action-utils';
5
+
6
+ type Options = {
7
+ noWarnings?: boolean;
8
+ printStatus?: boolean;
9
+ };
10
+
11
+ /**
12
+ * CLI action for seeding the database.
13
+ */
14
+ export async function run(options: Options, args: string[]) {
15
+ const pkgJsonConfig = getPkgJsonConfig(process.cwd());
16
+ if (!pkgJsonConfig.seed) {
17
+ if (!options.noWarnings) {
18
+ console.warn(colors.yellow('No seed script defined in package.json. Skipping seeding.'));
19
+ }
20
+ return;
21
+ }
22
+
23
+ const command = `${pkgJsonConfig.seed}${args.length > 0 ? ' ' + args.join(' ') : ''}`;
24
+
25
+ if (options.printStatus) {
26
+ console.log(colors.gray(`Running seed script "${command}"...`));
27
+ }
28
+
29
+ try {
30
+ await execaCommand(command, {
31
+ stdout: 'inherit',
32
+ stderr: 'inherit',
33
+ });
34
+ } catch (err) {
35
+ console.error(colors.red(err instanceof Error ? err.message : String(err)));
36
+ throw new CliError('Failed to seed the database. Please check the error message above for details.');
37
+ }
38
+ }
@@ -26,7 +26,7 @@ model Post {
26
26
  }
27
27
  `;
28
28
 
29
- export const STARTER_MAIN_TS = `import { ZenStackClient } from '@zenstackhq/runtime';
29
+ export const STARTER_MAIN_TS = `import { ZenStackClient } from '@zenstackhq/orm';
30
30
  import SQLite from 'better-sqlite3';
31
31
  import { SqliteDialect } from 'kysely';
32
32
  import { schema } from './zenstack/schema';
package/src/constants.ts CHANGED
@@ -1,2 +1,5 @@
1
1
  // replaced at build time
2
2
  export const TELEMETRY_TRACKING_TOKEN = '<TELEMETRY_TRACKING_TOKEN>';
3
+
4
+ // plugin-contributed model file name
5
+ export const PLUGIN_MODULE_NAME = 'plugin.zmodel';
package/src/index.ts CHANGED
@@ -30,10 +30,19 @@ const checkAction = async (options: Parameters<typeof actions.check>[0]): Promis
30
30
  await telemetry.trackCommand('check', () => actions.check(options));
31
31
  };
32
32
 
33
- function createProgram() {
34
- const program = new Command('zen');
33
+ const formatAction = async (options: Parameters<typeof actions.format>[0]): Promise<void> => {
34
+ await telemetry.trackCommand('format', () => actions.format(options));
35
+ };
35
36
 
36
- program.version(getVersion()!, '-v --version', 'display CLI version');
37
+ const seedAction = async (options: Parameters<typeof actions.seed>[0], args: string[]): Promise<void> => {
38
+ await telemetry.trackCommand('db seed', () => actions.seed(options, args));
39
+ };
40
+
41
+ function createProgram() {
42
+ const program = new Command('zen')
43
+ .alias('zenstack')
44
+ .helpOption('-h, --help', 'Show this help message')
45
+ .version(getVersion()!, '-v --version', 'Show CLI version');
37
46
 
38
47
  const schemaExtensions = ZModelLanguageMetaData.fileExtensions.join(', ');
39
48
 
@@ -55,10 +64,12 @@ function createProgram() {
55
64
 
56
65
  program
57
66
  .command('generate')
58
- .description('Run code generation plugins.')
67
+ .description('Run code generation plugins')
59
68
  .addOption(schemaOption)
60
69
  .addOption(noVersionCheckOption)
61
70
  .addOption(new Option('-o, --output <path>', 'default output directory for code generation'))
71
+ .addOption(new Option('--lite', 'also generate a lite version of schema without attributes').default(false))
72
+ .addOption(new Option('--lite-only', 'only generate lite version of schema without attributes').default(false))
62
73
  .addOption(new Option('--silent', 'suppress all output except errors').default(false))
63
74
  .action(generateAction);
64
75
 
@@ -72,7 +83,7 @@ function createProgram() {
72
83
  .addOption(new Option('-n, --name <name>', 'migration name'))
73
84
  .addOption(new Option('--create-only', 'only create migration, do not apply'))
74
85
  .addOption(migrationsOption)
75
- .description('Create a migration from changes in schema and apply it to the database.')
86
+ .description('Create a migration from changes in schema and apply it to the database')
76
87
  .action((options) => migrateAction('dev', options));
77
88
 
78
89
  migrateCommand
@@ -80,8 +91,13 @@ function createProgram() {
80
91
  .addOption(schemaOption)
81
92
  .addOption(new Option('--force', 'skip the confirmation prompt'))
82
93
  .addOption(migrationsOption)
94
+ .addOption(new Option('--skip-seed', 'skip seeding the database after reset'))
83
95
  .addOption(noVersionCheckOption)
84
- .description('Reset your database and apply all migrations, all data will be lost.')
96
+ .description('Reset your database and apply all migrations, all data will be lost')
97
+ .addHelpText(
98
+ 'after',
99
+ '\nIf there is a seed script defined in package.json, it will be run after the reset. Use --skip-seed to skip it.',
100
+ )
85
101
  .action((options) => migrateAction('reset', options));
86
102
 
87
103
  migrateCommand
@@ -89,7 +105,7 @@ function createProgram() {
89
105
  .addOption(schemaOption)
90
106
  .addOption(noVersionCheckOption)
91
107
  .addOption(migrationsOption)
92
- .description('Deploy your pending migrations to your production/staging database.')
108
+ .description('Deploy your pending migrations to your production/staging database')
93
109
  .action((options) => migrateAction('deploy', options));
94
110
 
95
111
  migrateCommand
@@ -97,7 +113,7 @@ function createProgram() {
97
113
  .addOption(schemaOption)
98
114
  .addOption(noVersionCheckOption)
99
115
  .addOption(migrationsOption)
100
- .description('Check the status of your database migrations.')
116
+ .description('Check the status of your database migrations')
101
117
  .action((options) => migrateAction('status', options));
102
118
 
103
119
  migrateCommand
@@ -107,41 +123,70 @@ function createProgram() {
107
123
  .addOption(migrationsOption)
108
124
  .addOption(new Option('--applied <migration>', 'record a specific migration as applied'))
109
125
  .addOption(new Option('--rolled-back <migration>', 'record a specific migration as rolled back'))
110
- .description('Resolve issues with database migrations in deployment databases.')
126
+ .description('Resolve issues with database migrations in deployment databases')
111
127
  .action((options) => migrateAction('resolve', options));
112
128
 
113
- const dbCommand = program.command('db').description('Manage your database schema during development.');
129
+ const dbCommand = program.command('db').description('Manage your database schema during development');
114
130
 
115
131
  dbCommand
116
132
  .command('push')
117
- .description('Push the state from your schema to your database.')
133
+ .description('Push the state from your schema to your database')
118
134
  .addOption(schemaOption)
119
135
  .addOption(noVersionCheckOption)
120
136
  .addOption(new Option('--accept-data-loss', 'ignore data loss warnings'))
121
137
  .addOption(new Option('--force-reset', 'force a reset of the database before push'))
122
138
  .action((options) => dbAction('push', options));
123
139
 
140
+ dbCommand
141
+ .command('seed')
142
+ .description('Seed the database')
143
+ .allowExcessArguments(true)
144
+ .addHelpText(
145
+ 'after',
146
+ `
147
+ Seed script is configured under the "zenstack.seed" field in package.json.
148
+ E.g.:
149
+ {
150
+ "zenstack": {
151
+ "seed": "ts-node ./zenstack/seed.ts"
152
+ }
153
+ }
154
+
155
+ Arguments following -- are passed to the seed script. E.g.: "zen db seed -- --users 10"`,
156
+ )
157
+ .addOption(noVersionCheckOption)
158
+ .action((options, command) => seedAction(options, command.args));
159
+
124
160
  program
125
161
  .command('info')
126
- .description('Get information of installed ZenStack packages.')
162
+ .description('Get information of installed ZenStack packages')
127
163
  .argument('[path]', 'project path', '.')
128
164
  .addOption(noVersionCheckOption)
129
165
  .action(infoAction);
130
166
 
131
167
  program
132
168
  .command('init')
133
- .description('Initialize an existing project for ZenStack.')
169
+ .description('Initialize an existing project for ZenStack')
134
170
  .argument('[path]', 'project path', '.')
135
171
  .addOption(noVersionCheckOption)
136
172
  .action(initAction);
137
173
 
138
174
  program
139
175
  .command('check')
140
- .description('Check a ZModel schema for syntax or semantic errors.')
176
+ .description('Check a ZModel schema for syntax or semantic errors')
141
177
  .addOption(schemaOption)
142
178
  .addOption(noVersionCheckOption)
143
179
  .action(checkAction);
144
180
 
181
+ program
182
+ .command('format')
183
+ .description('Format a ZModel schema file')
184
+ .addOption(schemaOption)
185
+ .addOption(noVersionCheckOption)
186
+ .action(formatAction);
187
+
188
+ program.addHelpCommand('help [command]', 'Display help for a command');
189
+
145
190
  program.hook('preAction', async (_thisCommand, actionCommand) => {
146
191
  if (actionCommand.getOptionValue('versionCheck') !== false) {
147
192
  await checkNewVersion();
@@ -5,10 +5,10 @@ import path from 'node:path';
5
5
  const plugin: CliPlugin = {
6
6
  name: 'Prisma Schema Generator',
7
7
  statusText: 'Generating Prisma schema',
8
- async generate({ model, schemaFile, defaultOutputPath, pluginOptions }) {
8
+ async generate({ model, defaultOutputPath, pluginOptions }) {
9
9
  let outFile = path.join(defaultOutputPath, 'schema.prisma');
10
10
  if (typeof pluginOptions['output'] === 'string') {
11
- outFile = path.resolve(path.dirname(schemaFile), pluginOptions['output']);
11
+ outFile = path.resolve(defaultOutputPath, pluginOptions['output']);
12
12
  if (!fs.existsSync(path.dirname(outFile))) {
13
13
  fs.mkdirSync(path.dirname(outFile), { recursive: true });
14
14
  }
@@ -7,6 +7,7 @@ const plugin: CliPlugin = {
7
7
  name: 'TypeScript Schema Generator',
8
8
  statusText: 'Generating TypeScript schema',
9
9
  async generate({ model, defaultOutputPath, pluginOptions }) {
10
+ // output path
10
11
  let outDir = defaultOutputPath;
11
12
  if (typeof pluginOptions['output'] === 'string') {
12
13
  outDir = path.resolve(defaultOutputPath, pluginOptions['output']);
@@ -14,7 +15,25 @@ const plugin: CliPlugin = {
14
15
  fs.mkdirSync(outDir, { recursive: true });
15
16
  }
16
17
  }
17
- await new TsSchemaGenerator().generate(model, outDir);
18
+
19
+ // lite mode
20
+ const lite = pluginOptions['lite'] === true;
21
+
22
+ // liteOnly mode
23
+ const liteOnly = pluginOptions['liteOnly'] === true;
24
+
25
+ // add .js extension when importing
26
+ const importWithFileExtension = pluginOptions['importWithFileExtension'];
27
+ if (importWithFileExtension && typeof importWithFileExtension !== 'string') {
28
+ throw new Error('The "importWithFileExtension" option must be a string if specified.');
29
+ }
30
+
31
+ await new TsSchemaGenerator().generate(model, {
32
+ outDir,
33
+ lite,
34
+ liteOnly,
35
+ importWithFileExtension: importWithFileExtension as string | undefined,
36
+ });
18
37
  },
19
38
  };
20
39
 
@@ -1,4 +1,5 @@
1
1
  import { execSync as _exec, type ExecSyncOptions } from 'child_process';
2
+ import { fileURLToPath } from 'url';
2
3
 
3
4
  /**
4
5
  * Utility for executing command synchronously and prints outputs on current console
@@ -24,3 +25,37 @@ export function execPackage(
24
25
  const packageManager = process?.versions?.['bun'] ? 'bunx' : 'npx';
25
26
  execSync(`${packageManager} ${cmd}`, options);
26
27
  }
28
+
29
+ /**
30
+ * Utility for running prisma commands
31
+ */
32
+ export function execPrisma(args: string, options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> }) {
33
+ let prismaPath: string | undefined;
34
+ try {
35
+ if (typeof import.meta.resolve === 'function') {
36
+ // esm
37
+ prismaPath = fileURLToPath(import.meta.resolve('prisma/build/index.js'));
38
+ } else {
39
+ // cjs
40
+ prismaPath = require.resolve('prisma/build/index.js');
41
+ }
42
+ } catch {
43
+ // ignore and fallback
44
+ }
45
+
46
+ const _options = {
47
+ ...options,
48
+ env: {
49
+ ...options?.env,
50
+ PRISMA_HIDE_UPDATE_MESSAGE: '1',
51
+ },
52
+ };
53
+
54
+ if (!prismaPath) {
55
+ // fallback to npx/bunx execute
56
+ execPackage(`prisma ${args}`, _options);
57
+ return;
58
+ }
59
+
60
+ execSync(`node ${prismaPath} ${args}`, _options);
61
+ }
package/test/db.test.ts CHANGED
@@ -15,4 +15,47 @@ describe('CLI db commands test', () => {
15
15
  runCli('db push', workDir);
16
16
  expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
17
17
  });
18
+
19
+ it('should seed the database with db seed with seed script', () => {
20
+ const workDir = createProject(model);
21
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
22
+ pkgJson.zenstack = {
23
+ seed: 'node seed.js',
24
+ };
25
+ fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
26
+ fs.writeFileSync(
27
+ path.join(workDir, 'seed.js'),
28
+ `
29
+ import fs from 'node:fs';
30
+ fs.writeFileSync('seed.txt', 'success');
31
+ `,
32
+ );
33
+
34
+ runCli('db seed', workDir);
35
+ expect(fs.readFileSync(path.join(workDir, 'seed.txt'), 'utf8')).toBe('success');
36
+ });
37
+
38
+ it('should seed the database after migrate reset', () => {
39
+ const workDir = createProject(model);
40
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
41
+ pkgJson.zenstack = {
42
+ seed: 'node seed.js',
43
+ };
44
+ fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
45
+ fs.writeFileSync(
46
+ path.join(workDir, 'seed.js'),
47
+ `
48
+ import fs from 'node:fs';
49
+ fs.writeFileSync('seed.txt', 'success');
50
+ `,
51
+ );
52
+
53
+ runCli('migrate reset --force', workDir);
54
+ expect(fs.readFileSync(path.join(workDir, 'seed.txt'), 'utf8')).toBe('success');
55
+ });
56
+
57
+ it('should skip seeding the database without seed script', () => {
58
+ const workDir = createProject(model);
59
+ runCli('db seed', workDir);
60
+ });
18
61
  });
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { createProject, runCli } from './utils';
3
+ import fs from 'node:fs';
4
+
5
+ const model = `
6
+ model User {
7
+ id String @id @default(cuid())
8
+ email String @unique
9
+ }
10
+ `;
11
+
12
+ describe('CLI format command test', () => {
13
+ it('should format a valid schema successfully', () => {
14
+ const workDir = createProject(model);
15
+ expect(() => runCli('format', workDir)).not.toThrow();
16
+ const updatedContent = fs.readFileSync(`${workDir}/zenstack/schema.zmodel`, 'utf-8');
17
+ expect(
18
+ updatedContent.includes(`model User {
19
+ id String @id @default(cuid())
20
+ email String @unique
21
+ }`),
22
+ ).toBeTruthy();
23
+ });
24
+
25
+ it('should silently ignore invalid schema', () => {
26
+ const invalidModel = `
27
+ model User {
28
+ id String @id @default(cuid())
29
+ `;
30
+ const workDir = createProject(invalidModel);
31
+ expect(() => runCli('format', workDir)).not.toThrow();
32
+ });
33
+ });
@@ -44,4 +44,33 @@ describe('CLI generate command test', () => {
44
44
  runCli('generate', workDir);
45
45
  expect(fs.existsSync(path.join(workDir, 'bar/schema.ts'))).toBe(true);
46
46
  });
47
+
48
+ it('should respect package.json schema dir config', () => {
49
+ const workDir = createProject(model);
50
+ fs.mkdirSync(path.join(workDir, 'foo'));
51
+ fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'foo/schema.zmodel'));
52
+ fs.rmdirSync(path.join(workDir, 'zenstack'));
53
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
54
+ pkgJson.zenstack = {
55
+ schema: './foo',
56
+ output: './bar',
57
+ };
58
+ fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
59
+ runCli('generate', workDir);
60
+ expect(fs.existsSync(path.join(workDir, 'bar/schema.ts'))).toBe(true);
61
+ });
62
+
63
+ it('should respect lite option', () => {
64
+ const workDir = createProject(model);
65
+ runCli('generate --lite', workDir);
66
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
67
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true);
68
+ });
69
+
70
+ it('should respect liteOnly option', () => {
71
+ const workDir = createProject(model);
72
+ runCli('generate --lite-only', workDir);
73
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(false);
74
+ expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true);
75
+ });
47
76
  });
@@ -9,8 +9,7 @@ model User {
9
9
  }
10
10
  `;
11
11
 
12
- // skip due to timeout in CI
13
- describe.skip('CLI migrate commands test', () => {
12
+ describe('CLI migrate commands test', () => {
14
13
  it('should generate a database with migrate dev', () => {
15
14
  const workDir = createProject(model);
16
15
  runCli('migrate dev --name init', workDir);