diff --git a/packages/cli/src/actions/action-utils.ts b/packages/cli/src/actions/action-utils.ts index 287c5593..fa2bb0a5 100644 --- a/packages/cli/src/actions/action-utils.ts +++ b/packages/cli/src/actions/action-utils.ts @@ -19,7 +19,15 @@ export function getSchemaFile(file?: string) { if (!fs.existsSync(pkgJsonConfig.schema)) { throw new CliError(`Schema file not found: ${pkgJsonConfig.schema}`); } - return pkgJsonConfig.schema; + if (fs.statSync(pkgJsonConfig.schema).isDirectory()) { + const schemaPath = path.join(pkgJsonConfig.schema, 'schema.zmodel'); + if (!fs.existsSync(schemaPath)) { + throw new CliError(`Schema file not found: ${schemaPath}`); + } + return schemaPath; + } else { + return pkgJsonConfig.schema; + } } if (fs.existsSync('./zenstack/schema.zmodel')) { diff --git a/packages/cli/src/actions/db.ts b/packages/cli/src/actions/db.ts index e0839121..bd3d45a8 100644 --- a/packages/cli/src/actions/db.ts +++ b/packages/cli/src/actions/db.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import { execPackage } from '../utils/exec-utils'; +import { execPrisma } from '../utils/exec-utils'; import { generateTempPrismaSchema, getSchemaFile, handleSubProcessError } from './action-utils'; type Options = { @@ -27,7 +27,7 @@ async function runPush(options: Options) { try { // run prisma db push const cmd = [ - 'prisma db push', + 'db push', ` --schema "${prismaSchemaFile}"`, options.acceptDataLoss ? ' --accept-data-loss' : '', options.forceReset ? ' --force-reset' : '', @@ -35,7 +35,7 @@ async function runPush(options: Options) { ].join(''); try { - await execPackage(cmd); + execPrisma(cmd); } catch (err) { handleSubProcessError(err); } diff --git a/packages/cli/src/actions/migrate.ts b/packages/cli/src/actions/migrate.ts index eb001e8f..d2f3595b 100644 --- a/packages/cli/src/actions/migrate.ts +++ b/packages/cli/src/actions/migrate.ts @@ -1,7 +1,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { CliError } from '../cli-error'; -import { execPackage } from '../utils/exec-utils'; +import { execPrisma } from '../utils/exec-utils'; import { generateTempPrismaSchema, getSchemaFile } from './action-utils'; type CommonOptions = { @@ -64,69 +64,65 @@ export async function run(command: string, options: CommonOptions) { } } -async function runDev(prismaSchemaFile: string, options: DevOptions) { +function runDev(prismaSchemaFile: string, options: DevOptions) { try { const cmd = [ - 'prisma migrate dev', + 'migrate dev', ` --schema "${prismaSchemaFile}"`, ' --skip-generate', - options.name ? ` --name ${options.name}` : '', + options.name ? ` --name "${options.name}"` : '', options.createOnly ? ' --create-only' : '', ].join(''); - - await execPackage(cmd); + execPrisma(cmd); } catch (err) { handleSubProcessError(err); } } -async function runReset(prismaSchemaFile: string, options: ResetOptions) { +function runReset(prismaSchemaFile: string, options: ResetOptions) { try { const cmd = [ - 'prisma migrate reset', + 'migrate reset', ` --schema "${prismaSchemaFile}"`, ' --skip-generate', options.force ? ' --force' : '', ].join(''); - - await execPackage(cmd); + execPrisma(cmd); } catch (err) { handleSubProcessError(err); } } -async function runDeploy(prismaSchemaFile: string, _options: DeployOptions) { +function runDeploy(prismaSchemaFile: string, _options: DeployOptions) { try { - const cmd = ['prisma migrate deploy', ` --schema "${prismaSchemaFile}"`].join(''); - - await execPackage(cmd); + const cmd = ['migrate deploy', ` --schema "${prismaSchemaFile}"`].join(''); + execPrisma(cmd); } catch (err) { handleSubProcessError(err); } } -async function runStatus(prismaSchemaFile: string, _options: StatusOptions) { +function runStatus(prismaSchemaFile: string, _options: StatusOptions) { try { - await execPackage(`prisma migrate status --schema "${prismaSchemaFile}"`); + execPrisma(`migrate status --schema "${prismaSchemaFile}"`); } catch (err) { handleSubProcessError(err); } } -async function runResolve(prismaSchemaFile: string, options: ResolveOptions) { +function runResolve(prismaSchemaFile: string, options: ResolveOptions) { if (!options.applied && !options.rolledBack) { throw new CliError('Either --applied or --rolled-back option must be provided'); } try { const cmd = [ - 'prisma migrate resolve', + 'migrate resolve', ` --schema "${prismaSchemaFile}"`, - options.applied ? ` --applied ${options.applied}` : '', - options.rolledBack ? ` --rolled-back ${options.rolledBack}` : '', + options.applied ? ` --applied "${options.applied}"` : '', + options.rolledBack ? ` --rolled-back "${options.rolledBack}"` : '', ].join(''); - - await execPackage(cmd); + execPrisma(cmd); } catch (err) { handleSubProcessError(err); } diff --git a/packages/cli/src/utils/exec-utils.ts b/packages/cli/src/utils/exec-utils.ts index 4b2bd463..2ef0e26d 100644 --- a/packages/cli/src/utils/exec-utils.ts +++ b/packages/cli/src/utils/exec-utils.ts @@ -1,4 +1,5 @@ import { execSync as _exec, type ExecSyncOptions } from 'child_process'; +import { fileURLToPath } from 'url'; /** * Utility for executing command synchronously and prints outputs on current console @@ -24,3 +25,18 @@ export function execPackage( const packageManager = process?.versions?.['bun'] ? 'bunx' : 'npx'; execSync(`${packageManager} ${cmd}`, options); } + +/** + * Utility for running prisma commands + */ +export function execPrisma(args: string, options?: Omit & { env?: Record }) { + let prismaPath: string; + if (typeof import.meta.resolve === 'function') { + // esm + prismaPath = fileURLToPath(import.meta.resolve('prisma/build/index.js')); + } else { + // cjs + prismaPath = require.resolve('prisma/build/index.js'); + } + execSync(`node ${prismaPath} ${args}`, options); +} diff --git a/packages/cli/test/generate.test.ts b/packages/cli/test/generate.test.ts index 738cc9c7..074e88e5 100644 --- a/packages/cli/test/generate.test.ts +++ b/packages/cli/test/generate.test.ts @@ -45,6 +45,21 @@ describe('CLI generate command test', () => { expect(fs.existsSync(path.join(workDir, 'bar/schema.ts'))).toBe(true); }); + it('should respect package.json schema dir config', () => { + const workDir = createProject(model); + fs.mkdirSync(path.join(workDir, 'foo')); + fs.renameSync(path.join(workDir, 'zenstack/schema.zmodel'), path.join(workDir, 'foo/schema.zmodel')); + fs.rmdirSync(path.join(workDir, 'zenstack')); + const pkgJson = JSON.parse(fs.readFileSync(path.join(workDir, 'package.json'), 'utf8')); + pkgJson.zenstack = { + schema: './foo', + output: './bar', + }; + fs.writeFileSync(path.join(workDir, 'package.json'), JSON.stringify(pkgJson, null, 2)); + runCli('generate', workDir); + expect(fs.existsSync(path.join(workDir, 'bar/schema.ts'))).toBe(true); + }); + it('should respect lite option', () => { const workDir = createProject(model); runCli('generate --lite', workDir); diff --git a/packages/cli/test/migrate.test.ts b/packages/cli/test/migrate.test.ts index aace483a..56a0fec8 100644 --- a/packages/cli/test/migrate.test.ts +++ b/packages/cli/test/migrate.test.ts @@ -9,8 +9,7 @@ model User { } `; -// skip due to timeout in CI -describe.skip('CLI migrate commands test', () => { +describe('CLI migrate commands test', () => { it('should generate a database with migrate dev', () => { const workDir = createProject(model); runCli('migrate dev --name init', workDir);