Skip to content
Closed
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
11 changes: 10 additions & 1 deletion packages/cli/src/actions/action-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import fs from 'node:fs';
import path from 'node:path';
import { CliError } from '../cli-error';

/**
* Resolve and return the filesystem path to the ZenStack schema file to use.
*
* If `file` is provided, it is validated and returned. Otherwise the function looks for a schema configured in the nearest package.json `zenstack.schema`; if that path is a directory, it looks for `schema.zmodel` inside it. If no package.json configuration is found, the function checks the default locations `./zenstack/schema.zmodel` and `./schema.zmodel`.
*
* @param file - Optional explicit path to a schema file or directory to use
* @returns The resolved path to the schema file to load
* @throws {CliError} If the resolved schema file (or expected schema inside a configured directory) does not exist
*/
export function getSchemaFile(file?: string) {
if (file) {
if (!fs.existsSync(file)) {
Expand Down Expand Up @@ -123,4 +132,4 @@ function findUp<Multiple extends boolean = false>(
return (multiple && result.length > 0 ? result : undefined) as FindUpResult<Multiple>;
}
return findUp(names, up, multiple, result);
}
}
12 changes: 11 additions & 1 deletion packages/cli/src/actions/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ export async function run(command: string, options: Options) {
}
}

/**
* Pushes the Prisma schema to the database using a temporary schema file and removes the temporary file when finished.
*
* Generates a temporary Prisma schema from the provided base schema, runs `prisma db push` with optional flags, and ensures the temporary file is deleted regardless of success or failure. Subprocess errors from the Prisma CLI are handled internally.
*
* @param options - Configuration for the push:
* - `schema`: path to the base Prisma schema to use when generating the temporary schema (optional).
* - `acceptDataLoss`: include `--accept-data-loss` when pushing to the database (optional).
* - `forceReset`: include `--force-reset` when pushing to the database (optional).
*/
async function runPush(options: Options) {
// generate a temp prisma schema file
const schemaFile = getSchemaFile(options.schema);
Expand All @@ -44,4 +54,4 @@ async function runPush(options: Options) {
fs.unlinkSync(prismaSchemaFile);
}
}
}
}
40 changes: 38 additions & 2 deletions packages/cli/src/actions/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ type ResolveOptions = CommonOptions & {
};

/**
* CLI action for migration-related commands
* Run a migration-related CLI command using a temporary Prisma schema and ensure the temporary schema file is removed.
*
* @param command - The migration command to run: 'dev', 'reset', 'deploy', 'status', or 'resolve'
* @param options - Common options that may include `schema` (path to a Prisma schema) and `migrations` (path to the migrations directory); additional command-specific options are accepted for certain commands
*/
export async function run(command: string, options: CommonOptions) {
const schemaFile = getSchemaFile(options.schema);
Expand Down Expand Up @@ -64,6 +67,14 @@ export async function run(command: string, options: CommonOptions) {
}
}

