Skip to content

Commit f638291

Browse files
authored
Merge pull request #66 from zenstackhq/dev
merge dev to main
2 parents c225e62 + 26f050a commit f638291

40 files changed

+6018
-256
lines changed

.github/workflows/publish-release.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,3 @@ jobs:
7878
7979
${{ steps.changelog.outputs.changelog }}
8080
draft: true
81-
prerelease: true

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zenstack-v3",
3-
"version": "3.0.0-alpha.6",
3+
"version": "3.0.0-alpha.7",
44
"description": "ZenStack",
55
"packageManager": "[email protected]",
66
"scripts": {

packages/cli/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"publisher": "zenstack",
44
"displayName": "ZenStack CLI",
55
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
6-
"version": "3.0.0-alpha.6",
6+
"version": "3.0.0-alpha.7",
77
"type": "module",
88
"author": {
99
"name": "ZenStack Team"
@@ -28,9 +28,9 @@
2828
"pack": "pnpm pack"
2929
},
3030
"dependencies": {
31+
"@zenstackhq/common-helpers": "workspace:*",
3132
"@zenstackhq/language": "workspace:*",
3233
"@zenstackhq/sdk": "workspace:*",
33-
"@zenstackhq/common-helpers": "workspace:*",
3434
"colors": "1.4.0",
3535
"commander": "^8.3.0",
3636
"langium": "catalog:",
@@ -43,10 +43,12 @@
4343
},
4444
"devDependencies": {
4545
"@types/better-sqlite3": "^7.6.13",
46+
"@types/tmp": "^0.2.6",
4647
"@zenstackhq/eslint-config": "workspace:*",
4748
"@zenstackhq/runtime": "workspace:*",
4849
"@zenstackhq/testtools": "workspace:*",
4950
"@zenstackhq/typescript-config": "workspace:*",
50-
"better-sqlite3": "^11.8.1"
51+
"better-sqlite3": "^11.8.1",
52+
"tmp": "^0.2.3"
5153
}
5254
}

packages/cli/src/actions/action-utils.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import fs from 'node:fs';
2-
import { CliError } from '../cli-error';
31
import { loadDocument } from '@zenstackhq/language';
2+
import { PrismaSchemaGenerator } from '@zenstackhq/sdk';
43
import colors from 'colors';
4+
import fs from 'node:fs';
5+
import path from 'node:path';
6+
import { CliError } from '../cli-error';
57

