@scriptc/compiler 0.0.0 → 0.0.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 (206) hide show
  1. package/LICENSE +202 -0
  2. package/ambient/package.json +3 -0
  3. package/ambient/scriptc-node-fallback.d.ts +3001 -0
  4. package/ambient/scriptc-overrides.d.ts +95 -0
  5. package/ambient/scriptc.d.ts +74 -0
  6. package/dist/backend/cc.d.ts +169 -0
  7. package/dist/backend/cc.js +897 -0
  8. package/dist/backend/cc.js.map +1 -0
  9. package/dist/backend/emission/emit-async.d.ts +145 -0
  10. package/dist/backend/emission/emit-async.js +996 -0
  11. package/dist/backend/emission/emit-async.js.map +1 -0
  12. package/dist/backend/emission/emit-exprs.d.ts +3 -0
  13. package/dist/backend/emission/emit-exprs.js +5947 -0
  14. package/dist/backend/emission/emit-exprs.js.map +1 -0
  15. package/dist/backend/emission/emit-island.d.ts +45 -0
  16. package/dist/backend/emission/emit-island.js +271 -0
  17. package/dist/backend/emission/emit-island.js.map +1 -0
  18. package/dist/backend/emission/emit-shapes.d.ts +142 -0
  19. package/dist/backend/emission/emit-shapes.js +575 -0
  20. package/dist/backend/emission/emit-shapes.js.map +1 -0
  21. package/dist/backend/emission/emit-stmts.d.ts +78 -0
  22. package/dist/backend/emission/emit-stmts.js +960 -0
  23. package/dist/backend/emission/emit-stmts.js.map +1 -0
  24. package/dist/backend/emission/emit-types.d.ts +65 -0
  25. package/dist/backend/emission/emit-types.js +652 -0
  26. package/dist/backend/emission/emit-types.js.map +1 -0
  27. package/dist/backend/emission/emit-walkers.d.ts +154 -0
  28. package/dist/backend/emission/emit-walkers.js +1587 -0
  29. package/dist/backend/emission/emit-walkers.js.map +1 -0
  30. package/dist/backend/emission/emitter.d.ts +452 -0
  31. package/dist/backend/emission/emitter.js +1249 -0
  32. package/dist/backend/emission/emitter.js.map +1 -0
  33. package/dist/backend/emission/may-throw.d.ts +15 -0
  34. package/dist/backend/emission/may-throw.js +250 -0
  35. package/dist/backend/emission/may-throw.js.map +1 -0
  36. package/dist/backend/llvm/blocks.d.ts +21 -0
  37. package/dist/backend/llvm/blocks.js +68 -0
  38. package/dist/backend/llvm/blocks.js.map +1 -0
  39. package/dist/backend/llvm/classes.d.ts +84 -0
  40. package/dist/backend/llvm/classes.js +438 -0
  41. package/dist/backend/llvm/classes.js.map +1 -0
  42. package/dist/backend/llvm/dyn.d.ts +123 -0
  43. package/dist/backend/llvm/dyn.js +2624 -0
  44. package/dist/backend/llvm/dyn.js.map +1 -0
  45. package/dist/backend/llvm/emitter.d.ts +3 -0
  46. package/dist/backend/llvm/emitter.js +10087 -0
  47. package/dist/backend/llvm/emitter.js.map +1 -0
  48. package/dist/backend/llvm/shapes.d.ts +78 -0
  49. package/dist/backend/llvm/shapes.js +754 -0
  50. package/dist/backend/llvm/shapes.js.map +1 -0
  51. package/dist/backend/llvm/unsupported.d.ts +6 -0
  52. package/dist/backend/llvm/unsupported.js +12 -0
  53. package/dist/backend/llvm/unsupported.js.map +1 -0
  54. package/dist/backend/llvm/walkers.d.ts +58 -0
  55. package/dist/backend/llvm/walkers.js +793 -0
  56. package/dist/backend/llvm/walkers.js.map +1 -0
  57. package/dist/backend/mangle.d.ts +124 -0
  58. package/dist/backend/mangle.js +232 -0
  59. package/dist/backend/mangle.js.map +1 -0
  60. package/dist/coverage/report.d.ts +64 -0
  61. package/dist/coverage/report.js +226 -0
  62. package/dist/coverage/report.js.map +1 -0
  63. package/dist/diagnostics/diagnostic.d.ts +140 -0
  64. package/dist/diagnostics/diagnostic.js +458 -0
  65. package/dist/diagnostics/diagnostic.js.map +1 -0
  66. package/dist/diagnostics/render.d.ts +11 -0
  67. package/dist/diagnostics/render.js +58 -0
  68. package/dist/diagnostics/render.js.map +1 -0
  69. package/dist/frontend/cjs-lexer.d.ts +20 -0
  70. package/dist/frontend/cjs-lexer.js +813 -0
  71. package/dist/frontend/cjs-lexer.js.map +1 -0
  72. package/dist/frontend/lowering/http2-constants.d.ts +1 -0
  73. package/dist/frontend/lowering/http2-constants.js +251 -0
  74. package/dist/frontend/lowering/http2-constants.js.map +1 -0
  75. package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
  76. package/dist/frontend/lowering/lib-boundary.js +143 -0
  77. package/dist/frontend/lowering/lib-boundary.js.map +1 -0
  78. package/dist/frontend/lowering/lower-assert.d.ts +18 -0
  79. package/dist/frontend/lowering/lower-assert.js +1269 -0
  80. package/dist/frontend/lowering/lower-assert.js.map +1 -0
  81. package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
  82. package/dist/frontend/lowering/lower-builtins.js +5249 -0
  83. package/dist/frontend/lowering/lower-builtins.js.map +1 -0
  84. package/dist/frontend/lowering/lower-calls.d.ts +581 -0
  85. package/dist/frontend/lowering/lower-calls.js +6281 -0
  86. package/dist/frontend/lowering/lower-calls.js.map +1 -0
  87. package/dist/frontend/lowering/lower-classes.d.ts +727 -0
  88. package/dist/frontend/lowering/lower-classes.js +4701 -0
  89. package/dist/frontend/lowering/lower-classes.js.map +1 -0
  90. package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
  91. package/dist/frontend/lowering/lower-comptime.js +244 -0
  92. package/dist/frontend/lowering/lower-comptime.js.map +1 -0
  93. package/dist/frontend/lowering/lower-containers.d.ts +483 -0
  94. package/dist/frontend/lowering/lower-containers.js +6281 -0
  95. package/dist/frontend/lowering/lower-containers.js.map +1 -0
  96. package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
  97. package/dist/frontend/lowering/lower-dgram.js +319 -0
  98. package/dist/frontend/lowering/lower-dgram.js.map +1 -0
  99. package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
  100. package/dist/frontend/lowering/lower-emitter.js +688 -0
  101. package/dist/frontend/lowering/lower-emitter.js.map +1 -0
  102. package/dist/frontend/lowering/lower-enums.d.ts +22 -0
  103. package/dist/frontend/lowering/lower-enums.js +235 -0
  104. package/dist/frontend/lowering/lower-enums.js.map +1 -0
  105. package/dist/frontend/lowering/lower-expando.d.ts +35 -0
  106. package/dist/frontend/lowering/lower-expando.js +276 -0
  107. package/dist/frontend/lowering/lower-expando.js.map +1 -0
  108. package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
  109. package/dist/frontend/lowering/lower-exprs.js +8099 -0
  110. package/dist/frontend/lowering/lower-exprs.js.map +1 -0
  111. package/dist/frontend/lowering/lower-generators.d.ts +50 -0
  112. package/dist/frontend/lowering/lower-generators.js +421 -0
  113. package/dist/frontend/lowering/lower-generators.js.map +1 -0
  114. package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
  115. package/dist/frontend/lowering/lower-inspect.js +1069 -0
  116. package/dist/frontend/lowering/lower-inspect.js.map +1 -0
  117. package/dist/frontend/lowering/lower-island.d.ts +108 -0
  118. package/dist/frontend/lowering/lower-island.js +650 -0
  119. package/dist/frontend/lowering/lower-island.js.map +1 -0
  120. package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
  121. package/dist/frontend/lowering/lower-mixins.js +534 -0
  122. package/dist/frontend/lowering/lower-mixins.js.map +1 -0
  123. package/dist/frontend/lowering/lower-modules.d.ts +135 -0
  124. package/dist/frontend/lowering/lower-modules.js +1613 -0
  125. package/dist/frontend/lowering/lower-modules.js.map +1 -0
  126. package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
  127. package/dist/frontend/lowering/lower-namespaces.js +706 -0
  128. package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
  129. package/dist/frontend/lowering/lower-server.d.ts +90 -0
  130. package/dist/frontend/lowering/lower-server.js +3471 -0
  131. package/dist/frontend/lowering/lower-server.js.map +1 -0
  132. package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
  133. package/dist/frontend/lowering/lower-stmts.js +5341 -0
  134. package/dist/frontend/lowering/lower-stmts.js.map +1 -0
  135. package/dist/frontend/lowering/lower-stream.d.ts +88 -0
  136. package/dist/frontend/lowering/lower-stream.js +1533 -0
  137. package/dist/frontend/lowering/lower-stream.js.map +1 -0
  138. package/dist/frontend/lowering/lower-test.d.ts +27 -0
  139. package/dist/frontend/lowering/lower-test.js +353 -0
  140. package/dist/frontend/lowering/lower-test.js.map +1 -0
  141. package/dist/frontend/lowering/lowerer.d.ts +1748 -0
  142. package/dist/frontend/lowering/lowerer.js +6256 -0
  143. package/dist/frontend/lowering/lowerer.js.map +1 -0
  144. package/dist/frontend/lowering/surfaces.d.ts +272 -0
  145. package/dist/frontend/lowering/surfaces.js +1096 -0
  146. package/dist/frontend/lowering/surfaces.js.map +1 -0
  147. package/dist/frontend/npm-static.d.ts +55 -0
  148. package/dist/frontend/npm-static.js +300 -0
  149. package/dist/frontend/npm-static.js.map +1 -0
  150. package/dist/frontend/npm.d.ts +330 -0
  151. package/dist/frontend/npm.js +1259 -0
  152. package/dist/frontend/npm.js.map +1 -0
  153. package/dist/frontend/program.d.ts +186 -0
  154. package/dist/frontend/program.js +2172 -0
  155. package/dist/frontend/program.js.map +1 -0
  156. package/dist/frontend/provenance-registry.d.ts +48 -0
  157. package/dist/frontend/provenance-registry.js +87 -0
  158. package/dist/frontend/provenance-registry.js.map +1 -0
  159. package/dist/frontend/provenance.d.ts +6 -0
  160. package/dist/frontend/provenance.js +459 -0
  161. package/dist/frontend/provenance.js.map +1 -0
  162. package/dist/frontend/resolve.d.ts +56 -0
  163. package/dist/frontend/resolve.js +682 -0
  164. package/dist/frontend/resolve.js.map +1 -0
  165. package/dist/frontend/shared.d.ts +60 -0
  166. package/dist/frontend/shared.js +232 -0
  167. package/dist/frontend/shared.js.map +1 -0
  168. package/dist/frontend/ts7/adapter.d.ts +7 -0
  169. package/dist/frontend/ts7/adapter.js +54 -0
  170. package/dist/frontend/ts7/adapter.js.map +1 -0
  171. package/dist/frontend/ts7/ast.d.ts +50 -0
  172. package/dist/frontend/ts7/ast.js +211 -0
  173. package/dist/frontend/ts7/ast.js.map +1 -0
  174. package/dist/frontend/ts7/census-check.d.ts +1 -0
  175. package/dist/frontend/ts7/census-check.js +12 -0
  176. package/dist/frontend/ts7/census-check.js.map +1 -0
  177. package/dist/frontend/ts7/checker.d.ts +140 -0
  178. package/dist/frontend/ts7/checker.js +544 -0
  179. package/dist/frontend/ts7/checker.js.map +1 -0
  180. package/dist/frontend/ts7/enums.d.ts +18 -0
  181. package/dist/frontend/ts7/enums.js +43 -0
  182. package/dist/frontend/ts7/enums.js.map +1 -0
  183. package/dist/frontend/ts7/program.d.ts +99 -0
  184. package/dist/frontend/ts7/program.js +278 -0
  185. package/dist/frontend/ts7/program.js.map +1 -0
  186. package/dist/frontend/ts7/world-check.d.ts +3 -0
  187. package/dist/frontend/ts7/world-check.js +34 -0
  188. package/dist/frontend/ts7/world-check.js.map +1 -0
  189. package/dist/frontend/types.d.ts +205 -0
  190. package/dist/frontend/types.js +2485 -0
  191. package/dist/frontend/types.js.map +1 -0
  192. package/dist/index.d.ts +85 -0
  193. package/dist/index.js +424 -0
  194. package/dist/index.js.map +1 -0
  195. package/dist/ir/nodes.d.ts +4334 -0
  196. package/dist/ir/nodes.js +1906 -0
  197. package/dist/ir/nodes.js.map +1 -0
  198. package/dist/ir/serialize.d.ts +4 -0
  199. package/dist/ir/serialize.js +34 -0
  200. package/dist/ir/serialize.js.map +1 -0
  201. package/dist/ir/validate.d.ts +36 -0
  202. package/dist/ir/validate.js +4884 -0
  203. package/dist/ir/validate.js.map +1 -0
  204. package/package.json +30 -6
  205. package/README.md +0 -3
  206. package/index.js +0 -2
