@deepseek-ai/dsh-app-boot 0.2.2-rc.1 → 0.2.2-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.i18n.yaml +4 -4
  2. package/README.md +4 -2
  3. package/README.zh.md +4 -2
  4. package/lib/index.js +37 -17
  5. package/lib/types/bundle-platform.d.ts +4 -2
  6. package/lib/types/bundle-platform.d.ts.map +1 -0
  7. package/lib/types/bundle-platform.js.map +1 -0
  8. package/lib/types/compatibility-preflight.d.ts.map +1 -0
  9. package/lib/types/compatibility-preflight.js.map +1 -0
  10. package/lib/types/config-schema/collect.d.ts.map +1 -0
  11. package/lib/types/config-schema/collect.js.map +1 -0
  12. package/lib/types/config-schema/document.d.ts.map +1 -0
  13. package/lib/types/config-schema/document.js.map +1 -0
  14. package/lib/types/config-schema/index.d.ts.map +1 -0
  15. package/lib/types/config-schema/index.js.map +1 -0
  16. package/lib/types/config-schema/native.d.ts.map +1 -0
  17. package/lib/types/config-schema/native.js.map +1 -0
  18. package/lib/types/config-schema/pattern.d.ts.map +1 -0
  19. package/lib/types/config-schema/pattern.js.map +1 -0
  20. package/lib/types/config-schema/projector.d.ts.map +1 -0
  21. package/lib/types/config-schema/projector.js.map +1 -0
  22. package/lib/types/config-schema/types.d.ts.map +1 -0
  23. package/lib/types/config-schema/types.js.map +1 -0
  24. package/lib/types/index.d.ts +1 -1
  25. package/lib/types/index.d.ts.map +1 -0
  26. package/lib/types/index.js.map +1 -0
  27. package/lib/types/package-meta.d.ts.map +1 -0
  28. package/lib/types/package-meta.js.map +1 -0
  29. package/lib/types/plugin-compatibility.d.ts.map +1 -0
  30. package/lib/types/plugin-compatibility.js.map +1 -0
  31. package/lib/types/profile-compatibility.d.ts.map +1 -0
  32. package/lib/types/profile-compatibility.js.map +1 -0
  33. package/lib/types/profile-context.d.ts.map +1 -0
  34. package/lib/types/profile-context.js.map +1 -0
  35. package/lib/types/profile-plugins.d.ts.map +1 -0
  36. package/lib/types/profile-plugins.js.map +1 -0
  37. package/lib/types/profile-resolution/legacy-links.d.ts.map +1 -0
  38. package/lib/types/profile-resolution/legacy-links.js.map +1 -0
  39. package/lib/types/profile-resolution/resolver.d.ts.map +1 -0
  40. package/lib/types/profile-resolution/resolver.js.map +1 -0
  41. package/lib/types/profile-resolution/service.d.ts.map +1 -0
  42. package/lib/types/profile-resolution/service.js.map +1 -0
  43. package/lib/types/profile-resolution/worker-bootstrap.d.ts.map +1 -0
  44. package/lib/types/profile-resolution/worker-bootstrap.js.map +1 -0
  45. package/lib/types/profile-sanitize.d.ts.map +1 -0
  46. package/lib/types/profile-sanitize.js.map +1 -0
  47. package/lib/types/profile.d.ts.map +1 -0
  48. package/lib/types/profile.js.map +1 -0
  49. package/lib/types/startup-diagnostics.d.ts.map +1 -0
  50. package/lib/types/startup-diagnostics.js.map +1 -0
  51. package/package.json +15 -11
  52. package/src/bundle-platform.ts +35 -0
  53. package/src/compatibility-preflight.ts +187 -0
  54. package/src/config-schema/collect.ts +249 -0
  55. package/src/config-schema/document.ts +169 -0
  56. package/src/config-schema/index.ts +36 -0
  57. package/src/config-schema/native.ts +16 -0
  58. package/src/config-schema/pattern.ts +62 -0
  59. package/src/config-schema/projector.ts +455 -0
  60. package/src/config-schema/types.ts +95 -0
  61. package/src/index.ts +1286 -0
  62. package/src/package-meta.ts +200 -0
  63. package/src/plugin-compatibility.ts +117 -0
  64. package/src/profile-compatibility.ts +141 -0
  65. package/src/profile-context.ts +85 -0
  66. package/src/profile-plugins.ts +133 -0
  67. package/src/profile-resolution/legacy-links.ts +33 -0
  68. package/src/profile-resolution/resolver.ts +1072 -0
  69. package/src/profile-resolution/service.ts +148 -0
  70. package/src/profile-resolution/worker-bootstrap.ts +12 -0
  71. package/src/profile-sanitize.ts +34 -0
  72. package/src/profile.ts +762 -0
  73. package/src/startup-diagnostics.ts +104 -0
