Add quiet mode to detect CLI (#259)

This commit is contained in:
Abdul Wahab
2026-06-18 18:47:02 -07:00
committed by GitHub
parent 1c897a09c8
commit 07667ed08f
2 changed files with 47 additions and 4 deletions
+11 -4
View File
@@ -22,6 +22,10 @@ import {
// Output formatting
// ---------------------------------------------------------------------------
function formatFindingSummary(count) {
return `${count} anti-pattern${count === 1 ? '' : 's'} found.`;
}
function formatFindings(findings, jsonMode) {
if (jsonMode) return JSON.stringify(findings, null, 2);
@@ -39,7 +43,7 @@ function formatFindings(findings, jsonMode) {
out.push(`${item.description}`);
}
}
out.push(`\n${findings.length} anti-pattern${findings.length === 1 ? '' : 's'} found.`);
out.push(`\n${formatFindingSummary(findings.length)}`);
return out.join('\n');
}
@@ -86,6 +90,7 @@ Scan files or URLs for UI anti-patterns and design quality issues.
Options:
--json Output results as JSON
--quiet In text mode, only print the final findings count
--gpt Also report GPT-specific provider tells (off by default)
--gemini Also report Gemini-specific provider tells (off by default)
--no-config Do not apply project config, detector ignores, or DESIGN.md
@@ -118,6 +123,7 @@ async function detectCli() {
});
if (args[0] === 'detect') args = args.slice(1);
const jsonMode = args.includes('--json');
const quietMode = args.includes('--quiet');
const helpMode = args.includes('--help');
// --fast (regex-only) is deprecated: since the jsdom removal, the static
// HTML/CSS analysis is fast and covers every rule, so the regex-only path
@@ -169,8 +175,8 @@ async function detectCli() {
catch { process.stderr.write(`Warning: cannot access ${target}\n`); continue; }
if (stat.isDirectory()) {
// Check for framework dev server config (skip in JSON mode to avoid polluting output)
if (!jsonMode) {
// Check for framework dev server config (skip in JSON/quiet modes to avoid polluting output)
if (!jsonMode && !quietMode) {
const fwConfig = detectFrameworkConfig(resolved);
if (fwConfig) {
const probe = await isPortListening(fwConfig.port, fwConfig.fingerprint);
@@ -200,7 +206,7 @@ async function detectCli() {
const htmlCount = files.filter(f => HTML_EXTENSIONS.has(path.extname(f).toLowerCase())).length;
// Warn and confirm if scanning many files (static HTML/CSS processes each HTML file)
if (files.length > 50 && process.stdin.isTTY && !jsonMode) {
if (files.length > 50 && process.stdin.isTTY && !jsonMode && !quietMode) {
process.stderr.write(
`\nFound ${files.length} files (${htmlCount} HTML) in ${target}.\n` +
`Scanning may take a while${htmlCount > 10 ? ' (static HTML/CSS processes each HTML file individually)' : ''}.\n` +
@@ -258,6 +264,7 @@ async function detectCli() {
if (allFindings.length > 0) {
if (jsonMode) process.stdout.write(formatFindings(allFindings, true) + '\n');
else if (quietMode) process.stderr.write(formatFindingSummary(allFindings.length) + '\n');
else process.stderr.write(formatFindings(allFindings, false) + '\n');
process.exit(2);
}
+36
View File
@@ -879,6 +879,7 @@ describe('CLI', () => {
const { stdout, code } = run('--help');
expect(code).toBe(0);
expect(stdout).toContain('Usage:');
expect(stdout).toContain('--quiet');
});
test('detect subcommand is not treated as a scan target', () => {
@@ -906,6 +907,32 @@ describe('CLI', () => {
expect(parsed.length).toBeGreaterThan(0);
});
test('--quiet suppresses text details and keeps the summary exit signal', () => {
const { stdout, stderr, code } = run('--quiet', path.join(FIXTURES, 'should-flag.html'));
expect(code).toBe(2);
expect(stdout).toBe('');
expect(stderr.trim()).toMatch(/^[1-9]\d* anti-patterns? found\.$/);
expect(stderr).not.toContain('side-tab');
expect(stderr).not.toContain('line ');
});
test('--quiet stays silent on clean files', () => {
const { stdout, stderr, code } = run('--quiet', path.join(FIXTURES, 'should-pass.html'));
expect(code).toBe(0);
expect(stdout).toBe('');
expect(stderr).toBe('');
});
test('--quiet does not affect JSON output', () => {
const { stdout, stderr, code } = run('--json', '--quiet', path.join(FIXTURES, 'should-flag.html'));
expect(code).toBe(2);
expect(stderr).toBe('');
const parsed = JSON.parse(stdout.trim());
expect(parsed).toBeArray();
expect(parsed.length).toBeGreaterThan(0);
expect(parsed.some(f => f.antipattern === 'side-tab')).toBe(true);
});
test('-json alias outputs valid JSON', () => {
const { stdout, stderr, code } = run('-json', path.join(FIXTURES, 'should-flag.html'));
expect(code).toBe(2);
@@ -1745,6 +1772,15 @@ describe('CLI -- dev server suggestion', () => {
expect(stderr).toContain('3000');
});
test('--quiet suppresses framework URL scan suggestions', () => {
const { stderr, code } = run('--quiet', path.join(FIXTURES, 'framework-next-tailwind'));
expect(code).toBe(2);
expect(stderr.trim()).toMatch(/^[1-9]\d* anti-patterns? found\.$/);
expect(stderr).not.toContain('Next.js');
expect(stderr).not.toContain('3000');
expect(stderr).not.toContain('Start the dev server');
});
test('suggests URL scan when Vite config found', () => {
const { stderr } = run(path.join(FIXTURES, 'framework-vite'));
expect(stderr).toContain('Vite');