Skip to content

Commit 4ae0903

Browse files
authored
feat: exit non-zero when analysis has findings (#163)
* feat(cli): exit non-zero when analysis has findings * refactor(analyze): move severity and fail threshold ranks to module scope * test(cli): simplify tests by using a fixture for chalk package * update: snapshots
1 parent 5bdd7df commit 4ae0903

5 files changed

Lines changed: 74 additions & 5 deletions

File tree

src/commands/analyze.meta.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export const meta = {
66
type: 'enum',
77
choices: ['debug', 'info', 'warn', 'error'],
88
default: 'info',
9-
description: 'Set the log level (debug | info | warn | error)'
9+
description:
10+
'Set the log level and the minimum severity that causes a non-zero exit code (debug | info | warn | error)'
1011
},
1112
manifest: {
1213
type: 'string',

src/commands/analyze.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ function formatBytes(bytes: number) {
1919
return `${size.toFixed(1)} ${units[unitIndex]}`;
2020
}
2121

22+
const SEVERITY_RANK: Record<string, number> = {
23+
error: 3,
24+
warning: 2,
25+
suggestion: 1
26+
};
27+
const FAIL_THRESHOLD_RANK: Record<string, number> = {
28+
error: 3,
29+
warn: 2,
30+
info: 1,
31+
debug: 0
32+
};
33+
2234
export async function run(ctx: CommandContext<typeof meta>) {
2335
const [_commandName, providedPath] = ctx.positionals;
2436
const logLevel = ctx.values['log-level'];
@@ -148,4 +160,13 @@ export async function run(ctx: CommandContext<typeof meta>) {
148160
}
149161
}
150162
prompts.outro('Done!');
163+
164+
// Exit with non-zero when messages meet the fail threshold (--log-level)
165+
const thresholdRank = FAIL_THRESHOLD_RANK[logLevel] ?? 0;
166+
const hasFailingMessages =
167+
thresholdRank > 0 &&
168+
messages.some((m) => SEVERITY_RANK[m.severity] >= thresholdRank);
169+
if (hasFailingMessages) {
170+
process.exit(1);
171+
}
151172
}

src/test/cli.test.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ const stripVersion = (str: string): string =>
1414
const normalizeStderr = (str: string): string =>
1515
str.replace(/\(node:\d+\)/g, '(node:<pid>)');
1616

17+
const basicChalkFixture = path.join(
18+
__dirname,
19+
'../../test/fixtures/basic-chalk'
20+
);
21+
1722
beforeAll(async () => {
1823
// Create a temporary directory for the test package
1924
tempDir = await createTempDir();
@@ -94,10 +99,33 @@ describe('CLI', () => {
9499
});
95100
});
96101

97-
const basicChalkFixture = path.join(
98-
__dirname,
99-
'../../test/fixtures/basic-chalk'
100-
);
102+
describe('analyze exit codes', () => {
103+
it('exits 1 when path is not a directory', async () => {
104+
const {code} = await runCliProcess(['analyze', '/nonexistent-path']);
105+
expect(code).toBe(1);
106+
});
107+
108+
it('exits 0 with --log-level=debug', async () => {
109+
const {code} = await runCliProcess(
110+
['analyze', '--log-level=debug'],
111+
tempDir
112+
);
113+
expect(code).toBe(0);
114+
});
115+
116+
it('exits 1 with default log-level when analysis has messages', async () => {
117+
const {code} = await runCliProcess(['analyze'], basicChalkFixture);
118+
expect(code).toBe(1);
119+
});
120+
121+
it('exits 0 with --log-level=debug when analysis has messages', async () => {
122+
const {code} = await runCliProcess(
123+
['analyze', '--log-level=debug'],
124+
basicChalkFixture
125+
);
126+
expect(code).toBe(0);
127+
});
128+
});
101129

102130
describe('migrate --all', () => {
103131
it('should migrate all fixable replacements with --all --dry-run when project has fixable deps', async () => {

test/fixtures/basic-chalk/node_modules/chalk/package.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/basic-chalk/package-lock.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)