@zenstackhq/cli 3.0.0-alpha.25 → 3.0.0-alpha.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/dist/index.cjs +153 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +157 -52
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/actions/generate.ts +84 -31
- package/src/index.ts +0 -6
- package/src/plugins/index.ts +2 -0
- package/src/plugins/prisma.ts +21 -0
- package/src/plugins/typescript.ts +21 -0
- package/test/generate.test.ts +0 -12
- package/test/plugins/custom-plugin.test.ts +50 -0
- package/test/plugins/prisma-plugin.test.ts +60 -0
- package/tsconfig.json +0 -3
package/src/actions/generate.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { invariant } from '@zenstackhq/common-helpers';
|
|
2
|
-
import { isPlugin, LiteralExpr, type Model } from '@zenstackhq/language/ast';
|
|
3
|
-
import {
|
|
2
|
+
import { isPlugin, LiteralExpr, Plugin, type Model } from '@zenstackhq/language/ast';
|
|
3
|
+
import { getLiteral, getLiteralArray } from '@zenstackhq/language/utils';
|
|
4
|
+
import { type CliPlugin } from '@zenstackhq/sdk';
|
|
4
5
|
import colors from 'colors';
|
|
5
|
-
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { CliError } from '../cli-error';
|
|
9
|
+
import * as corePlugins from '../plugins';
|
|
7
10
|
import { getPkgJsonConfig, getSchemaFile, loadSchemaDocument } from './action-utils';
|
|
8
11
|
|
|
9
12
|
type Options = {
|
|
10
13
|
schema?: string;
|
|
11
14
|
output?: string;
|
|
12
15
|
silent?: boolean;
|
|
13
|
-
savePrismaSchema?: string | boolean;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -24,25 +26,10 @@ export async function run(options: Options) {
|
|
|
24
26
|
const model = await loadSchemaDocument(schemaFile);
|
|
25
27
|
const outputPath = getOutputPath(options, schemaFile);
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
const tsSchemaFile = path.join(outputPath, 'schema.ts');
|
|
29
|
-
await new TsSchemaGenerator().generate(schemaFile, [], outputPath);
|
|
30
|
-
|
|
31
|
-
await runPlugins(model, outputPath, tsSchemaFile);
|
|
32
|
-
|
|
33
|
-
// generate Prisma schema
|
|
34
|
-
if (options.savePrismaSchema) {
|
|
35
|
-
const prismaSchema = await new PrismaSchemaGenerator(model).generate();
|
|
36
|
-
let prismaSchemaFile = path.join(outputPath, 'schema.prisma');
|
|
37
|
-
if (typeof options.savePrismaSchema === 'string') {
|
|
38
|
-
prismaSchemaFile = path.resolve(outputPath, options.savePrismaSchema);
|
|
39
|
-
fs.mkdirSync(path.dirname(prismaSchemaFile), { recursive: true });
|
|
40
|
-
}
|
|
41
|
-
fs.writeFileSync(prismaSchemaFile, prismaSchema);
|
|
42
|
-
}
|
|
29
|
+
await runPlugins(schemaFile, model, outputPath);
|
|
43
30
|
|
|
44
31
|
if (!options.silent) {
|
|
45
|
-
console.log(colors.green(`Generation completed successfully in ${Date.now() - start}ms
|
|
32
|
+
console.log(colors.green(`Generation completed successfully in ${Date.now() - start}ms.\n`));
|
|
46
33
|
console.log(`You can now create a ZenStack client with it.
|
|
47
34
|
|
|
48
35
|
\`\`\`ts
|
|
@@ -68,18 +55,84 @@ function getOutputPath(options: Options, schemaFile: string) {
|
|
|
68
55
|
}
|
|
69
56
|
}
|
|
70
57
|
|
|
71
|
-
async function runPlugins(
|
|
58
|
+
async function runPlugins(schemaFile: string, model: Model, outputPath: string) {
|
|
72
59
|
const plugins = model.declarations.filter(isPlugin);
|
|
60
|
+
const processedPlugins: { cliPlugin: CliPlugin; pluginOptions: Record<string, unknown> }[] = [];
|
|
61
|
+
|
|
73
62
|
for (const plugin of plugins) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
63
|
+
const provider = getPluginProvider(plugin);
|
|
64
|
+
|
|
65
|
+
let cliPlugin: CliPlugin;
|
|
66
|
+
if (provider.startsWith('@core/')) {
|
|
67
|
+
cliPlugin = (corePlugins as any)[provider.slice('@core/'.length)];
|
|
68
|
+
if (!cliPlugin) {
|
|
69
|
+
throw new CliError(`Unknown core plugin: ${provider}`);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
let moduleSpec = provider;
|
|
73
|
+
if (moduleSpec.startsWith('.')) {
|
|
74
|
+
// relative to schema's path
|
|
75
|
+
moduleSpec = path.resolve(path.dirname(schemaFile), moduleSpec);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
cliPlugin = (await import(moduleSpec)).default as CliPlugin;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new CliError(`Failed to load plugin ${provider}: ${error}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
processedPlugins.push({ cliPlugin, pluginOptions: getPluginOptions(plugin) });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const defaultPlugins = [corePlugins['typescript']].reverse();
|
|
88
|
+
defaultPlugins.forEach((d) => {
|
|
89
|
+
if (!processedPlugins.some((p) => p.cliPlugin === d)) {
|
|
90
|
+
processedPlugins.push({ cliPlugin: d, pluginOptions: {} });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
for (const { cliPlugin, pluginOptions } of processedPlugins) {
|
|
95
|
+
invariant(
|
|
96
|
+
typeof cliPlugin.generate === 'function',
|
|
97
|
+
`Plugin ${cliPlugin.name} does not have a generate function`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// run plugin generator
|
|
101
|
+
const spinner = ora(cliPlugin.statusText ?? `Running plugin ${cliPlugin.name}`).start();
|
|
102
|
+
try {
|
|
103
|
+
await cliPlugin.generate({
|
|
104
|
+
schemaFile,
|
|
105
|
+
model,
|
|
106
|
+
defaultOutputPath: outputPath,
|
|
107
|
+
pluginOptions,
|
|
108
|
+
});
|
|
109
|
+
spinner.succeed();
|
|
110
|
+
} catch (err) {
|
|
111
|
+
spinner.fail();
|
|
112
|
+
console.error(err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getPluginProvider(plugin: Plugin) {
|
|
118
|
+
const providerField = plugin.fields.find((f) => f.name === 'provider');
|
|
119
|
+
invariant(providerField, `Plugin ${plugin.name} does not have a provider field`);
|
|
120
|
+
const provider = (providerField.value as LiteralExpr).value as string;
|
|
121
|
+
return provider;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function getPluginOptions(plugin: Plugin): Record<string, unknown> {
|
|
125
|
+
const result: Record<string, unknown> = {};
|
|
126
|
+
for (const field of plugin.fields) {
|
|
127
|
+
if (field.name === 'provider') {
|
|
128
|
+
continue; // skip provider
|
|
129
|
+
}
|
|
130
|
+
const value = getLiteral(field.value) ?? getLiteralArray(field.value);
|
|
131
|
+
if (value === undefined) {
|
|
132
|
+
console.warn(`Plugin "${plugin.name}" option "${field.name}" has unsupported value, skipping`);
|
|
133
|
+
continue;
|
|
80
134
|
}
|
|
81
|
-
|
|
82
|
-
console.log('Running generator:', provider);
|
|
83
|
-
await generator({ model, outputPath, tsSchemaFile });
|
|
135
|
+
result[field.name] = value;
|
|
84
136
|
}
|
|
137
|
+
return result;
|
|
85
138
|
}
|
package/src/index.ts
CHANGED
|
@@ -55,12 +55,6 @@ export function createProgram() {
|
|
|
55
55
|
.description('Run code generation.')
|
|
56
56
|
.addOption(schemaOption)
|
|
57
57
|
.addOption(new Option('--silent', 'do not print any output'))
|
|
58
|
-
.addOption(
|
|
59
|
-
new Option(
|
|
60
|
-
'--save-prisma-schema [path]',
|
|
61
|
-
'save a Prisma schema file, by default into the output directory',
|
|
62
|
-
),
|
|
63
|
-
)
|
|
64
58
|
.addOption(new Option('-o, --output <path>', 'default output directory for core plugins'))
|
|
65
59
|
.action(generateAction);
|
|
66
60
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PrismaSchemaGenerator, type CliPlugin } from '@zenstackhq/sdk';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const plugin: CliPlugin = {
|
|
6
|
+
name: 'Prisma Schema Generator',
|
|
7
|
+
statusText: 'Generating Prisma schema',
|
|
8
|
+
async generate({ model, defaultOutputPath, pluginOptions }) {
|
|
9
|
+
let outDir = defaultOutputPath;
|
|
10
|
+
if (typeof pluginOptions['output'] === 'string') {
|
|
11
|
+
outDir = path.resolve(defaultOutputPath, pluginOptions['output']);
|
|
12
|
+
if (!fs.existsSync(outDir)) {
|
|
13
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const prismaSchema = await new PrismaSchemaGenerator(model).generate();
|
|
17
|
+
fs.writeFileSync(path.join(outDir, 'schema.prisma'), prismaSchema);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default plugin;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CliPlugin } from '@zenstackhq/sdk';
|
|
2
|
+
import { TsSchemaGenerator } from '@zenstackhq/sdk';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const plugin: CliPlugin = {
|
|
7
|
+
name: 'TypeScript Schema Generator',
|
|
8
|
+
statusText: 'Generating TypeScript schema',
|
|
9
|
+
async generate({ model, defaultOutputPath, pluginOptions }) {
|
|
10
|
+
let outDir = defaultOutputPath;
|
|
11
|
+
if (typeof pluginOptions['output'] === 'string') {
|
|
12
|
+
outDir = path.resolve(defaultOutputPath, pluginOptions['output']);
|
|
13
|
+
if (!fs.existsSync(outDir)) {
|
|
14
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
await new TsSchemaGenerator().generate(model, outDir);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default plugin;
|
package/test/generate.test.ts
CHANGED
|
@@ -30,18 +30,6 @@ describe('CLI generate command test', () => {
|
|
|
30
30
|
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
it('should respect save prisma schema option', () => {
|
|
34
|
-
const workDir = createProject(model);
|
|
35
|
-
runCli('generate --save-prisma-schema', workDir);
|
|
36
|
-
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.prisma'))).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should respect save prisma schema custom path option', () => {
|
|
40
|
-
const workDir = createProject(model);
|
|
41
|
-
runCli('generate --save-prisma-schema "../prisma/schema.prisma"', workDir);
|
|
42
|
-
expect(fs.existsSync(path.join(workDir, 'prisma/schema.prisma'))).toBe(true);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
33
|
it('should respect package.json config', () => {
|
|
46
34
|
const workDir = createProject(model);
|
|
47
35
|
fs.mkdirSync(path.join(workDir, 'foo'));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { createProject, runCli } from '../utils';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
describe('Custom plugins tests', () => {
|
|
8
|
+
it('runs custom plugin generator', () => {
|
|
9
|
+
const workDir = createProject(`
|
|
10
|
+
plugin custom {
|
|
11
|
+
provider = '../my-plugin.js'
|
|
12
|
+
output = '../custom-output'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
model User {
|
|
16
|
+
id String @id @default(cuid())
|
|
17
|
+
}
|
|
18
|
+
`);
|
|
19
|
+
|
|
20
|
+
fs.writeFileSync(
|
|
21
|
+
path.join(workDir, 'my-plugin.ts'),
|
|
22
|
+
`
|
|
23
|
+
import type { CliPlugin } from '@zenstackhq/sdk';
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
|
|
27
|
+
const plugin: CliPlugin = {
|
|
28
|
+
name: 'Custom Generator',
|
|
29
|
+
statusText: 'Generating foo.txt',
|
|
30
|
+
async generate({ model, defaultOutputPath, pluginOptions }) {
|
|
31
|
+
let outDir = defaultOutputPath;
|
|
32
|
+
if (typeof pluginOptions['output'] === 'string') {
|
|
33
|
+
outDir = path.resolve(defaultOutputPath, pluginOptions['output']);
|
|
34
|
+
if (!fs.existsSync(outDir)) {
|
|
35
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
fs.writeFileSync(path.join(outDir, 'foo.txt'), 'from my plugin');
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default plugin;
|
|
43
|
+
`,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
execSync('npx tsc', { cwd: workDir });
|
|
47
|
+
runCli('generate', workDir);
|
|
48
|
+
expect(fs.existsSync(path.join(workDir, 'custom-output/foo.txt'))).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { createProject, runCli } from '../utils';
|
|
5
|
+
|
|
6
|
+
describe('Core plugins tests', () => {
|
|
7
|
+
it('can automatically generate a TypeScript schema with default output', () => {
|
|
8
|
+
const workDir = createProject(`
|
|
9
|
+
model User {
|
|
10
|
+
id String @id @default(cuid())
|
|
11
|
+
}
|
|
12
|
+
`);
|
|
13
|
+
runCli('generate', workDir);
|
|
14
|
+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('can automatically generate a TypeScript schema with custom output', () => {
|
|
18
|
+
const workDir = createProject(`
|
|
19
|
+
plugin typescript {
|
|
20
|
+
provider = '@core/typescript'
|
|
21
|
+
output = '../generated-schema'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
model User {
|
|
25
|
+
id String @id @default(cuid())
|
|
26
|
+
}
|
|
27
|
+
`);
|
|
28
|
+
runCli('generate', workDir);
|
|
29
|
+
expect(fs.existsSync(path.join(workDir, 'generated-schema/schema.ts'))).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('can generate a Prisma schema with default output', () => {
|
|
33
|
+
const workDir = createProject(`
|
|
34
|
+
plugin prisma {
|
|
35
|
+
provider = '@core/prisma'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
model User {
|
|
39
|
+
id String @id @default(cuid())
|
|
40
|
+
}
|
|
41
|
+
`);
|
|
42
|
+
runCli('generate', workDir);
|
|
43
|
+
expect(fs.existsSync(path.join(workDir, 'zenstack/schema.prisma'))).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('can generate a Prisma schema with custom output', () => {
|
|
47
|
+
const workDir = createProject(`
|
|
48
|
+
plugin prisma {
|
|
49
|
+
provider = '@core/prisma'
|
|
50
|
+
output = './prisma'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
model User {
|
|
54
|
+
id String @id @default(cuid())
|
|
55
|
+
}
|
|
56
|
+
`);
|
|
57
|
+
runCli('generate', workDir);
|
|
58
|
+
expect(fs.existsSync(path.join(workDir, 'zenstack/prisma/schema.prisma'))).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|