@nubjs/nub-win32-arm64 0.0.14 → 0.0.16
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/bin/nub.exe +0 -0
- package/package.json +1 -1
- package/runtime/addons/nub-native.node +0 -0
- package/runtime/preload-async-hooks.mjs +5 -6
- package/runtime/preload.cjs +10 -6
- package/runtime/preload.mjs +22 -11
- package/runtime/transform-core.mjs +183 -521
- package/runtime/version.mjs +1 -1
- package/runtime/worker-polyfill.mjs +15 -2
- package/runtime/node_modules/@oxc-parser/binding-win32-arm64-msvc/README.md +0 -3
- package/runtime/node_modules/@oxc-parser/binding-win32-arm64-msvc/package.json +0 -39
- package/runtime/node_modules/@oxc-parser/binding-win32-arm64-msvc/parser.win32-arm64-msvc.node +0 -0
- package/runtime/node_modules/@oxc-parser/binding-win32-x64-msvc/README.md +0 -3
- package/runtime/node_modules/@oxc-parser/binding-win32-x64-msvc/package.json +0 -39
- package/runtime/node_modules/@oxc-parser/binding-win32-x64-msvc/parser.win32-x64-msvc.node +0 -0
- package/runtime/node_modules/@oxc-project/types/LICENSE +0 -22
- package/runtime/node_modules/@oxc-project/types/README.md +0 -3
- package/runtime/node_modules/@oxc-project/types/package.json +0 -26
- package/runtime/node_modules/@oxc-project/types/types.d.ts +0 -1912
- package/runtime/node_modules/@oxc-transform/binding-win32-arm64-msvc/README.md +0 -3
- package/runtime/node_modules/@oxc-transform/binding-win32-arm64-msvc/package.json +0 -41
- package/runtime/node_modules/@oxc-transform/binding-win32-arm64-msvc/transform.win32-arm64-msvc.node +0 -0
- package/runtime/node_modules/@oxc-transform/binding-win32-x64-msvc/README.md +0 -3
- package/runtime/node_modules/@oxc-transform/binding-win32-x64-msvc/package.json +0 -41
- package/runtime/node_modules/@oxc-transform/binding-win32-x64-msvc/transform.win32-x64-msvc.node +0 -0
- package/runtime/node_modules/get-tsconfig/LICENSE +0 -21
- package/runtime/node_modules/get-tsconfig/README.md +0 -268
- package/runtime/node_modules/get-tsconfig/dist/index.cjs +0 -7
- package/runtime/node_modules/get-tsconfig/dist/index.d.cts +0 -2116
- package/runtime/node_modules/get-tsconfig/dist/index.d.mts +0 -2116
- package/runtime/node_modules/get-tsconfig/dist/index.mjs +0 -7
- package/runtime/node_modules/get-tsconfig/package.json +0 -46
- package/runtime/node_modules/oxc-parser/LICENSE +0 -22
- package/runtime/node_modules/oxc-parser/README.md +0 -167
- package/runtime/node_modules/oxc-parser/package.json +0 -153
- package/runtime/node_modules/oxc-parser/src-js/bindings.js +0 -601
- package/runtime/node_modules/oxc-parser/src-js/generated/constants.js +0 -105
- package/runtime/node_modules/oxc-parser/src-js/generated/deserialize/js.js +0 -5862
- package/runtime/node_modules/oxc-parser/src-js/generated/deserialize/js_range.js +0 -6403
- package/runtime/node_modules/oxc-parser/src-js/generated/deserialize/ts.js +0 -6154
- package/runtime/node_modules/oxc-parser/src-js/generated/deserialize/ts_range.js +0 -6723
- package/runtime/node_modules/oxc-parser/src-js/generated/lazy/constructors.js +0 -13875
- package/runtime/node_modules/oxc-parser/src-js/generated/lazy/type_ids.js +0 -191
- package/runtime/node_modules/oxc-parser/src-js/generated/lazy/walk.js +0 -5810
- package/runtime/node_modules/oxc-parser/src-js/generated/visit/keys.js +0 -220
- package/runtime/node_modules/oxc-parser/src-js/generated/visit/type_ids.js +0 -177
- package/runtime/node_modules/oxc-parser/src-js/generated/visit/visitor.d.ts +0 -387
- package/runtime/node_modules/oxc-parser/src-js/generated/visit/walk.js +0 -2455
- package/runtime/node_modules/oxc-parser/src-js/index.d.ts +0 -312
- package/runtime/node_modules/oxc-parser/src-js/index.js +0 -108
- package/runtime/node_modules/oxc-parser/src-js/raw-transfer/common.js +0 -301
- package/runtime/node_modules/oxc-parser/src-js/raw-transfer/eager.js +0 -255
- package/runtime/node_modules/oxc-parser/src-js/raw-transfer/lazy-common.js +0 -11
- package/runtime/node_modules/oxc-parser/src-js/raw-transfer/lazy.js +0 -162
- package/runtime/node_modules/oxc-parser/src-js/raw-transfer/node-array.js +0 -365
- package/runtime/node_modules/oxc-parser/src-js/raw-transfer/supported.js +0 -52
- package/runtime/node_modules/oxc-parser/src-js/raw-transfer/visitor.js +0 -127
- package/runtime/node_modules/oxc-parser/src-js/visit/index.js +0 -41
- package/runtime/node_modules/oxc-parser/src-js/visit/visitor.js +0 -405
- package/runtime/node_modules/oxc-parser/src-js/wasm.js +0 -13
- package/runtime/node_modules/oxc-parser/src-js/webcontainer-fallback.cjs +0 -21
- package/runtime/node_modules/oxc-parser/src-js/wrap.js +0 -57
- package/runtime/node_modules/oxc-transform/LICENSE +0 -22
- package/runtime/node_modules/oxc-transform/README.md +0 -84
- package/runtime/node_modules/oxc-transform/browser.js +0 -1
- package/runtime/node_modules/oxc-transform/index.d.ts +0 -658
- package/runtime/node_modules/oxc-transform/index.js +0 -598
- package/runtime/node_modules/oxc-transform/package.json +0 -114
- package/runtime/node_modules/oxc-transform/webcontainer-fallback.cjs +0 -21
- package/runtime/node_modules/resolve-pkg-maps/LICENSE +0 -21
- package/runtime/node_modules/resolve-pkg-maps/README.md +0 -216
- package/runtime/node_modules/resolve-pkg-maps/dist/index.cjs +0 -1
- package/runtime/node_modules/resolve-pkg-maps/dist/index.d.cts +0 -11
- package/runtime/node_modules/resolve-pkg-maps/dist/index.d.mts +0 -11
- package/runtime/node_modules/resolve-pkg-maps/dist/index.mjs +0 -1
- package/runtime/node_modules/resolve-pkg-maps/package.json +0 -42
|
@@ -8,131 +8,88 @@
|
|
|
8
8
|
// Temporal lazy global, watch-mode IPC, and the compat-tier CJS `require()`
|
|
9
9
|
// shim. EVERYTHING about how a file is resolved and transpiled — extension
|
|
10
10
|
// probing, the `.js`→`.ts` swap, tsconfig `paths`, module-format detection,
|
|
11
|
-
//
|
|
11
|
+
// transform options (including `target: 'es2022'` `using`-lowering), the
|
|
12
12
|
// Stage-3 decorator guard, the on-disk cache, data-format imports, package
|
|
13
13
|
// clobbering — lives here, so the two tiers can never drift. (They used to:
|
|
14
14
|
// separate copies diverged on probe order, `target` lowering, the decorator
|
|
15
15
|
// guard, module-format detection, the Temporal clobber's named exports, and the
|
|
16
16
|
// reserved-export filter — every one a real compat bug. This module is the fix.)
|
|
17
17
|
//
|
|
18
|
-
// Side effects are confined to: loading the N-API data
|
|
19
|
-
//
|
|
20
|
-
// top-level hook registration here — importing this module never augments the
|
|
18
|
+
// Side effects are confined to: loading the N-API addon (data parsers + the
|
|
19
|
+
// in-process TS/JSX transpiler), and reading/writing the transpile cache. There is
|
|
20
|
+
// no top-level hook registration here — importing this module never augments the
|
|
21
21
|
// realm; the tier files do that.
|
|
22
22
|
|
|
23
|
-
// EVERY
|
|
24
|
-
// via static ESM `import`. This is load-bearing for
|
|
25
|
-
// nub loads transform-core through `require(esm)`, and
|
|
26
|
-
// instantiates the module by walking its STATIC IMPORT graph
|
|
27
|
-
// loader hooks are registered — including the USER's
|
|
28
|
-
// chain. Static `import
|
|
29
|
-
// here therefore leaked nub's entire internal graph (transform-core,
|
|
30
|
-
//
|
|
31
|
-
// builtins) THROUGH the user's resolve/load hooks, which observed and corrupted
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
// `oxc-transform` specifier — see test-esm-loader-chaining, -example-loader,
|
|
23
|
+
// EVERY node: builtin this module needs is pulled in via CJS `require()` / `process
|
|
24
|
+
// .getBuiltinModule` (below), NOT via static ESM `import`. This is load-bearing for
|
|
25
|
+
// loader compatibility (R11): nub loads transform-core through `require(esm)`, and
|
|
26
|
+
// Node's `require(esm)` instantiates the module by walking its STATIC IMPORT graph
|
|
27
|
+
// through whatever ESM loader hooks are registered — including the USER's
|
|
28
|
+
// `--loader`/`register()` chain. Static `import get-tsconfig`/`./version.mjs`/`node:*`
|
|
29
|
+
// here therefore once leaked nub's entire internal graph (transform-core,
|
|
30
|
+
// version.mjs, get-tsconfig, their transitive node_modules deps, and the node:
|
|
31
|
+
// builtins) THROUGH the user's resolve/load hooks, which observed and corrupted it
|
|
32
|
+
// (a user load hook returning `source: 1` for version.mjs, a strict loader throwing
|
|
33
|
+
// on a bare specifier — see test-esm-loader-chaining, -example-loader,
|
|
35
34
|
// -preserve-symlinks-not-found, test-shadow-realm-custom-loaders). Verified: a CJS
|
|
36
|
-
// `require()` of a
|
|
37
|
-
//
|
|
35
|
+
// `require()` of a builtin does NOT route through the ESM loader chain, so loading
|
|
36
|
+
// off it bypasses the user chain entirely. As of this migration the point is
|
|
37
|
+
// stronger: transform-core `require()`s ZERO npm packages — the transpiler, TS/JSX
|
|
38
|
+
// detection, tsconfig discovery/parse, the additive TS-resolver, AND the transpile
|
|
39
|
+
// cache are ALL native calls into nub's own N-API addon (loaded by absolute `.node`
|
|
40
|
+
// path, off the loader chain), and the version.mjs text read is gone (the cache
|
|
41
|
+
// version is baked into the addon). So the worst historical leaks — oxc-transform's
|
|
42
|
+
// and then get-tsconfig's graphs pulled through the user chain — are gone by
|
|
43
|
+
// construction; only node: builtins remain, fetched off the chain. `process
|
|
38
44
|
// .getBuiltinModule` fetches node: builtins synchronously off the loader chain;
|
|
39
|
-
// `createRequire(import.meta.url)` resolves the
|
|
45
|
+
// `createRequire(import.meta.url)` resolves the (now CommonJS-only) vendored
|
|
46
|
+
// polyfills + the `@oxc-project/runtime` helpers from nub's distribution.
|
|
40
47
|
// This file keeps its `export`s (it stays an ES module), but has ZERO static
|
|
41
48
|
// imports, so `require(esm)` finds no dependency graph to route through the user.
|
|
42
|
-
|
|
49
|
+
// `process.getBuiltinModule` (Node 22.3 / backported to 20.16 / 18.20.4) fetches a
|
|
50
|
+
// node: builtin synchronously off the loader chain. On older floor Node (18.19,
|
|
51
|
+
// 20.11–20.15, 22.0–22.2) it's `undefined` — calling it threw `TypeError: process
|
|
52
|
+
// .getBuiltinModule is not a function`, aborting every run. Fall back to a
|
|
53
|
+
// createRequire bootstrapped from a single static `node:module` import. That import
|
|
54
|
+
// is a BUILTIN specifier — resolved by Node natively, never routed through a user
|
|
55
|
+
// loader hook (and resolved here at preload time, before any hook registers) — so
|
|
56
|
+
// the "zero user-routable dependency graph for require(esm)" property still holds.
|
|
57
|
+
import { createRequire as __bootstrapCreateRequire } from "node:module";
|
|
58
|
+
const __getBuiltin =
|
|
59
|
+
typeof process.getBuiltinModule === "function"
|
|
60
|
+
? (id) => process.getBuiltinModule(id)
|
|
61
|
+
: ((__r) => (id) => __r(id))(__bootstrapCreateRequire(import.meta.url));
|
|
62
|
+
|
|
63
|
+
const { createRequire } = __getBuiltin("node:module");
|
|
43
64
|
const __require = createRequire(import.meta.url);
|
|
44
65
|
|
|
45
|
-
const module =
|
|
46
|
-
const { readFileSync, writeFileSync, mkdirSync, statSync
|
|
47
|
-
const { fileURLToPath, pathToFileURL } =
|
|
48
|
-
const { join, dirname
|
|
49
|
-
//
|
|
50
|
-
// `
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
function requireOxcAsCjs() {
|
|
65
|
-
const Module = module;
|
|
66
|
-
let entry;
|
|
67
|
-
try {
|
|
68
|
-
entry = __require.resolve("oxc-transform");
|
|
69
|
-
} catch {
|
|
70
|
-
return __require("oxc-transform");
|
|
71
|
-
}
|
|
72
|
-
// Only the .js NAPI-RS wrapper has the mixed ESM-header/CJS-body shape we can
|
|
73
|
-
// safely transform; anything else, defer to the normal loader.
|
|
74
|
-
if (!entry.endsWith(".js")) return __require("oxc-transform");
|
|
75
|
-
try {
|
|
76
|
-
let src = readFileSync(entry, "utf8");
|
|
77
|
-
// Strip the ESM header: `import { createRequire } from 'node:module'`,
|
|
78
|
-
// `const require = createRequire(import.meta.url)`, and any
|
|
79
|
-
// `const __dirname = new URL('.', import.meta.url).pathname`. In a CJS module
|
|
80
|
-
// `require`, `__dirname`, `__filename` are already injected by _compile.
|
|
81
|
-
src = src
|
|
82
|
-
.replace(/^\s*import\s*\{[^}]*\}\s*from\s*['"]node:module['"];?\s*$/m, "")
|
|
83
|
-
.replace(/^\s*const\s+require\s*=\s*createRequire\([^)]*\);?\s*$/m, "")
|
|
84
|
-
.replace(/^\s*const\s+__dirname\s*=\s*new URL\([^)]*\)\.pathname;?\s*$/m, "");
|
|
85
|
-
// Convert the `export { Name }` footer lines to a single CJS exports block.
|
|
86
|
-
const names = [];
|
|
87
|
-
src = src.replace(/^\s*export\s*\{\s*([A-Za-z0-9_$]+)\s*\}\s*;?\s*$/gm, (_m, n) => {
|
|
88
|
-
names.push(n);
|
|
89
|
-
return "";
|
|
90
|
-
});
|
|
91
|
-
// Guard: if any `import`/`export`/`import.meta` survived, we don't understand
|
|
92
|
-
// this version's shape — bail to the safe loader rather than ship broken code.
|
|
93
|
-
if (/^\s*(import|export)\s/m.test(src) || /\bimport\.meta\b/.test(src)) {
|
|
94
|
-
return __require("oxc-transform");
|
|
95
|
-
}
|
|
96
|
-
if (names.length === 0) return __require("oxc-transform");
|
|
97
|
-
src += `\nmodule.exports = { ${names.join(", ")} };\n`;
|
|
98
|
-
const m = new Module(entry, null);
|
|
99
|
-
m.filename = entry;
|
|
100
|
-
m.paths = Module._nodeModulePaths(dirname(entry));
|
|
101
|
-
m._compile(src, entry);
|
|
102
|
-
return m.exports;
|
|
103
|
-
} catch {
|
|
104
|
-
return __require("oxc-transform");
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const { transformSync } = requireOxcAsCjs();
|
|
108
|
-
const { getTsconfig, createPathsMatcher } = __require("get-tsconfig");
|
|
109
|
-
|
|
110
|
-
// NUB_VERSION is the single source of truth in runtime/version.mjs. We must NOT
|
|
111
|
-
// `import` it (that would route version.mjs through the user loader chain — see
|
|
112
|
-
// above; a user load hook returning bogus source corrupts it), and we cannot
|
|
113
|
-
// `require()` it either (it is an ES module, so `require()` uses require(esm),
|
|
114
|
-
// which re-routes version.mjs's own load through the chain). Instead read its
|
|
115
|
-
// text directly and extract the literal — `make version` keeps the assignment on
|
|
116
|
-
// one line (`export const NUB_VERSION = "x.y.z";`), so a tight regex is stable.
|
|
117
|
-
const NUB_VERSION = (() => {
|
|
118
|
-
try {
|
|
119
|
-
const text = readFileSync(fileURLToPath(new URL("./version.mjs", import.meta.url)), "utf8");
|
|
120
|
-
const m = text.match(/NUB_VERSION\s*=\s*["']([^"']+)["']/);
|
|
121
|
-
if (m) return m[1];
|
|
122
|
-
} catch {}
|
|
123
|
-
return "0.0.0";
|
|
124
|
-
})();
|
|
125
|
-
|
|
126
|
-
// `node:crypto` is used ONLY to hash the transpile-cache key, so it loads lazily
|
|
127
|
-
// on first transpile rather than at module top level. Importing it eagerly pulls
|
|
128
|
-
// in the crypto/tls native tree (~dozens of builtins) on EVERY startup — including
|
|
129
|
-
// a plain-JS run that never transpiles anything (R7). The first `.ts` transpile
|
|
130
|
-
// pays the one-time require; a no-TS run never touches it. Memoized.
|
|
131
|
-
let _createHash = null;
|
|
132
|
-
function getCreateHash() {
|
|
133
|
-
return (_createHash ??= __require("node:crypto").createHash);
|
|
66
|
+
const module = __getBuiltin("node:module");
|
|
67
|
+
const { readFileSync, writeFileSync, mkdirSync, statSync } = __getBuiltin("node:fs");
|
|
68
|
+
const { fileURLToPath, pathToFileURL } = __getBuiltin("node:url");
|
|
69
|
+
const { join, dirname } = __getBuiltin("node:path");
|
|
70
|
+
// Nub's N-API addon — the in-process TS/JSX transpiler (`transform`,
|
|
71
|
+
// `transformCached`, `detectModuleInfo`), the tsconfig reader + additive
|
|
72
|
+
// TS-resolver (`loadTsconfig`, `resolveTs`), AND the data-format parsers
|
|
73
|
+
// (`parseYaml`/`parseToml`/`parseJson5`/`parseJsonc`), all native. Loaded once
|
|
74
|
+
// per module instance (= once per thread: the main thread and the loader worker
|
|
75
|
+
// each import this module separately). It is a `.node` binary resolved by absolute
|
|
76
|
+
// path off this file's dir, so it never touches the ESM loader chain — the
|
|
77
|
+
// historical require(esm)-of-an-ESM-npm-package leak (oxc-transform, and before
|
|
78
|
+
// this migration get-tsconfig) is gone: transpilation, tsconfig discovery, the
|
|
79
|
+
// additive resolution, and the transpile cache are synchronous native calls, no JS
|
|
80
|
+
// package, no static-import graph to route. nub now loads ZERO npm packages
|
|
81
|
+
// internally, so the user ESM loader chain can never observe a nub dependency.
|
|
82
|
+
let nubNative = null;
|
|
83
|
+
for (const rel of ["./addons/nub-native.node", "../runtime/addons/nub-native.node"]) {
|
|
84
|
+
try { nubNative = __require(fileURLToPath(new URL(rel, import.meta.url))); break; } catch {}
|
|
134
85
|
}
|
|
135
86
|
|
|
87
|
+
// NOTE: the transpile-cache version component is no longer read here. nub's
|
|
88
|
+
// version is baked into the native addon at compile time (`env!("CARGO_PKG_VERSION")`
|
|
89
|
+
// in nub-native's cache.rs), which `make version` keeps in lockstep with
|
|
90
|
+
// runtime/version.mjs and Cargo.toml — so the cache key's version component lives
|
|
91
|
+
// natively now, and this file no longer needs to read version.mjs.
|
|
92
|
+
|
|
136
93
|
// ── Constants ───────────────────────────────────────────────────────
|
|
137
94
|
export const TRANSPILE_EXTS = new Set([".ts", ".tsx", ".mts", ".cts", ".jsx"]);
|
|
138
95
|
export const DATA_EXTS = { ".jsonc": "jsonc", ".json5": "json5", ".toml": "toml", ".yaml": "yaml", ".yml": "yaml", ".txt": "txt" };
|
|
@@ -174,14 +131,6 @@ export const CLOBBER_MAP = new Map([
|
|
|
174
131
|
["abort-controller", () => `export const AbortController = globalThis.AbortController; export const AbortSignal = globalThis.AbortSignal; export default globalThis.AbortController;`],
|
|
175
132
|
]);
|
|
176
133
|
|
|
177
|
-
// Nub's N-API addon for data-format parsing (Rust-native YAML/TOML/JSON5/JSONC).
|
|
178
|
-
// Loaded once per module instance (= once per thread: the main thread and the
|
|
179
|
-
// loader worker each import this module separately).
|
|
180
|
-
let nubNative = null;
|
|
181
|
-
for (const rel of ["./addons/nub-native.node", "../runtime/addons/nub-native.node"]) {
|
|
182
|
-
try { nubNative = __require(fileURLToPath(new URL(rel, import.meta.url))); break; } catch {}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
134
|
// ── Watch-mode hooks (injected by the main-thread tier) ─────────────
|
|
186
135
|
// `nub watch` needs config files (tsconfig.json, package.json) and `.env*` —
|
|
187
136
|
// which are not in any import graph — surfaced to Node's FilesWatcher. The main
|
|
@@ -195,15 +144,24 @@ export function setWatchHooks({ reportDep, reportEnvDir } = {}) {
|
|
|
195
144
|
}
|
|
196
145
|
|
|
197
146
|
// ── tsconfig + package-type caches ──────────────────────────────────
|
|
147
|
+
// tsconfig discovery / parse / `extends` resolution + the `paths` matcher all
|
|
148
|
+
// happen natively (nub-native `loadTsconfig`, the get-tsconfig@4.14.0 port). This
|
|
149
|
+
// JS wrapper exists only to (a) memoize per importer-dir — native ALSO memoizes,
|
|
150
|
+
// but a JS-side Map skips the napi boundary on a hit and lets watch-mode report
|
|
151
|
+
// the dep exactly once per dir — and (b) surface the resolved tsconfig path to the
|
|
152
|
+
// watch FilesWatcher. The returned shape exposes the transform-relevant
|
|
153
|
+
// `compilerOptions` slice and the `tsconfigHash` cache-key component; the `paths`
|
|
154
|
+
// matcher lives entirely in native (`resolveTs` runs it), so there is no JS matcher.
|
|
198
155
|
const tsconfigCache = new Map();
|
|
199
156
|
export function getTsconfigForDir(dir) {
|
|
200
157
|
if (tsconfigCache.has(dir)) return tsconfigCache.get(dir);
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
158
|
+
// { path: string|null, compilerOptions: object|null, tsconfigHash: string }
|
|
159
|
+
const result = nubNative
|
|
160
|
+
? nubNative.loadTsconfig(dir)
|
|
161
|
+
: { path: null, compilerOptions: null, tsconfigHash: "" };
|
|
162
|
+
tsconfigCache.set(dir, result);
|
|
163
|
+
if (result.path) _reportDep?.(result.path);
|
|
164
|
+
return result;
|
|
207
165
|
}
|
|
208
166
|
|
|
209
167
|
// The NEAREST package.json's `type` decides the format of ambiguous extensions
|
|
@@ -250,11 +208,6 @@ export function fileExists(filePath) {
|
|
|
250
208
|
return s !== undefined && s.isFile();
|
|
251
209
|
}
|
|
252
210
|
|
|
253
|
-
export function dirExists(filePath) {
|
|
254
|
-
const s = statSync(filePath, { throwIfNoEntry: false });
|
|
255
|
-
return s !== undefined && s.isDirectory();
|
|
256
|
-
}
|
|
257
|
-
|
|
258
211
|
function safeRequireResolve(specifier) {
|
|
259
212
|
try { return __require.resolve(specifier); } catch { return null; }
|
|
260
213
|
}
|
|
@@ -266,167 +219,52 @@ export function barePkg(specifier) {
|
|
|
266
219
|
}
|
|
267
220
|
|
|
268
221
|
// ── Resolution ──────────────────────────────────────────────────────
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
// Node
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
222
|
+
// The ADDITIVE TS resolution — tsconfig `paths` aliases, `.ts/.tsx/.mts/.cts/.jsx`
|
|
223
|
+
// extension probing, the `.js`→`.ts` (and `.jsx→.tsx`, `.mjs→.mts`, `.cjs→.cts`)
|
|
224
|
+
// emit-convention swap, directory-index probing, and reading a directory's
|
|
225
|
+
// `package.json#main` — all happens natively now (nub-native `resolveTs`). It
|
|
226
|
+
// returns an absolute path for the additive cases nub owns, or `null` for
|
|
227
|
+
// EVERYTHING Node owns (node_modules, `exports`/`imports`, conditions, scoped/bare
|
|
228
|
+
// specifiers), which the resolve hooks below turn into a fall-through to Node. That
|
|
229
|
+
// `null` is the byte-for-byte compat boundary; reimplementing Node's resolution in
|
|
230
|
+
// nub is forbidden. The `node:`/`data:`/builtin guards, the nub-internal-graph
|
|
231
|
+
// bypass, vendored packages, and the clobber map all stay in JS and run BEFORE the
|
|
232
|
+
// native resolver (see resolveSpec / resolveCjsPath).
|
|
233
|
+
function resolveTs(specifier, parentPath) {
|
|
234
|
+
if (!nubNative) return null;
|
|
278
235
|
try {
|
|
279
|
-
|
|
280
|
-
return typeof main === "string" && main.trim() ? main : null;
|
|
236
|
+
return nubNative.resolveTs(specifier, parentPath || "");
|
|
281
237
|
} catch {
|
|
282
238
|
return null;
|
|
283
239
|
}
|
|
284
240
|
}
|
|
285
241
|
|
|
286
|
-
// Try to resolve a file path with extensionless probing + .js→.ts swap.
|
|
287
|
-
// `allowDirMain` honors a resolved directory's package.json `main` before its
|
|
288
|
-
// index; it is cleared on the recursive main-target probe because Node's
|
|
289
|
-
// LOAD_AS_DIRECTORY resolves `main` with file+index probing only and does not
|
|
290
|
-
// recurse into the target's own nested `main` (verified against Node 24).
|
|
291
|
-
export function tryResolveFile(target, parentExt, allowDirMain = true) {
|
|
292
|
-
// If the target already has an extension and exists, use it.
|
|
293
|
-
const existingExt = pathExtname(target);
|
|
294
|
-
if (existingExt && fileExists(target)) return target;
|
|
295
|
-
|
|
296
|
-
// .js → .ts swap (tsc emit convention reversal).
|
|
297
|
-
if (existingExt === ".js") {
|
|
298
|
-
const tsSwap = target.slice(0, -3) + ".ts";
|
|
299
|
-
if (fileExists(tsSwap)) return tsSwap;
|
|
300
|
-
const tsxSwap = target.slice(0, -3) + ".tsx";
|
|
301
|
-
if (fileExists(tsxSwap)) return tsxSwap;
|
|
302
|
-
}
|
|
303
|
-
if (existingExt === ".jsx") {
|
|
304
|
-
const tsxSwap = target.slice(0, -4) + ".tsx";
|
|
305
|
-
if (fileExists(tsxSwap)) return tsxSwap;
|
|
306
|
-
}
|
|
307
|
-
// .mjs → .mts swap (Bun does this).
|
|
308
|
-
if (existingExt === ".mjs") {
|
|
309
|
-
const mtsSwap = target.slice(0, -4) + ".mts";
|
|
310
|
-
if (fileExists(mtsSwap)) return mtsSwap;
|
|
311
|
-
}
|
|
312
|
-
// .cjs → .cts swap — the CommonJS analog of .mjs→.mts. tsc resolves
|
|
313
|
-
// `import "./foo.cjs"` to foo.cts (it strips the .cjs and finds the .cts
|
|
314
|
-
// source — verified via --traceResolution), so a TS file using the emitted
|
|
315
|
-
// extension to reference a .cts source must resolve at runtime. (Bun omits
|
|
316
|
-
// this swap even though it does .mjs→.mts; we match tsc, not that gap.)
|
|
317
|
-
if (existingExt === ".cjs") {
|
|
318
|
-
const ctsSwap = target.slice(0, -4) + ".cts";
|
|
319
|
-
if (fileExists(ctsSwap)) return ctsSwap;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Extensionless: probe in parent-ext-aware order.
|
|
323
|
-
if (!existingExt) {
|
|
324
|
-
const probeOrder = getProbeOrder(parentExt);
|
|
325
|
-
for (const ext of probeOrder) {
|
|
326
|
-
if (fileExists(target + ext)) return target + ext;
|
|
327
|
-
}
|
|
328
|
-
// Directory: honor package.json `main` (Node's legacy LOAD_AS_DIRECTORY)
|
|
329
|
-
// before falling back to index probing. The main target is resolved with
|
|
330
|
-
// the same extensionless/TS-swap probing (so a TS package can point `main`
|
|
331
|
-
// at a `.ts`, or `.js`→`.ts` swaps apply), but without re-reading a nested
|
|
332
|
-
// `main` — matching Node. If `main` is absent or unresolvable, index wins
|
|
333
|
-
// (Node falls back to index too, with a DEP0128 warning we needn't emit).
|
|
334
|
-
if (dirExists(target)) {
|
|
335
|
-
if (allowDirMain) {
|
|
336
|
-
const main = readPackageMain(target);
|
|
337
|
-
if (main) {
|
|
338
|
-
const resolved = tryResolveFile(pathResolve(target, main), parentExt, false);
|
|
339
|
-
if (resolved) return resolved;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
for (const ext of probeOrder) {
|
|
343
|
-
const idx = join(target, "index" + ext);
|
|
344
|
-
if (fileExists(idx)) return idx;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
export function getProbeOrder(parentExt) {
|
|
353
|
-
switch (parentExt) {
|
|
354
|
-
case ".tsx": return [".tsx", ".ts", ".jsx", ".js", ".json"];
|
|
355
|
-
// .mts/.cts prefer their own module system first, but STILL fall through to
|
|
356
|
-
// the general TS (`.ts`) and JS extensions: tsc and Node resolve an
|
|
357
|
-
// extensionless `./foo` from a .mts/.cts parent to foo.ts / foo.js too, not
|
|
358
|
-
// only foo.mts / foo.cts. Omitting `.ts` here is what made `require('./config')`
|
|
359
|
-
// — and a tsconfig-paths alias — from a .cts (or .mts) parent miss a `.ts`
|
|
360
|
-
// target (works from .js/.cjs, which use the default order below).
|
|
361
|
-
case ".mts": return [".mts", ".ts", ".mjs", ".js", ".json"];
|
|
362
|
-
case ".cts": return [".cts", ".ts", ".cjs", ".js", ".json"];
|
|
363
|
-
default: return [".ts", ".tsx", ".js", ".jsx", ".json"];
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
242
|
// nub's own runtime directory (this file's dir, as a file: URL prefix). Any
|
|
368
243
|
// resolution whose IMPORTER lives here is one of nub's internal requires — the
|
|
369
|
-
// preload loading transform-core,
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
244
|
+
// preload loading transform-core, the Temporal lazy getter resolving
|
|
245
|
+
// @js-temporal/polyfill — and must NEVER be routed through nub's own
|
|
246
|
+
// clobber/vendored/tsconfig logic: those are user-code conveniences, and applying
|
|
247
|
+
// them to nub's internals both breaks them (e.g. the Temporal clobber re-exports
|
|
248
|
+
// globalThis.Temporal, which IS the getter → a require of the polyfill from the
|
|
249
|
+
// getter would recurse into the clobber) and amplifies the user loader chain by
|
|
250
|
+
// re-walking nub's internal graph through user hooks (R11). Short-circuit to native
|
|
251
|
+
// resolution for these.
|
|
377
252
|
const RUNTIME_DIR_URL = new URL(".", import.meta.url).href;
|
|
378
253
|
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
// including node: builtins and the package's own relative imports. Computed lazily
|
|
391
|
-
// (and pinned even on resolve failure) so a missing dep can't wedge startup.
|
|
392
|
-
let _nubGraphRoots = null;
|
|
393
|
-
function nubGraphRoots() {
|
|
394
|
-
if (_nubGraphRoots) return _nubGraphRoots;
|
|
395
|
-
const roots = [];
|
|
396
|
-
for (const pkg of ["oxc-transform", "get-tsconfig"]) {
|
|
397
|
-
try {
|
|
398
|
-
const entry = __require.resolve(pkg);
|
|
399
|
-
// Package root = the directory two levels up does not work generically;
|
|
400
|
-
// instead key on the package-name segment: everything under
|
|
401
|
-
// `.../node_modules/<pkg>/` is that package. Use the entry's dir-with-pkg.
|
|
402
|
-
const idx = entry.lastIndexOf(`${sep()}node_modules${sep()}`);
|
|
403
|
-
if (idx !== -1) {
|
|
404
|
-
// Keep through the package-name segment (handles scoped names too).
|
|
405
|
-
const afterNM = entry.slice(idx + (`${sep()}node_modules${sep()}`).length);
|
|
406
|
-
const firstSeg = afterNM.startsWith("@")
|
|
407
|
-
? afterNM.split(sep()).slice(0, 2).join(sep())
|
|
408
|
-
: afterNM.split(sep())[0];
|
|
409
|
-
const pkgRoot = entry.slice(0, idx) + `${sep()}node_modules${sep()}` + firstSeg + sep();
|
|
410
|
-
roots.push(pathToFileURL(pkgRoot).href);
|
|
411
|
-
}
|
|
412
|
-
} catch {}
|
|
413
|
-
}
|
|
414
|
-
return (_nubGraphRoots = roots);
|
|
415
|
-
}
|
|
416
|
-
function sep() {
|
|
417
|
-
return process.platform === "win32" ? "\\" : "/";
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Is this importer part of nub's own internal module graph (runtime dir or a nub
|
|
421
|
-
// dependency package)? Such imports must bypass the user ESM loader chain entirely.
|
|
254
|
+
// Is this importer part of nub's own internal module graph? Such imports must
|
|
255
|
+
// bypass the user ESM loader chain entirely (R11). nub now loads ZERO npm packages
|
|
256
|
+
// internally — tsconfig, the additive resolver, the transpile cache, the
|
|
257
|
+
// transpiler, and module detection are ALL native nub-native calls, and the only
|
|
258
|
+
// remaining JS deps (@oxc-project/runtime helpers, the polyfills) are CommonJS,
|
|
259
|
+
// whose `require()` graph already bypasses the ESM loader chain by construction. So
|
|
260
|
+
// the only nub-internal ESM importer left is nub's own runtime directory (this
|
|
261
|
+
// file, the preload tiers, the Temporal lazy getter resolving @js-temporal/
|
|
262
|
+
// polyfill). The historical "nub-dependency package roots" walk — which existed
|
|
263
|
+
// solely to catch an ESM hop into get-tsconfig (and before that oxc-transform) — is
|
|
264
|
+
// gone with those packages.
|
|
422
265
|
function isNubInternalParent(parentURL) {
|
|
423
266
|
if (!parentURL) return false;
|
|
424
|
-
|
|
425
|
-
if (p.startsWith(RUNTIME_DIR_URL)) return true;
|
|
426
|
-
for (const root of nubGraphRoots()) {
|
|
427
|
-
if (p.startsWith(root)) return true;
|
|
428
|
-
}
|
|
429
|
-
return false;
|
|
267
|
+
return String(parentURL).startsWith(RUNTIME_DIR_URL);
|
|
430
268
|
}
|
|
431
269
|
|
|
432
270
|
// Resolve a specifier the way both hook tiers do. Returns `{ url, shortCircuit }`
|
|
@@ -438,8 +276,8 @@ export function resolveSpec(specifier, parentURL) {
|
|
|
438
276
|
// user's loader chain) never observes nub's internals. This MUST run before the
|
|
439
277
|
// node:/data:/builtin early-returns below, because those `return null` =
|
|
440
278
|
// DELEGATE to the user loader — and a nub-internal `import "node:module"` (e.g.
|
|
441
|
-
// from
|
|
442
|
-
// the R11 leak. See isNubInternalParent
|
|
279
|
+
// from a nub-dependency ESM entry) delegated to a strict user loader is exactly
|
|
280
|
+
// the R11 leak. See isNubInternalParent.
|
|
443
281
|
if (isNubInternalParent(parentURL)) {
|
|
444
282
|
if (specifier.startsWith("node:") || module.builtinModules.includes(specifier)) {
|
|
445
283
|
const url = specifier.startsWith("node:") ? specifier : `node:${specifier}`;
|
|
@@ -482,30 +320,16 @@ export function resolveSpec(specifier, parentURL) {
|
|
|
482
320
|
}
|
|
483
321
|
|
|
484
322
|
const parent = String(parentURL || "");
|
|
485
|
-
const parentExt = extname(parent);
|
|
486
|
-
|
|
487
|
-
// 4. tsconfig-paths (only for bare/aliased specifiers, not relative).
|
|
488
|
-
if (!specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("file:") && !isNodeModules(parent)) {
|
|
489
|
-
const parentDir = parent.startsWith("file:") ? dirname(fileURLToPath(parent)) : process.cwd();
|
|
490
|
-
const { matcher } = getTsconfigForDir(parentDir);
|
|
491
|
-
if (matcher) {
|
|
492
|
-
const mapped = matcher(specifier);
|
|
493
|
-
if (mapped && mapped.length > 0) {
|
|
494
|
-
for (const candidate of mapped) {
|
|
495
|
-
const resolved = tryResolveFile(candidate, parentExt);
|
|
496
|
-
if (resolved) return { url: pathToFileURL(resolved).href, shortCircuit: true };
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
323
|
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
324
|
+
// 4. The ADDITIVE TS resolution (tsconfig `paths`, extension probing, `.js`→`.ts`
|
|
325
|
+
// swap, directory index/`main`) — native. `resolveTs` is handed the parent's
|
|
326
|
+
// absolute FS path (or "" for a non-file: parent / the entry, where it falls back
|
|
327
|
+
// to cwd, matching the old `process.cwd()` parentDir). A non-null result is an
|
|
328
|
+
// additive hit nub owns; null falls through to Node's resolver (the compat
|
|
329
|
+
// boundary — node_modules, `exports`, bare/scoped specifiers stay Node's).
|
|
330
|
+
const parentPath = parent.startsWith("file:") ? fileURLToPath(parent) : "";
|
|
331
|
+
const resolved = resolveTs(specifier, parentPath);
|
|
332
|
+
if (resolved) return { url: pathToFileURL(resolved).href, shortCircuit: true };
|
|
509
333
|
|
|
510
334
|
return null;
|
|
511
335
|
}
|
|
@@ -522,36 +346,11 @@ export function resolveCjsPath(request, parentPath) {
|
|
|
522
346
|
module.builtinModules.includes(request)) {
|
|
523
347
|
return null;
|
|
524
348
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
!isNodeModules(parentPath || "")) {
|
|
531
|
-
const parentDir = parentPath ? dirname(parentPath) : process.cwd();
|
|
532
|
-
const { matcher } = getTsconfigForDir(parentDir);
|
|
533
|
-
if (matcher) {
|
|
534
|
-
const mapped = matcher(request);
|
|
535
|
-
if (mapped && mapped.length > 0) {
|
|
536
|
-
for (const candidate of mapped) {
|
|
537
|
-
const resolved = tryResolveFile(candidate, parentExt);
|
|
538
|
-
if (resolved) return resolved;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return null; // a plain bare package → let Node resolve it from node_modules
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Extensionless probing + .js→.ts swap for a relative specifier — only when the
|
|
546
|
-
// requiring file is itself TS (same TS_PARENT_EXTS gate as resolveSpec step 5).
|
|
547
|
-
if (parentPath && TS_PARENT_EXTS.has(parentExt) &&
|
|
548
|
-
(request.startsWith("./") || request.startsWith("../"))) {
|
|
549
|
-
const target = pathResolve(dirname(parentPath), request);
|
|
550
|
-
const resolved = tryResolveFile(target, parentExt);
|
|
551
|
-
if (resolved) return resolved;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
return null;
|
|
349
|
+
// The SAME native additive resolver as resolveSpec, returning an absolute path
|
|
350
|
+
// (not a URL). Vendored/clobber/builtin are import-only and never reach here. A
|
|
351
|
+
// null result (node_modules / `exports` / a plain bare package) falls through to
|
|
352
|
+
// Node's CJS resolver — the compat boundary.
|
|
353
|
+
return resolveTs(request, parentPath || "");
|
|
555
354
|
}
|
|
556
355
|
|
|
557
356
|
// Would `require()`-ing this resolved TS file need Node's require(esm)? An
|
|
@@ -572,68 +371,29 @@ export function requireTargetIsEsm(filePath, ext) {
|
|
|
572
371
|
}
|
|
573
372
|
|
|
574
373
|
// ── Module-format detection ─────────────────────────────────────────
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
//
|
|
583
|
-
//
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
/// Async, idempotent: ensure oxc-parser is loaded via dynamic import (the only
|
|
593
|
-
/// form that works below require(esm)). Compat-tier callers await this before the
|
|
594
|
-
/// synchronous detection below runs.
|
|
595
|
-
export async function ensureParser() {
|
|
596
|
-
if (_parseSync || _importTried) return;
|
|
597
|
-
_importTried = true;
|
|
598
|
-
try { _parseSync = (await import("oxc-parser")).parseSync; } catch { /* unavailable */ }
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function getParseSync() {
|
|
602
|
-
if (!_parseSync && !_requireTried) {
|
|
603
|
-
_requireTried = true;
|
|
604
|
-
try { _parseSync = __require("oxc-parser").parseSync; } catch { /* try ensureParser instead */ }
|
|
605
|
-
}
|
|
606
|
-
return _parseSync;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
// Does the source carry VALUE-level ESM syntax? Mirrors Node's
|
|
610
|
-
// `--experimental-detect-module`: type-only imports/exports are erased by oxc
|
|
611
|
-
// and must NOT count, but a value import/export, a bare `import "x"`, an
|
|
612
|
-
// `export {}` marker, `import.meta`, or top-level `await` all force ESM.
|
|
613
|
-
// Used only for the ambiguous extensions when package.json has no `type`.
|
|
614
|
-
function hasEsmSyntax(filePath, source, lang) {
|
|
615
|
-
const parse = getParseSync();
|
|
616
|
-
if (!parse) return true; // detection unavailable → default ESM (the common case)
|
|
617
|
-
let mod;
|
|
374
|
+
// Both signals nub needs to read off a file's syntax — the absent-`type` module
|
|
375
|
+
// format and the Stage-3-decorator guard — come from ONE native call into nub's
|
|
376
|
+
// N-API addon (`detectModuleInfo`, the oxc parser compiled in-process). There is
|
|
377
|
+
// no JS parser package anymore: `oxc-parser` (ESM-only, which used to need
|
|
378
|
+
// `require(esm)` on the fast tier and a dynamic-`import()` `ensureParser()` dance
|
|
379
|
+
// on the 18.19 compat tier) is gone, and with it the whole "is require(esm)
|
|
380
|
+
// available here?" fork. The native call is synchronous and works identically on
|
|
381
|
+
// every supported Node, so there is nothing to preload and no async warm-up — the
|
|
382
|
+
// former `ensureParser()` export is removed (its compat-tier callers just stop
|
|
383
|
+
// calling it). Used only for ambiguous extensions / the decorator guard; explicit
|
|
384
|
+
// `type` and `.mts`/`.cts` short-circuit before the parser runs.
|
|
385
|
+
function detectModuleInfo(filePath, source, lang) {
|
|
386
|
+
// Addon missing (should never happen in a real install): default to ESM for
|
|
387
|
+
// format (the common case) and "no decorators" for the guard — the same fallback
|
|
388
|
+
// the old oxc-parser-unavailable branches used.
|
|
389
|
+
if (!nubNative) return { hasValueEsmSyntax: true, hasDecorators: false };
|
|
618
390
|
try {
|
|
619
|
-
|
|
391
|
+
return nubNative.detectModuleInfo(filePath, source, lang);
|
|
620
392
|
} catch {
|
|
621
|
-
|
|
393
|
+
// Unparseable → CJS for format + no decorators (the transpile/V8 surfaces the
|
|
394
|
+
// real error), matching the old per-call catch blocks.
|
|
395
|
+
return { hasValueEsmSyntax: false, hasDecorators: false };
|
|
622
396
|
}
|
|
623
|
-
const valueImport = mod.staticImports.some(
|
|
624
|
-
(si) => si.entries.length === 0 || si.entries.some((e) => !e.isType),
|
|
625
|
-
);
|
|
626
|
-
const valueExport = mod.staticExports.some(
|
|
627
|
-
(se) => se.entries.length === 0 || se.entries.some((e) => !e.isType),
|
|
628
|
-
);
|
|
629
|
-
if (valueImport || valueExport || mod.importMetas.length > 0) return true;
|
|
630
|
-
// Top-level await: `hasModuleSyntax` is set with no static import/export/meta.
|
|
631
|
-
return (
|
|
632
|
-
mod.hasModuleSyntax &&
|
|
633
|
-
mod.staticImports.length === 0 &&
|
|
634
|
-
mod.staticExports.length === 0 &&
|
|
635
|
-
mod.importMetas.length === 0
|
|
636
|
-
);
|
|
637
397
|
}
|
|
638
398
|
|
|
639
399
|
// Map a transpiled file's extension + nearest package.json "type" to the module
|
|
@@ -647,7 +407,7 @@ export function moduleFormatFor(ext, pkgType, filePath, source) {
|
|
|
647
407
|
if (pkgType === "module") return "module";
|
|
648
408
|
if (pkgType === "commonjs") return "commonjs";
|
|
649
409
|
const lang = ext === ".tsx" ? "tsx" : ext === ".jsx" ? "jsx" : "ts";
|
|
650
|
-
return
|
|
410
|
+
return detectModuleInfo(filePath, source, lang).hasValueEsmSyntax ? "module" : "commonjs";
|
|
651
411
|
}
|
|
652
412
|
|
|
653
413
|
// The Stage-3-decorator rejection diagnostic. oxc does not lower TC39 Stage 3
|
|
@@ -672,54 +432,22 @@ function stage3DecoratorError(filePath) {
|
|
|
672
432
|
// member)? Used ONLY when legacy decorators are off, to surface a clear
|
|
673
433
|
// diagnostic instead of oxc's verbatim passthrough → V8 SyntaxError. The cheap
|
|
674
434
|
// `source.includes("@")` pre-filter in the caller keeps decorator-free files off
|
|
675
|
-
// the parser
|
|
676
|
-
// their members (incl. accessors), and to `export`/`export default` wrappers.
|
|
435
|
+
// the native parser. The walk now happens in Rust (detectModuleInfo's AST visit).
|
|
677
436
|
function hasDecoratorSyntax(filePath, source, lang) {
|
|
678
|
-
|
|
679
|
-
if (!parse) return false; // parser unavailable → let oxc/V8 surface the error
|
|
680
|
-
let program;
|
|
681
|
-
try {
|
|
682
|
-
program = parse(filePath, source, { lang }).program;
|
|
683
|
-
} catch {
|
|
684
|
-
return false; // unparseable → the transpile/V8 surfaces the real error
|
|
685
|
-
}
|
|
686
|
-
let found = false;
|
|
687
|
-
const visit = (node) => {
|
|
688
|
-
if (found || !node || typeof node !== "object") return;
|
|
689
|
-
if (Array.isArray(node)) {
|
|
690
|
-
for (const child of node) visit(child);
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
if (Array.isArray(node.decorators) && node.decorators.length > 0) {
|
|
694
|
-
found = true;
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
for (const k in node) {
|
|
698
|
-
if (k === "type" || k === "start" || k === "end") continue;
|
|
699
|
-
visit(node[k]);
|
|
700
|
-
if (found) return;
|
|
701
|
-
}
|
|
702
|
-
};
|
|
703
|
-
visit(program.body);
|
|
704
|
-
return found;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// Drop a trailing bare `export {};` — oxc injects it to preserve module-ness
|
|
708
|
-
// after stripping a file's only module syntax (e.g. a lone `import type`).
|
|
709
|
-
const EMPTY_EXPORT_MARKER = /(?:^|\n)[ \t]*export[ \t]*\{[ \t]*\}[ \t]*;?\s*$/;
|
|
710
|
-
function stripEmptyExportMarker(code) {
|
|
711
|
-
return code.replace(EMPTY_EXPORT_MARKER, "");
|
|
437
|
+
return detectModuleInfo(filePath, source, lang).hasDecorators;
|
|
712
438
|
}
|
|
713
439
|
|
|
714
440
|
// ── Transpile cache ─────────────────────────────────────────────────
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
//
|
|
720
|
-
//
|
|
721
|
-
//
|
|
722
|
-
|
|
441
|
+
// The transpile cache — `cacheGet` + transform-on-miss + post-processing
|
|
442
|
+
// (CJS empty-export strip, inline sourceMap, `//# sourceURL=`) + `cacheSet` — is
|
|
443
|
+
// ONE native call now (nub-native `transformCached`): the cache key (NUB_VERSION
|
|
444
|
+
// is the sole version component — a new release ships any emit change + a rebuilt
|
|
445
|
+
// addon), the 16-hex integrity prefix, the `c`/`m` format byte, and the atomic
|
|
446
|
+
// `*.tmp`-then-rename write all live in Rust, byte-identical to the old JS cache so
|
|
447
|
+
// warm caches survive. This JS file keeps only (a) the cache enable/disable signal
|
|
448
|
+
// and (b) the cache directory it passes IN, so the policy stays in JS and native
|
|
449
|
+
// just does the I/O against the dir nub hands it.
|
|
450
|
+
//
|
|
723
451
|
// Disable the transpile cache when (a) the permission model is active (writing a
|
|
724
452
|
// cache file may not be granted), or (b) the user set `NODE_COMPILE_CACHE=0` —
|
|
725
453
|
// Node's compile-cache disable signal, which nub honors as "no caching in this
|
|
@@ -736,50 +464,6 @@ if (!CACHE_DISABLED) {
|
|
|
736
464
|
}
|
|
737
465
|
}
|
|
738
466
|
|
|
739
|
-
function cacheKey(source) {
|
|
740
|
-
return getCreateHash()("sha256")
|
|
741
|
-
.update(NUB_VERSION).update("\0")
|
|
742
|
-
.update(CACHE_SCHEMA).update("\0")
|
|
743
|
-
.update(source)
|
|
744
|
-
.digest("hex");
|
|
745
|
-
}
|
|
746
|
-
// Each entry is stored as `<16-hex integrity prefix><body>`, where the prefix is
|
|
747
|
-
// the first 8 bytes of sha256(body). cacheGet re-checks it and treats ANY
|
|
748
|
-
// mismatch — truncation, on-disk corruption, bit-rot, external edits — as a miss,
|
|
749
|
-
// so the entry is re-transpiled and overwritten (self-heals) instead of feeding
|
|
750
|
-
// garbage to V8.
|
|
751
|
-
const CACHE_INTEGRITY_LEN = 16;
|
|
752
|
-
function cacheIntegrity(body) {
|
|
753
|
-
return getCreateHash()("sha256").update(body).digest("hex").slice(0, CACHE_INTEGRITY_LEN);
|
|
754
|
-
}
|
|
755
|
-
function cacheGet(key) {
|
|
756
|
-
if (!cacheDir) return null;
|
|
757
|
-
let raw;
|
|
758
|
-
try {
|
|
759
|
-
raw = readFileSync(join(cacheDir, key), "utf8");
|
|
760
|
-
} catch {
|
|
761
|
-
return null;
|
|
762
|
-
}
|
|
763
|
-
if (raw.length < CACHE_INTEGRITY_LEN) return null;
|
|
764
|
-
const body = raw.slice(CACHE_INTEGRITY_LEN);
|
|
765
|
-
if (raw.slice(0, CACHE_INTEGRITY_LEN) !== cacheIntegrity(body)) return null;
|
|
766
|
-
return body;
|
|
767
|
-
}
|
|
768
|
-
let cacheTmpCounter = 0;
|
|
769
|
-
function cacheSet(key, value) {
|
|
770
|
-
if (!cacheDir) return;
|
|
771
|
-
const finalPath = join(cacheDir, key);
|
|
772
|
-
// Atomic write: temp file in the same dir, then rename (atomic on POSIX +
|
|
773
|
-
// Windows same-volume), so a concurrent reader sees old-or-complete, never torn.
|
|
774
|
-
const tmpPath = `${finalPath}.${process.pid}.${cacheTmpCounter++}.tmp`;
|
|
775
|
-
try {
|
|
776
|
-
writeFileSync(tmpPath, cacheIntegrity(value) + value);
|
|
777
|
-
renameSync(tmpPath, finalPath);
|
|
778
|
-
} catch {
|
|
779
|
-
try { unlinkSync(tmpPath); } catch {}
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
|
|
783
467
|
// ── Bounded-cache maintenance ───────────────────────────────────────
|
|
784
468
|
const CACHE_MAX_BYTES = 512 * 1024 * 1024; // 512 MiB — bounds runaway growth, not normal use
|
|
785
469
|
const SWEEP_INTERVAL_MS = 24 * 60 * 60 * 1000; // ≤ one sweep per day
|
|
@@ -814,25 +498,14 @@ export function loadTranspile(url, ext) {
|
|
|
814
498
|
const filePath = fileURLToPath(url);
|
|
815
499
|
const source = readFileSync(filePath, "utf8");
|
|
816
500
|
const dir = dirname(filePath);
|
|
817
|
-
|
|
818
|
-
|
|
501
|
+
// The transform-relevant compilerOptions slice + the byte-for-byte cache-key
|
|
502
|
+
// component (`tsconfigHash`) both come from the native tsconfig reader.
|
|
503
|
+
const { compilerOptions: co, tsconfigHash } = getTsconfigForDir(dir);
|
|
819
504
|
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
//
|
|
823
|
-
// so a hit needs no re-detection.
|
|
505
|
+
// The nearest package.json `type` decides the format of an ambiguous extension
|
|
506
|
+
// (.ts/.tsx/.jsx); .mts/.cts are explicit so its lookup is skipped. The chosen
|
|
507
|
+
// format is folded into the cache key (and the entry's leading byte) by native.
|
|
824
508
|
const pkgType = ext === ".mts" || ext === ".cts" ? undefined : getPackageType(dir);
|
|
825
|
-
const tsconfigHash = co ? JSON.stringify(co) : "";
|
|
826
|
-
const key = cacheKey(source + "\0" + ext + "\0" + tsconfigHash + "\0" + (pkgType || ""));
|
|
827
|
-
const cached = cacheGet(key);
|
|
828
|
-
if (cached) {
|
|
829
|
-
return {
|
|
830
|
-
format: cached[0] === "c" ? "commonjs" : "module",
|
|
831
|
-
source: cached.slice(1),
|
|
832
|
-
shortCircuit: true,
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
|
|
836
509
|
const format = moduleFormatFor(ext, pkgType, filePath, source);
|
|
837
510
|
|
|
838
511
|
const lang = ext === ".tsx" ? "tsx" : ext === ".jsx" ? "jsx" : "ts";
|
|
@@ -867,39 +540,28 @@ export function loadTranspile(url, ext) {
|
|
|
867
540
|
// Stage-3 decorators: oxc returns errors:[] and emits the `@decorator` syntax
|
|
868
541
|
// verbatim, so the result-error check below never fires and V8 throws a bare
|
|
869
542
|
// SyntaxError. When legacy mode is off and decorator syntax is present, reject
|
|
870
|
-
// with the documented Option-A diagnostic instead.
|
|
543
|
+
// with the documented Option-A diagnostic instead. (Cheap `source.includes("@")`
|
|
544
|
+
// pre-filter keeps decorator-free files off the native parser; runs BEFORE the
|
|
545
|
+
// cache so the diagnostic surfaces even on what would be a warm hit.)
|
|
871
546
|
if (co?.experimentalDecorators !== true && source.includes("@") &&
|
|
872
547
|
hasDecoratorSyntax(filePath, source, lang)) {
|
|
873
548
|
throw stage3DecoratorError(filePath);
|
|
874
549
|
}
|
|
875
550
|
|
|
876
|
-
|
|
551
|
+
// cacheGet + transform-on-miss + post-process (CJS empty-export strip, inline
|
|
552
|
+
// sourceMap, sourceURL append) + cacheSet — ALL native, byte-identical on-disk.
|
|
553
|
+
// The cache key folds in ext + tsconfigHash + pkgType (same source, different
|
|
554
|
+
// type → different format → distinct entry). `cacheDir: null/undefined` is the
|
|
555
|
+
// JS enable/disable signal: native then skips all cache I/O and just transforms.
|
|
556
|
+
const formatByte = format === "commonjs" ? "c" : "m";
|
|
557
|
+
const result = nubNative.transformCached(
|
|
558
|
+
filePath, source, opts, ext, tsconfigHash || "", pkgType || "", formatByte, cacheDir ?? undefined,
|
|
559
|
+
);
|
|
877
560
|
if (result.errors.length > 0) {
|
|
878
561
|
const details = result.errors.map((e) => e.codeframe || e.message).join("\n\n");
|
|
879
562
|
throw new Error(`Transpile error in ${filePath}:\n${details}`);
|
|
880
563
|
}
|
|
881
|
-
|
|
882
|
-
let code = result.code;
|
|
883
|
-
// A CommonJS file must not carry oxc's injected ESM `export {};` marker (CJS
|
|
884
|
-
// body + ESM marker won't run). Node's strip-types emits no such marker.
|
|
885
|
-
if (format === "commonjs") code = stripEmptyExportMarker(code);
|
|
886
|
-
if (result.map) {
|
|
887
|
-
const map = typeof result.map === "string" ? JSON.parse(result.map) : result.map;
|
|
888
|
-
map.sourcesContent = [source];
|
|
889
|
-
code += `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(map)).toString("base64")}\n`;
|
|
890
|
-
}
|
|
891
|
-
// Append a `//# sourceURL=` magic comment, matching Node's native strip-types
|
|
892
|
-
// (lib/internal/modules/typescript.js: `return ${code}\n\n//# sourceURL=${filename}`).
|
|
893
|
-
// This is the marker V8/the inspector reads to set `scriptParsed.hasSourceURL =
|
|
894
|
-
// true` — the signal that a script is generated/transpiled rather than read
|
|
895
|
-
// verbatim from disk (test-inspector-strip-types asserts it). It coexists with
|
|
896
|
-
// the inline sourceMappingURL above (maps still drive stack frames); sourceURL
|
|
897
|
-
// only names the origin. Use the absolute file path, exactly as Node does.
|
|
898
|
-
code += `\n//# sourceURL=${filePath}\n`;
|
|
899
|
-
|
|
900
|
-
// Store the chosen format as a leading byte so cache hits skip re-detection.
|
|
901
|
-
cacheSet(key, (format === "commonjs" ? "c" : "m") + code);
|
|
902
|
-
return { format, source: code, shortCircuit: true };
|
|
564
|
+
return { format: result.format, source: result.code, shortCircuit: true };
|
|
903
565
|
}
|
|
904
566
|
|
|
905
567
|
// ── Data-format imports ─────────────────────────────────────────────
|