11/* eslint-disable @typescript-eslint/no-explicit-any */
22/* eslint-disable @typescript-eslint/no-var-requires */
33import type { DMMF } from '@prisma/generator-helper' ;
4- import { isPlugin , Plugin } from '@zenstackhq/language/ast' ;
4+ import { isPlugin , Model , Plugin } from '@zenstackhq/language/ast' ;
55import {
66 getDataModels ,
77 getDMMF ,
@@ -19,9 +19,7 @@ import ora from 'ora';
1919import path from 'path' ;
2020import { ensureDefaultOutputFolder } from '../plugins/plugin-utils' ;
2121import telemetry from '../telemetry' ;
22- import type { Context } from '../types' ;
2322import { getVersion } from '../utils/version-utils' ;
24- import { config } from './config' ;
2523
2624type PluginInfo = {
2725 name : string ;
@@ -32,23 +30,31 @@ type PluginInfo = {
3230 module : any ;
3331} ;
3432
33+ export type PluginRunnerOptions = {
34+ schema : Model ;
35+ schemaPath : string ;
36+ output ?: string ;
37+ defaultPlugins : boolean ;
38+ compile : boolean ;
39+ } ;
40+
3541/**
3642 * ZenStack plugin runner
3743 */
3844export class PluginRunner {
3945 /**
4046 * Runs a series of nested generators
4147 */
42- async run ( context : Context ) : Promise < void > {
48+ async run ( options : PluginRunnerOptions ) : Promise < void > {
4349 const version = getVersion ( ) ;
4450 console . log ( colors . bold ( `⌛️ ZenStack CLI v${ version } , running plugins` ) ) ;
4551
46- ensureDefaultOutputFolder ( ) ;
52+ ensureDefaultOutputFolder ( options ) ;
4753
4854 const plugins : PluginInfo [ ] = [ ] ;
49- const pluginDecls = context . schema . declarations . filter ( ( d ) : d is Plugin => isPlugin ( d ) ) ;
55+ const pluginDecls = options . schema . declarations . filter ( ( d ) : d is Plugin => isPlugin ( d ) ) ;
5056
51- let prismaOutput = resolvePath ( './prisma/schema.prisma' , { schemaPath : context . schemaPath , name : '' } ) ;
57+ let prismaOutput = resolvePath ( './prisma/schema.prisma' , { schemaPath : options . schemaPath , name : '' } ) ;
5258
5359 for ( const pluginDecl of pluginDecls ) {
5460 const pluginProvider = this . getPluginProvider ( pluginDecl ) ;
@@ -73,59 +79,35 @@ export class PluginRunner {
7379
7480 const dependencies = this . getPluginDependencies ( pluginModule ) ;
7581 const pluginName = this . getPluginName ( pluginModule , pluginProvider ) ;
76- const options : PluginOptions = { schemaPath : context . schemaPath , name : pluginName } ;
82+ const pluginOptions : PluginOptions = { schemaPath : options . schemaPath , name : pluginName } ;
7783
7884 pluginDecl . fields . forEach ( ( f ) => {
7985 const value = getLiteral ( f . value ) ?? getLiteralArray ( f . value ) ;
8086 if ( value === undefined ) {
8187 throw new PluginError ( pluginName , `Invalid option value for ${ f . name } ` ) ;
8288 }
83- options [ f . name ] = value ;
89+ pluginOptions [ f . name ] = value ;
8490 } ) ;
8591
8692 plugins . push ( {
8793 name : pluginName ,
8894 provider : pluginProvider ,
8995 dependencies,
90- options,
96+ options : pluginOptions ,
9197 run : pluginModule . default as PluginFunction ,
9298 module : pluginModule ,
9399 } ) ;
94100
95- if ( pluginProvider === '@core/prisma' && typeof options . output === 'string' ) {
101+ if ( pluginProvider === '@core/prisma' && typeof pluginOptions . output === 'string' ) {
96102 // record custom prisma output path
97- prismaOutput = resolvePath ( options . output , options ) ;
103+ prismaOutput = resolvePath ( pluginOptions . output , pluginOptions ) ;
98104 }
99105 }
100106
101- // make sure prerequisites are included
102- const corePlugins : Array < { provider : string ; options ?: Record < string , unknown > } > = [
103- { provider : '@core/prisma' } ,
104- { provider : '@core/model-meta' } ,
105- { provider : '@core/access-policy' } ,
106- ] ;
107-
108- if ( getDataModels ( context . schema ) . some ( ( model ) => hasValidationAttributes ( model ) ) ) {
109- // '@core/zod' plugin is auto-enabled if there're validation rules
110- corePlugins . push ( { provider : '@core/zod' , options : { modelOnly : true } } ) ;
111- }
112-
113- // core plugins introduced by dependencies
114- plugins
115- . flatMap ( ( p ) => p . dependencies )
116- . forEach ( ( dep ) => {
117- if ( dep . startsWith ( '@core/' ) ) {
118- const existing = corePlugins . find ( ( p ) => p . provider === dep ) ;
119- if ( existing ) {
120- // reset options to default
121- existing . options = undefined ;
122- } else {
123- // add core dependency
124- corePlugins . push ( { provider : dep } ) ;
125- }
126- }
127- } ) ;
107+ // get core plugins that need to be enabled
108+ const corePlugins = this . calculateCorePlugins ( options , plugins ) ;
128109
110+ // shift/insert core plugins to the front
129111 for ( const corePlugin of corePlugins . reverse ( ) ) {
130112 const existingIdx = plugins . findIndex ( ( p ) => p . provider === corePlugin . provider ) ;
131113 if ( existingIdx >= 0 ) {
@@ -141,7 +123,7 @@ export class PluginRunner {
141123 name : pluginName ,
142124 provider : corePlugin . provider ,
143125 dependencies : [ ] ,
144- options : { schemaPath : context . schemaPath , name : pluginName , ...corePlugin . options } ,
126+ options : { schemaPath : options . schemaPath , name : pluginName , ...corePlugin . options } ,
145127 run : pluginModule . default ,
146128 module : pluginModule ,
147129 } ) ;
@@ -161,12 +143,17 @@ export class PluginRunner {
161143 }
162144 }
163145
146+ if ( plugins . length === 0 ) {
147+ console . log ( colors . yellow ( 'No plugins configured.' ) ) ;
148+ return ;
149+ }
150+
164151 const warnings : string [ ] = [ ] ;
165152
166153 let dmmf : DMMF . Document | undefined = undefined ;
167- for ( const { name, provider, run, options } of plugins ) {
154+ for ( const { name, provider, run, options : pluginOptions } of plugins ) {
168155 // const start = Date.now();
169- await this . runPlugin ( name , run , context , options , dmmf , warnings ) ;
156+ await this . runPlugin ( name , run , options , pluginOptions , dmmf , warnings ) ;
170157 // console.log(`✅ Plugin ${colors.bold(name)} (${provider}) completed in ${Date.now() - start}ms`);
171158 if ( provider === '@core/prisma' ) {
172159 // load prisma DMMF
@@ -175,14 +162,64 @@ export class PluginRunner {
175162 } ) ;
176163 }
177164 }
178-
179165 console . log ( colors . green ( colors . bold ( '\n👻 All plugins completed successfully!' ) ) ) ;
180166
181167 warnings . forEach ( ( w ) => console . warn ( colors . yellow ( w ) ) ) ;
182168
183169 console . log ( `Don't forget to restart your dev server to let the changes take effect.` ) ;
184170 }
185171
172+ private calculateCorePlugins ( options : PluginRunnerOptions , plugins : PluginInfo [ ] ) {
173+ const corePlugins : Array < { provider : string ; options ?: Record < string , unknown > } > = [ ] ;
174+
175+ if ( options . defaultPlugins ) {
176+ corePlugins . push (
177+ { provider : '@core/prisma' } ,
178+ { provider : '@core/model-meta' } ,
179+ { provider : '@core/access-policy' }
180+ ) ;
181+ } else if ( plugins . length > 0 ) {
182+ // "@core/prisma" plugin is always enabled if any plugin is configured
183+ corePlugins . push ( { provider : '@core/prisma' } ) ;
184+ }
185+
186+ // "@core/access-policy" has implicit requirements
187+ if ( [ ...plugins , ...corePlugins ] . find ( ( p ) => p . provider === '@core/access-policy' ) ) {
188+ // make sure "@core/model-meta" is enabled
189+ if ( ! corePlugins . find ( ( p ) => p . provider === '@core/model-meta' ) ) {
190+ corePlugins . push ( { provider : '@core/model-meta' } ) ;
191+ }
192+
193+ // '@core/zod' plugin is auto-enabled by "@core/access-policy"
194+ // if there're validation rules
195+ if ( ! corePlugins . find ( ( p ) => p . provider === '@core/zod' ) && this . hasValidation ( options . schema ) ) {
196+ corePlugins . push ( { provider : '@core/zod' , options : { modelOnly : true } } ) ;
197+ }
198+ }
199+
200+ // core plugins introduced by dependencies
201+ plugins
202+ . flatMap ( ( p ) => p . dependencies )
203+ . forEach ( ( dep ) => {
204+ if ( dep . startsWith ( '@core/' ) ) {
205+ const existing = corePlugins . find ( ( p ) => p . provider === dep ) ;
206+ if ( existing ) {
207+ // reset options to default
208+ existing . options = undefined ;
209+ } else {
210+ // add core dependency
211+ corePlugins . push ( { provider : dep } ) ;
212+ }
213+ }
214+ } ) ;
215+
216+ return corePlugins ;
217+ }
218+
219+ private hasValidation ( schema : Model ) {
220+ return getDataModels ( schema ) . some ( ( model ) => hasValidationAttributes ( model ) ) ;
221+ }
222+
186223 // eslint-disable-next-line @typescript-eslint/no-explicit-any
187224 private getPluginName ( pluginModule : any , pluginProvider : string ) : string {
188225 return typeof pluginModule . name === 'string' ? ( pluginModule . name as string ) : pluginProvider ;
@@ -200,7 +237,7 @@ export class PluginRunner {
200237 private async runPlugin (
201238 name : string ,
202239 run : PluginFunction ,
203- context : Context ,
240+ runnerOptions : PluginRunnerOptions ,
204241 options : PluginOptions ,
205242 dmmf : DMMF . Document | undefined ,
206243 warnings : string [ ]
@@ -216,7 +253,10 @@ export class PluginRunner {
216253 options,
217254 } ,
218255 async ( ) => {
219- let result = run ( context . schema , options , dmmf , config ) ;
256+ let result = run ( runnerOptions . schema , options , dmmf , {
257+ output : runnerOptions . output ,
258+ compile : runnerOptions . compile ,
259+ } ) ;
220260 if ( result instanceof Promise ) {
221261 result = await result ;
222262 }
0 commit comments