Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.
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 .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"skipFiles": ["<node_internals>/**"],
"type": "node",
"args": ["generate"],
"cwd": "${workspaceFolder}/samples/blog"
"cwd": "${workspaceFolder}/samples/orm"
},
{
"name": "Debug with TSX",
Expand Down
47 changes: 45 additions & 2 deletions packages/cli/src/actions/action-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -125,10 +126,10 @@ function findUp<Multiple extends boolean = false>(
}
const target = names.find((name) => fs.existsSync(path.join(cwd, name)));
if (multiple === false && target) {
return path.join(cwd, target) as FindUpResult<Multiple>;
return path.resolve(cwd, target) as FindUpResult<Multiple>;
}
if (target) {
result.push(path.join(cwd, target));
result.push(path.resolve(cwd, target));
}
const up = path.resolve(cwd, '..');
if (up === cwd) {
Expand Down Expand Up @@ -156,3 +157,45 @@ export function getOutputPath(options: { output?: string }, schemaFile: string)
return path.dirname(schemaFile);
}
}
export async function getZenStackPackages(
searchPath: string,
): Promise<Array<{ pkg: string; version: string | undefined }>> {
const pkgJsonFile = findUp(['package.json'], searchPath, false);
if (!pkgJsonFile) {
return [];
}

let pkgJson: {
dependencies?: Record<string, unknown>;
devDependencies?: Record<string, unknown>;
};
try {
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/'),
),
),
).sort();

const require = createRequire(pkgJsonFile);

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);
}
45 changes: 44 additions & 1 deletion packages/cli/src/actions/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,6 +28,11 @@ type Options = {
* CLI action for generating code from schema
*/
export async function run(options: Options) {
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) {
Expand Down Expand Up @@ -315,3 +321,40 @@ async function loadPluginModule(provider: string, basePath: string) {
return undefined;
}
}

async function checkForMismatchedPackages(projectPath: string) {
const packages = await getZenStackPackages(projectPath);
if (!packages.length) {
return false;
}

const versions = new Set<string>();
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\n\t${message}\n`));
for (const { pkg, version } of packages) {
if (!version) continue;

if (version === latestVersion) {
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)}`);

return true;
}

return false;
}
49 changes: 2 additions & 47 deletions packages/cli/src/actions/info.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import colors from 'colors';
import path from 'node:path';
import { getZenStackPackages } from './action-utils';

/**
* CLI action for getting information about installed ZenStack packages
*/
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;
}
Expand All @@ -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<Array<{ pkg: string; version: string | undefined }>> {
let pkgJson: {
dependencies: Record<string, unknown>;
devDependencies: Record<string, unknown>;
};
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);
}
Loading