-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtypecheck.ts
219 lines (189 loc) · 8.74 KB
/
typecheck.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/* eslint-disable max-classes-per-file */
import buildModule from 'next/dist/build/index.js';
import * as Log from 'next/dist/build/output/log';
//import { writeFileSync } from 'node:fs';
import fs, { type FileHandle } from 'node:fs/promises';
let interval: NodeJS.Timer | null = null;
const writeLogToFile = process.env.CI_WRITE_LOG_TO_FILE === 'true';
let logFile: FileHandle | null = null;
// eslint-disable-next-line no-empty-function
let cleanupFunction = async () => {};
if (writeLogToFile) {
logFile = await fs.open('typecheck.log', 'w');
const oldSTDOUTWrite = process.stdout.write;
// @ts-ignore
process.stdout.write = function write(this: ThisParameterType<typeof oldSTDOUTWrite>, ...args: Parameters<typeof oldSTDOUTWrite>) {
logFile!.write(args[0].toString());
oldSTDOUTWrite.call(this, ...args);
};
const oldSTDERRWrite = process.stderr.write;
// @ts-ignore
process.stderr.write = function write(this: ThisParameterType<typeof oldSTDERRWrite>, ...args: Parameters<typeof oldSTDERRWrite>) {
logFile!.write(args[0].toString());
oldSTDERRWrite.call(this, ...args);
};
process.setUncaughtExceptionCaptureCallback(async (err) => {
Log.error(err);
await cleanupFunction();
oldProcessExit(1);
});
process.on('uncaughtException', async (err) => {
Log.error(err);
await cleanupFunction();
oldProcessExit(1);
});
process.on('unhandledRejection', async (err) => {
Log.error(err);
await cleanupFunction();
oldProcessExit(1);
});
cleanupFunction = async () => {
if (interval) clearInterval(interval);
process.stdout.write = oldSTDOUTWrite;
process.stderr.write = oldSTDERRWrite;
console.log = reallyOldLog;
console.log('\n'.repeat(10));
console.log('========== END LOG ==========');
console.log('Flushing the above log to file...');
await logFile!.close();
console.log('Flushed the log above the line to file.');
};
process.on('beforeExit', async () => {
await cleanupFunction();
});
}
// WARNING: This file is one...
/***
* spell-checker: disable
*
*
*
* BBBBBBBBBBBBBBBBB IIIIIIIIIIIIIIII GGGGGGGGGGGGG
* B::::::::::::::::B I::::::::::::::I GGG::::::::::::G
* B::::::BBBBBB:::::B I::::::::::::::I GG:::::::::::::::G
* BB:::::B B:::::B IIIII::::::IIIII G:::::GGGGGGGG::::G
* B::::B B:::::B I::::I G:::::G GGGGGG
* B::::B B:::::B I::::I G:::::G
* B::::BBBBBB:::::B I::::I G:::::G
* B:::::::::::::BB I::::I G:::::G GGGGGGGGGG
* B::::BBBBBB:::::B I::::I G:::::G G::::::::G
* B::::B B:::::B I::::I G:::::G GGGGG::::G
* B::::B B:::::B I::::I G:::::G G::::G
* B::::B B:::::B I::::I G:::::G G::::G
* BB:::::BBBBBB::::::B IIIII::::::IIIII G:::::GGGGGGGG::::G
* B:::::::::::::::::B I::::::::::::::I GG:::::::::::::::G
* B::::::::::::::::B I::::::::::::::I GGG::::::GGG:::G
* BBBBBBBBBBBBBBBBB IIIIIIIIIIIIIIII GGGGGG GGGG
*
*
*
* HHHHHHH HHHHHHH AAA CCCCCCCCCC KKKKKKK KKKKKKK
* H:::::H H:::::H A:::A CCC:::::::::C K:::::K K:::::K
* H:::::H H:::::H A:::::A CC::::::::::::C K:::::K K::::::K
* H:::::H H:::::H A:::::::A C:::::CCCCCCCCCC K:::::K K::::::K
* H:::::H H:::::H A:::::::::A C:::::C K:::::K K:::::K
* H:::::H H:::::H A:::::A:::::A C:::::C K:::::K K:::::K
* H::::::HHHHH::::::H A:::::A A:::::A C:::::C K::::::KK:::::K
* H:::::::::::::::::H A:::::A A:::::A C:::::C K::::::::::::K
* H:::::::::::::::::H A:::::A A:::::A C:::::C K::::::::::::K
* H::::::HHHHH::::::H A:::::AAAAAAAAA:::::A C:::::C K::::::KK:::::K
* H:::::H H:::::H A:::::::::::::::::::::A C:::::C K:::::K K:::::K
* H:::::H H:::::H A:::::AAAAAAAAAAAAA:::::A C:::::C K:::::K K:::::K
* H:::::H H:::::H A:::::A A:::::A C:::::CCCCCCCCCC K:::::K K::::::K
* H:::::H H:::::H A:::::A A:::::A CC::::::::::::C K:::::K K::::::K
* H:::::H H:::::H A:::::A A:::::A CCC:::::::::C K:::::K K:::::K
* HHHHHHH HHHHHHH AAAAAAA AAAAAAA CCCCCCCCCC KKKKKKK KKKKKKK
*
*
*
* spell-checker: enable
*/
// Do not be surprised if this breaks in the future!
//
// The current hack: Force the environment to not use TTY, and then check to see if Next.js logs the message we're looking for.
//
// When this script was first devised with TTY mode in mind,
// console.log() got reset every time a log "spinner" (cycling ellipsis) finished, so we used that to detect when something finished.
// Was a great way to optimize our checks, but it failed once TTY mode was disabled (in CI builds, which was what this script was meant for).
//
/** This "error" is an error only in name. It's really an escape hatch to exit out of the build and give control back to our script. */
class PleaseExitTypecheckNowError extends Error {
constructor() {
super('This error is thrown to indicate that the typechecking process has finished and to exit the build process. It should only be caught by the typechecking script.');
}
}
/** This error is a way for us to be able to clean up when process.exit() is called while still exiting the current context. */
class WorkerExitedError extends Error {
constructor(code: number) {
super(`A worker process exited (or tried to exit) with code ${code}!`);
}
}
process.stdout.isTTY = false;
const targetLogMessage = ` ${Log.prefixes.info} Collecting page data ...`;
/** The log since the last time patchLog() ran successfully */
let currentLog = '';
const isOurLog = Symbol('isOurLog');
const reallyOldLog = console.log;
function patchLog(isInitialRun = false) {
if ((console.log as any)[isOurLog]) return;
const oldLog = console.log;
//writeFileSync('typecheck.log.json', JSON.stringify(logsSoFar));
currentLog = '';
console.log = function log(...args: any[]) {
if (args[0] === targetLogMessage) {
Log.info('Typechecking finished without errors! Throwing an escape hatch, nominal "error" up the stack...');
throw new PleaseExitTypecheckNowError();
}
oldLog(...args);
};
(console.log as any)[isOurLog] = true;
if (isInitialRun) Log.event('Typecheck script successfully monkey-patched console.log! To detect when typechecking ends, we look for the next log message, ""');
else Log.info('Reestablished console.log monkey patch.');
}
const oldSTDOUTWrite = process.stdout.write;
// @ts-ignore
process.stdout.write = function write(this: ThisParameterType<typeof oldSTDOUTWrite>, ...args: Parameters<typeof oldSTDOUTWrite>) {
currentLog += args[0].toString();
oldSTDOUTWrite.call(this, ...args);
};
patchLog(true);
interval = setInterval(patchLog, 50);
const oldProcessExit = process.exit;
process.exit = function exit(code: number) {
throw new WorkerExitedError(code);
};
process.on('beforeExit', () => {
process.stdout.write = oldSTDOUTWrite;
console.log = reallyOldLog;
console.log('\n'.repeat(10));
console.log('Script never did its usual "premature exit" thing after typechecking finished! This indicates that the script is broken!');
console.log('\n'.repeat(2));
oldProcessExit(1);
});
const build = buildModule as unknown as typeof import('next/dist/build/index.js'); // Next.js' types are screwed up here
build.default(
//dir: string,
process.cwd(),
//reactProductionProfiling = false,
false,
//debugOutput = false,
false,
//runLint = true,
false,
//noMangling = false,
true,
//appDirOnly = false,
false,
//turboNextBuild = false,
false,
//experimentalBuildMode: 'default' | 'compile' | 'generate',
'default',
//traceUploadUrl: string | undefined})
undefined
).catch(async (err: Error) => {
const isBenign =err instanceof PleaseExitTypecheckNowError;
if (isBenign) Log.event('Typechecking finished without errors! Exiting...');
else Log.error(err);
await cleanupFunction();
oldProcessExit(isBenign ? 0 : 1);
});