@@ -185,6 +185,142 @@ describe("discoverScenarios", () => {
185185 await expect ( discoverScenarios ( FIXTURES_DIR , "./nonexistent" ) ) . rejects . toThrow ( "Could not read scenarios path" ) ;
186186 } ) ;
187187
188+ describe ( "walk filtering" , ( ) => {
189+ it ( "silently skips JSON files that do not look like scenarios" , async ( ) => {
190+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "axis-walk-" ) ) ;
191+ const scenariosDir = path . join ( tmpDir , "scenarios" ) ;
192+ const fixtureDir = path . join ( scenariosDir , "fixture-site" ) ;
193+ await fs . mkdir ( fixtureDir , { recursive : true } ) ;
194+
195+ await fs . writeFile (
196+ path . join ( scenariosDir , "real.json" ) ,
197+ JSON . stringify ( { name : "Real" , prompt : "p" , rubric : "r" } ) ,
198+ ) ;
199+ await fs . writeFile (
200+ path . join ( fixtureDir , "package.json" ) ,
201+ JSON . stringify ( { name : "fixture-site" , version : "1.0.0" , dependencies : { } } ) ,
202+ ) ;
203+ await fs . writeFile (
204+ path . join ( fixtureDir , "state.json" ) ,
205+ JSON . stringify ( { siteId : "abc" , deployId : "xyz" } ) ,
206+ ) ;
207+
208+ const scenarios = await discoverScenarios ( tmpDir , "./scenarios" ) ;
209+ expect ( scenarios . map ( ( s ) => s . key ) ) . toEqual ( [ "real" ] ) ;
210+
211+ await fs . rm ( tmpDir , { recursive : true } ) ;
212+ } ) ;
213+
214+ it ( "still validates JSON files that have any scenario-marker field" , async ( ) => {
215+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "axis-walk-" ) ) ;
216+ const scenariosDir = path . join ( tmpDir , "scenarios" ) ;
217+ await fs . mkdir ( scenariosDir , { recursive : true } ) ;
218+ // Has `prompt` but no `name` — clearly intended as a scenario, must error.
219+ await fs . writeFile (
220+ path . join ( scenariosDir , "broken.json" ) ,
221+ JSON . stringify ( { prompt : "do thing" , rubric : "judged" } ) ,
222+ ) ;
223+
224+ await expect ( discoverScenarios ( tmpDir , "./scenarios" ) ) . rejects . toThrow ( / m i s s i n g r e q u i r e d f i e l d " n a m e " / ) ;
225+
226+ await fs . rm ( tmpDir , { recursive : true } ) ;
227+ } ) ;
228+
229+ it ( "silently skips invalid JSON files when walking" , async ( ) => {
230+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "axis-walk-" ) ) ;
231+ const scenariosDir = path . join ( tmpDir , "scenarios" ) ;
232+ await fs . mkdir ( scenariosDir , { recursive : true } ) ;
233+ await fs . writeFile (
234+ path . join ( scenariosDir , "real.json" ) ,
235+ JSON . stringify ( { name : "Real" , prompt : "p" , rubric : "r" } ) ,
236+ ) ;
237+ await fs . writeFile ( path . join ( scenariosDir , "garbage.json" ) , "this is not json" ) ;
238+
239+ const scenarios = await discoverScenarios ( tmpDir , "./scenarios" ) ;
240+ expect ( scenarios . map ( ( s ) => s . key ) ) . toEqual ( [ "real" ] ) ;
241+
242+ await fs . rm ( tmpDir , { recursive : true } ) ;
243+ } ) ;
244+
245+ it ( "does not descend into hidden directories or node_modules" , async ( ) => {
246+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "axis-walk-" ) ) ;
247+ const scenariosDir = path . join ( tmpDir , "scenarios" ) ;
248+ const hidden = path . join ( scenariosDir , ".netlify" ) ;
249+ const vendored = path . join ( scenariosDir , "node_modules" , "some-pkg" ) ;
250+ await fs . mkdir ( hidden , { recursive : true } ) ;
251+ await fs . mkdir ( vendored , { recursive : true } ) ;
252+
253+ await fs . writeFile (
254+ path . join ( scenariosDir , "real.json" ) ,
255+ JSON . stringify ( { name : "Real" , prompt : "p" , rubric : "r" } ) ,
256+ ) ;
257+ // Files inside these dirs would otherwise be readdir'd and parsed.
258+ // Even if they happened to look like scenarios, we should not pick them up.
259+ await fs . writeFile (
260+ path . join ( hidden , "state.json" ) ,
261+ JSON . stringify ( { name : "Should Not Load" , prompt : "p" , rubric : "r" } ) ,
262+ ) ;
263+ await fs . writeFile (
264+ path . join ( vendored , "package.json" ) ,
265+ JSON . stringify ( { name : "Should Not Load" , prompt : "p" , rubric : "r" } ) ,
266+ ) ;
267+
268+ const scenarios = await discoverScenarios ( tmpDir , "./scenarios" ) ;
269+ expect ( scenarios . map ( ( s ) => s . key ) ) . toEqual ( [ "real" ] ) ;
270+
271+ await fs . rm ( tmpDir , { recursive : true } ) ;
272+ } ) ;
273+
274+ it ( "treats explicit single-file JSON entries strictly even if they don't look like scenarios" , async ( ) => {
275+ // Pointing at a file directly is intent — surface validation errors instead of silently skipping.
276+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "axis-walk-" ) ) ;
277+ await fs . writeFile ( path . join ( tmpDir , "not-a-scenario.json" ) , JSON . stringify ( { siteId : "x" } ) ) ;
278+
279+ await expect ( discoverScenarios ( tmpDir , "./not-a-scenario.json" ) ) . rejects . toThrow ( / m i s s i n g r e q u i r e d f i e l d / ) ;
280+
281+ await fs . rm ( tmpDir , { recursive : true } ) ;
282+ } ) ;
283+
284+ it ( "silently skips ESM module files that default-export non-scenario configs" , async ( ) => {
285+ // Mimics next.config.mjs / vite.config.mjs living inside a fixture codebase.
286+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "axis-walk-" ) ) ;
287+ const scenariosDir = path . join ( tmpDir , "scenarios" ) ;
288+ const fixtureDir = path . join ( scenariosDir , "nextjs-fixture" ) ;
289+ await fs . mkdir ( fixtureDir , { recursive : true } ) ;
290+
291+ await fs . writeFile (
292+ path . join ( scenariosDir , "real.json" ) ,
293+ JSON . stringify ( { name : "Real" , prompt : "p" , rubric : "r" } ) ,
294+ ) ;
295+ await fs . writeFile (
296+ path . join ( fixtureDir , "next.config.mjs" ) ,
297+ `export default { reactStrictMode: true, images: { remotePatterns: [] } };` ,
298+ ) ;
299+
300+ const scenarios = await discoverScenarios ( tmpDir , "./scenarios" ) ;
301+ expect ( scenarios . map ( ( s ) => s . key ) ) . toEqual ( [ "real" ] ) ;
302+
303+ await fs . rm ( tmpDir , { recursive : true } ) ;
304+ } ) ;
305+
306+ it ( "silently skips module files that fail to import when walking" , async ( ) => {
307+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , "axis-walk-" ) ) ;
308+ const scenariosDir = path . join ( tmpDir , "scenarios" ) ;
309+ await fs . mkdir ( scenariosDir , { recursive : true } ) ;
310+
311+ await fs . writeFile (
312+ path . join ( scenariosDir , "real.json" ) ,
313+ JSON . stringify ( { name : "Real" , prompt : "p" , rubric : "r" } ) ,
314+ ) ;
315+ await fs . writeFile ( path . join ( scenariosDir , "broken.mjs" ) , `import "./does-not-exist.js";` ) ;
316+
317+ const scenarios = await discoverScenarios ( tmpDir , "./scenarios" ) ;
318+ expect ( scenarios . map ( ( s ) => s . key ) ) . toEqual ( [ "real" ] ) ;
319+
320+ await fs . rm ( tmpDir , { recursive : true } ) ;
321+ } ) ;
322+ } ) ;
323+
188324 describe ( "variants" , ( ) => {
189325 let tmpDir : string ;
190326
0 commit comments