Skip to content

Commit 1e6f7ef

Browse files
authored
Merge pull request #435 from zenstackhq/dev
merge dev to main (v3.0.0-beta.25)
2 parents a831866 + 7e57864 commit 1e6f7ef

File tree

45 files changed

+539
-122
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+539
-122
lines changed

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-beta.24",
3+
"version": "3.0.0-beta.25",
44
"description": "ZenStack",
55
"packageManager": "[email protected]",
66
"scripts": {

packages/auth-adapters/better-auth/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zenstackhq/better-auth",
3-
"version": "3.0.0-beta.24",
3+
"version": "3.0.0-beta.25",
44
"description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.",
55
"type": "module",
66
"scripts": {

packages/cli/package.json

Lines changed: 3 additions & 4 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-beta.24",
6+
"version": "3.0.0-beta.25",
77
"type": "module",
88
"author": {
99
"name": "ZenStack Team"
@@ -32,18 +32,17 @@
3232
"@zenstackhq/common-helpers": "workspace:*",
3333
"@zenstackhq/language": "workspace:*",
3434
"@zenstackhq/sdk": "workspace:*",
35+
"prisma": "catalog:",
3536
"colors": "1.4.0",
3637
"commander": "^8.3.0",
38+
"execa": "^9.6.0",
3739
"langium": "catalog:",
3840
"mixpanel": "^0.18.1",
3941
"ora": "^5.4.1",
4042
"package-manager-detector": "^1.3.0",
4143
"semver": "^7.7.2",
4244
"ts-pattern": "catalog:"
4345
},
44-
"peerDependencies": {
45-
"prisma": "catalog:"
46-
},
4746
"devDependencies": {
4847
"@types/better-sqlite3": "catalog:",
4948
"@types/semver": "^7.7.0",

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ export async function generateTempPrismaSchema(zmodelPath: string, folder?: stri
7878
}
7979

8080
export function getPkgJsonConfig(startPath: string) {
81-
const result: { schema: string | undefined; output: string | undefined } = { schema: undefined, output: undefined };
81+
const result: { schema: string | undefined; output: string | undefined; seed: string | undefined } = {
82+
schema: undefined,
83+
output: undefined,
84+
seed: undefined,
85+
};
8286
const pkgJsonFile = findUp(['package.json'], startPath, false);
8387

8488
if (!pkgJsonFile) {
@@ -93,8 +97,16 @@ export function getPkgJsonConfig(startPath: string) {
9397
}
9498

9599
if (pkgJson.zenstack && typeof pkgJson.zenstack === 'object') {
96-
result.schema = pkgJson.zenstack.schema && path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.schema);
97-
result.output = pkgJson.zenstack.output && path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.output);
100+
result.schema =
101+
pkgJson.zenstack.schema && typeof pkgJson.zenstack.schema === 'string'
102+
? path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.schema)
103+
: undefined;
104+
result.output =
105+
pkgJson.zenstack.output && typeof pkgJson.zenstack.output === 'string'
106+
? path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.output)
107+
: undefined;
108+
result.seed =
109+
typeof pkgJson.zenstack.seed === 'string' && pkgJson.zenstack.seed ? pkgJson.zenstack.seed : undefined;
98110
}
99111

100112
return result;
@@ -124,3 +136,11 @@ function findUp<Multiple extends boolean = false>(
124136
}
125137
return findUp(names, up, multiple, result);
126138
}
139+
140+
export async function requireDataSourceUrl(schemaFile: string) {
141+
const zmodel = await loadSchemaDocument(schemaFile);
142+
const dataSource = zmodel.declarations.find(isDataSource);
143+
if (!dataSource?.fields.some((f) => f.name === 'url')) {
144+
throw new CliError('The schema\'s "datasource" must have a "url" field to use this command.');
145+
}
146+
}

packages/cli/src/actions/db.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from 'node:fs';
22
import { execPrisma } from '../utils/exec-utils';
3-
import { generateTempPrismaSchema, getSchemaFile, handleSubProcessError } from './action-utils';
3+
import { generateTempPrismaSchema, getSchemaFile, handleSubProcessError, requireDataSourceUrl } from './action-utils';
44

