From e22b2de802b5404b19267890147e7cd4c5971279 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Fri, 6 Feb 2026 09:13:20 +0000 Subject: [PATCH 1/8] chore: add warning for mismatched versions during `zen generate` --- packages/cli/src/actions/action-utils.ts | 44 ++++++++++++++++++++++ packages/cli/src/actions/generate.ts | 41 ++++++++++++++++++++- packages/cli/src/actions/info.ts | 47 +----------------------- 3 files changed, 85 insertions(+), 47 deletions(-) diff --git a/packages/cli/src/actions/action-utils.ts b/packages/cli/src/actions/action-utils.ts index d2e0ca2e9..1f225c14a 100644 --- a/packages/cli/src/actions/action-utils.ts +++ b/packages/cli/src/actions/action-utils.ts @@ -156,3 +156,47 @@ export function getOutputPath(options: { output?: string }, schemaFile: string) return path.dirname(schemaFile); } } +export async function getZenStackPackages(projectPath: string): Promise> { + let pkgJson: { + dependencies: Record; + devDependencies: Record; + }; + const resolvedPath = path.resolve(projectPath); + try { + pkgJson = ( + await import(path.join(resolvedPath, 'package.json'), { + with: { type: 'json' }, + }) + ).default; + } catch { + return []; + } + + const packages = Array.from( + new Set( + [...Object.keys(pkgJson.dependencies ?? {}), ...Object.keys(pkgJson.devDependencies ?? {})].filter( + (p) => p.startsWith('@zenstackhq/') || p === 'zenstack', + ), + ), + ).sort(); + + const result = await Promise.all( + packages.map(async (pkg) => { + try { + const depPkgJson = ( + await import(`${pkg}/package.json`, { + with: { type: 'json' }, + }) + ).default; + if (depPkgJson.private) { + return undefined; + } + return { pkg, version: depPkgJson.version as string }; + } catch { + return { pkg, version: undefined }; + } + }), + ); + + return result.filter((p) => !!p); +} diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index 7ac6db6b2..f609e552f 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -12,7 +12,8 @@ import { watch } from 'chokidar'; import ora, { type Ora } from 'ora'; import { CliError } from '../cli-error'; import * as corePlugins from '../plugins'; -import { getOutputPath, getSchemaFile, loadSchemaDocument } from './action-utils'; +import { getOutputPath, getSchemaFile, getZenStackPackages, loadSchemaDocument } from './action-utils'; +import semver from 'semver'; type Options = { schema?: string; @@ -27,6 +28,7 @@ type Options = { * CLI action for generating code from schema */ export async function run(options: Options) { + await checkForMismatchedPackages(process.cwd()); const model = await pureGenerate(options, false); if (options.watch) { @@ -315,3 +317,40 @@ async function loadPluginModule(provider: string, basePath: string) { return undefined; } } + +async function checkForMismatchedPackages(projectPath: string) { + const packages = await getZenStackPackages(projectPath); + if (!packages) { + return false; + } + + const versions = new Set(); + for (const { version } of packages) { + if (version) { + versions.add(version); + } + } + + if (versions.size > 1) { + const message = 'WARNING: Multiple versions of ZenStack packages detected.\n\tThis will probably cause issues and break your types.'; + const slashes = '/'.repeat(73); + const latestVersion = semver.sort(Array.from(versions)).reverse()[0]!; + + console.warn(colors.yellow(`${slashes}\n\t${message}\n`)); + for (const { pkg, version } of packages) { + if (!version) continue; + + if (version === latestVersion) { + console.log(`\t${pkg.padEnd(20)}\t${colors.green(version)}`); + } + else { + console.log(`\t${pkg.padEnd(20)}\t${colors.yellow(version)}`); + } + } + console.warn(`\n${colors.yellow(slashes)}`); + + return true; + } + + return false; +} diff --git a/packages/cli/src/actions/info.ts b/packages/cli/src/actions/info.ts index bbea51ebb..0423db7d5 100644 --- a/packages/cli/src/actions/info.ts +++ b/packages/cli/src/actions/info.ts @@ -1,5 +1,5 @@ import colors from 'colors'; -import path from 'node:path'; +import { getZenStackPackages } from './action-utils'; /** * CLI action for getting information about installed ZenStack packages @@ -24,48 +24,3 @@ export async function run(projectPath: string) { console.warn(colors.yellow('WARNING: Multiple versions of Zenstack packages detected. This may cause issues.')); } } - -async function getZenStackPackages(projectPath: string): Promise> { - let pkgJson: { - dependencies: Record; - devDependencies: Record; - }; - const resolvedPath = path.resolve(projectPath); - try { - pkgJson = ( - await import(path.join(resolvedPath, 'package.json'), { - with: { type: 'json' }, - }) - ).default; - } catch { - return []; - } - - const packages = Array.from( - new Set( - [...Object.keys(pkgJson.dependencies ?? {}), ...Object.keys(pkgJson.devDependencies ?? {})].filter( - (p) => p.startsWith('@zenstackhq/') || p === 'zenstack', - ), - ), - ).sort(); - - const result = await Promise.all( - packages.map(async (pkg) => { - try { - const depPkgJson = ( - await import(`${pkg}/package.json`, { - with: { type: 'json' }, - }) - ).default; - if (depPkgJson.private) { - return undefined; - } - return { pkg, version: depPkgJson.version as string }; - } catch { - return { pkg, version: undefined }; - } - }), - ); - - return result.filter((p) => !!p); -} From 832616c2089f195650cf1a52ea18383aa07b640c Mon Sep 17 00:00:00 2001 From: sanny-io Date: Fri, 6 Feb 2026 09:22:19 +0000 Subject: [PATCH 2/8] chore: swap falsy checks for length checks --- packages/cli/src/actions/generate.ts | 2 +- packages/cli/src/actions/info.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index f609e552f..2c17a10b5 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -320,7 +320,7 @@ async function loadPluginModule(provider: string, basePath: string) { async function checkForMismatchedPackages(projectPath: string) { const packages = await getZenStackPackages(projectPath); - if (!packages) { + if (!packages.length) { return false; } diff --git a/packages/cli/src/actions/info.ts b/packages/cli/src/actions/info.ts index 0423db7d5..26e42422f 100644 --- a/packages/cli/src/actions/info.ts +++ b/packages/cli/src/actions/info.ts @@ -6,7 +6,7 @@ import { getZenStackPackages } from './action-utils'; */ export async function run(projectPath: string) { const packages = await getZenStackPackages(projectPath); - if (!packages) { + if (!packages.length) { console.error('Unable to locate package.json. Are you in a valid project directory?'); return; } From 971325e01c3759c10e730fab378ee998275d04b9 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Fri, 6 Feb 2026 09:32:08 +0000 Subject: [PATCH 3/8] chore: do not block generation upon error --- packages/cli/src/actions/generate.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index 2c17a10b5..5df6c8d19 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -28,7 +28,11 @@ type Options = { * CLI action for generating code from schema */ export async function run(options: Options) { - await checkForMismatchedPackages(process.cwd()); + try { + await checkForMismatchedPackages(process.cwd()); + } catch (err) { + console.warn(colors.yellow(`Failed to check for mismatched ZenStack packages: ${err}`)); + } const model = await pureGenerate(options, false); if (options.watch) { From 1076be215727849d287fad93cf5603a833081d7d Mon Sep 17 00:00:00 2001 From: sanny-io Date: Fri, 6 Feb 2026 11:31:37 +0000 Subject: [PATCH 4/8] Add additional new line. --- packages/cli/src/actions/generate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index 5df6c8d19..05089536d 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -340,7 +340,7 @@ async function checkForMismatchedPackages(projectPath: string) { const slashes = '/'.repeat(73); const latestVersion = semver.sort(Array.from(versions)).reverse()[0]!; - console.warn(colors.yellow(`${slashes}\n\t${message}\n`)); + console.warn(colors.yellow(`${slashes}\n\n\t${message}\n`)); for (const { pkg, version } of packages) { if (!version) continue; From 846883b3bd42b1767e088cbee9785220e9d45932 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Fri, 6 Feb 2026 11:50:08 +0000 Subject: [PATCH 5/8] Trigger Build From 8f807ae52e3f1a26902dffa90e76f1f49a4ef944 Mon Sep 17 00:00:00 2001 From: sanny-io Date: Fri, 6 Feb 2026 12:00:50 +0000 Subject: [PATCH 6/8] Trigger Build From e15072c482d5b59e26dc855574c4d6fdea197853 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:26:21 +0800 Subject: [PATCH 7/8] fix: addressing PR comments - use `createRequire` instead of dynamic import for better compatibility - use file search to locate nearest package.json file - use longer padding length when formatting warnings --- .vscode/launch.json | 2 +- packages/cli/src/actions/action-utils.ts | 53 ++++++++++++------------ packages/cli/src/actions/generate.ts | 10 ++--- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 09ccbd596..df6fbc3c6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "skipFiles": ["/**"], "type": "node", "args": ["generate"], - "cwd": "${workspaceFolder}/samples/blog" + "cwd": "${workspaceFolder}/samples/orm" }, { "name": "Debug with TSX", diff --git a/packages/cli/src/actions/action-utils.ts b/packages/cli/src/actions/action-utils.ts index 1f225c14a..93a69d5c7 100644 --- a/packages/cli/src/actions/action-utils.ts +++ b/packages/cli/src/actions/action-utils.ts @@ -3,6 +3,7 @@ import { isDataSource } from '@zenstackhq/language/ast'; import { PrismaSchemaGenerator } from '@zenstackhq/sdk'; import colors from 'colors'; import fs from 'node:fs'; +import { createRequire } from 'node:module'; import path from 'node:path'; import { CliError } from '../cli-error'; @@ -156,47 +157,45 @@ export function getOutputPath(options: { output?: string }, schemaFile: string) return path.dirname(schemaFile); } } -export async function getZenStackPackages(projectPath: string): Promise> { +export async function getZenStackPackages( + searchPath: string, +): Promise> { + const pkgJsonFile = findUp(['package.json'], searchPath, false); + if (!pkgJsonFile) { + return []; + } + let pkgJson: { - dependencies: Record; - devDependencies: Record; + dependencies?: Record; + devDependencies?: Record; }; - const resolvedPath = path.resolve(projectPath); try { - pkgJson = ( - await import(path.join(resolvedPath, 'package.json'), { - with: { type: 'json' }, - }) - ).default; + pkgJson = JSON.parse(fs.readFileSync(pkgJsonFile, 'utf8')); } catch { return []; } const packages = Array.from( new Set( - [...Object.keys(pkgJson.dependencies ?? {}), ...Object.keys(pkgJson.devDependencies ?? {})].filter( - (p) => p.startsWith('@zenstackhq/') || p === 'zenstack', + [...Object.keys(pkgJson.dependencies ?? {}), ...Object.keys(pkgJson.devDependencies ?? {})].filter((p) => + p.startsWith('@zenstackhq/'), ), ), ).sort(); - const result = await Promise.all( - packages.map(async (pkg) => { - try { - const depPkgJson = ( - await import(`${pkg}/package.json`, { - with: { type: 'json' }, - }) - ).default; - if (depPkgJson.private) { - return undefined; - } - return { pkg, version: depPkgJson.version as string }; - } catch { - return { pkg, version: undefined }; + const require = createRequire(import.meta.url); + + const result = packages.map((pkg) => { + try { + const depPkgJson = require(`${pkg}/package.json`); + if (depPkgJson.private) { + return undefined; } - }), - ); + return { pkg, version: depPkgJson.version as string }; + } catch { + return { pkg, version: undefined }; + } + }); return result.filter((p) => !!p); } diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index 05089536d..c014c02ef 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -336,7 +336,8 @@ async function checkForMismatchedPackages(projectPath: string) { } if (versions.size > 1) { - const message = 'WARNING: Multiple versions of ZenStack packages detected.\n\tThis will probably cause issues and break your types.'; + const message = + 'WARNING: Multiple versions of ZenStack packages detected.\n\tThis will probably cause issues and break your types.'; const slashes = '/'.repeat(73); const latestVersion = semver.sort(Array.from(versions)).reverse()[0]!; @@ -345,10 +346,9 @@ async function checkForMismatchedPackages(projectPath: string) { if (!version) continue; if (version === latestVersion) { - console.log(`\t${pkg.padEnd(20)}\t${colors.green(version)}`); - } - else { - console.log(`\t${pkg.padEnd(20)}\t${colors.yellow(version)}`); + console.log(`\t${pkg.padEnd(32)}\t${colors.green(version)}`); + } else { + console.log(`\t${pkg.padEnd(32)}\t${colors.yellow(version)}`); } } console.warn(`\n${colors.yellow(slashes)}`); From 8da677d027f5aac838ff57561075cac81b8a7221 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:56:43 +0800 Subject: [PATCH 8/8] fix: make sure require base dir is absolute path --- packages/cli/src/actions/action-utils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/actions/action-utils.ts b/packages/cli/src/actions/action-utils.ts index 93a69d5c7..5c6962ee7 100644 --- a/packages/cli/src/actions/action-utils.ts +++ b/packages/cli/src/actions/action-utils.ts @@ -126,10 +126,10 @@ function findUp( } const target = names.find((name) => fs.existsSync(path.join(cwd, name))); if (multiple === false && target) { - return path.join(cwd, target) as FindUpResult; + return path.resolve(cwd, target) as FindUpResult; } if (target) { - result.push(path.join(cwd, target)); + result.push(path.resolve(cwd, target)); } const up = path.resolve(cwd, '..'); if (up === cwd) { @@ -183,7 +183,7 @@ export async function getZenStackPackages( ), ).sort(); - const require = createRequire(import.meta.url); + const require = createRequire(pkgJsonFile); const result = packages.map((pkg) => { try {