@lensmcp/cluster 1.17.1 → 1.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +8 -1
- package/package.json +3 -3
- package/tsgo-check-plugin.d.ts +14 -0
- package/tsgo-check-plugin.d.ts.map +1 -1
- package/tsgo-check-plugin.js +47 -1
- package/typecheck-bus.d.ts +82 -0
- package/typecheck-bus.d.ts.map +1 -0
- package/typecheck-bus.js +256 -0
package/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { type NodeExternalsConfig, type ScopePatterns, buildScopePatterns } from './build-scope-patterns';
|
|
2
2
|
export { createDevWebpackConfig, type DevWebpackOptions } from './create-webpack-dev';
|
|
3
3
|
export { createProdWebpackConfig, type ProdWebpackOptions } from './create-webpack-prod';
|
|
4
|
+
export { appendTypecheckEvents, buildTypecheckRunEvents, parseTscDiagnosticLine, parseTscOutput, publishTypecheckRun, resolveEventFile, type TscDiagnostic, type TypecheckBusEvent, } from './typecheck-bus';
|
|
4
5
|
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EAAE,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,uBAAuB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,aAAa,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC1G,OAAO,EAAE,sBAAsB,EAAE,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,uBAAuB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC"}
|
package/index.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createProdWebpackConfig = exports.createDevWebpackConfig = exports.buildScopePatterns = void 0;
|
|
3
|
+
exports.resolveEventFile = exports.publishTypecheckRun = exports.parseTscOutput = exports.parseTscDiagnosticLine = exports.buildTypecheckRunEvents = exports.appendTypecheckEvents = exports.createProdWebpackConfig = exports.createDevWebpackConfig = exports.buildScopePatterns = void 0;
|
|
4
4
|
var build_scope_patterns_1 = require("./build-scope-patterns");
|
|
5
5
|
Object.defineProperty(exports, "buildScopePatterns", { enumerable: true, get: function () { return build_scope_patterns_1.buildScopePatterns; } });
|
|
6
6
|
var create_webpack_dev_1 = require("./create-webpack-dev");
|
|
7
7
|
Object.defineProperty(exports, "createDevWebpackConfig", { enumerable: true, get: function () { return create_webpack_dev_1.createDevWebpackConfig; } });
|
|
8
8
|
var create_webpack_prod_1 = require("./create-webpack-prod");
|
|
9
9
|
Object.defineProperty(exports, "createProdWebpackConfig", { enumerable: true, get: function () { return create_webpack_prod_1.createProdWebpackConfig; } });
|
|
10
|
+
var typecheck_bus_1 = require("./typecheck-bus");
|
|
11
|
+
Object.defineProperty(exports, "appendTypecheckEvents", { enumerable: true, get: function () { return typecheck_bus_1.appendTypecheckEvents; } });
|
|
12
|
+
Object.defineProperty(exports, "buildTypecheckRunEvents", { enumerable: true, get: function () { return typecheck_bus_1.buildTypecheckRunEvents; } });
|
|
13
|
+
Object.defineProperty(exports, "parseTscDiagnosticLine", { enumerable: true, get: function () { return typecheck_bus_1.parseTscDiagnosticLine; } });
|
|
14
|
+
Object.defineProperty(exports, "parseTscOutput", { enumerable: true, get: function () { return typecheck_bus_1.parseTscOutput; } });
|
|
15
|
+
Object.defineProperty(exports, "publishTypecheckRun", { enumerable: true, get: function () { return typecheck_bus_1.publishTypecheckRun; } });
|
|
16
|
+
Object.defineProperty(exports, "resolveEventFile", { enumerable: true, get: function () { return typecheck_bus_1.resolveEventFile; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.2",
|
|
4
4
|
"description": "Run your Nx workspace as a local production cluster: pods on unix sockets with true HMR, one https gateway with per-project domains, scale-from-zero, autoscale, idle-kill, local-CA TLS — observed by the LensMCP lens.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"types": "./index.d.ts",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@lensmcp/node-instrumentation": "1.17.
|
|
69
|
-
"@lensmcp/nx-plugin": "1.17.
|
|
68
|
+
"@lensmcp/node-instrumentation": "1.17.2",
|
|
69
|
+
"@lensmcp/nx-plugin": "1.17.2",
|
|
70
70
|
"fork-ts-checker-webpack-plugin": "^9.0.0",
|
|
71
71
|
"glob": "^11.0.0",
|
|
72
72
|
"http-proxy": "^1.18.0",
|
package/tsgo-check-plugin.d.ts
CHANGED
|
@@ -35,6 +35,14 @@ export interface TsgoCheckPluginOptions {
|
|
|
35
35
|
binPath?: string;
|
|
36
36
|
/** Injectable for tests. */
|
|
37
37
|
spawnImpl?: typeof spawn;
|
|
38
|
+
/**
|
|
39
|
+
* Override the lens bus file. Defaults to `LENSMCP_EVENT_FILE` (which the
|
|
40
|
+
* gateway already injects into this process) or
|
|
41
|
+
* `<workspaceRoot>/.lensmcp/events.jsonl`. Injectable for tests.
|
|
42
|
+
*/
|
|
43
|
+
eventFile?: string;
|
|
44
|
+
/** Set false to skip publishing to the lens bus (console output is unchanged). */
|
|
45
|
+
publishEvents?: boolean;
|
|
38
46
|
}
|
|
39
47
|
/** The workspace's tsgo binary, or null when not installed (caller falls back to fork-ts-checker). */
|
|
40
48
|
export declare function resolveTsgoBin(workspaceRoot: string, override?: string): string | null;
|
|
@@ -56,6 +64,12 @@ export declare class TsgoCheckPlugin {
|
|
|
56
64
|
apply(compiler: any): void;
|
|
57
65
|
/** Debounced: rapid successive rebuilds collapse into one check; an in-flight check is superseded. */
|
|
58
66
|
schedule(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Publish one completed check onto the lens bus so `typecheck://*` reflects
|
|
69
|
+
* it. Strictly passive + fully swallowed: the console output above is the
|
|
70
|
+
* contract webpack/the gateway rely on, this is pure observation on top.
|
|
71
|
+
*/
|
|
72
|
+
private publish;
|
|
59
73
|
private run;
|
|
60
74
|
}
|
|
61
75
|
//# sourceMappingURL=tsgo-check-plugin.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tsgo-check-plugin.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/tsgo-check-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"tsgo-check-plugin.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/tsgo-check-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAK9D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,sGAAsG;AACtG,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGtF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CA0BlG;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,QAAQ,CAA6B;IAC7C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,QAAQ,CAAS;gBAEb,IAAI,EAAE,sBAAsB;IAMxC,KAAK,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAO1B,sGAAsG;IACtG,QAAQ,IAAI,IAAI;IAOhB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAkBf,OAAO,CAAC,GAAG;CA4DZ"}
|
package/tsgo-check-plugin.js
CHANGED
|
@@ -33,6 +33,7 @@ const tslib_1 = require("tslib");
|
|
|
33
33
|
const node_child_process_1 = require("node:child_process");
|
|
34
34
|
const fs = tslib_1.__importStar(require("node:fs"));
|
|
35
35
|
const path = tslib_1.__importStar(require("node:path"));
|
|
36
|
+
const typecheck_bus_1 = require("./typecheck-bus");
|
|
36
37
|
/** The workspace's tsgo binary, or null when not installed (caller falls back to fork-ts-checker). */
|
|
37
38
|
function resolveTsgoBin(workspaceRoot, override) {
|
|
38
39
|
const candidate = override || path.join(workspaceRoot, 'node_modules', '.bin', 'tsgo');
|
|
@@ -90,12 +91,41 @@ class TsgoCheckPlugin {
|
|
|
90
91
|
this.timer = setTimeout(() => this.run(), this.opts.debounceMs ?? 500);
|
|
91
92
|
this.timer.unref?.();
|
|
92
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Publish one completed check onto the lens bus so `typecheck://*` reflects
|
|
96
|
+
* it. Strictly passive + fully swallowed: the console output above is the
|
|
97
|
+
* contract webpack/the gateway rely on, this is pure observation on top.
|
|
98
|
+
*/
|
|
99
|
+
publish(args) {
|
|
100
|
+
if (this.opts.publishEvents === false)
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
(0, typecheck_bus_1.publishTypecheckRun)({
|
|
104
|
+
workspaceRoot: this.opts.workspaceRoot,
|
|
105
|
+
project: this.opts.appName,
|
|
106
|
+
tool: 'tsgo',
|
|
107
|
+
tsConfig: this.opts.tsConfig,
|
|
108
|
+
output: args.output,
|
|
109
|
+
durationMs: args.durationMs,
|
|
110
|
+
...(args.toolError ? { toolError: args.toolError } : {}),
|
|
111
|
+
...(this.opts.eventFile ? { eventFile: this.opts.eventFile } : {}),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
/* never let instrumentation disturb a build */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
93
118
|
run() {
|
|
94
119
|
const bin = resolveTsgoBin(this.opts.workspaceRoot, this.opts.binPath);
|
|
95
120
|
if (!bin) {
|
|
96
121
|
// The binary vanished after config time (e.g. a reinstall mid-serve) — warn once, go quiet.
|
|
97
122
|
this.disabled = true;
|
|
98
123
|
console.warn(`[tsgo] ${this.opts.appName}: tsgo binary not found — type checking disabled for this session`);
|
|
124
|
+
// A checker that stopped checking must SAY so: without this the resource
|
|
125
|
+
// would keep serving the last (possibly clean) verdict forever, which is
|
|
126
|
+
// exactly the "looks healthy, nobody is watching" state this work exists
|
|
127
|
+
// to make impossible.
|
|
128
|
+
this.publish({ output: '', durationMs: 0, toolError: `tsgo binary not found — type checking disabled` });
|
|
99
129
|
return;
|
|
100
130
|
}
|
|
101
131
|
if (this.inflight) {
|
|
@@ -117,15 +147,20 @@ class TsgoCheckPlugin {
|
|
|
117
147
|
if (this.inflight === child)
|
|
118
148
|
this.inflight = null;
|
|
119
149
|
console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${bin}`);
|
|
150
|
+
// A spawn failure is NOT a clean check — report it as a tool error so the
|
|
151
|
+
// resource says "the checker died", never "your types are fine".
|
|
152
|
+
this.publish({ output: '', durationMs: Date.now() - startedAt, toolError: `failed to spawn ${bin}` });
|
|
120
153
|
});
|
|
121
154
|
child.on('close', (code, signal) => {
|
|
122
155
|
if (this.inflight === child)
|
|
123
156
|
this.inflight = null;
|
|
124
157
|
if (signal)
|
|
125
158
|
return; // superseded — a newer check owns the verdict
|
|
126
|
-
const
|
|
159
|
+
const durationMs = Date.now() - startedAt;
|
|
160
|
+
const secs = (durationMs / 1000).toFixed(1);
|
|
127
161
|
if (code === 0) {
|
|
128
162
|
console.log(`[tsgo] ${this.opts.appName}: types clean (${secs}s)`);
|
|
163
|
+
this.publish({ output: '', durationMs });
|
|
129
164
|
return;
|
|
130
165
|
}
|
|
131
166
|
// Print the raw `file.ts(1,2): error TSxxxx:` lines — the gateway's `error TS\d+` sniff
|
|
@@ -134,7 +169,18 @@ class TsgoCheckPlugin {
|
|
|
134
169
|
process.stdout.write(out.endsWith('\n') ? out : `${out}\n`);
|
|
135
170
|
const count = (out.match(/error TS\d+/g) ?? []).length;
|
|
136
171
|
console.log(`[tsgo] ${this.opts.appName}: ${count} type error${count === 1 ? '' : 's'} (${secs}s)`);
|
|
172
|
+
// A non-zero exit with NO parseable diagnostic is the checker itself
|
|
173
|
+
// failing (bad shim tsconfig, OOM) — `publishTypecheckRun` would
|
|
174
|
+
// otherwise read "0 errors" and report CLEAN on a failed run.
|
|
175
|
+
this.publish({
|
|
176
|
+
output: out,
|
|
177
|
+
durationMs,
|
|
178
|
+
...(count === 0 ? { toolError: `tsgo exited ${code} with no diagnostics: ${firstLine(out)}` } : {}),
|
|
179
|
+
});
|
|
137
180
|
});
|
|
138
181
|
}
|
|
139
182
|
}
|
|
140
183
|
exports.TsgoCheckPlugin = TsgoCheckPlugin;
|
|
184
|
+
function firstLine(s) {
|
|
185
|
+
return s.trim().split('\n')[0]?.slice(0, 300) ?? '';
|
|
186
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** One parsed compiler diagnostic. MIRROR of session's `TscDiagnostic`. */
|
|
2
|
+
export interface TscDiagnostic {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
column: number;
|
|
6
|
+
severity: 'error' | 'warning';
|
|
7
|
+
code: string;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function stripAnsi(s: string): string;
|
|
11
|
+
/** MIRROR of session's `parseTscDiagnosticLine`. */
|
|
12
|
+
export declare function parseTscDiagnosticLine(raw: string): TscDiagnostic | null;
|
|
13
|
+
/** MIRROR of session's `parseTscOutput`. */
|
|
14
|
+
export declare function parseTscOutput(output: string): TscDiagnostic[];
|
|
15
|
+
/** MIRROR of session's cap — a broken tsconfig can cascade into thousands. */
|
|
16
|
+
export declare const MAX_DIAGNOSTICS_PER_RUN = 200;
|
|
17
|
+
/** Loose BaseEvent shape — cluster does not depend on `@lensmcp/protocol-types`. */
|
|
18
|
+
export interface TypecheckBusEvent {
|
|
19
|
+
id: string;
|
|
20
|
+
sessionId: string;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
source: 'typescript';
|
|
23
|
+
category: 'typecheck';
|
|
24
|
+
severity: 'info' | 'warning' | 'error';
|
|
25
|
+
context: {
|
|
26
|
+
sessionId: string;
|
|
27
|
+
projectName?: string;
|
|
28
|
+
};
|
|
29
|
+
fingerprint: string;
|
|
30
|
+
title: string;
|
|
31
|
+
message?: string;
|
|
32
|
+
location?: {
|
|
33
|
+
file: string;
|
|
34
|
+
line: number;
|
|
35
|
+
column: number;
|
|
36
|
+
};
|
|
37
|
+
raw?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the ordered event list for one completed check.
|
|
41
|
+
* MIRROR of session's `buildTypecheckRunEvents` — see it for the protocol
|
|
42
|
+
* (START carries the legacy `tsc clear` prefix an older reducer resets on;
|
|
43
|
+
* END deliberately does not, or it would wipe what was just published).
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildTypecheckRunEvents(args: {
|
|
46
|
+
project: string;
|
|
47
|
+
tool: string;
|
|
48
|
+
tsConfig?: string;
|
|
49
|
+
diagnostics: readonly TscDiagnostic[];
|
|
50
|
+
durationMs: number;
|
|
51
|
+
toolError?: string;
|
|
52
|
+
now?: () => number;
|
|
53
|
+
idImpl?: () => string;
|
|
54
|
+
}): TypecheckBusEvent[];
|
|
55
|
+
/**
|
|
56
|
+
* Where the bus lives for this process. Same resolution order the serve
|
|
57
|
+
* executor uses for the pod env, so the checker and the pods it type-checks
|
|
58
|
+
* always land on ONE file.
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveEventFile(workspaceRoot: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Append events as NDJSON, honouring the shared rotation contract
|
|
63
|
+
* (`maybeRotateEventFile` is exported from node-instrumentation precisely so
|
|
64
|
+
* every OTHER writer to the bus enforces the same caps — the gateway once was
|
|
65
|
+
* the one writer without it, and the bus outgrew every consumer).
|
|
66
|
+
*
|
|
67
|
+
* Never throws: a lens that cannot write must not break a build.
|
|
68
|
+
*/
|
|
69
|
+
export declare function appendTypecheckEvents(eventFile: string, events: readonly TypecheckBusEvent[]): void;
|
|
70
|
+
/** Parse a raw tsgo/tsc run and publish it. Returns the events written (for tests). */
|
|
71
|
+
export declare function publishTypecheckRun(args: {
|
|
72
|
+
workspaceRoot: string;
|
|
73
|
+
project: string;
|
|
74
|
+
tool: string;
|
|
75
|
+
tsConfig?: string;
|
|
76
|
+
/** Raw combined stdout+stderr of the check. */
|
|
77
|
+
output: string;
|
|
78
|
+
durationMs: number;
|
|
79
|
+
toolError?: string;
|
|
80
|
+
eventFile?: string;
|
|
81
|
+
}): TypecheckBusEvent[];
|
|
82
|
+
//# sourceMappingURL=typecheck-bus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typecheck-bus.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/typecheck-bus.ts"],"names":[],"mappings":"AAwDA,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAexE;AAED,4CAA4C;AAC5C,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAO9D;AAED,8EAA8E;AAC9E,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;IACvC,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAQD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,SAAS,aAAa,EAAE,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB,GAAG,iBAAiB,EAAE,CA8EtB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE9D;AAqBD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI,CAsBnG;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,iBAAiB,EAAE,CAWtB"}
|
package/typecheck-bus.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_DIAGNOSTICS_PER_RUN = void 0;
|
|
4
|
+
exports.stripAnsi = stripAnsi;
|
|
5
|
+
exports.parseTscDiagnosticLine = parseTscDiagnosticLine;
|
|
6
|
+
exports.parseTscOutput = parseTscOutput;
|
|
7
|
+
exports.buildTypecheckRunEvents = buildTypecheckRunEvents;
|
|
8
|
+
exports.resolveEventFile = resolveEventFile;
|
|
9
|
+
exports.appendTypecheckEvents = appendTypecheckEvents;
|
|
10
|
+
exports.publishTypecheckRun = publishTypecheckRun;
|
|
11
|
+
const tslib_1 = require("tslib");
|
|
12
|
+
/**
|
|
13
|
+
* The tsgo checker's bus writer — turns a completed type check into the
|
|
14
|
+
* `typescript`/`typecheck` events `installTypecheckReducer` materialises into
|
|
15
|
+
* `typecheck://*`.
|
|
16
|
+
*
|
|
17
|
+
* ── The failure this fixes ──────────────────────────────────────────────────
|
|
18
|
+
* `typecheck://current` answered `{"status":"unknown","errorCount":0,
|
|
19
|
+
* "revision":0}` on a workspace whose dev cluster was type-checking on every
|
|
20
|
+
* single rebuild. `revision: 0` means no event had EVER arrived: the reducer
|
|
21
|
+
* had no producer at all. The checks were running — `[tsgo] engine: types
|
|
22
|
+
* clean (2.6s)`, and on failure real diagnostics like
|
|
23
|
+
* `…/hazard-analyst.agent.ts(52,12): error TS2416: …` — but that output went
|
|
24
|
+
* to the executor's stdout and nowhere else.
|
|
25
|
+
*
|
|
26
|
+
* The gateway's console sniff (`runtime/lifecycle.ts`, `/error TS\d+/`) does
|
|
27
|
+
* see those lines, but it emits `source:'gateway' / category:'cluster'` with
|
|
28
|
+
* the whole line stuffed verbatim into `raw.line` — no file, no position, no
|
|
29
|
+
* code, no per-project status, throttled to one line per 5s, and filtered out
|
|
30
|
+
* by all three check reducers. It is a crash-banner, not a typecheck feed.
|
|
31
|
+
*
|
|
32
|
+
* So the checker publishes directly. `TsgoCheckPlugin` runs INSIDE the nx
|
|
33
|
+
* serve executor process, which the gateway already spawns with
|
|
34
|
+
* `LENSMCP_EVENT_FILE` and `LENSMCP_PROJECT` in its env
|
|
35
|
+
* (`executors/gateway/runtime/lifecycle.ts`) — the same JSONL rendezvous the
|
|
36
|
+
* MCP server tails at 50 ms. No new transport, no new wiring.
|
|
37
|
+
*
|
|
38
|
+
* ── Why this file MIRRORS `@lensmcp/session` ────────────────────────────────
|
|
39
|
+
* The canonical parser + event builder is
|
|
40
|
+
* `libs/session/src/lib/collectors/typecheck-events.ts`. `@lensmcp/cluster` is
|
|
41
|
+
* PUBLISHED and `@lensmcp/session` is private/bundled-into-the-CLI, so cluster
|
|
42
|
+
* cannot depend on it. This is the same deliberate mirror
|
|
43
|
+
* `libs/bridge/src/lib/env-sink.ts` makes of node-instrumentation's emit
|
|
44
|
+
* contract. Drift is pinned by `TSC_PARITY_FIXTURE`, asserted byte-for-byte in
|
|
45
|
+
* BOTH projects' specs — change one parser without the other and a test fails.
|
|
46
|
+
*
|
|
47
|
+
* Strictly passive: every write is best-effort inside try/catch, the host
|
|
48
|
+
* build is never blocked, delayed, or failed by it.
|
|
49
|
+
*/
|
|
50
|
+
const fs = tslib_1.__importStar(require("node:fs"));
|
|
51
|
+
const path = tslib_1.__importStar(require("node:path"));
|
|
52
|
+
const busHygiene = (() => {
|
|
53
|
+
try {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
55
|
+
return require('@lensmcp/node-instrumentation');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
/** MIRROR of session's regex — `path(line,col): error TS1234: message`. */
|
|
62
|
+
const DIAG_LINE_REGEX = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/;
|
|
63
|
+
// CSI/ANSI escape, built from String.fromCharCode so the literal control char
|
|
64
|
+
// never appears in source (no-control-regex).
|
|
65
|
+
const ANSI_CSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[A-Za-z]`, 'g');
|
|
66
|
+
function stripAnsi(s) {
|
|
67
|
+
return s.replace(ANSI_CSI, '');
|
|
68
|
+
}
|
|
69
|
+
/** MIRROR of session's `parseTscDiagnosticLine`. */
|
|
70
|
+
function parseTscDiagnosticLine(raw) {
|
|
71
|
+
const line = stripAnsi(raw).trim();
|
|
72
|
+
if (!line)
|
|
73
|
+
return null;
|
|
74
|
+
const m = DIAG_LINE_REGEX.exec(line);
|
|
75
|
+
if (!m)
|
|
76
|
+
return null;
|
|
77
|
+
const [, file, lineStr, colStr, severity, code, message] = m;
|
|
78
|
+
if (!file || !lineStr || !colStr || !severity || !code || !message)
|
|
79
|
+
return null;
|
|
80
|
+
return {
|
|
81
|
+
file,
|
|
82
|
+
line: Number(lineStr),
|
|
83
|
+
column: Number(colStr),
|
|
84
|
+
severity: severity === 'warning' ? 'warning' : 'error',
|
|
85
|
+
code,
|
|
86
|
+
message,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** MIRROR of session's `parseTscOutput`. */
|
|
90
|
+
function parseTscOutput(output) {
|
|
91
|
+
const out = [];
|
|
92
|
+
for (const line of output.split('\n')) {
|
|
93
|
+
const d = parseTscDiagnosticLine(line);
|
|
94
|
+
if (d)
|
|
95
|
+
out.push(d);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
/** MIRROR of session's cap — a broken tsconfig can cascade into thousands. */
|
|
100
|
+
exports.MAX_DIAGNOSTICS_PER_RUN = 200;
|
|
101
|
+
let idCounter = 0;
|
|
102
|
+
function makeId() {
|
|
103
|
+
idCounter = (idCounter + 1) % 0xffff;
|
|
104
|
+
return `${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the ordered event list for one completed check.
|
|
108
|
+
* MIRROR of session's `buildTypecheckRunEvents` — see it for the protocol
|
|
109
|
+
* (START carries the legacy `tsc clear` prefix an older reducer resets on;
|
|
110
|
+
* END deliberately does not, or it would wipe what was just published).
|
|
111
|
+
*/
|
|
112
|
+
function buildTypecheckRunEvents(args) {
|
|
113
|
+
const at = (args.now ?? Date.now)();
|
|
114
|
+
const id = args.idImpl ?? makeId;
|
|
115
|
+
const sessionId = 'pending'; // stamped by the ingesting session, as every file-bus producer does
|
|
116
|
+
const base = {
|
|
117
|
+
sessionId,
|
|
118
|
+
source: 'typescript',
|
|
119
|
+
category: 'typecheck',
|
|
120
|
+
context: { sessionId, projectName: args.project },
|
|
121
|
+
};
|
|
122
|
+
const rawBase = {
|
|
123
|
+
project: args.project,
|
|
124
|
+
tool: args.tool,
|
|
125
|
+
...(args.tsConfig ? { tsConfig: args.tsConfig } : {}),
|
|
126
|
+
};
|
|
127
|
+
const errors = args.diagnostics.filter((d) => d.severity === 'error');
|
|
128
|
+
const forwarded = args.diagnostics.slice(0, exports.MAX_DIAGNOSTICS_PER_RUN);
|
|
129
|
+
const events = [];
|
|
130
|
+
// A run that produced NO verdict emits NO clear: wiping the previous state
|
|
131
|
+
// and replacing it with zero diagnostics would read as a clean check.
|
|
132
|
+
if (!args.toolError) {
|
|
133
|
+
events.push({
|
|
134
|
+
...base,
|
|
135
|
+
id: id(),
|
|
136
|
+
timestamp: at,
|
|
137
|
+
severity: 'info',
|
|
138
|
+
title: `tsc clear: ${args.project}`,
|
|
139
|
+
message: `typecheck run starting for ${args.project}`,
|
|
140
|
+
fingerprint: `tsc:${args.project}:run-start`,
|
|
141
|
+
raw: { ...rawBase, kind: 'typecheck-run', phase: 'start' },
|
|
142
|
+
});
|
|
143
|
+
for (const d of forwarded) {
|
|
144
|
+
events.push({
|
|
145
|
+
...base,
|
|
146
|
+
id: id(),
|
|
147
|
+
timestamp: at,
|
|
148
|
+
severity: d.severity,
|
|
149
|
+
title: `${d.code}: ${d.message}`,
|
|
150
|
+
message: d.message,
|
|
151
|
+
location: { file: d.file, line: d.line, column: d.column },
|
|
152
|
+
// MIRROR of session's `diagnosticFingerprint` — position included so N
|
|
153
|
+
// hits of one code in one file stay N, not 1.
|
|
154
|
+
fingerprint: `tsc:${args.project}:${d.file}:${d.line}:${d.column}:${d.code}`,
|
|
155
|
+
raw: { ...rawBase, kind: 'typecheck-diagnostic', code: d.code },
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const status = args.toolError ? 'error' : errors.length > 0 ? 'failing' : 'clean';
|
|
160
|
+
events.push({
|
|
161
|
+
...base,
|
|
162
|
+
id: id(),
|
|
163
|
+
timestamp: at,
|
|
164
|
+
severity: 'info',
|
|
165
|
+
title: `typecheck run: ${args.project} — ${args.toolError ? 'tool error' : `${errors.length} error${errors.length === 1 ? '' : 's'}`} (${args.durationMs}ms)`,
|
|
166
|
+
...(args.toolError ? { message: args.toolError } : {}),
|
|
167
|
+
fingerprint: `tsc:${args.project}:run-end`,
|
|
168
|
+
raw: {
|
|
169
|
+
...rawBase,
|
|
170
|
+
kind: 'typecheck-run',
|
|
171
|
+
phase: 'end',
|
|
172
|
+
status,
|
|
173
|
+
durationMs: args.durationMs,
|
|
174
|
+
errorCount: errors.length,
|
|
175
|
+
warningCount: args.diagnostics.length - errors.length,
|
|
176
|
+
...(args.diagnostics.length > forwarded.length
|
|
177
|
+
? { truncatedDiagnostics: args.diagnostics.length - forwarded.length }
|
|
178
|
+
: {}),
|
|
179
|
+
...(args.toolError ? { error: args.toolError } : {}),
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
return events;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Where the bus lives for this process. Same resolution order the serve
|
|
186
|
+
* executor uses for the pod env, so the checker and the pods it type-checks
|
|
187
|
+
* always land on ONE file.
|
|
188
|
+
*/
|
|
189
|
+
function resolveEventFile(workspaceRoot) {
|
|
190
|
+
return process.env['LENSMCP_EVENT_FILE'] || path.join(workspaceRoot, '.lensmcp', 'events.jsonl');
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Last directory we successfully created, so the common case costs no syscall.
|
|
194
|
+
*
|
|
195
|
+
* Deliberately NOT a boolean: a `dirReady` flag latches TRUE for the life of
|
|
196
|
+
* the process, so if `.lensmcp/` is later removed (a clean, a `rm -rf tmp`, a
|
|
197
|
+
* different bus path) every subsequent append fails ENOENT and the producer
|
|
198
|
+
* goes silent FOREVER — reporting nothing, which is the precise failure mode
|
|
199
|
+
* this whole file exists to make impossible. Keyed by path + re-ensured on
|
|
200
|
+
* ENOENT instead.
|
|
201
|
+
*/
|
|
202
|
+
let ensuredDir = null;
|
|
203
|
+
function ensureDir(eventFile) {
|
|
204
|
+
const dir = path.dirname(eventFile);
|
|
205
|
+
if (ensuredDir === dir)
|
|
206
|
+
return;
|
|
207
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
208
|
+
ensuredDir = dir;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Append events as NDJSON, honouring the shared rotation contract
|
|
212
|
+
* (`maybeRotateEventFile` is exported from node-instrumentation precisely so
|
|
213
|
+
* every OTHER writer to the bus enforces the same caps — the gateway once was
|
|
214
|
+
* the one writer without it, and the bus outgrew every consumer).
|
|
215
|
+
*
|
|
216
|
+
* Never throws: a lens that cannot write must not break a build.
|
|
217
|
+
*/
|
|
218
|
+
function appendTypecheckEvents(eventFile, events) {
|
|
219
|
+
if (events.length === 0)
|
|
220
|
+
return;
|
|
221
|
+
const payload = events.map((e) => JSON.stringify(e)).join('\n') + '\n';
|
|
222
|
+
try {
|
|
223
|
+
ensureDir(eventFile);
|
|
224
|
+
busHygiene?.maybeRotateEventFile(eventFile, payload.length);
|
|
225
|
+
fs.appendFileSync(eventFile, payload);
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
// The directory vanished under us (clean / rotate race). Re-create it once
|
|
229
|
+
// and retry — self-healing beats going permanently quiet.
|
|
230
|
+
if (err?.code === 'ENOENT') {
|
|
231
|
+
try {
|
|
232
|
+
ensuredDir = null;
|
|
233
|
+
ensureDir(eventFile);
|
|
234
|
+
fs.appendFileSync(eventFile, payload);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
/* lens offline — never break the host build */
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
/* lens offline — never break the host build */
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** Parse a raw tsgo/tsc run and publish it. Returns the events written (for tests). */
|
|
245
|
+
function publishTypecheckRun(args) {
|
|
246
|
+
const events = buildTypecheckRunEvents({
|
|
247
|
+
project: args.project,
|
|
248
|
+
tool: args.tool,
|
|
249
|
+
...(args.tsConfig ? { tsConfig: args.tsConfig } : {}),
|
|
250
|
+
diagnostics: args.toolError ? [] : parseTscOutput(args.output),
|
|
251
|
+
durationMs: args.durationMs,
|
|
252
|
+
...(args.toolError ? { toolError: args.toolError } : {}),
|
|
253
|
+
});
|
|
254
|
+
appendTypecheckEvents(args.eventFile ?? resolveEventFile(args.workspaceRoot), events);
|
|
255
|
+
return events;
|
|
256
|
+
}
|