/**
* Run Prisma Migrate in development mode using the given schema and options.
*
* Invokes the CLI command equivalent to `migrate dev` with `--schema` and `--skip-generate`, and adds `--name` and/or `--create-only` when provided in `options`.
*
* @param prismaSchemaFile - Path to the Prisma schema file to use for the migrate command.
* @param options - Options controlling migrate behavior (may include `name` and `createOnly`).
*/
function runDev(prismaSchemaFile: string, options: DevOptions) {
try {
const cmd = [
Expand All @@ -79,6 +90,12 @@ function runDev(prismaSchemaFile: string, options: DevOptions) {
}
}

/**
* Runs `prisma migrate reset` against the provided Prisma schema file.
*
* @param prismaSchemaFile - Path to the Prisma schema file to target
* @param options - Reset options; if `options.force` is `true`, the reset proceeds without interactive confirmation
*/
function runReset(prismaSchemaFile: string, options: ResetOptions) {
try {
const cmd = [
Expand All @@ -93,6 +110,11 @@ function runReset(prismaSchemaFile: string, options: ResetOptions) {
}
}

/**
* Executes a Prisma Migrate deploy using the specified Prisma schema file.
*
* @param prismaSchemaFile - Path to the Prisma schema file to use for the deploy command
*/
function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
try {
const cmd = ['migrate deploy', ` --schema "${prismaSchemaFile}"`].join('');
Expand All @@ -102,6 +124,13 @@ function runDeploy(prismaSchemaFile: string, _options: DeployOptions) {
}
}

/**
* Show the current status of database migrations for the given Prisma schema.
*
* Runs the `migrate status` command against the provided schema file. Subprocess failures are handled by the module's subprocess error handler.
*
* @param prismaSchemaFile - Path to the Prisma schema file to use for the status check
*/
function runStatus(prismaSchemaFile: string, _options: StatusOptions) {
try {
execPrisma(`migrate status --schema "${prismaSchemaFile}"`);
Expand All @@ -110,6 +139,13 @@ function runStatus(prismaSchemaFile: string, _options: StatusOptions) {
}
}

/**
* Resolve migration status for specified migration names against a Prisma schema.
*
* @param prismaSchemaFile - Path to the Prisma schema file to use for the migrate command
* @param options - Resolve options; include `applied` to mark a migration as applied and/or `rolledBack` to mark a migration as rolled back
* @throws CliError - If neither `applied` nor `rolledBack` is provided on `options`
*/
function runResolve(prismaSchemaFile: string, options: ResolveOptions) {
if (!options.applied && !options.rolledBack) {
throw new CliError('Either --applied or --rolled-back option must be provided');
Expand All @@ -134,4 +170,4 @@ function handleSubProcessError(err: unknown) {
} else {
process.exit(1);
}
}
}
14 changes: 11 additions & 3 deletions packages/cli/src/utils/exec-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export function execSync(cmd: string, options?: Omit<ExecSyncOptions, 'env'> & {
}

/**
* Utility for running package commands through npx/bunx
* Run a package-manager command using `bunx` when running on Bun, otherwise `npx`.
*
* @param cmd - The package command and its arguments (e.g. `"install foo"` or `"run build"`).
* @param options - Additional child_process.execSync options; `env` may be provided to override or extend the environment.
*/
export function execPackage(
cmd: string,
Expand All @@ -27,7 +30,12 @@ export function execPackage(
}

/**
* Utility for running prisma commands
* Execute the Prisma CLI with the provided command-line arguments.
*
* Resolves the installed Prisma binary and runs it via `node` with the given `args`.
*
* @param args - Command-line arguments to pass to the Prisma CLI (e.g., `"migrate deploy"`).
* @param options - Options forwarded to the underlying `execSync`. If `options.env` is provided, its entries are merged with `process.env`.
*/
export function execPrisma(args: string, options?: Omit<ExecSyncOptions, 'env'> & { env?: Record<string, string> }) {
let prismaPath: string;
Expand All @@ -39,4 +47,4 @@ export function execPrisma(args: string, options?: Omit<ExecSyncOptions, 'env'>
prismaPath = require.resolve('prisma/build/index.js');
}
execSync(`node ${prismaPath} ${args}`, options);
}
}
26 changes: 25 additions & 1 deletion packages/testtools/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,30 @@ export async function createTestClient<Schema extends SchemaDef>(
schema: string,
options?: CreateTestClientOptions<Schema>,
): Promise<any>;
/**
* Creates and configures a test ZenStackClient using the provided schema and options.
*
* This function prepares a working directory and datasource, optionally creates or resets
* the test database (SQLite file or PostgreSQL database), may write schema files, copy
* additional files into the work directory, and applies any provided client plugins.
*
* @param schema - A schema definition object or a ZModel schema string. When a string is provided,
* a TypeScript schema is generated into a temporary work directory.
* @param options - Configuration for test client creation. Important options:
* - provider: 'sqlite' | 'postgresql' (selects the database provider)
* - schemaFile: path to an existing .zmodel file to copy/adjust into the workDir
* - dbName: explicit database name (auto-generated when omitted)
* - usePrismaPush: when true, runs `prisma db push` using a generated Prisma schema
* - extraSourceFiles: additional source files to include when generating a TS schema
* - workDir: directory to use for generated files and database (created if omitted)
* - debug: when true, enables test logging and prints the work directory
* - dbFile: path to an existing SQLite database file to copy into the workDir (sqlite only)
* - dataSourceExtensions: list of datasource extensions to include in generated schema
* - copyFiles: array of { globPattern, destination } entries whose matched files are copied
* (matched relative to the current test file) into the workDir
* - plugins: array of PolicyPlugin or other client plugins to apply to the returned client
* @returns The configured ZenStackClient instance for the prepared test schema and datasource.
*/
export async function createTestClient<Schema extends SchemaDef>(
schema: Schema | string,
options?: CreateTestClientOptions<Schema>,
Expand Down Expand Up @@ -288,4 +312,4 @@ function getTestDbName(provider: string) {
.substring(0, 30) +
digest.slice(0, 6)
);
}
}
13 changes: 12 additions & 1 deletion tests/e2e/scripts/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { fileURLToPath } from 'node:url';

const dir = path.dirname(fileURLToPath(import.meta.url));

/**
* Discovers project .zmodel schema files and generates TypeScript schemas for each.
*
* Searches the repository's ORM and app schema locations for `.zmodel` files, logs each file being processed, and invokes `generate` for every discovered file.
*/
async function main() {
const zmodelFiles = [
...glob.sync(path.resolve(dir, '../orm/schemas/**/*.zmodel')),
Expand All @@ -18,6 +23,12 @@ async function main() {
}
}

/**
* Generates TypeScript schema files from a .zmodel file and writes them to the schema's directory.
*
* @param schemaPath - Filesystem path to the source `.zmodel` schema file
* @throws Error if the schema document fails to load; the error message includes the schema path and load errors
*/
async function generate(schemaPath: string) {
const generator = new TsSchemaGenerator();
const outDir = path.dirname(schemaPath);
Expand All @@ -35,4 +46,4 @@ async function generate(schemaPath: string) {
await generator.generate(result.model as Model, { outDir });
}

main();
main();
Loading