Skip to content

Commit 2dc5358

Browse files
Don't log stacktrace if CLI succeeds (#182)
1 parent c454571 commit 2dc5358

File tree

5 files changed

+100
-34
lines changed

5 files changed

+100
-34
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"eslint-config-xo-typescript": "^0.41.1",
6262
"execa": "^5.0.0",
6363
"react": "^16.9.0",
64+
"resolve-from": "^5.0.0",
6465
"rxjs": "^6.5.3",
6566
"typescript": "~4.9.5"
6667
},

source/cli.ts

+23-10
Original file line numberDiff line numberDiff line change
@@ -44,26 +44,39 @@ const cli = meow(`
4444
},
4545
});
4646

47+
/**
48+
* Displays a message and exits, conditionally erroring.
49+
*
50+
* @param message The message to display.
51+
* @param isError Whether or not to fail on exit.
52+
*/
53+
const exit = (message: string, {isError = true}: {isError?: boolean} = {}) => {
54+
if (isError) {
55+
console.error(message);
56+
process.exit(1);
57+
} else {
58+
console.log(message);
59+
process.exit(0);
60+
}
61+
};
62+
4763
(async () => {
4864
try {
4965
const cwd = cli.input.length > 0 ? cli.input[0] : process.cwd();
5066
const {typings: typingsFile, files: testFiles, showDiff} = cli.flags;
5167

52-
const options = {cwd, typingsFile, testFiles};
53-
54-
const diagnostics = await tsd(options);
68+
const diagnostics = await tsd({cwd, typingsFile, testFiles});
5569

5670
if (diagnostics.length > 0) {
57-
throw new Error(formatter(diagnostics, showDiff));
71+
const hasErrors = diagnostics.some(diagnostic => diagnostic.severity === 'error');
72+
const formattedDiagnostics = formatter(diagnostics, showDiff);
73+
74+
exit(formattedDiagnostics, {isError: hasErrors});
5875
}
5976
} catch (error: unknown) {
6077
const potentialError = error as Error | undefined;
61-
const errorMessage = potentialError?.stack ?? potentialError?.message;
62-
63-
if (errorMessage) {
64-
console.error(`Error running tsd: ${errorMessage}`);
65-
}
78+
const errorMessage = potentialError?.stack ?? potentialError?.message ?? 'tsd unexpectedly crashed.';
6679

67-
process.exit(1);
80+
exit(`Error running tsd:\n${errorMessage}`);
6881
}
6982
})();

source/test/cli.ts

+53-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import test from 'ava';
33
import execa from 'execa';
44
import readPkgUp from 'read-pkg-up';
55
import tsd, {formatter} from '..';
6+
import {verifyCli} from './fixtures/utils';
7+
import resolveFrom from 'resolve-from';
68

79
interface ExecaError extends Error {
810
readonly exitCode: number;
@@ -15,7 +17,11 @@ test('fail if errors are found', async t => {
1517
}));
1618

1719
t.is(exitCode, 1);
18-
t.regex(stderr, /5:19[ ]{2}Argument of type number is not assignable to parameter of type string./);
20+
verifyCli(t, stderr, [
21+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
22+
'',
23+
'1 error',
24+
]);
1925
});
2026

2127
test('succeed if no errors are found', async t => {
@@ -32,7 +38,11 @@ test('provide a path', async t => {
3238
const {exitCode, stderr} = await t.throwsAsync<ExecaError>(execa('dist/cli.js', [file]));
3339

3440
t.is(exitCode, 1);
35-
t.regex(stderr, /5:19[ ]{2}Argument of type number is not assignable to parameter of type string./);
41+
verifyCli(t, stderr, [
42+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
43+
'',
44+
'1 error',
45+
]);
3646
});
3747

3848
test('cli help flag', async t => {
@@ -57,7 +67,11 @@ test('cli typings flag', async t => {
5767
}));
5868

5969
t.is(exitCode, 1);
60-
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
70+
verifyCli(t, stderr, [
71+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
72+
'',
73+
'1 error',
74+
]);
6175
};
6276

6377
await runTest('--typings');
@@ -71,7 +85,11 @@ test('cli files flag', async t => {
7185
}));
7286

7387
t.is(exitCode, 1);
74-
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
88+
verifyCli(t, stderr, [
89+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
90+
'',
91+
'1 error',
92+
]);
7593
};
7694

7795
await runTest('--files');
@@ -84,7 +102,11 @@ test('cli files flag array', async t => {
84102
}));
85103

86104
t.is(exitCode, 1);
87-
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
105+
verifyCli(t, stderr, [
106+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
107+
'',
108+
'1 error',
109+
]);
88110
});
89111

90112
test('cli typings and files flags', async t => {
@@ -94,17 +116,29 @@ test('cli typings and files flags', async t => {
94116
const {exitCode, stderr} = t.throws<ExecaError>(() => execa.commandSync(`dist/cli.js -t ${typingsFile} -f ${testFile}`));
95117

96118
t.is(exitCode, 1);
97-
t.true(stderr.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
119+
verifyCli(t, stderr, [
120+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
121+
'',
122+
'1 error',
123+
]);
98124
});
99125

100126
test('tsd logs stacktrace on failure', async t => {
101-
const {exitCode, stderr, stack} = await t.throwsAsync<ExecaError>(execa('../../../cli.js', {
127+
const {exitCode, stderr} = await t.throwsAsync<ExecaError>(execa('../../../cli.js', {
102128
cwd: path.join(__dirname, 'fixtures/empty-package-json')
103129
}));
104130

131+
const nodeModulesPath = path.resolve('node_modules');
132+
const parseJsonPath = resolveFrom.silent(`${nodeModulesPath}/read-pkg`, 'parse-json') ?? `${nodeModulesPath}/index.js`;
133+
105134
t.is(exitCode, 1);
106-
t.true(stderr.includes('Error running tsd: JSONError: Unexpected end of JSON input while parsing empty string'));
107-
t.truthy(stack);
135+
verifyCli(t, stderr, [
136+
'Error running tsd:',
137+
'JSONError: Unexpected end of JSON input while parsing empty string',
138+
`at parseJson (${parseJsonPath}:29:21)`,
139+
`at module.exports (${nodeModulesPath}/read-pkg/index.js:17:15)`,
140+
`at async module.exports (${nodeModulesPath}/read-pkg-up/index.js:14:16)`,
141+
], {startLine: 0});
108142
});
109143

110144
test('exported formatter matches cli results', async t => {
@@ -114,10 +148,18 @@ test('exported formatter matches cli results', async t => {
114148

115149
const {stderr: cliResults} = await t.throwsAsync<ExecaError>(execa('../../../cli.js', options));
116150

117-
t.true(cliResults.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
151+
verifyCli(t, cliResults, [
152+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
153+
'',
154+
'1 error',
155+
]);
118156

119157
const tsdResults = await tsd(options);
120158
const formattedResults = formatter(tsdResults);
121159

122-
t.true(formattedResults.includes('✖ 5:19 Argument of type number is not assignable to parameter of type string.'));
160+
verifyCli(t, formattedResults, [
161+
'✖ 5:19 Argument of type number is not assignable to parameter of type string.',
162+
'',
163+
'1 error',
164+
]);
123165
});

source/test/diff.ts

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {verifyWithDiff} from './fixtures/utils';
1+
import {verifyWithDiff, verifyCli} from './fixtures/utils';
22
import execa, {ExecaError} from 'execa';
33
import path from 'path';
44
import test from 'ava';
@@ -41,8 +41,7 @@ test('diff cli', async t => {
4141
const {exitCode, stderr} = await t.throwsAsync<ExecaError>(execa('dist/cli.js', [file, '--show-diff']));
4242

4343
t.is(exitCode, 1);
44-
45-
const expectedLines = [
44+
verifyCli(t, stderr, [
4645
'✖ 8:0 Parameter type { life?: number | undefined; } is declared too wide for argument type { life: number; }.',
4746
'',
4847
'- { life?: number | undefined; }',
@@ -69,13 +68,5 @@ test('diff cli', async t => {
6968
'+ This is a comment.',
7069
'',
7170
'6 errors'
72-
];
73-
74-
// NOTE: If lines are added to the output in the future startLine and endLine should be adjusted.
75-
const startLine = 2; // Skip tsd error message and file location.
76-
const endLine = startLine + expectedLines.length; // Grab diff output only and skip stack trace.
77-
78-
const receivedLines = stderr.trim().split('\n').slice(startLine, endLine).map(line => line.trim());
79-
80-
t.deepEqual(receivedLines, expectedLines);
71+
]);
8172
});

source/test/fixtures/utils.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export const verifyWithFileName = (
9494
* @param diagnostics - List of diagnostics to verify.
9595
* @param expectations - Expected diagnostics.
9696
*/
97-
export const verifyWithDiff = (
97+
export const verifyWithDiff = (
9898
t: ExecutionContext,
9999
diagnostics: Diagnostic[],
100100
expectations: ExpectationWithDiff[]
@@ -117,3 +117,22 @@ export const verifyWithFileName = (
117117

118118
t.deepEqual(diagnosticObjs, expectationObjs, 'Received diagnostics that are different from expectations!');
119119
};
120+
121+
/**
122+
* Verify a list of diagnostics reported from the CLI.
123+
*
124+
* @param t - The AVA execution context.
125+
* @param diagnostics - List of diagnostics to verify.
126+
* @param expectations - Expected diagnostics.
127+
* @param startLine - Optionally specify how many lines to skip from start.
128+
*/
129+
export const verifyCli = (
130+
t: ExecutionContext,
131+
diagnostics: string,
132+
expectedLines: string[],
133+
{startLine}: {startLine: number} = {startLine: 1} // Skip file location.
134+
) => {
135+
const receivedLines = diagnostics.trim().split('\n').slice(startLine).map(line => line.trim());
136+
137+
t.deepEqual(receivedLines, expectedLines, 'Received diagnostics that are different from expectations!');
138+
};

0 commit comments

Comments
 (0)