@@ -129,7 +129,7 @@ export default function scenarioPlugin() {
129
129
}
130
130
131
131
/**
132
- * Scan scenario directory and generate config object
132
+ * Scan scenario directory and generate config object with parallel processing
133
133
* @param {string } scenariosPath - Path to scenarios directory
134
134
* @returns {Promise<object> } - Config object
135
135
*/
@@ -154,49 +154,39 @@ async function generateConfig(scenariosPath) {
154
154
155
155
const dirs = await fs . promises . readdir ( scenariosPath )
156
156
157
- for ( const dir of dirs ) {
157
+ // Process all scenario directories in parallel
158
+ const scenarioPromises = dirs . map ( async ( dir ) => {
158
159
const scenarioPath = path . join ( scenariosPath , dir )
159
160
const stat = await fs . promises . stat ( scenarioPath )
160
161
161
- if ( ! stat . isDirectory ( ) ) continue
162
+ if ( ! stat . isDirectory ( ) ) return null
162
163
163
164
// Read all files in the scenario
164
- const files = { }
165
165
const allFiles = await fs . promises . readdir ( scenarioPath )
166
-
167
166
let meta = { mainFile : 'main.ts' } // Default metadata
168
167
169
168
// Handle metadata file first to get metadata before other files
170
169
const metaFile = allFiles . find ( ( file ) => file === '_meta.js' )
171
170
if ( metaFile ) {
172
171
const metaFilePath = path . join ( scenarioPath , metaFile )
173
172
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
+ )
200
190
}
201
191
} catch ( error ) {
202
192
console . error (
@@ -206,25 +196,49 @@ async function generateConfig(scenariosPath) {
206
196
}
207
197
}
208
198
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
+ } )
217
215
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 )
221
219
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
224
224
files,
225
+ // all meta config support extend
225
226
...meta ,
226
227
}
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
+ } )
228
242
229
243
console . log (
230
244
`[vite-plugin-scenario] Config generated, scenarios: ${ Object . keys ( config ) . join ( ', ' ) } ` ,
0 commit comments