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
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": "pnpm@10.20.0",
"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
7 changes: 3 additions & 4 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 Down
26 changes: 23 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,16 @@ 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)
: undefined;
result.output =
pkgJson.zenstack.output && typeof pkgJson.zenstack.output === 'string'
? path.resolve(path.dirname(pkgJsonFile), pkgJson.zenstack.output)
: undefined;
result.seed =
typeof pkgJson.zenstack.seed === 'string' && pkgJson.zenstack.seed ? pkgJson.zenstack.seed : undefined;
}

return result;
Expand Down Expand Up @@ -124,3 +136,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
2 changes: 1 addition & 1 deletion packages/ide/vscode/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "zenstack-v3",
"publisher": "zenstack",
"version": "3.0.12",
"version": "3.0.13",
"displayName": "ZenStack V3 Language Tools",
"description": "VSCode extension for ZenStack (v3) ZModel language",
"private": true,
Expand Down
Loading
Loading