@lensmcp/cluster 1.16.26 → 1.16.27
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/create-webpack-dev.d.ts.map +1 -1
- package/create-webpack-dev.js +28 -3
- package/executors/gateway/gateway.impl.d.ts.map +1 -1
- package/executors/gateway/gateway.impl.js +14 -0
- package/executors/gateway/runtime/lifecycle.d.ts.map +1 -1
- package/executors/gateway/runtime/lifecycle.js +19 -1
- package/executors/gateway/runtime/server.d.ts +3 -0
- package/executors/gateway/runtime/server.d.ts.map +1 -1
- package/executors/gateway/runtime/server.js +31 -0
- package/executors/gateway/runtime/upgrade.d.ts.map +1 -1
- package/executors/gateway/runtime/upgrade.js +9 -1
- package/package.json +3 -3
- package/tsgo-check-plugin.d.ts +61 -0
- package/tsgo-check-plugin.d.ts.map +1 -0
- package/tsgo-check-plugin.js +130 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-webpack-dev.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/create-webpack-dev.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-webpack-dev.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/create-webpack-dev.ts"],"names":[],"mappings":"AAIA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAQtF,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,qBAAqB,CAAC,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AA2CD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAgKtF"}
|
package/create-webpack-dev.js
CHANGED
|
@@ -7,6 +7,7 @@ const fs = tslib_1.__importStar(require("node:fs"));
|
|
|
7
7
|
const path = tslib_1.__importStar(require("node:path"));
|
|
8
8
|
const webpack_node_externals_1 = tslib_1.__importDefault(require("webpack-node-externals"));
|
|
9
9
|
const build_scope_patterns_1 = require("./build-scope-patterns");
|
|
10
|
+
const tsgo_check_plugin_1 = require("./tsgo-check-plugin");
|
|
10
11
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
12
|
const webpack = require('webpack');
|
|
12
13
|
class DevServerReloadPlugin {
|
|
@@ -45,6 +46,17 @@ class DevServerReloadPlugin {
|
|
|
45
46
|
function createDevWebpackConfig(options) {
|
|
46
47
|
const { appRoot, outputDir, main, tsConfig, workspaceRoot, assets = [], port, memoryLimit = 8192, buildLibsFromSource = true, orgScopes = [], bundlePackages = [], additionalEntryPoints = [], nodeExternalsConfig: userNodeExternalsConfig, webpackConfigPath, } = options;
|
|
47
48
|
const { allowlistPatterns, scopePrefixes, scopePatterns } = (0, build_scope_patterns_1.buildScopePatterns)(orgScopes);
|
|
49
|
+
// Type-checking mode (LENSMCP_TYPECHECK): default = the resident fork-ts-checker pair (async,
|
|
50
|
+
// feeds the lens `error TS` service-error signal); 'off' = none (fastest, quietest — the signal
|
|
51
|
+
// is traded away); 'tsgo' = zero-resident cold re-checks via @typescript/native-preview (same
|
|
52
|
+
// signal, no idle memory — see tsgo-check-plugin.ts). tsgo falls back to fork-ts-checker when
|
|
53
|
+
// the binary isn't installed in the workspace, so the flag can ship ahead of the devDep.
|
|
54
|
+
const typecheckMode = process.env.LENSMCP_TYPECHECK;
|
|
55
|
+
const tsgoBin = typecheckMode === 'tsgo' ? (0, tsgo_check_plugin_1.resolveTsgoBin)(workspaceRoot, process.env.LENSMCP_TSGO_BIN) : null;
|
|
56
|
+
if (typecheckMode === 'tsgo' && !tsgoBin) {
|
|
57
|
+
console.warn(`[serve] ${options.appName}: LENSMCP_TYPECHECK=tsgo but no tsgo binary (yarn add -D @typescript/native-preview) — falling back to fork-ts-checker`);
|
|
58
|
+
}
|
|
59
|
+
const useTsgo = !!tsgoBin;
|
|
48
60
|
// Build combined allowlist: orgScopes + bundlePackages + user-provided
|
|
49
61
|
const bundlePatterns = bundlePackages.map((pkg) => new RegExp(`^${pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(/|$)`));
|
|
50
62
|
const combinedAllowlist = [
|
|
@@ -126,12 +138,25 @@ function createDevWebpackConfig(options) {
|
|
|
126
138
|
outputHashing: 'none',
|
|
127
139
|
generatePackageJson: false,
|
|
128
140
|
watchDependencies: true,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
// Type checking = the fork-ts-checker pair (2 worker processes, ~80MB per service) AND the
|
|
142
|
+
// source of the in-pod `error TS…` lines the gateway sniffs into the lens "service error"
|
|
143
|
+
// signal. Default ON. `LENSMCP_TYPECHECK=off` trades the signal for memory/CPU;
|
|
144
|
+
// `LENSMCP_TYPECHECK=tsgo` keeps the signal at zero resident cost (the TsgoCheckPlugin
|
|
145
|
+
// below replaces the resident pair with debounced cold native checks).
|
|
146
|
+
typeCheckOptions: process.env.LENSMCP_TYPECHECK === 'off' || useTsgo ? false : { async: true },
|
|
132
147
|
buildLibsFromSource,
|
|
133
148
|
}),
|
|
134
149
|
new DevServerReloadPlugin(port, options.httpsReload === true),
|
|
150
|
+
...(useTsgo
|
|
151
|
+
? [
|
|
152
|
+
new tsgo_check_plugin_1.TsgoCheckPlugin({
|
|
153
|
+
appName: options.appName,
|
|
154
|
+
tsConfig,
|
|
155
|
+
workspaceRoot,
|
|
156
|
+
...(process.env.LENSMCP_TSGO_BIN ? { binPath: process.env.LENSMCP_TSGO_BIN } : {}),
|
|
157
|
+
}),
|
|
158
|
+
]
|
|
159
|
+
: []),
|
|
135
160
|
],
|
|
136
161
|
snapshot: { managedPaths: [/^(.+?[\\/])?node_modules[\\/]/] },
|
|
137
162
|
watch: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.impl.d.ts","sourceRoot":"","sources":["../../../../../libs/cluster/src/executors/gateway/gateway.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"gateway.impl.d.ts","sourceRoot":"","sources":["../../../../../libs/cluster/src/executors/gateway/gateway.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAGlE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAA8B,eAAe,CAC3C,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA0B/B"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.default = gatewayExecutor;
|
|
4
4
|
const gateway_lib_1 = require("./gateway.lib");
|
|
5
|
+
const server_1 = require("./runtime/server");
|
|
5
6
|
/**
|
|
6
7
|
* `gateway` executor — ONE https front door for the whole workspace,
|
|
7
8
|
* assembled dynamically from every project's `cluster` declaration:
|
|
@@ -20,6 +21,19 @@ const gateway_lib_1 = require("./gateway.lib");
|
|
|
20
21
|
* without signals; this wrapper only adds the executor contract.
|
|
21
22
|
*/
|
|
22
23
|
async function gatewayExecutor(options, context) {
|
|
24
|
+
// LAST-RESORT netting for the long-running daemon (this executor runs for DAYS): a benign socket
|
|
25
|
+
// errno that escaped every layer's local handlers — e.g. a dependency emitting 'error' on a socket
|
|
26
|
+
// it forgot to guard — must cost the ONE connection, never the process. Scoped to exactly the
|
|
27
|
+
// read/write network errnos; anything else preserves crash semantics (print + exit 1, matching
|
|
28
|
+
// Node's default). Lives HERE, not in startGateway, so tests driving the lib keep real crashes loud.
|
|
29
|
+
process.on('uncaughtException', (err) => {
|
|
30
|
+
if ((err.syscall === 'read' || err.syscall === 'write') && server_1.BENIGN_SOCKET_ERRNOS.has(err.code ?? '')) {
|
|
31
|
+
console.warn(`[gateway] survived a stray socket ${err.syscall} error (${err.code}) — one connection dropped, the daemon lives`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.error(err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
23
37
|
let handle;
|
|
24
38
|
try {
|
|
25
39
|
handle = await (0, gateway_lib_1.startGateway)(options, context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lifecycle.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAapE,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,iBAAiB,CAAC;AA8C/D;;sGAEsG;AACtG,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,oBAAoB,CAAC,EACpG,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,gBAAgB,GACpB,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAQvC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;CAChC;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/lifecycle.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAapE,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,iBAAiB,CAAC;AA8C/D;;sGAEsG;AACtG,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBtE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,oBAAoB,CAAC,EACpG,GAAG,EAAE,MAAM,EACX,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,gBAAgB,GACpB,kBAAkB,GAAG,SAAS,GAAG,IAAI,CAQvC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,MAAM,EAC1B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAMT;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAChC,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,2EAA2E;IAC3E,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC;CAChC;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,aAAa,GAAG,YAAY,CAoSvF"}
|
|
@@ -161,7 +161,16 @@ function createServiceLayer(rt, obs) {
|
|
|
161
161
|
svc.spawnAt = Date.now();
|
|
162
162
|
// childCount goes as a CLI option, NOT env: nx executes tasks via its
|
|
163
163
|
// daemon, whose environment is not the gateway's — env vars get lost.
|
|
164
|
-
|
|
164
|
+
// Invoke the workspace's nx entry DIRECTLY (node + nx/dist/bin/nx.js) instead of through the
|
|
165
|
+
// `yarn nx` shim: the shim is a ~40MB node process that does nothing after the handoff yet
|
|
166
|
+
// lives for the service's whole life — ~600MB across a 15-service cluster. Resolved per
|
|
167
|
+
// svc.root so each hosted workspace runs its own nx; falls back to `yarn nx` when the entry
|
|
168
|
+
// isn't where node_modules linkers put it (pnp/odd layouts).
|
|
169
|
+
const nxEntry = path.join(svc.root, 'node_modules', 'nx', 'dist', 'bin', 'nx.js');
|
|
170
|
+
const [spawnCmd, spawnArgs] = fs.existsSync(nxEntry)
|
|
171
|
+
? [process.execPath, [nxEntry, 'run', target, `--childCount=${min}`]]
|
|
172
|
+
: ['yarn', ['nx', 'run', target, `--childCount=${min}`]];
|
|
173
|
+
const proc = (0, node_child_process_1.spawn)(spawnCmd, spawnArgs, {
|
|
165
174
|
// The service's OWN workspace root — `rt.root` for the daemon's own workspace, the registered
|
|
166
175
|
// workspace's root for a second one (so ONE daemon hosts several workspaces' devservers — model (A)).
|
|
167
176
|
cwd: svc.root,
|
|
@@ -181,6 +190,15 @@ function createServiceLayer(rt, obs) {
|
|
|
181
190
|
env: {
|
|
182
191
|
...process.env,
|
|
183
192
|
NX_DAEMON: 'false',
|
|
193
|
+
// NX_ISOLATE_PLUGINS=false: with the daemon off (above), EVERY serve client builds the
|
|
194
|
+
// project graph itself — and with plugin isolation on (nx's default) each client spawns
|
|
195
|
+
// 8 plugin-worker processes (~50MB each) that stay ALIVE for the life of the service:
|
|
196
|
+
// ~400MB of nx machinery per ~50MB service pod, ~4-5GB across a 13-service cluster
|
|
197
|
+
// (measured tetros 2026-07-25). Isolation-off loads plugins in-process: the graph is
|
|
198
|
+
// identical and zero workers spawn (nx itself runs isolation-off in sandboxes/WASM —
|
|
199
|
+
// a supported mode). NOTE this is authoritative for spawned clients: nx's dotenv load
|
|
200
|
+
// never overrides an already-set env var, so a workspace .env cannot flip it back on.
|
|
201
|
+
NX_ISOLATE_PLUGINS: 'false',
|
|
184
202
|
...(svc.wsKey ? { LENSMCP_WS_KEY: svc.wsKey } : {}),
|
|
185
203
|
LENSMCP_EVENT_FILE: svc.wsKey === rt.wsKey ? rt.eventFile : path.join(svc.root, '.lensmcp', 'events.jsonl'),
|
|
186
204
|
...eastWestEnv(rt, svc.project),
|
|
@@ -12,5 +12,8 @@ import type { GatewayContext, GatewayHandle, GatewayRuntimeOptions, Route } from
|
|
|
12
12
|
* unless the insecure opt-in is set).
|
|
13
13
|
*/
|
|
14
14
|
export declare function buildEdgeVerifierPool(getRoutes: () => Route[]): EdgeVerifierPool;
|
|
15
|
+
/** Client-abort errnos that are NORMAL front-door traffic (a closed tab, a sleeping laptop, a canceled
|
|
16
|
+
* HMR socket) — dropped quietly. Anything else on an inbound socket is logged before the drop. */
|
|
17
|
+
export declare const BENIGN_SOCKET_ERRNOS: ReadonlySet<string>;
|
|
15
18
|
export declare function startGateway(options: GatewayRuntimeOptions, context: GatewayContext): Promise<GatewayHandle>;
|
|
16
19
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAK/D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAkB,qBAAqB,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AA0BxH;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,KAAK,EAAE,GAAG,gBAAgB,CA4ChF;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAK/D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAkB,qBAAqB,EAAe,KAAK,EAAE,MAAM,SAAS,CAAC;AA0BxH;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,KAAK,EAAE,GAAG,gBAAgB,CA4ChF;AAED;mGACmG;AACnG,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,CAA8E,CAAC;AAkBpI,wBAAsB,YAAY,CAChC,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA8RxB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BENIGN_SOCKET_ERRNOS = void 0;
|
|
3
4
|
exports.buildEdgeVerifierPool = buildEdgeVerifierPool;
|
|
4
5
|
exports.startGateway = startGateway;
|
|
5
6
|
const tslib_1 = require("tslib");
|
|
@@ -112,6 +113,24 @@ function buildEdgeVerifierPool(getRoutes) {
|
|
|
112
113
|
},
|
|
113
114
|
};
|
|
114
115
|
}
|
|
116
|
+
/** Client-abort errnos that are NORMAL front-door traffic (a closed tab, a sleeping laptop, a canceled
|
|
117
|
+
* HMR socket) — dropped quietly. Anything else on an inbound socket is logged before the drop. */
|
|
118
|
+
exports.BENIGN_SOCKET_ERRNOS = new Set(['ECONNRESET', 'EPIPE', 'ETIMEDOUT', 'ECONNABORTED', 'ECANCELED']);
|
|
119
|
+
/** Attach the inbound-socket error guard. EVERY socket the front door accepts gets an 'error' listener
|
|
120
|
+
* the moment it exists — both the raw TCP socket ('connection') and the post-handshake TLSSocket
|
|
121
|
+
* ('secureConnection', a DIFFERENT emitter). A client abort is routine, but with NO listener Node turns
|
|
122
|
+
* it into an UNHANDLED 'error' event that kills the whole daemon (observed: a days-old gateway died on
|
|
123
|
+
* `read ECONNRESET` at TLSWrap.onStreamRead — the exposed window is a socket the http layer has handed
|
|
124
|
+
* off, e.g. right after an 'upgrade', where no internal handler is attached any more). Handling =
|
|
125
|
+
* destroy the ONE socket; never the process. */
|
|
126
|
+
function guardInboundSocket(socket) {
|
|
127
|
+
socket.on('error', (err) => {
|
|
128
|
+
if (!exports.BENIGN_SOCKET_ERRNOS.has(err.code ?? '')) {
|
|
129
|
+
console.warn(`[gateway] inbound socket error (${err.code ?? err.message}) — dropping this connection only`);
|
|
130
|
+
}
|
|
131
|
+
socket.destroy();
|
|
132
|
+
});
|
|
133
|
+
}
|
|
115
134
|
async function startGateway(options, context) {
|
|
116
135
|
// NB: this local `https` (TLS-on flag) deliberately SHADOWS the `node:https`
|
|
117
136
|
// module — which is why TLS/agents are reached via `require('node:https')`.
|
|
@@ -303,6 +322,18 @@ async function startGateway(options, context) {
|
|
|
303
322
|
: http.createServer(handler);
|
|
304
323
|
server.on('upgrade', upgrade);
|
|
305
324
|
server.on('error', (err) => console.error(`[gateway] port ${port}: ${err.code}`));
|
|
325
|
+
// Inbound-socket resilience (the ECONNRESET-crash fix): guard every accepted socket, and end
|
|
326
|
+
// handshake-phase ('tlsClientError') / H1 parse-phase ('clientError') faults on the ONE connection.
|
|
327
|
+
// Registering 'clientError' replaces Node's default 400-writer, so keep its semantics: a best-effort
|
|
328
|
+
// 400 on a still-writable socket, then destroy.
|
|
329
|
+
server.on('connection', guardInboundSocket);
|
|
330
|
+
server.on('secureConnection', guardInboundSocket); // never fires on a plain-http server — harmless
|
|
331
|
+
server.on('tlsClientError', (_err, tlsSocket) => tlsSocket.destroy());
|
|
332
|
+
server.on('clientError', (err, socket) => {
|
|
333
|
+
if (err.code !== 'ECONNRESET' && socket.writable)
|
|
334
|
+
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
|
|
335
|
+
socket.destroy();
|
|
336
|
+
});
|
|
306
337
|
// h2 SESSION resilience: without a `sessionError` handler ANY session-level
|
|
307
338
|
// fault (a client GOAWAY mid-burst, a flood-protection trip, a decode error)
|
|
308
339
|
// is unhandled → Node tears the session down, failing EVERY sibling stream at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/upgrade.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,cAAc,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAEvG,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../../../../../libs/cluster/src/executors/gateway/runtime/upgrade.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,KAAK,IAAI,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,KAAK,MAAM,MAAM,aAAa,CAAC;AAI3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,cAAc,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,UAAU,CAAC;IAClB,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAEvG,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,CA6ChE"}
|
|
@@ -59,5 +59,13 @@ function createUpgrade(deps) {
|
|
|
59
59
|
}
|
|
60
60
|
proxy.proxy.ws(req, socket, head, { target, changeOrigin: !route.pool, agent: proxy.agentForTarget(target) }, () => socket.destroy());
|
|
61
61
|
};
|
|
62
|
-
return (req, socket, head) => {
|
|
62
|
+
return (req, socket, head) => {
|
|
63
|
+
// Guard the handed-off socket IMMEDIATELY: on 'upgrade' the http layer removes its OWN listeners,
|
|
64
|
+
// so until proxy.ws attaches handlers (an `await` away in `run`) a client abort (ECONNRESET — an
|
|
65
|
+
// HMR socket torn mid-upgrade) would be an unhandled 'error' event → the whole daemon dies. And a
|
|
66
|
+
// rejection out of `run` (a throwing auth/hook) must likewise cost the ONE socket, never the
|
|
67
|
+
// process — a floating promise rejection is fatal by default.
|
|
68
|
+
socket.on('error', () => socket.destroy());
|
|
69
|
+
run(req, socket, head).catch(() => socket.destroy());
|
|
70
|
+
};
|
|
63
71
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lensmcp/cluster",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.27",
|
|
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.16.
|
|
69
|
-
"@lensmcp/nx-plugin": "1.16.
|
|
68
|
+
"@lensmcp/node-instrumentation": "1.16.27",
|
|
69
|
+
"@lensmcp/nx-plugin": "1.16.27",
|
|
70
70
|
"fork-ts-checker-webpack-plugin": "^9.0.0",
|
|
71
71
|
"glob": "^11.0.0",
|
|
72
72
|
"http-proxy": "^1.18.0",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TsgoCheckPlugin — dev type checking with ZERO resident memory (`LENSMCP_TYPECHECK=tsgo`).
|
|
3
|
+
*
|
|
4
|
+
* The default fork-ts-checker keeps an incremental tsc PROGRAM resident between saves — a
|
|
5
|
+
* 2-worker pair (~80MB+) per service, ~1GB across a 13-service cluster, alive even when nobody
|
|
6
|
+
* saves. tsgo (@typescript/native-preview, the native TS compiler) full-checks one of these
|
|
7
|
+
* services cold in ~1-3s, so instead of holding a program we re-check COLD after each successful
|
|
8
|
+
* rebuild (debounced, killing any in-flight check): idle cost is exactly zero.
|
|
9
|
+
*
|
|
10
|
+
* The output CONTRACT is the point: `--pretty false` prints classic `file.ts(1,2): error TS1234:`
|
|
11
|
+
* lines to the executor's console, which the gateway sniffs (`error TS\d+`) into the lens
|
|
12
|
+
* "service error" signal — the same wire the fork-ts-checker fed. Advisory like
|
|
13
|
+
* `typeCheckOptions.async: true`: a type error never fails or blocks the webpack build.
|
|
14
|
+
*
|
|
15
|
+
* SHIM tsconfig: tsgo is TS7 — `moduleResolution: node10` (what node-app tsconfigs often pin) is
|
|
16
|
+
* REMOVED there, so the check runs against a generated shim extending the app tsconfig with
|
|
17
|
+
* `{ module: 'preserve', moduleResolution: 'bundler', isolatedModules: false }`. `extends` is an
|
|
18
|
+
* ABSOLUTE path and tsconfig path-options resolve relative to the file they ORIGINATE in, so the
|
|
19
|
+
* shim lives in the workspace's git-ignored `tmp/` (in-tree, so @types resolution still walks up to
|
|
20
|
+
* the workspace node_modules — see writeShimTsconfig). `isolatedModules: false` because
|
|
21
|
+
* `module: preserve` + `emitDecoratorMetadata` otherwise flags TS1272 on decorated signatures —
|
|
22
|
+
* an emit-safety rule that doesn't apply here (this checker never emits; webpack owns emit).
|
|
23
|
+
* Parity was verified per adopting workspace (0-error parity with tsc on all spot-checked apps);
|
|
24
|
+
* tsgo is a PREVIEW compiler, which is why this mode is opt-in and falls back to fork-ts-checker
|
|
25
|
+
* when the binary is absent.
|
|
26
|
+
*/
|
|
27
|
+
import { spawn } from 'node:child_process';
|
|
28
|
+
export interface TsgoCheckPluginOptions {
|
|
29
|
+
appName: string;
|
|
30
|
+
/** Absolute path to the app tsconfig the check covers. */
|
|
31
|
+
tsConfig: string;
|
|
32
|
+
workspaceRoot: string;
|
|
33
|
+
debounceMs?: number;
|
|
34
|
+
/** Binary override (LENSMCP_TSGO_BIN); default: <workspaceRoot>/node_modules/.bin/tsgo. */
|
|
35
|
+
binPath?: string;
|
|
36
|
+
/** Injectable for tests. */
|
|
37
|
+
spawnImpl?: typeof spawn;
|
|
38
|
+
}
|
|
39
|
+
/** The workspace's tsgo binary, or null when not installed (caller falls back to fork-ts-checker). */
|
|
40
|
+
export declare function resolveTsgoBin(workspaceRoot: string, override?: string): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Write the shim tsconfig (absolute `extends`; see the header) and return its path. It lives under
|
|
43
|
+
* the WORKSPACE's git-ignored `tmp/` — NOT os.tmpdir(): `types: ['node']` (and every other @types
|
|
44
|
+
* lookup) resolves typeRoots by walking node_modules UP FROM THE CONFIG FILE's directory, so a shim
|
|
45
|
+
* outside the tree fails every service with `TS2688: Cannot find type definition file for 'node'`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function writeShimTsconfig(appName: string, tsConfig: string, workspaceRoot: string): string;
|
|
48
|
+
export declare class TsgoCheckPlugin {
|
|
49
|
+
private readonly opts;
|
|
50
|
+
private readonly spawnImpl;
|
|
51
|
+
private timer;
|
|
52
|
+
private inflight;
|
|
53
|
+
private shimPath;
|
|
54
|
+
private disabled;
|
|
55
|
+
constructor(opts: TsgoCheckPluginOptions);
|
|
56
|
+
apply(compiler: any): void;
|
|
57
|
+
/** Debounced: rapid successive rebuilds collapse into one check; an in-flight check is superseded. */
|
|
58
|
+
schedule(): void;
|
|
59
|
+
private run;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=tsgo-check-plugin.d.ts.map
|
|
@@ -0,0 +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;AAI9D,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;CAC1B;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,CAgBlG;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,OAAO,CAAC,GAAG;CA0CZ"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TsgoCheckPlugin = void 0;
|
|
4
|
+
exports.resolveTsgoBin = resolveTsgoBin;
|
|
5
|
+
exports.writeShimTsconfig = writeShimTsconfig;
|
|
6
|
+
const tslib_1 = require("tslib");
|
|
7
|
+
/**
|
|
8
|
+
* TsgoCheckPlugin — dev type checking with ZERO resident memory (`LENSMCP_TYPECHECK=tsgo`).
|
|
9
|
+
*
|
|
10
|
+
* The default fork-ts-checker keeps an incremental tsc PROGRAM resident between saves — a
|
|
11
|
+
* 2-worker pair (~80MB+) per service, ~1GB across a 13-service cluster, alive even when nobody
|
|
12
|
+
* saves. tsgo (@typescript/native-preview, the native TS compiler) full-checks one of these
|
|
13
|
+
* services cold in ~1-3s, so instead of holding a program we re-check COLD after each successful
|
|
14
|
+
* rebuild (debounced, killing any in-flight check): idle cost is exactly zero.
|
|
15
|
+
*
|
|
16
|
+
* The output CONTRACT is the point: `--pretty false` prints classic `file.ts(1,2): error TS1234:`
|
|
17
|
+
* lines to the executor's console, which the gateway sniffs (`error TS\d+`) into the lens
|
|
18
|
+
* "service error" signal — the same wire the fork-ts-checker fed. Advisory like
|
|
19
|
+
* `typeCheckOptions.async: true`: a type error never fails or blocks the webpack build.
|
|
20
|
+
*
|
|
21
|
+
* SHIM tsconfig: tsgo is TS7 — `moduleResolution: node10` (what node-app tsconfigs often pin) is
|
|
22
|
+
* REMOVED there, so the check runs against a generated shim extending the app tsconfig with
|
|
23
|
+
* `{ module: 'preserve', moduleResolution: 'bundler', isolatedModules: false }`. `extends` is an
|
|
24
|
+
* ABSOLUTE path and tsconfig path-options resolve relative to the file they ORIGINATE in, so the
|
|
25
|
+
* shim lives in the workspace's git-ignored `tmp/` (in-tree, so @types resolution still walks up to
|
|
26
|
+
* the workspace node_modules — see writeShimTsconfig). `isolatedModules: false` because
|
|
27
|
+
* `module: preserve` + `emitDecoratorMetadata` otherwise flags TS1272 on decorated signatures —
|
|
28
|
+
* an emit-safety rule that doesn't apply here (this checker never emits; webpack owns emit).
|
|
29
|
+
* Parity was verified per adopting workspace (0-error parity with tsc on all spot-checked apps);
|
|
30
|
+
* tsgo is a PREVIEW compiler, which is why this mode is opt-in and falls back to fork-ts-checker
|
|
31
|
+
* when the binary is absent.
|
|
32
|
+
*/
|
|
33
|
+
const node_child_process_1 = require("node:child_process");
|
|
34
|
+
const fs = tslib_1.__importStar(require("node:fs"));
|
|
35
|
+
const path = tslib_1.__importStar(require("node:path"));
|
|
36
|
+
/** The workspace's tsgo binary, or null when not installed (caller falls back to fork-ts-checker). */
|
|
37
|
+
function resolveTsgoBin(workspaceRoot, override) {
|
|
38
|
+
const candidate = override || path.join(workspaceRoot, 'node_modules', '.bin', 'tsgo');
|
|
39
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Write the shim tsconfig (absolute `extends`; see the header) and return its path. It lives under
|
|
43
|
+
* the WORKSPACE's git-ignored `tmp/` — NOT os.tmpdir(): `types: ['node']` (and every other @types
|
|
44
|
+
* lookup) resolves typeRoots by walking node_modules UP FROM THE CONFIG FILE's directory, so a shim
|
|
45
|
+
* outside the tree fails every service with `TS2688: Cannot find type definition file for 'node'`.
|
|
46
|
+
*/
|
|
47
|
+
function writeShimTsconfig(appName, tsConfig, workspaceRoot) {
|
|
48
|
+
const dir = path.join(workspaceRoot, 'tmp', 'lensmcp-tsgo');
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
const file = path.join(dir, `${appName}.tsconfig.json`);
|
|
51
|
+
fs.writeFileSync(file, JSON.stringify({
|
|
52
|
+
extends: tsConfig,
|
|
53
|
+
compilerOptions: { module: 'preserve', moduleResolution: 'bundler', isolatedModules: false },
|
|
54
|
+
}, null, 2));
|
|
55
|
+
return file;
|
|
56
|
+
}
|
|
57
|
+
class TsgoCheckPlugin {
|
|
58
|
+
constructor(opts) {
|
|
59
|
+
this.timer = null;
|
|
60
|
+
this.inflight = null;
|
|
61
|
+
this.shimPath = null;
|
|
62
|
+
this.disabled = false;
|
|
63
|
+
this.opts = opts;
|
|
64
|
+
this.spawnImpl = opts.spawnImpl ?? node_child_process_1.spawn;
|
|
65
|
+
}
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- webpack Compiler (untyped here like DevServerReloadPlugin)
|
|
67
|
+
apply(compiler) {
|
|
68
|
+
compiler.hooks.done.tap('TsgoCheckPlugin', (stats) => {
|
|
69
|
+
if (stats.hasErrors())
|
|
70
|
+
return; // webpack already printed build errors — don't pile on
|
|
71
|
+
this.schedule();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Debounced: rapid successive rebuilds collapse into one check; an in-flight check is superseded. */
|
|
75
|
+
schedule() {
|
|
76
|
+
if (this.disabled)
|
|
77
|
+
return;
|
|
78
|
+
if (this.timer)
|
|
79
|
+
clearTimeout(this.timer);
|
|
80
|
+
this.timer = setTimeout(() => this.run(), this.opts.debounceMs ?? 500);
|
|
81
|
+
this.timer.unref?.();
|
|
82
|
+
}
|
|
83
|
+
run() {
|
|
84
|
+
const bin = resolveTsgoBin(this.opts.workspaceRoot, this.opts.binPath);
|
|
85
|
+
if (!bin) {
|
|
86
|
+
// The binary vanished after config time (e.g. a reinstall mid-serve) — warn once, go quiet.
|
|
87
|
+
this.disabled = true;
|
|
88
|
+
console.warn(`[tsgo] ${this.opts.appName}: tsgo binary not found — type checking disabled for this session`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (this.inflight) {
|
|
92
|
+
this.inflight.kill('SIGTERM'); // superseded by a newer build
|
|
93
|
+
this.inflight = null;
|
|
94
|
+
}
|
|
95
|
+
if (!this.shimPath)
|
|
96
|
+
this.shimPath = writeShimTsconfig(this.opts.appName, this.opts.tsConfig, this.opts.workspaceRoot);
|
|
97
|
+
const startedAt = Date.now();
|
|
98
|
+
const child = this.spawnImpl(bin, ['--noEmit', '--pretty', 'false', '-p', this.shimPath], {
|
|
99
|
+
cwd: this.opts.workspaceRoot,
|
|
100
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
101
|
+
});
|
|
102
|
+
this.inflight = child;
|
|
103
|
+
let out = '';
|
|
104
|
+
child.stdout?.on('data', (d) => { out += d.toString(); });
|
|
105
|
+
child.stderr?.on('data', (d) => { out += d.toString(); });
|
|
106
|
+
child.on('error', () => {
|
|
107
|
+
if (this.inflight === child)
|
|
108
|
+
this.inflight = null;
|
|
109
|
+
console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${bin}`);
|
|
110
|
+
});
|
|
111
|
+
child.on('close', (code, signal) => {
|
|
112
|
+
if (this.inflight === child)
|
|
113
|
+
this.inflight = null;
|
|
114
|
+
if (signal)
|
|
115
|
+
return; // superseded — a newer check owns the verdict
|
|
116
|
+
const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
|
|
117
|
+
if (code === 0) {
|
|
118
|
+
console.log(`[tsgo] ${this.opts.appName}: types clean (${secs}s)`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Print the raw `file.ts(1,2): error TSxxxx:` lines — the gateway's `error TS\d+` sniff
|
|
122
|
+
// turns these into the lens service-error signal, exactly like fork-ts-checker output did.
|
|
123
|
+
if (out.trim())
|
|
124
|
+
process.stdout.write(out.endsWith('\n') ? out : `${out}\n`);
|
|
125
|
+
const count = (out.match(/error TS\d+/g) ?? []).length;
|
|
126
|
+
console.log(`[tsgo] ${this.opts.appName}: ${count} type error${count === 1 ? '' : 's'} (${secs}s)`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.TsgoCheckPlugin = TsgoCheckPlugin;
|