@@ -0,0 +1,1259 @@
1
+ /* The npm RUNTIME graph: what --dynamic builds embed.
2
+ *
3
+ * Types come from the package's shipped .d.ts (program.ts resolves those
4
+ * through TypeScript itself); the CODE that runs is the package's shipped
5
+ * JS, resolved HERE at build time with Node's own resolution algorithm —
6
+ * for every (importer, specifier) pair, node_modules directories are
7
+ * probed from the importing FILE upward to the filesystem root, symlinks
8
+ * are resolved with realpath (pnpm/bun virtual stores are symlink farms —
9
+ * a package's dependencies live next to its REAL location, not its link),
10
+ * package.json "exports" maps are honored (import/require conditions,
11
+ * subpath patterns with '*'), with main/module fallbacks and extension/
12
+ * directory-index resolution for the rest. The internal module graph
13
+ * (the package's own relative imports/requires plus its dependencies')
14
+ * is scanned with a real TypeScript parse collecting every module edge —
15
+ * import/export declarations (including `export * as ns from`, which
16
+ * ts.preProcessFile silently drops), dynamic import() of string literals,
17
+ * and CommonJS require() calls — never a regex.
18
+ *
19
+ * Every reached module's SOURCE is embedded into the emitted C as a static
20
+ * string keyed by its REALPATH — one physical module embeds once, however
21
+ * many symlink aliases reach it; scr_island.c serves the engine's module
22
+ * loader (and a CommonJS require shim) from that map, so binaries never
23
+ * read node_modules at runtime. Each CommonJS module additionally embeds a
24
+ * synthesized ESM FACADE — default plus the named exports lexed from its
25
+ * source with the compiler's quirk-faithful port of Node's vendored CJS
26
+ * lexer (cjs-lexer.ts, the merve port program.ts also answers with) — so
27
+ * ES modules inside the graph named-import CJS files exactly like under
28
+ * Node, including the failures: a name Node's lexer cannot see is absent
29
+ * from the facade and the embedded import fails at engine link time just
30
+ * as Node's instantiate would.
31
+ * Node builtins are NOT embedded: the island
32
+ * ships shims for the set CLIs actually drive — the pure-JS tier (util,
33
+ * assert, buffer, string_decoder, querystring, punycode, console, timers,
34
+ * readline, async_hooks' two classes, events, module, tty) and the host
35
+ * bridges riding the same C implementations the static lowerings use
36
+ * (fs + fs/promises, path both families, url's WHATWG parser and file
37
+ * converters, crypto's hashing/random slice, zlib, process, os) — the
38
+ * worker_threads tier stays unshimmed (SHIMMED_BUILTINS is
39
+ * the source of truth).
40
+ *
41
+ * EDGE-KIND SEMANTICS match Node's per kind, TRANSITIVELY. A STATIC
42
+ * import/export edge in an eagerly-reached module is EAGER — Node links the
43
+ * whole static graph before anything evaluates — so a static edge that
44
+ * reaches an unshimmed builtin or does not resolve is a build-time
45
+ * diagnostic naming the builtin/specifier, the requiring package, and the
46
+ * dependency chain that reached it. A require() or dynamic import() edge is
47
+ * LAZY — Node fails require at the CALL and import() at evaluation — and so
48
+ * is EVERY edge of a module only reachable through lazy edges: Node links
49
+ * that whole subgraph at the boundary call (the vercel CLI's vc.js shim
50
+ * import()s its real CLI, so the CLI graph's own static imports fail at
51
+ * that import(), never sooner). A blocked lazy edge embeds a runtime trap
52
+ * instead of failing the build:
53
+ * an unshimmed builtin keeps the island's own lazy throw (the edge embeds
54
+ * pointing at the "node:x" key the shim table refuses at the call), an
55
+ * unresolvable require() gets Node's MODULE_NOT_FOUND shape from the
56
+ * island's require shim (message carrying the live Require stack, code,
57
+ * requireStack — NO edge embeds; the shim owns the throw), and an
58
+ * unresolvable import() embeds a throwing ESM stub with Node's
59
+ * ERR_MODULE_NOT_FOUND shape (the addDynamicImport
60
+ * ERR_UNKNOWN_FILE_EXTENSION stub's pattern). Blocked-but-lazy edges stay in
61
+ * the build-time inventory (builtins/lazyTraps) so the coverage report says
62
+ * exactly what the binary cannot reach. esbuild-published bundles route
63
+ * external requires through a `__require` helper; its literal call sites
64
+ * collect as require edges too, so esbuild dists get the same honest
65
+ * build-time inventory. A specifier that RESOLVES to a .node native addon
66
+ * embeds a throwing ERR_DLOPEN_FAILED stub instead of the addon's machine
67
+ * code (the island has no process.dlopen; napi binding packages require()
68
+ * their .node lazily next to a fallback, so the stub is the "addon
69
+ * missing" path they already handle) — those keep an inventory row too
70
+ * (lazyTraps, native: true).
71
+ *
72
+ * CONDITION SETS come from the CALL FORM, never the importer's format —
73
+ * Node resolves import/export declarations and import() with
74
+ * ["node","import"] and require() with ["node","require"] — so a DUAL
75
+ * package (smol-toml's shape: "exports" whose "import" and "require"
76
+ * conditions name different files) embeds BOTH entries, each behind its
77
+ * own kind-tagged edge; the island's module loader looks edges up with
78
+ * the import kind, its require shim with the require kind (the dual-
79
+ * package hazard included: the two entries are distinct module
80
+ * instances, exactly Node). And a require edge ATTRIBUTES to the module
81
+ * whose scope defines the require function being called: esbuild's
82
+ * `__require` lives in a shared chunk whose banner createRequire it
83
+ * closed over, so a call site in ANOTHER chunk resolves — at build time
84
+ * here and at runtime in Node — from the DEFINING chunk, and the edge
85
+ * lives under its key (requireHelperOriginOf follows the import and
86
+ * re-export hops).
87
+ */
88
+ import { readFileSync, realpathSync } from "node:fs";
89
+ import { dirname, extname, join, resolve } from "node:path";
90
+ import ts from "typescript5";
91
+ import { cjsLexedExportsOf } from "./cjs-lexer.js";
92
+ /* Node synthesizes named exports for CommonJS modules imported from ESM by
93
+ * LEXING the CJS source at link time: `exports.x =` / `module.exports.x =`
94
+ * assignments, `module.exports = {…}` object literals including esbuild's
95
+ * dead `0 && (module.exports = {…})` annotation,
96
+ * `Object.defineProperty(exports, "x", …)`, `module.exports = require(…)`
97
+ * re-export forwarding, and the tsc/babel star-copy patterns. scriptc
98
+ * holds every embedded CJS source at BUILD time, so the same detection
99
+ * runs here through cjs-lexer.ts — the compiler's probed port of merve,
100
+ * Node v24.15.0's vendored lexer. The npm package cjs-module-lexer is NOT
101
+ * that lexer anymore and disagrees with it (a phantom "get" export for
102
+ * `{ get a() {…} }` tables, missed `k: require(…)` value reexports), so
103
+ * the port is the single source of truth for program CJS files and
104
+ * embedded npm modules alike. */
105
+ /** The ESM facade for an embedded CommonJS module, matching Node's interop
106
+ * exactly (verified against Node 24): `default` IS module.exports;
107
+ * "module.exports" is a named alias for it (Node ships that export name);
108
+ * every lexed name binds the exports object's OWN property when one exists
109
+ * at facade evaluation (getters read once — a snapshot, like Node's
110
+ * loop over the lexed set) and stays undefined otherwise (a lexed-but-
111
+ * never-assigned name is a live binding of undefined, not an error;
112
+ * inherited properties do NOT leak through, hence the hasOwnProperty
113
+ * guard). Export names that aren't identifiers ("dash-name") use string
114
+ * ModuleExportNames — every name does, uniformly; the locals are e0, e1, …
115
+ * temps. */
116
+ export function cjsEsmFacadeSource(key, names) {
117
+ const uniq = [...new Set(names)].filter((n) => n !== "default" && n !== "module.exports");
118
+ const parts = [
119
+ `const m=globalThis.__scr_require(${JSON.stringify(key)});`,
120
+ `export default m;`,
121
+ ];
122
+ if (uniq.length > 0)
123
+ parts.push(`const H=Object.prototype.hasOwnProperty;`);
124
+ const bound = uniq.map((n, i) => {
125
+ const name = JSON.stringify(n);
126
+ parts.push(`let e${i};if(H.call(m,${name}))e${i}=m[${name}];`);
127
+ return `,e${i} as ${name}`;
128
+ });
129
+ parts.push(`export{m as "module.exports"${bound.join("")}};`);
130
+ return parts.join("\n");
131
+ }
132
+ /** Builtins the island provides shims for (scr_island.c). Both spellings
133
+ * resolve to the canonical "node:" key. Subpath forms ("fs/promises") are
134
+ * NOT shimmed unless listed here explicitly. */
135
+ const SHIMMED_BUILTINS = new Set([
136
+ "events", "path", "process", "fs", "child_process", "os",
137
+ "diagnostics_channel", "module", "url", "tty", "util", "util/types",
138
+ "buffer", "string_decoder", "crypto", "stream", "stream/promises",
139
+ "stream/consumers", "stream/web", "fs/promises", "async_hooks",
140
+ "readline", "path/posix", "path/win32", "assert", "assert/strict",
141
+ "punycode", "querystring", "constants", "console", "timers",
142
+ "timers/promises", "zlib",
143
+ // The deprecated legacy module, kept loadable because @sentry/node (the
144
+ // vercel CLI's crash reporter) requires it at load on every path while
145
+ // only driving it on Node < 14.
146
+ "domain",
147
+ // Loadable with Node's surface, answers fenced AT THE CALL (through the
148
+ // callback/promise, dns's error channel): proxy-agent's pac-resolver
149
+ // requires dns whenever proxy env vars exist and only calls lookup when
150
+ // a PAC proxy resolves.
151
+ "dns",
152
+ // The main-thread worker_threads surface (real in-process MessageChannel
153
+ // ports, Worker fences at construction) and perf_hooks' performance —
154
+ // undici requires both UNGUARDED at load.
155
+ "worker_threads", "perf_hooks",
156
+ // The socket tier (scr_net_island.c + the bootstrap's shims): http/https
157
+ // are working CLIENTS over the socket units; net/tls load and fence
158
+ // their socket surfaces loudly at the call (isIP and friends are real).
159
+ "http", "https", "net", "tls",
160
+ // Loadable with Node's surface: startupSnapshot answers for real (never
161
+ // a snapshot build; the mutators throw Node's ERR_NOT_BUILDING_SNAPSHOT),
162
+ // heap statistics are inert-but-typed, and the V8 serialization format
163
+ // fences at the call. Prettier's bundled error helpers call
164
+ // startupSnapshot.isBuildingSnapshot() on every CLI start.
165
+ "v8",
166
+ ]);
167
+ /** Node builtins importable WITHOUT the "node:" prefix — used to tell
168
+ * "missing builtin shim" apart from "missing package" for bare specifiers.
169
+ * ("node:"-prefixed specifiers are always builtins: the prefix cannot name
170
+ * an npm package.) */
171
+ const KNOWN_BUILTINS = new Set([
172
+ ...SHIMMED_BUILTINS,
173
+ "assert", "async_hooks", "buffer", "cluster", "console", "constants",
174
+ "crypto", "dgram", "diagnostics_channel", "dns", "domain", "http",
175
+ "https", "http2", "inspector", "module", "net", "os", "perf_hooks",
176
+ "punycode", "querystring", "readline", "repl", "stream",
177
+ "string_decoder", "sys", "timers", "tls", "trace_events", "tty", "url",
178
+ "util", "v8", "vm", "wasi", "worker_threads", "zlib",
179
+ ]);
180
+ /** Every module specifier `source` can load at runtime, in encounter
181
+ * order with its call-site kinds merged per specifier: import/export
182
+ * declarations (INCLUDING `export * as ns from "x"`, which
183
+ * ts.preProcessFile silently drops — zod v4 re-exports its util namespace
184
+ * that way), dynamic import("literal"), and require("literal") /
185
+ * __require("literal") (esbuild's external-require helper — collecting its
186
+ * literal call sites gives bundled dists an honest build-time inventory).
187
+ * A real parse, never a regex. */
188
+ function moduleSpecifiersOf(source, fileName) {
189
+ const sf = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, false, ts.ScriptKind.JS);
190
+ const uses = [];
191
+ const bySpec = new Map();
192
+ let requireHelperImport = null;
193
+ let requireHelperReexport = null;
194
+ /** Uses with `__require(…)` sites — attributed local vs helper AFTER the
195
+ * walk, once the (hoisted) import declarations have all been seen. */
196
+ const viaHelperIdent = new Set();
197
+ const push = (spec, kind) => {
198
+ let use = bySpec.get(spec);
199
+ if (!use) {
200
+ use = {
201
+ specifier: spec,
202
+ static: false,
203
+ require: false,
204
+ requireLocal: false,
205
+ requireViaHelper: false,
206
+ dynamicImport: false,
207
+ };
208
+ bySpec.set(spec, use);
209
+ uses.push(use);
210
+ }
211
+ if (kind !== null)
212
+ use[kind] = true;
213
+ return use;
214
+ };
215
+ const visit = (n) => {
216
+ if ((ts.isImportDeclaration(n) || ts.isExportDeclaration(n)) &&
217
+ n.moduleSpecifier !== undefined &&
218
+ ts.isStringLiteral(n.moduleSpecifier)) {
219
+ push(n.moduleSpecifier.text, "static");
220
+ if (ts.isImportDeclaration(n) &&
221
+ n.importClause?.namedBindings !== undefined &&
222
+ ts.isNamedImports(n.importClause.namedBindings) &&
223
+ n.importClause.namedBindings.elements.some((el) => el.name.text === "__require")) {
224
+ requireHelperImport = n.moduleSpecifier.text;
225
+ }
226
+ if (ts.isExportDeclaration(n) &&
227
+ n.exportClause !== undefined &&
228
+ ts.isNamedExports(n.exportClause) &&
229
+ n.exportClause.elements.some((el) => (el.propertyName ?? el.name).text === "__require")) {
230
+ requireHelperReexport = n.moduleSpecifier.text;
231
+ }
232
+ }
233
+ else if (ts.isCallExpression(n)) {
234
+ const arg = n.arguments[0];
235
+ if (n.expression.kind === ts.SyntaxKind.ImportKeyword &&
236
+ arg !== undefined &&
237
+ ts.isStringLiteralLike(arg)) {
238
+ push(arg.text, "dynamicImport");
239
+ }
240
+ else if (ts.isIdentifier(n.expression) &&
241
+ (n.expression.text === "require" || n.expression.text === "__require") &&
242
+ n.arguments.length === 1 &&
243
+ arg !== undefined &&
244
+ ts.isStringLiteralLike(arg)) {
245
+ if (n.expression.text === "__require") {
246
+ // local vs imported-helper attribution is decided after the walk
247
+ viaHelperIdent.add(push(arg.text, null));
248
+ }
249
+ else {
250
+ push(arg.text, "requireLocal");
251
+ }
252
+ }
253
+ }
254
+ ts.forEachChild(n, visit);
255
+ };
256
+ visit(sf);
257
+ for (const use of viaHelperIdent) {
258
+ if (requireHelperImport !== null)
259
+ use.requireViaHelper = true;
260
+ else
261
+ use.requireLocal = true;
262
+ }
263
+ for (const use of uses)
264
+ use.require = use.requireLocal || use.requireViaHelper;
265
+ return { uses, requireHelperImport, requireHelperReexport };
266
+ }
267
+ function builtinKeyOf(specifier) {
268
+ if (specifier.startsWith("node:"))
269
+ return specifier; // forced builtin
270
+ const root = specifier.split("/")[0]; // fs/promises → fs
271
+ if (!KNOWN_BUILTINS.has(root))
272
+ return null;
273
+ return `node:${specifier}`;
274
+ }
275
+ const realHost = {
276
+ readFile: (path) => {
277
+ try {
278
+ return readFileSync(path, "utf8");
279
+ }
280
+ catch {
281
+ return null;
282
+ }
283
+ },
284
+ isFile: (path) => ts.sys.fileExists(path),
285
+ isDirectory: (path) => ts.sys.directoryExists(path),
286
+ realpath: (path) => {
287
+ try {
288
+ return realpathSync(path);
289
+ }
290
+ catch {
291
+ return path;
292
+ }
293
+ },
294
+ };
295
+ /** Package name from a specifier ("@scope/pkg/sub" → "@scope/pkg"). */
296
+ function packageNameOf(specifier) {
297
+ const parts = specifier.split("/");
298
+ return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
299
+ }
300
+ /** Package name from a PATH into node_modules ("…/node_modules/@s/p/d/x.js"
301
+ * → "@s/p"), for diagnostics/chains when the entry is a file, not a bare
302
+ * specifier. Null when the path holds no node_modules segment. */
303
+ export function packageNameOfPath(path) {
304
+ const norm = path.split("\\").join("/");
305
+ const idx = norm.lastIndexOf("/node_modules/");
306
+ if (idx === -1)
307
+ return null;
308
+ const rest = norm.slice(idx + "/node_modules/".length);
309
+ return rest === "" ? null : packageNameOf(rest);
310
+ }
311
+ /** package.json "exports" resolution, per Node: subpath map with exact
312
+ * keys first, then '*' patterns (longest literal prefix wins, the matched
313
+ * text substitutes into the target), condition objects matched in OBJECT
314
+ * KEY order against the enabled set ("import" or "require", plus "node"
315
+ * and "default"), string targets, arrays taking the first resolvable
316
+ * entry. Returns a path relative to the package dir, or null. */
317
+ export function resolveExports(exports, subpath, mode) {
318
+ const enabled = new Set([mode, "node", "default"]);
319
+ const resolveTarget = (target, wildcard) => {
320
+ if (typeof target === "string") {
321
+ return wildcard === null ? target : target.split("*").join(wildcard);
322
+ }
323
+ if (Array.isArray(target)) {
324
+ for (const t of target) {
325
+ const r = resolveTarget(t, wildcard);
326
+ if (r)
327
+ return r;
328
+ }
329
+ return null;
330
+ }
331
+ if (target && typeof target === "object") {
332
+ for (const [key, value] of Object.entries(target)) {
333
+ if (key.startsWith("."))
334
+ continue; // a subpath map, not conditions
335
+ if (enabled.has(key)) {
336
+ const r = resolveTarget(value, wildcard);
337
+ if (r)
338
+ return r;
339
+ }
340
+ }
341
+ }
342
+ return null;
343
+ };
344
+ if (typeof exports === "string" || Array.isArray(exports)) {
345
+ return subpath === "." ? resolveTarget(exports, null) : null;
346
+ }
347
+ if (exports && typeof exports === "object") {
348
+ const map = exports;
349
+ const keys = Object.keys(map);
350
+ const isSubpathMap = keys.every((k) => k.startsWith("."));
351
+ if (!isSubpathMap) {
352
+ // A bare condition object applies to the root subpath only.
353
+ return subpath === "." ? resolveTarget(exports, null) : null;
354
+ }
355
+ if (Object.prototype.hasOwnProperty.call(map, subpath)) {
356
+ return resolveTarget(map[subpath], null);
357
+ }
358
+ // Pattern keys: "./v4/locales/*" etc. Longest literal prefix wins.
359
+ let best = null;
360
+ for (const key of keys) {
361
+ const star = key.indexOf("*");
362
+ if (star < 0)
363
+ continue;
364
+ const prefix = key.slice(0, star);
365
+ const suffix = key.slice(star + 1);
366
+ if (subpath.startsWith(prefix) &&
367
+ subpath.length >= prefix.length + suffix.length &&
368
+ subpath.endsWith(suffix) &&
369
+ (!best || prefix.length > best.prefix.length)) {
370
+ best = { key, prefix, suffix };
371
+ }
372
+ }
373
+ if (!best)
374
+ return null;
375
+ const wildcard = subpath.slice(best.prefix.length, subpath.length - best.suffix.length);
376
+ return resolveTarget(map[best.key], wildcard);
377
+ }
378
+ return null;
379
+ }
380
+ export function probeNodeImportRefusal(fromFile, specifier, host = realHost) {
381
+ if (specifier.startsWith("#") || specifier.startsWith("node:"))
382
+ return null;
383
+ const name = packageNameOf(specifier);
384
+ const parts = specifier.split("/");
385
+ const subparts = specifier.startsWith("@") ? parts.slice(2) : parts.slice(1);
386
+ const subpath = subparts.length > 0 ? `./${subparts.join("/")}` : ".";
387
+ const importer = resolve(fromFile);
388
+ const pkgJsonAt = (dir) => {
389
+ const text = host.readFile(join(dir, "package.json"));
390
+ if (text === null)
391
+ return null;
392
+ try {
393
+ return JSON.parse(text);
394
+ }
395
+ catch {
396
+ return null;
397
+ }
398
+ };
399
+ // The final answer within one package scope (self or node_modules):
400
+ // Node's PACKAGE_EXPORTS_RESOLVE outcome for the subpath.
401
+ const withinScope = (pkgDir, pkg) => {
402
+ if (pkg.exports === undefined)
403
+ return null; // legacy resolution — conservative
404
+ const target = resolveExports(pkg.exports, subpath, "import");
405
+ if (target === null) {
406
+ return {
407
+ code: "ERR_PACKAGE_PATH_NOT_EXPORTED",
408
+ message: subpath === "."
409
+ ? `No "exports" main defined in ${join(pkgDir, "package.json")} imported from ${importer}`
410
+ : `Package subpath '${subpath}' is not defined by "exports" in ${join(pkgDir, "package.json")} imported from ${importer}`,
411
+ };
412
+ }
413
+ const file = join(pkgDir, target);
414
+ if (host.isFile(file))
415
+ return null;
416
+ if (host.isDirectory(file))
417
+ return null; // ERR_UNSUPPORTED_DIR_IMPORT territory — conservative
418
+ return {
419
+ code: "ERR_MODULE_NOT_FOUND",
420
+ message: `Cannot find module '${file}' imported from ${importer}`,
421
+ url: `file://${file}`,
422
+ };
423
+ };
424
+ // Node's PACKAGE_SELF_RESOLVE first: the nearest package scope above the
425
+ // importer whose "name" matches AND that carries "exports" answers —
426
+ // finally (its refusals do NOT fall back to node_modules).
427
+ for (let dir = dirname(importer);;) {
428
+ const pkg = pkgJsonAt(dir);
429
+ if (pkg) {
430
+ if (pkg.name === name && pkg.exports !== undefined)
431
+ return withinScope(dir, pkg);
432
+ break;
433
+ }
434
+ const parent = dirname(dir);
435
+ if (parent === dir)
436
+ break;
437
+ dir = parent;
438
+ }
439
+ for (let dir = dirname(importer);;) {
440
+ const linkDir = join(dir, "node_modules", name);
441
+ if (host.isDirectory(linkDir)) {
442
+ const pkgDir = host.realpath(linkDir);
443
+ const pkg = pkgJsonAt(pkgDir);
444
+ if (pkg === null)
445
+ return null; // unreadable manifest — conservative
446
+ return withinScope(pkgDir, pkg);
447
+ }
448
+ const parent = dirname(dir);
449
+ if (parent === dir) {
450
+ return {
451
+ code: "ERR_MODULE_NOT_FOUND",
452
+ message: `Cannot find package '${name}' imported from ${importer}`,
453
+ };
454
+ }
455
+ dir = parent;
456
+ }
457
+ }
458
+ /** A bare-specifier `require()` edge Node's CJS resolution REFUSES because
459
+ * NOTHING INSTALLED resolves it: no node_modules/<name> from the requiring
460
+ * file up to the filesystem root, and no self-scope whose "name" matches.
461
+ * Answers Node's exact require-site Error message (MODULE_NOT_FOUND's
462
+ * "Cannot find module '<spec>'" with the one-entry require stack — the
463
+ * message property really carries both lines). Everything else — installed
464
+ * packages (whatever their exports say), builtins, relative and "#"
465
+ * specifiers — answers null: the callers' existing stories stand. Unlike
466
+ * the ESM startup-crash channel (SEMANTICS.md 287), this refusal is a
467
+ * RUNTIME throw at the require site, catchable — the optional-dependency
468
+ * try/require pattern depends on exactly that. */
469
+ export function probeNodeRequireRefusal(fromFile, specifier, host = realHost) {
470
+ if (specifier.startsWith("./") || specifier.startsWith("../") ||
471
+ specifier.startsWith("/") || specifier.startsWith("#") ||
472
+ specifier.startsWith("node:")) {
473
+ return null;
474
+ }
475
+ const name = packageNameOf(specifier);
476
+ const importer = resolve(fromFile);
477
+ for (let dir = dirname(importer);;) {
478
+ // A self-scope whose "name" matches: Node's require honors package
479
+ // self-reference — conservative null (resolvable or its own story).
480
+ const pkgText = host.readFile(join(dir, "package.json"));
481
+ if (pkgText !== null) {
482
+ try {
483
+ if (JSON.parse(pkgText).name === name)
484
+ return null;
485
+ }
486
+ catch {
487
+ return null; // unreadable manifest — conservative
488
+ }
489
+ }
490
+ if (host.isDirectory(join(dir, "node_modules", name)))
491
+ return null;
492
+ const parent = dirname(dir);
493
+ if (parent === dir)
494
+ break;
495
+ dir = parent;
496
+ }
497
+ return {
498
+ message: `Cannot find module '${specifier}'\nRequire stack:\n- ${importer}`,
499
+ };
500
+ }
501
+ export class NpmGraphBuilder {
502
+ host;
503
+ modules = new Map();
504
+ edges = [];
505
+ edgeSeen = new Set();
506
+ entries = new Map(); // fromDir\0spec → key
507
+ errors = [];
508
+ pkgJsonCache = new Map();
509
+ /** Modules whose format the RESOLUTION decided (the "module" field names
510
+ * ESM sources regardless of the package's "type"). */
511
+ formatOverrides = new Map();
512
+ /** Canonical "node:x" → packages whose code imports it. */
513
+ builtinsSeen = new Map();
514
+ /** Canonical "node:x" keys some STATIC import reaches — those keep the
515
+ * eager build error, so their builtins never mark lazy. */
516
+ builtinsEager = new Set();
517
+ /** Unresolvable (or native-addon) lazily-reached specifiers → merged
518
+ * call forms/packages. */
519
+ lazyTrapsSeen = new Map();
520
+ /** Synthetic keys for embedded import()-not-found stubs. */
521
+ importTrapCount = 0;
522
+ /** moduleSpecifiersOf per module key — sweepEdges parses each module
523
+ * once; the `__require` attribution chain re-reads helper chunks
524
+ * through this cache. */
525
+ specifiersCache = new Map();
526
+ constructor(host = realHost) {
527
+ this.host = host;
528
+ }
529
+ /** Registers one user-level npm import and walks everything it reaches.
530
+ * Idempotent per (importing dir, specifier). */
531
+ addImport(fromFile, specifier) {
532
+ this.resolveEntry(fromFile, specifier, false);
533
+ }
534
+ /** Registers one user-level RELATIVE import whose target lives inside
535
+ * node_modules — the e2e driver shape (`import "../node_modules/vercel/
536
+ * dist/index.js"`): tsc resolves the FILE into the program, but shipped
537
+ * package code has exactly one execution home, the embedded engine.
538
+ * Resolves with Node's file rules, embeds the reached graph EAGERLY (a
539
+ * static import links at the root), and answers the runtime entry key.
540
+ * Idempotent per (importing dir, specifier). */
541
+ addFileImport(fromFile, specifier) {
542
+ const cacheKey = this.entryKeyFor(fromFile, specifier);
543
+ const cached = this.entries.get(cacheKey);
544
+ if (cached !== undefined) {
545
+ if (cached !== "")
546
+ this.walk(cached, [packageNameOfPath(cached) ?? cached], false);
547
+ return cached === "" ? null : cached;
548
+ }
549
+ const abs = resolve(dirname(resolve(fromFile)), specifier);
550
+ const file = this.resolveFile(abs);
551
+ if (file === null) {
552
+ this.errors.push({ message: `cannot resolve '${specifier}' from ${fromFile}` });
553
+ this.entries.set(cacheKey, "");
554
+ return null;
555
+ }
556
+ const key = this.host.realpath(file);
557
+ this.entries.set(cacheKey, key);
558
+ this.walk(key, [packageNameOfPath(key) ?? key], false);
559
+ return key;
560
+ }
561
+ /** Registers one user-level dynamic `import(specifier)` and embeds what
562
+ * it reaches, returning the build-time classification the lowering acts
563
+ * on. Bare package specifiers take the same path as static imports;
564
+ * builtins map to their island shim keys ("fs" → "node:fs") or report
565
+ * the missing shim; RELATIVE specifiers — the shape static imports never
566
+ * see, because tsc compiles those — resolve against the importing FILE
567
+ * with Node's file/extension/directory rules and embed the reached JS
568
+ * graph. A relative target that is TypeScript is the program's own
569
+ * module (fenced at the site: static imports are the way in), and one
570
+ * with an extension no loader executes (.wasm, .node, ...) embeds a stub
571
+ * whose EVALUATION throws Node's exact ERR_UNKNOWN_FILE_EXTENSION
572
+ * TypeError — the build succeeds and the import() rejects at runtime,
573
+ * which is precisely what Node does. */
574
+ addDynamicImport(fromFile, specifier) {
575
+ if (specifier.startsWith("./") || specifier.startsWith("../") || specifier.startsWith("/")) {
576
+ const abs = specifier.startsWith("/")
577
+ ? specifier
578
+ : resolve(dirname(resolve(fromFile)), specifier);
579
+ const file = this.resolveFile(abs);
580
+ if (!file) {
581
+ return {
582
+ kind: "unresolved",
583
+ message: `cannot resolve '${specifier}' from ${fromFile}`,
584
+ };
585
+ }
586
+ const key = this.host.realpath(file);
587
+ if (/\.(ts|tsx|mts|cts)$/.test(key))
588
+ return { kind: "program-module" };
589
+ const ext = extname(key);
590
+ if (ext !== ".js" && ext !== ".mjs" && ext !== ".cjs" && ext !== ".json") {
591
+ if (!this.modules.has(key)) {
592
+ const msg = `Unknown file extension "${ext}" for ${key}`;
593
+ this.modules.set(key, {
594
+ key,
595
+ source: `throw Object.assign(new TypeError(${JSON.stringify(msg)}),` +
596
+ `{code:"ERR_UNKNOWN_FILE_EXTENSION"});\n`,
597
+ format: "esm",
598
+ });
599
+ }
600
+ return { kind: "module", key };
601
+ }
602
+ // The reached subgraph sits behind THIS import() — a lazy boundary:
603
+ // blocked edges inside it (static ones included) trap at runtime,
604
+ // where Node fails the import()'s link.
605
+ this.walk(key, [specifier], true);
606
+ return { kind: "module", key };
607
+ }
608
+ const builtin = builtinKeyOf(specifier);
609
+ if (builtin) {
610
+ if (!SHIMMED_BUILTINS.has(builtin.slice(5))) {
611
+ return { kind: "unsupported-builtin", builtin };
612
+ }
613
+ return { kind: "module", key: builtin };
614
+ }
615
+ const errorsBefore = this.errors.length;
616
+ const key = this.resolveEntry(fromFile, specifier, true);
617
+ if (key === null) {
618
+ // A refusal NODE ITSELF would answer at runtime — the package isn't
619
+ // installed, its exports refuse the subpath, or the matched target
620
+ // file doesn't exist — is not a build error: Node evaluates the
621
+ // import() and REJECTS it with exactly this error (the program may
622
+ // never even call it). Embed the throwing stub with Node's message
623
+ // and code, exactly like the ERR_UNKNOWN_FILE_EXTENSION stub above;
624
+ // the resolveEntry error text it superseded comes off the list.
625
+ // Failures that are NOT Node-shaped refusals (unreadable manifests,
626
+ // deep-graph embed problems) stay build errors.
627
+ const refusal = probeNodeImportRefusal(fromFile, specifier, this.host);
628
+ if (refusal !== null) {
629
+ this.errors.length = errorsBefore;
630
+ return { kind: "module", key: this.refusalTrap(refusal) };
631
+ }
632
+ return {
633
+ kind: "unresolved",
634
+ message: this.errors[this.errors.length - 1]?.message ??
635
+ `cannot resolve '${specifier}' from ${fromFile}`,
636
+ };
637
+ }
638
+ return { kind: "module", key };
639
+ }
640
+ /** Embeds (once per distinct refusal) the ESM stub whose EVALUATION
641
+ * throws a probed Node refusal — the import() rejects at runtime with
642
+ * Node's exact message and code, the build succeeds. */
643
+ refusalTrap(refusal) {
644
+ const key = `scr:import-trap:${this.importTrapCount++}`;
645
+ const props = `{code:${JSON.stringify(refusal.code)}` +
646
+ (refusal.url === undefined ? `}` : `,url:${JSON.stringify(refusal.url)}}`);
647
+ this.modules.set(key, {
648
+ key,
649
+ source: `throw Object.assign(new Error(${JSON.stringify(refusal.message)}),${props});\n`,
650
+ format: "esm",
651
+ });
652
+ return key;
653
+ }
654
+ /** The runtime entry key a previously-added import resolved to. */
655
+ entryOf(fromFile, specifier) {
656
+ const key = this.entries.get(this.entryKeyFor(fromFile, specifier));
657
+ return key === undefined || key === "" ? null : key;
658
+ }
659
+ entryKeyFor(fromFile, specifier) {
660
+ return `${dirname(resolve(fromFile))}\u0000${specifier}`;
661
+ }
662
+ resolveEntry(fromFile, specifier, lazy) {
663
+ const cacheKey = this.entryKeyFor(fromFile, specifier);
664
+ const cached = this.entries.get(cacheKey);
665
+ if (cached !== undefined) {
666
+ // A static import of an entry first reached through import():
667
+ // re-walk eagerly so the subgraph promotes (Node links it now).
668
+ if (cached !== "" && !lazy)
669
+ this.walk(cached, [packageNameOf(specifier)], false);
670
+ return cached === "" ? null : cached;
671
+ }
672
+ // User-level entries prefer the ESM ("import") condition — the engine
673
+ // loads real ES modules natively; CJS-only packages fall back to the
674
+ // require shim.
675
+ const key = this.resolvePackage(dirname(resolve(fromFile)), specifier, "import", {
676
+ importer: resolve(fromFile),
677
+ chain: [],
678
+ });
679
+ this.entries.set(cacheKey, key ?? "");
680
+ if (key)
681
+ this.walk(key, [packageNameOf(specifier)], lazy);
682
+ return key;
683
+ }
684
+ /** The cjs-lexer.ts pass over every embedded CommonJS module: lex the
685
+ * export names exactly as Node's vendored lexer would, follow the
686
+ * re-export forms (`module.exports = require(…)`, table spreads/values,
687
+ * the tsc/babel star patterns) through the ALREADY-RESOLVED edges
688
+ * (builtin and unresolved targets are skipped; a require cycle
689
+ * contributes what is known when re-entered, like Node's seen-set), then
690
+ * synthesize each module's ESM facade. */
691
+ synthesizeCjsFacades() {
692
+ const target = new Map();
693
+ // Following a CJS re-export IS a require: when a dual package embeds
694
+ // kind-split edges, the "require" one names the module whose exports
695
+ // Node's lexer chain would read.
696
+ for (const e of this.edges) {
697
+ const k = `${e.from}\u0000${e.specifier}`;
698
+ if (e.kind === "require" || !target.has(k))
699
+ target.set(k, e.to);
700
+ }
701
+ const memo = new Map();
702
+ const namesOf = (key) => {
703
+ let names = memo.get(key);
704
+ if (names)
705
+ return names;
706
+ memo.set(key, (names = new Set()));
707
+ const mod = this.modules.get(key);
708
+ if (!mod || mod.format !== "cjs")
709
+ return names;
710
+ const lexed = cjsLexedExportsOf(mod.source, key);
711
+ for (const n of lexed.exports)
712
+ names.add(n);
713
+ for (const spec of lexed.reexports) {
714
+ const to = target.get(`${key}\u0000${spec}`);
715
+ if (to !== undefined && !to.startsWith("node:")) {
716
+ for (const n of namesOf(to))
717
+ names.add(n);
718
+ }
719
+ }
720
+ return names;
721
+ };
722
+ for (const mod of this.modules.values()) {
723
+ if (mod.format === "cjs" && mod.esm === undefined) {
724
+ mod.esm = cjsEsmFacadeSource(mod.key, [...namesOf(mod.key)]);
725
+ }
726
+ }
727
+ }
728
+ finish() {
729
+ this.synthesizeCjsFacades();
730
+ return {
731
+ modules: [...this.modules.values()],
732
+ edges: this.edges,
733
+ entryOf: (fromFile, specifier) => {
734
+ const key = this.entries.get(this.entryKeyFor(fromFile, specifier));
735
+ return key === undefined || key === "" ? null : key;
736
+ },
737
+ builtins: [...this.builtinsSeen.entries()]
738
+ .map(([builtin, packages]) => {
739
+ const shimmed = SHIMMED_BUILTINS.has(builtin.slice(5));
740
+ return {
741
+ builtin,
742
+ shimmed,
743
+ lazy: !shimmed && !this.builtinsEager.has(builtin),
744
+ packages: [...packages].sort(),
745
+ };
746
+ })
747
+ .sort((a, b) => a.builtin.localeCompare(b.builtin)),
748
+ lazyTraps: [...this.lazyTrapsSeen.entries()]
749
+ .map(([specifier, t]) => ({
750
+ specifier,
751
+ via: ["require", "import()", "import"].filter((v) => t.via.has(v)),
752
+ packages: [...t.packages].sort(),
753
+ ...(t.native ? { native: true } : {}),
754
+ }))
755
+ .sort((a, b) => a.specifier.localeCompare(b.specifier)),
756
+ errors: this.errors,
757
+ };
758
+ }
759
+ /* ── lazy runtime traps (require()/import() edges Node fails at the
760
+ * call — the build embeds the failure instead of refusing) ─────────── */
761
+ /** Records one unresolvable lazily-reached specifier for the build-time
762
+ * inventory (the coverage report's lazy-trap rows). `via` carries only
763
+ * the call forms whose OWN resolution failed — with per-kind condition
764
+ * resolution, one form of a specifier can fail while another succeeds. */
765
+ noteLazyTrap(spec, via, pkgName, moduleLazy, native = false) {
766
+ let t = this.lazyTrapsSeen.get(spec);
767
+ if (!t)
768
+ this.lazyTrapsSeen.set(spec, (t = { via: new Set(), packages: new Set(), native }));
769
+ if (via.require)
770
+ t.via.add("require");
771
+ if (via.dynamicImport)
772
+ t.via.add("import()");
773
+ if (via.static && moduleLazy)
774
+ t.via.add("import");
775
+ t.packages.add(pkgName);
776
+ }
777
+ /** Embeds the throwing ESM stub for an unresolvable dynamic import()
778
+ * edge — Node's exact ERR_MODULE_NOT_FOUND shape at evaluation: bare
779
+ * specifiers say `Cannot find package 'x' imported from <file>`,
780
+ * relative ones resolve against the importer first and say `Cannot find
781
+ * module '<abs>' imported from <file>` carrying the file:// url, which
782
+ * is what Node's ESM resolver rejects with. Returns the stub's key (the
783
+ * edge target). require() never reaches these: unresolvable require
784
+ * edges embed NO edge and the island's require shim throws Node's
785
+ * MODULE_NOT_FOUND at the call, with the LIVE require stack. */
786
+ importNotFoundTrap(from, spec) {
787
+ const key = `scr:import-trap:${this.importTrapCount++}`;
788
+ let msg;
789
+ let url = null;
790
+ if (spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/")) {
791
+ const abs = spec.startsWith("/") ? spec : resolve(dirname(from), spec);
792
+ msg = `Cannot find module '${abs}' imported from ${from}`;
793
+ url = `file://${abs}`;
794
+ }
795
+ else {
796
+ msg = `Cannot find package '${packageNameOf(spec)}' imported from ${from}`;
797
+ }
798
+ const props = `{code:"ERR_MODULE_NOT_FOUND"` + (url === null ? `}` : `,url:${JSON.stringify(url)}}`);
799
+ this.modules.set(key, {
800
+ key,
801
+ source: `throw Object.assign(new Error(${JSON.stringify(msg)}),${props});\n`,
802
+ format: "esm",
803
+ });
804
+ return key;
805
+ }
806
+ /** The throwing stub embedded for a resolved .node native addon: Node's
807
+ * ERR_DLOPEN_FAILED code (what a broken dlopen throws) with the island's
808
+ * honest message. Evaluation throws — at the require() call, at a
809
+ * facade-mediated import, at import() — and the CJS cache keeps no entry
810
+ * for a throwing module, so every retry throws again, like Node. */
811
+ static nativeAddonStub(key) {
812
+ const msg = `Cannot load native addon '${key}': scriptc binaries cannot load .node addons ` +
813
+ `(the island has no process.dlopen)`;
814
+ return `throw Object.assign(new Error(${JSON.stringify(msg)}),{code:"ERR_DLOPEN_FAILED"});\n`;
815
+ }
816
+ /* ── resolution ─────────────────────────────────────────────────── */
817
+ pkgJsonOf(dir) {
818
+ let cached = this.pkgJsonCache.get(dir);
819
+ if (cached === undefined) {
820
+ const text = this.host.readFile(join(dir, "package.json"));
821
+ cached = text === null ? null : JSON.parse(text);
822
+ this.pkgJsonCache.set(dir, cached);
823
+ }
824
+ return cached;
825
+ }
826
+ /** The nearest package.json "type" above `file` — the .js format rule.
827
+ * Resolution-time overrides (the "module" field) win. */
828
+ formatOf(file) {
829
+ const override = this.formatOverrides.get(file);
830
+ if (override)
831
+ return override;
832
+ if (file.endsWith(".mjs"))
833
+ return "esm";
834
+ if (file.endsWith(".cjs"))
835
+ return "cjs";
836
+ if (file.endsWith(".json"))
837
+ return "json";
838
+ for (let dir = dirname(file);;) {
839
+ const pkg = this.pkgJsonOf(dir);
840
+ if (pkg)
841
+ return pkg.type === "module" ? "esm" : "cjs";
842
+ const parent = dirname(dir);
843
+ if (parent === dir)
844
+ return "cjs";
845
+ dir = parent;
846
+ }
847
+ }
848
+ /** Node-style file resolution: exact file, extension candidates
849
+ * (commander requires './suggestSimilar'; ".node" is in Node's require
850
+ * probe list — the resolved addon embeds as a throwing dlopen stub),
851
+ * then directory resolution (the directory's package.json "main", else
852
+ * its index files). */
853
+ resolveFile(path, depth = 0) {
854
+ if (depth > 8)
855
+ return null; // a "main" cycle — refuse quietly
856
+ if (this.host.isFile(path))
857
+ return path;
858
+ for (const ext of [".js", ".json", ".mjs", ".cjs", ".node"]) {
859
+ if (this.host.isFile(path + ext))
860
+ return path + ext;
861
+ }
862
+ if (this.host.isDirectory(path)) {
863
+ const dir = this.host.realpath(path);
864
+ const main = this.pkgJsonOf(dir)?.main;
865
+ if (typeof main === "string" && main !== "") {
866
+ const viaMain = this.resolveFile(join(dir, main), depth + 1);
867
+ if (viaMain)
868
+ return viaMain;
869
+ }
870
+ for (const idx of ["index.js", "index.json", "index.mjs", "index.cjs", "index.node"]) {
871
+ const p = join(path, idx);
872
+ if (this.host.isFile(p))
873
+ return p;
874
+ }
875
+ }
876
+ return null;
877
+ }
878
+ /** "a → b → c" — the package chain from the user's import to the point
879
+ * of failure, for diagnostics. */
880
+ static chainOf(chain, last) {
881
+ const full = last === undefined ? chain : [...chain, last];
882
+ return full.join(" → ");
883
+ }
884
+ /** Locates node_modules/<name> by walking up from `fromDir` to the
885
+ * filesystem ROOT (dependencies hoist arbitrarily far in workspace
886
+ * layouts), following symlinks with realpath (pnpm/bun virtual stores),
887
+ * then resolves the runtime entry: package.json "exports" with the
888
+ * given condition (strict — an exports map that doesn't match is an
889
+ * error, like Node's ERR_PACKAGE_PATH_NOT_EXPORTED), else the "module"/
890
+ * "main" fallbacks. Returns the realpath'd entry key. */
891
+ resolvePackage(fromDir, specifier, mode, ctx) {
892
+ const name = packageNameOf(specifier);
893
+ const parts = specifier.split("/");
894
+ const subparts = specifier.startsWith("@") ? parts.slice(2) : parts.slice(1);
895
+ const subpath = subparts.length > 0 ? `./${subparts.join("/")}` : ".";
896
+ const chain = NpmGraphBuilder.chainOf(ctx.chain, name);
897
+ // Node's PACKAGE_SELF_RESOLVE runs BEFORE any node_modules lookup: the
898
+ // nearest package scope above the importing module whose "name" equals
899
+ // the specifier's package name AND that carries "exports" answers the
900
+ // specifier through its OWN exports map — finally (a refusal there
901
+ // never falls back to node_modules). Common in packages whose internal
902
+ // modules import the package by its published name (benign self-
903
+ // reference cycles); without this rule the up-walk only finds the
904
+ // package when it happens to sit under a node_modules/<name> path.
905
+ for (let dir = fromDir;;) {
906
+ const selfPkg = this.pkgJsonOf(dir);
907
+ if (selfPkg) {
908
+ if (selfPkg.name === name && selfPkg.exports !== undefined) {
909
+ const fromExports = resolveExports(selfPkg.exports, subpath, mode);
910
+ if (fromExports === null) {
911
+ this.errors.push({
912
+ message: `package '${name}' does not export '${subpath}' in its package.json "exports"` +
913
+ ` (self-reference; imported from ${ctx.importer}; dependency chain: ${chain})`,
914
+ });
915
+ return null;
916
+ }
917
+ const resolved = this.resolveFile(join(dir, fromExports));
918
+ if (!resolved) {
919
+ this.errors.push({
920
+ message: `package '${name}' resolves its ${subpath === "." ? "entry" : `'${subpath}'`} to '${fromExports}', which does not exist` +
921
+ ` (self-reference; imported from ${ctx.importer}; dependency chain: ${chain})`,
922
+ });
923
+ return null;
924
+ }
925
+ return this.host.realpath(resolved);
926
+ }
927
+ break; // nearest scope decides self-reference — matching or not
928
+ }
929
+ const parent = dirname(dir);
930
+ if (parent === dir)
931
+ break;
932
+ dir = parent;
933
+ }
934
+ for (let dir = fromDir;;) {
935
+ const linkDir = join(dir, "node_modules", name);
936
+ const pkgDir = this.host.isDirectory(linkDir) ? this.host.realpath(linkDir) : null;
937
+ const pkg = pkgDir !== null ? this.pkgJsonOf(pkgDir) : null;
938
+ if (pkgDir !== null && pkg !== null) {
939
+ let target;
940
+ let forceEsm = false;
941
+ if (pkg.exports !== undefined) {
942
+ const fromExports = resolveExports(pkg.exports, subpath, mode);
943
+ if (fromExports === null) {
944
+ this.errors.push({
945
+ message: `package '${name}' does not export '${subpath}' in its package.json "exports"` +
946
+ ` (imported from ${ctx.importer}; dependency chain: ${chain})`,
947
+ });
948
+ return null;
949
+ }
950
+ target = fromExports;
951
+ }
952
+ else if (subpath !== ".") {
953
+ target = subpath;
954
+ }
955
+ else if (mode === "import" && typeof pkg.module === "string" && pkg.module !== "") {
956
+ // No "exports": the "module" field names the ESM build (a
957
+ // bundler convention Node itself ignores; embedding prefers the
958
+ // real ES module over require-shimming the CJS "main").
959
+ target = pkg.module;
960
+ forceEsm = true;
961
+ }
962
+ else {
963
+ target = pkg.main && pkg.main !== "" ? pkg.main : "index.js";
964
+ }
965
+ const resolved = this.resolveFile(join(pkgDir, target));
966
+ if (!resolved) {
967
+ this.errors.push({
968
+ message: `package '${name}' resolves its ${subpath === "." ? "entry" : `'${subpath}'`} to '${target}', which does not exist` +
969
+ ` (imported from ${ctx.importer}; dependency chain: ${chain})`,
970
+ });
971
+ return null;
972
+ }
973
+ const key = this.host.realpath(resolved);
974
+ if (forceEsm && !key.endsWith(".mjs") && !key.endsWith(".cjs") && !key.endsWith(".json")) {
975
+ this.formatOverrides.set(key, "esm");
976
+ }
977
+ return key;
978
+ }
979
+ const parent = dirname(dir);
980
+ if (parent === dir) {
981
+ this.errors.push({
982
+ message: `cannot find package '${name}' in any node_modules from ${fromDir} up to the root` +
983
+ ` (imported from ${ctx.importer}${ctx.chain.length > 0 ? `; dependency chain: ${chain}` : ""})`,
984
+ });
985
+ return null;
986
+ }
987
+ dir = parent;
988
+ }
989
+ }
990
+ /* ── the walk ─────────────────────────────────────────────────── */
991
+ /** Modules reached ONLY through lazy edges so far. Node has not linked
992
+ * these at the root: the whole subgraph behind a require()/import()
993
+ * boundary links AT THE CALL, so every blocked edge inside it — static
994
+ * imports included — fails there at runtime, never at build time. A
995
+ * later STATIC path promotes the module to eager: its edges re-sweep so
996
+ * blocked static ones become build errors, exactly the graph Node would
997
+ * refuse at link. */
998
+ lazilyReached = new Set();
999
+ /** Embeds `key` and everything it reaches. `chain` is the package path
1000
+ * from the user's import to this module's package — diagnostics name it
1001
+ * so a failure five dependencies deep is attributable. `lazy` is the
1002
+ * REACHABILITY mode: true when every path from the user's import to this
1003
+ * module crosses a require()/dynamic-import() edge. */
1004
+ walk(key, chain, lazy) {
1005
+ const existing = this.modules.get(key);
1006
+ if (existing) {
1007
+ if (!lazy && this.lazilyReached.has(key)) {
1008
+ // Promotion: a static path reached a module first seen behind a
1009
+ // lazy boundary — Node links it at the root now, so its edge
1010
+ // sweep re-runs eagerly (pushes dedupe through edgeSeen).
1011
+ this.lazilyReached.delete(key);
1012
+ if (existing.format !== "json") {
1013
+ this.sweepEdges(key, existing.source, chain, false);
1014
+ }
1015
+ }
1016
+ return;
1017
+ }
1018
+ if (key.endsWith(".node")) {
1019
+ // A native addon: Node process.dlopen()s it; the island has no
1020
+ // dlopen, so the resolved edge embeds a throwing CJS STUB (Node's
1021
+ // ERR_DLOPEN_FAILED code) instead of megabytes of machine code the
1022
+ // engine could never execute — rolldown/oxc-style binding packages
1023
+ // require() their .node next to a wasm/js fallback, so the stub is
1024
+ // exactly the "addon missing" path those packages already handle.
1025
+ // The require sites are lazy by construction (an eager static
1026
+ // import of a .node would refuse under Node too), so the throw
1027
+ // lands at the call, the only point Node would have dlopen'd.
1028
+ this.modules.set(key, { key, source: NpmGraphBuilder.nativeAddonStub(key), format: "cjs" });
1029
+ return;
1030
+ }
1031
+ const source = this.host.readFile(key);
1032
+ if (source === null) {
1033
+ this.errors.push({
1034
+ message: `cannot read embedded module '${key}' (dependency chain: ${NpmGraphBuilder.chainOf(chain)})`,
1035
+ });
1036
+ return;
1037
+ }
1038
+ const format = this.formatOf(key);
1039
+ this.modules.set(key, { key, source, format });
1040
+ if (lazy)
1041
+ this.lazilyReached.add(key);
1042
+ if (format === "json")
1043
+ return;
1044
+ this.sweepEdges(key, source, chain, lazy);
1045
+ }
1046
+ /** moduleSpecifiersOf through the per-module cache (each module parses
1047
+ * once even across promotion re-sweeps, and the `__require` attribution
1048
+ * chain re-reads helper chunks it never sweeps itself). */
1049
+ specifiersOf(key, source) {
1050
+ let cached = this.specifiersCache.get(key);
1051
+ if (cached === undefined) {
1052
+ const text = source ?? this.host.readFile(key);
1053
+ cached = text === null ? null : moduleSpecifiersOf(text, key);
1054
+ this.specifiersCache.set(key, cached);
1055
+ }
1056
+ return cached;
1057
+ }
1058
+ /** The module whose scope DEFINES the `__require` helper that `key`'s
1059
+ * call sites invoke, or null when it is key's own (or unresolvable).
1060
+ * esbuild splits the helper into a shared chunk that every other chunk
1061
+ * imports; the helper closed over THAT chunk's banner require
1062
+ * (createRequire(import.meta.url)), so Node resolves its calls from the
1063
+ * DEFINING chunk — and the island's require shim looks the edge up
1064
+ * under that key at the call. Follows import → re-export hops through
1065
+ * intermediate chunks, bounded. */
1066
+ requireHelperOriginOf(key) {
1067
+ let cur = key;
1068
+ for (let hops = 0; hops < 8; hops++) {
1069
+ const specs = this.specifiersOf(cur);
1070
+ const next = specs === null
1071
+ ? null
1072
+ : cur === key
1073
+ ? specs.requireHelperImport
1074
+ : (specs.requireHelperImport ?? specs.requireHelperReexport);
1075
+ if (next === null)
1076
+ break;
1077
+ let target = null;
1078
+ if (next.startsWith("./") || next.startsWith("../")) {
1079
+ const file = this.resolveFile(resolve(dirname(cur), next));
1080
+ target = file === null ? null : this.host.realpath(file);
1081
+ }
1082
+ else if (builtinKeyOf(next) === null) {
1083
+ const errorsBefore = this.errors.length;
1084
+ target = this.resolvePackage(dirname(cur), next, "import", { importer: cur, chain: [] });
1085
+ if (target === null)
1086
+ this.errors.length = errorsBefore;
1087
+ }
1088
+ if (target === null)
1089
+ break;
1090
+ cur = target;
1091
+ }
1092
+ return cur === key ? null : cur;
1093
+ }
1094
+ /** One module's edge sweep. Per-edge semantics match Node's: an edge is
1095
+ * EAGER only when the module itself is eagerly reached AND the site is a
1096
+ * static import/export declaration — Node's link-time surface, where a
1097
+ * blocked edge fails the BUILD. Everything else is LAZY — Node fails
1098
+ * require at the CALL and import() at evaluation, and a subgraph behind
1099
+ * either boundary (its static edges included) links at that call — so a
1100
+ * blocked lazy edge embeds a runtime trap and the build succeeds: a
1101
+ * program that never executes the call runs fine under Node too. */
1102
+ sweepEdges(key, source, chain, lazy) {
1103
+ const pkgName = chain[chain.length - 1] ?? key;
1104
+ const pushEdge = (from, spec, to, kind) => {
1105
+ const edgeKey = `${from}\u0000${spec}\u0000${kind}`;
1106
+ if (this.edgeSeen.has(edgeKey))
1107
+ return;
1108
+ this.edgeSeen.add(edgeKey);
1109
+ this.edges.push({ from, specifier: spec, to, kind });
1110
+ };
1111
+ for (const use of this.specifiersOf(key, source)?.uses ?? []) {
1112
+ const spec = use.specifier;
1113
+ const eager = !lazy && use.static;
1114
+ if (spec.startsWith("./") || spec.startsWith("../")) {
1115
+ // A relative file: no conditions apply, so every call form shares
1116
+ // one "any" edge. A blocked lazy one embeds the import trap only
1117
+ // for import()/static-in-lazy sites — require-reached specs embed
1118
+ // NO edge and the island's require shim throws Node's
1119
+ // MODULE_NOT_FOUND with the live require stack at the call.
1120
+ const file = this.resolveFile(resolve(dirname(key), spec));
1121
+ const to = file === null ? null : this.host.realpath(file);
1122
+ if (!to) {
1123
+ if (eager) {
1124
+ this.errors.push({
1125
+ message: `package '${pkgName}' cannot resolve '${spec}' from ${key}` +
1126
+ ` (dependency chain: ${NpmGraphBuilder.chainOf(chain)})`,
1127
+ });
1128
+ }
1129
+ else {
1130
+ this.noteLazyTrap(spec, use, pkgName, lazy);
1131
+ if (use.dynamicImport || (lazy && use.static)) {
1132
+ pushEdge(key, spec, this.importNotFoundTrap(key, spec), "import");
1133
+ }
1134
+ }
1135
+ continue;
1136
+ }
1137
+ // A resolved .node addon embeds as a throwing dlopen stub (see
1138
+ // walk) — the inventory row keeps the coverage report honest
1139
+ // about what the binary cannot reach.
1140
+ if (to.endsWith(".node"))
1141
+ this.noteLazyTrap(spec, use, pkgName, lazy, true);
1142
+ pushEdge(key, spec, to, "any");
1143
+ this.walk(to, chain, lazy || !use.static);
1144
+ continue;
1145
+ }
1146
+ const builtin = builtinKeyOf(spec);
1147
+ if (builtin) {
1148
+ let seen = this.builtinsSeen.get(builtin);
1149
+ if (!seen)
1150
+ this.builtinsSeen.set(builtin, (seen = new Set()));
1151
+ seen.add(pkgName);
1152
+ if (eager)
1153
+ this.builtinsEager.add(builtin);
1154
+ if (eager && !SHIMMED_BUILTINS.has(builtin.slice(5))) {
1155
+ this.errors.push({
1156
+ message: `package '${pkgName}' requires '${spec}', a Node builtin the island does not provide ` +
1157
+ `(available: ${[...SHIMMED_BUILTINS].map((b) => `node:${b}`).join(", ")}; ` +
1158
+ `imported from ${key}; dependency chain: ${NpmGraphBuilder.chainOf(chain)})`,
1159
+ });
1160
+ continue;
1161
+ }
1162
+ // The edge embeds pointing at the "node:x" key (for an unshimmed
1163
+ // builtin reached lazily, the island's require shim — "the island
1164
+ // does not provide the 'node:x' builtin" — or module loader
1165
+ // throws at the CALL, the only point Node would have loaded the
1166
+ // module either). Builtin resolution is location-independent
1167
+ // under Node, but the EDGE isn't: a call through the imported
1168
+ // `__require` helper asks from the DEFINING chunk's key at
1169
+ // runtime, so the edge registers there too (sentry's
1170
+ // `__require("domain")` sites live in one chunk, the helper in
1171
+ // another).
1172
+ pushEdge(key, spec, builtin, "any");
1173
+ if (use.requireViaHelper) {
1174
+ const origin = this.requireHelperOriginOf(key);
1175
+ if (origin !== null)
1176
+ pushEdge(origin, spec, builtin, "require");
1177
+ }
1178
+ continue;
1179
+ }
1180
+ // A bare dependency package: Node picks the "exports" condition set
1181
+ // from the EDGE KIND, never from the importer's format — import/
1182
+ // export declarations and import() resolve with ["node","import"],
1183
+ // require() with ["node","require"] — so a dual package legitimately
1184
+ // embeds TWO targets for one specifier, each behind its own
1185
+ // kind-tagged edge (the island's module loader asks with the import
1186
+ // kind, its require shim with the require kind).
1187
+ const depName = packageNameOf(spec);
1188
+ const nextChain = depName === pkgName ? chain : [...chain, depName];
1189
+ if (use.require) {
1190
+ // require edges attribute to the module whose scope DEFINES the
1191
+ // require function being called: a direct require() is this file's
1192
+ // own (its banner createRequire), but an esbuild `__require`
1193
+ // imported from a shared chunk closed over THAT chunk's require,
1194
+ // so Node resolves from the defining chunk — and the island's
1195
+ // require shim looks the edge up under its key at the call.
1196
+ const froms = new Set();
1197
+ if (use.requireLocal)
1198
+ froms.add(key);
1199
+ if (use.requireViaHelper)
1200
+ froms.add(this.requireHelperOriginOf(key) ?? key);
1201
+ for (const from of froms) {
1202
+ const errorsBefore = this.errors.length;
1203
+ const to = this.resolvePackage(dirname(from), spec, "require", {
1204
+ importer: from,
1205
+ chain,
1206
+ });
1207
+ if (to === null) {
1208
+ // require failures are ALWAYS lazy — no edge embeds; the
1209
+ // island's require shim throws Node's MODULE_NOT_FOUND with
1210
+ // the live require stack at the call.
1211
+ this.errors.length = errorsBefore;
1212
+ this.noteLazyTrap(spec, { require: true, dynamicImport: false, static: false }, pkgName, lazy);
1213
+ continue;
1214
+ }
1215
+ // A package whose entry IS a .node addon (napi per-platform
1216
+ // binding packages: "main" names the addon) embeds the dlopen
1217
+ // stub — inventory row like the relative-specifier shape.
1218
+ if (to.endsWith(".node")) {
1219
+ this.noteLazyTrap(spec, { require: true, dynamicImport: false, static: false }, pkgName, lazy, true);
1220
+ }
1221
+ pushEdge(from, spec, to, "require");
1222
+ this.walk(to, nextChain, true);
1223
+ }
1224
+ }
1225
+ if (use.static || use.dynamicImport) {
1226
+ const errorsBefore = this.errors.length;
1227
+ const to = this.resolvePackage(dirname(key), spec, "import", { importer: key, chain });
1228
+ if (to === null) {
1229
+ if (!eager) {
1230
+ // Roll the resolver's diagnostics back off the build — the
1231
+ // blocked edge is lazy, so the failure belongs to the runtime
1232
+ // trap (and the coverage inventory), not the error list. The
1233
+ // trap is an ESM stub with Node's ERR_MODULE_NOT_FOUND shape,
1234
+ // behind an import-kind edge the require shim never matches.
1235
+ this.errors.length = errorsBefore;
1236
+ this.noteLazyTrap(spec, { require: false, dynamicImport: use.dynamicImport, static: use.static }, pkgName, lazy);
1237
+ if (use.dynamicImport || (lazy && use.static)) {
1238
+ pushEdge(key, spec, this.importNotFoundTrap(key, spec), "import");
1239
+ }
1240
+ }
1241
+ continue; // eager: error already recorded
1242
+ }
1243
+ if (to.endsWith(".node")) {
1244
+ this.noteLazyTrap(spec, { require: false, dynamicImport: use.dynamicImport, static: use.static }, pkgName, lazy, true);
1245
+ }
1246
+ pushEdge(key, spec, to, "import");
1247
+ this.walk(to, nextChain, lazy || !use.static);
1248
+ }
1249
+ }
1250
+ }
1251
+ }
1252
+ /** One-call convenience over NpmGraphBuilder. */
1253
+ export function buildNpmRuntimeGraph(imports) {
1254
+ const b = new NpmGraphBuilder();
1255
+ for (const i of imports)
1256
+ b.addImport(i.fromFile, i.specifier);
1257
+ return b.finish();
1258
+ }
1259
+ //# sourceMappingURL=npm.js.map