Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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;
}

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(' ') : ''}`;

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.');
}
}
25 changes: 25 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,26 @@ 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')
.allowExcessArguments(true)
.addHelpText(
'after',
`
Seed script is configured under the "zenstack.seed" field in package.json.
E.g.:
{
"zenstack": {
"seed": "ts-node ./zenstack/seed.ts"
}
}

Arguments following -- are passed to the seed script. E.g.: "zen db seed -- --users 10"`,
)
.addOption(noVersionCheckOption)
.action((options, command) => seedAction(options, command.args));

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');
`,
);

runCli('db seed', workDir);
expect(fs.readFileSync(path.join(workDir, 'seed.txt'), 'utf8')).toBe('success');
});

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');
`,
);

runCli('migrate reset --force', workDir);
expect(fs.readFileSync(path.join(workDir, 'seed.txt'), 'utf8')).toBe('success');
});

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