@lensmcp/cluster 1.25.25 → 1.25.26
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/compile-retry.d.ts +26 -0
- package/compile-retry.js +1 -0
- package/create-webpack-dev.js +1 -1
- package/devserver-notifier.d.ts +13 -2
- package/devserver-notifier.js +1 -1
- package/executors/gateway/gateway.impl.js +1 -1
- package/executors/gateway/runtime/lens-children.js +1 -1
- package/executors/gateway/runtime/lifecycle.d.ts +18 -3
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/observability.js +3 -2
- package/executors/gateway/runtime/orphan-reaper.d.ts +44 -0
- package/executors/gateway/runtime/orphan-reaper.js +2 -0
- package/executors/gateway/runtime/own-log.d.ts +71 -0
- package/executors/gateway/runtime/own-log.js +6 -4
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/types.d.ts +10 -0
- package/executors/gateway/runtime/types.js +1 -1
- package/executors/serve/serve.impl.js +1 -1
- package/executors/serve/stop-on-signal.d.ts +19 -0
- package/executors/serve/stop-on-signal.js +1 -0
- package/main.devserver.js +7 -7
- package/package.json +4 -4
- package/process-name.d.ts +11 -2
- package/process-name.js +1 -1
- package/service-build-events.d.ts +2 -0
- package/service-build-events.js +2 -1
- package/shared-build/host.d.ts +7 -0
- package/shared-build/host.js +4 -4
- package/tsgo-check-plugin.d.ts +23 -0
- package/tsgo-check-plugin.js +5 -5
- package/typecheck-bus.d.ts +24 -0
- package/typecheck-bus.js +3 -3
- package/typecheck-queue.d.ts +24 -1
- package/typecheck-queue.js +1 -1
package/tsgo-check-plugin.d.ts
CHANGED
|
@@ -20,6 +20,15 @@
|
|
|
20
20
|
* reported as the checker failing, never as a verdict;
|
|
21
21
|
* - one checker is held to a few cores (`GOMAXPROCS`).
|
|
22
22
|
*
|
|
23
|
+
* INCREMENTAL (issues/closed/foodguard-94). Cold was the design — and on foodguard every save to a shared library
|
|
24
|
+
* re-checked all of it once per service: 1,054 checks in 12 hours, up to 5.9GB each. So a check keeps what it learnt
|
|
25
|
+
* in a build-info file beside its shim (`tmp/lensmcp-tsgo/<app>.tsbuildinfo`, `--incremental --tsBuildInfoFile`): the
|
|
26
|
+
* next one re-checks only the files a change can affect, and reports the errors of the rest from that file. Still
|
|
27
|
+
* nothing is resident between saves — the knowledge is on disk. A run that gave no verdict (killed at its timeout, the
|
|
28
|
+
* checker failing) deletes the file, so a bad one is never read twice. `LENSMCP_TSGO_INCREMENTAL=0` checks cold.
|
|
29
|
+
* And a check's turn waits for memory: the queue holds it while the machine has little free, and it says so
|
|
30
|
+
* (`[tsgo] <app>: check held …`, and a `phase: 'held'` event the lens shows in `service_state`).
|
|
31
|
+
*
|
|
23
32
|
* The output CONTRACT is the point: `--pretty false` prints classic `file.ts(1,2): error TS1234:`
|
|
24
33
|
* lines to the executor's console, which the gateway sniffs (`error TS\d+`) into the lens
|
|
25
34
|
* "service error" signal — the same wire the fork-ts-checker fed. Advisory like
|
|
@@ -73,9 +82,17 @@ export interface TsgoCheckPluginOptions {
|
|
|
73
82
|
timeoutMs?: number;
|
|
74
83
|
/** Cores one checker may use (`GOMAXPROCS`). Default {@link checkerMaxProcs}. */
|
|
75
84
|
maxProcs?: number;
|
|
85
|
+
/** Keep a build-info file between checks (see the header). Default {@link tsgoIncremental}. */
|
|
86
|
+
incremental?: boolean;
|
|
76
87
|
}
|
|
77
88
|
/** `LENSMCP_TYPECHECK_GOMAXPROCS`, else {@link DEFAULT_MAX_PROCS}. An explicit `GOMAXPROCS` in the env wins over both. */
|
|
78
89
|
export declare function checkerMaxProcs(env?: NodeJS.ProcessEnv): number;
|
|
90
|
+
/** `LENSMCP_TSGO_INCREMENTAL` — on unless it is `0`/`false`/`off`. */
|
|
91
|
+
export declare function tsgoIncremental(env?: NodeJS.ProcessEnv): boolean;
|
|
92
|
+
/** Where a service's check keeps what it learnt: beside its shim, in the workspace's git-ignored `tmp/`. */
|
|
93
|
+
export declare function tsgoBuildInfoPath(appName: string, workspaceRoot: string): string;
|
|
94
|
+
/** The checker's arguments. `--pretty false` is the output contract (see the header); the build info is optional. */
|
|
95
|
+
export declare function tsgoCheckArgs(shimPath: string, buildInfo?: string): string[];
|
|
79
96
|
/** The npm package that ships the tsgo compiler — a real `dependency` of `@lensmcp/cluster`. */
|
|
80
97
|
export declare const TSGO_PACKAGE = "@typescript/native-preview";
|
|
81
98
|
/** Seams for {@link resolveTsgoBin}, so every branch of the precedence is testable without an install. */
|
|
@@ -165,6 +182,8 @@ export declare class TsgoCheckPlugin {
|
|
|
165
182
|
private inflight;
|
|
166
183
|
private askedAt;
|
|
167
184
|
private shimPath;
|
|
185
|
+
/** The build-info file the checks keep, when they are incremental. */
|
|
186
|
+
private readonly buildInfo;
|
|
168
187
|
private disabled;
|
|
169
188
|
private disposed;
|
|
170
189
|
constructor(opts: TsgoCheckPluginOptions);
|
|
@@ -178,6 +197,10 @@ export declare class TsgoCheckPlugin {
|
|
|
178
197
|
* it cannot be stopped politely, and alone it is done in seconds.
|
|
179
198
|
*/
|
|
180
199
|
private request;
|
|
200
|
+
/** Its turn came and waits for memory: said once, so a check that has not run reads «held», not «slow». */
|
|
201
|
+
private held;
|
|
202
|
+
/** A run that gave no verdict may have left a build-info file nobody should trust: the next check starts cold. */
|
|
203
|
+
private forgetBuildInfo;
|
|
181
204
|
/** Asked through a method on purpose: `run()` may have moved the phase inside the call above. */
|
|
182
205
|
private isIdle;
|
|
183
206
|
/**
|
package/tsgo-check-plugin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`&&(
|
|
3
|
-
`)?
|
|
4
|
-
`);const g=(
|
|
5
|
-
`)[0]?.slice(0,300)??""}
|
|
1
|
+
"use strict";var v=Object.defineProperty;var a=(e,t)=>v(e,"name",{value:t,configurable:!0});var b=Object.defineProperty,l=a((e,t)=>b(e,"name",{value:t,configurable:!0}),"l");Object.defineProperty(exports,"__esModule",{value:!0}),exports.TsgoCheckPlugin=exports.TSGO_MISSING_CAUSE=exports.TSGO_PACKAGE=void 0,exports.checkerMaxProcs=checkerMaxProcs,exports.tsgoIncremental=tsgoIncremental,exports.tsgoBuildInfoPath=tsgoBuildInfoPath,exports.tsgoCheckArgs=tsgoCheckArgs,exports.resolveTsgoPackageDir=resolveTsgoPackageDir,exports.resolveTsgoBin=resolveTsgoBin,exports.reportTsgoUnavailable=reportTsgoUnavailable,exports.effectivePaths=effectivePaths,exports.writeShimTsconfig=writeShimTsconfig;const tslib_1=require("tslib"),node_child_process_1=require("node:child_process"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),typecheck_bus_1=require("./typecheck-bus"),typecheck_queue_1=require("./typecheck-queue"),DEFAULT_TIMEOUT_MS=12e4,DEFAULT_MAX_PROCS=4;function checkerMaxProcs(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_GOMAXPROCS);return Number.isInteger(t)&&t>=1?t:DEFAULT_MAX_PROCS}a(checkerMaxProcs,"checkerMaxProcs"),l(checkerMaxProcs,"checkerMaxProcs");function tsgoIncremental(e=process.env){const t=(e.LENSMCP_TSGO_INCREMENTAL??"").trim().toLowerCase();return!(t==="0"||t==="false"||t==="off"||t==="no")}a(tsgoIncremental,"tsgoIncremental"),l(tsgoIncremental,"tsgoIncremental");function tsgoBuildInfoPath(e,t){return path.join(t,"tmp","lensmcp-tsgo",`${e}.tsbuildinfo`)}a(tsgoBuildInfoPath,"tsgoBuildInfoPath"),l(tsgoBuildInfoPath,"tsgoBuildInfoPath");function tsgoCheckArgs(e,t){return["--noEmit","--pretty","false",...t?["--incremental","--tsBuildInfoFile",t]:[],"-p",e]}a(tsgoCheckArgs,"tsgoCheckArgs"),l(tsgoCheckArgs,"tsgoCheckArgs");const livePlugins=new Set;let exitHooked=!1;function stopWithTheProcess(e){livePlugins.add(e),!exitHooked&&(exitHooked=!0,process.once("exit",()=>{for(const t of livePlugins)t.dispose()}))}a(stopWithTheProcess,"stopWithTheProcess"),l(stopWithTheProcess,"stopWithTheProcess"),exports.TSGO_PACKAGE="@typescript/native-preview";function shimNames(e){return e==="win32"?["tsgo.cmd","tsgo.exe"]:["tsgo"]}a(shimNames,"shimNames"),l(shimNames,"shimNames");function isExecutableFile(e,t){try{if(!fs.statSync(e).isFile())return!1}catch{return!1}if(t==="win32")return!0;try{return fs.accessSync(e,fs.constants.X_OK),!0}catch{return!1}}a(isExecutableFile,"isExecutableFile"),l(isExecutableFile,"isExecutableFile");function resolveTsgoPackageDir(){try{return path.dirname(require.resolve(`${exports.TSGO_PACKAGE}/package.json`))}catch{}try{let e=path.dirname(require.resolve(exports.TSGO_PACKAGE));for(let t=0;t<6;t++){const i=path.join(e,"package.json");if(fs.existsSync(i))try{const{name:s}=JSON.parse(fs.readFileSync(i,"utf8"));if(s===exports.TSGO_PACKAGE)return e}catch{}const n=path.dirname(e);if(n===e)break;e=n}}catch{}return null}a(resolveTsgoPackageDir,"resolveTsgoPackageDir"),l(resolveTsgoPackageDir,"resolveTsgoPackageDir");function packageDeclaredBin(e){try{const{bin:t}=JSON.parse(fs.readFileSync(path.join(e,"package.json"),"utf8")),i=typeof t=="string"?t:t?.tsgo;return i?path.join(e,i):null}catch{return null}}a(packageDeclaredBin,"packageDeclaredBin"),l(packageDeclaredBin,"packageDeclaredBin");function ownBinCandidates(e,t){const i=path.resolve(e,"..","..",".bin"),n=shimNames(t).map(o=>path.join(i,o)),s=t==="win32"?null:packageDeclaredBin(e);return s?[s,...n]:n}a(ownBinCandidates,"ownBinCandidates"),l(ownBinCandidates,"ownBinCandidates");function resolveTsgoBin(e,t,i={}){const n=i.platform??process.platform,s=i.isExecutable??(r=>isExecutableFile(r,n));if(t)return s(t)?t:null;for(const r of shimNames(n)){const c=path.join(e,"node_modules",".bin",r);if(s(c))return c}const o=(i.ownPackageDir??resolveTsgoPackageDir)();return o?ownBinCandidates(o,n).find(s)??null:null}a(resolveTsgoBin,"resolveTsgoBin"),l(resolveTsgoBin,"resolveTsgoBin"),exports.TSGO_MISSING_CAUSE=`tsgo binary not found \u2014 @lensmcp/cluster depends on ${exports.TSGO_PACKAGE}, so an absent binary means an incomplete install (reinstall node_modules), a platform with no ${exports.TSGO_PACKAGE} build, or a LENSMCP_TSGO_BIN override pointing at a missing or non-executable file`;function reportTsgoUnavailable(e){try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:e.workspaceRoot,project:e.appName,tool:"tsgo",tsConfig:e.tsConfig,output:"",durationMs:0,toolError:e.detail,...e.eventFile?{eventFile:e.eventFile}:{}})}catch{}}a(reportTsgoUnavailable,"reportTsgoUnavailable"),l(reportTsgoUnavailable,"reportTsgoUnavailable");function parseJsonc(e){let t="",i=!1,n=!1,s=!1;for(let o=0;o<e.length;o++){const r=e[o],c=e[o+1];if(n){r===`
|
|
2
|
+
`&&(n=!1,t+=r);continue}if(s){r==="*"&&c==="/"&&(s=!1,o++);continue}if(i){t+=r,r==="\\"?(t+=c??"",o++):r==='"'&&(i=!1);continue}if(r==='"'){i=!0,t+=r;continue}if(r==="/"&&c==="/"){n=!0,o++;continue}if(r==="/"&&c==="*"){s=!0,o++;continue}t+=r}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}a(parseJsonc,"parseJsonc"),l(parseJsonc,"parseJsonc");function resolveExtends(e,t){if(!e.startsWith(".")&&!path.isAbsolute(e))return null;const i=path.isAbsolute(e)?e:path.resolve(t,e);for(const n of[i,`${i}.json`,path.join(i,"tsconfig.json")])try{if(fs.statSync(n).isFile())return n}catch{}return null}a(resolveExtends,"resolveExtends"),l(resolveExtends,"resolveExtends");function effectivePaths(e,t=new Set){if(t.has(e))return null;t.add(e);let i;try{i=fs.readFileSync(e,"utf8")}catch{return null}const n=parseJsonc(i);if(!n)return null;const s=path.dirname(e),o=n.compilerOptions??{},r=Array.isArray(n.extends)?n.extends.filter(p=>typeof p=="string"):typeof n.extends=="string"?[n.extends]:[],c=l(()=>{for(const p of[...r].reverse()){const d=resolveExtends(p,s);if(!d)continue;const f=effectivePaths(d,t);if(f)return f}return null},"inherited"),u=o.paths,h=typeof o.baseUrl=="string"?path.resolve(s,o.baseUrl):void 0;if(!u){const p=c();return p?{paths:p.paths,anchorDir:h??p.anchorDir}:null}return{paths:u,anchorDir:h??c()?.anchorDir??s}}a(effectivePaths,"effectivePaths"),l(effectivePaths,"effectivePaths");function writeShimTsconfig(e,t,i){const n=path.join(i,"tmp","lensmcp-tsgo");fs.mkdirSync(n,{recursive:!0});const s=path.join(n,`${e}.tsconfig.json`),o=effectivePaths(t),r=o?Object.fromEntries(Object.entries(o.paths).map(([c,u])=>[c,(Array.isArray(u)?u:[]).map(h=>path.isAbsolute(h)?h:path.resolve(o.anchorDir,h))])):void 0;return fs.writeFileSync(s,JSON.stringify({extends:t,compilerOptions:{module:"preserve",moduleResolution:"bundler",isolatedModules:!1,baseUrl:null,...r&&Object.keys(r).length?{paths:r}:{}}},null,2)),s}a(writeShimTsconfig,"writeShimTsconfig"),l(writeShimTsconfig,"writeShimTsconfig");class TsgoCheckPlugin{static{a(this,"TsgoCheckPlugin")}static{l(this,"TsgoCheckPlugin")}constructor(t){this.timer=null,this.phase="idle",this.dirty=!1,this.ticket=null,this.inflight=null,this.askedAt=0,this.shimPath=null,this.disabled=!1,this.disposed=!1,this.opts=t,this.spawnImpl=t.spawnImpl??node_child_process_1.spawn,this.queue=t.queue??(0,typecheck_queue_1.machineTypecheckQueue)(),this.buildInfo=t.incremental??tsgoIncremental()?tsgoBuildInfoPath(t.appName,t.workspaceRoot):void 0}apply(t){t.hooks.done.tap("TsgoCheckPlugin",i=>{i.hasErrors()||this.schedule()}),t.hooks.shutdown?.tap?.("TsgoCheckPlugin",()=>this.dispose()),stopWithTheProcess(this)}schedule(){this.disabled||this.disposed||(this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>this.request(),this.opts.debounceMs??500),this.timer.unref?.())}request(){if(this.disabled||this.disposed)return;if(this.phase==="running"){this.dirty=!0;return}if(this.phase==="waiting")return;this.phase="waiting",this.askedAt=Date.now();const t=this.queue(this.opts.appName,()=>this.run(),{onHold:l(i=>this.held(i),"onHold")});this.isIdle()?t.release():this.ticket=t}held(t){if(!this.disposed&&(console.log(`[tsgo] ${this.opts.appName}: check held \u2014 ${t.availableMb} MB free, it waits for ${t.minFreeMb} MB (LENSMCP_TYPECHECK_MIN_FREE_MB)`),this.opts.publishEvents!==!1))try{(0,typecheck_bus_1.publishTypecheckHeld)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,availableMb:t.availableMb,minFreeMb:t.minFreeMb,...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}forgetBuildInfo(){if(this.buildInfo)try{fs.rmSync(this.buildInfo,{force:!0})}catch{}}isIdle(){return this.phase==="idle"}publish(t){if(this.opts.publishEvents!==!1)try{(0,typecheck_bus_1.publishTypecheckRun)({workspaceRoot:this.opts.workspaceRoot,project:this.opts.appName,tool:"tsgo",tsConfig:this.opts.tsConfig,output:t.output,durationMs:t.durationMs,...t.toolError?{toolError:t.toolError}:{},...this.opts.eventFile?{eventFile:this.opts.eventFile}:{}})}catch{}}run(){if(this.disposed)return this.leave();let t;try{t=resolveTsgoBin(this.opts.workspaceRoot,this.opts.binPath),t&&!this.shimPath&&(this.shimPath=writeShimTsconfig(this.opts.appName,this.opts.tsConfig,this.opts.workspaceRoot))}catch(h){return console.warn(`[tsgo] ${this.opts.appName}: could not prepare the check \u2014 ${h.message}`),this.publish({output:"",durationMs:0,toolError:`could not prepare the check: ${h.message}`}),this.leave()}if(!t){this.disabled=!0;const h=`${exports.TSGO_MISSING_CAUSE}. Type checking is disabled for the rest of this session`;return console.warn(`[tsgo] ${this.opts.appName}: ${h}.`),this.publish({output:"",durationMs:0,toolError:h}),this.leave()}this.phase="running",this.dirty=!1;const i=Date.now(),n=i-this.askedAt,s=this.spawnImpl(t,tsgoCheckArgs(this.shimPath,this.buildInfo),{cwd:this.opts.workspaceRoot,stdio:["ignore","pipe","pipe"],env:{...process.env,GOMAXPROCS:process.env.GOMAXPROCS??String(this.opts.maxProcs??checkerMaxProcs())}});this.inflight=s;let o="",r=!1;const c=this.opts.timeoutMs??DEFAULT_TIMEOUT_MS,u=setTimeout(()=>{r=!0,s.kill("SIGKILL")},c);u.unref?.(),s.stdout?.on("data",h=>{o+=h.toString()}),s.stderr?.on("data",h=>{o+=h.toString()}),s.on("error",()=>{clearTimeout(u),this.inflight===s&&(console.warn(`[tsgo] ${this.opts.appName}: failed to spawn ${t}`),this.forgetBuildInfo(),this.publish({output:"",durationMs:Date.now()-i,toolError:`failed to spawn ${t}`}),this.finish(s))}),s.on("close",(h,p)=>{if(clearTimeout(u),this.inflight!==s)return;const d=Date.now()-i,f=(d/1e3).toFixed(1),m=n>=1e3?`${f}s, waited ${(n/1e3).toFixed(1)}s`:`${f}s`;if(this.disposed)return this.finish(s);if(r){const k=`the check was killed after ${Math.round(c/1e3)}s without finishing`;return console.warn(`[tsgo] ${this.opts.appName}: ${k} \u2014 no verdict.`),this.forgetBuildInfo(),this.publish({output:"",durationMs:d,toolError:k}),this.finish(s)}if(p)return this.forgetBuildInfo(),this.finish(s);if(h===0)return console.log(`[tsgo] ${this.opts.appName}: types clean (${m})`),this.publish({output:"",durationMs:d}),this.finish(s);o.trim()&&process.stdout.write(o.endsWith(`
|
|
3
|
+
`)?o:`${o}
|
|
4
|
+
`);const g=(o.match(/error TS\d+/g)??[]).length;console.log(`[tsgo] ${this.opts.appName}: ${g} type error${g===1?"":"s"} (${m})`),g===0&&this.forgetBuildInfo(),this.publish({output:o,durationMs:d,...g===0?{toolError:`tsgo exited ${h} with no diagnostics: ${firstLine(o)}`}:{}}),this.finish(s)})}finish(t){this.inflight===t&&(this.inflight=null),this.leave(),this.dirty&&!this.disposed&&(this.dirty=!1,this.request())}leave(){this.phase="idle",this.ticket?.release(),this.ticket=null}dispose(){this.disposed||(this.disposed=!0,livePlugins.delete(this),this.timer&&clearTimeout(this.timer),this.inflight&&(this.inflight.kill("SIGKILL"),this.forgetBuildInfo()),this.ticket?.release(),this.ticket=null)}}exports.TsgoCheckPlugin=TsgoCheckPlugin;function firstLine(e){return e.trim().split(`
|
|
5
|
+
`)[0]?.slice(0,300)??""}a(firstLine,"firstLine"),l(firstLine,"firstLine");
|
package/typecheck-bus.d.ts
CHANGED
|
@@ -96,3 +96,27 @@ export declare function publishTypecheckRun(args: {
|
|
|
96
96
|
toolError?: string;
|
|
97
97
|
eventFile?: string;
|
|
98
98
|
}): TypecheckBusEvent[];
|
|
99
|
+
/**
|
|
100
|
+
* A check whose turn came and is held for memory (issues/closed/foodguard-94) — `typecheck-queue.ts` holds the head of
|
|
101
|
+
* the line while the machine has less free than a check waits for. Said once per held check, so a check that has not
|
|
102
|
+
* run reads «held», not «slow». No verdict: `phase: 'held'` is neither a run's START (its title does NOT start with
|
|
103
|
+
* `tsc clear`, which an older reducer resets on) nor its END. Producer-only — session's mirror has no checker to hold.
|
|
104
|
+
*/
|
|
105
|
+
export declare function buildTypecheckHeldEvent(args: {
|
|
106
|
+
project: string;
|
|
107
|
+
tool: string;
|
|
108
|
+
tsConfig?: string;
|
|
109
|
+
availableMb: number;
|
|
110
|
+
minFreeMb: number;
|
|
111
|
+
now?: () => number;
|
|
112
|
+
idImpl?: () => string;
|
|
113
|
+
}): TypecheckBusEvent;
|
|
114
|
+
export declare function publishTypecheckHeld(args: {
|
|
115
|
+
workspaceRoot: string;
|
|
116
|
+
project: string;
|
|
117
|
+
tool: string;
|
|
118
|
+
tsConfig?: string;
|
|
119
|
+
availableMb: number;
|
|
120
|
+
minFreeMb: number;
|
|
121
|
+
eventFile?: string;
|
|
122
|
+
}): TypecheckBusEvent;
|
package/typecheck-bus.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)){const
|
|
1
|
+
"use strict";var g=Object.defineProperty;var n=(t,e)=>g(t,"name",{value:e,configurable:!0});var f=Object.defineProperty,o=n((t,e)=>f(t,"name",{value:e,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.MAX_DIAGNOSTICS_PER_RUN=void 0,exports.stripAnsi=stripAnsi,exports.parseTscDiagnosticLine=parseTscDiagnosticLine,exports.parseTscOutput=parseTscOutput,exports.buildTypecheckRunEvents=buildTypecheckRunEvents,exports.resolveEventFile=resolveEventFile,exports.appendTypecheckEvents=appendTypecheckEvents,exports.publishTypecheckDiagnostics=publishTypecheckDiagnostics,exports.publishTypecheckRun=publishTypecheckRun,exports.buildTypecheckHeldEvent=buildTypecheckHeldEvent,exports.publishTypecheckHeld=publishTypecheckHeld;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),busHygiene=(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})(),DIAG_LINE_REGEX=/^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s+(.+)$/,ANSI_CSI=new RegExp("\x1B\\[[0-9;]*[A-Za-z]","g");function stripAnsi(t){return t.replace(ANSI_CSI,"")}n(stripAnsi,"stripAnsi"),o(stripAnsi,"stripAnsi");function parseTscDiagnosticLine(t){const e=stripAnsi(t).trim();if(!e)return null;const i=DIAG_LINE_REGEX.exec(e);if(!i)return null;const[,s,l,u,c,a,p]=i;return!s||!l||!u||!c||!a||!p?null:{file:s,line:Number(l),column:Number(u),severity:c==="warning"?"warning":"error",code:a,message:p}}n(parseTscDiagnosticLine,"parseTscDiagnosticLine"),o(parseTscDiagnosticLine,"parseTscDiagnosticLine");function parseTscOutput(t){const e=[];for(const i of t.split(`
|
|
2
|
+
`)){const s=parseTscDiagnosticLine(i);s&&e.push(s)}return e}n(parseTscOutput,"parseTscOutput"),o(parseTscOutput,"parseTscOutput"),exports.MAX_DIAGNOSTICS_PER_RUN=200;let idCounter=0;function makeId(){return idCounter=(idCounter+1)%65535,`${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2,8)}`}n(makeId,"makeId"),o(makeId,"makeId");function buildTypecheckRunEvents(t){const e=(t.now??Date.now)(),i=t.idImpl??makeId,s="pending",l={sessionId:s,source:"typescript",category:"typecheck",context:{sessionId:s,projectName:t.project}},u={project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{}},c=t.diagnostics.filter(r=>r.severity==="error"),a=t.diagnostics.slice(0,exports.MAX_DIAGNOSTICS_PER_RUN),p=[];if(!t.toolError){p.push({...l,id:i(),timestamp:e,severity:"info",title:`tsc clear: ${t.project}`,message:`typecheck run starting for ${t.project}`,fingerprint:`tsc:${t.project}:run-start`,raw:{...u,kind:"typecheck-run",phase:"start"}});for(const r of a)p.push({...l,id:i(),timestamp:e,severity:r.severity,title:`${r.code}: ${r.message}`,message:r.message,location:{file:r.file,line:r.line,column:r.column},fingerprint:`tsc:${t.project}:${r.file}:${r.line}:${r.column}:${r.code}`,raw:{...u,kind:"typecheck-diagnostic",code:r.code}})}const d=t.toolError?"error":c.length>0?"failing":"clean";return p.push({...l,id:i(),timestamp:e,severity:"info",title:`typecheck run: ${t.project} \u2014 ${t.toolError?"tool error":`${c.length} error${c.length===1?"":"s"}`} (${t.durationMs}ms)`,...t.toolError?{message:t.toolError}:{},fingerprint:`tsc:${t.project}:run-end`,raw:{...u,kind:"typecheck-run",phase:"end",status:d,durationMs:t.durationMs,errorCount:c.length,warningCount:t.diagnostics.length-c.length,...t.diagnostics.length>a.length?{truncatedDiagnostics:t.diagnostics.length-a.length}:{},...t.toolError?{error:t.toolError}:{}}}),p}n(buildTypecheckRunEvents,"buildTypecheckRunEvents"),o(buildTypecheckRunEvents,"buildTypecheckRunEvents");function resolveEventFile(t){return process.env.LENSMCP_EVENT_FILE||path.join(t,".lensmcp","events.jsonl")}n(resolveEventFile,"resolveEventFile"),o(resolveEventFile,"resolveEventFile");let ensuredDir=null;function ensureDir(t){const e=path.dirname(t);ensuredDir!==e&&(fs.mkdirSync(e,{recursive:!0}),ensuredDir=e)}n(ensureDir,"ensureDir"),o(ensureDir,"ensureDir");function appendTypecheckEvents(t,e){if(e.length===0)return;const i=e.map(s=>JSON.stringify(s)).join(`
|
|
3
3
|
`)+`
|
|
4
|
-
`;try{ensureDir(
|
|
4
|
+
`;try{ensureDir(t),busHygiene?.maybeRotateEventFile(t,i.length),fs.appendFileSync(t,i)}catch(s){if(s?.code==="ENOENT"){try{ensuredDir=null,ensureDir(t),fs.appendFileSync(t,i)}catch{}return}}}n(appendTypecheckEvents,"appendTypecheckEvents"),o(appendTypecheckEvents,"appendTypecheckEvents");function publishTypecheckDiagnostics(t){const e=buildTypecheckRunEvents({project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{},diagnostics:t.diagnostics,durationMs:t.durationMs});return appendTypecheckEvents(t.eventFile??resolveEventFile(t.workspaceRoot),e),e}n(publishTypecheckDiagnostics,"publishTypecheckDiagnostics"),o(publishTypecheckDiagnostics,"publishTypecheckDiagnostics");function publishTypecheckRun(t){const e=buildTypecheckRunEvents({project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{},diagnostics:t.toolError?[]:parseTscOutput(t.output),durationMs:t.durationMs,...t.toolError?{toolError:t.toolError}:{}});return appendTypecheckEvents(t.eventFile??resolveEventFile(t.workspaceRoot),e),e}n(publishTypecheckRun,"publishTypecheckRun"),o(publishTypecheckRun,"publishTypecheckRun");function buildTypecheckHeldEvent(t){const e="pending";return{id:(t.idImpl??makeId)(),sessionId:e,timestamp:(t.now??Date.now)(),source:"typescript",category:"typecheck",severity:"info",context:{sessionId:e,projectName:t.project},title:`typecheck held: ${t.project} \u2014 ${t.availableMb} MB free, a check waits for ${t.minFreeMb} MB`,message:`the type check of ${t.project} waits until ${t.minFreeMb} MB is free (LENSMCP_TYPECHECK_MIN_FREE_MB)`,fingerprint:`tsc:${t.project}:run-held`,raw:{project:t.project,tool:t.tool,...t.tsConfig?{tsConfig:t.tsConfig}:{},kind:"typecheck-run",phase:"held",availableMb:t.availableMb,minFreeMb:t.minFreeMb}}}n(buildTypecheckHeldEvent,"buildTypecheckHeldEvent"),o(buildTypecheckHeldEvent,"buildTypecheckHeldEvent");function publishTypecheckHeld(t){const e=buildTypecheckHeldEvent(t);return appendTypecheckEvents(t.eventFile??resolveEventFile(t.workspaceRoot),[e]),e}n(publishTypecheckHeld,"publishTypecheckHeld"),o(publishTypecheckHeld,"publishTypecheckHeld");
|
package/typecheck-queue.d.ts
CHANGED
|
@@ -2,8 +2,17 @@ export interface QueueTicket {
|
|
|
2
2
|
/** Give the slot back, or leave the line. Idempotent. */
|
|
3
3
|
release(): void;
|
|
4
4
|
}
|
|
5
|
+
/** What a caller in line is told besides its turn. */
|
|
6
|
+
export interface QueueHooks {
|
|
7
|
+
/** Its turn came, and is held: the machine has less memory free than a check waits for. Called once per ticket. */
|
|
8
|
+
onHold?(hold: MemoryHold): void;
|
|
9
|
+
}
|
|
10
|
+
export interface MemoryHold {
|
|
11
|
+
availableMb: number;
|
|
12
|
+
minFreeMb: number;
|
|
13
|
+
}
|
|
5
14
|
/** Join the line; `onTurn` is called once, when this caller may run. */
|
|
6
|
-
export type TypecheckQueue = (label: string, onTurn: () => void) => QueueTicket;
|
|
15
|
+
export type TypecheckQueue = (label: string, onTurn: () => void, hooks?: QueueHooks) => QueueTicket;
|
|
7
16
|
export interface MachineQueueOptions {
|
|
8
17
|
dir?: string;
|
|
9
18
|
slots?: number;
|
|
@@ -20,11 +29,25 @@ export interface MachineQueueOptions {
|
|
|
20
29
|
pid?: number;
|
|
21
30
|
isAlive?: (pid: number) => boolean;
|
|
22
31
|
now?: () => number;
|
|
32
|
+
/** A turn is held while less than this is free (MB); 0 holds nothing. Default {@link typecheckMinFreeMb}. */
|
|
33
|
+
minFreeMb?: number;
|
|
34
|
+
/** The memory the OS can still hand out, in MB; undefined when it cannot tell. Default {@link availableMemoryMb}. */
|
|
35
|
+
availableMb?: () => number | undefined;
|
|
36
|
+
/** How often a held turn looks at the memory again. */
|
|
37
|
+
holdPollMs?: number;
|
|
23
38
|
}
|
|
24
39
|
/** `~/.lensmcp/typecheck-queue` — machine-wide on purpose; `LENSMCP_HOME` moves it (tests, sandboxes). */
|
|
25
40
|
export declare function typecheckQueueDir(env?: NodeJS.ProcessEnv): string;
|
|
26
41
|
/** `LENSMCP_TYPECHECK_CONCURRENCY`, default ONE. Anything unreadable is one, never unlimited. */
|
|
27
42
|
export declare function typecheckSlots(env?: NodeJS.ProcessEnv): number;
|
|
43
|
+
/** `LENSMCP_TYPECHECK_MIN_FREE_MB`, default 2048; `0` turns the hold off. Anything unreadable is the default. */
|
|
44
|
+
export declare function typecheckMinFreeMb(env?: NodeJS.ProcessEnv): number;
|
|
45
|
+
/**
|
|
46
|
+
* What the OS can still hand out, in MB — `process.availableMemory()`: free plus reclaimable on macOS (free + inactive
|
|
47
|
+
* pages), and a container's own limit under cgroups. Not `os.freemem()`: on macOS that counts only the free pages, a
|
|
48
|
+
* few hundred MB on a busy machine that has gigabytes to give. A Node without it cannot tell: undefined, nothing held.
|
|
49
|
+
*/
|
|
50
|
+
export declare function availableMemoryMb(): number | undefined;
|
|
28
51
|
/**
|
|
29
52
|
* The line, oldest first. A ticket whose owner is dead — or that has sat past `maxAgeMs` — is
|
|
30
53
|
* deleted on the way, so a crashed service never holds a slot.
|
package/typecheck-queue.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var P=Object.defineProperty;var i=(e,t)=>P(e,"name",{value:t,configurable:!0});var x=Object.defineProperty,n=i((e,t)=>x(e,"name",{value:t,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.immediateQueue=void 0,exports.typecheckQueueDir=typecheckQueueDir,exports.typecheckSlots=typecheckSlots,exports.typecheckMinFreeMb=typecheckMinFreeMb,exports.availableMemoryMb=availableMemoryMb,exports.liveTickets=liveTickets,exports.machineTypecheckQueue=machineTypecheckQueue;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),TICKET_SUFFIX=".ticket",DEFAULT_POLL_MS=250,DEFAULT_SETTLE_MS=120,DEFAULT_MAX_AGE_MS=15*6e4,MAX_SLOTS=8,DEFAULT_HOLD_POLL_MS=2e3,DEFAULT_MIN_FREE_MB=2048;function typecheckQueueDir(e=process.env){return path.join(e.LENSMCP_HOME||os.homedir(),".lensmcp","typecheck-queue")}i(typecheckQueueDir,"typecheckQueueDir"),n(typecheckQueueDir,"typecheckQueueDir");function typecheckSlots(e=process.env){const t=Number(e.LENSMCP_TYPECHECK_CONCURRENCY);return Number.isInteger(t)&&t>=1?Math.min(t,MAX_SLOTS):1}i(typecheckSlots,"typecheckSlots"),n(typecheckSlots,"typecheckSlots");function typecheckMinFreeMb(e=process.env){const t=e.LENSMCP_TYPECHECK_MIN_FREE_MB,o=Number(t);return t!==void 0&&t.trim()!==""&&Number.isFinite(o)&&o>=0?Math.round(o):DEFAULT_MIN_FREE_MB}i(typecheckMinFreeMb,"typecheckMinFreeMb"),n(typecheckMinFreeMb,"typecheckMinFreeMb");function availableMemoryMb(){const e=process.availableMemory;if(typeof e=="function")try{const t=e.call(process);return Number.isFinite(t)&&t>0?Math.round(t/(1024*1024)):void 0}catch{return}}i(availableMemoryMb,"availableMemoryMb"),n(availableMemoryMb,"availableMemoryMb");function pidAlive(e){try{return process.kill(e,0),!0}catch(t){return t.code==="EPERM"}}i(pidAlive,"pidAlive"),n(pidAlive,"pidAlive");const ticketPid=n(e=>{const t=Number(e.split("-")[0]);return Number.isInteger(t)&&t>0?t:void 0},"ticketPid");function liveTickets(e,t={}){const o=t.isAlive??pidAlive,_=(t.now??Date.now)(),p=t.maxAgeMs??DEFAULT_MAX_AGE_MS;let u;try{u=fs.readdirSync(e).filter(r=>r.endsWith(TICKET_SUFFIX))}catch{return[]}const s=[];for(const r of u){const c=path.join(e,r);let a;try{a=fs.statSync(c,{bigint:!0})}catch{continue}const M=ticketPid(r);if(M===void 0||!o(M)||_-Number(a.mtimeMs)>p){try{fs.unlinkSync(c)}catch{}continue}s.push({name:r,at:a.mtimeNs})}return s.sort((r,c)=>r.at<c.at?-1:r.at>c.at?1:r.name<c.name?-1:r.name>c.name?1:0),s.map(r=>r.name)}i(liveTickets,"liveTickets"),n(liveTickets,"liveTickets");let ticketSeq=0;function machineTypecheckQueue(e={}){const t=e.dir??typecheckQueueDir(),o=Math.max(1,e.slots??typecheckSlots()),_=e.pollMs??DEFAULT_POLL_MS,p=e.settleMs??DEFAULT_SETTLE_MS,u=e.pid??process.pid,s=e.minFreeMb??typecheckMinFreeMb(),r=e.availableMb??availableMemoryMb,c=e.holdPollMs??DEFAULT_HOLD_POLL_MS,a=n(()=>liveTickets(t,e),"scan"),M=n(()=>{if(s<=0)return;const l=r();return l!==void 0&&l<s?{availableMb:l,minFreeMb:s}:void 0},"memoryHold");return(l,N,L)=>{const v=`${u}-${(ticketSeq++).toString(36)}-${Math.random().toString(36).slice(2,8)}${TICKET_SUFFIX}`,S=path.join(t,v);let m=!1,b=!1,k=!1,d;const f=n(()=>{m||b||(b=!0,N())},"turn"),E=n(()=>{try{return fs.mkdirSync(t,{recursive:!0}),fs.writeFileSync(S,JSON.stringify({pid:u,label:l,at:(e.now??Date.now)()})),!0}catch{return!1}},"write"),y=n(h=>{d=setTimeout(T,h),d.unref?.()},"later");function T(){if(m||b)return;const h=a().indexOf(v);if(h===-1)return E()?y(p):f();if(h<o){const F=M();if(!F)return f();if(!k){k=!0;try{L?.onHold?.(F)}catch{}}return y(c)}y(_)}return i(T,"L"),n(T,"look"),E()?(y(p),{release(){if(!m){m=!0,d&&clearTimeout(d);try{fs.unlinkSync(S)}catch{}}}}):(f(),{release(){m=!0}})}}i(machineTypecheckQueue,"machineTypecheckQueue"),n(machineTypecheckQueue,"machineTypecheckQueue");const immediateQueue=n((e,t)=>(t(),{release(){}}),"immediateQueue");exports.immediateQueue=immediateQueue;
|