Skip to content
This repository was archived by the owner on Apr 23, 2025. It is now read-only.

Commit eba3390

Browse files
authored
feat: CLI improvements (zenstackhq#694)
1 parent cea2019 commit eba3390

File tree

7 files changed

+252
-228
lines changed

7 files changed

+252
-228
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { PluginError } from '@zenstackhq/sdk';
2+
import colors from 'colors';
3+
import path from 'path';
4+
import { Context } from '../../types';
5+
import { PackageManagers } from '../../utils/pkg-utils';
6+
import { CliError } from '../cli-error';
7+
import {
8+
checkNewVersion,
9+
checkRequiredPackage,
10+
getZenStackPackages,
11+
loadDocument,
12+
requiredPrismaVersion,
13+
} from '../cli-util';
14+
import { PluginRunner } from '../plugin-runner';
15+
16+
type Options = {
17+
schema: string;
18+
packageManager: PackageManagers | undefined;
19+
dependencyCheck: boolean;
20+
versionCheck: boolean;
21+
};
22+
23+
/**
24+
* CLI action for generating code from schema
25+
*/
26+
export async function generate(projectPath: string, options: Options) {
27+
if (options.dependencyCheck) {
28+
checkRequiredPackage('prisma', requiredPrismaVersion);
29+
checkRequiredPackage('@prisma/client', requiredPrismaVersion);
30+
}
31+
32+
// check for multiple versions of Zenstack packages
33+
const packages = getZenStackPackages(projectPath);
34+
if (packages) {
35+
const versions = new Set<string>(packages.map((p) => p.version));
36+
if (versions.size > 1) {
37+
console.warn(
38+
colors.yellow(
39+
'WARNING: Multiple versions of Zenstack packages detected. Run "zenstack info" to see details.'
40+
)
41+
);
42+
}
43+
}
44+
45+
const tasks = [runPlugins(options)];
46+
47+
if (options.versionCheck) {
48+
tasks.push(checkNewVersion());
49+
}
50+
51+
await Promise.all(tasks);
52+
}
53+
54+
async function runPlugins(options: Options) {
55+
const model = await loadDocument(options.schema);
56+
const context: Context = {
57+
schema: model,
58+
schemaPath: path.resolve(options.schema),
59+
outDir: path.dirname(options.schema),
60+
};
61+
62+
try {
63+
await new PluginRunner().run(context);
64+
} catch (err) {
65+
if (err instanceof PluginError) {
66+
console.error(colors.red(`${err.plugin}: ${err.message}`));
67+
throw new CliError(err.message);
68+
} else {
69+
throw err;
70+
}
71+
}
72+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './generate';
2+
export * from './info';
3+
export * from './init';
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import colors from 'colors';
2+
import getLatestVersion from 'get-latest-version';
3+
import ora from 'ora';
4+
import semver from 'semver';
5+
import { getZenStackPackages } from '../cli-util';
6+
7+
/**
8+
* CLI action for getting information about installed ZenStack packages
9+
*/
10+
export async function info(projectPath: string) {
11+
const packages = getZenStackPackages(projectPath);
12+
if (!packages) {
13+
console.error('Unable to locate package.json. Are you in a valid project directory?');
14+
return;
15+
}
16+
17+
console.log('Installed ZenStack Packages:');
18+
const versions = new Set<string>();
19+
for (const { pkg, version } of packages) {
20+
versions.add(version);
21+
console.log(` ${colors.green(pkg.padEnd(20))}\t${version}`);
22+
}
23+
24+
if (versions.size > 1) {
25+
console.warn(colors.yellow('WARNING: Multiple versions of Zenstack packages detected. This may cause issues.'));
26+
} else if (versions.size > 0) {
27+
const spinner = ora('Checking npm registry').start();
28+
const latest = await getLatestVersion('zenstack');
29+
30+
if (!latest) {
31+
spinner.fail('unable to check for latest version');
32+
} else {
33+
spinner.succeed();
34+
const version = [...versions][0];
35+
if (semver.gt(latest, version)) {
36+
console.log(`A newer version of Zenstack is available: ${latest}.`);
37+
} else if (semver.gt(version, latest)) {
38+
console.log('You are using a pre-release version of Zenstack.');
39+
} else {
40+
console.log('You are using the latest version of Zenstack.');
41+
}
42+
}
43+
}
44+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import colors from 'colors';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import { PackageManagers, ensurePackage, installPackage } from '../../utils/pkg-utils';
5+
import { getVersion } from '../../utils/version-utils';
6+
import { CliError } from '../cli-error';
7+
import { checkNewVersion } from '../cli-util';
8+
9+
type Options = {
10+
prisma: string | undefined;
11+
packageManager: PackageManagers | undefined;
12+
versionCheck: boolean;
13+
tag?: string;
14+
};
15+
16+
/**
17+
* CLI action for initializing an existing project
18+
*/
19+
export async function init(projectPath: string, options: Options) {
20+
if (!fs.existsSync(projectPath)) {
21+
console.error(`Path does not exist: ${projectPath}`);
22+
throw new CliError('project path does not exist');
23+
}
24+
25+
const defaultPrismaSchemaLocation = './prisma/schema.prisma';
26+
let prismaSchema = options.prisma;
27+
if (prismaSchema) {
28+
if (!fs.existsSync(prismaSchema)) {
29+
console.error(`Prisma schema file does not exist: ${prismaSchema}`);
30+
throw new CliError('prisma schema does not exist');
31+
}
32+
} else if (fs.existsSync(defaultPrismaSchemaLocation)) {
33+
prismaSchema = defaultPrismaSchemaLocation;
34+
}
35+
36+
const zmodelFile = path.join(projectPath, './schema.zmodel');
37+
let sampleModelGenerated = false;
38+
39+
if (fs.existsSync(zmodelFile)) {
40+
console.warn(`ZenStack model already exists at ${zmodelFile}, not generating a new one.`);
41+
} else {
42+
if (prismaSchema) {
43+
// copy over schema.prisma
44+
fs.copyFileSync(prismaSchema, zmodelFile);
45+
} else {
46+
// create a new model
47+
const starterContent = fs.readFileSync(path.join(__dirname, '../../res/starter.zmodel'), 'utf-8');
48+
fs.writeFileSync(zmodelFile, starterContent);
49+
sampleModelGenerated = true;
50+
}
51+
}
52+
53+
ensurePackage('prisma', true, options.packageManager, 'latest', projectPath);
54+
ensurePackage('@prisma/client', false, options.packageManager, 'latest', projectPath);
55+
56+
const tag = options.tag ?? getVersion();
57+
installPackage('zenstack', true, options.packageManager, tag, projectPath);
58+
installPackage('@zenstackhq/runtime', false, options.packageManager, tag, projectPath);
59+
60+
if (sampleModelGenerated) {
61+
console.log(`Sample model generated at: ${colors.blue(zmodelFile)}
62+
63+
Please check the following guide on how to model your app:
64+
https://zenstack.dev/#/modeling-your-app.`);
65+
} else if (prismaSchema) {
66+
console.log(
67+
`Your current Prisma schema "${prismaSchema}" has been copied to "${zmodelFile}".
68+
Moving forward please edit this file and run "zenstack generate" to regenerate Prisma schema.`
69+
);
70+
}
71+
72+
console.log(colors.green('\nProject initialized successfully!'));
73+
74+
if (options.versionCheck) {
75+
await checkNewVersion();
76+
}
77+
}

0 commit comments

Comments
 (0)