@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.
- package/.turbo/turbo-build.log +22 -23
- package/bin/cli +1 -1
- package/dist/index.cjs +244 -170
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +245 -179
- package/dist/index.js.map +1 -1
- package/eslint.config.js +4 -0
- package/package.json +15 -16
- package/src/actions/action-utils.ts +81 -10
- package/src/actions/db.ts +30 -29
- package/src/actions/generate.ts +37 -22
- package/src/actions/index.ts +2 -1
- package/src/actions/info.ts +9 -18
- package/src/actions/init.ts +6 -27
- package/src/actions/migrate.ts +61 -63
- package/src/actions/templates.ts +7 -1
- package/src/actions/validate.ts +22 -0
- package/src/index.ts +45 -41
- package/src/utils/exec-utils.ts +2 -5
- package/src/utils/version-utils.ts +9 -9
- package/test/db.test.ts +18 -0
- package/test/generate.test.ts +59 -0
- package/test/init.test.ts +13 -0
- package/test/migrate.test.ts +41 -0
- package/test/ts-schema-gen.test.ts +180 -2
- package/test/utils.ts +23 -0
- package/test/validate.test.ts +101 -0
- package/tsconfig.json +1 -1
- package/vitest.config.ts +1 -1
- package/.turbo/turbo-lint.log +0 -18
package/src/actions/info.ts
CHANGED
|
@@ -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
|
|
40
|
+
} catch {
|
|
49
41
|
return [];
|
|
50
42
|
}
|
|
51
43
|
|
|
52
44
|
const packages = Array.from(
|
|
53
45
|
new Set(
|
|
54
|
-
[
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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;
|
package/src/actions/init.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/actions/migrate.ts
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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,
|
|
57
|
+
async function runDev(prismaSchemaFile: string, options: DevOptions) {
|
|
48
58
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
|
|
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:
|
|
73
|
+
async function runReset(prismaSchemaFile: string, options: ResetOptions) {
|
|
61
74
|
try {
|
|
62
|
-
|
|
63
|
-
|
|
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:
|
|
85
|
+
async function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
|
|
76
86
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
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:
|
|
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);
|
package/src/actions/templates.ts
CHANGED
|
@@ -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
|
|
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
|
-
'-
|
|
57
|
-
'default output directory
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
.
|
|
83
|
-
|
|
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
|
-
.
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|
package/src/utils/exec-utils.ts
CHANGED
|
@@ -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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
// dev environment
|
|
8
|
-
return require('../../package.json').version;
|
|
9
|
-
} catch {
|
|
10
|
-
return undefined;
|
|
11
|
-
}
|
|
11
|
+
return undefined;
|
|
12
12
|
}
|
|
13
13
|
}
|
package/test/db.test.ts
ADDED
|
@@ -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
|
+
});
|