@lensmcp/cluster 1.17.1 → 1.17.3

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.
@@ -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;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"}
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;AAatF,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,CAyKtF"}
@@ -49,12 +49,22 @@ function createDevWebpackConfig(options) {
49
49
  // Type-checking mode (LENSMCP_TYPECHECK): default = the resident fork-ts-checker pair (async,
50
50
  // feeds the lens `error TS` service-error signal); 'off' = none (fastest, quietest — the signal
51
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.
52
+ // signal, no idle memory — see tsgo-check-plugin.ts).
53
+ //
54
+ // tsgo still falls back to fork-ts-checker when no binary resolves — degrading gracefully beats
55
+ // failing a dev server — but that fallback is now an ANOMALY, not the expected "the flag shipped
56
+ // ahead of the devDep" state: @lensmcp/cluster DEPENDS on the compiler. So it also reports itself
57
+ // onto the lens bus. fork-ts-checker publishes to the CONSOLE only, so without this the
58
+ // `typecheck://` resources would sit at `producer: 'none'` with nothing naming the reason —
59
+ // the precise "looks healthy because nobody is watching" state this producer exists to end.
54
60
  const typecheckMode = process.env.LENSMCP_TYPECHECK;
55
61
  const tsgoBin = typecheckMode === 'tsgo' ? (0, tsgo_check_plugin_1.resolveTsgoBin)(workspaceRoot, process.env.LENSMCP_TSGO_BIN) : null;
56
62
  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`);
63
+ // No trailing period: `summariseProducers` renders this INSIDE a sentence it terminates itself.
64
+ const detail = `${tsgo_check_plugin_1.TSGO_MISSING_CAUSE}. Fell back to fork-ts-checker, which reports to the console only — ` +
65
+ `so typecheck:// has no producer for this project until it is fixed`;
66
+ console.warn(`[serve] ${options.appName}: LENSMCP_TYPECHECK=tsgo but ${detail}.`);
67
+ (0, tsgo_check_plugin_1.reportTsgoUnavailable)({ appName: options.appName, tsConfig, workspaceRoot, detail });
58
68
  }
59
69
  const useTsgo = !!tsgoBin;
60
70
  // Build combined allowlist: orgScopes + bundlePackages + user-provided
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.1",
3
+ "version": "1.17.3",
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,9 @@
65
65
  }
66
66
  },
