@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.
Files changed (107) hide show
  1. package/build-host.d.ts +1 -0
  2. package/build-host.js +1 -0
  3. package/bundle-parent.d.ts +8 -0
  4. package/bundle-parent.js +1 -0
  5. package/create-webpack-dev.d.ts +74 -40
  6. package/create-webpack-dev.js +1 -1
  7. package/dev-devtool.d.ts +45 -0
  8. package/dev-devtool.js +1 -0
  9. package/devserver-notifier.d.ts +57 -0
  10. package/devserver-notifier.js +1 -0
  11. package/executors/gateway/body-stall.d.ts +13 -1
  12. package/executors/gateway/body-stall.js +1 -1
  13. package/executors/gateway/canonical-path.d.ts +1 -1
  14. package/executors/gateway/client-ip.d.ts +1 -1
  15. package/executors/gateway/gateway.impl.js +1 -1
  16. package/executors/gateway/jwt-claims.d.ts +1 -1
  17. package/executors/gateway/prod-config.d.ts +1 -1
  18. package/executors/gateway/prod-runtime/body-stall.d.ts +1 -1
  19. package/executors/gateway/rate-limit.d.ts +1 -1
  20. package/executors/gateway/runtime/auth.d.ts +1 -1
  21. package/executors/gateway/runtime/chooser.d.ts +2 -2
  22. package/executors/gateway/runtime/control.d.ts +3 -3
  23. package/executors/gateway/runtime/control.js +2 -2
  24. package/executors/gateway/runtime/dev-auth.d.ts +1 -1
  25. package/executors/gateway/runtime/edge.d.ts +3 -3
  26. package/executors/gateway/runtime/guest-graph.d.ts +113 -0
  27. package/executors/gateway/runtime/guest-graph.js +2 -0
  28. package/executors/gateway/runtime/lens-children.d.ts +11 -1
  29. package/executors/gateway/runtime/lens-children.js +1 -1
  30. package/executors/gateway/runtime/lifecycle.d.ts +56 -8
  31. package/executors/gateway/runtime/lifecycle.js +3 -3
  32. package/executors/gateway/runtime/process-names.d.ts +18 -0
  33. package/executors/gateway/runtime/process-names.js +1 -0
  34. package/executors/gateway/runtime/project-graph-store.d.ts +74 -0
  35. package/executors/gateway/runtime/project-graph-store.js +1 -0
  36. package/executors/gateway/runtime/proxy.d.ts +2 -2
  37. package/executors/gateway/runtime/proxy.js +1 -1
  38. package/executors/gateway/runtime/scope.d.ts +1 -1
  39. package/executors/gateway/runtime/serve-launch.d.ts +118 -0
  40. package/executors/gateway/runtime/serve-launch.js +3 -0
  41. package/executors/gateway/runtime/server.d.ts +3 -3
  42. package/executors/gateway/runtime/server.js +2 -2
  43. package/executors/gateway/runtime/shared-build.d.ts +65 -0
  44. package/executors/gateway/runtime/shared-build.js +4 -0
  45. package/executors/gateway/runtime/types.d.ts +39 -9
  46. package/executors/gateway/runtime/types.js +1 -1
  47. package/executors/gateway/runtime/workspace-log.js +1 -1
  48. package/executors/gateway/schema.d.ts +3 -0
  49. package/executors/gateway/schema.json +4 -0
  50. package/executors/serve/schema.d.ts +2 -0
  51. package/executors/serve/schema.json +4 -0
  52. package/executors/serve/serve.impl.d.ts +12 -6
  53. package/executors/serve/serve.impl.js +1 -1
  54. package/executors/trust/hostname.d.ts +1 -1
  55. package/exit-reason.d.ts +1 -1
  56. package/fork-ts-check-reporter.d.ts +12 -0
  57. package/fork-ts-check-reporter.js +1 -1
  58. package/graph-snapshot.d.ts +1 -0
  59. package/graph-snapshot.js +1 -0
  60. package/heap-snapshot.d.ts +66 -0
  61. package/heap-snapshot.js +1 -0
  62. package/hot-swap-registries.d.ts +1 -1
  63. package/inspector-secret.d.ts +39 -0
  64. package/inspector-secret.js +1 -0
  65. package/main.devserver.d.ts +33 -5
  66. package/main.devserver.js +11 -9
  67. package/package.json +4 -4
  68. package/perf/heavy-barrel-import.d.ts +135 -0
  69. package/perf/heavy-barrel-import.js +1 -0
  70. package/perf/perf-config.d.ts +29 -0
  71. package/perf/perf-config.js +1 -0
  72. package/perf/perf-rule-events.d.ts +71 -0
  73. package/perf/perf-rule-events.js +2 -0
  74. package/perf/perf-rules-analysis.d.ts +28 -0
  75. package/perf/perf-rules-analysis.js +1 -0
  76. package/perf/perf-rules-plugin.d.ts +71 -0
  77. package/perf/perf-rules-plugin.js +1 -0
  78. package/perf/perf-rules-worker.d.ts +1 -0
  79. package/perf/perf-rules-worker.js +1 -0
  80. package/perf/source-scan.d.ts +59 -0
  81. package/perf/source-scan.js +5 -0
  82. package/perf/workspace-libraries.d.ts +52 -0
  83. package/perf/workspace-libraries.js +2 -0
  84. package/pod-memory-events.d.ts +71 -0
  85. package/pod-memory-events.js +2 -0
  86. package/pod-memory.d.ts +264 -0
  87. package/pod-memory.js +1 -0
  88. package/process-name.d.ts +45 -0
  89. package/process-name.js +1 -1
  90. package/serve-runner.d.ts +2 -0
  91. package/serve-runner.js +1 -0
  92. package/shared-build/attach.d.ts +56 -0
  93. package/shared-build/attach.js +3 -0
  94. package/shared-build/host.d.ts +52 -0
  95. package/shared-build/host.js +4 -0
  96. package/shared-build/protocol.d.ts +118 -0
  97. package/shared-build/protocol.js +1 -0
  98. package/shared-build/shared-compilation.d.ts +91 -0
  99. package/shared-build/shared-compilation.js +3 -0
  100. package/shared-build/signature.d.ts +52 -0
  101. package/shared-build/signature.js +1 -0
  102. package/stdio-guard.d.ts +1 -1
  103. package/swap-recycle.d.ts +21 -0
  104. package/swap-recycle.js +1 -1
  105. package/tsgo-check-plugin.d.ts +1 -1
  106. package/workspace-paths.d.ts +76 -0
  107. package/workspace-paths.js +1 -0
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The shared compiler (issues/open/pod-memory-05): ONE webpack compilation for every service of a workspace
3
+ * instead of one watcher per service. Three processes talk:
4
+ *
5
+ * runner (the serve executor, ATTACHED) ⇄ gateway (relay) ⇄ build host (one per workspace)
6
+ *
7
+ * over the two IPC channels the gateway already owns — the runner's (it hands the runner its task on it,
8
+ * serve-launch.ts) and the build host's. Nothing listens on a socket, so there is nothing for another local
9
+ * account to connect to. These are the messages; nothing here touches a process.
10
+ */
11
+ import type { DevWebpackOptions } from '../create-webpack-dev';
12
+ /** The gateway's switch: `LENSMCP_SHARED_COMPILER=1` turns the shared compiler on, `0` off. */
13
+ export declare const SHARED_COMPILER_ENV = "LENSMCP_SHARED_COMPILER";
14
+ /** Set by the gateway on a runner whose build-host messages it relays — the serve executor attaches only then. */
15
+ export declare const SHARED_BUILD_ENV = "LENSMCP_SHARED_BUILD";
16
+ /** Every message on a runner's channel that belongs to the shared build carries this, so nothing else is mistaken for one. */
17
+ export declare const SHARED_BUILD_CHANNEL = "lensmcp-shared-build";
18
+ /** The role in the build host's process name: `<workspace>-build` (process-name.ts). */
19
+ export declare const BUILD_HOST_ROLE = "build";
20
+ /** The argument that names the build host's workspace on its command line — always the LAST one (the reaper matches it exactly). */
21
+ export declare const BUILD_HOST_ROOT_FLAG = "--workspace-root=";
22
+ /** Default when the variable is unset. */
23
+ export declare const SHARED_COMPILER_DEFAULT = false;
24
+ /** Is the shared compiler on? The variable wins; else the gateway option; else {@link SHARED_COMPILER_DEFAULT}. */
25
+ export declare function sharedCompilerEnabled(env?: NodeJS.ProcessEnv, option?: boolean): boolean;
26
+ /** What a runner hands the build host: everything the host needs to build this service's config itself. */
27
+ export interface MemberJoinRequest {
28
+ project: string;
29
+ target: string;
30
+ configuration?: string;
31
+ /** The `createDevWebpackConfig` options, exactly as the serve executor computed them. */
32
+ options: DevWebpackOptions;
33
+ /** The target's resolved options — what NxAppWebpackPlugin merges into its own. */
34
+ targetOptions: Record<string, unknown>;
35
+ /** The task's environment (nx's dotenv files applied): an override that reads `process.env` sees this. */
36
+ env: Record<string, string>;
37
+ }
38
+ /** runner → gateway. */
39
+ export interface RunnerJoinMessage {
40
+ channel: typeof SHARED_BUILD_CHANNEL;
41
+ type: 'join';
42
+ join: MemberJoinRequest;
43
+ }
44
+ /** gateway → host. `member` names one runner tree: `<project>#g<generation>`. */
45
+ export type HostInbound = {
46
+ type: 'join';
47
+ member: string;
48
+ join: MemberJoinRequest;
49
+ } | {
50
+ type: 'leave';
51
+ member: string;
52
+ };
53
+ /** The host joined the member: its entries are (or are being) compiled in `group`. */
54
+ export interface AcceptedEvent {
55
+ type: 'accepted';
56
+ group: string;
57
+ /** The projects compiled in the same compilation, this one included. */
58
+ projects: string[];
59
+ }
60
+ /** The host will not compile this member; the runner compiles it on its own and says why. */
61
+ export interface RefusedEvent {
62
+ type: 'refused';
63
+ reason: string;
64
+ }
65
+ /** The shared watcher saw files change — the watcher testimony's `watchRun` (DevServerReloadPlugin). */
66
+ export interface WatchRunEvent {
67
+ type: 'watch-run';
68
+ paths: string[];
69
+ }
70
+ /** One compilation finished, as far as this member's entries are concerned. */
71
+ export interface BuiltEvent {
72
+ type: 'built';
73
+ /** None of this member's modules (nor its entries) has an error. */
74
+ ok: boolean;
75
+ /** Its emitted chunks differ from the last build this member was told about. */
76
+ changed: boolean;
77
+ /** The chunk hash of its entries — what `changed` compares. */
78
+ hash: string;
79
+ /** The stats lines for this member — what its runner prints (the gateway reads `ERROR in`/`compiled successfully`). */
80
+ text: string;
81
+ errors: number;
82
+ warnings: number;
83
+ durationMs: number;
84
+ /** The bundle carries source maps (dev-devtool.ts) — the devserver's `LENSMCP_DEV_SOURCE_MAPS`. */
85
+ sourceMaps: boolean;
86
+ /** Every file this member's bundle was built from — a type-check-only compiler watches them (attach.ts). Sent when the set changed. */
87
+ files?: string[];
88
+ }
89
+ /** The shared compiler failed outright (not a module error) — every member is told. */
90
+ export interface FailedEvent {
91
+ type: 'failed';
92
+ message: string;
93
+ }
94
+ /** The shared watcher closed (the host is stopping). */
95
+ export interface WatchClosedEvent {
96
+ type: 'watch-closed';
97
+ }
98
+ export type MemberEvent = AcceptedEvent | RefusedEvent | WatchRunEvent | BuiltEvent | FailedEvent | WatchClosedEvent;
99
+ /** host → gateway. */
100
+ export type HostOutbound = {
101
+ type: 'member';
102
+ member: string;
103
+ event: MemberEvent;
104
+ } | {
105
+ type: 'ready';
106
+ pid: number;
107
+ };
108
+ /** gateway → runner. */
109
+ export interface RunnerInbound {
110
+ channel: typeof SHARED_BUILD_CHANNEL;
111
+ event: MemberEvent;
112
+ }
113
+ export declare function isRunnerJoinMessage(m: unknown): m is RunnerJoinMessage;
114
+ export declare function isRunnerInbound(m: unknown): m is RunnerInbound;
115
+ /** The build host's arguments after its script — read only by `ps` and the reaper: the root comes last. */
116
+ export declare function buildHostArgs(root: string): string[];
117
+ /** Is this command line a build host started for the workspace at `root`? Exact, like the serve runner's. */
118
+ export declare function isBuildHostCommandOf(cmd: string, root: string): boolean;
@@ -0,0 +1 @@
1
+ "use strict";var _=Object.defineProperty;var r=(t,e)=>_(t,"name",{value:e,configurable:!0});var o=Object.defineProperty,s=r((t,e)=>o(t,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.SHARED_COMPILER_DEFAULT=exports.BUILD_HOST_ROOT_FLAG=exports.BUILD_HOST_ROLE=exports.SHARED_BUILD_CHANNEL=exports.SHARED_BUILD_ENV=exports.SHARED_COMPILER_ENV=void 0,exports.sharedCompilerEnabled=sharedCompilerEnabled,exports.isRunnerJoinMessage=isRunnerJoinMessage,exports.isRunnerInbound=isRunnerInbound,exports.buildHostArgs=buildHostArgs,exports.isBuildHostCommandOf=isBuildHostCommandOf,exports.SHARED_COMPILER_ENV="LENSMCP_SHARED_COMPILER",exports.SHARED_BUILD_ENV="LENSMCP_SHARED_BUILD",exports.SHARED_BUILD_CHANNEL="lensmcp-shared-build",exports.BUILD_HOST_ROLE="build",exports.BUILD_HOST_ROOT_FLAG="--workspace-root=";const ON=/^(1|on|true|yes)$/i,OFF=/^(0|off|false|no)$/i;exports.SHARED_COMPILER_DEFAULT=!1;function sharedCompilerEnabled(t=process.env,e){const n=(t[exports.SHARED_COMPILER_ENV]??"").trim();return ON.test(n)?!0:OFF.test(n)?!1:e??exports.SHARED_COMPILER_DEFAULT}r(sharedCompilerEnabled,"sharedCompilerEnabled"),s(sharedCompilerEnabled,"sharedCompilerEnabled");function isRunnerJoinMessage(t){const e=t;return!!e&&e.channel===exports.SHARED_BUILD_CHANNEL&&e.type==="join"&&typeof e.join?.project=="string"}r(isRunnerJoinMessage,"isRunnerJoinMessage"),s(isRunnerJoinMessage,"isRunnerJoinMessage");function isRunnerInbound(t){const e=t;return!!e&&e.channel===exports.SHARED_BUILD_CHANNEL&&typeof e.event?.type=="string"}r(isRunnerInbound,"isRunnerInbound"),s(isRunnerInbound,"isRunnerInbound");function buildHostArgs(t){return["host",`${exports.BUILD_HOST_ROOT_FLAG}${t.replace(/\/+$/,"")}`]}r(buildHostArgs,"buildHostArgs"),s(buildHostArgs,"buildHostArgs");function isBuildHostCommandOf(t,e){return/\bbuild-host\.(js|ts)\b/.test(t)&&t.trimEnd().endsWith(` ${exports.BUILD_HOST_ROOT_FLAG}${e.replace(/\/+$/,"")}`)}r(isBuildHostCommandOf,"isBuildHostCommandOf"),s(isBuildHostCommandOf,"isBuildHostCommandOf");
@@ -0,0 +1,91 @@
1
+ import type { DevFilesystemCache, DevWebpackOptions } from '../create-webpack-dev';
2
+ /** One webpack entry of one service. */
3
+ export interface SharedEntry {
4
+ /** The webpack entry name: the project for its main bundle, `<project>~<worker>` for a worker. */
5
+ name: string;
6
+ project: string;
7
+ /** Absolute path of the entry module. */
8
+ import: string;
9
+ /** Absolute path of the bundle this entry writes — where the service's devserver loads it from. */
10
+ outputFile: string;
11
+ }
12
+ /** The entries of one service, from the options its serve executor computed. */
13
+ export declare function memberEntries(project: string, options: DevWebpackOptions): SharedEntry[];
14
+ /** webpack's normalised entry description — what a dynamic `entry` function must return. */
15
+ interface EntryDescription {
16
+ import: string[];
17
+ filename: string;
18
+ }
19
+ export declare class EntryRegistry {
20
+ private readonly byProject;
21
+ set(project: string, entries: SharedEntry[]): void;
22
+ delete(project: string): boolean;
23
+ has(project: string): boolean;
24
+ projects(): string[];
25
+ entriesOf(project: string): readonly SharedEntry[];
26
+ /** entry name → project, for every live entry. */
27
+ entryProjects(): Record<string, string>;
28
+ /** The entry for `name`, if any. */
29
+ entry(name: string): SharedEntry | undefined;
30
+ /** webpack's `entry`, with each filename relative to `outputPath` (a bundle lands in its service's own directory). */
31
+ webpackEntries(outputPath: string): Record<string, EntryDescription>;
32
+ }
33
+ export interface SharedEntriesOptions {
34
+ registry: EntryRegistry;
35
+ workspaceRoot: string;
36
+ /** The compilation's own `output.path` — a directory nothing else uses; every bundle's filename is relative to it. */
37
+ outputPath: string;
38
+ cache: DevFilesystemCache;
39
+ }
40
+ /**
41
+ * Applied LAST: by then NxAppWebpackPlugin has written the first member's entry, context and output, and
42
+ * DevBuildFinalizePlugin its cache.
43
+ *
44
+ * - `entry` is the registry, so members come and go between compilations;
45
+ * - `context` is the workspace root: module ids and names no longer depend on which member came first;
46
+ * - `output.path` is a private directory and every bundle's filename climbs out of it to its service's own
47
+ * output directory. `clean` is off: webpack's CleanPlugin skips an asset whose path leaves `output.path`, so it
48
+ * would clean nothing — and a service's own directory is not emptied on its first build, as its own compiler's
49
+ * `clean: true` did;
50
+ * - an async chunk is written beside the bundle of the first runtime that loads it;
51
+ * - the cache is the group's own.
52
+ */
53
+ export declare class SharedEntriesPlugin {
54
+ private readonly options;
55
+ constructor(options: SharedEntriesOptions);
56
+ apply(compiler: any): void;
57
+ }
58
+ /** What one compilation means for one service. */
59
+ export interface EntryAttribution {
60
+ project: string;
61
+ /** Every chunk of its entries, `<id>:<hash>`, hashed — equal iff its emitted bundles are. */
62
+ hash: string;
63
+ /** Its errors and warnings, as printed — at most {@link MAX_PROBLEMS} of each; the counts are complete. */
64
+ errors: string[];
65
+ warnings: string[];
66
+ errorCount: number;
67
+ warningCount: number;
68
+ /** The source files its bundles were built from (absolute, no query). */
69
+ files: string[];
70
+ /** Its emitted bundles, relative to the workspace root, with their sizes. */
71
+ assets: Array<{
72
+ file: string;
73
+ size: number;
74
+ }>;
75
+ }
76
+ /**
77
+ * Split a finished compilation by service: each project's chunks (its entries' and every async chunk they
78
+ * load), the modules in them, the errors and warnings whose module is among them, and a hash over the chunks.
79
+ *
80
+ * An error with no module is a project's when its location names one of that project's entries (an entry that
81
+ * does not resolve); otherwise nobody's in particular, so every project gets it — a reader must not miss it.
82
+ */
83
+ export declare function attributeCompilation(compilation: any, registry: EntryRegistry): Map<string, EntryAttribution>;
84
+ /** The stats lines one service's runner prints for a build — the shapes the gateway reads (pod-output.ts). */
85
+ export interface DescribeBuildOptions {
86
+ webpackVersion: string;
87
+ durationMs: number;
88
+ changed: boolean;
89
+ }
90
+ export declare function describeBuild(a: EntryAttribution, opts: DescribeBuildOptions): string;
91
+ export {};
@@ -0,0 +1,3 @@
1
+ "use strict";var w=Object.defineProperty;var l=(t,e)=>w(t,"name",{value:e,configurable:!0});var $=Object.defineProperty,c=l((t,e)=>$(t,"name",{value:e,configurable:!0}),"c");Object.defineProperty(exports,"__esModule",{value:!0}),exports.SharedEntriesPlugin=exports.EntryRegistry=void 0,exports.memberEntries=memberEntries,exports.attributeCompilation=attributeCompilation,exports.describeBuild=describeBuild;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),path=tslib_1.__importStar(require("node:path"));function memberEntries(t,e){const r=[{name:t,project:t,import:e.main,outputFile:path.join(e.outputDir,"main.js")}];for(const o of e.additionalEntryPoints??[])r.push({name:`${t}~${o.entryName}`,project:t,import:o.entryPath,outputFile:path.join(e.outputDir,`${o.entryName}.js`)});return r}l(memberEntries,"memberEntries"),c(memberEntries,"memberEntries");class EntryRegistry{static{l(this,"EntryRegistry")}static{c(this,"EntryRegistry")}constructor(){this.byProject=new Map}set(e,r){this.byProject.set(e,r)}delete(e){return this.byProject.delete(e)}has(e){return this.byProject.has(e)}projects(){return[...this.byProject.keys()]}entriesOf(e){return this.byProject.get(e)??[]}entryProjects(){const e={};for(const[r,o]of this.byProject)for(const n of o)e[n.name]=r;return e}entry(e){for(const r of this.byProject.values()){const o=r.find(n=>n.name===e);if(o)return o}}webpackEntries(e){const r={};for(const o of this.byProject.values())for(const n of o)r[n.name]={import:[n.import],filename:path.relative(e,n.outputFile)};return r}}exports.EntryRegistry=EntryRegistry;class SharedEntriesPlugin{static{l(this,"SharedEntriesPlugin")}static{c(this,"SharedEntriesPlugin")}constructor(e){this.options=e}apply(e){const{registry:r,workspaceRoot:o,outputPath:n,cache:s}=this.options;e.options.context=o,e.context=o,e.options.output={...e.options.output,path:n,clean:!1,chunkFilename:c(m=>{const i=m.chunk?.runtime,d=typeof i=="string"?i:i?[...i].sort()[0]:void 0,u=d?r.entry(d):void 0;return`${u?path.relative(n,path.dirname(u.outputFile)):"."}/[id].js`},"chunkFilename")},e.options.cache={...s,buildDependencies:{...s.buildDependencies}},e.options.entry=()=>Promise.resolve(r.webpackEntries(n))}}exports.SharedEntriesPlugin=SharedEntriesPlugin;const resourceOf=c(t=>{if(typeof t.resource!="string"||!t.resource)return;const e=t.resource.search(/[?#]/);return e===-1?t.resource:t.resource.slice(0,e)},"resourceOf");function formatLoc(t){const e=t?.start;if(!e||typeof e.line!="number")return"";const r=`${e.line}:${e.column??0}`,o=t?.end;return!o||typeof o.line!="number"?r:o.line===e.line?`${r}-${o.column??0}`:`${r}-${o.line}:${o.column??0}`}l(formatLoc,"formatLoc"),c(formatLoc,"formatLoc");function formatProblem(t,e,r){const o=e.module;let n="";try{n=o?.readableIdentifier?o.readableIdentifier(r.requestShortener):""}catch{}const s=formatLoc(e.loc);return`${n?`${t} in ${n}${s?` ${s}`:""}`:t}
2
+ ${String(e.message??e).trim()}`}l(formatProblem,"formatProblem"),c(formatProblem,"formatProblem");const MAX_PROBLEMS=30,MAX_PROBLEM_CHARS=4e3;function attributeCompilation(t,e){const r=new Map,o=new Map,n=t.compiler?.context??t.options?.context??process.cwd();for(const i of e.projects()){const d=new Set;let u=!1;for(const a of e.entriesOf(i)){const p=t.entrypoints?.get(a.name);if(p){u=!0;for(const f of p.chunks??[]){d.add(f);for(const y of f.getAllAsyncChunks?.()??[])d.add(y)}}}if(!u)continue;const h=new Set,g=[],b=[];for(const a of d){g.push(`${String(a.id)}:${a.hash??""}`);for(const p of t.chunkGraph.getChunkModulesIterable(a)){let f=o.get(p);f||o.set(p,f=new Set),f.add(i);const y=resourceOf(p);y&&h.add(y)}for(const p of a.files??[]){const f=t.getAsset?.(p),y=path.resolve(t.outputOptions?.path??n,p);b.push({file:path.relative(n,y),size:f?.info?.size??f?.source?.size?.()??0})}}r.set(i,{project:i,hash:(0,node_crypto_1.createHash)("sha256").update(g.sort().join("|")).digest("hex").slice(0,16),errors:[],warnings:[],errorCount:0,warningCount:0,files:[...h],assets:b})}const s=e.entryProjects(),m=c((i,d)=>{for(const u of d){let h=u.module?o.get(u.module):void 0;!h&&u.loc?.name&&s[u.loc.name]&&(h=[s[u.loc.name]]),h||(h=r.keys());const g=formatProblem(i==="errors"?"ERROR":"WARNING",u,t);for(const b of h){const a=r.get(b);a&&(i==="errors"?a.errorCount+=1:a.warningCount+=1,a[i].length<MAX_PROBLEMS&&a[i].push(g.length>MAX_PROBLEM_CHARS?`${g.slice(0,MAX_PROBLEM_CHARS)}\u2026`:g))}}},"assign");return m("errors",t.errors??[]),m("warnings",t.warnings??[]),r}l(attributeCompilation,"attributeCompilation"),c(attributeCompilation,"attributeCompilation");function describeBuild(t,e){const r=[];if(e.changed)for(const s of t.assets)r.push(`asset ${s.file} ${formatSize(s.size)} [emitted]`);for(const s of t.errors)r.push("",s);for(const s of t.warnings)r.push("",s);(t.errors.length||t.warnings.length)&&r.push("");const o=c((s,m)=>`${s} ${m}${s===1?"":"s"}`,"plural"),n=t.errorCount?`compiled with ${o(t.errorCount,"error")}${t.warningCount?` and ${o(t.warningCount,"warning")}`:""}`:t.warningCount?`compiled with ${o(t.warningCount,"warning")}`:"compiled successfully";return r.push(`webpack ${e.webpackVersion} ${n} in ${e.durationMs} ms (shared compilation${e.changed?"":" \u2014 this service's bundle is unchanged"})`),r.join(`
3
+ `)}l(describeBuild,"describeBuild"),c(describeBuild,"describeBuild");function formatSize(t){return t>=1024*1024?`${(t/(1024*1024)).toFixed(2)} MiB`:t>=1024?`${(t/1024).toFixed(1)} KiB`:`${t} bytes`}l(formatSize,"formatSize"),c(formatSize,"formatSize");
@@ -0,0 +1,52 @@
1
+ import type { DevWebpackOptions } from '../create-webpack-dev';
2
+ import type { MemberJoinRequest } from './protocol';
3
+ /** Target options that only name or wire THIS service — never part of what its modules compile to. */
4
+ export declare const PER_SERVICE_TARGET_KEYS: readonly string[];
5
+ /**
6
+ * Compiler options that change type checking or where files go, never what ts-loader emits for a module in
7
+ * `transpileOnly` mode. Everything NOT listed is compared, so an option this list does not know keeps two
8
+ * services apart rather than merging them wrongly.
9
+ */
10
+ export declare const TYPECHECK_ONLY_COMPILER_OPTIONS: ReadonlySet<string>;
11
+ /** Plugins a member's config carries for ITSELF — the shared compilation leaves them out (and adds its own). */
12
+ export declare const PER_SERVICE_PLUGINS: ReadonlySet<string>;
13
+ /** A member's signature: the key, and each part's hash — what the reason line names when two differ. */
14
+ export interface MemberSignature {
15
+ key: string;
16
+ parts: Record<string, string>;
17
+ }
18
+ /** What {@link memberSignature} reads. */
19
+ export interface SignatureInput {
20
+ join: MemberJoinRequest;
21
+ /** The devtool the member's config resolved to. */
22
+ devtool: unknown;
23
+ /** The member's evaluated webpack config (`createDevWebpackConfig` + its override). */
24
+ config: Record<string, unknown>;
25
+ /** The member's tsconfig compiler options, as {@link transpileCompilerOptions} returns them. */
26
+ compilerOptions: Record<string, unknown>;
27
+ }
28
+ /**
29
+ * The compiler options of `tsConfig` that change what ts-loader EMITS, as TypeScript resolves them: `extends`
30
+ * followed, enum values as TypeScript stores them, `paths` resolved to absolute targets. No file is listed:
31
+ * the parse host lists no directories, so `include` globs cost nothing.
32
+ */
33
+ export declare function transpileCompilerOptions(tsConfig: string, workspaceRoot: string): Record<string, unknown>;
34
+ /** The strings in a member's config that only name THIS member, longest first — replaced before fingerprinting. */
35
+ export interface FingerprintMask {
36
+ from: string;
37
+ to: string;
38
+ /** Replace only a string that IS `from` — a project name inside a longer word (`auth` in `authority`) is not this member's. */
39
+ whole?: boolean;
40
+ }
41
+ export declare function memberMasks(options: DevWebpackOptions, project: string): FingerprintMask[];
42
+ /**
43
+ * A stable, serialisable picture of a webpack config: RegExps by source, functions by the hash of their
44
+ * source, class instances by constructor name and fields, cycles cut, depth and width bounded. Strings are
45
+ * masked. The per-service plugins are left out.
46
+ */
47
+ export declare function configFingerprint(config: Record<string, unknown>, masks: readonly FingerprintMask[]): Record<string, string>;
48
+ export declare function memberSignature(input: SignatureInput): MemberSignature;
49
+ /** The first part `a` and `b` disagree on, in words — `undefined` when they agree. */
50
+ export declare function describeDifference(a: MemberSignature, b: MemberSignature): string | undefined;
51
+ /** Why a member cannot be compiled by the build host at all — it compiles on its own, as before. */
52
+ export declare function refusalOf(join: MemberJoinRequest, workspaceRoot: string): string | undefined;
@@ -0,0 +1 @@
1
+ "use strict";var g=Object.defineProperty;var p=(e,i)=>g(e,"name",{value:i,configurable:!0});var d=Object.defineProperty,a=p((e,i)=>d(e,"name",{value:i,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PER_SERVICE_PLUGINS=exports.TYPECHECK_ONLY_COMPILER_OPTIONS=exports.PER_SERVICE_TARGET_KEYS=void 0,exports.transpileCompilerOptions=transpileCompilerOptions,exports.memberMasks=memberMasks,exports.configFingerprint=configFingerprint,exports.memberSignature=memberSignature,exports.describeDifference=describeDifference,exports.refusalOf=refusalOf;const tslib_1=require("tslib"),node_crypto_1=require("node:crypto"),node_module_1=require("node:module"),path=tslib_1.__importStar(require("node:path"));exports.PER_SERVICE_TARGET_KEYS=["entryFile","tsConfigFile","outputPath","workers","configEnv","childCount","serviceName","servePrefix","gateway","internalPort","debugPort","logFile","webpackConfigPath","devtool","memoryLimit","port"],exports.TYPECHECK_ONLY_COMPILER_OPTIONS=new Set(["configFilePath","pathsBasePath","rootDir","rootDirs","outDir","outFile","declaration","declarationDir","declarationMap","emitDeclarationOnly","composite","incremental","tsBuildInfoFile","noEmit","noEmitOnError","types","typeRoots","lib","skipLibCheck","skipDefaultLibCheck","forceConsistentCasingInFileNames","strict","noImplicitAny","strictNullChecks","strictFunctionTypes","strictBindCallApply","strictPropertyInitialization","strictBuiltinIteratorReturn","noImplicitThis","useUnknownInCatchVariables","noUnusedLocals","noUnusedParameters","exactOptionalPropertyTypes","noImplicitReturns","noFallthroughCasesInSwitch","noUncheckedIndexedAccess","noImplicitOverride","noPropertyAccessFromIndexSignature","allowUnusedLabels","allowUnreachableCode","noErrorTruncation","diagnostics","extendedDiagnostics","listFiles","listEmittedFiles","traceResolution","explainFiles","pretty","preserveWatchOutput","assumeChangesOnlyAffectDirectDependencies","noStrictGenericChecks","suppressExcessPropertyErrors","suppressImplicitAnyIndexErrors","disableSizeLimit","disableSourceOfProjectReferenceRedirect","disableSolutionSearching","disableReferencedProjectLoad","noCheck","noUncheckedSideEffectImports","checkJs","maxNodeModuleJsDepth","isolatedDeclarations","erasableSyntaxOnly","libReplacement"]),exports.PER_SERVICE_PLUGINS=new Set(["DevServerReloadPlugin","PerfRulesPlugin","ForkTsCheckReporterPlugin","TsgoCheckPlugin","ForkTsCheckerWebpackPlugin","HotModuleReplacementPlugin"]);const UNWALKED_PLUGINS=new Set([...exports.PER_SERVICE_PLUGINS,"NxAppWebpackPlugin"]),sha=a(e=>(0,node_crypto_1.createHash)("sha256").update(e).digest("hex").slice(0,16),"sha");function loadTypeScript(e){try{return(0,node_module_1.createRequire)(path.join(e,"package.json"))("typescript")}catch{return require("typescript")}}p(loadTypeScript,"loadTypeScript"),a(loadTypeScript,"loadTypeScript");function canonical(e){if(Array.isArray(e))return e.map(canonical);if(e&&typeof e=="object"){const i={};for(const o of Object.keys(e).sort())i[o]=canonical(e[o]);return i}return e}p(canonical,"canonical"),a(canonical,"canonical");function transpileCompilerOptions(e,i){const o=loadTypeScript(i),r=o.readConfigFile(e,c=>o.sys.readFile(c));if(r.error)throw new Error(`cannot read ${e}: ${String(r.error.messageText)}`);const s={...o.sys,readDirectory:a(()=>[],"readDirectory")},u=o.parseJsonConfigFileContent(r.config,s,path.dirname(e),void 0,e),t={};for(const[c,f]of Object.entries(u.options))exports.TYPECHECK_ONLY_COMPILER_OPTIONS.has(c)||(t[c]=f);const n=u.options.paths;if(n){const c=u.options.baseUrl??u.options.pathsBasePath??path.dirname(e);t.paths=Object.fromEntries(Object.entries(n).map(([f,l])=>[f,l.map(m=>path.resolve(c,m))]))}return canonical(t)}p(transpileCompilerOptions,"transpileCompilerOptions"),a(transpileCompilerOptions,"transpileCompilerOptions");function memberMasks(e,i){const o=[{from:e.appRoot,to:"<appRoot>"},{from:e.outputDir,to:"<outputDir>"},{from:e.main,to:"<main>"},{from:e.tsConfig,to:"<tsConfig>"}];for(const r of e.additionalEntryPoints??[])o.push({from:r.entryPath,to:`<worker:${r.entryName}>`});return o.filter(r=>r.from).sort((r,s)=>s.from.length-r.from.length).concat([i,i.replace(/[^\w.-]+/g,"_"),e.appName,e.serviceName].filter(r=>!!r).map(r=>({from:r,to:"<project>",whole:!0})))}p(memberMasks,"memberMasks"),a(memberMasks,"memberMasks");const MAX_DEPTH=8,MAX_KEYS=200;function configFingerprint(e,i){const o=new WeakSet,r=a(t=>{let n=t;for(const c of i){if(c.whole){if(n===c.from)return c.to;continue}n.includes(c.from)&&(n=n.split(c.from).join(c.to))}return n},"mask"),s=a((t,n)=>{if(typeof t=="string")return r(t);if(typeof t=="function")return`fn:${sha(r(Function.prototype.toString.call(t)))}`;if(t instanceof RegExp)return`re:${r(String(t))}`;if(!t||typeof t!="object")return t;if(o.has(t))return"<cycle>";if(n>=MAX_DEPTH)return"<deep>";o.add(t);try{if(Array.isArray(t))return t.slice(0,MAX_KEYS).map(l=>s(l,n+1));if(t instanceof Map)return{$map:[...t.entries()].slice(0,MAX_KEYS).map(([l,m])=>[s(l,n+1),s(m,n+1)])};if(t instanceof Set)return{$set:[...t].slice(0,MAX_KEYS).map(l=>s(l,n+1))};const c=t.constructor?.name,f=c&&c!=="Object"?{$class:c}:{};for(const l of Object.keys(t).sort().slice(0,MAX_KEYS))f[l]=s(t[l],n+1);return f}finally{o.delete(t)}},"walk"),u={};for(const t of Object.keys(e).sort()){if(t==="entry"||t==="watch")continue;let n=e[t];t==="plugins"&&Array.isArray(n)&&(n=n.filter(c=>!UNWALKED_PLUGINS.has(c?.constructor?.name??""))),t==="output"&&n&&typeof n=="object"&&(n=Object.fromEntries(Object.entries(n).filter(([c])=>c!=="path"))),u[`config.${t}`]=sha(JSON.stringify(s(n,0)))}return u}p(configFingerprint,"configFingerprint"),a(configFingerprint,"configFingerprint");function moduleShapingOptions(e,i){return canonical({devtool:i,buildLibsFromSource:e.buildLibsFromSource??!0,orgScopes:e.orgScopes??[],bundlePackages:e.bundlePackages??[],nodeExternalsConfig:e.nodeExternalsConfig??null})}p(moduleShapingOptions,"moduleShapingOptions"),a(moduleShapingOptions,"moduleShapingOptions");function memberSignature(e){const{join:i}=e,o={};for(const[s,u]of Object.entries(i.targetOptions??{}))exports.PER_SERVICE_TARGET_KEYS.includes(s)||(o[s]=u);const r={options:sha(JSON.stringify(moduleShapingOptions(i.options,e.devtool))),targetOptions:sha(JSON.stringify(canonical(o))),tsconfig:sha(JSON.stringify(e.compilerOptions)),...configFingerprint(e.config,memberMasks(i.options,i.project))};return{key:sha(JSON.stringify(canonical(r))),parts:r}}p(memberSignature,"memberSignature"),a(memberSignature,"memberSignature");const PART_NAMES={options:"its build options (devtool, buildLibsFromSource, orgScopes, bundlePackages or nodeExternalsConfig)",targetOptions:"its serve target options",tsconfig:"its tsconfig compiler options"};function describeDifference(e,i){const o=[...new Set([...Object.keys(e.parts),...Object.keys(i.parts)])].sort((r,s)=>order(r)-order(s)||r.localeCompare(s));for(const r of o)if(e.parts[r]!==i.parts[r])return PART_NAMES[r]??`its webpack config (\`${r.slice(7)}\` \u2014 a webpackConfigPath override?)`}p(describeDifference,"describeDifference"),a(describeDifference,"describeDifference");function order(e){return e in PART_NAMES?0:1}p(order,"order"),a(order,"order");function refusalOf(e,i){const{options:o}=e,r=a(s=>!!s&&!path.relative(i,path.resolve(i,s)).startsWith(".."),"inside");if(o.buildLibsFromSource===!1)return"buildLibsFromSource is false \u2014 its library paths are its own (a tsconfig nx writes per project)";if([...o.assets??[],...Array.isArray(e.targetOptions?.assets)?e.targetOptions.assets:[]].length>0)return"it copies assets \u2014 they are copied relative to one output directory, and a shared compilation writes several";if(!r(o.outputDir))return`its output directory (${o.outputDir}) is outside the workspace`;if(o.webpackConfigPath&&!r(path.resolve(o.appRoot,o.webpackConfigPath)))return"its webpackConfigPath is outside the workspace";for(const s of o.additionalEntryPoints??[])if(!/^[\w.-]+$/.test(s.entryName))return`worker "${s.entryName}" is not a plain file name`}p(refusalOf,"refusalOf"),a(refusalOf,"refusalOf");
package/stdio-guard.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * A pod child's stdout and stderr are PIPES to its devserver parent, and the parent can go first
3
- * (issues/foodguard/25). A write to a pipe nobody reads fails with EPIPE, which Node delivers as an
3
+ * (issues/closed/foodguard-25). A write to a pipe nobody reads fails with EPIPE, which Node delivers as an
4
4
  * `'error'` EVENT on the stream; with no listener that is an uncaught exception. The devserver's
5
5
  * catch-all then printed `[child] uncaughtException (uncaughtException): Error: write EPIPE`, and the
6
6
  * stdout tap filed it — stack and all — as a server-side error. So a `lensmcp setup` or a gateway
package/swap-recycle.d.ts CHANGED
@@ -38,6 +38,9 @@ export interface SwapRecyclePolicy {
38
38
  /** How long the pod must have been quiet — no swap running, no previous app closing — before its heap
39
39
  * is compared with what it booted with. `0` = compare at once, whatever is going on. */
40
40
  settleMs: number;
41
+ /** Replace the pod once a CLOSED generation has stayed in memory this long, through at least two full GCs
42
+ * (pod-memory.ts proves it). `0` = off. */
43
+ retainedMs: number;
41
44
  }
42
45
  export interface SwapRecycleState {
43
46
  /** Live heap right after the first app was built, in bytes. Unknown until measured. */
@@ -51,6 +54,18 @@ export interface SwapRecycleState {
51
54
  stuckCloses?: number;
52
55
  /** `false` while a swap is running or a previous app is still closing, and for `settleMs` after. */
53
56
  settled?: boolean;
57
+ /** The oldest generation that was closed and is PROVEN still in memory (alive after ≥2 full GCs since its
58
+ * close) — what `pod-memory.ts`'s tracker knows. Absent when every closed generation was collected. */
59
+ retained?: RetainedGenerationState;
60
+ }
61
+ /** A closed generation the pod has proven is still in memory. */
62
+ export interface RetainedGenerationState {
63
+ /** The app generation in this pod (1 = the one it booted with). */
64
+ generation: number;
65
+ /** Since its app was closed. */
66
+ ageMs: number;
67
+ /** Full GCs it has survived since then. */
68
+ gcs: number;
54
69
  }
55
70
  /**
56
71
  * - `LENSMCP_SWAP_RECYCLE` — `0`/`false`/`off`/`no` disables the guard.
@@ -68,6 +83,12 @@ export interface SwapRecycleState {
68
83
  * the engine at 3.2× its boot heap, knowledge at 4.1× and audit at 3.5× — three pods replaced in the same
69
84
  * half minute, each replacement one more cold boot on top of the burst that caused it. The share-of-limit
70
85
  * rule is NOT held back: running into V8's limit is a crash whenever it happens.
86
+ * - `LENSMCP_SWAP_RECYCLE_RETAINED_MS` — default `600000` (10 min). A generation whose app was closed and that
87
+ * is still in memory this long after, through at least two full GCs, is not garbage waiting for a collector:
88
+ * something points into it, and nothing in the process is going to let go (issues/closed/pod-memory-02). Ten minutes is
89
+ * past every timer a closed app was measured to leave behind (BullMQ's blocking-client and lock-renewal
90
+ * timeouts, app intervals: 15 s – ~2.5 min on foodguard) with a wide margin, and it fires whatever the heap
91
+ * says — a leak that costs 32 MB a swap (registry, 2026-10-09) takes forty swaps to reach 3×.
71
92
  */
72
93
  export declare function swapRecyclePolicy(env?: NodeJS.ProcessEnv): SwapRecyclePolicy;
73
94
  /**
package/swap-recycle.js CHANGED
@@ -1 +1 @@
1
- "use strict";var r=Object.defineProperty;var i=(e,s)=>r(e,"name",{value:s,configurable:!0});var o=Object.defineProperty,t=i((e,s)=>o(e,"name",{value:s,configurable:!0}),"t");Object.defineProperty(exports,"__esModule",{value:!0}),exports.swapRecyclePolicy=swapRecyclePolicy,exports.recycleReason=recycleReason;const MB=1024*1024,numberFrom=t((e,s,a)=>{if(e===void 0||e.trim()==="")return s;const n=Number(e);return Number.isFinite(n)&&n>=a?n:s},"numberFrom");function swapRecyclePolicy(e=process.env){return{enabled:!["0","false","no","off"].includes((e.LENSMCP_SWAP_RECYCLE??"").trim().toLowerCase()),heapFactor:numberFrom(e.LENSMCP_SWAP_RECYCLE_HEAP_FACTOR,3,0),minHeapBytes:numberFrom(e.LENSMCP_SWAP_RECYCLE_MIN_MB,512,0)*MB,heapRatio:Math.min(1,numberFrom(e.LENSMCP_SWAP_RECYCLE_HEAP_RATIO,.7,0)),maxSwaps:Math.floor(numberFrom(e.LENSMCP_SWAP_RECYCLE_AFTER,0,0)),stuckCloseMs:Math.floor(numberFrom(e.LENSMCP_SWAP_RECYCLE_STUCK_CLOSE_MS,6e4,0)),settleMs:Math.floor(numberFrom(e.LENSMCP_SWAP_RECYCLE_SETTLE_MS,5e3,0))}}i(swapRecyclePolicy,"swapRecyclePolicy"),t(swapRecyclePolicy,"swapRecyclePolicy");const mb=t(e=>(e/MB).toFixed(0),"mb");function recycleReason(e,s){if(!(!e.enabled||s.swaps<1)){if(e.maxSwaps>0&&s.swaps>=e.maxSwaps)return`${s.swaps} hot swaps in this process (LENSMCP_SWAP_RECYCLE_AFTER=${e.maxSwaps})`;if(e.stuckCloseMs>0&&(s.stuckCloses??0)>0){const a=s.stuckCloses;return`${a} previous app${a===1?" has":"s have"} not finished closing after ${Math.round(e.stuckCloseMs/1e3)}s \u2014 a shutdown hook that never returns keeps the whole app, its connections included, in memory`}if(e.heapRatio>0&&s.limitBytes>0&&s.usedBytes>=e.heapRatio*s.limitBytes){const a=Math.round(s.usedBytes/s.limitBytes*100);return`its heap is ${mb(s.usedBytes)} MB after a full GC, ${a}% of V8's ${mb(s.limitBytes)} MB limit`}if(e.heapFactor>0&&s.settled!==!1&&s.baselineBytes!==void 0&&s.baselineBytes>0&&s.usedBytes>=e.minHeapBytes&&s.usedBytes>=e.heapFactor*s.baselineBytes){const a=(s.usedBytes/s.baselineBytes).toFixed(1);return`its heap is ${mb(s.usedBytes)} MB after a full GC, ${a}\xD7 the ${mb(s.baselineBytes)} MB it booted with \u2014 earlier generations of the bundle are still in memory`}}}i(recycleReason,"recycleReason"),t(recycleReason,"recycleReason");
1
+ "use strict";var o=Object.defineProperty;var n=(s,e)=>o(s,"name",{value:e,configurable:!0});var t=Object.defineProperty,r=n((s,e)=>t(s,"name",{value:e,configurable:!0}),"r");Object.defineProperty(exports,"__esModule",{value:!0}),exports.swapRecyclePolicy=swapRecyclePolicy,exports.recycleReason=recycleReason;const MB=1024*1024,numberFrom=r((s,e,a)=>{if(s===void 0||s.trim()==="")return e;const i=Number(s);return Number.isFinite(i)&&i>=a?i:e},"numberFrom");function swapRecyclePolicy(s=process.env){return{enabled:!["0","false","no","off"].includes((s.LENSMCP_SWAP_RECYCLE??"").trim().toLowerCase()),heapFactor:numberFrom(s.LENSMCP_SWAP_RECYCLE_HEAP_FACTOR,3,0),minHeapBytes:numberFrom(s.LENSMCP_SWAP_RECYCLE_MIN_MB,512,0)*MB,heapRatio:Math.min(1,numberFrom(s.LENSMCP_SWAP_RECYCLE_HEAP_RATIO,.7,0)),maxSwaps:Math.floor(numberFrom(s.LENSMCP_SWAP_RECYCLE_AFTER,0,0)),stuckCloseMs:Math.floor(numberFrom(s.LENSMCP_SWAP_RECYCLE_STUCK_CLOSE_MS,6e4,0)),settleMs:Math.floor(numberFrom(s.LENSMCP_SWAP_RECYCLE_SETTLE_MS,5e3,0)),retainedMs:Math.floor(numberFrom(s.LENSMCP_SWAP_RECYCLE_RETAINED_MS,10*6e4,0))}}n(swapRecyclePolicy,"swapRecyclePolicy"),r(swapRecyclePolicy,"swapRecyclePolicy");const mb=r(s=>(s/MB).toFixed(0),"mb");function recycleReason(s,e){if(!(!s.enabled||e.swaps<1)){if(s.maxSwaps>0&&e.swaps>=s.maxSwaps)return`${e.swaps} hot swaps in this process (LENSMCP_SWAP_RECYCLE_AFTER=${s.maxSwaps})`;if(s.stuckCloseMs>0&&(e.stuckCloses??0)>0){const a=e.stuckCloses;return`${a} previous app${a===1?" has":"s have"} not finished closing after ${Math.round(s.stuckCloseMs/1e3)}s \u2014 a shutdown hook that never returns keeps the whole app, its connections included, in memory`}if(s.retainedMs>0&&e.retained&&e.retained.gcs>=2&&e.retained.ageMs>=s.retainedMs){const a=e.retained,i=a.ageMs<9e4?`${Math.round(a.ageMs/1e3)}s`:`${Math.round(a.ageMs/6e4)} min`;return`generation ${a.generation} is still in memory ${i} after its app was closed, through ${a.gcs} full GCs \u2014 something still points into it, and only a fresh process gives it back`}if(s.heapRatio>0&&e.limitBytes>0&&e.usedBytes>=s.heapRatio*e.limitBytes){const a=Math.round(e.usedBytes/e.limitBytes*100);return`its heap is ${mb(e.usedBytes)} MB after a full GC, ${a}% of V8's ${mb(e.limitBytes)} MB limit`}if(s.heapFactor>0&&e.settled!==!1&&e.baselineBytes!==void 0&&e.baselineBytes>0&&e.usedBytes>=s.minHeapBytes&&e.usedBytes>=s.heapFactor*e.baselineBytes){const a=(e.usedBytes/e.baselineBytes).toFixed(1);return`its heap is ${mb(e.usedBytes)} MB after a full GC, ${a}\xD7 the ${mb(e.baselineBytes)} MB it booted with \u2014 earlier generations of the bundle are still in memory`}}}n(recycleReason,"recycleReason"),r(recycleReason,"recycleReason");
@@ -7,7 +7,7 @@
7
7
  * services cold in ~1-3s, so instead of holding a program we re-check COLD after each successful
8
8
  * rebuild (debounced): idle cost is exactly zero.
9
9
  *
10
- * ONE AT A TIME (issues/foodguard/34). "Zero resident" is not "small": a cold check of a real service
10
+ * ONE AT A TIME (issues/closed/foodguard-34). "Zero resident" is not "small": a cold check of a real service
11
11
  * peaks at 1.4-3.4GB. Every service's plugin used to start its own the moment its build finished, so
12
12
  * one edit to a shared file started one per service at the same instant; and a newer build was meant
13
13
  * to REPLACE the running check with SIGTERM, which this checker ignores — it runs to the end and
@@ -0,0 +1,76 @@
1
+ /** A `paths` key with one `*`, parsed as TypeScript parses it. */
2
+ export interface PathPattern {
3
+ /** The key as written. */
4
+ key: string;
5
+ /** The text before the `*`. */
6
+ prefix: string;
7
+ /** The text after it. */
8
+ suffix: string;
9
+ /** Its substitutions, as written. */
10
+ targets: string[];
11
+ }
12
+ /** A tsconfig's `paths`, as TypeScript reads them. */
13
+ export interface WorkspacePaths {
14
+ /** The tsconfig read, then every file it extends: a change to any of them can change the mapping. */
15
+ configFiles: string[];
16
+ /** What the substitutions resolve against: the effective `baseUrl`, else the directory of the tsconfig that defines `paths`. */
17
+ baseDir: string;
18
+ /** The effective `baseUrl`, when the tsconfig sets one: a bare specifier is also tried under it. */
19
+ baseUrl?: string;
20
+ /** The keys without a `*`. */
21
+ exact: Map<string, string[]>;
22
+ /** The keys with one `*`, in the order written. A key with more than one is ignored, as TypeScript ignores it. */
23
+ patterns: PathPattern[];
24
+ }
25
+ /** A file a bare specifier may resolve to, and what mapped it there. */
26
+ export interface PathCandidate {
27
+ file: string;
28
+ /** The `paths` key, or `baseUrl`. */
29
+ via: string;
30
+ }
31
+ /**
32
+ * `tsConfig`'s `paths` as TypeScript reads them (`extends` followed, comments allowed) — `null` when the file does
33
+ * not exist or cannot be parsed. `exact` and `patterns` are both empty when it maps nothing.
34
+ */
35
+ export declare function readWorkspacePaths(tsConfig: string, workspaceRoot: string): WorkspacePaths | null;
36
+ /** Whether `paths` maps anything. */
37
+ export declare function hasPathMappings(paths: WorkspacePaths | null): paths is WorkspacePaths;
38
+ /**
39
+ * The files a bare specifier resolves to under `paths`, in the order TypeScript tries them (module resolution's
40
+ * `tryLoadModuleUsingPaths`, then `tryLoadModuleUsingBaseUrl`): the targets of the key that matches it best — an
41
+ * exact key, else the wildcard with the longest prefix — then the specifier under `baseUrl`. Empty when nothing
42
+ * applies.
43
+ */
44
+ export declare function workspacePathCandidates(paths: WorkspacePaths, specifier: string): PathCandidate[];
45
+ /** The part of enhanced-resolve's request this reads and passes on. */
46
+ interface ResolveRequest {
47
+ request?: string;
48
+ [key: string]: unknown;
49
+ }
50
+ type ResolveCallback = (err?: Error | null, result?: unknown) => void;
51
+ type ResolveStep = (request: ResolveRequest, resolveContext: unknown, callback: ResolveCallback) => void;
52
+ /** The part of an enhanced-resolve hook this taps. */
53
+ interface ResolveHook {
54
+ tapAsync(name: string, step: ResolveStep): void;
55
+ }
56
+ /** The part of enhanced-resolve's `Resolver` this uses. */
57
+ interface PathsResolver {
58
+ ensureHook(name: string): unknown;
59
+ getHook(name: string): ResolveHook;
60
+ doResolve(hook: unknown, request: ResolveRequest, message: string, resolveContext: unknown, callback: ResolveCallback): void;
61
+ }
62
+ /**
63
+ * A resolver plugin (`resolve.plugins`) resolving a bare specifier through {@link WorkspacePaths}.
64
+ *
65
+ * It taps `normal-resolve`: after `resolve.alias` (on `raw-resolve`), so an alias a `webpackConfigPath` override
66
+ * sets still wins, and before the lookup in node_modules, so a mapped specifier beats an installed package of the
67
+ * same name, as in `tsc`. Each candidate re-enters resolution the way an alias does — extensions, `index` files and
68
+ * the watcher's missing-file tracking all apply — and the first that resolves is the answer; when none does, the
69
+ * request goes on to node_modules.
70
+ */
71
+ export declare class WorkspacePathsResolverPlugin {
72
+ readonly paths: WorkspacePaths;
73
+ constructor(paths: WorkspacePaths);
74
+ apply(resolver: PathsResolver): void;
75
+ }
76
+ export {};
@@ -0,0 +1 @@
1
+ "use strict";var x=Object.defineProperty;var f=(s,e)=>x(s,"name",{value:e,configurable:!0});var h=Object.defineProperty,u=f((s,e)=>h(s,"name",{value:e,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.WorkspacePathsResolverPlugin=void 0,exports.readWorkspacePaths=readWorkspacePaths,exports.hasPathMappings=hasPathMappings,exports.workspacePathCandidates=workspacePathCandidates;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),node_module_1=require("node:module"),path=tslib_1.__importStar(require("node:path"));function loadTypeScript(s){try{return(0,node_module_1.createRequire)(path.join(s,"package.json"))("typescript")}catch{return require("typescript")}}f(loadTypeScript,"loadTypeScript"),u(loadTypeScript,"loadTypeScript");function readWorkspacePaths(s,e){const i=path.resolve(e,s);if(!fs.existsSync(i))return null;try{const n=loadTypeScript(e),r=n.readJsonConfigFile(i,o=>n.sys.readFile(o)),a={...n.sys,readDirectory:u(()=>[],"readDirectory")},{options:t}=n.parseJsonSourceFileConfigFileContent(r,a,path.dirname(i),void 0,i),l=new Map,d=[];for(const[o,c]of Object.entries(t.paths??{})){if(!Array.isArray(c))continue;const p=o.indexOf("*");p===-1?l.set(o,c):o.indexOf("*",p+1)===-1&&d.push({key:o,prefix:o.slice(0,p),suffix:o.slice(p+1),targets:c})}return{configFiles:[i,...(r.extendedSourceFiles??[]).map(o=>path.resolve(o))],baseDir:t.baseUrl??t.pathsBasePath??path.dirname(i),...t.baseUrl?{baseUrl:t.baseUrl}:{},exact:l,patterns:d}}catch{return null}}f(readWorkspacePaths,"readWorkspacePaths"),u(readWorkspacePaths,"readWorkspacePaths");function hasPathMappings(s){return!!s&&(s.exact.size>0||s.patterns.length>0)}f(hasPathMappings,"hasPathMappings"),u(hasPathMappings,"hasPathMappings");function workspacePathCandidates(s,e){const i=[],n=s.exact.get(e);if(n)for(const r of n)i.push({file:path.resolve(s.baseDir,r),via:e});else{let r;for(const a of s.patterns)e.length<a.prefix.length+a.suffix.length||!e.startsWith(a.prefix)||!e.endsWith(a.suffix)||(!r||a.prefix.length>r.prefix.length)&&(r=a);if(r){const a=e.slice(r.prefix.length,e.length-r.suffix.length);for(const t of r.targets){const l=t.indexOf("*"),d=l===-1?t:t.slice(0,l)+a+t.slice(l+1);i.push({file:path.resolve(s.baseDir,d),via:r.key})}}}return s.baseUrl&&i.push({file:path.resolve(s.baseUrl,e),via:"baseUrl"}),i}f(workspacePathCandidates,"workspacePathCandidates"),u(workspacePathCandidates,"workspacePathCandidates");class WorkspacePathsResolverPlugin{static{f(this,"WorkspacePathsResolverPlugin")}static{u(this,"WorkspacePathsResolverPlugin")}constructor(e){this.paths=e}apply(e){const i=e.ensureHook("internal-resolve");e.getHook("normal-resolve").tapAsync("WorkspacePathsResolverPlugin",(n,r,a)=>{const t=n.request;if(!t||t.startsWith(".")||path.isAbsolute(t))return a();const l=workspacePathCandidates(this.paths,t);let d=0;const o=u(()=>{const c=l[d++];if(!c)return a();e.doResolve(i,{...n,request:c.file,fullySpecified:!1},`aliased with mapping '${c.via}' (tsconfig paths): '${t}' to '${c.file}'`,r,(p,g)=>{if(p)return a(p);if(g)return a(null,g);o()})},"attempt");o()})}}exports.WorkspacePathsResolverPlugin=WorkspacePathsResolverPlugin;