Skip to content

Commit e0fac4f

Browse files
authored
feat: add "create-zenstack" and "init" command (#20)
* feat: add "create-zenstack" and "init" command * update * dedup package names * remove duplicated code
1 parent 3ee7f20 commit e0fac4f

File tree

26 files changed

+408
-81
lines changed

26 files changed

+408
-81
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zenstack-v3",
3-
"version": "3.0.0-alpha.1",
3+
"version": "3.0.0-alpha.0",
44
"description": "ZenStack",
55
"packageManager": "pnpm@10.12.1",
66
"scripts": {

packages/cli/package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
2-
"name": "zenstack",
2+
"name": "@zenstackhq/cli",
33
"publisher": "zenstack",
4-
"displayName": "ZenStack Language Tools",
4+
"displayName": "ZenStack CLI",
55
"description": "FullStack database toolkit with built-in access control and automatic API generation.",
6-
"version": "3.0.0-alpha.1",
6+
"version": "3.0.0-alpha.0",
77
"type": "module",
88
"author": {
99
"name": "ZenStack Team"
@@ -28,7 +28,7 @@
2828
"pack": "pnpm pack"
2929
},
3030
"dependencies": {
31-
"@types/node": "^18.0.0",
31+
"@types/node": "^20.0.0",
3232
"@zenstackhq/language": "workspace:*",
3333
"@zenstackhq/runtime": "workspace:*",
3434
"@zenstackhq/sdk": "workspace:*",
@@ -37,6 +37,7 @@
3737
"commander": "^8.3.0",
3838
"langium": "~3.3.0",
3939
"ora": "^5.4.1",
40+
"package-manager-detector": "^1.3.0",
4041
"tiny-invariant": "^1.3.3",
4142
"ts-pattern": "^4.3.0"
4243
},
@@ -45,11 +46,11 @@
4546
"typescript": "^5.0.0"
4647
},
4748
"devDependencies": {
48-
"@zenstackhq/testtools": "workspace:*",
4949
"@types/async-exit-hook": "^2.0.0",
5050
"@types/better-sqlite3": "^7.6.13",
5151
"@types/semver": "^7.3.13",
5252
"@types/tmp": "^0.2.6",
53+
"@zenstackhq/testtools": "workspace:*",
5354
"better-sqlite3": "^11.8.1",
5455
"tmp": "^0.2.3"
5556
}

packages/cli/src/actions/action-utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,15 @@ export async function loadSchemaDocument(schemaFile: string) {
3333
}
3434
return loadResult.model;
3535
}
36+
37+
export function handleSubProcessError(err: unknown) {
38+
if (
39+
err instanceof Error &&
40+
'status' in err &&
41+
typeof err.status === 'number'
42+
) {
43+
process.exit(err.status);
44+
} else {
45+
process.exit(1);
46+
}
47+
}

packages/cli/src/actions/common.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

packages/cli/src/actions/db.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import path from 'node:path';
22
import { execPackage } from '../utils/exec-utils';
3-
import { getSchemaFile } from './action-utils';
3+
import { getSchemaFile, handleSubProcessError } from './action-utils';
44
import { run as runGenerate } from './generate';
5-
import { handleSubProcessError } from './common';
65

76
type CommonOptions = {
87
schema?: string;

packages/cli/src/actions/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export async function run(options: Options) {
4040
import { ZenStackClient } from '@zenstackhq/runtime';
4141
import { schema } from '${outputPath}/schema';
4242
43-
const db = new ZenStackClient(schema);
43+
const client = new ZenStackClient(schema);
4444
\`\`\`
4545
`);
4646
}

packages/cli/src/actions/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { run as db } from './db';
22
import { run as generate } from './generate';
33
import { run as info } from './info';
4+
import { run as init } from './init';
45
import { run as migrate } from './migrate';
56

6-
export { db, generate, info, migrate };
7+
export { db, generate, info, init, migrate };

packages/cli/src/actions/info.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ async function getZenStackPackages(
4949
return [];
5050
}
5151

52-
const packages = [
53-
...Object.keys(pkgJson.dependencies ?? {}).filter((p) =>
54-
p.startsWith('@zenstackhq/')
55-
),
56-
...Object.keys(pkgJson.devDependencies ?? {}).filter((p) =>
57-
p.startsWith('@zenstackhq/')
58-
),
59-
];
52+
const packages = Array.from(
53+
new Set(
54+
[
55+
...Object.keys(pkgJson.dependencies ?? {}),
56+
...Object.keys(pkgJson.devDependencies ?? {}),
57+
].filter((p) => p.startsWith('@zenstackhq/') || p === 'zenstack')
58+
)
59+
).sort();
6060

