@lensmcp/cluster 1.23.12 → 1.24.0

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.
Files changed (107) hide show
  1. package/build-host.d.ts +1 -0
  2. package/build-host.js +1 -0
  3. package/create-webpack-dev.d.ts +75 -39
  4. package/create-webpack-dev.js +1 -1
  5. package/dev-devtool.d.ts +45 -0
  6. package/dev-devtool.js +1 -0
  7. package/devserver-notifier.d.ts +57 -0
  8. package/devserver-notifier.js +1 -0
  9. package/executors/gateway/body-stall.d.ts +13 -1
  10. package/executors/gateway/body-stall.js +1 -1
  11. package/executors/gateway/canonical-path.d.ts +1 -1
  12. package/executors/gateway/client-ip.d.ts +1 -1
  13. package/executors/gateway/gateway.impl.js +1 -1
  14. package/executors/gateway/jwt-claims.d.ts +1 -1
  15. package/executors/gateway/prod-config.d.ts +1 -1
  16. package/executors/gateway/prod-runtime/body-stall.d.ts +1 -1
  17. package/executors/gateway/rate-limit.d.ts +1 -1
  18. package/executors/gateway/runtime/auth.d.ts +1 -1
  19. package/executors/gateway/runtime/chooser.d.ts +2 -2
  20. package/executors/gateway/runtime/control.d.ts +3 -3
  21. package/executors/gateway/runtime/control.js +2 -2
  22. package/executors/gateway/runtime/dev-auth.d.ts +1 -1
  23. package/executors/gateway/runtime/edge.d.ts +3 -3
  24. package/executors/gateway/runtime/guest-graph.d.ts +113 -0
  25. package/executors/gateway/runtime/guest-graph.js +2 -0
  26. package/executors/gateway/runtime/lens-children.d.ts +11 -1
  27. package/executors/gateway/runtime/lens-children.js +1 -1
  28. package/executors/gateway/runtime/lifecycle.d.ts +74 -3
  29. package/executors/gateway/runtime/lifecycle.js +3 -3
  30. package/executors/gateway/runtime/process-names.d.ts +18 -0
  31. package/executors/gateway/runtime/process-names.js +1 -0
  32. package/executors/gateway/runtime/project-graph-store.d.ts +74 -0
  33. package/executors/gateway/runtime/project-graph-store.js +1 -0
  34. package/executors/gateway/runtime/proxy.d.ts +2 -2
  35. package/executors/gateway/runtime/proxy.js +1 -1
  36. package/executors/gateway/runtime/registration-guards.d.ts +32 -0
  37. package/executors/gateway/runtime/registration-guards.js +1 -0
  38. package/executors/gateway/runtime/scope.d.ts +1 -1
  39. package/executors/gateway/runtime/serve-launch.d.ts +118 -0
  40. package/executors/gateway/runtime/serve-launch.js +3 -0
  41. package/executors/gateway/runtime/server.d.ts +3 -3
  42. package/executors/gateway/runtime/server.js +2 -2
  43. package/executors/gateway/runtime/shared-build.d.ts +65 -0
  44. package/executors/gateway/runtime/shared-build.js +4 -0
  45. package/executors/gateway/runtime/types.d.ts +35 -9
  46. package/executors/gateway/runtime/types.js +1 -1
  47. package/executors/gateway/runtime/workspace-log.js +1 -1
  48. package/executors/gateway/schema.d.ts +3 -0
  49. package/executors/gateway/schema.json +4 -0
  50. package/executors/serve/schema.d.ts +2 -0
  51. package/executors/serve/schema.json +4 -0
  52. package/executors/serve/serve.impl.d.ts +12 -6
  53. package/executors/serve/serve.impl.js +1 -1
  54. package/executors/trust/hostname.d.ts +1 -1
  55. package/exit-reason.d.ts +1 -1
  56. package/fork-ts-check-reporter.d.ts +12 -0
  57. package/fork-ts-check-reporter.js +1 -1
  58. package/graph-snapshot.d.ts +1 -0
  59. package/graph-snapshot.js +1 -0
  60. package/heap-snapshot.d.ts +66 -0
  61. package/heap-snapshot.js +1 -0
  62. package/hot-swap-registries.d.ts +1 -1
  63. package/inspector-secret.d.ts +39 -0
  64. package/inspector-secret.js +1 -0
  65. package/main.devserver.d.ts +50 -4
  66. package/main.devserver.js +11 -9
  67. package/package.json +4 -4
  68. package/perf/heavy-barrel-import.d.ts +135 -0
  69. package/perf/heavy-barrel-import.js +1 -0
  70. package/perf/perf-config.d.ts +29 -0
  71. package/perf/perf-config.js +1 -0
  72. package/perf/perf-rule-events.d.ts +71 -0
  73. package/perf/perf-rule-events.js +2 -0
  74. package/perf/perf-rules-analysis.d.ts +28 -0
  75. package/perf/perf-rules-analysis.js +1 -0
  76. package/perf/perf-rules-plugin.d.ts +71 -0
  77. package/perf/perf-rules-plugin.js +1 -0
  78. package/perf/perf-rules-worker.d.ts +1 -0
  79. package/perf/perf-rules-worker.js +1 -0
  80. package/perf/source-scan.d.ts +59 -0
  81. package/perf/source-scan.js +5 -0
  82. package/perf/workspace-libraries.d.ts +52 -0
  83. package/perf/workspace-libraries.js +2 -0
  84. package/pod-memory-events.d.ts +71 -0
  85. package/pod-memory-events.js +2 -0
  86. package/pod-memory.d.ts +258 -0
  87. package/pod-memory.js +1 -0
  88. package/process-name.d.ts +45 -0
  89. package/process-name.js +1 -1
  90. package/serve-runner.d.ts +2 -0
  91. package/serve-runner.js +1 -0
  92. package/shared-build/attach.d.ts +56 -0
  93. package/shared-build/attach.js +3 -0
  94. package/shared-build/host.d.ts +52 -0
  95. package/shared-build/host.js +4 -0
  96. package/shared-build/protocol.d.ts +118 -0
  97. package/shared-build/protocol.js +1 -0
  98. package/shared-build/shared-compilation.d.ts +91 -0
  99. package/shared-build/shared-compilation.js +3 -0
  100. package/shared-build/signature.d.ts +52 -0
  101. package/shared-build/signature.js +1 -0
  102. package/stdio-guard.d.ts +1 -1
  103. package/swap-recycle.d.ts +21 -0
  104. package/swap-recycle.js +1 -1
  105. package/tsgo-check-plugin.d.ts +1 -1
  106. package/workspace-paths.d.ts +76 -0
  107. package/workspace-paths.js +1 -0
