diff --git a/cli/engine/cli/main.mjs b/cli/engine/cli/main.mjs index b77281e7d..e54887323 100644 --- a/cli/engine/cli/main.mjs +++ b/cli/engine/cli/main.mjs @@ -364,7 +364,11 @@ async function detectCli() { const resolved = path.resolve(target); let stat; try { stat = fs.statSync(resolved); } - catch { process.stderr.write(`Warning: cannot access ${target}\n`); continue; } + catch { + hadOperationalFailure = true; + process.stderr.write(`Warning: cannot access ${target}\n`); + continue; + } if (stat.isDirectory()) { // Check for framework dev server config (skip in JSON/quiet modes to avoid polluting output) diff --git a/tests/detect-url-launch.test.mjs b/tests/detect-url-launch.test.mjs index 75200c3e6..18119e96c 100644 --- a/tests/detect-url-launch.test.mjs +++ b/tests/detect-url-launch.test.mjs @@ -138,6 +138,26 @@ describe('detect CLI browser failures', () => { expect(findings.some(finding => finding.antipattern === 'bounce-easing')).toBe(true); expect(result.stderr).toContain('puppeteer is required for URL scanning'); }); + + test('exits 1 when an explicitly requested local target cannot be accessed', () => { + const result = runWithoutPuppeteer(['missing.css']); + + expect(result.status).toBe(1); + expect(result.stdout).toBe('[]\n'); + expect(result.stderr).toContain('Warning: cannot access missing.css'); + }); + + test('missing local target takes precedence over findings from another target', () => { + const result = runWithoutPuppeteer( + ['missing.css', 'page.css'], + { 'page.css': '.hero { animation: bounce 1s linear infinite; }\n' }, + ); + const findings = JSON.parse(result.stdout); + + expect(result.status).toBe(1); + expect(findings.some(finding => finding.antipattern === 'bounce-easing')).toBe(true); + expect(result.stderr).toContain('Warning: cannot access missing.css'); + }); }); describe('splitScanUrl', () => {