6161
const result = await Promise.all(
6262
packages.map(async (pkg) => {

packages/cli/src/actions/init.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import colors from 'colors';
2+
import fs from 'node:fs';
3+
import path from 'node:path';
4+
import ora from 'ora';
5+
import { detect, resolveCommand } from 'package-manager-detector';
6+
import { CliError } from '../cli-error';
7+
import { execSync } from '../utils/exec-utils';
8+
import { STARTER_ZMODEL } from './templates';
9+
10+
/**
11+
* CLI action for getting information about installed ZenStack packages
12+
*/
13+
export async function run(projectPath: string) {
14+
const packages = [
15+
{ name: '@zenstackhq/cli', dev: true },
16+
{ name: '@zenstackhq/runtime', dev: false },
17+
];
18+
let pm = await detect();
19+
if (!pm) {
20+
pm = { agent: 'npm', name: 'npm' };
21+
}
22+
23+
console.log(colors.gray(`Using package manager: ${pm.agent}`));
24+
25+
for (const pkg of packages) {
26+
const resolved = resolveCommand(pm.agent, 'install', [
27+
pkg.name,
28+
...(pkg.dev ? [pm.agent === 'yarn' ? '--dev' : '--save-dev'] : []),
29+
]);
30+
if (!resolved) {
31+
throw new CliError(
32+
`Unable to determine how to install package "${pkg.name}". Please install it manually.`
33+
);
34+
}
35+
36+
const spinner = ora(`Installing "${pkg.name}"`).start();
37+
try {
38+
execSync(`${resolved.command} ${resolved.args.join(' ')}`, {
39+
cwd: projectPath,
40+
});
41+
spinner.succeed();
42+
} catch (e) {
43+
spinner.fail();
44+
throw e;
45+
}
46+
}
47+
48+
const generationFolder = 'zenstack';
49+
50+
if (!fs.existsSync(path.join(projectPath, generationFolder))) {
51+
fs.mkdirSync(path.join(projectPath, generationFolder));
52+
}
53+
54+
if (
55+
!fs.existsSync(
56+
path.join(projectPath, generationFolder, 'schema.zmodel')
57+
)
58+
) {
59+
fs.writeFileSync(
60+
path.join(projectPath, generationFolder, 'schema.zmodel'),
61+
STARTER_ZMODEL
62+
);
63+
} else {
64+
console.log(
65+
colors.yellow(
66+
'Schema file already exists. Skipping generation of sample.'
67+
)
68+
);
69+
}
70+
71+
console.log(colors.green('ZenStack project initialized successfully!'));
72+
console.log(
73+
colors.gray(
74+
`See "${generationFolder}/schema.zmodel" for your database schema.`
75+
)
76+
);
77+
console.log(
78+
colors.gray(
79+
'Run `zenstack generate` to compile the the schema into a TypeScript file.'
80+
)
81+
);
82+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
export const STARTER_ZMODEL = `// This is a sample model to get you started.
2+
3+
/// A sample data source using local sqlite db.
4+
datasource db {
5+
provider = 'sqlite'
6+
url = 'file:./dev.db'
7+
}
8+
9+
/// User model
10+
model User {
11+
id String @id @default(cuid())
12+
email String @unique @email @length(6, 32)
13+
posts Post[]
14+
}
15+
16+
/// Post model
17+
model Post {
18+
id String @id @default(cuid())
19+
createdAt DateTime @default(now())
20+
updatedAt DateTime @updatedAt
21+
title String @length(1, 256)
22+
content String
23+
published Boolean @default(false)
24+
author User @relation(fields: [authorId], references: [id])
25+
authorId String
26+
}
27+
`;
28+
29+
export const STARTER_MAIN_TS = `import { ZenStackClient } from '@zenstackhq/runtime';
30+
import { schema } from './zenstack/schema';
31+
32+
async function main() {
33+
const client = new ZenStackClient(schema);
34+
const user = await client.user.create({
35+
data: {
36+
email: 'test@zenstack.dev',
37+
posts: {
38+
create: [
39+
{
40+
title: 'Hello World',
41+
content: 'This is a test post',
42+
},
43+
],
44+
},
45+
},
46+
include: { posts: true }
47+
});
48+
console.log('User created:', user);
49+
}
50+
51+
main();
52+
`;

0 commit comments

Comments
 (0)