67
67
  "dependencies": {
68
- "@lensmcp/node-instrumentation": "1.17.1",
69
- "@lensmcp/nx-plugin": "1.17.1",
68
+ "@lensmcp/node-instrumentation": "1.17.3",
69
+ "@lensmcp/nx-plugin": "1.17.3",
70
+ "@typescript/native-preview": "^7.0.0-dev.20260707.2",
70
71
  "fork-ts-checker-webpack-plugin": "^9.0.0",
71
72
  "glob": "^11.0.0",
72
73
  "http-proxy": "^1.18.0",
@@ -21,8 +21,15 @@
21
21
  * `module: preserve` + `emitDecoratorMetadata` otherwise flags TS1272 on decorated signatures —
22
22
  * an emit-safety rule that doesn't apply here (this checker never emits; webpack owns emit).
23
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.
24
+ * tsgo is a PREVIEW compiler, which is why this MODE is opt-in.
25
+ *
26
+ * OWNERSHIP: `@lensmcp/cluster` DEPENDS on `@typescript/native-preview` — this is our feature, so we
27
+ * ship the compiler that powers it rather than asking every consumer to remember a devDep. That was
28
+ * the old contract, and it made the feature (and, once this plugin became the `typecheck://`
29
+ * producer, the whole typecheck health signal) die quietly in any workspace that forgot. Resolution
30
+ * still PREFERS a workspace's own copy (a workspace pinning a tsgo version keeps winning), and a
31
+ * missing binary is now an ANOMALY: it degrades gracefully — never crashing the dev server — but it
32
+ * reports itself onto the lens bus instead of only warning into a log nobody reads.
26
33
  */
27
34
  import { spawn } from 'node:child_process';
28
35
  export interface TsgoCheckPluginOptions {
@@ -31,13 +38,83 @@ export interface TsgoCheckPluginOptions {
31
38
  tsConfig: string;
32
39
  workspaceRoot: string;
33
40
  debounceMs?: number;
34
- /** Binary override (LENSMCP_TSGO_BIN); default: <workspaceRoot>/node_modules/.bin/tsgo. */
41
+ /** Binary override (LENSMCP_TSGO_BIN); otherwise resolved by {@link resolveTsgoBin}. */
35
42
  binPath?: string;
36
43
  /** Injectable for tests. */
37
44
  spawnImpl?: typeof spawn;
45
+ /**
46
+ * Override the lens bus file. Defaults to `LENSMCP_EVENT_FILE` (which the
47
+ * gateway already injects into this process) or
48
+ * `<workspaceRoot>/.lensmcp/events.jsonl`. Injectable for tests.
49
+ */
50
+ eventFile?: string;
51
+ /** Set false to skip publishing to the lens bus (console output is unchanged). */
52
+ publishEvents?: boolean;
53
+ }
54
+ /** The npm package that ships the tsgo compiler — a real `dependency` of `@lensmcp/cluster`. */
55
+ export declare const TSGO_PACKAGE = "@typescript/native-preview";
56
+ /** Seams for {@link resolveTsgoBin}, so every branch of the precedence is testable without an install. */
57
+ export interface TsgoResolutionDeps {
58
+ /** Locate OUR `@typescript/native-preview`. Default: {@link resolveTsgoPackageDir}. */
59
+ ownPackageDir?: () => string | null;
60
+ /** Probe a candidate. Default: exists && is a file && is executable. */
61
+ isExecutable?: (candidate: string) => boolean;
62
+ /** Default: `process.platform`. */
63
+ platform?: NodeJS.Platform;
38
64
  }
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;
65
+ /**
66
+ * Where OUR copy of {@link TSGO_PACKAGE} lives, or null when it isn't installed.
67
+ *
68
+ * `require.resolve` walks up from THIS module, so it finds the copy installed for
69
+ * `@lensmcp/cluster` whether the consumer's installer HOISTED it
70
+ * (`<root>/node_modules/@typescript/native-preview`) or NESTED it
71
+ * (`<root>/node_modules/@lensmcp/cluster/node_modules/@typescript/native-preview`, the shape you
72
+ * get on a version conflict) or used pnpm's virtual store. A hand-written `../../node_modules/…`
73
+ * relative guess gets exactly one of those right.
74
+ */
75
+ export declare function resolveTsgoPackageDir(): string | null;
76
+ /**
77
+ * The tsgo binary to run, or null when none is usable.
78
+ *
79
+ * Precedence — first usable wins:
80
+ * 1. `override` (LENSMCP_TSGO_BIN). Deliberately NOT a fallthrough: an override pointing at a
81
+ * missing/non-executable file is an operator mistake to SURFACE, not to paper over by silently
82
+ * running a different compiler than the one that was asked for.
83
+ * 2. The consuming workspace's `node_modules/.bin/tsgo` — a workspace that pins its own tsgo
84
+ * version still beats the one we ship.
85
+ * 3. OURS, via {@link resolveTsgoPackageDir}. `@lensmcp/cluster` depends on {@link TSGO_PACKAGE},
86
+ * so this is the branch that normally answers, and the reason the feature no longer depends on
87
+ * every consumer independently remembering a devDep.
88
+ */
89
+ export declare function resolveTsgoBin(workspaceRoot: string, override?: string, deps?: TsgoResolutionDeps): string | null;
90
+ /**
91
+ * Why tsgo could not run, and what to do — the CAUSE half, shared by both sites that can hit it.
92
+ *
93
+ * Phrased for the new ownership: `@lensmcp/cluster` DEPENDS on the compiler, so "not installed" is
94
+ * no longer an expected opt-out state, it is a broken install (or an unsupported platform, or a bad
95
+ * override). Callers append the CONSEQUENCE, which differs by site.
96
+ */
97
+ export declare const TSGO_MISSING_CAUSE: string;
98
+ /**
99
+ * Report — onto the LENS BUS, not just the console — that tsgo cannot run.
100
+ *
101
+ * A `console.warn` is invisible to anything reading `typecheck://current`, which is precisely the
102
+ * failure this whole area exists to eliminate: a resource that looks healthy because nobody is
103
+ * watching. Published as a `toolError` run, so the reducer records the producer as reporting with
104
+ * `lastRunStatus: 'error'` — `status` degrades to `unknown` (never a false `clean`) and `detail`
105
+ * names the cause and the fix.
106
+ *
107
+ * Strictly passive: fully swallowed, never blocks or fails the host build.
108
+ */
109
+ export declare function reportTsgoUnavailable(args: {
110
+ appName: string;
111
+ tsConfig: string;
112
+ workspaceRoot: string;
113
+ /** Full sentence: {@link TSGO_MISSING_CAUSE} plus this site's consequence. */
114
+ detail: string;
115
+ /** Override the lens bus file. Injectable for tests. */
116
+ eventFile?: string;
117
+ }): void;
41
118
  /**
42
119
  * Write the shim tsconfig (absolute `extends`; see the header) and return its path. It lives under
43
120
  * the WORKSPACE's git-ignored `tmp/` — NOT os.tmpdir(): `types: ['node']` (and every other @types
@@ -56,6 +133,12 @@ export declare class TsgoCheckPlugin {
56
133
  apply(compiler: any): void;
57
134
  /** Debounced: rapid successive rebuilds collapse into one check; an in-flight check is superseded. */
58
135
  schedule(): void;
136
+ /**
137
+ * Publish one completed check onto the lens bus so `typecheck://*` reflects
138
+ * it. Strictly passive + fully swallowed: the console output above is the
139
+ * contract webpack/the gateway rely on, this is pure observation on top.
140
+ */
141
+ private publish;
59
142
  private run;
60
143
  }
61
144
  //# 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;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,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,OAAO,CAAC,GAAG;CA0CZ"}
1
+ {"version":3,"file":"tsgo-check-plugin.d.ts","sourceRoot":"","sources":["../../../libs/cluster/src/tsgo-check-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;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,wFAAwF;IACxF,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,gGAAgG;AAChG,eAAO,MAAM,YAAY,+BAA+B,CAAC;AAEzD,0GAA0G;AAC1G,MAAM,WAAW,kBAAkB;IACjC,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B;AAmCD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CA6BrD;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,GAAE,kBAAuB,GAC5B,MAAM,GAAG,IAAI,CAcf;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,QAG2C,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAeP;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;CA8DZ"}
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TsgoCheckPlugin = void 0;
3
+ exports.TsgoCheckPlugin = exports.TSGO_MISSING_CAUSE = exports.TSGO_PACKAGE = void 0;
4
+ exports.resolveTsgoPackageDir = resolveTsgoPackageDir;
4
5
  exports.resolveTsgoBin = resolveTsgoBin;
6
+ exports.reportTsgoUnavailable = reportTsgoUnavailable;
5
7
  exports.writeShimTsconfig = writeShimTsconfig;
6
8
  const tslib_1 = require("tslib");
7
9
  /**
@@ -27,16 +29,191 @@ const tslib_1 = require("tslib");
27
29
  * `module: preserve` + `emitDecoratorMetadata` otherwise flags TS1272 on decorated signatures —
28
30
  * an emit-safety rule that doesn't apply here (this checker never emits; webpack owns emit).
29
31
  * 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
+ * tsgo is a PREVIEW compiler, which is why this MODE is opt-in.
33
+ *
34
+ * OWNERSHIP: `@lensmcp/cluster` DEPENDS on `@typescript/native-preview` — this is our feature, so we
35
+ * ship the compiler that powers it rather than asking every consumer to remember a devDep. That was
36
+ * the old contract, and it made the feature (and, once this plugin became the `typecheck://`
37
+ * producer, the whole typecheck health signal) die quietly in any workspace that forgot. Resolution
38
+ * still PREFERS a workspace's own copy (a workspace pinning a tsgo version keeps winning), and a
39
+ * missing binary is now an ANOMALY: it degrades gracefully — never crashing the dev server — but it
40
+ * reports itself onto the lens bus instead of only warning into a log nobody reads.
32
41
  */
33
42
  const node_child_process_1 = require("node:child_process");
34
43
  const fs = tslib_1.__importStar(require("node:fs"));
35
44
  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;
45
+ const typecheck_bus_1 = require("./typecheck-bus");
46
+ /** The npm package that ships the tsgo compiler — a real `dependency` of `@lensmcp/cluster`. */
47
+ exports.TSGO_PACKAGE = '@typescript/native-preview';
48
+ /**
49
+ * The spawnable shim names inside a `node_modules/.bin`.
50
+ *
51
+ * On win32 the only shims `child_process.spawn` can execute directly are `tsgo.cmd` / `tsgo.exe`;
52
+ * the EXTENSIONLESS `tsgo` npm writes beside them is a POSIX shell script for git-bash, and
53
+ * spawning it without a shell fails. So win32 never considers the bare name.
54
+ */
55
+ function shimNames(platform) {
56
+ return platform === 'win32' ? ['tsgo.cmd', 'tsgo.exe'] : ['tsgo'];
57
+ }
58
+ /**
59
+ * Existence is not enough — a directory, a dangling symlink, or a non-`+x` file all "exist" and
60
+ * then fail at spawn time as an opaque EACCES/ENOENT mid-serve. Probe once, here.
61
+ * `statSync` follows symlinks on purpose (`.bin/tsgo` IS one).
62
+ */
63
+ function isExecutableFile(candidate, platform) {
64
+ try {
65
+ if (!fs.statSync(candidate).isFile())
66
+ return false;
67
+ }
68
+ catch {
69
+ return false; // missing, or a dangling symlink
70
+ }
71
+ // X_OK is meaningless on win32 (it reports every readable file executable), so the extension
72
+ // check in `shimNames` is the real gate there.
73
+ if (platform === 'win32')
74
+ return true;
75
+ try {
76
+ fs.accessSync(candidate, fs.constants.X_OK);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ /**
84
+ * Where OUR copy of {@link TSGO_PACKAGE} lives, or null when it isn't installed.
85
+ *
86
+ * `require.resolve` walks up from THIS module, so it finds the copy installed for
87
+ * `@lensmcp/cluster` whether the consumer's installer HOISTED it
88
+ * (`<root>/node_modules/@typescript/native-preview`) or NESTED it
89
+ * (`<root>/node_modules/@lensmcp/cluster/node_modules/@typescript/native-preview`, the shape you
90
+ * get on a version conflict) or used pnpm's virtual store. A hand-written `../../node_modules/…`
91
+ * relative guess gets exactly one of those right.
92
+ */
93
+ function resolveTsgoPackageDir() {
94
+ try {
95
+ // The package maps `"./package.json"` in its `exports`, which is the cheap, exact answer.
96
+ return path.dirname(require.resolve(`${exports.TSGO_PACKAGE}/package.json`));
97
+ }
98
+ catch {
99
+ /* an `exports` map without ./package.json — fall through to the entry-point walk */
100
+ }
101
+ try {
102
+ let dir = path.dirname(require.resolve(exports.TSGO_PACKAGE));
103
+ // The entry may sit a few levels below the package root (`<pkg>/lib/version.cjs`), so walk up
104
+ // to the manifest that actually NAMES the package rather than assuming a depth.
105
+ for (let i = 0; i < 6; i++) {
106
+ const manifest = path.join(dir, 'package.json');
107
+ if (fs.existsSync(manifest)) {
108
+ try {
109
+ const { name } = JSON.parse(fs.readFileSync(manifest, 'utf8'));
110
+ if (name === exports.TSGO_PACKAGE)
111
+ return dir;
112
+ }
113
+ catch {
114
+ /* unreadable manifest — keep walking */
115
+ }
116
+ }
117
+ const parent = path.dirname(dir);
118
+ if (parent === dir)
119
+ break;
120
+ dir = parent;
121
+ }
122
+ }
123
+ catch {
124
+ /* not installed */
125
+ }
126
+ return null;
127
+ }
128
+ /** `bin.tsgo` (or a string `bin`) from a package dir, as an absolute path. */
129
+ function packageDeclaredBin(pkgDir) {
130
+ try {
131
+ const { bin } = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
132
+ const rel = typeof bin === 'string' ? bin : bin?.['tsgo'];
133
+ return rel ? path.join(pkgDir, rel) : null;
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ /** Candidate binaries for an installed {@link TSGO_PACKAGE}, best first. */
140
+ function ownBinCandidates(pkgDir, platform) {
141
+ // `<…>/node_modules/@typescript/native-preview` ⇒ `<…>/node_modules/.bin` — two levels up holds
142
+ // for a hoisted install, a nested one, and pnpm's store alike (a scoped package is always
143
+ // `<node_modules>/<scope>/<name>`).
144
+ const binDir = path.resolve(pkgDir, '..', '..', '.bin');
145
+ const shims = shimNames(platform).map((n) => path.join(binDir, n));
146
+ // The package's OWN `bin/tsgo` is present whenever the package is, regardless of whether the
147
+ // installer wrote shims — but it is a `#!/usr/bin/env node` script, so it is directly spawnable
148
+ // on POSIX only. On win32 the `.cmd`/`.exe` shims are the only usable entry.
149
+ const declared = platform === 'win32' ? null : packageDeclaredBin(pkgDir);
150
+ return declared ? [declared, ...shims] : shims;
151
+ }
152
+ /**
153
+ * The tsgo binary to run, or null when none is usable.
154
+ *
155
+ * Precedence — first usable wins:
156
+ * 1. `override` (LENSMCP_TSGO_BIN). Deliberately NOT a fallthrough: an override pointing at a
157
+ * missing/non-executable file is an operator mistake to SURFACE, not to paper over by silently
158
+ * running a different compiler than the one that was asked for.
159
+ * 2. The consuming workspace's `node_modules/.bin/tsgo` — a workspace that pins its own tsgo
160
+ * version still beats the one we ship.
161
+ * 3. OURS, via {@link resolveTsgoPackageDir}. `@lensmcp/cluster` depends on {@link TSGO_PACKAGE},
162
+ * so this is the branch that normally answers, and the reason the feature no longer depends on
163
+ * every consumer independently remembering a devDep.
164
+ */
165
+ function resolveTsgoBin(workspaceRoot, override, deps = {}) {
166
+ const platform = deps.platform ?? process.platform;
167
+ const isExecutable = deps.isExecutable ?? ((c) => isExecutableFile(c, platform));
168
+ if (override)
169
+ return isExecutable(override) ? override : null;
170
+ for (const name of shimNames(platform)) {
171
+ const candidate = path.join(workspaceRoot, 'node_modules', '.bin', name);
172
+ if (isExecutable(candidate))
173
+ return candidate;
174
+ }
175
+ const pkgDir = (deps.ownPackageDir ?? resolveTsgoPackageDir)();
176
+ if (!pkgDir)
177
+ return null;
178
+ return ownBinCandidates(pkgDir, platform).find(isExecutable) ?? null;
179
+ }
180
+ /**
181
+ * Why tsgo could not run, and what to do — the CAUSE half, shared by both sites that can hit it.
182
+ *
183
+ * Phrased for the new ownership: `@lensmcp/cluster` DEPENDS on the compiler, so "not installed" is
184
+ * no longer an expected opt-out state, it is a broken install (or an unsupported platform, or a bad
185
+ * override). Callers append the CONSEQUENCE, which differs by site.
186
+ */
187
+ exports.TSGO_MISSING_CAUSE = `tsgo binary not found — @lensmcp/cluster depends on ${exports.TSGO_PACKAGE}, so an absent binary means an ` +
188
+ `incomplete install (reinstall node_modules), a platform with no ${exports.TSGO_PACKAGE} build, or a ` +
189
+ `LENSMCP_TSGO_BIN override pointing at a missing or non-executable file`;
190
+ /**
191
+ * Report — onto the LENS BUS, not just the console — that tsgo cannot run.
192
+ *
193
+ * A `console.warn` is invisible to anything reading `typecheck://current`, which is precisely the
194
+ * failure this whole area exists to eliminate: a resource that looks healthy because nobody is
195
+ * watching. Published as a `toolError` run, so the reducer records the producer as reporting with
196
+ * `lastRunStatus: 'error'` — `status` degrades to `unknown` (never a false `clean`) and `detail`
197
+ * names the cause and the fix.
198
+ *
199
+ * Strictly passive: fully swallowed, never blocks or fails the host build.
200
+ */
201
+ function reportTsgoUnavailable(args) {
202
+ try {
203
+ (0, typecheck_bus_1.publishTypecheckRun)({
204
+ workspaceRoot: args.workspaceRoot,
205
+ project: args.appName,
206
+ tool: 'tsgo',
207
+ tsConfig: args.tsConfig,
208
+ output: '',
209
+ durationMs: 0,
210
+ toolError: args.detail,
211
+ ...(args.eventFile ? { eventFile: args.eventFile } : {}),
212
+ });
213
+ }
214
+ catch {
215
+ /* never let instrumentation disturb a build */
216
+ }
40
217
  }
41
218
  /**
42
219
  * Write the shim tsconfig (absolute `extends`; see the header) and return its path. It lives under
@@ -90,12 +267,43 @@ class TsgoCheckPlugin {
90
267
  this.timer = setTimeout(() => this.run(), this.opts.debounceMs ?? 500);
91
268
  this.timer.unref?.();
92
269
  }
270
+ /**
271
+ * Publish one completed check onto the lens bus so `typecheck://*` reflects
272
+ * it. Strictly passive + fully swallowed: the console output above is the
273
+ * contract webpack/the gateway rely on, this is pure observation on top.
274
+ */
275
+ publish(args) {
276
+ if (this.opts.publishEvents === false)
277
+ return;
278
+ try {
279
+ (0, typecheck_bus_1.publishTypecheckRun)({
280
+ workspaceRoot: this.opts.workspaceRoot,
281
+ project: this.opts.appName,
282
+ tool: 'tsgo',
283
+ tsConfig: this.opts.tsConfig,
284
+ output: args.output,
285
+ durationMs: args.durationMs,
286
+ ...(args.toolError ? { toolError: args.toolError } : {}),
287
+ ...(this.opts.eventFile ? { eventFile: this.opts.eventFile } : {}),
288
+ });
289
+ }
290
+ catch {
291
+ /* never let instrumentation disturb a build */
292
+ }
293
+ }
93
294
  run() {
94
295
  const bin = resolveTsgoBin(this.opts.workspaceRoot, this.opts.binPath);
95
296
  if (!bin) {
96
297
  // The binary vanished after config time (e.g. a reinstall mid-serve) — warn once, go quiet.
97
298
  this.disabled = true;
98
- console.warn(`[tsgo] ${this.opts.appName}: tsgo binary not found type checking disabled for this session`);
299
+ // No trailing period: `summariseProducers` renders this INSIDE a sentence it terminates itself.
300
+ const detail = `${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;
301
+ console.warn(`[tsgo] ${this.opts.appName}: ${detail}.`);
302
+ // A checker that stopped checking must SAY so: without this the resource
303
+ // would keep serving the last (possibly clean) verdict forever, which is
304
+ // exactly the "looks healthy, nobody is watching" state this work exists
305
+ // to make impossible.
306
+ this.publish({ output: '', durationMs: 0, toolError: detail });
99
307
  return;
100
308
  }
101
309
  if (this.inflight) {
@@ -117,15 +325,20 @@ class TsgoCheckPlugin {
117
325
  if (this.inflight === child)
118
326
  this.inflight = null;
119
327
  console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${bin}`);
328
+ // A spawn failure is NOT a clean check — report it as a tool error so the
329
+ // resource says "the checker died", never "your types are fine".
330
+ this.publish({ output: '', durationMs: Date.now() - startedAt, toolError: `failed to spawn ${bin}` });
120
331
  });
121
332
  child.on('close', (code, signal) => {
122
333
  if (this.inflight === child)
123
334
  this.inflight = null;
124
335
  if (signal)
125
336
  return; // superseded — a newer check owns the verdict
126
- const secs = ((Date.now() - startedAt) / 1000).toFixed(1);
337
+ const durationMs = Date.now() - startedAt;
338
+ const secs = (durationMs / 1000).toFixed(1);
127
339
  if (code === 0) {
128
340
  console.log(`[tsgo] ${this.opts.appName}: types clean (${secs}s)`);
341
+ this.publish({ output: '', durationMs });
129
342
  return;
130
343
  }
131
344
  // Print the raw `file.ts(1,2): error TSxxxx:` lines — the gateway's `error TS\d+` sniff
@@ -134,7 +347,18 @@ class TsgoCheckPlugin {
134
347
  process.stdout.write(out.endsWith('\n') ? out : `${out}\n`);
135
348
  const count = (out.match(/error TS\d+/g) ?? []).length;
136
349
  console.log(`[tsgo] ${this.opts.appName}: ${count} type error${count === 1 ? '' : 's'} (${secs}s)`);
350
+ // A non-zero exit with NO parseable diagnostic is the checker itself
351
+ // failing (bad shim tsconfig, OOM) — `publishTypecheckRun` would
352
+ // otherwise read "0 errors" and report CLEAN on a failed run.
353
+ this.publish({
354
+ output: out,
355
+ durationMs,
356
+ ...(count === 0 ? { toolError: `tsgo exited ${code} with no diagnostics: ${firstLine(out)}` } : {}),
357
+ });
137
358
  });
138
359
  }
139
360
  }
140
361
  exports.TsgoCheckPlugin = TsgoCheckPlugin;
362
+ function firstLine(s) {
363
+ return s.trim().split('\n')[0]?.slice(0, 300) ?? '';
364
+ }
@@ -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"}
@@ -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
+ }