@lensmcp/cluster 1.23.12 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-host.d.ts +1 -0
- package/build-host.js +1 -0
- package/create-webpack-dev.d.ts +75 -39
- 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 +74 -3
- 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/registration-guards.d.ts +32 -0
- package/executors/gateway/runtime/registration-guards.js +1 -0
- 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 +35 -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 +50 -4
- 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 +258 -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,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");
|
|
@@ -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");
|