@@ -3,7 +3,10 @@ import { isPlugin, LiteralExpr, Plugin, type Model } from '@zenstackhq/language/
33import { getLiteral , getLiteralArray } from '@zenstackhq/language/utils' ;
44import { type CliPlugin } from '@zenstackhq/sdk' ;
55import colors from 'colors' ;
6+ import { createJiti } from 'jiti' ;
7+ import fs from 'node:fs' ;
68import path from 'node:path' ;
9+ import { pathToFileURL } from 'node:url' ;
710import ora , { type Ora } from 'ora' ;
811import { CliError } from '../cli-error' ;
912import * as corePlugins from '../plugins' ;
@@ -73,16 +76,7 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
7376 throw new CliError ( `Unknown core plugin: ${ provider } ` ) ;
7477 }
7578 } else {
76- let moduleSpec = provider ;
77- if ( moduleSpec . startsWith ( '.' ) ) {
78- // relative to schema's path
79- moduleSpec = path . resolve ( path . dirname ( schemaFile ) , moduleSpec ) ;
80- }
81- try {
82- cliPlugin = ( await import ( moduleSpec ) ) . default as CliPlugin ;
83- } catch {
84- // plugin may not export a generator so we simply ignore the error here
85- }
79+ cliPlugin = await loadPluginModule ( provider , path . dirname ( schemaFile ) ) ;
8680 }
8781
8882 if ( cliPlugin ) {
@@ -110,7 +104,8 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string,
110104 ] ;
111105 defaultPlugins . forEach ( ( { plugin, options } ) => {
112106 if ( ! processedPlugins . some ( ( p ) => p . cliPlugin === plugin ) ) {
113- processedPlugins . push ( { cliPlugin : plugin , pluginOptions : options } ) ;
107+ // default plugins are run before user plugins
108+ processedPlugins . unshift ( { cliPlugin : plugin , pluginOptions : options } ) ;
114109 }
115110 } ) ;
116111
@@ -163,3 +158,69 @@ function getPluginOptions(plugin: Plugin): Record<string, unknown> {
163158 }
164159 return result ;
165160}
161+
162+ async function loadPluginModule ( provider : string , basePath : string ) {
163+ let moduleSpec = provider ;
164+ if ( moduleSpec . startsWith ( '.' ) ) {
165+ // relative to schema's path
166+ moduleSpec = path . resolve ( basePath , moduleSpec ) ;
167+ }
168+
169+ const importAsEsm = async ( spec : string ) => {
170+ try {
171+ const result = ( await import ( spec ) ) . default as CliPlugin ;
172+ return result ;
173+ } catch ( err ) {
174+ throw new CliError ( `Failed to load plugin module from ${ spec } : ${ ( err as Error ) . message } ` ) ;
175+ }
176+ } ;
177+
178+ const jiti = createJiti ( pathToFileURL ( basePath ) . toString ( ) ) ;
179+ const importAsTs = async ( spec : string ) => {
180+ try {
181+ const result = ( await jiti . import ( spec , { default : true } ) ) as CliPlugin ;
182+ return result ;
183+ } catch ( err ) {
184+ throw new CliError ( `Failed to load plugin module from ${ spec } : ${ ( err as Error ) . message } ` ) ;
185+ }
186+ } ;
187+
188+ const esmSuffixes = [ '.js' , '.mjs' ] ;
189+ const tsSuffixes = [ '.ts' , '.mts' ] ;
190+
191+ if ( fs . existsSync ( moduleSpec ) && fs . statSync ( moduleSpec ) . isFile ( ) ) {
192+ // try provider as ESM file
193+ if ( esmSuffixes . some ( ( suffix ) => moduleSpec . endsWith ( suffix ) ) ) {
194+ return await importAsEsm ( pathToFileURL ( moduleSpec ) . toString ( ) ) ;
195+ }
196+
197+ // try provider as TS file
198+ if ( tsSuffixes . some ( ( suffix ) => moduleSpec . endsWith ( suffix ) ) ) {
199+ return await importAsTs ( moduleSpec ) ;
200+ }
201+ }
202+
203+ // try ESM index files in provider directory
204+ for ( const suffix of esmSuffixes ) {
205+ const indexPath = path . join ( moduleSpec , `index${ suffix } ` ) ;
206+ if ( fs . existsSync ( indexPath ) ) {
207+ return await importAsEsm ( pathToFileURL ( indexPath ) . toString ( ) ) ;
208+ }
209+ }
210+
211+ // try TS index files in provider directory
212+ for ( const suffix of tsSuffixes ) {
213+ const indexPath = path . join ( moduleSpec , `index${ suffix } ` ) ;
214+ if ( fs . existsSync ( indexPath ) ) {
215+ return await importAsTs ( indexPath ) ;
216+ }
217+ }
218+
219+ // last resort, try to import as esm directly
220+ try {
221+ return ( await import ( moduleSpec ) ) . default as CliPlugin ;
222+ } catch {
223+ // plugin may not export a generator so we simply ignore the error here
224+ return undefined ;
225+ }
226+ }
0 commit comments