68
export function getSchemaFile(file?: string) {
79
if (file) {
@@ -41,3 +43,11 @@ export function handleSubProcessError(err: unknown) {
4143
process.exit(1);
4244
}
4345
}
46+
47+
export async function generateTempPrismaSchema(zmodelPath: string) {
48+
const model = await loadSchemaDocument(zmodelPath);
49+
const prismaSchema = await new PrismaSchemaGenerator(model).generate();
50+
const prismaSchemaFile = path.resolve(path.dirname(zmodelPath), '~schema.prisma');
51+
fs.writeFileSync(prismaSchemaFile, prismaSchema);
52+
return prismaSchemaFile;
53+
}

packages/cli/src/actions/db.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,44 @@
1-
import path from 'node:path';
1+
import fs from 'node:fs';
22
import { execPackage } from '../utils/exec-utils';
3-
import { getSchemaFile, handleSubProcessError } from './action-utils';
4-
import { run as runGenerate } from './generate';
3+
import { generateTempPrismaSchema, getSchemaFile, handleSubProcessError } from './action-utils';
54

6-
type CommonOptions = {
5+
type Options = {
76
schema?: string;
8-
name?: string;
7+
acceptDataLoss?: boolean;
8+
forceReset?: boolean;
99
};
1010

1111
/**
1212
* CLI action for db related commands
1313
*/
14-
export async function run(command: string, options: CommonOptions) {
15-
const schemaFile = getSchemaFile(options.schema);
16-
17-
// run generate first
18-
await runGenerate({
19-
schema: schemaFile,
20-
silent: true,
21-
});
22-
23-
const prismaSchemaFile = path.join(path.dirname(schemaFile), 'schema.prisma');
24-
14+
export async function run(command: string, options: Options) {
2515
switch (command) {
2616
case 'push':
27-
await runPush(prismaSchemaFile, options);
17+
await runPush(options);
2818
break;
2919
}
3020
}
3121

32-
async function runPush(prismaSchemaFile: string, options: any) {
33-
const cmd = `prisma db push --schema "${prismaSchemaFile}"${
34-
options.acceptDataLoss ? ' --accept-data-loss' : ''
35-
}${options.forceReset ? ' --force-reset' : ''} --skip-generate`;
22+
async function runPush(options: Options) {
23+
// generate a temp prisma schema file
24+
const schemaFile = getSchemaFile(options.schema);
25+
const prismaSchemaFile = await generateTempPrismaSchema(schemaFile);
26+
3627
try {
37-
await execPackage(cmd, {
38-
stdio: 'inherit',
39-
});
40-
} catch (err) {
41-
handleSubProcessError(err);
28+
// run prisma db push
29+
const cmd = `prisma db push --schema "${prismaSchemaFile}"${
30+
options.acceptDataLoss ? ' --accept-data-loss' : ''
31+
}${options.forceReset ? ' --force-reset' : ''} --skip-generate`;
32+
try {
33+
await execPackage(cmd, {
34+
stdio: 'inherit',
35+
});
36+
} catch (err) {
37+
handleSubProcessError(err);
38+
}
39+
} finally {
40+
if (fs.existsSync(prismaSchemaFile)) {
41+
fs.unlinkSync(prismaSchemaFile);
42+
}
4243
}
4344
}

packages/cli/src/actions/generate.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type Options = {
1010
schema?: string;
1111
output?: string;
1212
silent?: boolean;
13+
savePrismaSchema?: string | boolean;
1314
};
1415

1516
/**
@@ -28,8 +29,15 @@ export async function run(options: Options) {
2829
await runPlugins(model, outputPath, tsSchemaFile);
2930

3031
// generate Prisma schema
31-
const prismaSchema = await new PrismaSchemaGenerator(model).generate();
32-
fs.writeFileSync(path.join(outputPath, 'schema.prisma'), prismaSchema);
32+
if (options.savePrismaSchema) {
33+
const prismaSchema = await new PrismaSchemaGenerator(model).generate();
34+
let prismaSchemaFile = path.join(outputPath, 'schema.prisma');
35+
if (typeof options.savePrismaSchema === 'string') {
36+
prismaSchemaFile = path.resolve(outputPath, options.savePrismaSchema);
37+
fs.mkdirSync(path.dirname(prismaSchemaFile), { recursive: true });
38+
}
39+
fs.writeFileSync(prismaSchemaFile, prismaSchema);
40+
}
3341

3442
if (!options.silent) {
3543
console.log(colors.green('Generation completed successfully.'));

packages/cli/src/actions/migrate.ts

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,70 @@
1-
import path from 'node:path';
1+
import fs from 'node:fs';
22
import { execPackage } from '../utils/exec-utils';
3-
import { getSchemaFile } from './action-utils';
4-
import { run as runGenerate } from './generate';
3+
import { generateTempPrismaSchema, getSchemaFile } from './action-utils';
54

65
type CommonOptions = {
76
schema?: string;
7+
};
8+
9+
type DevOptions = CommonOptions & {
810
name?: string;
11+
createOnly?: boolean;
12+
};
13+
14+
type ResetOptions = CommonOptions & {
15+
force?: boolean;
916
};
1017

18+
type DeployOptions = CommonOptions;
19+
20+
type StatusOptions = CommonOptions;
21+
1122
/**
1223
* CLI action for migration-related commands
1324
*/
1425
export async function run(command: string, options: CommonOptions) {
1526
const schemaFile = getSchemaFile(options.schema);
27+
const prismaSchemaFile = await generateTempPrismaSchema(schemaFile);
1628

17-
// run generate first
18-
await runGenerate({
19-
schema: schemaFile,
20-
silent: true,
21-
});
22-
23-
const prismaSchemaFile = path.join(path.dirname(schemaFile), 'schema.prisma');
24-
25-
switch (command) {
26-
case 'dev':
27-
await runDev(prismaSchemaFile, options);
28-
break;
29+
try {
30+
switch (command) {
31+
case 'dev':
32+
await runDev(prismaSchemaFile, options as DevOptions);
33+
break;
2934

30-
case 'reset':
31-
await runReset(prismaSchemaFile, options as any);
32-
break;
35+
case 'reset':
36+
await runReset(prismaSchemaFile, options as ResetOptions);
37+
break;
3338

34-
case 'deploy':
35-
await runDeploy(prismaSchemaFile, options);
36-
break;
39+
case 'deploy':
40+
await runDeploy(prismaSchemaFile, options as DeployOptions);
41+
break;
3742

38-
case 'status':
39-
await runStatus(prismaSchemaFile, options);
40-
break;
43+
case 'status':
44+
await runStatus(prismaSchemaFile, options as StatusOptions);
45+
break;
46+
}
47+
} finally {
48+
if (fs.existsSync(prismaSchemaFile)) {
49+
fs.unlinkSync(prismaSchemaFile);
50+
}
4151
}
4252
}
4353

44-
async function runDev(prismaSchemaFile: string, _options: unknown) {
54+
async function runDev(prismaSchemaFile: string, options: DevOptions) {
4555
try {
46-
await execPackage(`prisma migrate dev --schema "${prismaSchemaFile}" --skip-generate`, {
47-
stdio: 'inherit',
48-
});
56+
await execPackage(
57+
`prisma migrate dev --schema "${prismaSchemaFile}" --skip-generate${options.name ? ` --name ${options.name}` : ''}${options.createOnly ? ' --create-only' : ''}`,
58+
{
59+
stdio: 'inherit',
60+
},
61+
);
4962
} catch (err) {
5063
handleSubProcessError(err);
5164
}
5265
}
5366

54-
async function runReset(prismaSchemaFile: string, options: { force: boolean }) {
67+
async function runReset(prismaSchemaFile: string, options: ResetOptions) {
5568
try {
5669
await execPackage(`prisma migrate reset --schema "${prismaSchemaFile}"${options.force ? ' --force' : ''}`, {
5770
stdio: 'inherit',
@@ -61,7 +74,7 @@ async function runReset(prismaSchemaFile: string, options: { force: boolean }) {
6174
}
6275
}
6376

64-
async function runDeploy(prismaSchemaFile: string, _options: unknown) {
77+
async function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
6578
try {
6679
await execPackage(`prisma migrate deploy --schema "${prismaSchemaFile}"`, {
6780
stdio: 'inherit',
@@ -71,7 +84,7 @@ async function runDeploy(prismaSchemaFile: string, _options: unknown) {
7184
}
7285
}
7386

74-
async function runStatus(prismaSchemaFile: string, _options: unknown) {
87+
async function runStatus(prismaSchemaFile: string, _options: StatusOptions) {
7588
try {
7689
await execPackage(`prisma migrate status --schema "${prismaSchemaFile}"`, {
7790
stdio: 'inherit',

packages/cli/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ export function createProgram() {
4949
.command('generate')
5050
.description('Run code generation.')
5151
.addOption(schemaOption)
52+
.addOption(new Option('--silent', 'do not print any output'))
53+
.addOption(
54+
new Option(
55+
'--save-prisma-schema [path]',
56+
'save a Prisma schema file, by default into the output directory',
57+
),
58+
)
5259
.addOption(new Option('-o, --output <path>', 'default output directory for core plugins'))
5360
.action(generateAction);
5461

packages/cli/test/db.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
});

packages/cli/test/generate.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
});

0 commit comments

Comments
 (0)