Skip to content

Commit 2b0e5a3

Browse files
committed
Chore: Experimenting with Lage as a replacement for turbo
1 parent 34762a7 commit 2b0e5a3

32 files changed

+333
-18
lines changed
Binary file not shown.
790 KB
Binary file not shown.
Binary file not shown.
10.5 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
6.19 MB
Binary file not shown.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ESLint } from 'eslint';
2+
3+
/** @type {ESLint} */
4+
let eslintInstance = null;
5+
6+
/** caches an ESLint instance for the worker */
7+
function getEslintInstance(target) {
8+
if (!eslintInstance) {
9+
eslintInstance = new ESLint({
10+
fix: false,
11+
cache: false,
12+
cwd: target.cwd,
13+
});
14+
}
15+
return eslintInstance;
16+
}
17+
18+
/** Workers should have a run function that gets called per package task */
19+
async function run(data) {
20+
const { target } = data;
21+
const eslint = getEslintInstance(target);
22+
23+
// You can also use "options" to pass different files pattern to lint
24+
// e.g. data.options.files; you'll need to then configure this inside
25+
// lage.config.js's pipeline
26+
const files = 'src/**/*.ts';
27+
const results = await eslint.lintFiles(files);
28+
const formatter = await eslint.loadFormatter('stylish');
29+
const resultText = formatter.format(results);
30+
31+
// Output results to stdout
32+
process.stdout.write(resultText + '\n');
33+
if (results.some((r) => r.errorCount > 0)) {
34+
// throw an error to indicate that this task has failed
35+
throw new Error(`Linting failed with errors`);
36+
}
37+
}
38+
39+
// The module export is picked up by `lage` to run inside a worker, and the
40+
// module's state is preserved from target run to target run.
41+
export default run;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@repo/lage-workers",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"engines": {
7+
"node": ">=18"
8+
},
9+
"exports": {
10+
"./eslint-worker": "./eslint-worker.js",
11+
"./tsc-worker": "./tsc-worker.js"
12+
},
13+
"dependencies": {
14+
"eslint": "^9.18.0",
15+
"typescript": "^5.7.3"
16+
}
17+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import ts from 'typescript';
2+
import path from 'node:path';
3+
import { existsSync } from 'node:fs';
4+
5+
// Save the previously run ts.program to be fed inside the next call
6+
let oldProgram;
7+
8+
let compilerHost;
9+
10+
/** this is the patch to ts.compilerHost that retains sourceFiles in a Map **/
11+
function createCompilerHost(compilerOptions) {
12+
const host = ts.createCompilerHost(compilerOptions, true);
13+
const sourceFiles = new Map();
14+
const originalGetSourceFile = host.getSourceFile;
15+
16+
// monkey patch host to cache source files
17+
host.getSourceFile = (
18+
fileName,
19+
languageVersion,
20+
onError,
21+
shouldCreateNewSourceFile,
22+
) => {
23+
if (sourceFiles.has(fileName)) {
24+
return sourceFiles.get(fileName);
25+
}
26+
27+
const sourceFile = originalGetSourceFile(
28+
fileName,
29+
languageVersion,
30+
onError,
31+
shouldCreateNewSourceFile,
32+
);
33+
34+
sourceFiles.set(fileName, sourceFile);
35+
36+
return sourceFile;
37+
};
38+
39+
return host;
40+
}
41+
42+
async function tsc(data) {
43+
const { target } = data; // Lage target data
44+
45+
const tsconfigJsonFile = path.join(target.cwd, 'tsconfig.json');
46+
47+
if (!existsSync(tsconfigJsonFile)) {
48+
console.log(`this package (${target.cwd}) has no tsconfig.json, skipping work!`);
49+
return;
50+
}
51+
52+
// Parse tsconfig
53+
const configParserHost = parseConfigHostFromCompilerHostLike(compilerHost ?? ts.sys);
54+
const parsedCommandLine = ts.getParsedCommandLineOfConfigFile(
55+
tsconfigJsonFile,
56+
{},
57+
configParserHost,
58+
);
59+
if (!parsedCommandLine) {
60+
throw new Error('Could not parse tsconfig.json');
61+
}
62+
const compilerOptions = parsedCommandLine.options;
63+
64+
// Creating compilation host program
65+
compilerHost = compilerHost ?? createCompilerHost(compilerOptions);
66+
67+
// The re-use of oldProgram is a trick we all learned from gulp-typescript, credit to ivogabe
68+
// @see https://github.com/ivogabe/gulp-typescript
69+
const program = ts.createProgram(
70+
parsedCommandLine.fileNames,
71+
compilerOptions,
72+
compilerHost,
73+
oldProgram,
74+
);
75+
76+
oldProgram = program;
77+
78+
const errors = {
79+
semantics: program.getSemanticDiagnostics(),
80+
declaration: program.getDeclarationDiagnostics(),
81+
syntactic: program.getSyntacticDiagnostics(),
82+
global: program.getGlobalDiagnostics(),
83+
};
84+
85+
const allErrors = [];
86+
87+
try {
88+
program.emit();
89+
} catch (error) {
90+
console.log(error.messageText);
91+
throw new Error('Encountered errors while emitting');
92+
}
93+
94+
let hasErrors = false;
95+
96+
for (const kind of Object.keys(errors)) {
97+
for (const diagnostics of errors[kind]) {
98+
hasErrors = true;
99+
allErrors.push(diagnostics);
100+
}
101+
}
102+
103+
if (hasErrors) {
104+
console.log(ts.formatDiagnosticsWithColorAndContext(allErrors, compilerHost));
105+
throw new Error('Failed to compile');
106+
} else {
107+
console.log('Compiled successfully\n');
108+
return;
109+
}
110+
}
111+
112+
function parseConfigHostFromCompilerHostLike(host) {
113+
return {
114+
fileExists: (f) => host.fileExists(f),
115+
readDirectory(root, extensions, excludes, includes, depth) {
116+
return host.readDirectory(root, extensions, excludes, includes, depth);
117+
},
118+
readFile: (f) => host.readFile(f),
119+
useCaseSensitiveFileNames: host.useCaseSensitiveFileNames,
120+
getCurrentDirectory: host.getCurrentDirectory,
121+
onUnRecoverableConfigFileDiagnostic: (d) => {
122+
throw new Error(ts.flattenDiagnosticMessageText(d.messageText, '\n'));
123+
},
124+
trace: host.trace,
125+
};
126+
}
127+
128+
export default tsc;

0 commit comments

Comments
 (0)