@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
@@ -0,0 +1,28 @@
1
+ import { type BarrelMeasurement, type HeavyBarrelThresholds, type ProjectCompilation, type SourceReader } from './heavy-barrel-import';
2
+ export interface BarrelAnalysisJob {
3
+ workspaceRoot: string;
4
+ /** The tsconfig whose library aliases are read. */
5
+ tsConfig: string;
6
+ projects: ProjectCompilation[];
7
+ thresholds: HeavyBarrelThresholds;
8
+ budgetMs: number;
9
+ }
10
+ export interface BarrelAnalysisOutcome {
11
+ complete: boolean;
12
+ measurements: BarrelMeasurement[];
13
+ elapsedMs: number;
14
+ /** Set when the analysis threw — nothing is concluded. */
15
+ error?: string;
16
+ /** Where it ran. */
17
+ thread: 'worker' | 'main';
18
+ }
19
+ /** A source reader over the real files: each read once, size-capped. */
20
+ export declare function fileReader(): SourceReader;
21
+ /** Library discovery + the analysis, within the budget. Never throws. */
22
+ export declare function runBarrelAnalysis(job: BarrelAnalysisJob, thread?: BarrelAnalysisOutcome['thread']): BarrelAnalysisOutcome;
23
+ /**
24
+ * {@link runBarrelAnalysis} on a worker thread: the caller's event loop stays free while it runs. The worker never
25
+ * keeps the process alive. When no worker can be started, the analysis runs here, as it did before — the outcome
26
+ * says which.
27
+ */
28
+ export declare function runBarrelAnalysisOffThread(job: BarrelAnalysisJob): Promise<BarrelAnalysisOutcome>;
@@ -0,0 +1 @@
1
+ "use strict";var u=Object.defineProperty;var l=(e,t)=>u(e,"name",{value:t,configurable:!0});var c=Object.defineProperty,i=l((e,t)=>c(e,"name",{value:t,configurable:!0}),"i");Object.defineProperty(exports,"__esModule",{value:!0}),exports.fileReader=fileReader,exports.runBarrelAnalysis=runBarrelAnalysis,exports.runBarrelAnalysisOffThread=runBarrelAnalysisOffThread;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),node_worker_threads_1=require("node:worker_threads"),heavy_barrel_import_1=require("./heavy-barrel-import"),workspace_libraries_1=require("./workspace-libraries"),MAX_SOURCE_BYTES=1024*1024;function fileReader(){const e=new Map,t=new Map;return{read(r){if(e.has(r))return e.get(r);let s;try{fs.statSync(r).size<=MAX_SOURCE_BYTES&&(s=fs.readFileSync(r,"utf8"))}catch{s=void 0}return e.set(r,s),s},exists(r){let s=t.get(r);if(s===void 0){try{s=fs.statSync(r).isFile()}catch{s=!1}t.set(r,s)}return s}}}l(fileReader,"fileReader"),i(fileReader,"fileReader");function runBarrelAnalysis(e,t="main"){const r=Date.now(),s=r+e.budgetMs;try{const a=(0,workspace_libraries_1.discoverWorkspaceLibraries)(e.workspaceRoot,{tsConfig:e.tsConfig}),n=(0,heavy_barrel_import_1.analyzeBarrelImports)({workspaceRoot:e.workspaceRoot,libraries:a,projects:e.projects,thresholds:e.thresholds,source:fileReader(),expired:i(()=>Date.now()>s,"expired")});return{complete:n.complete,measurements:n.measurements,elapsedMs:Date.now()-r,thread:t}}catch(a){return{complete:!1,measurements:[],elapsedMs:Date.now()-r,error:a?.message??String(a),thread:t}}}l(runBarrelAnalysis,"runBarrelAnalysis"),i(runBarrelAnalysis,"runBarrelAnalysis");function workerScript(){const e=path.join(__dirname,"perf-rules-worker.js");if(fs.existsSync(e))return{file:e,eval:!1};const t=path.join(__dirname,"perf-rules-worker.ts");if(fs.existsSync(t))try{const r=require.resolve("@swc-node/register");return{file:`require(${JSON.stringify(r)}); require(${JSON.stringify(t)});`,eval:!0}}catch{return}}l(workerScript,"workerScript"),i(workerScript,"workerScript");function runBarrelAnalysisOffThread(e){const t=workerScript();return t?new Promise(r=>{let s=!1;const a=i(o=>{s||(s=!0,r(o))},"settle");let n;try{n=new node_worker_threads_1.Worker(t.file,{eval:t.eval,workerData:e})}catch{a(runBarrelAnalysis(e,"main"));return}n.unref(),n.once("message",o=>a(o)),n.once("error",o=>a({complete:!1,measurements:[],elapsedMs:0,error:o.message,thread:"worker"})),n.once("exit",o=>a({complete:!1,measurements:[],elapsedMs:0,error:`the analysis worker exited (${o}) without an answer`,thread:"worker"}))}):Promise.resolve(runBarrelAnalysis(e,"main"))}l(runBarrelAnalysisOffThread,"runBarrelAnalysisOffThread"),i(runBarrelAnalysisOffThread,"runBarrelAnalysisOffThread");
@@ -0,0 +1,71 @@
1
+ import { type BarrelMeasurement } from './heavy-barrel-import';
2
+ import { type PerfRulePublisher } from './perf-rule-events';
3
+ import { type BarrelAnalysisOutcome } from './perf-rules-analysis';
4
+ export interface PerfRulesPluginOptions {
5
+ /** The Nx project this compiler builds. */
6
+ project: string;
7
+ workspaceRoot: string;
8
+ /** The project's tsconfig — the library aliases are read from what it sees. */
9
+ tsConfig: string;
10
+ /** The bus file. Default `LENSMCP_EVENT_FILE`, else `<workspace>/.lensmcp/events.jsonl`. */
11
+ eventFile?: string;
12
+ /** A multi-entry compilation: entry name → project — or a function, read per compilation, when the entries change.
13
+ * Absent: every module is `project`'s. */
14
+ entries?: Record<string, string> | (() => Record<string, string>);
15
+ env?: NodeJS.ProcessEnv;
16
+ /** How long after a compilation the analysis runs. */
17
+ delayMs?: number;
18
+ publisher?: PerfRulePublisher;
19
+ now?: () => number;
20
+ /** After each analysis — for the spec and for anyone who wants the measurements. */
21
+ onAnalysis?: (run: PerfRulesRun) => void;
22
+ log?: (line: string) => void;
23
+ }
24
+ /** One analysis, as `onAnalysis` sees it. */
25
+ export interface PerfRulesRun {
26
+ complete: boolean;
27
+ elapsedMs: number;
28
+ /** Where the analysis ran: a worker thread, or this one (the spec's `analyze()`, or no worker could start). */
29
+ thread: BarrelAnalysisOutcome['thread'];
30
+ measurements: BarrelMeasurement[];
31
+ /** Per project: the subjects open after this run. */
32
+ open: Record<string, string[]>;
33
+ published: number;
34
+ }
35
+ export declare class PerfRulesPlugin {
36
+ private readonly options;
37
+ private readonly settings;
38
+ private readonly publisher;
39
+ private readonly now;
40
+ private readonly log;
41
+ /** Per project: the module-set fingerprint last analysed (or pending). */
42
+ private readonly signatures;
43
+ /** Per project: the subjects this process has published as open. */
44
+ private readonly open;
45
+ /** Per project: the title last logged per subject — a finding is logged when it opens or changes, not per run. */
46
+ private readonly logged;
47
+ private readonly pending;
48
+ private timer;
49
+ /** The analysis on its worker thread, while it runs — the next one waits for it. */
50
+ private running;
51
+ private closed;
52
+ /** The most recent compilation that built enough modules to estimate per-module compile time from. */
53
+ private estimateBasis;
54
+ constructor(options: PerfRulesPluginOptions);
55
+ apply(compiler: any): void;
56
+ /** Each project's modules in this compilation — by entry when `entries` says, else all of them. */
57
+ private projectModules;
58
+ private collect;
59
+ private schedule;
60
+ /** Take every pending project's modules: the job one analysis runs. */
61
+ private takeJob;
62
+ /** Run the rules over every pending project on THIS thread, publish what changed. Exposed for the spec. */
63
+ analyze(): PerfRulesRun | undefined;
64
+ /**
65
+ * Run the rules over every pending project on a worker thread, publish what changed — what the timer runs. One
66
+ * at a time: projects that changed meanwhile wait for this one, then go in the next.
67
+ */
68
+ analyzeOffThread(): Promise<PerfRulesRun | undefined>;
69
+ private conclude;
70
+ private publishProject;
71
+ }
@@ -0,0 +1 @@
1
+ "use strict";var E=Object.defineProperty;var g=(l,e)=>E(l,"name",{value:e,configurable:!0});var _=Object.defineProperty,p=g((l,e)=>_(l,"name",{value:e,configurable:!0}),"p");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PerfRulesPlugin=void 0;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),heavy_barrel_import_1=require("./heavy-barrel-import"),perf_config_1=require("./perf-config"),perf_rule_events_1=require("./perf-rule-events"),perf_rules_analysis_1=require("./perf-rules-analysis"),NAME="LensmcpPerfRulesPlugin",DEFAULT_DELAY_MS=1e3;function fnv(l){let e=2166136261;for(let t=0;t<l.length;t++)e^=l.charCodeAt(t),e=Math.imul(e,16777619);return e>>>0}g(fnv,"fnv"),p(fnv,"fnv");const resourceOf=p(l=>{if(typeof l.resource!="string"||!l.resource)return;const e=l.resource.search(/[?#]/);return e===-1?l.resource:l.resource.slice(0,e)},"resourceOf");class PerfRulesPlugin{static{g(this,"PerfRulesPlugin")}static{p(this,"PerfRulesPlugin")}constructor(e){this.options=e,this.signatures=new Map,this.open=new Map,this.logged=new Map,this.pending=new Map,this.closed=!1;const t=e.env??process.env;this.settings=(0,perf_config_1.resolveBundleRuleSettings)(e.workspaceRoot,t),this.publisher=e.publisher??(0,perf_rule_events_1.createPerfRulePublisher)(e.eventFile??(t.LENSMCP_EVENT_FILE||path.join(e.workspaceRoot,".lensmcp","events.jsonl"))),this.now=e.now??Date.now,this.log=e.log??(s=>console.log(s))}apply(e){if(!this.settings.enabled)return;const t=new WeakMap;e.hooks.thisCompilation.tap(NAME,s=>{t.set(s,0),s.hooks.buildModule.tap(NAME,()=>t.set(s,(t.get(s)??0)+1))}),e.hooks.done.tap(NAME,s=>{try{if(s.hasErrors())return;const i=s.compilation,o={durationMs:Math.max(0,(s.endTime??0)-(s.startTime??0)),builtModules:t.get(i)??0};this.collect(i,o)}catch{}}),e.hooks.watchClose?.tap(NAME,()=>{this.closed=!0,this.timer&&clearTimeout(this.timer),this.timer=void 0,this.pending.clear()})}projectModules(e){const t=new Map,s=typeof this.options.entries=="function"?this.options.entries():this.options.entries;if(!s)return t.set(this.options.project,e.modules),t;const i=new Map;for(const[o,r]of Object.entries(s)){const c=e.entrypoints?.get(o);if(!c)continue;const h=i.get(r)??new Set;for(const u of c.chunks??[])for(const n of e.chunkGraph.getChunkModulesIterable(u))h.add(n);i.set(r,h)}for(const[o,r]of i)t.set(o,r);return t}collect(e,t){t.builtModules>=50&&(this.estimateBasis=t);let s=!1;for(const[i,o]of this.projectModules(e)){let r=0,c=0,h=0;const u=[];for(const a of o){const f=resourceOf(a);if(!f)continue;const d=fnv(f);r=r+d>>>0,c=(c^Math.imul(d,2654435761))>>>0,h++,u.push(a)}const n=`${h}:${r.toString(36)}:${c.toString(36)}`;if(this.signatures.get(i)===n)continue;this.signatures.set(i,n);const j=e.moduleGraph,M=u.map(a=>{const f=new Set;for(const m of j.getOutgoingConnections(a)){const b=m.module?resourceOf(m.module):void 0;b&&f.add(b)}let d;try{d=typeof a.size=="function"?a.size():0}catch{d=0}return{resource:resourceOf(a),size:d,dependencies:[...f]}});this.pending.set(i,{modules:M,compile:t}),s=!0}s&&this.schedule()}schedule(){this.timer&&clearTimeout(this.timer),this.timer=setTimeout(()=>{this.timer=void 0,this.analyzeOffThread()},this.options.delayMs??DEFAULT_DELAY_MS),this.timer.unref?.()}takeJob(){if(this.pending.size===0)return;const e=[...this.pending].map(([t,s])=>({project:t,modules:s.modules,compile:s.compile}));return this.pending.clear(),{workspaceRoot:this.options.workspaceRoot,tsConfig:this.options.tsConfig,projects:e,thresholds:this.settings.thresholds,budgetMs:this.settings.budgetMs}}analyze(){const e=this.takeJob();return e?this.conclude(e,(0,perf_rules_analysis_1.runBarrelAnalysis)(e,"main")):void 0}analyzeOffThread(){if(this.running)return this.running.then(()=>this.analyzeOffThread());const e=this.takeJob();if(!e)return Promise.resolve(void 0);const t=(0,perf_rules_analysis_1.runBarrelAnalysisOffThread)(e).then(s=>this.closed?void 0:this.conclude(e,s)).finally(()=>{this.running=void 0,this.pending.size>0&&!this.closed&&!this.timer&&this.schedule()});return this.running=t,t}conclude(e,t){const{projects:s}=e;if(t.error){this.log(`[perf-rules] analysis failed: ${t.error}`);for(const o of s)this.signatures.delete(o.project);return}const i={complete:t.complete,elapsedMs:t.elapsedMs,thread:t.thread,measurements:t.measurements,open:{},published:0};if(!t.complete){this.log(`[perf-rules] ${s.map(o=>o.project).join(", ")}: the analysis ran past its ${this.settings.budgetMs} ms budget \u2014 nothing concluded (LENSMCP_PERF_RULES_BUDGET_MS)`);for(const o of s)this.signatures.delete(o.project);return this.options.onAnalysis?.(i),i}try{for(const o of s)i.published+=this.publishProject(o,t.measurements.filter(r=>r.project===o.project&&r.fires),i)}catch(o){this.log(`[perf-rules] publishing failed: ${o?.message??String(o)}`);for(const r of s)this.signatures.delete(r.project);return}return this.options.onAnalysis?.(i),i}publishProject(e,t,s){const i=this.now(),o=this.open.get(e.project)??new Set,r=new Set,c=this.logged.get(e.project)??new Map;let h=0;for(const u of t){const n=(0,heavy_barrel_import_1.describeHeavyBarrel)(u,e.compile.builtModules>=50?e.compile:this.estimateBasis);r.add(n.subject),this.publisher.publish((0,perf_rule_events_1.buildPerfFindingEvent)(e.project,{...n,rule:heavy_barrel_import_1.HEAVY_BARREL_RULE,docs:`${perf_rule_events_1.PERF_RULES_DOC}#bundleheavy-barrel-import`},i))&&h++,c.get(n.subject)!==n.title&&(c.set(n.subject,n.title),this.log(`[perf-rules] ${heavy_barrel_import_1.HEAVY_BARREL_RULE}: ${n.title} \u2014 ${n.cost.modules} modules (${(n.cost.bytes/(1024*1024)).toFixed(1)} MB) compiled for nothing; import ${n.subject} by subpath (the files and lines: perf://rules)`))}for(const u of o)r.has(u)||(this.publisher.publish((0,perf_rule_events_1.buildPerfRetractedEvent)(heavy_barrel_import_1.HEAVY_BARREL_RULE,e.project,u,"cleared",i))&&h++,c.delete(u),this.log(`[perf-rules] ${heavy_barrel_import_1.HEAVY_BARREL_RULE}: cleared for ${e.project} \u2014 it no longer compiles ${u} through its root barrel`));return(!this.open.has(e.project)||o.size>0||r.size>0)&&this.publisher.publish((0,perf_rule_events_1.buildPerfScanEvent)(heavy_barrel_import_1.HEAVY_BARREL_RULE,e.project,[...r].sort(),i,void 0,s.elapsedMs))&&h++,this.open.set(e.project,r),this.logged.set(e.project,c),s.open[e.project]=[...r].sort(),h}}exports.PerfRulesPlugin=PerfRulesPlugin;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const node_worker_threads_1=require("node:worker_threads"),perf_rules_analysis_1=require("./perf-rules-analysis");node_worker_threads_1.parentPort?.postMessage((0,perf_rules_analysis_1.runBarrelAnalysis)(node_worker_threads_1.workerData,"worker"));
@@ -0,0 +1,59 @@
1
+ /**
2
+ * A static scan of TypeScript/JavaScript source for what `bundle/heavy-barrel-import` needs to know: which names a
3
+ * file imports from a specifier, and which names a library file exports (declared, re-exported, or `export *`-ed).
4
+ *
5
+ * Deliberately not a parser: the rule runs inside the dev webpack process after a compilation, over hundreds of
6
+ * files, and must stay cheap and dependency-free. It reads import/export STATEMENTS — which sit at a file's top
7
+ * level in practice — after blanking comments, so a commented-out import never counts. What it cannot read (a
8
+ * dynamic `import()`/`require()` of the specifier, a namespace object passed around whole) is reported as
9
+ * `wholeModule`, and the rule treats that importer as needing everything: an unreadable usage never produces a
10
+ * finding, it only prevents one.
11
+ */
12
+ /** One name a file imports. */
13
+ export interface ImportedName {
14
+ /** The exported name (`default` for a default import). */
15
+ imported: string;
16
+ /** `import type`, or `{ type X }`: erased by the compiler, costs nothing at runtime. */
17
+ typeOnly: boolean;
18
+ }
19
+ /** Everything one file takes from one specifier. */
20
+ export interface SpecifierUsage {
21
+ specifier: string;
22
+ names: ImportedName[];
23
+ /** The whole module object escapes (`import *` used other than as `ns.name`, `require()`, `import()`). */
24
+ wholeModule: boolean;
25
+ /** `import 'x'` / `export * from 'x'` — loaded for its effects or forwarded whole. */
26
+ sideEffect: boolean;
27
+ }
28
+ /** What a library file exports, and from where. */
29
+ export interface ExportIndex {
30
+ /** Names declared here as values (functions, classes, consts, enums). */
31
+ values: Set<string>;
32
+ /** Names declared here as types only (interfaces, type aliases) — no runtime module behind them. */
33
+ types: Set<string>;
34
+ /** `export { a as b } from './x'` — exported name → [specifier, name there]. */
35
+ reexports: Map<string, {
36
+ specifier: string;
37
+ name: string;
38
+ typeOnly: boolean;
39
+ }>;
40
+ /** `export * from './x'` — searched, in order, for any name not found above. */
41
+ stars: string[];
42
+ /** `export * as ns from './x'` — the name stands for that whole module. */
43
+ namespaces: Map<string, string>;
44
+ /** Specifiers this file's OWN code loads (value and side-effect imports — not re-exports, not type-only). */
45
+ valueImports: string[];
46
+ }
47
+ /**
48
+ * The source with every comment replaced by spaces (newlines kept), strings, template literals and regex literals
49
+ * left intact — so offsets and line numbers still match the file, and a `//` inside a string is not a comment.
50
+ * Jumps from one quote or slash to the next: the text between them is copied as it is.
51
+ */
52
+ export declare function blankComments(src: string): string;
53
+ /**
54
+ * What `source` imports from each specifier `wanted` accepts — named imports, default, namespaces (with the
55
+ * `ns.name` accesses that follow), `import x = require()`, re-exports, side-effect and dynamic loads.
56
+ */
57
+ export declare function scanImports(source: string, wanted: (specifier: string) => boolean): SpecifierUsage[];
58
+ /** What a library file exports, read from its source. */
59
+ export declare function scanExports(source: string): ExportIndex;
@@ -0,0 +1,5 @@
1
+ "use strict";var y=Object.defineProperty;var d=(o,r)=>y(o,"name",{value:r,configurable:!0});var u=Object.defineProperty,m=d((o,r)=>u(o,"name",{value:r,configurable:!0}),"m");Object.defineProperty(exports,"__esModule",{value:!0}),exports.blankComments=blankComments,exports.scanImports=scanImports,exports.scanExports=scanExports;const KEYWORDS_BEFORE_REGEX=new Set(["return","typeof","case","do","else","in","of","void","delete","throw","new","yield","await","instanceof"]),PUNCT_BEFORE_REGEX=new Set(["(",",","=",":","[","!","&","|","?","{","}",";","+","-","*","%","<",">","~","^"]);function regexAllowedAt(o,r){let s=r-1;for(;s>=0&&(o[s]===" "||o[s]===" "||o[s]===`
2
+ `||o[s]==="\r");)s--;if(s<0)return!0;const a=o[s];if(PUNCT_BEFORE_REGEX.has(a))return!0;if(!/[\w$]/.test(a))return!1;let l=s;for(;l>0&&/[\w$]/.test(o[l-1]);)l--;return KEYWORDS_BEFORE_REGEX.has(o.slice(l,s+1))}d(regexAllowedAt,"regexAllowedAt"),m(regexAllowedAt,"regexAllowedAt");function stringEnd(o,r){const s=o[r];let a=r+1;for(;a<o.length&&o[a]!==s;){if(o[a]==="\\")a++;else if(s!=="`"&&o[a]===`
3
+ `)return a;a++}return Math.min(a+1,o.length)}d(stringEnd,"stringEnd"),m(stringEnd,"stringEnd");function regexEnd(o,r){let s=r+1,a=!1;for(;s<o.length&&o[s]!==`
4
+ `;){const l=o[s];if(l==="\\")s++;else if(l==="[")a=!0;else if(l==="]")a=!1;else if(l==="/"&&!a)return s+1;s++}return s}d(regexEnd,"regexEnd"),m(regexEnd,"regexEnd");function blankComments(o){const r=[],s=/[/"'`]/g;let a=0,l=0;for(;;){s.lastIndex=l;const t=s.exec(o);if(!t)break;const c=t.index;if(o[c]!=="/"){l=stringEnd(o,c);continue}const e=o[c+1];if(e==="/"||e==="*"){const n=e==="/"?o.indexOf(`
5
+ `,c):o.indexOf("*/",c+2),i=n===-1?o.length:e==="/"?n:n+2;r.push(o.slice(a,c),o.slice(c,i).replace(/[^\n]/g," ")),a=l=i;continue}l=regexAllowedAt(o,c)?regexEnd(o,c):c+1}return r.push(o.slice(a)),r.join("")}d(blankComments,"blankComments"),m(blankComments,"blankComments");const SPEC=String.raw`['"]([^'"\n]+)['"]`,IMPORT_NAMED=new RegExp(String.raw`\bimport\s+(type\s+)?(?:([\w$]+)\s*,\s*)?\{([^}]*)\}\s*from\s*${SPEC}`,"g"),IMPORT_NAMESPACE=new RegExp(String.raw`\bimport\s+(type\s+)?(?:([\w$]+)\s*,\s*)?\*\s*as\s+([\w$]+)\s+from\s*${SPEC}`,"g"),IMPORT_DEFAULT=new RegExp(String.raw`\bimport\s+(type\s+)?([\w$]+)\s+from\s*${SPEC}`,"g"),IMPORT_EQUALS=new RegExp(String.raw`\bimport\s+(type\s+)?([\w$]+)\s*=\s*require\s*\(\s*${SPEC}\s*\)`,"g"),IMPORT_BARE=new RegExp(String.raw`\bimport\s*${SPEC}`,"g"),EXPORT_FROM=new RegExp(String.raw`\bexport\s+(type\s+)?\{([^}]*)\}\s*from\s*${SPEC}`,"g"),EXPORT_STAR=new RegExp(String.raw`\bexport\s+(type\s+)?\*\s*(?:as\s+([\w$]+)\s+)?from\s*${SPEC}`,"g"),DYNAMIC=new RegExp(String.raw`\b(?:require|import)\s*\(\s*${SPEC}\s*\)`,"g"),EXPORT_LOCAL=/\bexport\s+(type\s+)?\{([^}]*)\}(?!\s*from)/g,EXPORT_VALUE_DECL=/\bexport\s+(?:declare\s+)?(?:default\s+)?(?:async\s+)?(?:function\s*\*?\s*|(?:abstract\s+class|class|const\s+enum|enum|const|let|var)\s+)([\w$]+)/g,EXPORT_DESTRUCTURED=/\bexport\s+(?:declare\s+)?(?:const|let|var)\s+[{[]([^}\]]*)[}\]]/g,EXPORT_TYPE_DECL=/\bexport\s+(?:declare\s+)?(?:interface|type)\s+([\w$]+)/g,EXPORT_DEFAULT=/\bexport\s+default\b/,EXPORT_NAMESPACE_DECL=/\bexport\s+(?:declare\s+)?namespace\s+([\w$]+)/g;function parseSpecifiers(o,r){const s=[];for(const a of o.split(",")){const l=a.trim();if(!l)continue;const t=/^(type\s+)?([\w$]+)(?:\s+as\s+([\w$]+))?$/.exec(l);t&&s.push({imported:t[2],local:t[3]??t[2],typeOnly:r||!!t[1]})}return s}d(parseSpecifiers,"parseSpecifiers"),m(parseSpecifiers,"parseSpecifiers");function reEscape(o){return o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}d(reEscape,"reEscape"),m(reEscape,"reEscape");function scanImports(o,r){const s=blankComments(o),a=new Map,l=m(e=>{let n=a.get(e);return n||(n={specifier:e,names:[],wholeModule:!1,sideEffect:!1},a.set(e,n)),n},"of"),t=new Set,c=[];for(const e of s.matchAll(IMPORT_NAMED)){const n=e[4];if(t.add(e.index),!r(n))continue;const i=l(n),p=!!e[1];e[2]&&i.names.push({imported:"default",typeOnly:p});for(const f of parseSpecifiers(e[3],p))i.names.push({imported:f.imported,typeOnly:f.typeOnly})}for(const e of s.matchAll(IMPORT_NAMESPACE)){const n=e[4];if(t.add(e.index),!r(n))continue;const i=l(n);e[2]&&i.names.push({imported:"default",typeOnly:!!e[1]}),c.push({local:e[3],specifier:n,typeOnly:!!e[1]})}for(const e of s.matchAll(IMPORT_EQUALS)){const n=e[3];t.add(e.index),r(n)&&(l(n),c.push({local:e[2],specifier:n,typeOnly:!!e[1]}))}for(const e of s.matchAll(IMPORT_DEFAULT)){const n=e[3];t.add(e.index),r(n)&&l(n).names.push({imported:"default",typeOnly:!!e[1]})}for(const e of s.matchAll(EXPORT_FROM)){const n=e[3];if(!r(n))continue;const i=l(n);for(const p of parseSpecifiers(e[2],!!e[1]))i.names.push({imported:p.imported,typeOnly:p.typeOnly})}for(const e of s.matchAll(EXPORT_STAR)){const n=e[3];if(!r(n)||e[1])continue;const i=l(n);i.wholeModule=!0,i.sideEffect=!0}for(const e of s.matchAll(IMPORT_BARE)){if(t.has(e.index))continue;const n=e[1];r(n)&&(l(n).sideEffect=!0)}for(const e of s.matchAll(DYNAMIC)){const n=e[1];if(!r(n))continue;const i=s.slice(Math.max(0,e.index-64),e.index);/\bimport\s+(?:type\s+)?[\w$]+\s*=\s*$/.test(i)||(l(n).wholeModule=!0)}for(const e of c){const n=l(e.specifier),i=new RegExp(String.raw`(?<![\w$.])${reEscape(e.local)}\s*(\.|\?\.|\[)?\s*([\w$]+)?`,"g");let p=0;for(const f of s.matchAll(i)){if(f[1]==="."||f[1]==="?."){f[2]&&n.names.push({imported:f[2],typeOnly:e.typeOnly});continue}p++,p>1&&(n.wholeModule=!0)}}for(const e of a.values()){const n=new Set;e.names=e.names.filter(p=>{const f=`${p.imported}:${p.typeOnly}`;return n.has(f)?!1:(n.add(f),!0)});const i=new Set(e.names.filter(p=>!p.typeOnly).map(p=>p.imported));e.names=e.names.filter(p=>!p.typeOnly||!i.has(p.imported))}return[...a.values()]}d(scanImports,"scanImports"),m(scanImports,"scanImports");function scanExports(o){const r=blankComments(o),s={values:new Set,types:new Set,reexports:new Map,stars:[],namespaces:new Map,valueImports:[]},a=new Map,l=new Set;for(const t of r.matchAll(IMPORT_NAMED)){const c=!!t[1];t[2]&&a.set(t[2],{specifier:t[4],name:"default",typeOnly:c});const e=parseSpecifiers(t[3],c);for(const n of e)a.set(n.local,{specifier:t[4],name:n.imported,typeOnly:n.typeOnly});!c&&(t[2]||e.some(n=>!n.typeOnly))&&l.add(t[4])}for(const t of r.matchAll(IMPORT_NAMESPACE))a.set(t[3],{specifier:t[4],name:"*",typeOnly:!!t[1]}),t[1]||l.add(t[4]);for(const t of r.matchAll(IMPORT_EQUALS))a.set(t[2],{specifier:t[3],name:"*",typeOnly:!!t[1]}),t[1]||l.add(t[3]);for(const t of r.matchAll(IMPORT_DEFAULT))a.set(t[2],{specifier:t[3],name:"default",typeOnly:!!t[1]}),t[1]||l.add(t[3]);for(const t of r.matchAll(IMPORT_BARE))l.add(t[1]);for(const t of r.matchAll(DYNAMIC))l.add(t[1]);s.valueImports=[...l];for(const t of r.matchAll(EXPORT_VALUE_DECL))s.values.add(t[1]);for(const t of r.matchAll(EXPORT_DESTRUCTURED))for(const c of t[1].split(",")){const e=/([\w$]+)\s*(?:=.*)?$/.exec(c.split(":").pop().trim())?.[1];e&&s.values.add(e)}for(const t of r.matchAll(EXPORT_NAMESPACE_DECL))s.values.add(t[1]);for(const t of r.matchAll(EXPORT_TYPE_DECL))s.types.add(t[1]);EXPORT_DEFAULT.test(r)&&s.values.add("default");for(const t of r.matchAll(EXPORT_FROM))for(const c of parseSpecifiers(t[2],!!t[1]))s.reexports.set(c.local,{specifier:t[3],name:c.imported,typeOnly:c.typeOnly});for(const t of r.matchAll(EXPORT_STAR))t[1]||(t[2]?s.namespaces.set(t[2],t[3]):s.stars.push(t[3]));for(const t of r.matchAll(EXPORT_LOCAL))for(const c of parseSpecifiers(t[2],!!t[1])){const e=a.get(c.imported);e&&e.name==="*"?s.namespaces.set(c.local,e.specifier):e?s.reexports.set(c.local,{specifier:e.specifier,name:e.name,typeOnly:c.typeOnly||e.typeOnly}):c.typeOnly?s.types.add(c.local):s.values.add(c.local)}return s}d(scanExports,"scanExports"),m(scanExports,"scanExports");
@@ -0,0 +1,52 @@
1
+ /** An exact entry: `@org/contracts/audit` → `<abs>/shared/contracts/src/audit/index.ts`. */
2
+ export interface LibraryEntry {
3
+ specifier: string;
4
+ entry: string;
5
+ }
6
+ /** A wildcard entry: `@org/platform/*` → `<abs>/shared/platform/src/` + `*`. */
7
+ export interface LibraryWildcard {
8
+ /** The specifier before the `*` (`@org/platform/`). */
9
+ prefix: string;
10
+ /** The directory the `*` is resolved in, ending in a separator. */
11
+ dir: string;
12
+ }
13
+ export interface WorkspaceLibrary {
14
+ /** The root specifier — the barrel's import path. */
15
+ name: string;
16
+ /** Other specifiers that resolve to the same barrel (`@a/config` and `@b/config` → one file). */
17
+ aliases: string[];
18
+ /** The library's directory (where its `project.json` / `package.json` is); its modules are under it. */
19
+ root: string;
20
+ /** The barrel: the file the root specifier resolves to. */
21
+ rootEntry: string;
22
+ subpaths: LibraryEntry[];
23
+ wildcards: LibraryWildcard[];
24
+ /** Source files in the library (tests, stories and declarations excluded), capped at the walk's bound. */
25
+ files: number;
26
+ }
27
+ export interface DiscoverOptions {
28
+ /** The project's tsconfig (absolute). Default `<workspace>/tsconfig.base.json`. */
29
+ tsConfig?: string;
30
+ /** The most files counted per library. */
31
+ maxFilesPerLibrary?: number;
32
+ }
33
+ /**
34
+ * JSON with comments and trailing commas (tsconfig's dialect) → a value. Strings are kept verbatim, so a
35
+ * `"https://…"` is not taken for a comment. `undefined` when it does not parse.
36
+ */
37
+ export declare function parseJsonc(text: string): unknown;
38
+ /** The `paths` the project's tsconfig sees, absolute: the first config along the `extends` chain that defines them. */
39
+ export declare function readTsconfigPaths(tsConfig: string): Map<string, string[]> | undefined;
40
+ /** A target path as the compiler would resolve it to a file: itself, with an extension, or its index. */
41
+ export declare function resolveSourceFile(target: string, exists?: (file: string) => boolean): string | undefined;
42
+ /** Source files under `dir`, capped at `max` (a capped count is still "at least"). */
43
+ export declare function countSourceFiles(dir: string, max: number): number;
44
+ /**
45
+ * The libraries a project can import by alias. A library is an exact alias that is not itself a subpath of a
46
+ * shorter alias; its subpaths are the aliases (and package exports) under it.
47
+ */
48
+ export declare function discoverWorkspaceLibraries(workspaceRoot: string, options?: DiscoverOptions): WorkspaceLibrary[];
49
+ /** The library `file` belongs to — the one with the deepest root. */
50
+ export declare function libraryOf(libraries: WorkspaceLibrary[], file: string): WorkspaceLibrary | undefined;
51
+ /** Is `file` one of `lib`'s modules? */
52
+ export declare function belongsTo(lib: WorkspaceLibrary, file: string): boolean;
@@ -0,0 +1,2 @@
1
+ "use strict";var g=Object.defineProperty;var l=(t,e)=>g(t,"name",{value:e,configurable:!0});var y=Object.defineProperty,u=l((t,e)=>y(t,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.parseJsonc=parseJsonc,exports.readTsconfigPaths=readTsconfigPaths,exports.resolveSourceFile=resolveSourceFile,exports.countSourceFiles=countSourceFiles,exports.discoverWorkspaceLibraries=discoverWorkspaceLibraries,exports.libraryOf=libraryOf,exports.belongsTo=belongsTo;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),SOURCE_FILE=/\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$/,NOT_SOURCE=/(?:\.d\.[cm]?ts|\.(?:spec|test|stories|e2e-spec)\.[cm]?[jt]sx?)$/,SKIP_DIRS=new Set(["node_modules","dist","build","coverage",".git","__tests__","__mocks__","__fixtures__",".nx"]),RESOLVE_EXTENSIONS=["",".ts",".tsx",".mts",".cts",".js",".jsx",".mjs",".cjs"],INDEX_FILES=["index.ts","index.tsx","index.mts","index.js","index.mjs","index.cjs"];function parseJsonc(t){let e="",s=0;const o=t.length;for(;s<o;){const n=t[s];if(n==='"'){let r=s+1;for(;r<o&&t[r]!=='"';)t[r]==="\\"&&r++,r++;e+=t.slice(s,r+1),s=r+1;continue}if(n==="/"&&t[s+1]==="/"){const r=t.indexOf(`
2
+ `,s);s=r===-1?o:r;continue}if(n==="/"&&t[s+1]==="*"){const r=t.indexOf("*/",s+2);s=r===-1?o:r+2;continue}e+=n,s++}try{return JSON.parse(e.replace(/,(\s*[}\]])/g,"$1"))}catch{return}}l(parseJsonc,"parseJsonc"),u(parseJsonc,"parseJsonc");function readJson(t){try{return parseJsonc(fs.readFileSync(t,"utf8"))}catch{return}}l(readJson,"readJson"),u(readJson,"readJson");function readTsconfigPaths(t){let e=t;const s=new Set;for(;e&&!s.has(e)&&s.size<8;){s.add(e);const o=readJson(e);if(!o)return;const n=o.compilerOptions?.paths;if(n&&typeof n=="object"){const f=path.resolve(path.dirname(e),o.compilerOptions?.baseUrl??"."),d=new Map;for(const[m,b]of Object.entries(n))Array.isArray(b)&&d.set(m,b.filter(i=>typeof i=="string").map(i=>path.resolve(f,i)));return d}const r=Array.isArray(o.extends)?o.extends[o.extends.length-1]:o.extends;e=r&&(r.startsWith(".")||path.isAbsolute(r))?path.resolve(path.dirname(e),r.endsWith(".json")?r:`${r}.json`):void 0}}l(readTsconfigPaths,"readTsconfigPaths"),u(readTsconfigPaths,"readTsconfigPaths");function resolveSourceFile(t,e=isFile){for(const o of RESOLVE_EXTENSIONS)if(o&&e(t+o))return t+o;if(e(t))return t;for(const o of INDEX_FILES){const n=path.join(t,o);if(e(n))return n}const s=/\.(c|m)?js$/.exec(t);if(s){const o=t.slice(0,-s[0].length);for(const n of[".ts",".tsx",".mts",".cts"])if(e(o+n))return o+n}}l(resolveSourceFile,"resolveSourceFile"),u(resolveSourceFile,"resolveSourceFile");function asDir(t){return t.endsWith(path.sep)?t:t+path.sep}l(asDir,"asDir"),u(asDir,"asDir");function isFile(t){try{return fs.statSync(t).isFile()}catch{return!1}}l(isFile,"isFile"),u(isFile,"isFile");function libraryRootOf(t,e){let s=path.dirname(t);for(;s.startsWith(e)&&s!==e;){if(isFile(path.join(s,"project.json"))||isFile(path.join(s,"package.json")))return s;const o=path.dirname(s);if(o===s)break;s=o}return path.dirname(t)}l(libraryRootOf,"libraryRootOf"),u(libraryRootOf,"libraryRootOf");function countSourceFiles(t,e){let s=0;const o=[t];for(;o.length&&s<e;){const n=o.pop();let r;try{r=fs.readdirSync(n,{withFileTypes:!0})}catch{continue}for(const f of r)if(f.isDirectory())!SKIP_DIRS.has(f.name)&&!f.name.startsWith(".")&&o.push(path.join(n,f.name));else if(f.isFile()&&SOURCE_FILE.test(f.name)&&!NOT_SOURCE.test(f.name)&&(s++,s>=e))break}return s}l(countSourceFiles,"countSourceFiles"),u(countSourceFiles,"countSourceFiles");function exportTarget(t,e){if(typeof t=="string")return resolveSourceFile(path.resolve(e,t));if(!t||typeof t!="object"||Array.isArray(t))return;const s=t,o=["source","development","require","import","node","default",...Object.keys(s)];for(const n of o){if(n==="types")continue;const r=exportTarget(s[n],e);if(r&&SOURCE_FILE.test(r)&&!/\.d\.[cm]?ts$/.test(r))return r}}l(exportTarget,"exportTarget"),u(exportTarget,"exportTarget");function packageEntries(t){const e=readJson(path.join(t.root,"package.json"))?.exports;if(!(!e||typeof e!="object"||Array.isArray(e)))for(const[s,o]of Object.entries(e)){if(!s.startsWith("./")||s==="./package.json")continue;const n=s.slice(2);if(n.includes("*")){const d=typeof o=="string"?o:void 0;d&&d.includes("*")&&n.endsWith("*")&&t.wildcards.push({prefix:`${t.name}/${n.slice(0,-1)}`,dir:asDir(path.resolve(t.root,d.slice(0,d.indexOf("*"))))});continue}const r=`${t.name}/${n}`;if(t.subpaths.some(d=>d.specifier===r))continue;const f=exportTarget(o,t.root);f&&t.subpaths.push({specifier:r,entry:f})}}l(packageEntries,"packageEntries"),u(packageEntries,"packageEntries");function discoverWorkspaceLibraries(t,e={}){const s=e.tsConfig??path.join(t,"tsconfig.base.json"),o=readTsconfigPaths(s)??(s.endsWith("tsconfig.base.json")?void 0:readTsconfigPaths(path.join(t,"tsconfig.base.json")));if(!o)return[];const n=new Map,r=[];for(const[i,p]of o){if(i.endsWith("/*")){const h=p.find(a=>a.endsWith("*"));h&&r.push({key:i.slice(0,-1),dir:asDir(h.slice(0,-1))});continue}if(!i.includes("*"))for(const h of p){const a=resolveSourceFile(h);if(a){n.set(i,a);break}}}const f=[...n.keys()],d=[];for(const[i,p]of n){if(p.includes(`${path.sep}node_modules${path.sep}`)||f.some(c=>c!==i&&i.startsWith(`${c}/`)))continue;const h=libraryRootOf(p,t),a={name:i,aliases:[],root:h,rootEntry:p,subpaths:f.filter(c=>c.startsWith(`${i}/`)).map(c=>({specifier:c,entry:n.get(c)})),wildcards:r.filter(c=>c.key.startsWith(`${i}/`)).map(c=>({prefix:c.key,dir:c.dir})),files:0};packageEntries(a),d.push(a)}const m=new Map;for(const i of d)m.set(i.rootEntry,[...m.get(i.rootEntry)??[],i]);const b=[];for(const i of m.values()){i.sort((a,c)=>c.subpaths.length+c.wildcards.length-(a.subpaths.length+a.wildcards.length)||a.name.length-c.name.length||a.name.localeCompare(c.name));const[p,...h]=i;for(const a of h)p.aliases.push(a.name),p.subpaths.push(...a.subpaths),p.wildcards.push(...a.wildcards);p.files=countSourceFiles(p.root,e.maxFilesPerLibrary??2e4),b.push(p)}return b}l(discoverWorkspaceLibraries,"discoverWorkspaceLibraries"),u(discoverWorkspaceLibraries,"discoverWorkspaceLibraries");function libraryOf(t,e){let s;for(const o of t)belongsTo(o,e)&&(!s||o.root.length>s.root.length)&&(s=o);return s}l(libraryOf,"libraryOf"),u(libraryOf,"libraryOf");function belongsTo(t,e){return e.startsWith(t.root+path.sep)&&!e.includes(`${path.sep}node_modules${path.sep}`)}l(belongsTo,"belongsTo"),u(belongsTo,"belongsTo");
@@ -0,0 +1,71 @@
1
+ import { type PodMemoryReport, type ReleasedGenerationReport, type RetainedGenerationReport } from './pod-memory';
2
+ /** Who a pod is — stamped into every event's `raw`. */
3
+ export interface PodIdentity {
4
+ /** The Nx project (`LENSMCP_PROJECT`) — what the lens attributes to. */
5
+ project: string;
6
+ /** The cluster service name (`SERVICE_NAME`) — what `lensmcp mem <svc>` takes. */
7
+ service: string;
8
+ /** The devserver's pod id (`Child#<pod>`). */
9
+ pod: number;
10
+ pid: number;
11
+ /** The pod-tree generation (`LENSMCP_POD_GENERATION`, a rolling recycle's). */
12
+ podGeneration: number;
13
+ }
14
+ /** Loose BaseEvent shape — cluster does not depend on `@lensmcp/protocol-types`. */
15
+ export interface PodMemoryBusEvent {
16
+ id: string;
17
+ sessionId: string;
18
+ timestamp: number;
19
+ source: 'memory';
20
+ category: 'memory';
21
+ severity: 'info' | 'warning';
22
+ context: {
23
+ sessionId: string;
24
+ projectName: string;
25
+ };
26
+ fingerprint: string;
27
+ title: string;
28
+ message?: string;
29
+ raw: Record<string, unknown>;
30
+ }
31
+ /** One post-GC reading. */
32
+ export declare function buildPodMemoryEvent(who: PodIdentity, r: PodMemoryReport, id?: () => string): PodMemoryBusEvent;
33
+ /** A closed generation proven still in memory. */
34
+ export declare function buildPodRetainedEvent(who: PodIdentity, r: RetainedGenerationReport, at: number, id?: () => string): PodMemoryBusEvent;
35
+ /** The retraction: a generation reported retained is gone. Same bucket as the verdict it retracts. */
36
+ export declare function buildPodReleasedEvent(who: PodIdentity, r: ReleasedGenerationReport, at: number, id?: () => string): PodMemoryBusEvent;
37
+ /** How a pod process ended, for `pod-memory-exited`. */
38
+ export interface PodExit {
39
+ code: number | null;
40
+ signal: string | null;
41
+ }
42
+ /**
43
+ * The pod process is gone: whatever the lens shows for it should go too. Readings only arrive on swaps and after
44
+ * bursts, so without this a recycled or crashed pod would sit in `memory://pods` beside its successor. The
45
+ * readings' bucket — it is one event per pod lifetime.
46
+ */
47
+ export declare function buildPodExitedEvent(who: PodIdentity, exit: PodExit, at: number, id?: () => string): PodMemoryBusEvent;
48
+ /** The flood guard and rotation every bus writer shares — node-instrumentation's, loaded lazily. */
49
+ interface BusHygiene {
50
+ maybeRotateEventFile(file: string, addedBytes: number): void;
51
+ createEventFloodGuard(opts?: {
52
+ burst?: number;
53
+ perSec?: number;
54
+ }): {
55
+ admit(fp: string): {
56
+ ok: boolean;
57
+ suppressed: number;
58
+ };
59
+ };
60
+ }
61
+ /** Appends pod-memory events to the bus. */
62
+ export interface PodMemoryPublisher {
63
+ /** `false` when the flood guard dropped it, or there is no bus to write to. */
64
+ publish(event: PodMemoryBusEvent): boolean;
65
+ }
66
+ /**
67
+ * A publisher for `eventFile` (the devserver's `LENSMCP_EVENT_FILE`, which the serve executor always sets).
68
+ * Without one there is no bus to write to — a devserver run by hand still logs its lines.
69
+ */
70
+ export declare function createPodMemoryPublisher(eventFile: string | undefined, hygiene?: BusHygiene): PodMemoryPublisher;
71
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";var u=Object.defineProperty;var i=(e,r)=>u(e,"name",{value:r,configurable:!0});var p=Object.defineProperty,n=i((e,r)=>p(e,"name",{value:r,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.buildPodMemoryEvent=buildPodMemoryEvent,exports.buildPodRetainedEvent=buildPodRetainedEvent,exports.buildPodReleasedEvent=buildPodReleasedEvent,exports.buildPodExitedEvent=buildPodExitedEvent,exports.createPodMemoryPublisher=createPodMemoryPublisher;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),pod_memory_1=require("./pod-memory"),MB=1024*1024,mb=n(e=>Math.round(e/MB),"mb");let idCounter=0;function makeId(){return idCounter=(idCounter+1)%65535,`${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2,8)}`}i(makeId,"makeId"),n(makeId,"makeId");function envelope(e,r,o,t,d,s,a){const l="pending";return{id:a(),sessionId:l,timestamp:r,source:"memory",category:"memory",severity:o,context:{sessionId:l,projectName:e.project},fingerprint:t,title:d,raw:{...s,project:e.project,service:e.service,pod:e.pod,pid:e.pid,podGeneration:e.podGeneration}}}i(envelope,"envelope"),n(envelope,"envelope");function buildPodMemoryEvent(e,r,o=makeId){return envelope(e,r.at,"info",`pod-memory:${e.project}`,`${e.service} pod #${e.pod}: ${mb(r.live)} MB live after GC (${r.trigger}, generation ${r.gen})`,{kind:"pod-memory",...r},o)}i(buildPodMemoryEvent,"buildPodMemoryEvent"),n(buildPodMemoryEvent,"buildPodMemoryEvent");function buildPodRetainedEvent(e,r,o,t=makeId){const d=envelope(e,o,"warning",`pod-memory-retained:${e.project}`,`${e.service} pod #${e.pod}: generation ${r.gen} still in memory ${(0,pod_memory_1.ageLabel)(r.ageMs)} after close`,{kind:"pod-memory-retained",...r},t);return d.message=`Its app was closed ${(0,pod_memory_1.ageLabel)(r.ageMs)} ago, ${r.swapsSinceClose} swap(s) and ${r.gcs} full GC(s) since, and ${r.alive.join(", ")} ${r.alive.length===1?"is":"are"} still alive: something still points into it.`,d}i(buildPodRetainedEvent,"buildPodRetainedEvent"),n(buildPodRetainedEvent,"buildPodRetainedEvent");function buildPodReleasedEvent(e,r,o,t=makeId){return envelope(e,o,"info",`pod-memory-retained:${e.project}`,r.reason==="pod-exited"?`${e.service} pod #${e.pod}: generation ${r.gen} went with its pod`:`${e.service} pod #${e.pod}: generation ${r.gen} was collected after all`,{kind:"pod-memory-released",...r},t)}i(buildPodReleasedEvent,"buildPodReleasedEvent"),n(buildPodReleasedEvent,"buildPodReleasedEvent");function buildPodExitedEvent(e,r,o,t=makeId){return envelope(e,o,"info",`pod-memory:${e.project}`,`${e.service} pod #${e.pod} exited`,{kind:"pod-memory-exited",code:r.code,signal:r.signal},t)}i(buildPodExitedEvent,"buildPodExitedEvent"),n(buildPodExitedEvent,"buildPodExitedEvent");function createPodMemoryPublisher(e,r){if(!e)return{publish:n(()=>!1,"publish")};const o=r??(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})(),t=o?.createEventFloodGuard({burst:30,perSec:5});let d=!1;return{publish(s){const a=t?.admit(s.fingerprint)??{ok:!0,suppressed:0};if(!a.ok)return!1;const l=JSON.stringify(a.suppressed>0?{...s,raw:{...s.raw,floodSuppressed:a.suppressed}}:s)+`
2
+ `;try{return d||(fs.mkdirSync(path.dirname(e),{recursive:!0}),d=!0),o?.maybeRotateEventFile(e,l.length),fs.appendFileSync(e,l),!0}catch{return d=!1,!1}}}}i(createPodMemoryPublisher,"createPodMemoryPublisher"),n(createPodMemoryPublisher,"createPodMemoryPublisher");
@@ -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;