@@ -129,7 +129,7 @@ export default function scenarioPlugin() {
129129}
130130
131131/**
132- * Scan scenario directory and generate config object
132+ * Scan scenario directory and generate config object with parallel processing
133133 * @param {string } scenariosPath - Path to scenarios directory
134134 * @returns {Promise<object> } - Config object
135135 */
@@ -154,49 +154,39 @@ async function generateConfig(scenariosPath) {
154154
155155 const dirs = await fs . promises . readdir ( scenariosPath )
156156
157- for ( const dir of dirs ) {
157+ // Process all scenario directories in parallel
158+ const scenarioPromises = dirs . map ( async ( dir ) => {
158159 const scenarioPath = path . join ( scenariosPath , dir )
159160 const stat = await fs . promises . stat ( scenarioPath )
160161
161- if ( ! stat . isDirectory ( ) ) continue
162+ if ( ! stat . isDirectory ( ) ) return null
162163
163164 // Read all files in the scenario
164- const files = { }
165165 const allFiles = await fs . promises . readdir ( scenarioPath )
166-
167166 let meta = { mainFile : 'main.ts' } // Default metadata
168167
169168 // Handle metadata file first to get metadata before other files
170169 const metaFile = allFiles . find ( ( file ) => file === '_meta.js' )
171170 if ( metaFile ) {
172171 const metaFilePath = path . join ( scenarioPath , metaFile )
173172 try {
174- // Read metadata file and manually parse
175- const metaContent = await fs . promises . readFile ( metaFilePath , 'utf-8' )
176-
177- // Extract default export part
178- // Use simple regex to match export default {...}
179- const defaultExportMatch = metaContent . match (
180- / e x p o r t \s + d e f a u l t \s + ( { [ \s \S ] * ?} ) / m,
181- )
182- if ( defaultExportMatch && defaultExportMatch [ 1 ] ) {
183- try {
184- // Use Function constructor to safely parse JS object
185- // Safer than eval, but can handle JS object syntax
186- const extractedMeta = new Function (
187- `return ${ defaultExportMatch [ 1 ] } ` ,
188- ) ( )
189- meta = { ...meta , ...extractedMeta }
190- console . log (
191- `[vite-plugin-scenario] Loaded scenario metadata: ${ dir } ` ,
192- meta ,
193- )
194- } catch ( parseError ) {
195- console . error (
196- `[vite-plugin-scenario] Failed to parse metadata: ${ metaFilePath } ` ,
197- parseError ,
198- )
199- }
173+ // Use dynamic import to properly load the ES module
174+ // This is safer and more reliable than regex extraction
175+ // Convert to absolute path URL for dynamic import
176+ // Add timestamp to URL to bypass module cache
177+ const fileUrl = `file://${ path . resolve ( metaFilePath ) } ?t=${ Date . now ( ) } `
178+
179+ // Dynamically import the metadata module
180+ const metaModule = await import ( fileUrl )
181+
182+ if ( metaModule . default ) {
183+ // Deep merge the metadata with defaults
184+ // This ensures all ReplOptions properties are properly merged
185+ meta = { ...( meta || { } ) , ...( metaModule . default || { } ) }
186+ console . log (
187+ `[vite-plugin-scenario] Loaded scenario metadata: ${ dir } ` ,
188+ meta ,
189+ )
200190 }
201191 } catch ( error ) {
202192 console . error (
@@ -206,25 +196,49 @@ async function generateConfig(scenariosPath) {
206196 }
207197 }
208198
209- // Process all files in the scenario
210- for ( const file of allFiles ) {
211- // Skip hidden files and metadata file
212- if ( file . startsWith ( '.' ) || file === '_meta.js' ) continue
213-
214- // Read file content
215- const filePath = path . join ( scenarioPath , file )
216- const content = await fs . promises . readFile ( filePath , 'utf-8' )
199+ // Read all scenario files in parallel
200+ const filePromises = allFiles
201+ . filter ( ( file ) => ! file . startsWith ( '.' ) && file !== '_meta.js' )
202+ . map ( async ( file ) => {
203+ const filePath = path . join ( scenarioPath , file )
204+ try {
205+ const content = await fs . promises . readFile ( filePath , 'utf-8' )
206+ return [ file , content ] // Return as key-value pair
207+ } catch ( error ) {
208+ console . error (
209+ `[vite-plugin-scenario] Error reading file: ${ filePath } ` ,
210+ error ,
211+ )
212+ return [ file , '' ] // Return empty content on error
213+ }
214+ } )
217215
218- // Store file content in config
219- files [ file ] = content
220- }
216+ // Wait for all file reading promises to complete
217+ const fileEntries = await Promise . all ( filePromises )
218+ const files = Object . fromEntries ( fileEntries )
221219
222- // Build scenario config
223- config [ dir ] = {
220+ // Build complete scenario configuration
221+ // Structure it to match what REPL expects: files object + options
222+ const scenarioConfig = {
223+ // Files must be in this format for REPL
224224 files,
225+ // all meta config support extend
225226 ...meta ,
226227 }
227- }
228+
229+ // Return scenario name and its configuration
230+ return [ dir , scenarioConfig ]
231+ } )
232+
233+ // Wait for all scenario processing to complete
234+ const scenarioResults = await Promise . all ( scenarioPromises )
235+
236+ // Filter out null results (non-directories) and build config object
237+ scenarioResults
238+ . filter ( ( result ) => result !== null )
239+ . forEach ( ( [ scenarioName , scenarioConfig ] ) => {
240+ config [ scenarioName ] = scenarioConfig
241+ } )
228242
229243 console . log (
230244 `[vite-plugin-scenario] Config generated, scenarios: ${ Object . keys ( config ) . join ( ', ' ) } ` ,
0 commit comments