| 251 | * a parsing edge case can never fail a run. |
| 252 | */ |
| 253 | const extractScenarioSource = (filePath: string, name: string): string | undefined => { |
| 254 | try { |
| 255 | const source = readFileSync(filePath, "utf8").replace(/^import[\s\S]*?;[^\S\n]*$/gm, ""); |
| 256 | const needle = "scenario("; |
| 257 | const blocks: Array<{ start: number; end: number; mine: boolean }> = []; |
| 258 | let index = 0; |
| 259 | while ((index = source.indexOf(needle, index)) !== -1) { |
| 260 | let depth = 0; |
| 261 | let end = -1; |
| 262 | for (let i = index + needle.length - 1; i < source.length; i++) { |
| 263 | if (source[i] === "(") depth++; |
| 264 | else if (source[i] === ")") { |
| 265 | depth--; |
| 266 | if (depth === 0) { |
| 267 | end = source[i + 1] === ";" ? i + 2 : i + 1; |
| 268 | break; |
| 269 | } |
| 270 | } |
| 271 | } |
| 272 | if (end === -1) return undefined; // unbalanced — bail to be safe |
| 273 | blocks.push({ |
| 274 | start: index, |
| 275 | end, |
| 276 | mine: source.slice(index, end).includes(`"${name}"`), |
| 277 | }); |
| 278 | index = end; |
| 279 | } |
| 280 | if (!blocks.some((b) => b.mine)) return undefined; |
| 281 | let out = source; |
| 282 | for (const block of [...blocks].reverse()) { |
| 283 | if (!block.mine) out = out.slice(0, block.start) + out.slice(block.end); |
| 284 | } |
| 285 | return `${out.replace(/\n{3,}/g, "\n\n").trim()}\n`; |
| 286 | } catch { |
| 287 | return undefined; |
| 288 | } |
| 289 | }; |