@@ -0,0 +1,1072 @@
1
+ /** In-memory profile package routing for Node's default ESM and CommonJS loaders. */
2
+
3
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'
4
+ import { createRequire, isBuiltin } from 'node:module'
5
+ import { basename, dirname, isAbsolute, join, resolve, sep } from 'node:path'
6
+ import { fileURLToPath, pathToFileURL } from 'node:url'
7
+ import { getEnvironmentData, setEnvironmentData } from 'node:worker_threads'
8
+ import type { ModuleLoaderV1, ModuleLoaderV2, ResolveResult } from '@deepseek-ai/cordis-plugin-loader'
9
+ import { imports as resolvePackageImports, type Package as ResolvePackageManifest } from 'resolve.exports'
10
+ import type { RuntimeResolutionEntry, RuntimeResolution } from '../profile.ts'
11
+
12
+ const WORKER_RESOLUTION_KEY = '@deepseek-ai/dsh-app-boot/profile-resolution'
13
+ const EMPTY_ATTRIBUTES: ImportAttributes = Object.freeze({})
14
+
15
+ interface CommonJsParent {
16
+ filename?: string | null
17
+ parent?: CommonJsParent | null
18
+ paths?: string[]
19
+ }
20
+
21
+ interface CommonJsOptions {
22
+ paths?: string[]
23
+ conditions?: ReadonlySet<string>
24
+ }
25
+
26
+ interface CommonJsModule {
27
+ new(id?: string, parent?: CommonJsParent): CommonJsParent
28
+ _nodeModulePaths(path: string): string[]
29
+ _resolveFilename(
30
+ request: string, parent: CommonJsParent | null | undefined, isMain: boolean, options?: CommonJsOptions,
31
+ ): string
32
+ }
33
+
34
+ interface InternalModules {
35
+ esm: ModuleLoaderV1 | ModuleLoaderV2
36
+ esmDefaultResolve: (
37
+ specifier: string,
38
+ context: { parentURL?: string; conditions?: readonly string[] },
39
+ ) => ResolveResult
40
+ esmConditions: readonly string[]
41
+ cjs: CommonJsModule
42
+ cjsConditions: ReadonlySet<string>
43
+ modern: boolean
44
+ }
45
+
46
+ type EsmResolve = (
47
+ request: string, parent: string | undefined, attributes: ImportAttributes,
48
+ ) => ResolveResult | Promise<ResolveResult>
49
+
50
+ /**
51
+ * Where Node continues a scoped bare request. `native` keeps the importer's own lookup: the chain is
52
+ * answered below the interception layer. `interception` answers at the interception layer from the
53
+ * entry's declaring manifest; `after` anchors Node's chain above the layer for a CommonJS subpath the
54
+ * entry lacks. `native-after-interception` resumes Node's chain from `parent`: the interception layer for a
55
+ * name without an entry, or the layer above it after an `interception` subpath miss.
56
+ */
57
+ type ResolutionRoute =
58
+ | { readonly kind: 'interception'; readonly entry: RuntimeResolutionEntry; readonly after: string }
59
+ | { readonly kind: 'native-after-interception'; readonly parent: string }
60
+ | { readonly kind: 'native'; readonly packageDir?: string }
61
+
62
+ interface ResolutionRouteState {
63
+ readonly route: ResolutionRoute
64
+ packageDir?: string
65
+ esm?: ResolveResult
66
+ cjs?: string
67
+ }
68
+
69
+ interface CommonJsLookup {
70
+ readonly cacheable: boolean
71
+ resolveNative(searchPaths: readonly string[]): string
72
+ resolveEntry(entry: RuntimeResolutionEntry, after: string): string
73
+ }
74
+
75
+ /** The lookup positions that one interception layer fixes on an importer's ancestor chain. */
76
+ interface LayerPositions {
77
+ /** Search paths with this prefix lie below the layer and are answered by their physical content. */
78
+ readonly localPrefix: string
79
+ /** Manifest anchoring Node's lookup at the layer's physical directory. */
80
+ readonly nativeAfter: string
81
+ /** Manifest anchoring Node's lookup above the layer after a CommonJS subpath miss. */
82
+ readonly after: string
83
+ }
84
+
85
+ /**
86
+ * The interception layer of one importer. A profile's layer is its parent's node_modules and answers every entry
87
+ * for the active profile. Linked importers apply current peer declarations at each ancestor node_modules
88
+ * position without memoizing the selected route.
89
+ */
90
+ type InterceptionLayer =
91
+ | LayerPositions & { readonly kind: 'profile'; readonly active: boolean }
92
+ | { readonly kind: 'linked' }
93
+
94
+ interface ParentRoutes {
95
+ readonly parent: string
96
+ readonly layer: InterceptionLayer
97
+ readonly requests: Map<string, ResolutionRouteState>
98
+ selfReferenceName?: string | false | null
99
+ }
100
+
101
+ type ResolutionRoutes = Map<string, ParentRoutes | false>
102
+
103
+ /** A published entry with the real directory its modules load from. */
104
+ interface CompiledEntry extends RuntimeResolutionEntry {
105
+ readonly realDir: string
106
+ }
107
+
108
+ interface CompiledResolution {
109
+ readonly entries: ReadonlyMap<string, CompiledEntry>
110
+ readonly profilesDir: string
111
+ readonly profileDir: string | undefined
112
+ readonly profilePaths: readonly string[]
113
+ readonly profile: readonly string[]
114
+ readonly installationPaths: readonly string[]
115
+ readonly linkedPaths: readonly string[]
116
+ readonly localPackageNames: ReadonlySet<string>
117
+ readonly esmRoutes: ResolutionRoutes
118
+ readonly cjsRoutes: ResolutionRoutes
119
+ }
120
+
121
+ /** A successor resolution the running process cannot adopt, such as a retained mapping in another directory; it waits for a restart. */
122
+ export class ResolutionRestartRequired extends Error {
123
+ /** @param message Diagnostic naming the rejected changes. */
124
+ constructor(message: string) {
125
+ super(message)
126
+ this.name = 'ResolutionRestartRequired'
127
+ }
128
+ }
129
+
130
+ /** Active interception in one Node isolate. */
131
+ export interface RuntimeInterception {
132
+ /**
133
+ * Locate a bare package without requiring one of its exports.
134
+ * A package subpath selects its package directory without resolving or validating the requested file.
135
+ * @param specifier - bare package or package-subpath specifier.
136
+ * @param parentURL - file URL whose lookup order applies.
137
+ * @returns selected package directory, or undefined when it is absent.
138
+ */
139
+ packageDir(specifier: string, parentURL: string): string | undefined
140
+ /**
141
+ * Atomically publish a complete successor and fresh caches. Profile-scoped mappings, local names, and linked
142
+ * roots may be removed; removed roots stop intercepting uncovered directories. A retained profile mapping may
143
+ * change its version and declarer when its normalized directory and scope stay unchanged, as when a package is
144
+ * upgraded in place. Existing modules and Node caches remain intact: callers remove a package only after its
145
+ * plugins stop, and replace the loaded modules of a package upgraded in place separately.
146
+ * A previously published link name may point to another real directory; callers stop the plugins of its previous
147
+ * target first, as removing and installing the package again does.
148
+ * @param successor - fully constructed generation retaining the installation mappings.
149
+ * @throws when the profile scope changes; and `ResolutionRestartRequired` when a retained profile mapping's real
150
+ * directory or scope, or an installation version or declarer changes; an installation mapping is removed; or a new
151
+ * local name overrides an existing mapping.
152
+ */
153
+ replace(successor: RuntimeResolution): void
154
+ /** Restore the native resolver methods. Interceptions dispose in reverse order. */
155
+ dispose(): void
156
+ }
157
+
158
+ /**
159
+ * Split a bare request into its package name without allocating path segments.
160
+ * @param request - module specifier to classify.
161
+ * @returns the bare package name, or undefined for non-package requests.
162
+ */
163
+ export function barePackageName(request: string): string | undefined {
164
+ if (!request || request[0] === '.' || request[0] === '/' || request[0] === '\\'
165
+ || request[0] === '#' || request.includes(':') || isBuiltin(request)) return
166
+ const first = request.indexOf('/')
167
+ if (request[0] !== '@') return first < 0 ? request : request.slice(0, first)
168
+ if (first < 0) return
169
+ const second = request.indexOf('/', first + 1)
170
+ return second < 0 ? request : request.slice(0, second)
171
+ }
172
+
173
+ /** The JavaScript and native real paths of an existing directory; none for a directory that no longer exists. */
174
+ function realSpellings(path: string): string[] {
175
+ try {
176
+ // A packed worker's file system has no native realpath.
177
+ const native: unknown = Reflect.get(realpathSync, 'native')
178
+ return [realpathSync(path), ...typeof native === 'function' ? [String(Reflect.apply(native, realpathSync, [path]))] : []]
179
+ } catch (_error) {
180
+ // A removed previous directory keeps only its recorded spelling.
181
+ return []
182
+ }
183
+ }
184
+
185
+ function canonicalPath(path: string): string {
186
+ try {
187
+ return realpathSync(path)
188
+ } catch {
189
+ // A runtime resolution may name a profile scope before that directory is materialized.
190
+ return resolve(path)
191
+ }
192
+ }
193
+
194
+ function prefixes(path: string): readonly string[] {
195
+ const configured = resolve(path) + sep
196
+ const canonical = canonicalPath(path) + sep
197
+ return canonical === configured ? [configured] : [configured, canonical]
198
+ }
199
+
200
+ function compileResolution(resolution: RuntimeResolution): CompiledResolution {
201
+ return {
202
+ entries: new Map(resolution.entries.map(entry => [entry.name, { ...entry, realDir: canonicalPath(entry.packageDir) }])),
203
+ profilesDir: resolution.profilesDir,
204
+ profileDir: resolution.profileDir,
205
+ profilePaths: prefixes(resolution.profilesDir),
206
+ profile: resolution.profileDir === undefined ? [] : prefixes(resolution.profileDir),
207
+ installationPaths: [...new Set(resolution.entries
208
+ .filter(entry => entry.scope === 'installation')
209
+ .flatMap(entry => prefixes(entry.packageDir)))],
210
+ linkedPaths: resolution.linkedRoots.flatMap(root => prefixes(root.realPath)),
211
+ localPackageNames: new Set(resolution.localPackageNames),
212
+ esmRoutes: new Map(),
213
+ cjsRoutes: new Map(),
214
+ }
215
+ }
216
+
217
+ /**
218
+ * The interception layer of a profile is `<profileParent>/node_modules`: an entry occupies its name there, so its
219
+ * subpath misses continue above it, and a name without an entry continues at it.
220
+ */
221
+ function computeProfileLayer(dir: string, active: boolean): InterceptionLayer {
222
+ const profileParent = dirname(dir)
223
+ return {
224
+ kind: 'profile', active,
225
+ localPrefix: dir + sep,
226
+ nativeAfter: join(profileParent, 'package.json'),
227
+ after: join(dirname(profileParent), 'package.json'),
228
+ }
229
+ }
230
+
231
+ /** The interception layer of a module path: from its profile directory inside a profiles tree, or its linked root. */
232
+ function findInterceptionLayer(path: string, resolution: CompiledResolution): InterceptionLayer | undefined {
233
+ const treeRoot = resolution.profilePaths.find(prefix => path.startsWith(prefix))
234
+ const activeProfile = resolution.profile.find(prefix => path.startsWith(prefix))
235
+ const dir = treeRoot !== undefined ? profileChild(path, treeRoot) : activeProfile?.slice(0, -1)
236
+ if (dir !== undefined) return computeProfileLayer(dir, activeProfile !== undefined)
237
+ if (!startsWithin(path, resolution.linkedPaths)) return undefined
238
+ if (startsWithin(path, resolution.installationPaths)) return undefined
239
+ return { kind: 'linked' }
240
+ }
241
+
242
+ /** Package names a directory's current manifest lists as peers; an unreadable manifest lists none. */
243
+ function readPeerNames(directory: string): ReadonlySet<string> {
244
+ try {
245
+ const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')) as {
246
+ peerDependencies?: unknown
247
+ }
248
+ const peers = manifest.peerDependencies
249
+ return new Set(peers !== null && typeof peers === 'object' ? Object.keys(peers) : [])
250
+ } catch (_error) {
251
+ // Node reports the unreadable or invalid manifest itself when it resolves the package's own imports.
252
+ return new Set()
253
+ }
254
+ }
255
+
256
+ function startsWithin(path: string, roots: readonly string[]): boolean {
257
+ for (const root of roots) {
258
+ if (path.startsWith(root)) return true
259
+ }
260
+ return false
261
+ }
262
+
263
+ /**
264
+ * The profile directory owning a module inside the profiles tree: the first path segment below the tree root.
265
+ * @param path - module path below `treePrefix`.
266
+ * @param treePrefix - profiles directory with a trailing separator.
267
+ * @returns the profile directory without a trailing separator.
268
+ */
269
+ function profileChild(path: string, treePrefix: string): string {
270
+ const rest = path.slice(treePrefix.length)
271
+ const end = rest.indexOf(sep)
272
+ return treePrefix + (end < 0 ? rest : rest.slice(0, end))
273
+ }
274
+
275
+ function nativePackageDir(parent: string, name: string): string | undefined {
276
+ for (const searchPath of createRequire(parent).resolve.paths(name) as string[]) {
277
+ const candidate = join(searchPath, name)
278
+ if (existsSync(join(candidate, 'package.json'))) return candidate
279
+ }
280
+ return undefined
281
+ }
282
+
283
+ function localPackageCandidate(searchPath: string, name: string, flavor: 'esm' | 'cjs'): string | undefined {
284
+ const candidate = join(searchPath, name)
285
+ const stat = statSync(candidate, { throwIfNoEntry: false })
286
+ const found = flavor === 'esm'
287
+ ? stat?.isDirectory() === true
288
+ : stat !== undefined
289
+ || ['.js', '.json', '.node'].some(extension => existsSync(candidate + extension))
290
+ return found ? candidate : undefined
291
+ }
292
+
293
+ function selfReferenceName(parent: string): string | false | null {
294
+ let current = dirname(parent)
295
+ while (true) {
296
+ const manifestPath = join(current, 'package.json')
297
+ if (existsSync(manifestPath)) {
298
+ let manifest: Record<string, unknown>
299
+ try {
300
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as Record<string, unknown>
301
+ } catch (_error) {
302
+ // Only Node decides whether this request consumes an invalid package manifest.
303
+ return null
304
+ }
305
+ return typeof manifest.name === 'string' && manifest.exports != null
306
+ ? manifest.name
307
+ : false
308
+ }
309
+ /* v8 ignore next -- scoped module requests normally find an owning manifest before node_modules. */
310
+ if (basename(current) === 'node_modules') return false
311
+ const next = dirname(current)
312
+ if (next === current) return false
313
+ current = next
314
+ }
315
+ }
316
+
317
+ function packageImportsTarget(
318
+ parent: string, request: string, conditions: Iterable<string>,
319
+ ): { specifier: string; parentURL: string } | undefined {
320
+ let current = dirname(parent)
321
+ while (true) {
322
+ const manifestPath = join(current, 'package.json')
323
+ if (existsSync(manifestPath)) {
324
+ let manifest: ResolvePackageManifest
325
+ try {
326
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as ResolvePackageManifest
327
+ } catch (_error) {
328
+ // The native resolver retains invalid package-target and manifest diagnostics.
329
+ /* v8 ignore next -- native resolution cannot report MODULE_NOT_FOUND after an invalid scope manifest */
330
+ return undefined
331
+ }
332
+ try {
333
+ const target = resolvePackageImports(manifest, request, {
334
+ conditions: [...conditions],
335
+ unsafe: true,
336
+ })?.[0]
337
+ return target !== undefined && barePackageName(target) !== undefined
338
+ ? { specifier: target, parentURL: pathToFileURL(manifestPath).href }
339
+ : undefined
340
+ } catch (_error) {
341
+ // The native resolver retains missing mappings and unmatched-condition diagnostics.
342
+ /* v8 ignore next -- native resolution cannot report MODULE_NOT_FOUND before selecting a valid mapping */
343
+ return undefined
344
+ }
345
+ }
346
+ if (basename(current) === 'node_modules') return undefined
347
+ const next = dirname(current)
348
+ /* v8 ignore next -- a MODULE_NOT_FOUND package-import target always has an owning package scope */
349
+ if (next === current) return undefined
350
+ current = next
351
+ }
352
+ }
353
+
354
+ function packageSearchPaths(
355
+ entry: RuntimeResolutionEntry, request: string, cjs: CommonJsModule,
356
+ ): string[] {
357
+ const name = barePackageName(request)
358
+ /* v8 ignore next -- interception routes are created only for bare package requests */
359
+ if (name === undefined) return cjs._nodeModulePaths(dirname(entry.declarer))
360
+ const suffix = sep + name.split('/').join(sep)
361
+ return entry.packageDir.endsWith(suffix)
362
+ ? [entry.packageDir.slice(0, -suffix.length)]
363
+ : cjs._nodeModulePaths(dirname(entry.declarer))
364
+ }
365
+
366
+ function localCandidateOwnsResolution(candidate: string, resolved: string, request: string, name: string): boolean {
367
+ if (startsWithin(resolved, prefixes(candidate))) return true
368
+ if (sameResolution(candidate, resolved)) return true
369
+ if (['.js', '.json', '.node'].some(extension => sameResolution(candidate + extension, resolved))) return true
370
+ /* v8 ignore next -- a bounded native lookup can escape a candidate only through its root legacy main */
371
+ if (request !== name) return false
372
+ try {
373
+ const manifest = JSON.parse(readFileSync(join(candidate, 'package.json'), 'utf8')) as Record<string, unknown>
374
+ /* v8 ignore next -- a bounded native lookup outside the package directory requires a legacy main */
375
+ if (typeof manifest.main !== 'string') return false
376
+ const main = createRequire(join(candidate, 'package.json')).resolve(resolve(candidate, manifest.main))
377
+ return sameResolution(main, resolved)
378
+ } catch (_error) {
379
+ // Native resolution already owns malformed manifests and missing legacy entries.
380
+ /* v8 ignore next -- this helper runs only after the same native resolution succeeded */
381
+ return false
382
+ }
383
+ }
384
+
385
+ function isUnselectedPackageMiss(error: unknown): boolean {
386
+ const failure = error as NodeJS.ErrnoException & { path?: unknown }
387
+ return failure.code === 'MODULE_NOT_FOUND' && failure.path === undefined
388
+ }
389
+
390
+ function sameResolution(left: string, right: string): boolean {
391
+ if (left === right) return true
392
+ return canonicalPath(left) === canonicalPath(right)
393
+ }
394
+
395
+ /** A directory as given and in its JavaScript and native real paths, each with a trailing separator. */
396
+ function spellings(dir: string): string[] {
397
+ const result = new Set([dir + sep])
398
+ for (const real of realSpellings(dir)) result.add(real + sep)
399
+ return [...result]
400
+ }
401
+
402
+ /**
403
+ * The previous real directories of packages that moved, each mapped to the current one. Node keeps the real path of
404
+ * every link it resolved through and never refreshes it, so a lookup through a link that now points elsewhere still
405
+ * selects a file below the target Node saw first; {@link MovedDirectories.relocate} selects the same file below the
406
+ * current target instead.
407
+ */
408
+ class MovedDirectories {
409
+ /** Previous directory to current directory, both with a trailing separator. */
410
+ private readonly targets = new Map<string, string>()
411
+
412
+ /**
413
+ * Record that a package moved from one directory to another. Every spelling of the previous directory leads to the
414
+ * current one. A directory that an earlier move led to the previous one now leads to the current one as well, since
415
+ * Node still selects files below the earliest target.
416
+ * @param from Previous real directory.
417
+ * @param to Current real directory.
418
+ */
419
+ record(from: string, to: string): void {
420
+ const previous = spellings(from)
421
+ const current = to + sep
422
+ for (const [key, value] of this.targets) {
423
+ if (previous.includes(value)) this.targets.set(key, current)
424
+ }
425
+ for (const key of previous) this.targets.set(key, current)
426
+ }
427
+
428
+ /**
429
+ * Forget the previous directories that are current again, in any spelling, and records that lead to themselves.
430
+ * @param current The real directories the published resolution maps.
431
+ */
432
+ forget(current: readonly string[]): void {
433
+ const active = new Set<string>()
434
+ for (const dir of current) for (const spelling of spellings(dir)) active.add(spelling)
435
+ for (const [key, value] of [...this.targets]) {
436
+ if (active.has(key) || key === value) this.targets.delete(key)
437
+ }
438
+ }
439
+
440
+ /**
441
+ * The same file below the current directory when it exists, for a file below a previous directory that an importer
442
+ * outside that directory selected. An importer inside the previous directory keeps its own package's files. The
443
+ * innermost previous directory decides.
444
+ * @param filename Selected file.
445
+ * @param parent Importer path.
446
+ * @returns The file to load.
447
+ */
448
+ relocate(filename: string, parent: string | undefined): string {
449
+ let from = ''
450
+ for (const dir of this.targets.keys()) {
451
+ if (filename.startsWith(dir) && dir.length > from.length) from = dir
452
+ }
453
+ if (from === '' || parent?.startsWith(from) === true) return filename
454
+ const relocated = (this.targets.get(from) as string) + filename.slice(from.length)
455
+ return existsSync(relocated) ? relocated : filename
456
+ }
457
+ }
458
+
459
+ /** One mutable pointer to the immutable runtime resolution. */
460
+ class ResolutionRouter {
461
+ private current: CompiledResolution
462
+ private linkedTargets: Map<string, string>
463
+ private readonly moved = new MovedDirectories()
464
+
465
+ constructor(resolution: RuntimeResolution, private readonly nodeModulePaths: (directory: string) => string[]) {
466
+ this.current = compileResolution(resolution)
467
+ this.linkedTargets = new Map(resolution.linkedRoots.map(root => [root.name, root.realPath]))
468
+ }
469
+
470
+ replace(successor: RuntimeResolution): void {
471
+ const entries = new Map(successor.entries.map(entry => [entry.name, entry]))
472
+ if (successor.profilesDir !== this.current.profilesDir
473
+ || successor.profileDir !== this.current.profileDir) {
474
+ throw new Error('profile resolution: a successor cannot change its profile scope')
475
+ }
476
+ const rejected: string[] = []
477
+ const moves: [string, string][] = []
478
+ for (const [name, current] of this.current.entries) {
479
+ const next = entries.get(name)
480
+ if (next === undefined && current.scope === 'profile') continue
481
+ if (next !== undefined && current.scope === 'profile' && next.scope === 'profile') {
482
+ // Real directories are compared: a link keeps its path when its target changes.
483
+ if (current.realDir === canonicalPath(next.packageDir)) continue
484
+ rejected.push(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`)
485
+ continue
486
+ }
487
+ if (next === undefined
488
+ || !sameResolution(current.packageDir, next.packageDir)
489
+ || (current.scope === 'installation' && !sameResolution(current.declarer, next.declarer))
490
+ || (current.scope === 'installation' && current.version !== next.version)
491
+ || current.scope !== next.scope) {
492
+ rejected.push(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`)
493
+ }
494
+ }
495
+ const localPackageNames = new Set(successor.localPackageNames)
496
+ for (const name of localPackageNames) {
497
+ if (!this.current.localPackageNames.has(name) && this.current.entries.has(name)) {
498
+ rejected.push(`profile resolution: overriding ${JSON.stringify(name)} locally requires a process restart`)
499
+ }
500
+ }
501
+ // A link name pointing elsewhere, or installed again as a directory, moves its package. Names whose link and mapping
502
+ // are gone stay recorded, so the package that returns under the name is compared with its last target.
503
+ const targets = new Map(this.linkedTargets)
504
+ for (const [name, previous] of this.linkedTargets) {
505
+ const root = successor.linkedRoots.find(candidate => candidate.name === name)
506
+ const entry = entries.get(name)
507
+ const target = root?.realPath ?? (entry?.scope === 'profile' ? canonicalPath(entry.packageDir) : undefined)
508
+ if (target === undefined || sameResolution(previous, target)) continue
509
+ moves.push([previous, target])
510
+ targets.set(name, target)
511
+ }
512
+ if (rejected.length > 0) throw new ResolutionRestartRequired(rejected.join('; '))
513
+ const next = compileResolution(successor)
514
+ for (const { name, realPath } of successor.linkedRoots) targets.set(name, realPath)
515
+ this.linkedTargets = targets
516
+ for (const [from, to] of moves) this.moved.record(from, to)
517
+ const current: string[] = []
518
+ for (const entry of next.entries.values()) current.push(entry.realDir)
519
+ for (const root of successor.linkedRoots) current.push(root.realPath)
520
+ this.moved.forget(current)
521
+ this.current = next
522
+ }
523
+
524
+ /**
525
+ * The file to load instead of one Node selected through a link that has moved since; see {@link MovedDirectories}.
526
+ * @param filename Selected file.
527
+ * @param parent Importer path.
528
+ * @returns The file to load.
529
+ */
530
+ relocate(filename: string, parent: string | undefined): string {
531
+ return this.moved.relocate(filename, parent)
532
+ }
533
+
534
+ /** Whether a module path has an interception layer: it lies in the profiles tree, the active profile, or a linked root. */
535
+ hasInterceptionLayerForPath(path: string): boolean {
536
+ return findInterceptionLayer(path, this.current) !== undefined
537
+ }
538
+
539
+ /** Whether an importer URL is scoped; the answer is memoized with the importer's routes. */
540
+ hasInterceptionLayerForUrl(parentURL: string): boolean {
541
+ return this.getOrCreateParentRoutesForUrl(parentURL) !== false
542
+ }
543
+
544
+ private createParentRoutes(parent: string, resolution: CompiledResolution): ParentRoutes | undefined {
545
+ const layer = findInterceptionLayer(parent, resolution)
546
+ return layer === undefined ? undefined : { parent, layer, requests: new Map() }
547
+ }
548
+
549
+ private getOrCreateParentRoutesForUrl(parentURL: string): ParentRoutes | false {
550
+ const resolution = this.current
551
+ let parentRoutes = resolution.esmRoutes.get(parentURL)
552
+ if (parentRoutes !== undefined) return parentRoutes
553
+ let parent: string | undefined
554
+ try {
555
+ parent = fileURLToPath(parentURL)
556
+ } catch {
557
+ // Only file URLs can lie inside a profile tree or linked root.
558
+ parent = undefined
559
+ }
560
+ parentRoutes = parent === undefined ? false : this.createParentRoutes(parent, resolution) ?? false
561
+ resolution.esmRoutes.set(parentURL, parentRoutes)
562
+ return parentRoutes
563
+ }
564
+
565
+ private routeScoped(
566
+ request: string,
567
+ parentRoutes: ParentRoutes,
568
+ resolution: CompiledResolution,
569
+ flavor: 'esm' | 'cjs',
570
+ cjs?: CommonJsLookup,
571
+ ): ResolutionRouteState | undefined {
572
+ const { parent, layer, requests } = parentRoutes
573
+ const memo = layer.kind === 'profile' && (cjs === undefined || cjs.cacheable)
574
+ const name = barePackageName(request)
575
+ if (name === undefined) return undefined
576
+
577
+ if (parentRoutes.selfReferenceName === undefined || !memo) {
578
+ parentRoutes.selfReferenceName = selfReferenceName(parent)
579
+ }
580
+ if (parentRoutes.selfReferenceName === name || parentRoutes.selfReferenceName === null) {
581
+ const state = { route: { kind: 'native' as const } }
582
+ if (memo) requests.set(request, state)
583
+ return state
584
+ }
585
+
586
+ if (layer.kind === 'linked') return this.routeLinked(request, name, parent, resolution, flavor, cjs)
587
+
588
+ const target = resolution.entries.get(name)
589
+ const candidates: string[] = []
590
+ const localSearchPaths: string[] = []
591
+ for (const searchPath of createRequire(parent).resolve.paths(name) as string[]) {
592
+ if (!searchPath.startsWith(layer.localPrefix)) break
593
+ localSearchPaths.push(searchPath)
594
+ const candidate = localPackageCandidate(searchPath, name, flavor)
595
+ if (candidate !== undefined) candidates.push(candidate)
596
+ }
597
+ if (candidates.length > 0) {
598
+ if (cjs !== undefined) {
599
+ try {
600
+ const resolved = cjs.resolveNative(localSearchPaths)
601
+ const selected = candidates.find(candidate => (
602
+ localCandidateOwnsResolution(candidate, resolved, request, name)
603
+ ))
604
+ if (selected !== undefined) {
605
+ const state = {
606
+ route: { kind: 'native' as const, packageDir: selected },
607
+ packageDir: selected,
608
+ cjs: resolved,
609
+ }
610
+ if (memo) requests.set(request, state)
611
+ return state
612
+ }
613
+ } catch (error) {
614
+ if (!isUnselectedPackageMiss(error)) throw error
615
+ }
616
+ } else {
617
+ const selected = candidates[0] as string
618
+ const state = {
619
+ route: { kind: 'native' as const, packageDir: selected },
620
+ packageDir: selected,
621
+ }
622
+ requests.set(request, state)
623
+ return state
624
+ }
625
+ }
626
+
627
+ const route: ResolutionRoute = target !== undefined
628
+ && (target.scope === 'installation' || layer.active)
629
+ ? { kind: 'interception', entry: target, after: layer.after }
630
+ : { kind: 'native-after-interception', parent: layer.nativeAfter }
631
+ const state: ResolutionRouteState = { route }
632
+ if (route.kind === 'interception' && memo) requests.set(request, state)
633
+ return state
634
+ }
635
+
636
+ private routeLinked(
637
+ request: string, name: string, parent: string, resolution: CompiledResolution,
638
+ flavor: 'esm' | 'cjs', cjs: CommonJsLookup | undefined,
639
+ ): ResolutionRouteState {
640
+ const target = resolution.entries.get(name)
641
+ if (target === undefined) return { route: { kind: 'native' } }
642
+ const ancestors = this.nodeModulePaths(dirname(parent))
643
+ const ancestorSet = new Set(ancestors)
644
+ const searchPaths = flavor === 'esm' ? ancestors : createRequire(parent).resolve.paths(name) as string[]
645
+ for (const searchPath of searchPaths) {
646
+ const directory = dirname(searchPath)
647
+ if (ancestorSet.has(searchPath) && readPeerNames(directory).has(name)) {
648
+ const route: ResolutionRoute = { kind: 'interception', entry: target, after: join(dirname(directory), 'package.json') }
649
+ if (cjs === undefined) return { route }
650
+ try {
651
+ const resolved = cjs.resolveEntry(target, route.after)
652
+ if (localCandidateOwnsResolution(target.packageDir, resolved, request, name)) {
653
+ return { route, packageDir: target.packageDir, cjs: resolved }
654
+ }
655
+ } catch (error) {
656
+ if (!isUnselectedPackageMiss(error)) throw error
657
+ }
658
+ // This position is occupied even when a legacy CommonJS subpath is absent from the supplied package.
659
+ continue
660
+ }
661
+ const candidate = localPackageCandidate(searchPath, name, flavor)
662
+ if (candidate === undefined) continue
663
+ if (cjs === undefined) return { route: { kind: 'native', packageDir: candidate }, packageDir: candidate }
664
+ try {
665
+ const resolved = cjs.resolveNative([searchPath])
666
+ if (localCandidateOwnsResolution(candidate, resolved, request, name)) {
667
+ return { route: { kind: 'native', packageDir: candidate }, packageDir: candidate, cjs: resolved }
668
+ }
669
+ } catch (error) {
670
+ if (!isUnselectedPackageMiss(error)) throw error
671
+ }
672
+ }
673
+ // An exhausted CommonJS lookup must not retry the physical copies at occupied peer positions.
674
+ return cjs === undefined ? { route: { kind: 'native' } } : { route: { kind: 'native' }, cjs: cjs.resolveNative([]) }
675
+ }
676
+
677
+ private routeLocalPackage(
678
+ request: string, parentRoutes: ParentRoutes, resolution: CompiledResolution,
679
+ ): ResolutionRouteState | undefined {
680
+ const name = barePackageName(request)
681
+ const { layer } = parentRoutes
682
+ if (name === undefined || layer.kind !== 'profile' || !layer.active || !resolution.localPackageNames.has(name)) return undefined
683
+ const state = { route: { kind: 'native' as const } }
684
+ parentRoutes.requests.set(request, state)
685
+ return state
686
+ }
687
+
688
+ routeUrl(request: string, parentURL: string): ResolutionRouteState | undefined {
689
+ const resolution = this.current
690
+ const parentRoutes = this.getOrCreateParentRoutesForUrl(parentURL)
691
+ if (parentRoutes === false) return undefined
692
+ const cached = parentRoutes.requests.get(request)
693
+ if (cached !== undefined) return cached
694
+ const local = this.routeLocalPackage(request, parentRoutes, resolution)
695
+ if (local !== undefined) return local
696
+ return this.routeScoped(request, parentRoutes, resolution, 'esm')
697
+ }
698
+
699
+ routePath(
700
+ request: string, parent: string, cjs: CommonJsLookup,
701
+ ): ResolutionRouteState | undefined {
702
+ const resolution = this.current
703
+ let parentRoutes = resolution.cjsRoutes.get(parent)
704
+ if (parentRoutes === false) return undefined
705
+ const cached = cjs.cacheable ? parentRoutes?.requests.get(request) : undefined
706
+ if (cached !== undefined) return cached
707
+ if (parentRoutes === undefined) {
708
+ parentRoutes = this.createParentRoutes(parent, resolution) ?? false
709
+ resolution.cjsRoutes.set(parent, parentRoutes)
710
+ if (parentRoutes === false) return undefined
711
+ }
712
+ return this.routeScoped(request, parentRoutes, resolution, 'cjs', cjs)
713
+ }
714
+
715
+ packageDir(specifier: string, parentURL: string): string | undefined {
716
+ const name = barePackageName(specifier)
717
+ if (name === undefined) return undefined
718
+ const state = this.routeUrl(name, parentURL)
719
+ if (state?.route.kind === 'interception') return state.route.entry.packageDir
720
+ if (state?.packageDir !== undefined) return state.packageDir
721
+ let parent: string
722
+ try {
723
+ parent = state?.route.kind === 'native-after-interception' ? state.route.parent : fileURLToPath(parentURL)
724
+ } catch {
725
+ return undefined
726
+ }
727
+ const found = nativePackageDir(parent, name)
728
+ if (state !== undefined && found !== undefined) state.packageDir = found
729
+ return found
730
+ }
731
+ }
732
+
733
+ function internalModules(): InternalModules {
734
+ const require = createRequire(import.meta.url)
735
+ const addon = require('node-addon-require-builtin') as { requireBuiltin(moduleId: string): unknown }
736
+ const esmModule = addon.requireBuiltin('internal/modules/esm/loader') as {
737
+ getOrInitializeCascadedLoader(): ModuleLoaderV1 | ModuleLoaderV2
738
+ }
739
+ const cjsModule = addon.requireBuiltin('internal/modules/cjs/loader') as { Module: CommonJsModule }
740
+ const cjsHelpers = addon.requireBuiltin('internal/modules/helpers') as {
741
+ getCjsConditions(): ReadonlySet<string>
742
+ }
743
+ const esmUtils = addon.requireBuiltin('internal/modules/esm/utils') as {
744
+ getDefaultConditions(): readonly string[]
745
+ }
746
+ const esmResolve = addon.requireBuiltin('internal/modules/esm/resolve') as {
747
+ defaultResolve(
748
+ specifier: string,
749
+ context: { parentURL?: string; conditions?: readonly string[] },
750
+ ): ResolveResult
751
+ }
752
+ const esm = esmModule.getOrInitializeCascadedLoader()
753
+ const modern = 'getOrCreateModuleJob' in esm
754
+ /* v8 ignore start -- the supported Node 24/26 matrix validates each available Internal interface */
755
+ if (typeof esm.resolveSync !== 'function'
756
+ || typeof Reflect.get(esm, modern ? 'getOrCreateModuleJob' : 'getModuleJobForImport') !== 'function'
757
+ || (!modern && typeof Reflect.get(esm, 'resolve') !== 'function')
758
+ || typeof cjsModule.Module._resolveFilename !== 'function'
759
+ || typeof cjsHelpers.getCjsConditions !== 'function'
760
+ || typeof esmUtils.getDefaultConditions !== 'function'
761
+ || typeof esmResolve.defaultResolve !== 'function') {
762
+ throw new Error('profile resolution: unsupported Node module loader')
763
+ }
764
+ /* v8 ignore stop */
765
+ return {
766
+ esm,
767
+ esmDefaultResolve: (specifier, context) => esmResolve.defaultResolve(specifier, context),
768
+ esmConditions: esmUtils.getDefaultConditions(),
769
+ cjs: cjsModule.Module,
770
+ cjsConditions: cjsHelpers.getCjsConditions(),
771
+ modern,
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Replace a resolver error's message and the same text in its stack.
777
+ * Node's module-hooks thread serializes errors and returns the stack accessor as a read-only configurable
778
+ * data property, so a rejected stack assignment redefines the property value.
779
+ * @param error - resolver error to update in place.
780
+ * @param message - replacement message.
781
+ */
782
+ function replaceErrorMessage(error: Error, message: string): void {
783
+ const { message: originalMessage, stack } = error
784
+ error.message = message
785
+ /* v8 ignore next -- Node's resolver errors always carry a stack */
786
+ if (stack === undefined) return
787
+ const replaced = stack.replace(originalMessage, message)
788
+ if (!Reflect.set(error, 'stack', replaced)) Object.defineProperty(error, 'stack', { value: replaced })
789
+ }
790
+
791
+ function throwWithImporter(error: unknown, routedParent: string, parent: string): never {
792
+ const code = (error as NodeJS.ErrnoException).code
793
+ if (error instanceof Error && (code === 'ERR_MODULE_NOT_FOUND' || code === 'ERR_PACKAGE_PATH_NOT_EXPORTED')) {
794
+ const routedPath = fileURLToPath(routedParent)
795
+ const parentPath = fileURLToPath(parent)
796
+ replaceErrorMessage(error, error.message.replaceAll(routedParent, parent).replaceAll(routedPath, parentPath))
797
+ }
798
+ throw error
799
+ }
800
+
801
+ function throwWithoutCjsAnchor(error: unknown, anchor: string): never {
802
+ const resolved = error as NodeJS.ErrnoException & { requireStack?: string[] }
803
+ const requireStack = resolved.requireStack
804
+ if (error instanceof Error
805
+ && resolved.code === 'MODULE_NOT_FOUND'
806
+ && requireStack?.[0] !== undefined
807
+ && sameResolution(requireStack[0], anchor)) {
808
+ const originalMessage = error.message
809
+ const originalBlock = `\nRequire stack:\n${requireStack.map(path => `- ${path}`).join('\n')}`
810
+ const remaining = requireStack.slice(1)
811
+ /* v8 ignore next -- routed calls always retain the original importing module */
812
+ const replacement = remaining.length === 0
813
+ ? ''
814
+ : `\nRequire stack:\n${remaining.map(path => `- ${path}`).join('\n')}`
815
+ error.message = originalMessage.replace(originalBlock, replacement)
816
+ resolved.requireStack = remaining
817
+ const stack = error.stack
818
+ /* v8 ignore next -- Node's resolver errors always carry a stack */
819
+ if (stack !== undefined) error.stack = stack.replace(originalMessage, error.message)
820
+ }
821
+ throw error
822
+ }
823
+
824
+ /**
825
+ * Install one runtime resolution as the interception on Node's default ESM and CommonJS resolvers.
826
+ * @param resolution - complete package table and profile scope.
827
+ * @returns an interception that publishes a successor or restores the native methods.
828
+ */
829
+ export function installRuntimeInterception(
830
+ resolution: RuntimeResolution,
831
+ ): RuntimeInterception {
832
+ const { esm, esmDefaultResolve, esmConditions, cjs, cjsConditions, modern } = internalModules()
833
+ const router = new ResolutionRouter(resolution, directory => cjs._nodeModulePaths(directory))
834
+ let delegatedEsm: { parent: string | undefined; request: string } | undefined
835
+
836
+ const adaptEsm = (native: EsmResolve): EsmResolve => {
837
+ const adapted: EsmResolve = (request, parent, attributes) => {
838
+ const delegated = delegatedEsm
839
+ /* v8 ignore next -- reentry requires a separate synchronous Node hook; supported launches install none */
840
+ if (delegated !== undefined && delegated.parent === parent && delegated.request === request) {
841
+ return native(request, parent, attributes)
842
+ }
843
+ if (parent === undefined || !router.hasInterceptionLayerForUrl(parent)) return native(request, parent, attributes)
844
+ const state = router.routeUrl(request, parent)
845
+ if (state === undefined) {
846
+ const target = request[0] === '#'
847
+ ? packageImportsTarget(fileURLToPath(parent), request, esmConditions)
848
+ : undefined
849
+ if (target === undefined) return native(request, parent, attributes)
850
+ const restoreImporter = (error: unknown): never => throwWithImporter(error, target.parentURL, parent)
851
+ let expected: ResolveResult | Promise<ResolveResult>
852
+ try {
853
+ expected = adapted(target.specifier, target.parentURL, attributes)
854
+ /* v8 ignore next -- the current loader resolves synchronously; the private interface also permits Promise results */
855
+ if (expected instanceof Promise) expected = expected.catch(restoreImporter)
856
+ } catch (error) {
857
+ return restoreImporter(error)
858
+ }
859
+ return expected
860
+ }
861
+ const cacheable = attributes === EMPTY_ATTRIBUTES || Object.keys(attributes).length === 0
862
+ if (cacheable && state.esm !== undefined) return state.esm
863
+ const route = state.route
864
+ if (route.kind === 'native') {
865
+ const result = native(request, parent, attributes)
866
+ if (cacheable && !(result instanceof Promise)) state.esm = result
867
+ return result
868
+ }
869
+ const routedParent = pathToFileURL(route.kind === 'interception' ? route.entry.declarer : route.parent).href
870
+ const previous = delegatedEsm
871
+ delegatedEsm = { parent: routedParent, request }
872
+ const restoreImporter = (error: unknown): never => throwWithImporter(error, routedParent, parent)
873
+ try {
874
+ let result: ResolveResult | Promise<ResolveResult>
875
+ try {
876
+ result = native(request, routedParent, attributes)
877
+ } catch (error) {
878
+ return restoreImporter(error)
879
+ }
880
+ /* v8 ignore next -- the current loader resolves synchronously; the private interface also permits Promise results */
881
+ if (result instanceof Promise) return result.catch(restoreImporter)
882
+ if (cacheable) state.esm = result
883
+ return result
884
+ } finally {
885
+ delegatedEsm = previous
886
+ }
887
+ }
888
+ // Node's last real-path step, for routed and native requests alike, may follow a link it resolved earlier.
889
+ const relocate = (result: ResolveResult, parent: string | undefined): ResolveResult => {
890
+ if (!result.url.startsWith('file:')) return result
891
+ const url = new URL(result.url)
892
+ const filename = fileURLToPath(url)
893
+ const relocated = router.relocate(filename, parent?.startsWith('file:') === true ? fileURLToPath(parent) : undefined)
894
+ if (relocated === filename) return result
895
+ url.pathname = pathToFileURL(relocated).pathname
896
+ return { ...result, url: url.href }
897
+ }
898
+ return (request, parent, attributes) => {
899
+ const result = adapted(request, parent, attributes)
900
+ /* v8 ignore next -- the current loader resolves synchronously; the private interface also permits Promise results */
901
+ return result instanceof Promise ? result.then(resolved => relocate(resolved, parent)) : relocate(result, parent)
902
+ }
903
+ }
904
+
905
+ let restoreEsm: () => void
906
+ /* v8 ignore else -- CI coverage runs Node 24 v2; the Node 24.0 matrix exercises the v1 adapter */
907
+ if (modern) {
908
+ const loader = esm as ModuleLoaderV2
909
+ const original = Reflect.get(loader, 'resolveSync')
910
+ const resolveRequest = adaptEsm((request, parent, attributes) => original.call(
911
+ loader, parent as string, { specifier: request, attributes },
912
+ ))
913
+ // Node 24.12+ adds skipSyncHooks after request. Its presence marks hook
914
+ // delegation whose outer call already selected a route.
915
+ const wrapped = (parent: string, request: { specifier: string; attributes?: ImportAttributes }, ...rest: unknown[]): ResolveResult => (
916
+ rest.length
917
+ ? Reflect.apply(original, loader, [parent, request, ...rest]) as ResolveResult
918
+ : resolveRequest(request.specifier, parent, request.attributes ?? EMPTY_ATTRIBUTES) as ResolveResult
919
+ )
920
+ loader.resolveSync = wrapped
921
+ restoreEsm = () => {
922
+ /* v8 ignore else -- interceptions are disposed in reverse installation order */
923
+ if (loader.resolveSync === wrapped) loader.resolveSync = original
924
+ }
925
+ } else {
926
+ const loader = esm as ModuleLoaderV1
927
+ const original = Reflect.get(loader, 'resolve')
928
+ const originalSync = Reflect.get(loader, 'resolveSync')
929
+ const resolveRequest = adaptEsm((request, parent, attributes) => original.call(loader, request, parent as string, attributes))
930
+ const resolveRequestSync = adaptEsm((request, parent, attributes) => originalSync.call(loader, request, parent as string, attributes))
931
+ const wrapped = (request: string, parent: string, attributes: ImportAttributes = EMPTY_ATTRIBUTES): Promise<ResolveResult> => (
932
+ resolveRequest(request, parent, attributes) as Promise<ResolveResult>
933
+ )
934
+ const wrappedSync = (request: string, parent: string, attributes: ImportAttributes = EMPTY_ATTRIBUTES): ResolveResult => (
935
+ resolveRequestSync(request, parent, attributes) as ResolveResult
936
+ )
937
+ loader.resolve = wrapped
938
+ loader.resolveSync = wrappedSync
939
+ restoreEsm = () => {
940
+ if (loader.resolve === wrapped) loader.resolve = original
941
+ if (loader.resolveSync === wrappedSync) loader.resolveSync = originalSync
942
+ }
943
+ }
944
+
945
+ const originalFilename = Reflect.get(cjs, '_resolveFilename')
946
+ let delegatedCjs = 0
947
+ const resolveRoutedCjs = (
948
+ request: string, routed: Exclude<ResolutionRoute, { kind: 'native' }>,
949
+ parent: CommonJsParent, main: boolean, options?: CommonJsOptions,
950
+ ): string => {
951
+ const anchor = routed.kind === 'interception' ? routed.entry.declarer : routed.parent
952
+ const synthetic = new cjs(anchor)
953
+ // Late parent assignment preserves Node's require stack without publishing this routing anchor in parent.children.
954
+ synthetic.parent = parent
955
+ synthetic.filename = anchor
956
+ synthetic.paths = routed.kind === 'interception'
957
+ ? packageSearchPaths(routed.entry, request, cjs)
958
+ : cjs._nodeModulePaths(dirname(anchor))
959
+ try {
960
+ return originalFilename.call(cjs, request, synthetic, main, options)
961
+ } catch (error) {
962
+ return throwWithoutCjsAnchor(error, anchor)
963
+ }
964
+ }
965
+ const resolveNativeCjs = (
966
+ request: string, searchPaths: readonly string[], parent: CommonJsParent,
967
+ parentFilename: string, main: boolean, conditions: ReadonlySet<string> | undefined,
968
+ ): string => {
969
+ const synthetic = new cjs(parentFilename)
970
+ if (parent.parent !== undefined) synthetic.parent = parent.parent
971
+ synthetic.filename = parentFilename
972
+ synthetic.paths = [...searchPaths]
973
+ const options = conditions === undefined ? undefined : { conditions }
974
+ return originalFilename.call(cjs, request, synthetic, main, options)
975
+ }
976
+ const resolvePackageImportCjs = (
977
+ target: { specifier: string; parentURL: string }, conditions: Iterable<string>,
978
+ ): string => {
979
+ const state = router.routeUrl(target.specifier, target.parentURL)
980
+ const resolveFrom = (parentURL: string): string => fileURLToPath(esmDefaultResolve(
981
+ target.specifier, { parentURL, conditions: [...conditions] },
982
+ ).url)
983
+ /* v8 ignore next -- the target manifest was found inside the established profile scope */
984
+ if (state === undefined) return resolveFrom(target.parentURL)
985
+ if (state.route.kind === 'native') return resolveFrom(target.parentURL)
986
+ const route = state.route
987
+ if (route.kind === 'native-after-interception') return resolveFrom(pathToFileURL(route.parent).href)
988
+ return resolveFrom(pathToFileURL(route.entry.declarer).href)
989
+ }
990
+ const wrappedFilename: CommonJsModule['_resolveFilename'] = (request, parent, main, options) => {
991
+ if (delegatedCjs || !parent?.filename || options?.paths !== undefined) {
992
+ return originalFilename.call(cjs, request, parent, main, options)
993
+ }
994
+ const parentFilename = parent.filename
995
+ const cacheable = options?.conditions === undefined
996
+ const routedOptions = options?.conditions === undefined ? undefined : { conditions: options.conditions }
997
+ const state = router.routePath(request, parentFilename, {
998
+ cacheable,
999
+ resolveNative: searchPaths => resolveNativeCjs(request, searchPaths, parent, parentFilename, main, options?.conditions),
1000
+ resolveEntry: (entry, after) => {
1001
+ delegatedCjs++
1002
+ try {
1003
+ return resolveRoutedCjs(request, { kind: 'interception', entry, after }, parent, main, routedOptions)
1004
+ } finally {
1005
+ delegatedCjs--
1006
+ }
1007
+ },
1008
+ })
1009
+ if (state === undefined) {
1010
+ const scoped = router.hasInterceptionLayerForPath(parentFilename)
1011
+ const conditions = options?.conditions ?? cjsConditions
1012
+ const target = request[0] === '#' && scoped
1013
+ ? packageImportsTarget(parentFilename, request, conditions)
1014
+ : undefined
1015
+ if (target === undefined) return originalFilename.call(cjs, request, parent, main, options)
1016
+ return resolvePackageImportCjs(target, conditions)
1017
+ }
1018
+ if (state.cjs !== undefined) return state.cjs
1019
+ const route = state.route
1020
+ if (route.kind === 'native') {
1021
+ const result = originalFilename.call(cjs, request, parent, main, options)
1022
+ if (cacheable) state.cjs = result
1023
+ return result
1024
+ }
1025
+ delegatedCjs++
1026
+ try {
1027
+ let expected: string
1028
+ try {
1029
+ expected = resolveRoutedCjs(request, route, parent, main, routedOptions)
1030
+ } catch (error) {
1031
+ if (route.kind !== 'interception' || !isUnselectedPackageMiss(error)) throw error
1032
+ expected = resolveRoutedCjs(
1033
+ request, { kind: 'native-after-interception', parent: route.after }, parent, main, routedOptions,
1034
+ )
1035
+ }
1036
+ if (cacheable) state.cjs = expected
1037
+ return expected
1038
+ } finally {
1039
+ delegatedCjs--
1040
+ }
1041
+ }
1042
+ const relocatedFilename: CommonJsModule['_resolveFilename'] = (request, parent, main, options) => {
1043
+ const filename = wrappedFilename(request, parent, main, options)
1044
+ return isAbsolute(filename) ? router.relocate(filename, parent?.filename ?? undefined) : filename
1045
+ }
1046
+ cjs._resolveFilename = relocatedFilename
1047
+
1048
+ return {
1049
+ packageDir(specifier, parentURL) {
1050
+ return router.packageDir(specifier, parentURL)
1051
+ },
1052
+ replace(next) { router.replace(next) },
1053
+ dispose() {
1054
+ /* v8 ignore else -- interceptions are disposed in reverse installation order */
1055
+ if (cjs._resolveFilename === relocatedFilename) cjs._resolveFilename = originalFilename
1056
+ restoreEsm()
1057
+ },
1058
+ }
1059
+ }
1060
+
1061
+ /**
1062
+ * Publish one runtime resolution for Harness-owned Workers.
1063
+ * @param resolution - complete package table and profile scope.
1064
+ * @returns a disposer restoring the previous thread environment data.
1065
+ */
1066
+ export function registerWorkerResolution(
1067
+ resolution: RuntimeResolution,
1068
+ ): () => void {
1069
+ const previous = getEnvironmentData(WORKER_RESOLUTION_KEY)
1070
+ setEnvironmentData(WORKER_RESOLUTION_KEY, { resolution })
1071
+ return () => { setEnvironmentData(WORKER_RESOLUTION_KEY, previous) }
1072
+ }