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,84 @@ 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+ 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 ;
80134 }
81- const generator = ( await import ( useProvider ) ) . default as CliGenerator ;
82- console . log ( 'Running generator:' , provider ) ;
83- await generator ( { model, outputPath, tsSchemaFile } ) ;
135+ result [ field . name ] = value ;
84136 }
137+ return result ;
85138}
0 commit comments