@@ -0,0 +1,258 @@
1
+ export interface PodMemoryPolicy {
2
+ /** `LENSMCP_POD_GC=0` turns off the scheduled collections, the idle one and the reports they make. */
3
+ enabled: boolean;
4
+ /** When to collect after a swap, from the swap — in addition to the collection once it has settled. */
5
+ scheduleMs: number[];
6
+ /** Collect once the pod has been quiet this long after traffic or a swap. `0` = off. */
7
+ idleMs: number;
8
+ /** …at most once per this long. */
9
+ idleEveryMs: number;
10
+ /** How long a closed generation may stay in memory before it is called retained. */
11
+ retainedAfterMs: number;
12
+ /** How quiet the pod must be before a swap counts as settled. */
13
+ settleMs: number;
14
+ }
15
+ /** Full GCs a closed generation must survive before it is called retained: one could have raced its last timer. */
16
+ export declare const RETAINED_MIN_GCS = 2;
17
+ /**
18
+ * - `LENSMCP_POD_GC` — `0`/`false`/`off`/`no` disables the scheduled and idle collections (and their reports).
19
+ * - `LENSMCP_POD_GC_SCHEDULE_MS` — default `30000,120000,300000`: the collections after a swap, by delay from it.
20
+ * - `LENSMCP_POD_GC_IDLE_MS` — default `60000`. `0` disables the idle collection.
21
+ * - `LENSMCP_POD_GC_IDLE_EVERY_MS` — default `300000`.
22
+ * - `LENSMCP_POD_RETAINED_AFTER_MS` — default `300000` (5 min).
23
+ * - `LENSMCP_POD_GC_SETTLE_MS` — default `2000`.
24
+ */
25
+ export declare function podMemoryPolicy(env?: NodeJS.ProcessEnv): PodMemoryPolicy;
26
+ /** What made the pod collect. `swap+<delay>` names a scheduled run by its delay from the swap. */
27
+ export type PodMemoryTrigger = 'boot' | 'swap-settled' | `swap+${string}` | 'idle' | 'retention-check';
28
+ /** One post-GC reading. MIRROR of `PodMemoryReportRaw` in `@lensmcp/protocol-types` (cluster does not depend on it). */
29
+ export interface PodMemoryReport {
30
+ trigger: PodMemoryTrigger;
31
+ at: number;
32
+ /** The app generation serving now (1 = the one the pod booted with). */
33
+ gen: number;
34
+ /** Hot swaps since boot. */
35
+ swaps: number;
36
+ /** Heap in use after a full GC. */
37
+ live: number;
38
+ /** Heap in use just before it — garbage included. */
39
+ used: number;
40
+ /** Heap V8 has reserved after the GC (`heapTotal`). */
41
+ reserved: number;
42
+ rss: number;
43
+ external: number;
44
+ /** The young generation's reserved size. */
45
+ youngGen: number;
46
+ /** V8's heap limit. */
47
+ limit: number;
48
+ /** Live heap right after the first app was built — what one generation of this service weighs. */
49
+ baseline?: number;
50
+ /** How long the collection paused the pod. */
51
+ gcMs: number;
52
+ /** The generation this pod's latest swap replaced. */
53
+ previousGen?: number;
54
+ /** Is it still in memory? `null` when there is none (no swap yet). */
55
+ previousAlive: boolean | null;
56
+ /** Closed generations still in memory, whatever their age. */
57
+ closedAlive: number;
58
+ /** …of them, the ones past the retained bound. */
59
+ retained: number;
60
+ /** Live `NestContainer` instances, when they can be counted. */
61
+ containers?: number;
62
+ /** Whether this process maps stack traces through source maps (`--enable-source-maps`) — what it costs. */
63
+ sourceMaps: boolean;
64
+ }
65
+ /** A closed generation proven still in memory. MIRROR of `PodMemoryRetainedRaw`. */
66
+ export interface RetainedGenerationReport {
67
+ gen: number;
68
+ closedAt: number;
69
+ ageMs: number;
70
+ /** Swaps since this generation's app was closed. */
71
+ swapsSinceClose: number;
72
+ /** Full GCs it survived. */
73
+ gcs: number;
74
+ /** Which of its registered objects are still alive: `module`, `app`, `nestApp`, `container`, `rootModule`. */
75
+ alive: string[];
76
+ /** Live instances of the classes a swapped app leaks through, where they could be counted. */
77
+ counts?: Record<string, number>;
78
+ /** Heap in use after the GC that proved it. */
79
+ live: number;
80
+ }
81
+ /** A retained generation collected after all, or gone with its pod. MIRROR of `PodMemoryReleasedRaw`. */
82
+ export interface ReleasedGenerationReport {
83
+ gen: number;
84
+ closedAt: number;
85
+ ageMs: number;
86
+ reason: 'collected' | 'pod-exited';
87
+ }
88
+ /** Report message: one post-GC reading. */
89
+ export interface PodMemoryReportMessage {
90
+ type: 'pod-memory';
91
+ report: PodMemoryReport;
92
+ }
93
+ /** Report message: a closed generation is proven retained. */
94
+ export interface PodMemoryRetainedMessage {
95
+ type: 'pod-memory-retained';
96
+ retained: RetainedGenerationReport;
97
+ }
98
+ /** Report message: a generation that was reported retained is gone. */
99
+ export interface PodMemoryReleasedMessage {
100
+ type: 'pod-memory-released';
101
+ released: ReleasedGenerationReport;
102
+ }
103
+ export type PodMemoryMessage = PodMemoryReportMessage | PodMemoryRetainedMessage | PodMemoryReleasedMessage;
104
+ /**
105
+ * A full, synchronous GC. V8 only hands out `gc` to a context created after the flag is set, hence the fresh
106
+ * context. `false` where it will not (the reading is then simply uncollected).
107
+ */
108
+ export declare function fullGc(): boolean;
109
+ /**
110
+ * Live instances of `ctor` — a full GC and a walk of the heap — or `undefined` where Node cannot count.
111
+ *
112
+ * `v8.queryObjects` is experimental and says so with a process warning the first time; a pod's output is the
113
+ * developer's console, and that line would be noise the lens put there. The warning is dropped for this call
114
+ * only (Node emits it once per process, so later calls have nothing to drop).
115
+ */
116
+ export declare function countLiveInstances(ctor: unknown): number | undefined;
117
+ /** Counts instances of the shared classes a bundle loaded, by name. */
118
+ export interface LiveObjectCounter {
119
+ /** Live `NestContainer` instances, or `undefined` when the bundle has no Nest or Node cannot count. */
120
+ containers(): number | undefined;
121
+ /** Every class it can count — one full GC each, so only when something is being reported. */
122
+ all(): Record<string, number> | undefined;
123
+ }
124
+ /**
125
+ * Resolve each class from the BUNDLE's directory and take it out of the module cache — never `require` it:
126
+ * a pod that does not use TypeORM must not load TypeORM to find out. Externals are shared by every generation
127
+ * (webpack-node-externals), so one class counts the instances every generation made.
128
+ */
129
+ export declare function createLiveObjectCounter(bundlePath: string, cache?: Record<string, {
130
+ exports?: unknown;
131
+ } | undefined>): LiveObjectCounter;
132
+ /** What a generation is made of, as far as this process can see. Every one is held WEAKLY once registered. */
133
+ export interface GenerationTargets {
134
+ /** The bundle's CJS Module — every closure the bundle made reaches it through `require`'s scope. */
135
+ module?: object;
136
+ /** What `createChildApp()` returned. */
137
+ app?: object;
138
+ /** The Nest application, when the app exposes it. */
139
+ nestApp?: object;
140
+ /** Its DI container. */
141
+ container?: object;
142
+ /** Its root module class. */
143
+ rootModule?: object;
144
+ }
145
+ /**
146
+ * The marker a heap snapshot finds a closed generation by: its class name, its `label` string, and a WeakRef
147
+ * per target. Weak on purpose — the marker must never be the reason a generation is alive — and named so the
148
+ * snapshot analyser (`lensmcp mem <svc> --snapshot --analyze`) can find it and walk the shortest path from the
149
+ * GC roots to whatever is still alive.
150
+ */
151
+ export declare class LensmcpClosedGeneration {
152
+ readonly gen: number;
153
+ readonly label: string;
154
+ readonly refs: Record<string, WeakRef<object>>;
155
+ constructor(gen: number, targets: GenerationTargets);
156
+ }
157
+ /** One closed generation, as the tracker sees it. */
158
+ export interface ClosedGenerationView {
159
+ gen: number;
160
+ closedAt: number;
161
+ swapsAtClose: number;
162
+ /** Full GCs since its close. */
163
+ gcs: number;
164
+ /** Names of the targets not yet collected. */
165
+ alive: string[];
166
+ /** When it was reported retained, if it was. */
167
+ reportedAt?: number;
168
+ }
169
+ /** Callbacks the tracker reports through. */
170
+ export interface GenerationTrackerOptions {
171
+ /** A closed generation's last registered target was collected. `reported` = it had been called retained. */
172
+ onFreed?: (view: ClosedGenerationView, reported: boolean) => void;
173
+ now?: () => number;
174
+ }
175
+ /**
176
+ * Which closed generations are still in memory, by FinalizationRegistry — never by holding them.
177
+ */
178
+ export declare class GenerationTracker {
179
+ private readonly opts;
180
+ private readonly closed;
181
+ /** Generations proven gone, most recent last (bounded) — so "is the previous one gone?" has an answer. */
182
+ private readonly freed;
183
+ private readonly registry;
184
+ private readonly now;
185
+ constructor(opts?: GenerationTrackerOptions);
186
+ /** A generation's app has been closed: from here on, everything it was made of should go. */
187
+ close(gen: number, swapsAtClose: number, targets: GenerationTargets): void;
188
+ /** A full GC ran: every closed generation still here survived one more. */
189
+ noteGc(): void;
190
+ /** `true` still in memory, `false` proven gone, `undefined` never closed here. */
191
+ isAlive(gen: number): boolean | undefined;
192
+ views(): ClosedGenerationView[];
193
+ /** Closed generations past the bound and through enough GCs, not yet reported. */
194
+ dueForReport(retainedAfterMs: number, at?: number): ClosedGenerationView[];
195
+ /** How many closed generations are past the bound (reported or about to be). */
196
+ retainedCount(retainedAfterMs: number, at?: number): number;
197
+ markReported(gen: number, at?: number): void;
198
+ /** The oldest generation proven retained — what the recycle rule weighs. */
199
+ oldestRetained(retainedAfterMs: number, at?: number): ClosedGenerationView | undefined;
200
+ /** The markers, for a heap snapshot to find (it reads them; nothing here does). */
201
+ markers(): LensmcpClosedGeneration[];
202
+ /** The pod is going away: whatever was reported retained goes with it. */
203
+ forgetAll(): ClosedGenerationView[];
204
+ private collected;
205
+ private drop;
206
+ private markFreed;
207
+ private view;
208
+ }
209
+ /** What the monitor needs to know about the pod it runs in. */
210
+ export interface PodMemoryHostState {
211
+ /** The generation serving now. */
212
+ gen: number;
213
+ swaps: number;
214
+ baseline?: number;
215
+ /** A swap is running or queued, or a previous app is still closing. */
216
+ busy: boolean;
217
+ /** The last moment a swap started or ended, or a previous app finished closing. */
218
+ lastBusyAt: number;
219
+ }
220
+ /** The pod the monitor runs in. */
221
+ export interface PodMemoryHost {
222
+ policy: PodMemoryPolicy;
223
+ state(): PodMemoryHostState;
224
+ send(message: PodMemoryMessage): void;
225
+ counter?: LiveObjectCounter;
226
+ /** A generation was just reported retained — the recycle rule's cue. */
227
+ onRetained?(oldest: ClosedGenerationView): void;
228
+ now?: () => number;
229
+ }
230
+ /** Drives the collections and the reports. One per pod. */
231
+ export interface PodMemoryMonitor {
232
+ readonly tracker: GenerationTracker;
233
+ /** The pod has built its first app. Measures and returns the boot baseline. */
234
+ booted(): Promise<PodMemoryReport>;
235
+ /** A swap replaced generation `previousGen`: run the post-swap schedule (a newer swap restarts it). */
236
+ swapped(previousGen: number | undefined, previousClosed: () => boolean): void;
237
+ /** A generation's app was closed. */
238
+ closed(gen: number, swaps: number, targets: GenerationTargets): void;
239
+ /** Request traffic, for the idle collection. */
240
+ activity(): void;
241
+ /** Collect and report now. Serialised: concurrent callers share one queue. */
242
+ measure(trigger: PodMemoryTrigger): Promise<PodMemoryReport>;
243
+ /** For the recycle rule. */
244
+ oldestRetained(): ClosedGenerationView | undefined;
245
+ /** The pod is exiting. */
246
+ stop(): void;
247
+ }
248
+ /** `30000` → `30s`, `120000` → `2m`. */
249
+ export declare function delayLabel(ms: number): string;
250
+ export declare function createPodMemoryMonitor(host: PodMemoryHost): PodMemoryMonitor;
251
+ /** `45000` → `45s`, `660000` → `11 min`. */
252
+ export declare function ageLabel(ms: number): string;
253
+ /** The one log line the parent prints per report. */
254
+ export declare function describePodMemory(who: string, r: PodMemoryReport): string;
255
+ /** The parent's line for a generation proven retained. */
256
+ export declare function describeRetained(who: string, service: string, r: RetainedGenerationReport): string;
257
+ /** The parent's line for a retained generation that went. */
258
+ export declare function describeReleased(who: string, service: string, r: ReleasedGenerationReport): string;
package/pod-memory.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var O=Object.defineProperty;var d=(s,e)=>O(s,"name",{value:e,configurable:!0});var L=Object.defineProperty,i=d((s,e)=>L(s,"name",{value:e,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.GenerationTracker=exports.LensmcpClosedGeneration=exports.RETAINED_MIN_GCS=void 0,exports.podMemoryPolicy=podMemoryPolicy,exports.fullGc=fullGc,exports.countLiveInstances=countLiveInstances,exports.createLiveObjectCounter=createLiveObjectCounter,exports.delayLabel=delayLabel,exports.createPodMemoryMonitor=createPodMemoryMonitor,exports.ageLabel=ageLabel,exports.describePodMemory=describePodMemory,exports.describeRetained=describeRetained,exports.describeReleased=describeReleased;const tslib_1=require("tslib"),node_module_1=require("node:module"),v8=tslib_1.__importStar(require("node:v8")),vm=tslib_1.__importStar(require("node:vm"));exports.RETAINED_MIN_GCS=2;const numberFrom=i((s,e,t)=>{if(s===void 0||s.trim()==="")return e;const r=Number(s);return Number.isFinite(r)&&r>=t?r:e},"numberFrom");function podMemoryPolicy(s=process.env){const e=["0","false","no","off"].includes((s.LENSMCP_POD_GC??"").trim().toLowerCase()),t=(s.LENSMCP_POD_GC_SCHEDULE_MS??"").split(",").map(r=>Number(r.trim())).filter(r=>Number.isFinite(r)&&r>0).map(Math.floor).sort((r,a)=>r-a);return{enabled:!e,scheduleMs:t.length?t:[3e4,12e4,3e5],idleMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_IDLE_MS,6e4,0)),idleEveryMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_IDLE_EVERY_MS,3e5,0)),retainedAfterMs:Math.floor(numberFrom(s.LENSMCP_POD_RETAINED_AFTER_MS,3e5,0)),settleMs:Math.floor(numberFrom(s.LENSMCP_POD_GC_SETTLE_MS,2e3,0))}}d(podMemoryPolicy,"podMemoryPolicy"),i(podMemoryPolicy,"podMemoryPolicy");let collector;function fullGc(){if(collector===void 0)try{v8.setFlagsFromString("--expose-gc");const s=vm.runInNewContext("gc");collector=typeof s=="function"?s:null}catch{collector=null}if(!collector)return!1;try{return collector(),!0}catch{return!1}}d(fullGc,"fullGc"),i(fullGc,"fullGc");function countLiveInstances(s){const e=v8.queryObjects;if(typeof e!="function"||typeof s!="function")return;const t=process.emitWarning;process.emitWarning=i(function(r,...a){const l=typeof a[0]=="string"?a[0]:a[0]?.type,n=typeof r=="string"?r:r?.message;if(!(l==="ExperimentalWarning"&&/queryObjects/.test(n??"")))return t.call(process,r,...a)},"quiet");try{const r=e(s,{format:"count"});return typeof r=="number"&&Number.isFinite(r)?r:void 0}catch{return}finally{process.emitWarning=t}}d(countLiveInstances,"countLiveInstances"),i(countLiveInstances,"countLiveInstances");const LEAK_CLASSES=[{name:"NestContainer",pkg:"@nestjs/core",pick:i(s=>s.NestContainer,"pick")},{name:"ZodObject",pkg:"zod",pick:i(s=>s.ZodObject,"pick")},{name:"DataSource",pkg:"typeorm",pick:i(s=>s.DataSource,"pick")},{name:"Redis",pkg:"ioredis",pick:i(s=>typeof s=="function"?s:s.default??s.Redis,"pick")},{name:"Worker",pkg:"bullmq",pick:i(s=>s.Worker,"pick")}];function createLiveObjectCounter(s,e=require.cache){let t;try{t=(0,node_module_1.createRequire)(s)}catch{t=void 0}const r=new Map,a=i(l=>{if(!t)return;let n=r.get(l.pkg);if(n===void 0){try{n=t.resolve(l.pkg)}catch{n=null}r.set(l.pkg,n)}if(!n)return;const p=e[n]?.exports;if(!(!p||typeof p!="object"&&typeof p!="function"))try{return l.pick(p)}catch{return}},"classOf");return{containers:i(()=>countLiveInstances(a(LEAK_CLASSES[0])),"containers"),all:i(()=>{const l={};for(const n of LEAK_CLASSES){const p=countLiveInstances(a(n));p!==void 0&&(l[n.name]=p)}return Object.keys(l).length?l:void 0},"all")}}d(createLiveObjectCounter,"createLiveObjectCounter"),i(createLiveObjectCounter,"createLiveObjectCounter");class LensmcpClosedGeneration{static{d(this,"LensmcpClosedGeneration")}static{i(this,"LensmcpClosedGeneration")}constructor(e,t){this.gen=e,this.refs={},this.label=`lensmcp closed generation ${e}`;for(const[r,a]of Object.entries(t))a&&(typeof a=="object"||typeof a=="function")&&(this.refs[r]=new WeakRef(a))}}exports.LensmcpClosedGeneration=LensmcpClosedGeneration;class GenerationTracker{static{d(this,"GenerationTracker")}static{i(this,"GenerationTracker")}constructor(e={}){this.opts=e,this.closed=new Map,this.freed=new Map,this.now=e.now??Date.now,this.registry=new FinalizationRegistry(t=>this.collected(t))}close(e,t,r){if(this.closed.has(e)||this.freed.has(e))return;const a=new LensmcpClosedGeneration(e,r),l=new Set(Object.keys(a.refs));if(l.size===0){this.markFreed(e);return}this.closed.set(e,{marker:a,closedAt:this.now(),swapsAtClose:t,gcs:0,alive:l});for(const[n,p]of Object.entries(a.refs)){const m=p.deref();m?this.registry.register(m,{gen:e,name:n}):l.delete(n)}l.size===0&&this.drop(e)}noteGc(){for(const e of this.closed.values())e.gcs+=1}isAlive(e){if(this.closed.has(e))return!0;if(this.freed.has(e))return!1}views(){return[...this.closed.entries()].map(([e,t])=>this.view(e,t))}dueForReport(e,t=this.now()){return this.views().filter(r=>r.reportedAt===void 0&&r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e)}retainedCount(e,t=this.now()){return this.views().filter(r=>r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e).length}markReported(e,t=this.now()){const r=this.closed.get(e);r&&(r.reportedAt=t)}oldestRetained(e,t=this.now()){return this.views().filter(r=>r.gcs>=exports.RETAINED_MIN_GCS&&t-r.closedAt>=e).sort((r,a)=>r.closedAt-a.closedAt)[0]}markers(){return[...this.closed.values()].map(e=>e.marker)}forgetAll(){const e=this.views().filter(t=>t.reportedAt!==void 0);for(const t of[...this.closed.keys()])this.drop(t);return e}collected(e){const t=this.closed.get(e.gen);if(!t||(t.alive.delete(e.name),t.alive.size>0))return;const r=this.view(e.gen,t);this.drop(e.gen);try{this.opts.onFreed?.(r,r.reportedAt!==void 0)}catch{}}drop(e){this.closed.delete(e),this.markFreed(e)}markFreed(e){this.freed.set(e,this.now()),this.freed.size>64&&this.freed.delete(this.freed.keys().next().value)}view(e,t){return{gen:e,closedAt:t.closedAt,swapsAtClose:t.swapsAtClose,gcs:t.gcs,alive:[...t.alive],...t.reportedAt!==void 0?{reportedAt:t.reportedAt}:{}}}}exports.GenerationTracker=GenerationTracker;const MB=1024*1024;function delayLabel(s){return s%6e4===0?`${s/6e4}m`:s%1e3===0?`${s/1e3}s`:`${s}ms`}d(delayLabel,"delayLabel"),i(delayLabel,"delayLabel");const turn=i(()=>new Promise(s=>setImmediate(s)),"turn");function createPodMemoryMonitor(s){const e=s.now??Date.now,t=s.policy,r=new GenerationTracker({now:e,onFreed:i((o,u)=>{u&&s.send({type:"pod-memory-released",released:{gen:o.gen,closedAt:o.closedAt,ageMs:e()-o.closedAt,reason:"collected"}})},"onFreed")});let a=Promise.resolve(),l=!1,n,p,m,y=0,w=!1,_=0;const g=i((o,u)=>{const c=setTimeout(o,Math.max(0,u));return c.unref?.(),c},"unrefTimer");function E(){return!n||n.previousGen===void 0?null:n.previousClosed()?r.isAlive(n.previousGen)!==!1:!0}d(E,"S"),i(E,"previousAlive");async function G(o){const u=process.memoryUsage().heapUsed,c=process.hrtime.bigint();let f=s.counter?.containers();f===void 0&&fullGc();const x=Number(process.hrtime.bigint()-c)/1e6;r.noteGc(),await turn(),await turn();const h=process.memoryUsage(),v=v8.getHeapStatistics(),R=v8.getHeapSpaceStatistics().find(I=>I.space_name==="new_space"),$=s.state(),P=r.views(),N=e();return f!==void 0&&f<0&&(f=void 0),{trigger:o,at:N,gen:$.gen,swaps:$.swaps,live:h.heapUsed,used:u,reserved:h.heapTotal,rss:h.rss,external:h.external,youngGen:R?.space_size??0,limit:v.heap_size_limit,...$.baseline!==void 0?{baseline:$.baseline}:{},gcMs:Math.round(x),...n?.previousGen!==void 0?{previousGen:n.previousGen}:{},previousAlive:E(),closedAlive:P.length,retained:r.retainedCount(t.retainedAfterMs,N),...f!==void 0?{containers:f}:{},sourceMaps:process.sourceMapsEnabled===!0}}d(G,"k"),i(G,"collect");function S(o){for(const c of r.dueForReport(t.retainedAfterMs,o.at)){r.markReported(c.gen,o.at);const f=s.counter?.all();s.send({type:"pod-memory-retained",retained:{gen:c.gen,closedAt:c.closedAt,ageMs:o.at-c.closedAt,swapsSinceClose:Math.max(0,o.swaps-c.swapsAtClose),gcs:c.gcs,alive:c.alive,...f?{counts:f}:{},live:o.live}})}const u=r.oldestRetained(t.retainedAfterMs,o.at);if(u)try{s.onRetained?.(u)}catch{}n&&!n.done&&o.previousAlive===!1&&A(),C()}d(S,"G"),i(S,"afterCollect");function M(o){const u=a.then(async()=>{const c=await G(o);return w=!1,l||(s.send({type:"pod-memory",report:c}),S(c)),c});return a=u.catch(()=>{}),u}d(M,"v"),i(M,"measure");function b(o){if(!l){if(s.state().busy){g(()=>b(o),1e3);return}M(o).catch(()=>{})}}d(b,"b"),i(b,"measureWhenFree");function A(){if(n){n.done=!0;for(const o of n.timers)clearTimeout(o);n.timers=[]}}d(A,"A"),i(A,"finishSchedule");function C(){if(p&&clearTimeout(p),p=void 0,l||!t.enabled)return;const o=r.views().filter(f=>f.reportedAt===void 0);if(o.length===0)return;const u=e(),c=Math.min(...o.map(f=>Math.max(f.closedAt+t.retainedAfterMs,u+1e3)));p=g(()=>{p=void 0,b("retention-check")},c-u)}d(C,"C"),i(C,"armRetentionCheck");function k(){if(l||!t.enabled||t.idleMs<=0||m)return;const o=Math.max(t.idleMs-(e()-y),_+t.idleEveryMs-e(),250);m=g(()=>{if(m=void 0,!!w){if(e()-y<t.idleMs||e()-_<t.idleEveryMs||s.state().busy){k();return}_=e(),b("idle")}},o)}return d(k,"x"),i(k,"armIdle"),{tracker:r,booted:i(()=>M("boot"),"booted"),swapped(o,u){if(l||(A(),y=e(),w=!0,!t.enabled))return;const c={...o!==void 0?{previousGen:o}:{},previousClosed:u,timers:[],done:!1};n=c;const f=e(),x=t.scheduleMs[0]??Number.POSITIVE_INFINITY,h=i(()=>{if(c.done||n!==c)return;const v=s.state();if(!v.busy&&u()&&e()-v.lastBusyAt>=t.settleMs){M("swap-settled").catch(()=>{});return}e()-f+500<x&&c.timers.push(g(h,500))},"settledCheck");c.timers.push(g(h,Math.min(t.settleMs,500)));for(const v of t.scheduleMs)c.timers.push(g(()=>{c.done||n!==c||b(`swap+${delayLabel(v)}`)},v))},closed(o,u,c){r.close(o,u,c),C()},activity(){y=e(),w=!0,k()},measure:M,oldestRetained:i(()=>r.oldestRetained(t.retainedAfterMs),"oldestRetained"),stop(){l=!0,A(),p&&clearTimeout(p),m&&clearTimeout(m)}}}d(createPodMemoryMonitor,"createPodMemoryMonitor"),i(createPodMemoryMonitor,"createPodMemoryMonitor");const mbOf=i(s=>s===void 0?"?":`${Math.round(s/MB)} MB`,"mbOf");function ageLabel(s){return s<9e4?`${Math.max(0,Math.round(s/1e3))}s`:`${Math.round(s/6e4)} min`}d(ageLabel,"ageLabel"),i(ageLabel,"ageLabel");function describePodMemory(s,e){const t=e.trigger==="boot"?`${s} memory at boot: ${mbOf(e.live)} live after a full GC \u2014 the baseline later readings compare with`:`${s} memory (${e.trigger}): ${mbOf(e.live)} live after a full GC`+(e.used-e.live>=MB?` (${mbOf(e.used)} before it)`:"")+(e.baseline?`, ${(e.live/e.baseline).toFixed(1)}\xD7 its ${mbOf(e.baseline)} boot baseline`:""),r=[`${mbOf(e.reserved)} reserved`,`rss ${mbOf(e.rss)}`,`generation ${e.gen}, ${e.swaps} swap${e.swaps===1?"":"s"}`,`GC ${e.gcMs} ms`];return e.previousAlive===!1?r.push(`generation ${e.previousGen} freed`):e.previousAlive===!0&&r.push(`generation ${e.previousGen} still in memory`),e.containers!==void 0&&r.push(`${e.containers} Nest container${e.containers===1?"":"s"}`),e.retained>0&&r.push(`${e.retained} closed generation${e.retained===1?"":"s"} RETAINED`),`${t} \xB7 ${r.join(" \xB7 ")}`}d(describePodMemory,"describePodMemory"),i(describePodMemory,"describePodMemory");function describeRetained(s,e,t){const r=t.counts&&Object.keys(t.counts).length?` Live now: ${Object.entries(t.counts).map(([a,l])=>`${a} ${l}`).join(", ")}.`:"";return`${s} ${e}: generation ${t.gen} still in memory ${ageLabel(t.ageMs)} after its app was closed (${t.swapsSinceClose} swap${t.swapsSinceClose===1?"":"s"} since, ${t.gcs} full GCs; still alive: ${t.alive.join(", ")}) \u2014 something still points into it.${r} See what with \`lensmcp mem ${e} --snapshot --analyze\`.`}d(describeRetained,"describeRetained"),i(describeRetained,"describeRetained");function describeReleased(s,e,t){return t.reason==="pod-exited"?`${s} ${e}: generation ${t.gen} went with its pod`:`${s} ${e}: generation ${t.gen} was collected after all, ${ageLabel(t.ageMs)} after its app was closed`}d(describeReleased,"describeReleased"),i(describeReleased,"describeReleased");
package/process-name.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  /** macOS keeps 31 characters (`p_name`, what Activity Monitor shows); `top` and Linux's comm show 15–16. */
2
2
  export declare const PROCESS_NAME_MAX = 31;
