@lensmcp/cluster 1.23.13 → 1.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-host.d.ts +1 -0
- package/build-host.js +1 -0
- package/bundle-parent.d.ts +8 -0
- package/bundle-parent.js +1 -0
- package/create-webpack-dev.d.ts +74 -40
- package/create-webpack-dev.js +1 -1
- package/dev-devtool.d.ts +45 -0
- package/dev-devtool.js +1 -0
- package/devserver-notifier.d.ts +57 -0
- package/devserver-notifier.js +1 -0
- package/executors/gateway/body-stall.d.ts +13 -1
- package/executors/gateway/body-stall.js +1 -1
- package/executors/gateway/canonical-path.d.ts +1 -1
- package/executors/gateway/client-ip.d.ts +1 -1
- package/executors/gateway/gateway.impl.js +1 -1
- package/executors/gateway/jwt-claims.d.ts +1 -1
- package/executors/gateway/prod-config.d.ts +1 -1
- package/executors/gateway/prod-runtime/body-stall.d.ts +1 -1
- package/executors/gateway/rate-limit.d.ts +1 -1
- package/executors/gateway/runtime/auth.d.ts +1 -1
- package/executors/gateway/runtime/chooser.d.ts +2 -2
- package/executors/gateway/runtime/control.d.ts +3 -3
- package/executors/gateway/runtime/control.js +2 -2
- package/executors/gateway/runtime/dev-auth.d.ts +1 -1
- package/executors/gateway/runtime/edge.d.ts +3 -3
- package/executors/gateway/runtime/guest-graph.d.ts +113 -0
- package/executors/gateway/runtime/guest-graph.js +2 -0
- package/executors/gateway/runtime/lens-children.d.ts +11 -1
- package/executors/gateway/runtime/lens-children.js +1 -1
- package/executors/gateway/runtime/lifecycle.d.ts +56 -8
- package/executors/gateway/runtime/lifecycle.js +3 -3
- package/executors/gateway/runtime/process-names.d.ts +18 -0
- package/executors/gateway/runtime/process-names.js +1 -0
- package/executors/gateway/runtime/project-graph-store.d.ts +74 -0
- package/executors/gateway/runtime/project-graph-store.js +1 -0
- package/executors/gateway/runtime/proxy.d.ts +2 -2
- package/executors/gateway/runtime/proxy.js +1 -1
- package/executors/gateway/runtime/scope.d.ts +1 -1
- package/executors/gateway/runtime/serve-launch.d.ts +118 -0
- package/executors/gateway/runtime/serve-launch.js +3 -0
- package/executors/gateway/runtime/server.d.ts +3 -3
- package/executors/gateway/runtime/server.js +2 -2
- package/executors/gateway/runtime/shared-build.d.ts +65 -0
- package/executors/gateway/runtime/shared-build.js +4 -0
- package/executors/gateway/runtime/types.d.ts +39 -9
- package/executors/gateway/runtime/types.js +1 -1
- package/executors/gateway/runtime/workspace-log.js +1 -1
- package/executors/gateway/schema.d.ts +3 -0
- package/executors/gateway/schema.json +4 -0
- package/executors/serve/schema.d.ts +2 -0
- package/executors/serve/schema.json +4 -0
- package/executors/serve/serve.impl.d.ts +12 -6
- package/executors/serve/serve.impl.js +1 -1
- package/executors/trust/hostname.d.ts +1 -1
- package/exit-reason.d.ts +1 -1
- package/fork-ts-check-reporter.d.ts +12 -0
- package/fork-ts-check-reporter.js +1 -1
- package/graph-snapshot.d.ts +1 -0
- package/graph-snapshot.js +1 -0
- package/heap-snapshot.d.ts +66 -0
- package/heap-snapshot.js +1 -0
- package/hot-swap-registries.d.ts +1 -1
- package/inspector-secret.d.ts +39 -0
- package/inspector-secret.js +1 -0
- package/main.devserver.d.ts +33 -5
- package/main.devserver.js +11 -9
- package/package.json +4 -4
- package/perf/heavy-barrel-import.d.ts +135 -0
- package/perf/heavy-barrel-import.js +1 -0
- package/perf/perf-config.d.ts +29 -0
- package/perf/perf-config.js +1 -0
- package/perf/perf-rule-events.d.ts +71 -0
- package/perf/perf-rule-events.js +2 -0
- package/perf/perf-rules-analysis.d.ts +28 -0
- package/perf/perf-rules-analysis.js +1 -0
- package/perf/perf-rules-plugin.d.ts +71 -0
- package/perf/perf-rules-plugin.js +1 -0
- package/perf/perf-rules-worker.d.ts +1 -0
- package/perf/perf-rules-worker.js +1 -0
- package/perf/source-scan.d.ts +59 -0
- package/perf/source-scan.js +5 -0
- package/perf/workspace-libraries.d.ts +52 -0
- package/perf/workspace-libraries.js +2 -0
- package/pod-memory-events.d.ts +71 -0
- package/pod-memory-events.js +2 -0
- package/pod-memory.d.ts +264 -0
- package/pod-memory.js +1 -0
- package/process-name.d.ts +45 -0
- package/process-name.js +1 -1
- package/serve-runner.d.ts +2 -0
- package/serve-runner.js +1 -0
- package/shared-build/attach.d.ts +56 -0
- package/shared-build/attach.js +3 -0
- package/shared-build/host.d.ts +52 -0
- package/shared-build/host.js +4 -0
- package/shared-build/protocol.d.ts +118 -0
- package/shared-build/protocol.js +1 -0
- package/shared-build/shared-compilation.d.ts +91 -0
- package/shared-build/shared-compilation.js +3 -0
- package/shared-build/signature.d.ts +52 -0
- package/shared-build/signature.js +1 -0
- package/stdio-guard.d.ts +1 -1
- package/swap-recycle.d.ts +21 -0
- package/swap-recycle.js +1 -1
- package/tsgo-check-plugin.d.ts +1 -1
- package/workspace-paths.d.ts +76 -0
- package/workspace-paths.js +1 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { type WorkspaceLibrary } from './workspace-libraries';
|
|
2
|
+
export declare const HEAVY_BARREL_RULE = "bundle/heavy-barrel-import";
|
|
3
|
+
/** One module of a compilation, as the rule needs it. */
|
|
4
|
+
export interface CompiledModule {
|
|
5
|
+
/** Absolute path of its file (no loaders, no query). */
|
|
6
|
+
resource: string;
|
|
7
|
+
/** Bytes webpack attributes to it — its source after loaders. */
|
|
8
|
+
size: number;
|
|
9
|
+
/** Files it imports (absolute; externals left out). */
|
|
10
|
+
dependencies: string[];
|
|
11
|
+
}
|
|
12
|
+
/** How long the compilation that produced the module set took, for the compile-time estimate. */
|
|
13
|
+
export interface CompileFacts {
|
|
14
|
+
durationMs: number;
|
|
15
|
+
/** Modules it actually built (a cached module costs nothing). */
|
|
16
|
+
builtModules: number;
|
|
17
|
+
}
|
|
18
|
+
/** Everything one project's code compiled to. */
|
|
19
|
+
export interface ProjectCompilation {
|
|
20
|
+
project: string;
|
|
21
|
+
modules: CompiledModule[];
|
|
22
|
+
compile?: CompileFacts;
|
|
23
|
+
}
|
|
24
|
+
/** Source access — injected, so the rule stays a pure function in tests and bounded in the plugin. */
|
|
25
|
+
export interface SourceReader {
|
|
26
|
+
read(file: string): string | undefined;
|
|
27
|
+
exists(file: string): boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface HeavyBarrelThresholds {
|
|
30
|
+
/** The share of the library's files compiled into the project at or above which it may fire. */
|
|
31
|
+
minShare: number;
|
|
32
|
+
/** Modules compiled from the library at or above which it may fire … */
|
|
33
|
+
minModules: number;
|
|
34
|
+
/** … or their bytes. */
|
|
35
|
+
minBytes: number;
|
|
36
|
+
/** It fires only when what the importers need is at most this share of what is compiled ("few symbols"). */
|
|
37
|
+
maxNeededShare: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Calibrated on FoodGuard's seven real dev bundles: five services needed 6–39% of the contracts modules they
|
|
41
|
+
* compiled (fires), registry 50.2% — 33 symbols, 445 modules avoidable (fires at 0.6, a miss at 0.5) — and engine 87%
|
|
42
|
+
* with 1,727 symbols, a service that genuinely uses most of the library (quiet).
|
|
43
|
+
*/
|
|
44
|
+
export declare const DEFAULT_HEAVY_BARREL_THRESHOLDS: HeavyBarrelThresholds;
|
|
45
|
+
/** One file that imports the root, and what it takes. */
|
|
46
|
+
export interface RootImporter {
|
|
47
|
+
/** Workspace-relative. */
|
|
48
|
+
file: string;
|
|
49
|
+
/** Value symbols it takes from the root (capped). */
|
|
50
|
+
symbols: string[];
|
|
51
|
+
/** Type-only names it imports — erased, no cost. */
|
|
52
|
+
typeOnly: number;
|
|
53
|
+
/** Its use could not be read: it counts as needing the whole library. */
|
|
54
|
+
wholeModule: boolean;
|
|
55
|
+
/** The imports that would replace its root import. */
|
|
56
|
+
rewrite: string[];
|
|
57
|
+
}
|
|
58
|
+
/** An entry that would satisfy some symbols — existing, or one the library should add. */
|
|
59
|
+
export interface SubpathSuggestion {
|
|
60
|
+
specifier: string;
|
|
61
|
+
/** Workspace-relative file the specifier should resolve to. */
|
|
62
|
+
entry: string;
|
|
63
|
+
symbols: string[];
|
|
64
|
+
/** False: the alias does not exist yet — add it (tsconfig `paths` + package `exports`). */
|
|
65
|
+
exists: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** The rule's measurement of one (project, library) pair reached through its root. */
|
|
68
|
+
export interface BarrelMeasurement {
|
|
69
|
+
project: string;
|
|
70
|
+
library: string;
|
|
71
|
+
/** Workspace-relative. */
|
|
72
|
+
libraryRoot: string;
|
|
73
|
+
rootEntry: string;
|
|
74
|
+
libraryFiles: number;
|
|
75
|
+
compiledModules: number;
|
|
76
|
+
compiledBytes: number;
|
|
77
|
+
/** compiled ÷ library files. */
|
|
78
|
+
share: number;
|
|
79
|
+
rootImporterCount: number;
|
|
80
|
+
/** The biggest takers first (capped). */
|
|
81
|
+
rootImporters: RootImporter[];
|
|
82
|
+
/** Files INSIDE the library that import its own root: they keep the barrel compiled after every importer moves. */
|
|
83
|
+
internalRootImporters: string[];
|
|
84
|
+
/** Distinct value symbols the importers take from the root. */
|
|
85
|
+
symbols: number;
|
|
86
|
+
/** Symbols the scan could not follow to a file. */
|
|
87
|
+
unresolvedSymbols: string[];
|
|
88
|
+
/** Symbols the barrel defines itself: importing one runs the whole barrel — they belong in a module of their own. */
|
|
89
|
+
barrelDefines: string[];
|
|
90
|
+
neededModules: number;
|
|
91
|
+
neededBytes: number;
|
|
92
|
+
avoidableModules: number;
|
|
93
|
+
avoidableBytes: number;
|
|
94
|
+
subpaths: SubpathSuggestion[];
|
|
95
|
+
fires: boolean;
|
|
96
|
+
/** Why it does not fire: the first condition it missed. */
|
|
97
|
+
quiet?: string;
|
|
98
|
+
}
|
|
99
|
+
export interface BarrelAnalysisInput {
|
|
100
|
+
workspaceRoot: string;
|
|
101
|
+
libraries: WorkspaceLibrary[];
|
|
102
|
+
projects: ProjectCompilation[];
|
|
103
|
+
thresholds: HeavyBarrelThresholds;
|
|
104
|
+
source: SourceReader;
|
|
105
|
+
/** True once the analysis is out of time: it stops and says it is incomplete. */
|
|
106
|
+
expired?: () => boolean;
|
|
107
|
+
}
|
|
108
|
+
export interface BarrelAnalysis {
|
|
109
|
+
/** False when it ran out of time — nothing may be concluded (least of all a retraction). */
|
|
110
|
+
complete: boolean;
|
|
111
|
+
measurements: BarrelMeasurement[];
|
|
112
|
+
}
|
|
113
|
+
/** Measure every (project, library) pair reached through a root barrel. */
|
|
114
|
+
export declare function analyzeBarrelImports(input: BarrelAnalysisInput): BarrelAnalysis;
|
|
115
|
+
/** Executor memory per compiled module, MB — the fit to FoodGuard's seven watchers (issues/open/pod-memory-05). */
|
|
116
|
+
export declare const EXECUTOR_MB_PER_MODULE = 0.374;
|
|
117
|
+
/** A finding's text, evidence and cost — the payload a finding event carries. */
|
|
118
|
+
export interface HeavyBarrelFindingText {
|
|
119
|
+
subject: string;
|
|
120
|
+
severity: 'warning' | 'error';
|
|
121
|
+
title: string;
|
|
122
|
+
summary: string;
|
|
123
|
+
evidence: Record<string, unknown>;
|
|
124
|
+
cost: {
|
|
125
|
+
modules: number;
|
|
126
|
+
bytes: number;
|
|
127
|
+
compileMs?: number;
|
|
128
|
+
memoryMb: number;
|
|
129
|
+
basis: string;
|
|
130
|
+
};
|
|
131
|
+
fix: string[];
|
|
132
|
+
verify: string;
|
|
133
|
+
}
|
|
134
|
+
/** What an agent reads: the measurement, as a problem with a fix and a way to prove it. */
|
|
135
|
+
export declare function describeHeavyBarrel(m: BarrelMeasurement, compile?: CompileFacts): HeavyBarrelFindingText;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var H=Object.defineProperty;var M=(e,o)=>H(e,"name",{value:o,configurable:!0});var C=Object.defineProperty,y=M((e,o)=>C(e,"name",{value:o,configurable:!0}),"y");Object.defineProperty(exports,"__esModule",{value:!0}),exports.EXECUTOR_MB_PER_MODULE=exports.DEFAULT_HEAVY_BARREL_THRESHOLDS=exports.HEAVY_BARREL_RULE=void 0,exports.analyzeBarrelImports=analyzeBarrelImports,exports.describeHeavyBarrel=describeHeavyBarrel;const tslib_1=require("tslib"),path=tslib_1.__importStar(require("node:path")),workspace_libraries_1=require("./workspace-libraries"),source_scan_1=require("./source-scan");exports.HEAVY_BARREL_RULE="bundle/heavy-barrel-import",exports.DEFAULT_HEAVY_BARREL_THRESHOLDS={minShare:.5,minModules:100,minBytes:1024*1024,maxNeededShare:.6};const MAX_IMPORTERS_LISTED=8,MAX_SYMBOLS_LISTED=12,MAX_SUBPATHS_LISTED=12,MAX_INTERNAL_LISTED=6,MAX_EXPORT_DEPTH=24,MAX_UNRESOLVED_SHARE=.1;class AliasResolver{static{M(this,"AliasResolver")}static{y(this,"AliasResolver")}constructor(o,r){this.exists=r,this.exact=new Map,this.wildcards=[];for(const s of o){this.exact.set(s.name,s.rootEntry);for(const i of s.aliases)this.exact.set(i,s.rootEntry);for(const i of s.subpaths)this.exact.set(i.specifier,i.entry);for(const i of s.wildcards)this.wildcards.push(i)}this.wildcards.sort((s,i)=>i.prefix.length-s.prefix.length)}resolve(o,r){if(r.startsWith("."))return(0,workspace_libraries_1.resolveSourceFile)(path.resolve(path.dirname(o),r),this.exists);const s=this.exact.get(r);if(s)return s;const i=this.wildcards.find(u=>r.startsWith(u.prefix));return i?(0,workspace_libraries_1.resolveSourceFile)(path.join(i.dir,r.slice(i.prefix.length)),this.exists):void 0}}class ExportReader{static{M(this,"ExportReader")}static{y(this,"ExportReader")}constructor(o,r){this.source=o,this.aliases=r,this.cache=new Map,this.resolved=new Map}index(o){let r=this.cache.get(o);if(r===void 0){const s=this.source.read(o);r=s===void 0?null:(0,source_scan_1.scanExports)(s),this.cache.set(o,r)}return r}resolve(o,r){const s=`${o}\0${r}`;let i=this.resolved.get(s);return i===void 0&&(i=this.follow(o,r,0,new Set)??null,this.resolved.set(s,i)),i??void 0}follow(o,r,s,i){if(s>MAX_EXPORT_DEPTH)return;const u=`${o}\0${r}`;if(i.has(u))return;i.add(u);const a=this.index(o);if(!a)return;if(a.values.has(r))return{file:o,typeOnly:!1};if(a.types.has(r))return{file:o,typeOnly:!0};const l=a.namespaces.get(r);if(l!==void 0){const p=this.aliases.resolve(o,l);return p?{file:p,typeOnly:!1}:void 0}const d=a.reexports.get(r);if(d){const p=this.aliases.resolve(o,d.specifier);if(!p)return;const c=this.follow(p,d.name,s+1,i);return c?{...c,typeOnly:c.typeOnly||d.typeOnly}:void 0}if(r!=="default")for(const p of a.stars){const c=this.aliases.resolve(o,p);if(!c)continue;const h=this.follow(c,r,s+1,i);if(h)return h}}}const rel=y((e,o)=>path.relative(e,o)||o,"rel");function readRootUse(e,o,r,s){const i=y(a=>l=>s.resolve(a,l)===e.rootEntry,"toBarrel"),u=[];for(const a of o){const l=/\.(?:[cm]?[jt]sx?)$/.test(a)?r.source.read(a):void 0;if(l===void 0){u.push({file:a,values:[],typeOnly:0,wholeModule:!0});continue}const d=(0,source_scan_1.scanImports)(l,i(a));if(d.length===0){u.push({file:a,values:[],typeOnly:0,wholeModule:!0});continue}const p=new Set;let c=0,h=!1;for(const x of d){h||=x.wholeModule;for(const f of x.names)f.typeOnly?c++:p.add(f.imported)}u.push({file:a,values:[...p].sort(),typeOnly:c,wholeModule:h})}return{importers:u}}M(readRootUse,"readRootUse"),y(readRootUse,"readRootUse");function closureWithin(e,o,r,s,i){const u=new Set,a=[...o].filter(l=>l!==s&&r.has(l)&&i(l)===e);for(;a.length;){const l=a.pop();if(!u.has(l)){u.add(l);for(const d of r.get(l)?.dependencies??[])d!==s&&!u.has(d)&&i(d)===e&&r.has(d)&&a.push(d)}}return u}M(closureWithin,"closureWithin"),y(closureWithin,"closureWithin");function subpathFor(e,o,r,s,i,u,a=0){if(r===e.rootEntry)return;const l=u(r);if(l!==e)return l&&a<2?subpathFor(l,o,r,s,i,u,a+1):void 0;const d=e.subpaths.filter(f=>r.startsWith(path.dirname(f.entry)+path.sep)||r===f.entry).filter(f=>s.resolve(f.entry,o)?.file===r).sort((f,$)=>$.entry.length-f.entry.length);if(d[0])return{specifier:d[0].specifier,entry:d[0].entry,exists:!0};const p=path.dirname(e.rootEntry);for(const f of e.wildcards){if(!r.startsWith(f.dir))continue;const $=r.slice(f.dir.length).split(path.sep).join("/").replace(/\.(?:[cm]?[jt]sx?)$/,""),w=$.split("/")[0],v=(0,workspace_libraries_1.resolveSourceFile)(path.join(f.dir,w),i.exists);return v&&v!==r&&s.resolve(v,o)?.file===r?{specifier:`${f.prefix}${w}`,entry:v,exists:!0}:{specifier:`${f.prefix}${$.replace(/\/index$/,"")}`,entry:r,exists:!0}}const c=path.relative(p,r).split(path.sep),h=c.length>1?c[0]:c[0].replace(/\.(?:[cm]?[jt]sx?)$/,""),x=c.length>1?(0,workspace_libraries_1.resolveSourceFile)(path.join(p,h),i.exists)??path.join(p,h,"index.ts"):r;return{specifier:`${e.name}/${h}`,entry:x,exists:!1}}M(subpathFor,"subpathFor"),y(subpathFor,"subpathFor");function measure(e,o,r,s,i,u,a){if(!r.has(o.rootEntry))return;const l=y(t=>a(t)===o,"inLib"),d=e.modules.filter(t=>l(t.resource)),p=e.modules.filter(t=>!l(t.resource)&&t.dependencies.includes(o.rootEntry)).map(t=>t.resource).sort();if(p.length===0)return;const c=s.workspaceRoot,h=s.thresholds,x=d.filter(t=>t.resource!==o.rootEntry&&t.dependencies.includes(o.rootEntry)).map(t=>t.resource),f=new Set;for(const t of e.modules)if(!l(t.resource))for(const n of t.dependencies)n!==o.rootEntry&&l(n)&&f.add(n);const $=readRootUse(o,p,s,i),w=new Set(f),v=new Set,S=new Map,R=new Set,_=new Set;for(const t of $.importers)for(const n of t.values){if(_.add(n),S.has(n)||v.has(n)||R.has(n))continue;const m=u.resolve(o.rootEntry,n);m?m.file===o.rootEntry?R.add(n):m.typeOnly||(S.set(n,m.file),w.add(m.file)):v.add(n)}if(R.size>0)for(const t of u.index(o.rootEntry)?.valueImports??[]){const n=i.resolve(o.rootEntry,t);n&&w.add(n)}const L=$.importers.some(t=>t.wholeModule),B=L?new Set(d.map(t=>t.resource)):closureWithin(o,w,r,o.rootEntry,a),T=y(t=>{let n=0;for(const m of t)n+=r.get(m)?.size??0;return n},"sizeOf"),U=d.reduce((t,n)=>t+n.size,0),z=T(B),A=d.filter(t=>!B.has(t.resource)),I=o.files>0?Math.min(1,d.length/o.files):1,O=new Map,D=new Map,k=y((t,n)=>{let m=D.get(t);return m===void 0&&(m=subpathFor(o,t,n,u,s.source,a)??null,D.set(t,m)),m??void 0},"choose"),F=$.importers.map(t=>{const n=new Map;for(const m of t.values){const g=S.get(m);if(!g)continue;const E=k(m,g);if(!E)continue;const j=O.get(E.specifier)??{specifier:E.specifier,entry:rel(c,E.entry),symbols:[],exists:E.exists};j.symbols.includes(m)||j.symbols.push(m),O.set(E.specifier,j),n.set(E.specifier,[...n.get(E.specifier)??[],m])}return{file:rel(c,t.file),symbols:t.values.slice(0,MAX_SYMBOLS_LISTED),typeOnly:t.typeOnly,wholeModule:t.wholeModule,rewrite:[...n].sort((m,g)=>m[0].localeCompare(g[0])).map(([m,g])=>`import { ${g.join(", ")} } from '${m}';`)}}).sort((t,n)=>Number(n.wholeModule)-Number(t.wholeModule)||n.symbols.length-t.symbols.length||t.file.localeCompare(n.file)),b={project:e.project,library:o.name,libraryRoot:rel(c,o.root),rootEntry:rel(c,o.rootEntry),libraryFiles:o.files,compiledModules:d.length,compiledBytes:U,share:I,rootImporterCount:p.length,rootImporters:F.slice(0,MAX_IMPORTERS_LISTED),internalRootImporters:x.slice(0,MAX_INTERNAL_LISTED).map(t=>rel(c,t)),symbols:_.size,unresolvedSymbols:[...v].sort().slice(0,MAX_SYMBOLS_LISTED),barrelDefines:[...R].sort().slice(0,MAX_SYMBOLS_LISTED),neededModules:B.size,neededBytes:z,avoidableModules:A.length,avoidableBytes:A.reduce((t,n)=>t+n.size,0),subpaths:[...O.values()].map(t=>({...t,symbols:t.symbols.sort().slice(0,MAX_SYMBOLS_LISTED)})).sort((t,n)=>Number(t.exists)-Number(n.exists)||n.symbols.length-t.symbols.length).slice(0,MAX_SUBPATHS_LISTED),fires:!1};return I<h.minShare?b.quiet=`compiles ${pct(I)} of the library (below ${pct(h.minShare)})`:b.compiledModules<h.minModules&&b.compiledBytes<h.minBytes?b.quiet=`compiles ${b.compiledModules} modules / ${mb(b.compiledBytes)} (below ${h.minModules} modules and ${mb(h.minBytes)})`:L?b.quiet="an importer uses the whole module (a namespace passed on, require(), import(), export *) \u2014 it may need all of it":_.size>0&&v.size/_.size>MAX_UNRESOLVED_SHARE?b.quiet=`could not follow ${v.size} of ${_.size} symbols to their files`:b.neededModules>h.maxNeededShare*b.compiledModules?b.quiet=`its importers need ${b.neededModules} of the ${b.compiledModules} compiled modules (above ${pct(h.maxNeededShare)})`:b.fires=!0,b}M(measure,"measure"),y(measure,"measure");const pct=y(e=>`${Math.round(e*100)}%`,"pct"),mb=y(e=>`${(e/(1024*1024)).toFixed(1)} MB`,"mb");function analyzeBarrelImports(e){const o=y(l=>e.source.exists(l),"exists"),r=new AliasResolver(e.libraries,o),s=new ExportReader(e.source,r),i=new Map,u=y(l=>(i.has(l)||i.set(l,(0,workspace_libraries_1.libraryOf)(e.libraries,l)),i.get(l)),"owner"),a=[];for(const l of e.projects){const d=new Map(l.modules.map(p=>[p.resource,p]));for(const p of e.libraries){if(e.expired?.())return{complete:!1,measurements:a};const c=measure(l,p,d,e,r,s,u);c&&a.push(c)}}return{complete:!e.expired?.(),measurements:a}}M(analyzeBarrelImports,"analyzeBarrelImports"),y(analyzeBarrelImports,"analyzeBarrelImports"),exports.EXECUTOR_MB_PER_MODULE=.374;function describeHeavyBarrel(e,o){const r=o&&o.builtModules>=50?o.durationMs/o.builtModules:void 0,s=r!==void 0?Math.round(r*e.avoidableModules):void 0,i=Math.round(exports.EXECUTOR_MB_PER_MODULE*e.avoidableModules),u=e.rootImporters.slice(0,3),a=e.subpaths.filter(c=>!c.exists),l=u.map(c=>`${c.file}: replace its \`${e.library}\` import with ${c.rewrite.length?c.rewrite.join(" "):"imports from the subpaths that export what it uses"}`),d=[`Import ${e.library} by subpath, never by its root barrel (${e.rootEntry}). ${e.rootImporterCount} file(s) of ${e.project}'s build import the root; ${l.length?`start with \u2014 ${l.join(" | ")}`:"see evidence.rootImporters"}${e.rootImporterCount>u.length?` (+${e.rootImporterCount-u.length} more: evidence.rootImporters)`:""}.`];a.length&&d.push(`Add the missing subpath entr${a.length===1?"y":"ies"} to the library: ${a.slice(0,4).map(c=>`"${c.specifier}" \u2192 ${c.entry}`).join(", ")} \u2014 in tsconfig.base.json \`compilerOptions.paths\` and, if the library has a package.json, its \`exports\` ("./<domain>"). Add exact keys (plus "<domain>/*" for single files), never one library-wide "${e.library}/*": a bundler's resolver can rank it above the exact keys and re-route them at runtime while tsc and jest stay green.`),e.barrelDefines.length&&d.push(`${e.rootEntry} defines ${e.barrelDefines.slice(0,4).join(", ")} itself, so importing ${e.barrelDefines.length===1?"it":"them"} runs the whole barrel: move ${e.barrelDefines.length===1?"it":"them"} into a module of ${e.barrelDefines.length===1?"its":"their"} own and import that.`),e.internalRootImporters.length&&d.push(`${e.internalRootImporters.length} file(s) inside ${e.library} import its own root barrel (${e.internalRootImporters.slice(0,3).join(", ")}): make them import the module they need, or every subpath still pulls the whole library in.`),d.push(`Stop the root import from coming back: in ${e.project}'s ESLint config add \`'no-restricted-imports': ['error', { paths: [{ name: '${e.library}', message: 'Import from a domain subpath (${e.library}/<domain>), not the root barrel.' }] }]\`.`);const p=`Save a file of ${e.project} (or restart it) so its dev build recompiles: it should compile about ${e.neededModules} modules of ${e.library} instead of ${e.compiledModules}, and LensMCP retracts this finding \u2014 \`observe_current_status\` no longer lists bundle/heavy-barrel-import for ${e.project}. Then check its pods' live heap (\`memory.pods\`).`;return{subject:e.library,severity:"warning",title:`${e.project} compiles ${e.compiledModules} of the ${e.libraryFiles} files of ${e.library} through its root barrel; its imports need ${e.neededModules}`,summary:`${e.rootImporterCount} file(s) of ${e.project} import ${e.library} through its root entry and take ${e.symbols} symbol(s); those need ${e.neededModules} of the library's modules, but the barrel makes the dev build compile ${e.compiledModules} (${mb(e.compiledBytes)}) \u2014 ${e.avoidableModules} modules (${mb(e.avoidableBytes)}) nothing uses, compiled in every service that does the same and loaded into every pod.`,evidence:{library:e.library,libraryRoot:e.libraryRoot,rootEntry:e.rootEntry,libraryFiles:e.libraryFiles,compiledModules:e.compiledModules,compiledBytes:e.compiledBytes,share:Math.round(e.share*1e3)/1e3,neededModules:e.neededModules,neededBytes:e.neededBytes,avoidableModules:e.avoidableModules,avoidableBytes:e.avoidableBytes,symbols:e.symbols,unresolvedSymbols:e.unresolvedSymbols,barrelDefines:e.barrelDefines,rootImporterCount:e.rootImporterCount,rootImporters:e.rootImporters,internalRootImporters:e.internalRootImporters,subpaths:e.subpaths},cost:{modules:e.avoidableModules,bytes:e.avoidableBytes,...s!==void 0?{compileMs:s}:{},memoryMb:i,basis:`modules/bytes measured in this compilation; memoryMb = ${exports.EXECUTOR_MB_PER_MODULE} MB of webpack-executor heap per compiled module (fitted on FoodGuard's watchers)${s!==void 0?"; compileMs = the last full build's time per built module \xD7 the avoidable modules":""}. Each pod also loads them, at runtime cost the bundle cannot measure.`},fix:d,verify:p}}M(describeHeavyBarrel,"describeHeavyBarrel"),y(describeHeavyBarrel,"describeHeavyBarrel");
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type HeavyBarrelThresholds } from './heavy-barrel-import';
|
|
2
|
+
/** `LENSMCP_PERF_RULES=0` (or `off`/`false`/`no`) turns every performance rule off. */
|
|
3
|
+
export declare const PERF_RULES_ENV = "LENSMCP_PERF_RULES";
|
|
4
|
+
/** How long one analysis may run before it gives up (and concludes nothing). */
|
|
5
|
+
export declare const PERF_RULES_BUDGET_ENV = "LENSMCP_PERF_RULES_BUDGET_MS";
|
|
6
|
+
export declare const DEFAULT_PERF_RULES_BUDGET_MS = 5000;
|
|
7
|
+
/** The env override of each heavy-barrel threshold. */
|
|
8
|
+
export declare const HEAVY_BARREL_ENV: {
|
|
9
|
+
readonly minShare: "LENSMCP_BARREL_MIN_SHARE";
|
|
10
|
+
readonly minModules: "LENSMCP_BARREL_MIN_MODULES";
|
|
11
|
+
readonly minKb: "LENSMCP_BARREL_MIN_KB";
|
|
12
|
+
readonly maxNeededShare: "LENSMCP_BARREL_MAX_NEEDED_SHARE";
|
|
13
|
+
};
|
|
14
|
+
export interface BundleRuleSettings {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
thresholds: HeavyBarrelThresholds;
|
|
17
|
+
/** Time budget of one analysis, ms. */
|
|
18
|
+
budgetMs: number;
|
|
19
|
+
}
|
|
20
|
+
interface PerfRulesConfigJson {
|
|
21
|
+
enabled?: unknown;
|
|
22
|
+
budgetMs?: unknown;
|
|
23
|
+
[rule: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
/** `.lensmcp/config.json` `perfRules`, or nothing. */
|
|
26
|
+
export declare function readPerfRulesConfig(workspaceRoot: string): PerfRulesConfigJson | undefined;
|
|
27
|
+
/** The build-side rules' settings for this workspace. */
|
|
28
|
+
export declare function resolveBundleRuleSettings(workspaceRoot: string, env?: NodeJS.ProcessEnv): BundleRuleSettings;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var p=Object.defineProperty;var o=(r,e)=>p(r,"name",{value:e,configurable:!0});var u=Object.defineProperty,n=o((r,e)=>u(r,"name",{value:e,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.HEAVY_BARREL_ENV=exports.DEFAULT_PERF_RULES_BUDGET_MS=exports.PERF_RULES_BUDGET_ENV=exports.PERF_RULES_ENV=void 0,exports.readPerfRulesConfig=readPerfRulesConfig,exports.resolveBundleRuleSettings=resolveBundleRuleSettings;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path")),heavy_barrel_import_1=require("./heavy-barrel-import");exports.PERF_RULES_ENV="LENSMCP_PERF_RULES",exports.PERF_RULES_BUDGET_ENV="LENSMCP_PERF_RULES_BUDGET_MS",exports.DEFAULT_PERF_RULES_BUDGET_MS=5e3,exports.HEAVY_BARREL_ENV={minShare:"LENSMCP_BARREL_MIN_SHARE",minModules:"LENSMCP_BARREL_MIN_MODULES",minKb:"LENSMCP_BARREL_MIN_KB",maxNeededShare:"LENSMCP_BARREL_MAX_NEEDED_SHARE"};const OFF=/^(0|off|false|no)$/i;function bounded(r,e,t){const E=typeof r=="string"&&r.trim()!==""?Number(r):r;return typeof E=="number"&&Number.isFinite(E)&&E>=e&&E<=t?E:void 0}o(bounded,"bounded"),n(bounded,"bounded");function readPerfRulesConfig(r){try{const e=JSON.parse(fs.readFileSync(path.join(r,".lensmcp","config.json"),"utf8"))?.perfRules;return e&&typeof e=="object"&&!Array.isArray(e)?e:void 0}catch{return}}o(readPerfRulesConfig,"readPerfRulesConfig"),n(readPerfRulesConfig,"readPerfRulesConfig");function resolveBundleRuleSettings(r,e=process.env){const t=readPerfRulesConfig(r),E=t?.[heavy_barrel_import_1.HEAVY_BARREL_RULE]&&typeof t[heavy_barrel_import_1.HEAVY_BARREL_RULE]=="object"?t[heavy_barrel_import_1.HEAVY_BARREL_RULE]:{},S=!OFF.test((e[exports.PERF_RULES_ENV]??"").trim())&&t?.enabled!==!1&&E.enabled!==!1,_=n((a,d,s,i,L)=>bounded(e[a],s,i)??bounded(d,s,i)??L,"pick"),R=heavy_barrel_import_1.DEFAULT_HEAVY_BARREL_THRESHOLDS;return{enabled:S,thresholds:{minShare:_(exports.HEAVY_BARREL_ENV.minShare,E.minShare,0,1,R.minShare),minModules:_(exports.HEAVY_BARREL_ENV.minModules,E.minModules,0,1e6,R.minModules),minBytes:Math.round(_(exports.HEAVY_BARREL_ENV.minKb,E.minKb,0,1e7,R.minBytes/1024)*1024),maxNeededShare:_(exports.HEAVY_BARREL_ENV.maxNeededShare,E.maxNeededShare,0,1,R.maxNeededShare)},budgetMs:_(exports.PERF_RULES_BUDGET_ENV,t?.budgetMs,100,12e4,exports.DEFAULT_PERF_RULES_BUDGET_MS)}}o(resolveBundleRuleSettings,"resolveBundleRuleSettings"),n(resolveBundleRuleSettings,"resolveBundleRuleSettings");
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** The documentation page of every rule — MIRROR of protocol-types' `PERF_RULES_DOC`. */
|
|
2
|
+
export declare const PERF_RULES_DOC = "https://github.com/kiwiapps-ltd/lensmcp/blob/main/docs/lensmcp/concepts/memory-and-build-cost.mdx";
|
|
3
|
+
/** MIRROR of protocol-types' `PerfRuleCost`. */
|
|
4
|
+
export interface PerfRuleCostWire {
|
|
5
|
+
modules?: number;
|
|
6
|
+
bytes?: number;
|
|
7
|
+
compileMs?: number;
|
|
8
|
+
memoryMb?: number;
|
|
9
|
+
basis?: string;
|
|
10
|
+
}
|
|
11
|
+
/** What a rule found — MIRROR of protocol-types' `PerfRuleFindingRaw`, without the kind and identity. */
|
|
12
|
+
export interface PerfFindingWire {
|
|
13
|
+
rule: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
severity: 'warning' | 'error';
|
|
16
|
+
title: string;
|
|
17
|
+
summary: string;
|
|
18
|
+
evidence: Record<string, unknown>;
|
|
19
|
+
cost: PerfRuleCostWire;
|
|
20
|
+
fix: string[];
|
|
21
|
+
verify: string;
|
|
22
|
+
docs: string;
|
|
23
|
+
}
|
|
24
|
+
/** Loose BaseEvent shape — cluster does not depend on `@lensmcp/protocol-types`. */
|
|
25
|
+
export interface PerfRuleBusEvent {
|
|
26
|
+
id: string;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
source: 'perf';
|
|
30
|
+
category: 'performance';
|
|
31
|
+
severity: 'info' | 'warning' | 'error';
|
|
32
|
+
context: {
|
|
33
|
+
sessionId: string;
|
|
34
|
+
projectName: string;
|
|
35
|
+
};
|
|
36
|
+
fingerprint: string;
|
|
37
|
+
title: string;
|
|
38
|
+
message?: string;
|
|
39
|
+
raw: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** MIRROR of protocol-types' `perfRuleFingerprint`. */
|
|
42
|
+
export declare function perfRuleFingerprint(rule: string, project: string, subject: string): string;
|
|
43
|
+
/** MIRROR of protocol-types' `perfRuleScanFingerprint`. */
|
|
44
|
+
export declare function perfRuleScanFingerprint(rule: string, project: string): string;
|
|
45
|
+
/** A rule fired. */
|
|
46
|
+
export declare function buildPerfFindingEvent(project: string, f: PerfFindingWire, at: number, id?: () => string): PerfRuleBusEvent;
|
|
47
|
+
/** The condition cleared. Same bucket as the finding it retracts. */
|
|
48
|
+
export declare function buildPerfRetractedEvent(rule: string, project: string, subject: string, reason: string, at: number, id?: () => string): PerfRuleBusEvent;
|
|
49
|
+
/** A completed scan: the subjects open now — anything else of this rule and project is gone. `elapsedMs`: what the
|
|
50
|
+
* analysis behind it took (all the projects it covered), so its cost is visible on the bus. */
|
|
51
|
+
export declare function buildPerfScanEvent(rule: string, project: string, open: string[], at: number, id?: () => string, elapsedMs?: number): PerfRuleBusEvent;
|
|
52
|
+
/** The flood guard and rotation every bus writer shares — node-instrumentation's, loaded lazily. */
|
|
53
|
+
export interface PerfBusHygiene {
|
|
54
|
+
maybeRotateEventFile(file: string, addedBytes: number): void;
|
|
55
|
+
createEventFloodGuard(opts?: {
|
|
56
|
+
burst?: number;
|
|
57
|
+
perSec?: number;
|
|
58
|
+
}): {
|
|
59
|
+
admit(fp: string): {
|
|
60
|
+
ok: boolean;
|
|
61
|
+
suppressed: number;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Appends perf-rule events to the bus. */
|
|
66
|
+
export interface PerfRulePublisher {
|
|
67
|
+
/** `false` when the flood guard dropped it, or there is no bus to write to. */
|
|
68
|
+
publish(event: PerfRuleBusEvent): boolean;
|
|
69
|
+
}
|
|
70
|
+
/** A publisher for `eventFile` (`LENSMCP_EVENT_FILE`, else `<workspace>/.lensmcp/events.jsonl`). */
|
|
71
|
+
export declare function createPerfRulePublisher(eventFile: string | undefined, hygiene?: PerfBusHygiene): PerfRulePublisher;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var d=Object.defineProperty;var o=(r,e)=>d(r,"name",{value:e,configurable:!0});var c=Object.defineProperty,u=o((r,e)=>c(r,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PERF_RULES_DOC=void 0,exports.perfRuleFingerprint=perfRuleFingerprint,exports.perfRuleScanFingerprint=perfRuleScanFingerprint,exports.buildPerfFindingEvent=buildPerfFindingEvent,exports.buildPerfRetractedEvent=buildPerfRetractedEvent,exports.buildPerfScanEvent=buildPerfScanEvent,exports.createPerfRulePublisher=createPerfRulePublisher;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),path=tslib_1.__importStar(require("node:path"));exports.PERF_RULES_DOC="https://github.com/kiwiapps-ltd/lensmcp/blob/main/docs/lensmcp/concepts/memory-and-build-cost.mdx";let idCounter=0;function makeId(){return idCounter=(idCounter+1)%65535,`${Date.now().toString(36)}${idCounter.toString(36)}${Math.random().toString(36).slice(2,8)}`}o(makeId,"makeId"),u(makeId,"makeId");function perfRuleFingerprint(r,e,t){return`perf-rule:${r}:${e}:${t}`}o(perfRuleFingerprint,"perfRuleFingerprint"),u(perfRuleFingerprint,"perfRuleFingerprint");function perfRuleScanFingerprint(r,e){return`perf-rule-scan:${r}:${e}`}o(perfRuleScanFingerprint,"perfRuleScanFingerprint"),u(perfRuleScanFingerprint,"perfRuleScanFingerprint");function envelope(r,e,t,s,n,i,p){const l="pending";return{id:p(),sessionId:l,timestamp:e,source:"perf",category:"performance",severity:t,context:{sessionId:l,projectName:r},fingerprint:s,title:n,raw:i}}o(envelope,"envelope"),u(envelope,"envelope");function buildPerfFindingEvent(r,e,t,s=makeId){const n=envelope(r,t,e.severity,perfRuleFingerprint(e.rule,r,e.subject),`${e.rule}: ${e.title}`,{kind:"perf-rule-finding",rule:e.rule,project:r,subject:e.subject,severity:e.severity,title:e.title,summary:e.summary,evidence:e.evidence,cost:e.cost,fix:e.fix,verify:e.verify,docs:e.docs,detectedAt:t},s);return n.message=e.summary,n}o(buildPerfFindingEvent,"buildPerfFindingEvent"),u(buildPerfFindingEvent,"buildPerfFindingEvent");function buildPerfRetractedEvent(r,e,t,s,n,i=makeId){return envelope(e,n,"info",perfRuleFingerprint(r,e,t),`${r}: cleared for ${e} (${t})`,{kind:"perf-rule-retracted",rule:r,project:e,subject:t,reason:s,at:n},i)}o(buildPerfRetractedEvent,"buildPerfRetractedEvent"),u(buildPerfRetractedEvent,"buildPerfRetractedEvent");function buildPerfScanEvent(r,e,t,s,n=makeId,i){return envelope(e,s,"info",perfRuleScanFingerprint(r,e),`${r}: scanned ${e} \u2014 ${t.length} open`,{kind:"perf-rule-scan",rule:r,project:e,open:t,at:s,...i!==void 0?{elapsedMs:i}:{}},n)}o(buildPerfScanEvent,"buildPerfScanEvent"),u(buildPerfScanEvent,"buildPerfScanEvent");function createPerfRulePublisher(r,e){if(!r)return{publish:u(()=>!1,"publish")};const t=e??(()=>{try{return require("@lensmcp/node-instrumentation")}catch{return}})(),s=t?.createEventFloodGuard({burst:30,perSec:5});let n=!1;return{publish(i){const p=s?.admit(i.fingerprint)??{ok:!0,suppressed:0};if(!p.ok)return!1;const l=JSON.stringify(p.suppressed>0?{...i,raw:{...i.raw,floodSuppressed:p.suppressed}}:i)+`
|
|
2
|
+
`;try{return n||(fs.mkdirSync(path.dirname(r),{recursive:!0}),n=!0),t?.maybeRotateEventFile(r,l.length),fs.appendFileSync(r,l),!0}catch{return n=!1,!1}}}}o(createPerfRulePublisher,"createPerfRulePublisher"),u(createPerfRulePublisher,"createPerfRulePublisher");
|
|
@@ -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");
|