Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@zenstackhq/sdk": "workspace:*",
"colors": "1.4.0",
"commander": "^8.3.0",
"execa": "^9.6.0",
"langium": "catalog:",
"mixpanel": "^0.18.1",
"ora": "^5.4.1",
Expand Down
17 changes: 14 additions & 3 deletions packages/cli/src/actions/action-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,11 @@ export async function generateTempPrismaSchema(zmodelPath: string, folder?: stri
}

export function getPkgJsonConfig(startPath: string) {
const result: { schema: string | undefined; output: string | undefined } = { schema: undefined, output: undefined };
const result: { schema: string | undefined; output: string | undefined; seed: string | undefined } = {
schema: undefined,
output: undefined,
seed: undefined,
};
const pkgJsonFile = findUp(['package.json'], startPath, false);

if (!pkgJsonFile) {
Expand All @@ -93,8 +97,15 @@ export function getPkgJsonConfig(startPath: string) {
}

if (pkgJson.zenstack && typeof pkgJson.zenstack === 'object') {
result.schema = pkgJson.zenstack.schema && path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.schema);
result.output = pkgJson.zenstack.output && path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.output);
result.schema =
pkgJson.zenstack.schema &&
typeof pkgJson.zenstack.schema === 'string' &&
path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.schema);
result.output =
pkgJson.zenstack.output &&
typeof pkgJson.zenstack.output === 'string' &&
path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.output);
result.seed = typeof pkgJson.zenstack.seed === 'string' && pkgJson.zenstack.seed;
Comment thread
ymc9 marked this conversation as resolved.
}

return result;
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import { run as generate } from './generate';
import { run as info } from './info';
import { run as init } from './init';
import { run as migrate } from './migrate';
import { run as seed } from './seed';

export { check, db, format, generate, info, init, migrate };
export { check, db, format, generate, info, init, migrate, seed };
10 changes: 9 additions & 1 deletion packages/cli/src/actions/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import path from 'node:path';
import { CliError } from '../cli-error';
import { execPrisma } from '../utils/exec-utils';
import { generateTempPrismaSchema, getSchemaFile } from './action-utils';
import { run as runSeed } from './seed';

type CommonOptions = {
schema?: string;
migrations?: string;
skipSeed?: boolean;
};

type DevOptions = CommonOptions & {
Expand Down Expand Up @@ -70,6 +72,7 @@ function runDev(prismaSchemaFile: string, options: DevOptions) {
'migrate dev',
` --schema "${prismaSchemaFile}"`,
' --skip-generate',
' --skip-seed',
options.name ? ` --name "${options.name}"` : '',
options.createOnly ? ' --create-only' : '',
].join('');
Expand All @@ -79,18 +82,23 @@ function runDev(prismaSchemaFile: string, options: DevOptions) {
}
}

function runReset(prismaSchemaFile: string, options: ResetOptions) {
async function runReset(prismaSchemaFile: string, options: ResetOptions) {
try {
const cmd = [
'migrate reset',
` --schema "${prismaSchemaFile}"`,
' --skip-generate',
' --skip-seed',
options.force ? ' --force' : '',
].join('');
execPrisma(cmd);
} catch (err) {
handleSubProcessError(err);
}

if (!options.skipSeed) {
await runSeed({ noWarnings: true, printStatus: true }, []);
}
}

function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
Expand Down
38 changes: 38 additions & 0 deletions packages/cli/src/actions/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import colors from 'colors';
import { execaCommand } from 'execa';
import { CliError } from '../cli-error';
import { getPkgJsonConfig } from './action-utils';

type Options = {
noWarnings?: boolean;
printStatus?: boolean;
};

/**
* CLI action for seeding the database.
*/
export async function run(options: Options, args: string[]) {
const pkgJsonConfig = getPkgJsonConfig(process.cwd());
if (!pkgJsonConfig.seed) {
if (!options.noWarnings) {
console.warn(colors.yellow('No seed script defined in package.json. Skipping seeding.'));
}
return;
}

const command = `${pkgJsonConfig.seed}${args.length > 0 ? ' ' + args.join(' ') : ''}`;
Comment thread
ymc9 marked this conversation as resolved.

if (options.printStatus) {
console.log(colors.gray(`Running seed script "${command}"...`));
}

try {
await execaCommand(command, {
stdout: 'inherit',
stderr: 'inherit',
});
} catch (err) {
console.error(colors.red(err instanceof Error ? err.message : String(err)));
throw new CliError('Failed to seed the database. Please check the error message above for details.');
}
}
13 changes: 13 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const formatAction = async (options: Parameters<typeof actions.format>[0]): Prom
await telemetry.trackCommand('format', () => actions.format(options));
};

const seedAction = async (options: Parameters<typeof actions.seed>[0], args: string[]): Promise<void> => {
await telemetry.trackCommand('db seed', () => actions.seed(options, args));
};

function createProgram() {
const program = new Command('zen')
.alias('zenstack')
Expand Down Expand Up @@ -87,6 +91,7 @@ function createProgram() {
.addOption(schemaOption)
.addOption(new Option('--force', 'skip the confirmation prompt'))
.addOption(migrationsOption)
.addOption(new Option('--skip-seed', 'skip seeding the database after reset'))
.addOption(noVersionCheckOption)
.description('Reset your database and apply all migrations, all data will be lost')
.action((options) => migrateAction('reset', options));
Expand Down Expand Up @@ -128,6 +133,14 @@ function createProgram() {
.addOption(new Option('--force-reset', 'force a reset of the database before push'))
.action((options) => dbAction('push', options));

dbCommand
.command('seed')
.description(
'Seed the database. Arguments following -- are passed to the seed script.\nE.g.: `zen db seed -- --users 10`',
Comment thread
ymc9 marked this conversation as resolved.
Outdated
)
.addOption(noVersionCheckOption)
.action((options, command) => seedAction(options, command.args));
Comment thread
ymc9 marked this conversation as resolved.

program
.command('info')
.description('Get information of installed ZenStack packages')
Expand Down
43 changes: 43 additions & 0 deletions packages/cli/test/db.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,47 @@ describe('CLI db commands test', () => {
runCli('db push', workDir);
expect(fs.existsSync(path.join(workDir, 'zenstack/dev.db'))).toBe(true);
});

it('should seed the database with db seed with seed script', () => {
const workDir = createProject(model);
const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
pkgJson.zenstack = {
seed: 'node seed.js',
};
fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
fs.writeFileSync(
path.join(workDir, 'seed.js'),
`
import fs from 'node:fs';
fs.writeFileSync('seed.txt', 'success');
`,
Comment thread
ymc9 marked this conversation as resolved.
);

runCli('db seed', workDir);
expect(fs.readFileSync(path.join(workDir, 'seed.txt'), 'utf8')).toBe('success');
});
Comment thread
ymc9 marked this conversation as resolved.

it('should seed the database after migrate reset', () => {
const workDir = createProject(model);
const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8'));
pkgJson.zenstack = {
seed: 'node seed.js',
};
fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
fs.writeFileSync(
path.join(workDir, 'seed.js'),
`
import fs from 'node:fs';
fs.writeFileSync('seed.txt', 'success');
`,
Comment thread
ymc9 marked this conversation as resolved.
);

runCli('migrate reset --force', workDir);
expect(fs.readFileSync(path.join(workDir, 'seed.txt'), 'utf8')).toBe('success');
});
Comment thread
ymc9 marked this conversation as resolved.

it('should skip seeding the database without seed script', () => {
const workDir = createProject(model);
runCli('db seed', workDir);
});
});
9 changes: 6 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading