diff --git a/src/cli.ts b/src/cli.ts index 70c3747..c8af74b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -223,7 +223,9 @@ async function loadConfig(configPath: string, cwd: string): Promise/src/foo/index.ts" }` + */ +export function readTsconfigAliases(cwd: string): Record { + const alias: Record = {} + try { + const raw = readFileSync(path.join(cwd, 'tsconfig.json'), 'utf-8') + // Strip single-line comments (// ...) so JSON.parse succeeds on tsconfig + const stripped = raw.replace(/\/\/.*$/gm, '') + const tsconfig = JSON.parse(stripped) + const paths: Record = tsconfig?.compilerOptions?.paths ?? {} + const baseUrl = tsconfig?.compilerOptions?.baseUrl ?? '.' + const base = path.resolve(cwd, baseUrl) + for (const [key, targets] of Object.entries(paths)) { + if (targets.length > 0) { + // Strip trailing /* for wildcard paths + const cleanKey = key.replace(/\/\*$/, '') + const cleanTarget = targets[0].replace(/\/\*$/, '') + alias[cleanKey] = path.resolve(base, cleanTarget) + } + } + } catch { + // No tsconfig or parse error — skip silently + } + return alias +} + // Mutex to prevent concurrent loadScenarioFile calls from clobbering // the global scenario registry (clear → import → read must be atomic). let registryLock: Promise = Promise.resolve() -// Reuse a single jiti instance across file loads to benefit from its internal -// module resolution and transform caches. -const jiti = createJiti(import.meta.url, { interopDefault: true }) +// Lazily initialized jiti instance — needs cwd for tsconfig alias resolution. +let _jiti: ReturnType | null = null -export async function loadScenarioFile(filePath: string): Promise { +function getJiti(cwd: string): ReturnType { + if (!_jiti) { + const alias = readTsconfigAliases(cwd) + _jiti = createJiti(import.meta.url, { interopDefault: true, alias }) + } + return _jiti +} + +export async function loadScenarioFile( + filePath: string, + cwd: string = process.cwd(), +): Promise { // Chain onto the lock so only one file loads at a time const release = registryLock let resolve: () => void @@ -51,6 +90,7 @@ export async function loadScenarioFile(filePath: string): Promise { try { clearScenarioRegistry() + const jiti = getJiti(cwd) await jiti.import(filePath) return getRegisteredScenarios() @@ -63,11 +103,12 @@ export async function discoverAndLoad( config: AgentestConfig, cwd?: string, ): Promise { - const files = await discoverScenarioFiles(config, cwd) + const resolvedCwd = cwd ?? process.cwd() + const files = await discoverScenarioFiles(config, resolvedCwd) const results: DiscoveryResult[] = [] for (const file of files) { - const scenarios = await loadScenarioFile(file) + const scenarios = await loadScenarioFile(file, resolvedCwd) if (scenarios.length > 0) { results.push({ file, scenarios }) }