@lensmcp/cluster 1.23.13 → 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 (105) hide show
  1. package/build-host.d.ts +1 -0
  2. package/build-host.js +1 -0
  3. package/create-webpack-dev.d.ts +74 -40
  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 +54 -8
  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/scope.d.ts +1 -1
  37. package/executors/gateway/runtime/serve-launch.d.ts +118 -0
  38. package/executors/gateway/runtime/serve-launch.js +3 -0
  39. package/executors/gateway/runtime/server.d.ts +3 -3
  40. package/executors/gateway/runtime/server.js +2 -2
  41. package/executors/gateway/runtime/shared-build.d.ts +65 -0
  42. package/executors/gateway/runtime/shared-build.js +4 -0
  43. package/executors/gateway/runtime/types.d.ts +35 -9
  44. package/executors/gateway/runtime/types.js +1 -1
  45. package/executors/gateway/runtime/workspace-log.js +1 -1
  46. package/executors/gateway/schema.d.ts +3 -0
  47. package/executors/gateway/schema.json +4 -0
  48. package/executors/serve/schema.d.ts +2 -0
  49. package/executors/serve/schema.json +4 -0
  50. package/executors/serve/serve.impl.d.ts +12 -6
  51. package/executors/serve/serve.impl.js +1 -1
  52. package/executors/trust/hostname.d.ts +1 -1
  53. package/exit-reason.d.ts +1 -1
  54. package/fork-ts-check-reporter.d.ts +12 -0
  55. package/fork-ts-check-reporter.js +1 -1
  56. package/graph-snapshot.d.ts +1 -0
  57. package/graph-snapshot.js +1 -0
  58. package/heap-snapshot.d.ts +66 -0
  59. package/heap-snapshot.js +1 -0
  60. package/hot-swap-registries.d.ts +1 -1
  61. package/inspector-secret.d.ts +39 -0
  62. package/inspector-secret.js +1 -0
  63. package/main.devserver.d.ts +33 -5
  64. package/main.devserver.js +11 -9
  65. package/package.json +4 -4
  66. package/perf/heavy-barrel-import.d.ts +135 -0
  67. package/perf/heavy-barrel-import.js +1 -0
  68. package/perf/perf-config.d.ts +29 -0
  69. package/perf/perf-config.js +1 -0
  70. package/perf/perf-rule-events.d.ts +71 -0
  71. package/perf/perf-rule-events.js +2 -0
  72. package/perf/perf-rules-analysis.d.ts +28 -0
  73. package/perf/perf-rules-analysis.js +1 -0
  74. package/perf/perf-rules-plugin.d.ts +71 -0
  75. package/perf/perf-rules-plugin.js +1 -0
  76. package/perf/perf-rules-worker.d.ts +1 -0
  77. package/perf/perf-rules-worker.js +1 -0
  78. package/perf/source-scan.d.ts +59 -0
  79. package/perf/source-scan.js +5 -0
  80. package/perf/workspace-libraries.d.ts +52 -0
  81. package/perf/workspace-libraries.js +2 -0
  82. package/pod-memory-events.d.ts +71 -0
  83. package/pod-memory-events.js +2 -0
  84. package/pod-memory.d.ts +258 -0
  85. package/pod-memory.js +1 -0
  86. package/process-name.d.ts +45 -0
  87. package/process-name.js +1 -1
  88. package/serve-runner.d.ts +2 -0
  89. package/serve-runner.js +1 -0
  90. package/shared-build/attach.d.ts +56 -0
  91. package/shared-build/attach.js +3 -0
  92. package/shared-build/host.d.ts +52 -0
  93. package/shared-build/host.js +4 -0
  94. package/shared-build/protocol.d.ts +118 -0
  95. package/shared-build/protocol.js +1 -0
  96. package/shared-build/shared-compilation.d.ts +91 -0
  97. package/shared-build/shared-compilation.js +3 -0
  98. package/shared-build/signature.d.ts +52 -0
  99. package/shared-build/signature.js +1 -0
  100. package/stdio-guard.d.ts +1 -1
  101. package/swap-recycle.d.ts +21 -0
  102. package/swap-recycle.js +1 -1
  103. package/tsgo-check-plugin.d.ts +1 -1
  104. package/workspace-paths.d.ts +76 -0
  105. package/workspace-paths.js +1 -0
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;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The shared compiler (issues/open/pod-memory-05): ONE webpack compilation for every service of a workspace
3
+ * instead of one watcher per service. Three processes talk:
4
+ *
5
+ * runner (the serve executor, ATTACHED) ⇄ gateway (relay) ⇄ build host (one per workspace)
6
+ *
7
+ * over the two IPC channels the gateway already owns — the runner's (it hands the runner its task on it,
8
+ * serve-launch.ts) and the build host's. Nothing listens on a socket, so there is nothing for another local
9
+ * account to connect to. These are the messages; nothing here touches a process.
10
+ */
11
+ import type { DevWebpackOptions } from '../create-webpack-dev';
12
+ /** The gateway's switch: `LENSMCP_SHARED_COMPILER=1` turns the shared compiler on, `0` off. */
13
+ export declare const SHARED_COMPILER_ENV = "LENSMCP_SHARED_COMPILER";
14
+ /** Set by the gateway on a runner whose build-host messages it relays — the serve executor attaches only then. */
15
+ export declare const SHARED_BUILD_ENV = "LENSMCP_SHARED_BUILD";
16
+ /** Every message on a runner's channel that belongs to the shared build carries this, so nothing else is mistaken for one. */
17
+ export declare const SHARED_BUILD_CHANNEL = "lensmcp-shared-build";
18
+ /** The role in the build host's process name: `<workspace>-build` (process-name.ts). */
19
+ export declare const BUILD_HOST_ROLE = "build";
20
+ /** The argument that names the build host's workspace on its command line — always the LAST one (the reaper matches it exactly). */
21
+ export declare const BUILD_HOST_ROOT_FLAG = "--workspace-root=";
22
+ /** Default when the variable is unset. */
23
+ export declare const SHARED_COMPILER_DEFAULT = false;
24
+ /** Is the shared compiler on? The variable wins; else the gateway option; else {@link SHARED_COMPILER_DEFAULT}. */
25
+ export declare function sharedCompilerEnabled(env?: NodeJS.ProcessEnv, option?: boolean): boolean;
26
+ /** What a runner hands the build host: everything the host needs to build this service's config itself. */
27
+ export interface MemberJoinRequest {
28
+ project: string;
29
+ target: string;
30
+ configuration?: string;
31
+ /** The `createDevWebpackConfig` options, exactly as the serve executor computed them. */
32
+ options: DevWebpackOptions;
33
+ /** The target's resolved options — what NxAppWebpackPlugin merges into its own. */
34
+ targetOptions: Record<string, unknown>;
35
+ /** The task's environment (nx's dotenv files applied): an override that reads `process.env` sees this. */
36
+ env: Record<string, string>;
37
+ }
38
+ /** runner → gateway. */
39
+ export interface RunnerJoinMessage {
40
+ channel: typeof SHARED_BUILD_CHANNEL;
41
+ type: 'join';
42
+ join: MemberJoinRequest;
43
+ }
44
+ /** gateway → host. `member` names one runner tree: `<project>#g<generation>`. */
45
+ export type HostInbound = {
46
+ type: 'join';
47
+ member: string;
48
+ join: MemberJoinRequest;
49
+ } | {
50
+ type: 'leave';
51
+ member: string;
52
+ };
53
+ /** The host joined the member: its entries are (or are being) compiled in `group`. */
54
+ export interface AcceptedEvent {
55
+ type: 'accepted';
56
+ group: string;
57
+ /** The projects compiled in the same compilation, this one included. */
58
+ projects: string[];
59
+ }
60
+ /** The host will not compile this member; the runner compiles it on its own and says why. */
61
+ export interface RefusedEvent {
62
+ type: 'refused';
63
+ reason: string;
64
+ }
65
+ /** The shared watcher saw files change — the watcher testimony's `watchRun` (DevServerReloadPlugin). */
66
+ export interface WatchRunEvent {
67
+ type: 'watch-run';
68
+ paths: string[];
69
+ }
70
+ /** One compilation finished, as far as this member's entries are concerned. */
71
+ export interface BuiltEvent {
72
+ type: 'built';
73
+ /** None of this member's modules (nor its entries) has an error. */
74
+ ok: boolean;
75
+ /** Its emitted chunks differ from the last build this member was told about. */
76
+ changed: boolean;
77
+ /** The chunk hash of its entries — what `changed` compares. */
78
+ hash: string;
79
+ /** The stats lines for this member — what its runner prints (the gateway reads `ERROR in`/`compiled successfully`). */
80
+ text: string;
81
+ errors: number;
82
+ warnings: number;
83
+ durationMs: number;
84
+ /** The bundle carries source maps (dev-devtool.ts) — the devserver's `LENSMCP_DEV_SOURCE_MAPS`. */
85
+ sourceMaps: boolean;
86
+ /** Every file this member's bundle was built from — a type-check-only compiler watches them (attach.ts). Sent when the set changed. */
87
+ files?: string[];
88
+ }
89
+ /** The shared compiler failed outright (not a module error) — every member is told. */
90
+ export interface FailedEvent {
91
+ type: 'failed';
92
+ message: string;
93
+ }
94
+ /** The shared watcher closed (the host is stopping). */
95
+ export interface WatchClosedEvent {
96
+ type: 'watch-closed';
97
+ }
98
+ export type MemberEvent = AcceptedEvent | RefusedEvent | WatchRunEvent | BuiltEvent | FailedEvent | WatchClosedEvent;
99
+ /** host → gateway. */
100
+ export type HostOutbound = {
101
+ type: 'member';
102
+ member: string;
103
+ event: MemberEvent;
104
+ } | {
105
+ type: 'ready';
106
+ pid: number;
107
+ };
108
+ /** gateway → runner. */
109
+ export interface RunnerInbound {
110
+ channel: typeof SHARED_BUILD_CHANNEL;
111
+ event: MemberEvent;
112
+ }
113
+ export declare function isRunnerJoinMessage(m: unknown): m is RunnerJoinMessage;
114
+ export declare function isRunnerInbound(m: unknown): m is RunnerInbound;
115
+ /** The build host's arguments after its script — read only by `ps` and the reaper: the root comes last. */
116
+ export declare function buildHostArgs(root: string): string[];
117
+ /** Is this command line a build host started for the workspace at `root`? Exact, like the serve runner's. */
118
+ export declare function isBuildHostCommandOf(cmd: string, root: string): boolean;
@@ -0,0 +1 @@
1
+ "use strict";var _=Object.defineProperty;var r=(t,e)=>_(t,"name",{value:e,configurable:!0});var o=Object.defineProperty,s=r((t,e)=>o(t,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.SHARED_COMPILER_DEFAULT=exports.BUILD_HOST_ROOT_FLAG=exports.BUILD_HOST_ROLE=exports.SHARED_BUILD_CHANNEL=exports.SHARED_BUILD_ENV=exports.SHARED_COMPILER_ENV=void 0,exports.sharedCompilerEnabled=sharedCompilerEnabled,exports.isRunnerJoinMessage=isRunnerJoinMessage,exports.isRunnerInbound=isRunnerInbound,exports.buildHostArgs=buildHostArgs,exports.isBuildHostCommandOf=isBuildHostCommandOf,exports.SHARED_COMPILER_ENV="LENSMCP_SHARED_COMPILER",exports.SHARED_BUILD_ENV="LENSMCP_SHARED_BUILD",exports.SHARED_BUILD_CHANNEL="lensmcp-shared-build",exports.BUILD_HOST_ROLE="build",exports.BUILD_HOST_ROOT_FLAG="--workspace-root=";const ON=/^(1|on|true|yes)$/i,OFF=/^(0|off|false|no)$/i;exports.SHARED_COMPILER_DEFAULT=!1;function sharedCompilerEnabled(t=process.env,e){const n=(t[exports.SHARED_COMPILER_ENV]??"").trim();return ON.test(n)?!0:OFF.test(n)?!1:e??exports.SHARED_COMPILER_DEFAULT}r(sharedCompilerEnabled,"sharedCompilerEnabled"),s(sharedCompilerEnabled,"sharedCompilerEnabled");function isRunnerJoinMessage(t){const e=t;return!!e&&e.channel===exports.SHARED_BUILD_CHANNEL&&e.type==="join"&&typeof e.join?.project=="string"}r(isRunnerJoinMessage,"isRunnerJoinMessage"),s(isRunnerJoinMessage,"isRunnerJoinMessage");function isRunnerInbound(t){const e=t;return!!e&&e.channel===exports.SHARED_BUILD_CHANNEL&&typeof e.event?.type=="string"}r(isRunnerInbound,"isRunnerInbound"),s(isRunnerInbound,"isRunnerInbound");function buildHostArgs(t){return["host",`${exports.BUILD_HOST_ROOT_FLAG}${t.replace(/\/+$/,"")}`]}r(buildHostArgs,"buildHostArgs"),s(buildHostArgs,"buildHostArgs");function isBuildHostCommandOf(t,e){return/\bbuild-host\.(js|ts)\b/.test(t)&&t.trimEnd().endsWith(` ${exports.BUILD_HOST_ROOT_FLAG}${e.replace(/\/+$/,"")}`)}r(isBuildHostCommandOf,"isBuildHostCommandOf"),s(isBuildHostCommandOf,"isBuildHostCommandOf");
@@ -0,0 +1,91 @@
1
+ import type { DevFilesystemCache, DevWebpackOptions } from '../create-webpack-dev';
2
+ /** One webpack entry of one service. */
3
+ export interface SharedEntry {
4
+ /** The webpack entry name: the project for its main bundle, `<project>~<worker>` for a worker. */
5
+ name: string;
6
+ project: string;
7
+ /** Absolute path of the entry module. */
8
+ import: string;
9
+ /** Absolute path of the bundle this entry writes — where the service's devserver loads it from. */
10
+ outputFile: string;
11
+ }
12
+ /** The entries of one service, from the options its serve executor computed. */
13
+ export declare function memberEntries(project: string, options: DevWebpackOptions): SharedEntry[];
14
+ /** webpack's normalised entry description — what a dynamic `entry` function must return. */
15
+ interface EntryDescription {
16
+ import: string[];
17
+ filename: string;
18
+ }
19
+ export declare class EntryRegistry {
20
+ private readonly byProject;
21
+ set(project: string, entries: SharedEntry[]): void;
22
+ delete(project: string): boolean;
23
+ has(project: string): boolean;
24
+ projects(): string[];
25
+ entriesOf(project: string): readonly SharedEntry[];
26
+ /** entry name → project, for every live entry. */
27
+ entryProjects(): Record<string, string>;
28
+ /** The entry for `name`, if any. */
29
+ entry(name: string): SharedEntry | undefined;
30
+ /** webpack's `entry`, with each filename relative to `outputPath` (a bundle lands in its service's own directory). */
31
+ webpackEntries(outputPath: string): Record<string, EntryDescription>;
32
+ }
33
+ export interface SharedEntriesOptions {
34
+ registry: EntryRegistry;
35
+ workspaceRoot: string;
36
+ /** The compilation's own `output.path` — a directory nothing else uses; every bundle's filename is relative to it. */
37
+ outputPath: string;
38
+ cache: DevFilesystemCache;
39
+ }
40
+ /**
41
+ * Applied LAST: by then NxAppWebpackPlugin has written the first member's entry, context and output, and
42
+ * DevBuildFinalizePlugin its cache.
43
+ *
44
+ * - `entry` is the registry, so members come and go between compilations;
45
+ * - `context` is the workspace root: module ids and names no longer depend on which member came first;
46
+ * - `output.path` is a private directory and every bundle's filename climbs out of it to its service's own
47
+ * output directory. `clean` is off: webpack's CleanPlugin skips an asset whose path leaves `output.path`, so it
48
+ * would clean nothing — and a service's own directory is not emptied on its first build, as its own compiler's
49
+ * `clean: true` did;
50
+ * - an async chunk is written beside the bundle of the first runtime that loads it;
51
+ * - the cache is the group's own.
52
+ */
53
+ export declare class SharedEntriesPlugin {
54
+ private readonly options;
55
+ constructor(options: SharedEntriesOptions);
56
+ apply(compiler: any): void;
57
+ }
58
+ /** What one compilation means for one service. */
59
+ export interface EntryAttribution {
60
+ project: string;
61
+ /** Every chunk of its entries, `<id>:<hash>`, hashed — equal iff its emitted bundles are. */
62
+ hash: string;
63
+ /** Its errors and warnings, as printed — at most {@link MAX_PROBLEMS} of each; the counts are complete. */
64
+ errors: string[];
65
+ warnings: string[];
66
+ errorCount: number;
67
+ warningCount: number;
68
+ /** The source files its bundles were built from (absolute, no query). */
69
+ files: string[];
70
+ /** Its emitted bundles, relative to the workspace root, with their sizes. */
71
+ assets: Array<{
72
+ file: string;
73
+ size: number;
74
+ }>;
75
+ }
76
+ /**
77
+ * Split a finished compilation by service: each project's chunks (its entries' and every async chunk they
78
+ * load), the modules in them, the errors and warnings whose module is among them, and a hash over the chunks.
79
+ *
80
+ * An error with no module is a project's when its location names one of that project's entries (an entry that
81
+ * does not resolve); otherwise nobody's in particular, so every project gets it — a reader must not miss it.
82
+ */
83
+ export declare function attributeCompilation(compilation: any, registry: EntryRegistry): Map<string, EntryAttribution>;
84
+ /** The stats lines one service's runner prints for a build — the shapes the gateway reads (pod-output.ts). */
85
+ export interface DescribeBuildOptions {
86
+ webpackVersion: string;
87
+ durationMs: number;
88
+ changed: boolean;
89
+ }
90
+ export declare function describeBuild(a: EntryAttribution, opts: DescribeBuildOptions): string;
91
+ export {};
@@ -0,0 +1,3 @@
1
+ "use strict";var w=Object.defineProperty;var l=(t,e)=>w(t,"name",{value:e,configurable:!0});var $=Object.defineProperty,c=l((t,e)=>$(t,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.SharedEntriesPlugin=exports.EntryRegistry=void 0,exports.memberEntries=memberEntries,exports.attributeCompilation=attributeCompilation,exports.describeBuild=describeBuild;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),path=tslib_1.__importStar(require("node:path"));function memberEntries(t,e){const r=[{name:t,project:t,import:e.main,outputFile:path.join(e.outputDir,"main.js")}];for(const o of e.additionalEntryPoints??[])r.push({name:`${t}~${o.entryName}`,project:t,import:o.entryPath,outputFile:path.join(e.outputDir,`${o.entryName}.js`)});return r}l(memberEntries,"memberEntries"),c(memberEntries,"memberEntries");class EntryRegistry{static{l(this,"EntryRegistry")}static{c(this,"EntryRegistry")}constructor(){this.byProject=new Map}set(e,r){this.byProject.set(e,r)}delete(e){return this.byProject.delete(e)}has(e){return this.byProject.has(e)}projects(){return[...this.byProject.keys()]}entriesOf(e){return this.byProject.get(e)??[]}entryProjects(){const e={};for(const[r,o]of this.byProject)for(const n of o)e[n.name]=r;return e}entry(e){for(const r of this.byProject.values()){const o=r.find(n=>n.name===e);if(o)return o}}webpackEntries(e){const r={};for(const o of this.byProject.values())for(const n of o)r[n.name]={import:[n.import],filename:path.relative(e,n.outputFile)};return r}}exports.EntryRegistry=EntryRegistry;class SharedEntriesPlugin{static{l(this,"SharedEntriesPlugin")}static{c(this,"SharedEntriesPlugin")}constructor(e){this.options=e}apply(e){const{registry:r,workspaceRoot:o,outputPath:n,cache:s}=this.options;e.options.context=o,e.context=o,e.options.output={...e.options.output,path:n,clean:!1,chunkFilename:c(m=>{const i=m.chunk?.runtime,d=typeof i=="string"?i:i?[...i].sort()[0]:void 0,u=d?r.entry(d):void 0;return`${u?path.relative(n,path.dirname(u.outputFile)):"."}/[id].js`},"chunkFilename")},e.options.cache={...s,buildDependencies:{...s.buildDependencies}},e.options.entry=()=>Promise.resolve(r.webpackEntries(n))}}exports.SharedEntriesPlugin=SharedEntriesPlugin;const resourceOf=c(t=>{if(typeof t.resource!="string"||!t.resource)return;const e=t.resource.search(/[?#]/);return e===-1?t.resource:t.resource.slice(0,e)},"resourceOf");function formatLoc(t){const e=t?.start;if(!e||typeof e.line!="number")return"";const r=`${e.line}:${e.column??0}`,o=t?.end;return!o||typeof o.line!="number"?r:o.line===e.line?`${r}-${o.column??0}`:`${r}-${o.line}:${o.column??0}`}l(formatLoc,"formatLoc"),c(formatLoc,"formatLoc");function formatProblem(t,e,r){const o=e.module;let n="";try{n=o?.readableIdentifier?o.readableIdentifier(r.requestShortener):""}catch{}const s=formatLoc(e.loc);return`${n?`${t} in ${n}${s?` ${s}`:""}`:t}
2
+ ${String(e.message??e).trim()}`}l(formatProblem,"formatProblem"),c(formatProblem,"formatProblem");const MAX_PROBLEMS=30,MAX_PROBLEM_CHARS=4e3;function attributeCompilation(t,e){const r=new Map,o=new Map,n=t.compiler?.context??t.options?.context??process.cwd();for(const i of e.projects()){const d=new Set;let u=!1;for(const a of e.entriesOf(i)){const p=t.entrypoints?.get(a.name);if(p){u=!0;for(const f of p.chunks??[]){d.add(f);for(const y of f.getAllAsyncChunks?.()??[])d.add(y)}}}if(!u)continue;const h=new Set,g=[],b=[];for(const a of d){g.push(`${String(a.id)}:${a.hash??""}`);for(const p of t.chunkGraph.getChunkModulesIterable(a)){let f=o.get(p);f||o.set(p,f=new Set),f.add(i);const y=resourceOf(p);y&&h.add(y)}for(const p of a.files??[]){const f=t.getAsset?.(p),y=path.resolve(t.outputOptions?.path??n,p);b.push({file:path.relative(n,y),size:f?.info?.size??f?.source?.size?.()??0})}}r.set(i,{project:i,hash:(0,node_crypto_1.createHash)("sha256").update(g.sort().join("|")).digest("hex").slice(0,16),errors:[],warnings:[],errorCount:0,warningCount:0,files:[...h],assets:b})}const s=e.entryProjects(),m=c((i,d)=>{for(const u of d){let h=u.module?o.get(u.module):void 0;!h&&u.loc?.name&&s[u.loc.name]&&(h=[s[u.loc.name]]),h||(h=r.keys());const g=formatProblem(i==="errors"?"ERROR":"WARNING",u,t);for(const b of h){const a=r.get(b);a&&(i==="errors"?a.errorCount+=1:a.warningCount+=1,a[i].length<MAX_PROBLEMS&&a[i].push(g.length>MAX_PROBLEM_CHARS?`${g.slice(0,MAX_PROBLEM_CHARS)}\u2026`:g))}}},"assign");return m("errors",t.errors??[]),m("warnings",t.warnings??[]),r}l(attributeCompilation,"attributeCompilation"),c(attributeCompilation,"attributeCompilation");function describeBuild(t,e){const r=[];if(e.changed)for(const s of t.assets)r.push(`asset ${s.file} ${formatSize(s.size)} [emitted]`);for(const s of t.errors)r.push("",s);for(const s of t.warnings)r.push("",s);(t.errors.length||t.warnings.length)&&r.push("");const o=c((s,m)=>`${s} ${m}${s===1?"":"s"}`,"plural"),n=t.errorCount?`compiled with ${o(t.errorCount,"error")}${t.warningCount?` and ${o(t.warningCount,"warning")}`:""}`:t.warningCount?`compiled with ${o(t.warningCount,"warning")}`:"compiled successfully";return r.push(`webpack ${e.webpackVersion} ${n} in ${e.durationMs} ms (shared compilation${e.changed?"":" \u2014 this service's bundle is unchanged"})`),r.join(`
3
+ `)}l(describeBuild,"describeBuild"),c(describeBuild,"describeBuild");function formatSize(t){return t>=1024*1024?`${(t/(1024*1024)).toFixed(2)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} bytes`}l(formatSize,"formatSize"),c(formatSize,"formatSize");
@@ -0,0 +1,52 @@
1
+ import type { DevWebpackOptions } from '../create-webpack-dev';
2
+ import type { MemberJoinRequest } from './protocol';
3
+ /** Target options that only name or wire THIS service — never part of what its modules compile to. */
4
+ export declare const PER_SERVICE_TARGET_KEYS: readonly string[];
5
+ /**
6
+ * Compiler options that change type checking or where files go, never what ts-loader emits for a module in
7
+ * `transpileOnly` mode. Everything NOT listed is compared, so an option this list does not know keeps two
8
+ * services apart rather than merging them wrongly.
9
+ */
10
+ export declare const TYPECHECK_ONLY_COMPILER_OPTIONS: ReadonlySet<string>;
11
+ /** Plugins a member's config carries for ITSELF — the shared compilation leaves them out (and adds its own). */
12
+ export declare const PER_SERVICE_PLUGINS: ReadonlySet<string>;
13
+ /** A member's signature: the key, and each part's hash — what the reason line names when two differ. */
14
+ export interface MemberSignature {
15
+ key: string;
16
+ parts: Record<string, string>;
17
+ }
18
+ /** What {@link memberSignature} reads. */
19
+ export interface SignatureInput {
20
+ join: MemberJoinRequest;
21
+ /** The devtool the member's config resolved to. */
22
+ devtool: unknown;
23
+ /** The member's evaluated webpack config (`createDevWebpackConfig` + its override). */
24
+ config: Record<string, unknown>;
25
+ /** The member's tsconfig compiler options, as {@link transpileCompilerOptions} returns them. */
26
+ compilerOptions: Record<string, unknown>;
27
+ }
28
+ /**
29
+ * The compiler options of `tsConfig` that change what ts-loader EMITS, as TypeScript resolves them: `extends`
30
+ * followed, enum values as TypeScript stores them, `paths` resolved to absolute targets. No file is listed:
31
+ * the parse host lists no directories, so `include` globs cost nothing.
32
+ */
33
+ export declare function transpileCompilerOptions(tsConfig: string, workspaceRoot: string): Record<string, unknown>;
34
+ /** The strings in a member's config that only name THIS member, longest first — replaced before fingerprinting. */
35
+ export interface FingerprintMask {
36
+ from: string;
37
+ to: string;
38
+ /** Replace only a string that IS `from` — a project name inside a longer word (`auth` in `authority`) is not this member's. */
39
+ whole?: boolean;
40
+ }
41
+ export declare function memberMasks(options: DevWebpackOptions, project: string): FingerprintMask[];
42
+ /**
43
+ * A stable, serialisable picture of a webpack config: RegExps by source, functions by the hash of their
44
+ * source, class instances by constructor name and fields, cycles cut, depth and width bounded. Strings are
45
+ * masked. The per-service plugins are left out.
46
+ */
47
+ export declare function configFingerprint(config: Record<string, unknown>, masks: readonly FingerprintMask[]): Record<string, string>;
48
+ export declare function memberSignature(input: SignatureInput): MemberSignature;
49
+ /** The first part `a` and `b` disagree on, in words — `undefined` when they agree. */
50
+ export declare function describeDifference(a: MemberSignature, b: MemberSignature): string | undefined;
51
+ /** Why a member cannot be compiled by the build host at all — it compiles on its own, as before. */
52
+ export declare function refusalOf(join: MemberJoinRequest, workspaceRoot: string): string | undefined;
@@ -0,0 +1 @@
1
+ "use strict";var g=Object.defineProperty;var p=(e,i)=>g(e,"name",{value:i,configurable:!0});var d=Object.defineProperty,a=p((e,i)=>d(e,"name",{value:i,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PER_SERVICE_PLUGINS=exports.TYPECHECK_ONLY_COMPILER_OPTIONS=exports.PER_SERVICE_TARGET_KEYS=void 0,exports.transpileCompilerOptions=transpileCompilerOptions,exports.memberMasks=memberMasks,exports.configFingerprint=configFingerprint,exports.memberSignature=memberSignature,exports.describeDifference=describeDifference,exports.refusalOf=refusalOf;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),node_module_1=require("node:module"),path=tslib_1.__importStar(require("node:path"));exports.PER_SERVICE_TARGET_KEYS=["entryFile","tsConfigFile","outputPath","workers","configEnv","childCount","serviceName","servePrefix","gateway","internalPort","debugPort","logFile","webpackConfigPath","devtool","memoryLimit","port"],exports.TYPECHECK_ONLY_COMPILER_OPTIONS=new Set(["configFilePath","pathsBasePath","rootDir","rootDirs","outDir","outFile","declaration","declarationDir","declarationMap","emitDeclarationOnly","composite","incremental","tsBuildInfoFile","noEmit","noEmitOnError","types","typeRoots","lib","skipLibCheck","skipDefaultLibCheck","forceConsistentCasingInFileNames","strict","noImplicitAny","strictNullChecks","strictFunctionTypes","strictBindCallApply","strictPropertyInitialization","strictBuiltinIteratorReturn","noImplicitThis","useUnknownInCatchVariables","noUnusedLocals","noUnusedParameters","exactOptionalPropertyTypes","noImplicitReturns","noFallthroughCasesInSwitch","noUncheckedIndexedAccess","noImplicitOverride","noPropertyAccessFromIndexSignature","allowUnusedLabels","allowUnreachableCode","noErrorTruncation","diagnostics","extendedDiagnostics","listFiles","listEmittedFiles","traceResolution","explainFiles","pretty","preserveWatchOutput","assumeChangesOnlyAffectDirectDependencies","noStrictGenericChecks","suppressExcessPropertyErrors","suppressImplicitAnyIndexErrors","disableSizeLimit","disableSourceOfProjectReferenceRedirect","disableSolutionSearching","disableReferencedProjectLoad","noCheck","noUncheckedSideEffectImports","checkJs","maxNodeModuleJsDepth","isolatedDeclarations","erasableSyntaxOnly","libReplacement"]),exports.PER_SERVICE_PLUGINS=new Set(["DevServerReloadPlugin","PerfRulesPlugin","ForkTsCheckReporterPlugin","TsgoCheckPlugin","ForkTsCheckerWebpackPlugin","HotModuleReplacementPlugin"]);const UNWALKED_PLUGINS=new Set([...exports.PER_SERVICE_PLUGINS,"NxAppWebpackPlugin"]),sha=a(e=>(0,node_crypto_1.createHash)("sha256").update(e).digest("hex").slice(0,16),"sha");function loadTypeScript(e){try{return(0,node_module_1.createRequire)(path.join(e,"package.json"))("typescript")}catch{return require("typescript")}}p(loadTypeScript,"loadTypeScript"),a(loadTypeScript,"loadTypeScript");function canonical(e){if(Array.isArray(e))return e.map(canonical);if(e&&typeof e=="object"){const i={};for(const o of Object.keys(e).sort())i[o]=canonical(e[o]);return i}return e}p(canonical,"canonical"),a(canonical,"canonical");function transpileCompilerOptions(e,i){const o=loadTypeScript(i),r=o.readConfigFile(e,c=>o.sys.readFile(c));if(r.error)throw new Error(`cannot read ${e}: ${String(r.error.messageText)}`);const s={...o.sys,readDirectory:a(()=>[],"readDirectory")},u=o.parseJsonConfigFileContent(r.config,s,path.dirname(e),void 0,e),t={};for(const[c,f]of Object.entries(u.options))exports.TYPECHECK_ONLY_COMPILER_OPTIONS.has(c)||(t[c]=f);const n=u.options.paths;if(n){const c=u.options.baseUrl??u.options.pathsBasePath??path.dirname(e);t.paths=Object.fromEntries(Object.entries(n).map(([f,l])=>[f,l.map(m=>path.resolve(c,m))]))}return canonical(t)}p(transpileCompilerOptions,"transpileCompilerOptions"),a(transpileCompilerOptions,"transpileCompilerOptions");function memberMasks(e,i){const o=[{from:e.appRoot,to:"<appRoot>"},{from:e.outputDir,to:"<outputDir>"},{from:e.main,to:"<main>"},{from:e.tsConfig,to:"<tsConfig>"}];for(const r of e.additionalEntryPoints??[])o.push({from:r.entryPath,to:`<worker:${r.entryName}>`});return o.filter(r=>r.from).sort((r,s)=>s.from.length-r.from.length).concat([i,i.replace(/[^\w.-]+/g,"_"),e.appName,e.serviceName].filter(r=>!!r).map(r=>({from:r,to:"<project>",whole:!0})))}p(memberMasks,"memberMasks"),a(memberMasks,"memberMasks");const MAX_DEPTH=8,MAX_KEYS=200;function configFingerprint(e,i){const o=new WeakSet,r=a(t=>{let n=t;for(const c of i){if(c.whole){if(n===c.from)return c.to;continue}n.includes(c.from)&&(n=n.split(c.from).join(c.to))}return n},"mask"),s=a((t,n)=>{if(typeof t=="string")return r(t);if(typeof t=="function")return`fn:${sha(r(Function.prototype.toString.call(t)))}`;if(t instanceof RegExp)return`re:${r(String(t))}`;if(!t||typeof t!="object")return t;if(o.has(t))return"<cycle>";if(n>=MAX_DEPTH)return"<deep>";o.add(t);try{if(Array.isArray(t))return t.slice(0,MAX_KEYS).map(l=>s(l,n+1));if(t instanceof Map)return{$map:[...t.entries()].slice(0,MAX_KEYS).map(([l,m])=>[s(l,n+1),s(m,n+1)])};if(t instanceof Set)return{$set:[...t].slice(0,MAX_KEYS).map(l=>s(l,n+1))};const c=t.constructor?.name,f=c&&c!=="Object"?{$class:c}:{};for(const l of Object.keys(t).sort().slice(0,MAX_KEYS))f[l]=s(t[l],n+1);return f}finally{o.delete(t)}},"walk"),u={};for(const t of Object.keys(e).sort()){if(t==="entry"||t==="watch")continue;let n=e[t];t==="plugins"&&Array.isArray(n)&&(n=n.filter(c=>!UNWALKED_PLUGINS.has(c?.constructor?.name??""))),t==="output"&&n&&typeof n=="object"&&(n=Object.fromEntries(Object.entries(n).filter(([c])=>c!=="path"))),u[`config.${t}`]=sha(JSON.stringify(s(n,0)))}return u}p(configFingerprint,"configFingerprint"),a(configFingerprint,"configFingerprint");function moduleShapingOptions(e,i){return canonical({devtool:i,buildLibsFromSource:e.buildLibsFromSource??!0,orgScopes:e.orgScopes??[],bundlePackages:e.bundlePackages??[],nodeExternalsConfig:e.nodeExternalsConfig??null})}p(moduleShapingOptions,"moduleShapingOptions"),a(moduleShapingOptions,"moduleShapingOptions");function memberSignature(e){const{join:i}=e,o={};for(const[s,u]of Object.entries(i.targetOptions??{}))exports.PER_SERVICE_TARGET_KEYS.includes(s)||(o[s]=u);const r={options:sha(JSON.stringify(moduleShapingOptions(i.options,e.devtool))),targetOptions:sha(JSON.stringify(canonical(o))),tsconfig:sha(JSON.stringify(e.compilerOptions)),...configFingerprint(e.config,memberMasks(i.options,i.project))};return{key:sha(JSON.stringify(canonical(r))),parts:r}}p(memberSignature,"memberSignature"),a(memberSignature,"memberSignature");const PART_NAMES={options:"its build options (devtool, buildLibsFromSource, orgScopes, bundlePackages or nodeExternalsConfig)",targetOptions:"its serve target options",tsconfig:"its tsconfig compiler options"};function describeDifference(e,i){const o=[...new Set([...Object.keys(e.parts),...Object.keys(i.parts)])].sort((r,s)=>order(r)-order(s)||r.localeCompare(s));for(const r of o)if(e.parts[r]!==i.parts[r])return PART_NAMES[r]??`its webpack config (\`${r.slice(7)}\` \u2014 a webpackConfigPath override?)`}p(describeDifference,"describeDifference"),a(describeDifference,"describeDifference");function order(e){return e in PART_NAMES?0:1}p(order,"order"),a(order,"order");function refusalOf(e,i){const{options:o}=e,r=a(s=>!!s&&!path.relative(i,path.resolve(i,s)).startsWith(".."),"inside");if(o.buildLibsFromSource===!1)return"buildLibsFromSource is false \u2014 its library paths are its own (a tsconfig nx writes per project)";if([...o.assets??[],...Array.isArray(e.targetOptions?.assets)?e.targetOptions.assets:[]].length>0)return"it copies assets \u2014 they are copied relative to one output directory, and a shared compilation writes several";if(!r(o.outputDir))return`its output directory (${o.outputDir}) is outside the workspace`;if(o.webpackConfigPath&&!r(path.resolve(o.appRoot,o.webpackConfigPath)))return"its webpackConfigPath is outside the workspace";for(const s of o.additionalEntryPoints??[])if(!/^[\w.-]+$/.test(s.entryName))return`worker "${s.entryName}" is not a plain file name`}p(refusalOf,"refusalOf"),a(refusalOf,"refusalOf");
package/stdio-guard.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * A pod child's stdout and stderr are PIPES to its devserver parent, and the parent can go first
3
- * (issues/foodguard/25). A write to a pipe nobody reads fails with EPIPE, which Node delivers as an
3
+ * (issues/closed/foodguard-25). A write to a pipe nobody reads fails with EPIPE, which Node delivers as an
4
4
  * `'error'` EVENT on the stream; with no listener that is an uncaught exception. The devserver's
5
5
  * catch-all then printed `[child] uncaughtException (uncaughtException): Error: write EPIPE`, and the
6
6
  * stdout tap filed it — stack and all — as a server-side error. So a `lensmcp setup` or a gateway