Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zenstack-v3",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"description": "ZenStack",
"packageManager": "[email protected]",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/auth-adapters/better-auth/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/better-auth",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.",
"type": "module",
"scripts": {
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publisher": "zenstack",
"displayName": "ZenStack CLI",
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"type": "module",
"author": {
"name": "ZenStack Team"
Expand Down Expand Up @@ -32,18 +32,17 @@
"@zenstackhq/common-helpers": "workspace:*",
"@zenstackhq/language": "workspace:*",
"@zenstackhq/sdk": "workspace:*",
"prisma": "catalog:",
"colors": "1.4.0",
"commander": "^8.3.0",
"execa": "^9.6.0",
"langium": "catalog:",
"mixpanel": "^0.18.1",
"ora": "^5.4.1",
"package-manager-detector": "^1.3.0",
"semver": "^7.7.2",
"ts-pattern": "catalog:"
},
"peerDependencies": {
"prisma": "catalog:"
},
"devDependencies": {
"@types/better-sqlite3": "catalog:",
"@types/semver": "^7.7.0",
Expand All @@ -54,6 +53,7 @@
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*",
"better-sqlite3": "catalog:",
"tmp": "catalog:"
"tmp": "catalog:",
"prisma": "catalog:"
}
}
25 changes: 22 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 Expand Up @@ -124,3 +135,11 @@ function findUp<Multiple extends boolean = false>(
}
return findUp(names, up, multiple, result);
}

export async function requireDataSourceUrl(schemaFile: string) {
const zmodel = await loadSchemaDocument(schemaFile);
const dataSource = zmodel.declarations.find(isDataSource);
if (!dataSource?.fields.some((f) => f.name === 'url')) {
throw new CliError('The schema\'s "datasource" must have a "url" field to use this command.');
}
}
8 changes: 6 additions & 2 deletions packages/cli/src/actions/db.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'node:fs';
import { execPrisma } from '../utils/exec-utils';
import { generateTempPrismaSchema, getSchemaFile, handleSubProcessError } from './action-utils';
import { generateTempPrismaSchema, getSchemaFile, handleSubProcessError, requireDataSourceUrl } from './action-utils';

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

async function runPush(options: Options) {
// generate a temp prisma schema file
const schemaFile = getSchemaFile(options.schema);

// validate datasource url exists
await requireDataSourceUrl(schemaFile);

// generate a temp prisma schema file
const prismaSchemaFile = await generateTempPrismaSchema(schemaFile);

try {
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 };
16 changes: 14 additions & 2 deletions packages/cli/src/actions/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import fs from 'node:fs';
import path from 'node:path';
import { CliError } from '../cli-error';
import { execPrisma } from '../utils/exec-utils';
import { generateTempPrismaSchema, getSchemaFile } from './action-utils';
import { generateTempPrismaSchema, getSchemaFile, requireDataSourceUrl } from './action-utils';
import { run as runSeed } from './seed';

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

type DevOptions = CommonOptions & {
Expand All @@ -32,6 +34,10 @@ type ResolveOptions = CommonOptions & {
*/
export async function run(command: string, options: CommonOptions) {
const schemaFile = getSchemaFile(options.schema);

// validate datasource url exists
await requireDataSourceUrl(schemaFile);

const prismaSchemaDir = options.migrations ? path.dirname(options.migrations) : undefined;
const prismaSchemaFile = await generateTempPrismaSchema(schemaFile, prismaSchemaDir);

Expand Down Expand Up @@ -70,6 +76,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 +86,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.');
}
}
29 changes: 29 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,8 +91,13 @@ 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')
.addHelpText(
'after',
'\nIf there is a seed script defined in package.json, it will be run after the reset. Use --skip-seed to skip it.',
)
.action((options) => migrateAction('reset', options));

migrateCommand
Expand Down Expand Up @@ -128,6 +137,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);
});
});
2 changes: 1 addition & 1 deletion packages/clients/tanstack-query/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/tanstack-query",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"description": "TanStack Query Client for consuming ZenStack v3's CRUD service",
"main": "index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion packages/common-helpers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/common-helpers",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"description": "ZenStack Common Helpers",
"type": "module",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion packages/config/eslint-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/eslint-config",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"type": "module",
"private": true,
"license": "MIT"
Expand Down
2 changes: 1 addition & 1 deletion packages/config/typescript-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zenstackhq/typescript-config",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"private": true,
"license": "MIT"
}
2 changes: 1 addition & 1 deletion packages/config/vitest-config/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/vitest-config",
"type": "module",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"private": true,
"license": "MIT",
"exports": {
Expand Down
2 changes: 1 addition & 1 deletion packages/create-zenstack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-zenstack",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"description": "Create a new ZenStack project",
"type": "module",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions packages/language/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@zenstackhq/language",
"description": "ZenStack ZModel language specification",
"version": "3.0.0-beta.24",
"version": "3.0.0-beta.25",
"license": "MIT",
"author": "ZenStack Team",
"files": [
Expand Down Expand Up @@ -55,6 +55,7 @@
}
},
"dependencies": {
"@zenstackhq/common-helpers": "workspace:*",
"langium": "catalog:",
"pluralize": "^8.0.0",
"ts-pattern": "catalog:",
Expand All @@ -63,7 +64,6 @@
"devDependencies": {
"@types/pluralize": "^0.0.33",
"@types/tmp": "catalog:",
"@zenstackhq/common-helpers": "workspace:*",
"@zenstackhq/eslint-config": "workspace:*",
"@zenstackhq/typescript-config": "workspace:*",
"@zenstackhq/vitest-config": "workspace:*",
Expand Down
Loading
Loading