Skip to content

Commit ddb9e50

Browse files
committed
🔧 chore: Sleep on it, then fix code
1 parent f15d8e5 commit ddb9e50

11 files changed

+171
-141
lines changed

‎TscExecutionResult.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,70 @@
1+
/**
2+
This structure encapsulates the output of `tsc` in a single run. Note that this structure is for the results regardless of whether `tsc` succeeded or failed. So, you may want to check `tscExitCode` to see if `tsc` succeeded (exit code 0) or failed (non-zero), depending what you want to do with the results. Although tsc emits diagnostics sometimes even when it fails, the results may be weird/incomplete in that case.
3+
*/
14
export interface TscExecutionResult
25
{
3-
code: number;
4-
checkTime: number;
6+
/**
7+
The exit code of `tsc`.
8+
*/
9+
tscExitCode: number;
10+
11+
/**
12+
Elapsed time in milliseconds, as perceived by the runtime executing `tsc` as a subprocess (e.g., Deno, Bun, Node, etc)
13+
*/
514
elapsedMs: number;
15+
16+
/**
17+
The standard output of `tsc`, as a string.
18+
*/
619
stdout: string;
20+
21+
/**
22+
The standard error of `tsc`, as a string.
23+
*/
724
stderr: string;
25+
26+
/**
27+
A string representation of the command line that was run to invoke `tsc`. E.g.:
28+
```
29+
'npx tsc --strict true --target esnext /Volumes/HOHOHO/my-project/libs/ts/core/foo-auth/tsconfig.lib.json.ts --allowImportingTsExtensions --noEmit --extendedDiagnostics'
30+
*/
831
tscCommand: string;
32+
33+
/**
34+
A best-effort attempt to parse the diagnostics, if any, from `tsc`. May not be present if `tsc` emitted no diagnostics or if an error occurred while parsing the diagnostics. Example (as of 2025-02-18 and `tsc Version 5.8.0-dev.20250218`):
35+
36+
```json
37+
diagnostics: {
38+
Files: "858",
39+
"Lines of Library": "38846",
40+
"Lines of Definitions": "75228",
41+
"Lines of TypeScript": "15172",
42+
"Lines of JavaScript": "0",
43+
"Lines of JSON": "0",
44+
"Lines of Other": "0",
45+
Identifiers: "115591",
46+
Symbols: "104179",
47+
Types: "20856",
48+
Instantiations: "12276",
49+
"Memory used": "165137K",
50+
"Assignability cache size": "7573",
51+
"Identity cache size": "69",
52+
"Subtype cache size": "274",
53+
"Strict subtype cache size": "135",
54+
"I/O Read time": "0.14s",
55+
"Parse time": "0.19s",
56+
"ResolveModule time": "0.40s",
57+
"ResolveLibrary time": "0.01s",
58+
"ResolveTypeReference time": "0.00s",
59+
"Program time": "0.77s",
60+
"Bind time": "0.10s",
61+
"Check time": "0.29s",
62+
"transformTime time": "0.23s",
63+
"printTime time": "0.00s",
64+
"Emit time": "0.00s",
65+
"Total time": "1.17s"
66+
}
67+
```
68+
*/
69+
diagnostics: Record<string, string>;
970
}

‎deno.jsonc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"name": "@axhxrx/tsc-time",
33
"version": "0.0.1",
44
"exports": "./mod.ts",
5-
"license": "MIT",
6-
}
5+
"license": "MIT"
6+
}

‎dprint.jsonc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
22
"extends": "https://jsr.io/@axhxrx/dprint-config/0.0.3/dprint.jsonc"
3-
}
3+
}
File renamed without changes.

