11import { invariant } from '@zenstackhq/common-helpers' ;
2- import { isPlugin , LiteralExpr , type Model } from '@zenstackhq/language/ast' ;
3- import { PrismaSchemaGenerator , TsSchemaGenerator , type CliGenerator } from '@zenstackhq/sdk' ;
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' ;
45import colors from 'colors' ;
5- import fs from 'node:fs' ;
66import path from 'node:path' ;
7+ import ora from 'ora' ;
8+ import { CliError } from '../cli-error' ;
9+ import * as corePlugins from '../plugins' ;
710import { getPkgJsonConfig , getSchemaFile , loadSchemaDocument } from './action-utils' ;
811
912type Options = {
1013 schema ?: string ;
1114 output ?: string ;
1215 silent ?: boolean ;
13- savePrismaSchema ?: string | boolean ;
1416} ;
1517
1618/**
@@ -24,25 +26,10 @@ export async function run(options: Options) {
2426 const model = await loadSchemaDocument ( schemaFile ) ;
2527 const outputPath = getOutputPath ( options , schemaFile ) ;
2628
27- // generate TS schema
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 ) ;
4330
4431 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 ` ) ) ;
4633 console . log ( `You can now create a ZenStack client with it.
4734
4835\`\`\`ts
@@ -68,18 +55,79 @@ function getOutputPath(options: Options, schemaFile: string) {
6855 }
6956}
7057
71- async function runPlugins ( model : Model , outputPath : string , tsSchemaFile : string ) {
58+ async function runPlugins ( schemaFile : string , model : Model , outputPath : string ) {
7259 const plugins = model . declarations . filter ( isPlugin ) ;
60+ const processedPlugins : { cliPlugin : CliPlugin ; pluginOptions : Record < string , unknown > } [ ] = [ ] ;
61+
7362 for ( const plugin of plugins ) {
74- const providerField = plugin . fields . find ( ( f ) => f . name === 'provider' ) ;
75- invariant ( providerField , `Plugin ${ plugin . name } does not have a provider field` ) ;
76- const provider = ( providerField . value as LiteralExpr ) . value as string ;
77- let useProvider = provider ;
78- if ( useProvider . startsWith ( '@core/' ) ) {
79- useProvider = `@zenstackhq/runtime/plugins/${ useProvider . slice ( 6 ) } ` ;
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+ try {
73+ cliPlugin = ( await import ( provider ) ) . default as CliPlugin ;
74+ } catch ( error ) {
75+ throw new CliError ( `Failed to load plugin ${ provider } : ${ error } ` ) ;
76+ }
77+ }
78+
79+ processedPlugins . push ( { cliPlugin, pluginOptions : getPluginOptions ( plugin ) } ) ;
80+ }
81+
82+ const defaultPlugins = [ corePlugins [ 'typescript' ] ] . reverse ( ) ;
83+ defaultPlugins . forEach ( ( d ) => {
84+ if ( ! processedPlugins . some ( ( p ) => p . cliPlugin === d ) ) {
85+ processedPlugins . push ( { cliPlugin : d , pluginOptions : { } } ) ;
86+ }
87+ } ) ;
88+
89+ for ( const { cliPlugin, pluginOptions } of processedPlugins ) {
90+ invariant (
91+ typeof cliPlugin . generate === 'function' ,
92+ `Plugin ${ cliPlugin . name } does not have a generate function` ,
93+ ) ;
94+
95+ // run plugin generator
96+ const spinner = ora ( cliPlugin . statusText ?? `Running plugin ${ cliPlugin . name } ` ) . start ( ) ;
97+ try {
98+ await cliPlugin . generate ( {
99+ schemaFile,
100+ model,
101+ defaultOutputPath : outputPath ,
102+ pluginOptions,
103+ } ) ;
104+ spinner . succeed ( ) ;
105+ } catch ( err ) {
106+ spinner . fail ( ) ;
107+ console . error ( err ) ;
108+ }
109+ }
110+ }
111+
112+ function getPluginProvider ( plugin : Plugin ) {
113+ const providerField = plugin . fields . find ( ( f ) => f . name === 'provider' ) ;
114+ invariant ( providerField , `Plugin ${ plugin . name } does not have a provider field` ) ;
115+ const provider = ( providerField . value as LiteralExpr ) . value as string ;
116+ return provider ;
117+ }
118+
119+ function getPluginOptions ( plugin : Plugin ) : Record < string , unknown > {
120+ const result : any = { } ;
121+ for ( const field of plugin . fields ) {
122+ if ( field . name === 'provider' ) {
123+ continue ; // skip provider
124+ }
125+ const value = getLiteral ( field . value ) ?? getLiteralArray ( field . value ) ;
126+ if ( value === undefined ) {
127+ console . warn ( `Plugin "${ plugin . name } " option "${ field . name } " has unsupported value, skipping` ) ;
128+ continue ;
80129 }
81- const generator = ( await import ( useProvider ) ) . default as CliGenerator ;
82- console . log ( 'Running generator:' , provider ) ;
83- await generator ( { model, outputPath, tsSchemaFile } ) ;
130+ result [ field . name ] = value ;
84131 }
132+ return result ;
85133}
0 commit comments