@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.
- package/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5947 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10087 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +64 -0
- package/dist/coverage/report.js +226 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6281 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +483 -0
- package/dist/frontend/lowering/lower-containers.js +6281 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8099 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1069 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6256 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2172 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +60 -0
- package/dist/frontend/shared.js +232 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +424 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4884 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- 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
|