‎isDenoRuntime.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
export function isDenoRuntime(): boolean {
1+
export function isDenoRuntime(): boolean
2+
{
23
return typeof Deno !== 'undefined' && typeof Deno?.Command === 'function';
34
}

‎main.ts

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,7 @@
11
// Deno is our jam, but because we want this to run under Bun and Node also, lets use modly old `process` which they all support:
22
import process from 'node:process';
33

4-
import { isDenoRuntime } from './isDenoRuntime.ts';
5-
import { parseDiagnostics } from './parseDiagnostics.ts';
6-
import { printErrorInfo } from './printErrorInfo.ts';
7-
import { runTscWithDeno } from './runTscWithDeno.ts';
8-
import { runTscWithNode } from './runTscWithNode.ts';
9-
import type { TscExecutionResult } from './TscExecutionResult.ts';
10-
11-
type TscExecutionResultWithDiagnostics = TscExecutionResult & { diagnostics: Record<string, unknown> };
12-
13-
export async function runTsc(file: string): Promise<TscExecutionResultWithDiagnostics>
14-
{
15-
let result: TscExecutionResult;
16-
17-
if (isDenoRuntime())
18-
{
19-
result = await runTscWithDeno(file);
20-
}
21-
else
22-
{
23-
// This works for Bun, too, so no need to check for Bun.
24-
result = await runTscWithNode(file);
25-
}
26-
27-
if (result.code !== 0)
28-
{
29-
printErrorInfo(result);
30-
process.exit(1);
31-
}
32-
33-
if (!result.stdout)
34-
{
35-
printErrorInfo(result, 'tsc produced no output');
36-
process.exit(11);
37-
}
38-
39-
// Parse "Check time:"
40-
const checkTimeLine = result.stdout
41-
.split('\n')
42-
.find((line) => line.includes('Check time'));
43-
44-
if (!checkTimeLine)
45-
{
46-
printErrorInfo(result, 'FATAL: wtf: cannot parse tsc output, probably a bug in this tool then... 🙄');
47-
process.exit(21);
48-
}
49-
50-
const match = checkTimeLine.match(/([\d.]+)s/);
51-
if (!match)
52-
{
53-
printErrorInfo(result,
54-
'FATAL: wtf: cannot parse checkTime from line: ' + checkTimeLine + '\n\nVery likely a bug in this tool... 🙄 ');
55-
process.exit(31);
56-
}
57-
58-
const checkTime = parseFloat(match[1]);
59-
60-
result = {
61-
...result,
62-
checkTime,
63-
};
64-
65-
const diagnostics = parseDiagnostics(result.stdout);
66-
67-
return {
68-
...result,
69-
diagnostics,
70-
};
71-
}
4+
import { runTsc } from './runTsc.ts';
725

736
export async function main()
747
{

‎parseDiagnostics.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/**
2-
Parse the tsc output to get the diagnostics as JSON.
2+
Parse the `tsc` output to get the diagnostics as JSON.
33
*/
4-
export function parseDiagnostics(output: string): Record<string, unknown>
4+
export function parseDiagnostics(output: string): Record<string, string>
55
{
66
const lines = output.split('\n');
7-
const diagnostics: Record<string, unknown> = {};
7+
const diagnostics: Record<string, string> = {};
88
let key: string | null = null;
99

1010
for (const line of lines)

‎printErrorInfo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import type { TscExecutionResult } from './TscExecutionResult.ts';
22

33
export function printErrorInfo(result: TscExecutionResult, additionalInfo?: string): void
44
{
5-
const isTscError = result.code !== 0;
5+
const isTscError = result.tscExitCode !== 0;
66

77
if (isTscError)
88
{
9-
console.error(`tsc exited with exit code ${result.code}: ${result.stderr}`);
9+
console.error(`tsc exited with exit code ${result.tscExitCode}: ${result.stderr}`);
1010
}
1111
else
1212
{

‎runTsc.ts

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,82 @@
1-
import { isBunRuntime } from './isBunRuntime.ts';
1+
// Deno is our jam, but because we want this to run under Bun and Node also, lets use modly old `process` which they all support:
2+
import process from 'node:process';
3+
24
import { isDenoRuntime } from './isDenoRuntime.ts';
3-
import { isNodeRuntime } from './isNodeRuntime.ts';
4-
import { TscExecutionResultWithDiagnostics } from './main.ts';
5-
import { parseDiagnostics } from './parseDiagnostics.ts';
65
import { printErrorInfo } from './printErrorInfo.ts';
7-
import { runTscWithBun } from './runTscWithBun.ts';
86
import { runTscWithDeno } from './runTscWithDeno.ts';
7+
import { runTscWithNode } from './runTscWithNode.ts';
98
import type { TscExecutionResult } from './TscExecutionResult.ts';
109

11-
export async function runTsc(file: string): Promise<TscExecutionResultWithDiagnostics>
10+
/**
11+
Run `tsc` on the given file, and return the result as a `TscExecutionResult`. In most cases, the result will be returned even if `tsc` fails. The `tsc` command is run as a subprocess and its output is parsed.
12+
13+
This should work under Deno, Node, or Bun. Other runtimes not tested, but would only work if they can run subprocesses in a Node-compatible way.
14+
15+
Example result:
16+
```ts
17+
{
18+
tscExitCode: 0,
19+
elapsedMs: 1576.915708,
20+
stdout: "Files: <blah blah blah omitted>...",
21+
stderr: "",
22+
tscCommand: "npx tsc --strict true --target esnext --project /Volumes/SORACOM/ucm-main/libs/ts/core/data-access-auth/tsconfig.lib.json --allowImportingTsExtensions --noEmit --extendedDiagnostics",
23+
diagnostics: {
24+
Files: "858",
25+
"Lines of Library": "39520",
26+
"Lines of Definitions": "75228",
27+
"Lines of TypeScript": "15172",
28+
"Lines of JavaScript": "0",
29+
"Lines of JSON": "0",
30+
"Lines of Other": "0",
31+
Identifiers: "116052",
32+
Symbols: "104457",
33+
Types: "20866",
34+
Instantiations: "12276",
35+
"Memory used": "168666K",
36+
"Assignability cache size": "7573",
37+
"Identity cache size": "69",
38+
"Subtype cache size": "274",
39+
"Strict subtype cache size": "135",
40+
"I/O Read time": "0.18s",
41+
"Parse time": "0.20s",
42+
"ResolveModule time": "0.04s",
43+
"ResolveLibrary time": "0.01s",
44+
"ResolveTypeReference time": "0.00s",
45+
"Program time": "0.48s",
46+
"Bind time": "0.11s",
47+
"Check time": "0.32s",
48+
"transformTime time": "0.19s",
49+
"printTime time": "0.00s",
50+
"Emit time": "0.00s",
51+
"Total time": "0.91s",
52+
},
53+
}
54+
```
55+
*/
56+
export async function runTsc(file: string): Promise<TscExecutionResult>
1257
{
1358
let result: TscExecutionResult;
1459

1560
if (isDenoRuntime())
1661
{
1762
result = await runTscWithDeno(file);
1863
}
19-
else if (isBunRuntime())
20-
{
21-
result = await runTscWithBun(file);
22-
}
23-
else if (isNodeRuntime())
24-
{
25-
result = await runTscWithNode(file);
26-
}
2764
else
2865
{
29-
throw new Error('Unsupported runtime. Not Node, Deno, or Bun!');
66+
// This works for Bun, too, so no need to check for Bun.
67+
result = await runTscWithNode(file);
3068
}
3169

32-
if (result.code !== 0)
70+
if (result.tscExitCode !== 0)
3371
{
34-
console.error('The tsc command failed:');
35-
console.log(result.stdout);
36-
throw new Error(`tsc exited with code ${result.code}: ${result.stderr}`);
72+
printErrorInfo(result);
73+
process.exit(1);
3774
}
3875

3976
if (!result.stdout)
4077
{
41-
throw new Error('tsc produced no output');
78+
printErrorInfo(result, 'tsc produced no output');
79+
process.exit(11);
4280
}
4381

4482
// Parse "Check time:"
@@ -49,28 +87,18 @@ export async function runTsc(file: string): Promise<TscExecutionResultWithDiagno
4987
if (!checkTimeLine)
5088
{
5189
printErrorInfo(result, 'FATAL: wtf: cannot parse tsc output, probably a bug in this tool then... 🙄');
52-
Deno.exit(1);
90+
process.exit(21);
5391
}
5492

5593
const match = checkTimeLine.match(/([\d.]+)s/);
5694
if (!match)
5795
{
58-
printErrorInfo(result,
59-
'FATAL: wtf: cannot parse checkTime from line: ' + checkTimeLine + '\n\nVery likely a bug in this tool... 🙄 ');
60-
Deno.exit(1);
96+
printErrorInfo(
97+
result,
98+
'FATAL: wtf: cannot parse checkTime from line: ' + checkTimeLine + '\n\nVery likely a bug in this tool... 🙄 ',
99+
);
100+
process.exit(31);
61101
}
62102

63-
const checkTime = parseFloat(match[1]);
64-
65-
result = {
66-
...result,
67-
checkTime,
68-
};
69-
70-
const diagnostics = parseDiagnostics(result.stdout);
71-
72-
return {
73-
...result,
74-
diagnostics,
75-
};
103+
return result;
76104
}

‎runTscWithDeno.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1-
import { getTscCommandComponents } from './geTscCommandComponents.ts';
2-
import { TscExecutionResult } from './TscExecutionResult.ts';
1+
import { getTscCommandComponents } from './getTscCommandComponents.ts';
2+
import { parseDiagnostics } from './parseDiagnostics.ts';
3+
import type { TscExecutionResult } from './TscExecutionResult.ts';
34

5+
/**
6+
The Deno flavor of `runTsc`.
7+
*/
48
export async function runTscWithDeno(file: string): Promise<TscExecutionResult>
59
{
610
const start = performance.now();
711

8-
let code = -1;
9-
let rawStdout = new Uint8Array();
10-
let rawStderr = new Uint8Array();
12+
let tscExitCode = -1;
1113
const args = getTscCommandComponents(file, false);
1214
const command = new Deno.Command('npx', { args });
1315

1416
const commandResult = await command.output();
15-
code = commandResult.code;
16-
rawStdout = commandResult.stdout;
17-
rawStderr = commandResult.stderr;
17+
tscExitCode = commandResult.code;
18+
const stdout = new TextDecoder().decode(commandResult.stdout);
19+
const stderr = new TextDecoder().decode(commandResult.stderr);
20+
const diagnostics = parseDiagnostics(stdout);
1821

1922
const result: TscExecutionResult = {
20-
code,
21-
checkTime: -1,
23+
tscExitCode,
2224
elapsedMs: performance.now() - start,
23-
stdout: new TextDecoder().decode(rawStdout),
24-
stderr: new TextDecoder().decode(rawStderr),
25+
stdout,
26+
stderr,
2527
tscCommand: 'npx ' + args.join(' '),
28+
diagnostics,
2629
};
2730

2831
return result;

0 commit comments

Comments
 (0)