3
+ /** A link not used for this long is removed by the sweep. Every use refreshes it. */
4
+ export declare const STALE_AFTER_MS: number;
5
+ /** The sweep runs at most this often, machine-wide (a stamp file in each link directory). */
6
+ export declare const SWEEP_INTERVAL_MS: number;
7
+ /** How long after its first naming a process sweeps: never on the spawn that needed the name. */
8
+ export declare const SWEEP_DELAY_MS = 60000;
9
+ /** Links one sweep removes at most. One batch costs one wait however long it is; this bounds the work. */
10
+ export declare const SWEEP_MAX_REMOVALS = 512;
3
11
  /**
4
12
  * `processName('foodguard', 'knowledge', 'pod')` → `foodguard-knowledge-pod`. The LAST part is the role
5
13
  * and is kept whole; when the name is too long, the parts before it are shortened instead.
@@ -17,5 +25,42 @@ export type NamedExecPathOptions = {
17
25
  /**
18
26
  * The path to execute node from so the process is listed as `name` — a hard link to `execPath` — or
19
27
  * `execPath` itself when no link can be made. Never throws.
28
+ *
29
+ * An existing link is reused as it is: the node binary's link count changes only the first time a name is ever
30
+ * needed (see the cost above). Each use is recorded, and the first call schedules the day's sweep.
20
31
  */