55
type Options = {
66
schema?: string;
@@ -20,8 +20,12 @@ export async function run(command: string, options: Options) {
2020
}
2121

2222
async function runPush(options: Options) {
23-
// generate a temp prisma schema file
2423
const schemaFile = getSchemaFile(options.schema);
24+
25+
// validate datasource url exists
26+
await requireDataSourceUrl(schemaFile);
27+
28+
// generate a temp prisma schema file
2529
const prismaSchemaFile = await generateTempPrismaSchema(schemaFile);
2630

2731
try {

packages/cli/src/actions/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ import { run as generate } from './generate';
55
import { run as info } from './info';
66
import { run as init } from './init';
77
import { run as migrate } from './migrate';
8+
import { run as seed } from './seed';
89

9-
export { check, db, format, generate, info, init, migrate };
10+
export { check, db, format, generate, info, init, migrate, seed };

packages/cli/src/actions/migrate.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import fs from 'node:fs';
22
import path from 'node:path';
33
import { CliError } from '../cli-error';
44
import { execPrisma } from '../utils/exec-utils';
5-
import { generateTempPrismaSchema, getSchemaFile } from './action-utils';
5+
import { generateTempPrismaSchema, getSchemaFile, requireDataSourceUrl } from './action-utils';
6+
import { run as runSeed } from './seed';
67

78
type CommonOptions = {
89
schema?: string;
910
migrations?: string;
11+
skipSeed?: boolean;
1012
};
1113

1214
type DevOptions = CommonOptions & {
@@ -32,6 +34,10 @@ type ResolveOptions = CommonOptions & {
3234
*/
3335
export async function run(command: string, options: CommonOptions) {
3436
const schemaFile = getSchemaFile(options.schema);
37+
38+
// validate datasource url exists
39+
await requireDataSourceUrl(schemaFile);
40+
3541
const prismaSchemaDir = options.migrations ? path.dirname(options.migrations) : undefined;
3642
const prismaSchemaFile = await generateTempPrismaSchema(schemaFile, prismaSchemaDir);
3743

@@ -70,6 +76,7 @@ function runDev(prismaSchemaFile: string, options: DevOptions) {
7076
'migrate dev',
7177
` --schema "${prismaSchemaFile}"`,
7278
' --skip-generate',
79+
' --skip-seed',
7380
options.name ? ` --name "${options.name}"` : '',
7481
options.createOnly ? ' --create-only' : '',
7582
].join('');
@@ -79,18 +86,23 @@ function runDev(prismaSchemaFile: string, options: DevOptions) {
7986
}
8087
}
8188

82-
function runReset(prismaSchemaFile: string, options: ResetOptions) {
89+
async function runReset(prismaSchemaFile: string, options: ResetOptions) {
8390
try {
8491
const cmd = [
8592
'migrate reset',
8693
` --schema "${prismaSchemaFile}"`,
8794
' --skip-generate',
95+
' --skip-seed',
8896
options.force ? ' --force' : '',
8997
].join('');
9098
execPrisma(cmd);
9199
} catch (err) {
92100
handleSubProcessError(err);
93101
}
102+
103+
if (!options.skipSeed) {
104+
await runSeed({ noWarnings: true, printStatus: true }, []);
105+
}
94106
}
95107

96108
function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {

packages/cli/src/actions/seed.ts

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

packages/cli/src/index.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ const formatAction = async (options: Parameters<typeof actions.format>[0]): Prom
3434
await telemetry.trackCommand('format', () => actions.format(options));
3535
};
3636

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+
3741
function createProgram() {
3842
const program = new Command('zen')
3943
.alias('zenstack')
@@ -87,8 +91,13 @@ function createProgram() {
8791
.addOption(schemaOption)
8892
.addOption(new Option('--force', 'skip the confirmation prompt'))
8993
.addOption(migrationsOption)
94+
.addOption(new Option('--skip-seed', 'skip seeding the database after reset'))
9095
.addOption(noVersionCheckOption)
9196
.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+
)
92101
.action((options) => migrateAction('reset', options));
93102

94103
migrateCommand
@@ -128,6 +137,26 @@ function createProgram() {
128137
.addOption(new Option('--force-reset', 'force a reset of the database before push'))
129138
.action((options) => dbAction('push', options));
130139

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+
131160
program
132161
.command('info')
133162
.description('Get information of installed ZenStack packages')

packages/cli/test/db.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,47 @@ describe('CLI db commands test', () => {
1515
runCli('db push', workDir);
1616
expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
1717
});
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+
});
1861
});

0 commit comments

Comments
 (0)