21
32
  export declare function namedExecPath(name: string, options?: NamedExecPathOptions): string;
33
+ /**
34
+ * Name every process in `names` NOW, in one batch: each name without a link gets one, back to back, so the node
35
+ * binary's link count changes in one burst — one wait at the next launch instead of one per new name, interleaved
36
+ * with launches (issues/closed/dev-gateway-18). A dev gateway calls it once at start with every name its service
37
+ * trees will use, before it spawns any of them. Names already linked change nothing. Returns what
38
+ * {@link namedExecPath} returns for each distinct name, in order. Never throws.
39
+ */
40
+ export declare function prelinkProcessNames(names: Iterable<string>, options?: NamedExecPathOptions): string[];
41
+ export interface SweepOptions {
42
+ dirs?: string[];
43
+ /** The clock, for tests. */
44
+ now?: number;
45
+ /** Ignore the once-a-day stamp. */
46
+ force?: boolean;
47
+ staleAfterMs?: number;
48
+ maxRemovals?: number;
49
+ }
50
+ export interface SweepReport {
51
+ /** Links removed, by path. */
52
+ removed: string[];
53
+ /** Links found with no record of use — recorded now, so they age from today rather than go at once. */
54
+ adopted: number;
55
+ /** Bases skipped because they were swept within the interval. */
56
+ skipped: number;
57
+ }
58
+ /**
59
+ * Remove the links nobody has used for {@link STALE_AFTER_MS}, in every inode directory under `dirs` — the
60
+ * current node's and those of node versions since replaced (whose links also keep a deleted binary on disk).
61
+ *
62
+ * All removals happen together, so the whole sweep costs one wait at the next launch of each binary it touched,
63
+ * and only when it removed something. Bounded by {@link SWEEP_MAX_REMOVALS}. Only directories this user owns and
64
+ * nobody else can write are read; only regular files in them are removed. Never throws.
65
+ */
66
+ export declare function sweepProcessNames(options?: SweepOptions): SweepReport;
package/process-name.js CHANGED
@@ -1 +1 @@
1
- "use strict";var E=Object.defineProperty;var s=(t,e)=>E(t,"name",{value:e,configurable:!0});var m=Object.defineProperty,r=s((t,e)=>m(t,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PROCESS_NAME_MAX=void 0,exports.processName=processName,exports.defaultLinkDirs=defaultLinkDirs,exports.processNamesEnabled=processNamesEnabled,exports.namedExecPath=namedExecPath;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"));exports.PROCESS_NAME_MAX=31;const sanitize=r(t=>(t??"").replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,""),"sanitize");function processName(...t){const e=t.map(sanitize).filter(Boolean),n=e.pop();if(n===void 0)return"";if(e.length===0)return n.slice(0,exports.PROCESS_NAME_MAX);const o=n.slice(0,exports.PROCESS_NAME_MAX-2),a=e.join("-").slice(0,exports.PROCESS_NAME_MAX-o.length-1).replace(/-+$/,"");return a?`${a}-${o}`:o}s(processName,"processName"),r(processName,"processName");function defaultLinkDirs(t=process.env){const e=t.LENSMCP_HOME||os.homedir();return[path.join(os.tmpdir(),"lensmcp-process-names"),path.join(e,".lensmcp","process-names")]}s(defaultLinkDirs,"defaultLinkDirs"),r(defaultLinkDirs,"defaultLinkDirs");function processNamesEnabled(t=process.env){const e=(t.LENSMCP_PROCESS_NAMES??"").trim().toLowerCase();return!(e==="0"||e==="false"||e==="no"||e==="off")}s(processNamesEnabled,"processNamesEnabled"),r(processNamesEnabled,"processNamesEnabled");function privateDir(t){try{fs.mkdirSync(t,{recursive:!0,mode:448});const e=fs.lstatSync(t);return!e.isDirectory()||typeof process.getuid=="function"&&e.uid!==process.getuid()?!1:((e.mode&63)!==0&&fs.chmodSync(t,448),!0)}catch{return!1}}s(privateDir,"privateDir"),r(privateDir,"privateDir");function isSameFile(t,e){try{const n=fs.lstatSync(t);return n.isFile()&&n.dev===e.dev&&n.ino===e.ino}catch{return!1}}s(isSameFile,"isSameFile"),r(isSameFile,"isSameFile");const resolved=new Map;function namedExecPath(t,e={}){const n=e.execPath??process.execPath,o=e.env??process.env,a=e.platform??process.platform,p=processName(t);if(!p||a==="win32"||!processNamesEnabled(o))return n;const l=e.dirs??defaultLinkDirs(o),f=`${n}|${p}|${l.join("|")}`,d=resolved.get(f);if(d)return d;let u,c;try{u=fs.realpathSync(n),c=fs.statSync(u)}catch{return n}for(const S of l){if(!privateDir(S))continue;const _=path.join(S,`${c.dev}-${c.ino}`);if(!privateDir(_))continue;const i=path.join(_,p);if(!isSameFile(i,c)){try{fs.unlinkSync(i)}catch{}try{fs.linkSync(u,i)}catch{}}if(isSameFile(i,c))return resolved.set(f,i),i}return n}s(namedExecPath,"namedExecPath"),r(namedExecPath,"namedExecPath");
1
+ "use strict";var y=Object.defineProperty;var i=(t,e)=>y(t,"name",{value:e,configurable:!0});var m=Object.defineProperty,c=i((t,e)=>m(t,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.SWEEP_MAX_REMOVALS=exports.SWEEP_DELAY_MS=exports.SWEEP_INTERVAL_MS=exports.STALE_AFTER_MS=exports.PROCESS_NAME_MAX=void 0,exports.processName=processName,exports.defaultLinkDirs=defaultLinkDirs,exports.processNamesEnabled=processNamesEnabled,exports.namedExecPath=namedExecPath,exports.prelinkProcessNames=prelinkProcessNames,exports.sweepProcessNames=sweepProcessNames;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"));exports.PROCESS_NAME_MAX=31,exports.STALE_AFTER_MS=336*60*6e4,exports.SWEEP_INTERVAL_MS=1440*6e4,exports.SWEEP_DELAY_MS=6e4,exports.SWEEP_MAX_REMOVALS=512;const USED_DIR=".used",SWEPT_STAMP=".swept",sanitize=c(t=>(t??"").replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,""),"sanitize");function processName(...t){const e=t.map(sanitize).filter(Boolean),n=e.pop();if(n===void 0)return"";if(e.length===0)return n.slice(0,exports.PROCESS_NAME_MAX);const s=n.slice(0,exports.PROCESS_NAME_MAX-2),d=e.join("-").slice(0,exports.PROCESS_NAME_MAX-s.length-1).replace(/-+$/,"");return d?`${d}-${s}`:s}i(processName,"processName"),c(processName,"processName");function defaultLinkDirs(t=process.env){const e=t.LENSMCP_HOME||os.homedir();return[path.join(os.tmpdir(),"lensmcp-process-names"),path.join(e,".lensmcp","process-names")]}i(defaultLinkDirs,"defaultLinkDirs"),c(defaultLinkDirs,"defaultLinkDirs");function processNamesEnabled(t=process.env){const e=(t.LENSMCP_PROCESS_NAMES??"").trim().toLowerCase();return!(e==="0"||e==="false"||e==="no"||e==="off")}i(processNamesEnabled,"processNamesEnabled"),c(processNamesEnabled,"processNamesEnabled");const ownedByMe=c(t=>typeof process.getuid!="function"||t.uid===process.getuid(),"ownedByMe");function privateDir(t){try{fs.mkdirSync(t,{recursive:!0,mode:448});const e=fs.lstatSync(t);return!e.isDirectory()||!ownedByMe(e)?!1:((e.mode&63)!==0&&fs.chmodSync(t,448),!0)}catch{return!1}}i(privateDir,"privateDir"),c(privateDir,"privateDir");function isPrivateDir(t){try{const e=fs.lstatSync(t);return e.isDirectory()&&ownedByMe(e)&&(e.mode&18)===0}catch{return!1}}i(isPrivateDir,"isPrivateDir"),c(isPrivateDir,"isPrivateDir");function isSameFile(t,e){try{const n=fs.lstatSync(t);return n.isFile()&&n.dev===e.dev&&n.ino===e.ino}catch{return!1}}i(isSameFile,"isSameFile"),c(isSameFile,"isSameFile");function noteUse(t,e,n=new Date){const s=path.join(t,USED_DIR,e);try{fs.utimesSync(s,n,n)}catch{try{fs.mkdirSync(path.dirname(s),{recursive:!0,mode:448}),fs.writeFileSync(s,"",{mode:384})}catch{}}}i(noteUse,"noteUse"),c(noteUse,"noteUse");const resolved=new Map;function namedExecPath(t,e={}){const n=e.execPath??process.execPath,s=e.env??process.env,d=e.platform??process.platform,a=processName(t);if(!a||d==="win32"||!processNamesEnabled(s))return n;const S=e.dirs??defaultLinkDirs(s),E=`${n}|${a}|${S.join("|")}`;scheduleSweep(S);let r,p;try{r=fs.realpathSync(n),p=fs.statSync(r)}catch{return n}const o=resolved.get(E);if(o&&isSameFile(o.link,p))return noteUse(o.dir,o.name),o.link;for(const f of S){if(!privateDir(f))continue;const u=path.join(f,`${p.dev}-${p.ino}`);if(!privateDir(u))continue;const l=path.join(u,a);if(!isSameFile(l,p)){try{fs.unlinkSync(l)}catch{}try{fs.linkSync(r,l)}catch{}}if(isSameFile(l,p))return resolved.set(E,{link:l,dir:u,name:a}),noteUse(u,a),l}return n}i(namedExecPath,"namedExecPath"),c(namedExecPath,"namedExecPath");function prelinkProcessNames(t,e={}){const n=[];for(const s of new Set([...t].filter(Boolean)))n.push(namedExecPath(s,e));return n}i(prelinkProcessNames,"prelinkProcessNames"),c(prelinkProcessNames,"prelinkProcessNames");function sweepProcessNames(t={}){const e={removed:[],adopted:0,skipped:0},n=t.now??Date.now(),s=t.staleAfterMs??exports.STALE_AFTER_MS,d=t.maxRemovals??exports.SWEEP_MAX_REMOVALS;for(const a of t.dirs??defaultLinkDirs()){if(!isPrivateDir(a))continue;const S=path.join(a,SWEPT_STAMP);if(!t.force)try{if(n-fs.statSync(S).mtimeMs<exports.SWEEP_INTERVAL_MS){e.skipped+=1;continue}}catch{}try{fs.writeFileSync(S,"",{mode:384}),fs.utimesSync(S,new Date(n),new Date(n))}catch{continue}let E;try{E=fs.readdirSync(a).filter(r=>/^\d+-\d+$/.test(r)).map(r=>path.join(a,r))}catch{continue}for(const r of E){if(!isPrivateDir(r))continue;let p;try{p=fs.readdirSync(r).filter(o=>o!==USED_DIR)}catch{continue}for(const o of p){const f=path.join(r,o);try{if(!fs.lstatSync(f).isFile())continue}catch{continue}let u;try{u=fs.statSync(path.join(r,USED_DIR,o)).mtimeMs}catch{noteUse(r,o,new Date(n)),e.adopted+=1;continue}if(!(n-u<s||e.removed.length>=d))try{fs.unlinkSync(f),e.removed.push(f),fs.rmSync(path.join(r,USED_DIR,o),{force:!0})}catch{}}try{for(const o of fs.readdirSync(path.join(r,USED_DIR)))fs.existsSync(path.join(r,o))||fs.rmSync(path.join(r,USED_DIR,o),{force:!0})}catch{}try{fs.readdirSync(r).every(o=>o===USED_DIR)&&(fs.rmSync(path.join(r,USED_DIR),{recursive:!0,force:!0}),fs.rmdirSync(r))}catch{}}}return e}i(sweepProcessNames,"sweepProcessNames"),c(sweepProcessNames,"sweepProcessNames");const sweepScheduled=new Set;function scheduleSweep(t){const e=t.join("|");sweepScheduled.has(e)||(sweepScheduled.add(e),setTimeout(()=>{try{sweepProcessNames({dirs:t})}catch{}},exports.SWEEP_DELAY_MS).unref?.())}i(scheduleSweep,"scheduleSweep"),c(scheduleSweep,"scheduleSweep");
@@ -0,0 +1,2 @@
1
+ /** The conventional exit status of a process ended by `signal` (nx reports a signalled task the same way). */
2
+ export declare function exitCodeForSignal(signal: NodeJS.Signals): number;
@@ -0,0 +1 @@
1
+ "use strict";var p=Object.defineProperty;var t=(r,s)=>p(r,"name",{value:s,configurable:!0});var a=Object.defineProperty,o=t((r,s)=>a(r,"name",{value:s,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.exitCodeForSignal=exitCodeForSignal;const tslib_1=require("tslib"),os=tslib_1.__importStar(require("node:os")),TASK_WAIT_MS=3e4,STOP_SIGNALS=["SIGTERM","SIGINT","SIGHUP"];function exitCodeForSignal(r){return 128+(os.constants.signals[r]??0)}t(exitCodeForSignal,"exitCodeForSignal"),o(exitCodeForSignal,"exitCodeForSignal");function main(){process.env.NX_CLI_SET="true";const r=process.env.NX_WORKSPACE_ROOT;(!r||typeof process.send!="function")&&(console.error("[serve-runner] started without a task channel \u2014 only the dev gateway starts this process"),process.exit(1));let s=!1;const i=o(e=>{s||(s=!0,setImmediate(()=>process.exit(exitCodeForSignal(e))))},"stop");for(const e of STOP_SIGNALS)process.on(e,()=>i(e));process.on("disconnect",()=>{s||(console.error("[serve-runner] the gateway is gone \u2014 stopping the service"),process.kill(process.pid,"SIGTERM"))});const c=setTimeout(()=>{console.error(`[serve-runner] no task arrived within ${TASK_WAIT_MS/1e3}s \u2014 exiting`),process.exit(1)},TASK_WAIT_MS);process.once("message",e=>{clearTimeout(c),(async()=>{let n=1;try{n=await require(e.nxRunModule).run(process.cwd(),r,e.targetDescription,e.overrides,e.isVerbose,e.taskGraph)}catch(u){console.error(u)}process.exit(n)})()})}t(main,"main"),o(main,"main"),require.main===module&&main();
@@ -0,0 +1,56 @@
1
+ import { type AcceptedEvent, type BuiltEvent, type MemberEvent, type MemberJoinRequest } from './protocol';
2
+ /** How long a join waits for the build host's answer before the service compiles on its own. */
3
+ export declare const JOIN_TIMEOUT_MS = 120000;
4
+ /** Was this executor started by a gateway that relays the shared build — and can it talk to it? */
5
+ export declare function sharedBuildOffered(env?: NodeJS.ProcessEnv, proc?: Pick<NodeJS.Process, 'send'>): boolean;
6
+ /** A joined member: every event after `accepted`, in order. */
7
+ export interface SharedBuildSession {
8
+ accepted: AcceptedEvent;
9
+ /** Receive events. Ones that arrived before a listener was set are delivered to it first. */
10
+ listen(listener: (event: MemberEvent) => void): void;
11
+ close(): void;
12
+ }
13
+ export type JoinOutcome = {
14
+ kind: 'attached';
15
+ session: SharedBuildSession;
16
+ } | {
17
+ kind: 'refused';
18
+ reason: string;
19
+ };
20
+ /** The process seams the join uses — the spec passes its own. */
21
+ export interface JoinProcess {
22
+ send?: NodeJS.Process['send'];
23
+ on(event: 'message', listener: (m: unknown) => void): unknown;
24
+ removeListener(event: 'message', listener: (m: unknown) => void): unknown;
25
+ }
26
+ /** Ask to be compiled by the build host. Resolves `refused` — never rejects — when it will not, or does not answer. */
27
+ export declare function joinSharedBuild(join: MemberJoinRequest, opts?: {
28
+ timeoutMs?: number;
29
+ proc?: JoinProcess;
30
+ }): Promise<JoinOutcome>;
31
+ /** One service's type checking while its bundle is built elsewhere. */
32
+ export interface AttachedTypecheck {
33
+ /** A build of this service's bundle finished. */
34
+ built(event: BuiltEvent): void;
35
+ dispose(): void;
36
+ }
37
+ export interface AttachedTypecheckOptions {
38
+ appName: string;
39
+ tsConfig: string;
40
+ workspaceRoot: string;
41
+ memoryLimit: number;
42
+ env?: NodeJS.ProcessEnv;
43
+ }
44
+ /**
45
+ * The checker `LENSMCP_TYPECHECK` asks for, run in THIS process for THIS service — what its own compiler used to
46
+ * carry (create-webpack-dev.ts), so the check's lines land in the service's output and its typecheck-queue ticket
47
+ * is this process's:
48
+ *
49
+ * - `off`: none.
50
+ * - `tsgo`: TsgoCheckPlugin, scheduled after each of this service's builds without errors.
51
+ * - default: fork-ts-checker, on a compiler with no entries that exists to watch: fork-ts-checker re-checks on
52
+ * the watcher's changed files and adds its program's files to what it watches. Its program lists only the
53
+ * tsconfig's own files, so the build host's file list for this bundle (`files`) is watched too — a library
54
+ * save re-checks every service that compiles it, as before.
55
+ */
56
+ export declare function createAttachedTypecheck(opts: AttachedTypecheckOptions): AttachedTypecheck;
@@ -0,0 +1,3 @@
1
+ "use strict";var T=Object.defineProperty;var f=(e,t)=>T(e,"name",{value:t,configurable:!0});var _=Object.defineProperty,o=f((e,t)=>_(e,"name",{value:t,configurable:!0}),"o");Object.defineProperty(exports,"__esModule",{value:!0}),exports.JOIN_TIMEOUT_MS=void 0,exports.sharedBuildOffered=sharedBuildOffered,exports.joinSharedBuild=joinSharedBuild,exports.createAttachedTypecheck=createAttachedTypecheck;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),fork_ts_check_reporter_1=require("../fork-ts-check-reporter"),tsgo_check_plugin_1=require("../tsgo-check-plugin"),protocol_1=require("./protocol");exports.JOIN_TIMEOUT_MS=12e4;function sharedBuildOffered(e=process.env,t=process){return e[protocol_1.SHARED_BUILD_ENV]==="1"&&typeof t.send=="function"}f(sharedBuildOffered,"sharedBuildOffered"),o(sharedBuildOffered,"sharedBuildOffered");function joinSharedBuild(e,t={}){const i=t.proc??process,c=t.timeoutMs??exports.JOIN_TIMEOUT_MS;return new Promise(n=>{const p=[];let d,s=!1;const u=o(r=>{if(!(0,protocol_1.isRunnerInbound)(r))return;const a=r.event;if(!s)return a.type==="refused"?l({kind:"refused",reason:a.reason}):a.type!=="accepted"?void 0:l({kind:"attached",session:m(a)});d?d(a):p.push(a)},"onMessage"),h=o(()=>{i.removeListener("message",u)},"close"),m=o(r=>({accepted:r,listen(a){d=a;for(const w of p.splice(0))a(w)},close:h}),"makeSession"),l=o(r=>{s||(s=!0,clearTimeout(k),r.kind==="refused"&&h(),n(r))},"finish"),k=setTimeout(()=>l({kind:"refused",reason:`the build host did not answer within ${Math.round(c/1e3)} s`}),c);k.unref?.(),i.on("message",u);const g={channel:protocol_1.SHARED_BUILD_CHANNEL,type:"join",join:e};try{i.send?.(g,void 0,void 0,r=>{r&&l({kind:"refused",reason:`could not reach the gateway (${r.message})`})})}catch(r){l({kind:"refused",reason:`could not reach the gateway (${r.message})`})}})}f(joinSharedBuild,"joinSharedBuild"),o(joinSharedBuild,"joinSharedBuild");function createAttachedTypecheck(e){const t=e.env??process.env,i=t.LENSMCP_TYPECHECK;if(i==="off")return{built:o(()=>{},"built"),dispose:o(()=>{},"dispose")};if(i==="tsgo"){if((0,tsgo_check_plugin_1.resolveTsgoBin)(e.workspaceRoot,t.LENSMCP_TSGO_BIN)){const n=new tsgo_check_plugin_1.TsgoCheckPlugin({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot,...t.LENSMCP_TSGO_BIN?{binPath:t.LENSMCP_TSGO_BIN}:{}}),p=[];return n.apply({hooks:{done:{tap:o((d,s)=>p.push(s),"tap")}}}),{built:o(d=>{for(const s of p)s({hasErrors:o(()=>!d.ok,"hasErrors")})},"built"),dispose:o(()=>n.dispose(),"dispose")}}const c=`${tsgo_check_plugin_1.TSGO_MISSING_CAUSE}. Fell back to fork-ts-checker, which reports to the console only \u2014 so typecheck:// has no producer for this project until it is fixed`;console.warn(`[serve] ${e.appName}: LENSMCP_TYPECHECK=tsgo but ${c}.`),(0,tsgo_check_plugin_1.reportTsgoUnavailable)({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot,detail:c})}return forkTsCheckerOnItsOwn(e)}f(createAttachedTypecheck,"createAttachedTypecheck"),o(createAttachedTypecheck,"createAttachedTypecheck");function forkTsCheckerOnItsOwn(e){const t=(0,fork_ts_check_reporter_1.loadForkTsChecker)(e.workspaceRoot),i=t?.default??t;if(typeof i!="function")return console.warn(`[serve] ${e.appName}: fork-ts-checker-webpack-plugin is not installed \u2014 this service is not type-checked`),{built:o(()=>{},"built"),dispose:o(()=>{},"dispose")};let c=[],n,p=!1;const d=o(()=>{n=require("webpack")({mode:"development",context:e.workspaceRoot,entry:{},infrastructureLogging:{level:"info"},plugins:[{apply(s){s.hooks.afterCompile.tapPromise("LensmcpAttachedTypecheck",()=>new Promise(u=>setTimeout(u,0))),s.hooks.afterCompile.tap("LensmcpAttachedTypecheck",u=>{for(const h of c)u.fileDependencies.add(h)})}},new i({async:!0,typescript:{configFile:e.tsConfig,memoryLimit:e.memoryLimit}}),new fork_ts_check_reporter_1.ForkTsCheckReporterPlugin({appName:e.appName,tsConfig:e.tsConfig,workspaceRoot:e.workspaceRoot})]}).watch({ignored:["**/node_modules/**",`${path.join(e.workspaceRoot,"dist")}/**`]},()=>{})},"start");return{built(s){if(p)return;const u=s.files&&s.files.join(`
2
+ `)!==c.join(`
3
+ `);s.files&&(c=[...s.files]),n?u&&n.invalidate():d()},dispose(){p=!0;try{n?.close(()=>{})}catch{}n=void 0}}}f(forkTsCheckerOnItsOwn,"forkTsCheckerOnItsOwn"),o(forkTsCheckerOnItsOwn,"forkTsCheckerOnItsOwn");
@@ -0,0 +1,52 @@
1
+ import type { HostOutbound, MemberJoinRequest } from './protocol';
2
+ /** How long a project with no member keeps its entries — a recycle that kills then respawns does not drop them. */
3
+ export declare const DEFAULT_PAUSE_MS = 5000;
4
+ type WebpackFactory = (config: Record<string, any>) => any;
5
+ export interface BuildHostOptions {
6
+ /** The workspace root (absolute). */
7
+ root: string;
8
+ send(message: HostOutbound): void;
9
+ log(line: string): void;
10
+ /** Default: the webpack `@lensmcp/cluster` resolves. */
11
+ webpack?: WebpackFactory;
12
+ /** Default {@link DEFAULT_PAUSE_MS}. */
13
+ pauseMs?: number;
14
+ }
15
+ /** Run `fn` with `process.env` replaced by `env` — synchronously, and restored whatever happens. */
16
+ export declare function withEnv<T>(env: Record<string, string | undefined>, fn: () => T): T;
17
+ /** `<project>:<target>[:<configuration>]` — what NxAppWebpackPlugin reads from `NX_BUILD_TARGET`. */
18
+ export declare function buildTargetString(join: Pick<MemberJoinRequest, 'project' | 'target' | 'configuration'>): string;
19
+ export declare class BuildHost {
20
+ private readonly opts;
21
+ private readonly groups;
22
+ /** member → its project and group. */
23
+ private readonly members;
24
+ private readonly pauseMs;
25
+ private readonly webpack;
26
+ private stopped;
27
+ constructor(opts: BuildHostOptions);
28
+ private tell;
29
+ /** The projects each group compiles — for the spec and the gateway's status line. */
30
+ describe(): Array<{
31
+ group: string;
32
+ projects: string[];
33
+ }>;
34
+ /** Build a member's config the way the shared compilation will, and its signature. Throws what the config throws. */
35
+ private evaluate;
36
+ join(member: string, join: MemberJoinRequest): void;
37
+ leave(member: string, opts?: {
38
+ quiet?: boolean;
39
+ }): void;
40
+ /** The project's entries leave the compilation; the group closes when none is left. */
41
+ private pause;
42
+ /** Start the group's watcher, or make it compile again with the registry as it is now. */
43
+ private kick;
44
+ private createGroup;
45
+ private membersOf;
46
+ private onBuild;
47
+ private tellBuilt;
48
+ private closeGroup;
49
+ /** Stop every group. Resolves once each watcher has closed and stored its cache, or after `timeoutMs`. */
50
+ stop(timeoutMs?: number): Promise<void>;
51
+ }
52
+ export {};
@@ -0,0 +1,4 @@
1
+ "use strict";var y=Object.defineProperty;var g=(l,e)=>y(l,"name",{value:e,configurable:!0});var v=Object.defineProperty,p=g((l,e)=>v(l,"name",{value:e,configurable:!0}),"p");Object.defineProperty(exports,"__esModule",{value:!0}),exports.BuildHost=exports.DEFAULT_PAUSE_MS=void 0,exports.withEnv=withEnv,exports.buildTargetString=buildTargetString;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),path=tslib_1.__importStar(require("node:path")),dev_devtool_1=require("../dev-devtool"),create_webpack_dev_1=require("../create-webpack-dev"),perf_rules_plugin_1=require("../perf/perf-rules-plugin"),signature_1=require("./signature"),shared_compilation_1=require("./shared-compilation");exports.DEFAULT_PAUSE_MS=5e3;function withEnv(l,e){const t={...process.env};for(const r of Object.keys(process.env))delete process.env[r];for(const[r,s]of Object.entries(l))s!==void 0&&(process.env[r]=s);try{return e()}finally{for(const r of Object.keys(process.env))delete process.env[r];Object.assign(process.env,t)}}g(withEnv,"withEnv"),p(withEnv,"withEnv");function buildTargetString(l){return`${l.project}:${l.target}${l.configuration?`:${l.configuration}`:""}`}g(buildTargetString,"buildTargetString"),p(buildTargetString,"buildTargetString");const shortHash=p(l=>(0,node_crypto_1.createHash)("sha256").update(l).digest("hex").slice(0,16),"shortHash");class BuildHost{static{g(this,"BuildHost")}static{p(this,"BuildHost")}constructor(e){this.opts=e,this.groups=new Map,this.members=new Map,this.stopped=!1,this.pauseMs=e.pauseMs??exports.DEFAULT_PAUSE_MS,this.webpack=e.webpack??require("webpack")}tell(e,t){this.opts.send({type:"member",member:e,event:t})}describe(){return[...this.groups.values()].map(e=>({group:e.key,projects:e.registry.projects()}))}evaluate(e){const t=e.options;if(t.webpackConfigPath)try{delete require.cache[require.resolve(path.resolve(t.appRoot,t.webpackConfigPath))]}catch{}const r={...e.env,NODE_ENV:"development",NX_BUILD_TARGET:buildTargetString(e),NX_WORKSPACE_ROOT:this.opts.root,LENSMCP_TYPECHECK:"off"},s=withEnv(r,()=>(0,create_webpack_dev_1.createDevWebpackConfig)(t)),o=(0,signature_1.transpileCompilerOptions)(t.tsConfig,this.opts.root),n=(0,signature_1.memberSignature)({join:e,devtool:s.devtool,config:s,compilerOptions:o});return{config:s,signature:n}}join(e,t){if(this.stopped)return this.tell(e,{type:"refused",reason:"the build host is stopping"});this.leave(e,{quiet:!0});const r=(0,signature_1.refusalOf)(t,this.opts.root);if(r)return this.opts.log(`${t.project}: compiles on its own \u2014 ${r}`),this.tell(e,{type:"refused",reason:r});let s;try{s=this.evaluate(t)}catch(i){const c=`its webpack config could not be built here (${(i?.message??String(i)).split(`
2
+ `)[0]})`;return this.opts.log(`${t.project}: compiles on its own \u2014 ${c}`),this.tell(e,{type:"refused",reason:c})}let o=this.groups.get(s.signature.key);if(!o){for(const i of this.groups.values()){const c=(0,signature_1.describeDifference)(s.signature,i.signature);this.opts.log(`${t.project}: compiled apart from ${i.registry.projects().sort().join(", ")||"the other group"} \u2014 ${c??"its compile signature differs"}`);break}try{o=this.createGroup(s,t)}catch(i){const c=`the shared compiler could not be created (${(i?.message??String(i)).split(`
3
+ `)[0]})`;return this.opts.log(`${t.project}: compiles on its own \u2014 ${c}`),this.tell(e,{type:"refused",reason:c})}this.groups.set(o.key,o)}this.members.set(e,{project:t.project,group:o});const n=o.projects.get(t.project)??new Set;n.add(e);const a=!o.projects.has(t.project);o.projects.set(t.project,n);const u=o.pausing.get(t.project);if(u&&(clearTimeout(u),o.pausing.delete(t.project)),this.tell(e,{type:"accepted",group:o.key,projects:[...o.projects.keys()]}),a)o.registry.set(t.project,(0,shared_compilation_1.memberEntries)(t.project,t.options)),this.opts.log(`shared compilation ${o.key.slice(0,8)}: ${o.registry.projects().sort().join(", ")}`),this.kick(o);else{const i=o.last.get(t.project);i&&this.tellBuilt(o,e,i)}}leave(e,t={}){const r=this.members.get(e);if(!r)return;this.members.delete(e);const{project:s,group:o}=r;o.told.delete(e),o.toldFiles.delete(e);const n=o.projects.get(s);if(n?.delete(e),n&&n.size>0)return;t.quiet||this.opts.log(`${s}: no member left \u2014 its entries pause in ${Math.round(this.pauseMs/1e3)} s`);const a=setTimeout(()=>this.pause(o,s),this.pauseMs);a.unref?.(),o.pausing.set(s,a)}pause(e,t){if(e.pausing.delete(t),!((e.projects.get(t)?.size??0)>0)){if(e.projects.delete(t),e.registry.delete(t),e.last.delete(t),e.projects.size===0){this.opts.log(`shared compilation ${e.key.slice(0,8)}: no service left \u2014 closed`),this.groups.delete(e.key),this.closeGroup(e);return}this.opts.log(`shared compilation ${e.key.slice(0,8)}: ${t} paused \u2014 ${e.registry.projects().sort().join(", ")}`),this.kick(e)}}kick(e){if(e.watching){e.watching.invalidate();return}e.watching=e.compiler.watch(e.watchOptions,(t,r)=>this.onBuild(e,t,r))}createGroup(e,t){const r=e.signature.key,s=new shared_compilation_1.EntryRegistry,o=t.options,n=e.config,a=n.devtool,u=path.join(this.opts.root,"node_modules",".cache","lensmcp-build",r,"out"),i=(0,create_webpack_dev_1.devCacheOptions)(o,a),c={key:r,signature:e.signature,registry:s,projects:new Map,pausing:new Map,last:new Map,told:new Map,toldFiles:new Map,compiler:void 0,watching:void 0,watchOptions:{},sourceMaps:!1,webpackVersion:""},h=n.plugins.filter(f=>!signature_1.PER_SERVICE_PLUGINS.has(f?.constructor?.name));h.push(new perf_rules_plugin_1.PerfRulesPlugin({project:t.project,workspaceRoot:this.opts.root,tsConfig:o.tsConfig,entries:p(()=>s.entryProjects(),"entries")}),new shared_compilation_1.SharedEntriesPlugin({registry:s,workspaceRoot:this.opts.root,outputPath:u,cache:{...i,name:`shared-${r}`,version:`${i.version}-${r}`}}),{apply:p(f=>{f.hooks.watchRun.tap("LensmcpSharedBuild",m=>{const j=[...m.modifiedFiles??[],...m.removedFiles??[]].filter(b=>typeof b=="string").slice(-32);for(const b of this.membersOf(c))this.tell(b,{type:"watch-run",paths:j})}),f.hooks.watchClose.tap("LensmcpSharedBuild",()=>{for(const m of this.membersOf(c))this.tell(m,{type:"watch-closed"})})},"apply")});const d={...n,plugins:h};delete d.watch,c.watchOptions=n.watchOptions??{};const w={...t.env,NODE_ENV:"development",NX_BUILD_TARGET:buildTargetString(t),NX_WORKSPACE_ROOT:this.opts.root,LENSMCP_TYPECHECK:"off"};return c.compiler=withEnv(w,()=>this.webpack(d)),c.sourceMaps=(0,dev_devtool_1.bundleCarriesSourceMaps)(c.compiler.options.devtool),c.webpackVersion=String(c.compiler.webpack?.version??"5"),c}membersOf(e){const t=[];for(const r of e.projects.values())t.push(...r);return t}onBuild(e,t,r){if(this.stopped)return;if(t){const i=t?.message??String(t);this.opts.log(`shared compilation ${e.key.slice(0,8)} failed: ${i}`);for(const c of this.membersOf(e))this.tell(c,{type:"failed",message:i});return}const s=r,o=Math.max(0,(s.endTime??0)-(s.startTime??0)),n=(0,shared_compilation_1.attributeCompilation)(s.compilation,e.registry),a=[];for(const[i,c]of n){e.last.get(i)?.attribution.hash!==c.hash&&a.push(i);const h={attribution:c,durationMs:o};e.last.set(i,h);for(const d of e.projects.get(i)??[])this.tellBuilt(e,d,h)}const u=[...n.values()].filter(i=>i.errorCount>0).map(i=>i.project);this.opts.log(`built ${[...n.keys()].sort().join(", ")||"nothing"} in ${o} ms \u2014 ${a.length?`changed: ${a.sort().join(", ")}`:"no bundle changed"}${u.length?`; errors in: ${u.sort().join(", ")}`:""}`)}tellBuilt(e,t,r){const s=r.attribution,o=e.told.get(t)!==s.hash;e.told.set(t,s.hash);const n=shortHash([...s.files].sort().join(`
4
+ `)),a=e.toldFiles.get(t)!==n;e.toldFiles.set(t,n),this.tell(t,{type:"built",ok:s.errorCount===0,changed:o,hash:s.hash,text:(0,shared_compilation_1.describeBuild)(s,{webpackVersion:e.webpackVersion,durationMs:r.durationMs,changed:o}),errors:s.errorCount,warnings:s.warningCount,durationMs:r.durationMs,sourceMaps:e.sourceMaps,...a?{files:s.files}:{}})}closeGroup(e){for(const o of e.pausing.values())clearTimeout(o);e.pausing.clear();const t=e.compiler,r=e.watching;e.watching=void 0;const s=p(()=>{try{t?.close?.(()=>{})}catch{}},"closeCompiler");r?r.close(s):s()}stop(e=5e3){this.stopped=!0;const t=[...this.groups.values()];return this.groups.clear(),this.members.clear(),new Promise(r=>{let s=t.length;if(s===0)return r();const o=setTimeout(r,e);o.unref?.();for(const n of t){for(const c of n.pausing.values())clearTimeout(c);const a=p(()=>{--s===0&&(clearTimeout(o),r())},"done"),u=n.compiler,i=p(()=>{try{u.close(()=>a())}catch{a()}},"closeCompiler");n.watching?n.watching.close(i):i()}})}}exports.BuildHost=BuildHost;