refactor(boot): rename the profile resolution generation to the runtime resolution

RuntimeResolution, RuntimeResolutionEntry, RuntimeResolutionOptions, and createRuntimeResolution replace the generation-named table types; installRuntimeInterception returns a RuntimeInterception; PluginPackages takes { resolution }; Worker environment data carries { resolution }; routes are native, interception, and native-after-interception. replace() keeps its successor-generation wording.
This commit is contained in:
imccyu committed 2026-09-19 17:06:34 +08:00
1 parent 5f48b2fff6
commit 15e07d2721
15 files changed
+364 -364

No files matched your search

+5 -5
View File
@@ -18,7 +18,7 @@ import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import {
boot,
readProfilePatches,
createProfileResolutionGeneration,
createRuntimeResolution,
initProfile,
installFailLoud,
loadOverlayPatches,
@@ -29,7 +29,7 @@ import {
resolveProfileDir,
type ProfileContext,
type Profile,
type ProfileResolutionGeneration,
type RuntimeResolution,
} from '@deepseek-ai/dsh-app-boot'
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
import { installProxyFromEnvironment } from '@deepseek-ai/dsh-http-proxy'
@@ -174,7 +174,7 @@ export function prepareProfile(name: string, userLayer = true, fromDefaultProfil
interface ComposedProfile {
profile: Profile
/** Immutable package fallback selected before any plugin imports. */
resolution: ProfileResolutionGeneration
resolution: RuntimeResolution
/** Command-line overlay contents, frozen for this invocation. */
overlays: PatchOptions[]
}
@@ -201,7 +201,7 @@ async function composeProfile(
const profile = resolvedProfile?.profile ?? prepareProfile(name, true, fromDefaultProfile)
if (resolvedProfile !== undefined) writeFileSync(join(profile.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG)
const resolutionOptions = { installAnchor: resolvedProfile?.installAnchor ?? INSTALL_ANCHOR, profile }
const resolution = await createProfileResolutionGeneration(resolutionOptions)
const resolution = await createRuntimeResolution(resolutionOptions)
const overlays = patchFiles.flatMap(file => loadOverlayPatches(NAME, resolve(file)))
return { profile, resolution, overlays }
}
@@ -298,7 +298,7 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
// environment values from the same immutable launch snapshot.
hostCtx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, options.environment)
await hostCtx.plugin(PluginPackages, {
generation: composed.resolution,
resolution: composed.resolution,
})
// The command line and bounded exit request are launcher facts available
// to every app plugin that injects the argument snapshot.
@@ -153,7 +153,7 @@ export function testProfileResolution(mode: ExampleMode): void {
' ctx.effect(() => ready.onReady(() => {',
` const cjs = require('${leafName}').leaf`,
` const externalCjs = require('${externalName}').external`,
" const entries = getEnvironmentData('@deepseek-ai/dsh-app-boot/profile-resolution').generation.entries",
" const entries = getEnvironmentData('@deepseek-ai/dsh-app-boot/profile-resolution').resolution.entries",
' const evidence = {',
' execArgv: process.execArgv, esm: leaf, cjs,',
' sameEsmLeaf: leaf === bridgeLeaf,',
+6 -6
View File
@@ -6,7 +6,7 @@ import { join } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
import { createLaunchEnvironmentSnapshot } from '@deepseek-ai/dsh-launch-environment'
import {
boot, composeEntries, createProfileResolutionGeneration,
boot, composeEntries, createRuntimeResolution,
PluginPackages, type Profile,
} from '@deepseek-ai/dsh-app-boot'
import { installProxyFromEnvironment } from '@deepseek-ai/dsh-http-proxy'
@@ -18,7 +18,7 @@ vi.mock('@deepseek-ai/dsh-app-boot', async (importOriginal) => {
return {
...actual,
boot: vi.fn(),
createProfileResolutionGeneration: vi.fn(actual.createProfileResolutionGeneration),
createRuntimeResolution: vi.fn(actual.createRuntimeResolution),
installFailLoud: vi.fn(),
}
})
@@ -59,7 +59,7 @@ describe('runProfile with an application-owned profile', () => {
await setup?.(ctx)
throw failure
})
if (stage === 'composition') vi.mocked(createProfileResolutionGeneration).mockRejectedValueOnce(failure)
if (stage === 'composition') vi.mocked(createRuntimeResolution).mockRejectedValueOnce(failure)
const profile: Profile = {
name: 'desktop', dir: home, patchPath: join(home, 'cordis.patch.yml'),
patches: [], layers: [],
@@ -140,10 +140,10 @@ describe('runProfile with an application-owned profile', () => {
patchFiles: [overlay], args: ['--port', '0', '--no-open'],
})
expect(installProxyFromEnvironment).toHaveBeenCalledWith(environment, expect.any(Function))
const generation = vi.mocked(createProfileResolutionGeneration).mock.settledResults
const resolution = vi.mocked(createRuntimeResolution).mock.settledResults
.find(result => result.type === 'fulfilled')?.value
expect(generation?.profileDir).toBe(home)
expect(plugin).toHaveBeenCalledWith(PluginPackages, { generation })
expect(resolution?.profileDir).toBe(home)
expect(plugin).toHaveBeenCalledWith(PluginPackages, { resolution })
expect(lstatSync(localPackageDir).isDirectory()).toBe(true)
expect(readFileSync(join(localPackageDir, 'package.json'), 'utf8')).toBe(localManifest)
const requireFromProfile = createRequire(join(home, 'package.json'))
+3 -3
View File
@@ -7,7 +7,7 @@ import { Context } from '@deepseek-ai/cordis'
import {
boot,
initProfile,
createProfileResolutionGeneration,
createRuntimeResolution,
loadOverlayPatches,
loadProfile,
PluginPackages,
@@ -156,7 +156,7 @@ async function bootWeb(
profile = loadProfile('dsh-test', 'spec', INSTALL_ANCHOR, home, { userLayer: false })
bundlePatches = profile.layers.flatMap(layer => layer.patches)
}
const resolution = await createProfileResolutionGeneration({ installAnchor: INSTALL_ANCHOR, home, profile })
const resolution = await createRuntimeResolution({ installAnchor: INSTALL_ANCHOR, home, profile })
const rootConfig = join(profileDir, 'cordis.yml')
await writeFile(rootConfig, '[]\n')
return await boot('dsh-test', rootConfig, [...bundlePatches, ...overrides], async (bootCtx) => {
@@ -164,7 +164,7 @@ async function bootWeb(
installAnchor: INSTALL_ANCHOR, home, cwd: home,
startedBundles: profileBundles ?? ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'],
overlays: overrides, telemetryDisabledEnv: '1' })
await bootCtx.plugin(PluginPackages, { generation: resolution })
await bootCtx.plugin(PluginPackages, { resolution })
bootCtx.provide('connection', {
fetch: { register: () => () => {} },
rpc: { intercept: () => () => {} },
+4 -4
View File
@@ -465,7 +465,7 @@ async function cleanupScaffoldWorld(ctx: Context, workspaceCwd: string, persiste
export async function launchWebScaffold(options: LaunchOptions = {}): Promise<WebScaffold> {
requireDist()
const {
auditStartupEntries, composeEntries, createProfileResolutionGeneration, initProfile,
auditStartupEntries, composeEntries, createRuntimeResolution, initProfile,
mountRootInclude, readProfileManifest, readProfilePatches, loadProfileDirectory, loadOverlayPatches, PluginPackages,
} = appBoot()
const mode = webSnapshotMode()
@@ -749,7 +749,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
patches: [],
}
const resolutionOptions = { installAnchor: INSTALL_ANCHOR, home: harnessHome, profile }
const resolution = await createProfileResolutionGeneration(resolutionOptions)
const resolution = await createRuntimeResolution(resolutionOptions)
await mkdir(profileDir, { recursive: true })
const rootConfig = join(profileDir, 'cordis.yml')
await writeFile(rootConfig, '[]\n')
@@ -796,7 +796,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
},
})
await ctx.plugin(PluginPackages, {
generation: resolution,
resolution,
})
await ctx.plugin(Loader)
if (profileContext === undefined) {
@@ -813,7 +813,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
} else {
// The launcher's own mount, so the manager's reloads find the root Include
// and compose the same layers the profile files name; bare names still
// resolve through the resolution generation above, as in the direct mount.
// resolve through the runtime resolution above, as in the direct mount.
await mountRootInclude(ctx, rootConfig, readProfilePatches('dsh', profileContext))
}
await ctx.loader.await()
+4 -4
View File
@@ -35,7 +35,7 @@ declare module '@deepseek-ai/cordis' {
export {
composeEntries,
createProfileResolutionGeneration,
createRuntimeResolution,
DEFAULT_PROFILE_BUNDLES,
OPTIONAL_BUNDLES,
initProfile,
@@ -52,9 +52,9 @@ export {
type Profile,
type ProfileLayer,
type ProfileManifest,
type ProfileResolutionOptions,
type ProfileResolutionEntry,
type ProfileResolutionGeneration,
type RuntimeResolutionOptions,
type RuntimeResolutionEntry,
type RuntimeResolution,
type ProfileTemplate,
} from './profile.ts'
export {
@@ -7,7 +7,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
import { getEnvironmentData, setEnvironmentData } from 'node:worker_threads'
import type { ModuleLoaderV1, ModuleLoaderV2, ResolveResult } from '@deepseek-ai/cordis-plugin-loader'
import { imports as resolvePackageImports, type Package as ResolvePackageManifest } from 'resolve.exports'
import type { ProfileResolutionEntry, ProfileResolutionGeneration } from '../profile.ts'
import type { RuntimeResolutionEntry, RuntimeResolution } from '../profile.ts'
const WORKER_RESOLUTION_KEY = '@deepseek-ai/dsh-app-boot/profile-resolution'
const EMPTY_ATTRIBUTES: ImportAttributes = Object.freeze({})
@@ -49,14 +49,14 @@ type EsmResolve = (
/**
* Where Node continues a scoped bare request. `native` keeps the importer's own lookup: the chain is
* answered below the interception layer. `generation` answers at the interception layer from the
* answered below the interception layer. `interception` answers at the interception layer from the
* entry's declaring manifest; `after` anchors Node's chain above the layer for a CommonJS subpath the
* entry lacks. `native-after-generation` has no entry: Node's chain resumes at the interception layer,
* entry lacks. `native-after-interception` has no entry: Node's chain resumes at the interception layer,
* anchored by `parent`.
*/
type ResolutionRoute =
| { readonly kind: 'generation'; readonly entry: ProfileResolutionEntry; readonly after: string }
| { readonly kind: 'native-after-generation'; readonly parent: string }
| { readonly kind: 'interception'; readonly entry: RuntimeResolutionEntry; readonly after: string }
| { readonly kind: 'native-after-interception'; readonly parent: string }
| { readonly kind: 'native'; readonly packageDir?: string }
interface ResolutionRouteState {
@@ -77,8 +77,8 @@ interface ParentRoutes {
type ResolutionRoutes = Map<string, ParentRoutes | false>
interface CompiledGeneration {
readonly entries: ReadonlyMap<string, ProfileResolutionEntry>
interface CompiledResolution {
readonly entries: ReadonlyMap<string, RuntimeResolutionEntry>
readonly profilesDir: string
readonly profileDir: string | undefined
readonly profilePaths: readonly string[]
@@ -90,8 +90,8 @@ interface CompiledGeneration {
readonly cjsRoutes: ResolutionRoutes
}
/** Active resolver registration in one Node isolate. */
export interface ProfileResolutionRegistration {
/** Active interception in one Node isolate. */
export interface RuntimeInterception {
/**
* Locate a bare package without requiring one of its exports.
* @param specifier - bare package or package-subpath specifier.
@@ -100,11 +100,11 @@ export interface ProfileResolutionRegistration {
*/
packageDir(specifier: string, parentURL: string): string | undefined
/**
* Atomically publish an additive package table and fresh generation-owned caches.
* @param generation - fully constructed successor generation.
* Atomically publish an additive package table and fresh caches.
* @param successor - fully constructed additive successor generation.
* @throws when the profile scope or an existing package mapping changes.
*/
replace(generation: ProfileResolutionGeneration): void
replace(successor: RuntimeResolution): void
/** Restore the native resolver methods. Registrations dispose in reverse order. */
dispose(): void
}
@@ -128,7 +128,7 @@ function canonicalPath(path: string): string {
try {
return realpathSync(path)
} catch {
// A generation may name a profile scope before that directory is materialized.
// A runtime resolution may name a profile scope before that directory is materialized.
return resolve(path)
}
}
@@ -139,18 +139,18 @@ function prefixes(path: string): readonly string[] {
return canonical === configured ? [configured] : [configured, canonical]
}
function compileGeneration(generation: ProfileResolutionGeneration): CompiledGeneration {
const profilePaths = prefixes(generation.profilesDir)
const profile = generation.profileDir === undefined ? [] : prefixes(generation.profileDir)
function compileResolution(resolution: RuntimeResolution): CompiledResolution {
const profilePaths = prefixes(resolution.profilesDir)
const profile = resolution.profileDir === undefined ? [] : prefixes(resolution.profileDir)
return {
entries: new Map(generation.entries.map(entry => [entry.name, entry])),
profilesDir: generation.profilesDir,
profileDir: generation.profileDir,
entries: new Map(resolution.entries.map(entry => [entry.name, entry])),
profilesDir: resolution.profilesDir,
profileDir: resolution.profileDir,
profilePaths,
profileUrls: profilePaths.map(path => pathToFileURL(path).href),
profile,
activeProfileUrls: profile.map(path => pathToFileURL(path).href),
localPackageNames: new Set(generation.localPackageNames),
localPackageNames: new Set(resolution.localPackageNames),
esmRoutes: new Map(),
cjsRoutes: new Map(),
}
@@ -255,10 +255,10 @@ function packageImportsTarget(
}
function packageSearchPaths(
entry: ProfileResolutionEntry, request: string, cjs: CommonJsModule,
entry: RuntimeResolutionEntry, request: string, cjs: CommonJsModule,
): string[] {
const name = barePackageName(request)
/* v8 ignore next -- generation routes are created only for bare package requests */
/* v8 ignore next -- interception routes are created only for bare package requests */
if (name === undefined) return cjs._nodeModulePaths(dirname(entry.declarer))
const suffix = sep + name.split('/').join(sep)
return entry.packageDir.endsWith(suffix)
@@ -295,19 +295,19 @@ function sameResolution(left: string, right: string): boolean {
return canonicalPath(left) === canonicalPath(right)
}
/** One mutable pointer to immutable generation data. */
/** One mutable pointer to the immutable runtime resolution. */
class ResolutionRouter {
private current: CompiledGeneration
private current: CompiledResolution
constructor(generation: ProfileResolutionGeneration) {
this.current = compileGeneration(generation)
constructor(resolution: RuntimeResolution) {
this.current = compileResolution(resolution)
}
replace(generation: ProfileResolutionGeneration): void {
const entries = new Map(generation.entries.map(entry => [entry.name, entry]))
if (generation.profilesDir !== this.current.profilesDir
|| generation.profileDir !== this.current.profileDir) {
throw new Error('profile resolution: a generation cannot change its profile scope')
replace(successor: RuntimeResolution): void {
const entries = new Map(successor.entries.map(entry => [entry.name, entry]))
if (successor.profilesDir !== this.current.profilesDir
|| successor.profileDir !== this.current.profileDir) {
throw new Error('profile resolution: a successor cannot change its profile scope')
}
for (const [name, current] of this.current.entries) {
const next = entries.get(name)
@@ -319,7 +319,7 @@ class ResolutionRouter {
throw new Error(`profile resolution: replacing ${JSON.stringify(name)} requires a process restart`)
}
}
const localPackageNames = new Set(generation.localPackageNames)
const localPackageNames = new Set(successor.localPackageNames)
for (const name of this.current.localPackageNames) {
if (!localPackageNames.has(name)) {
throw new Error(`profile resolution: removing local package ${JSON.stringify(name)} requires a process restart`)
@@ -330,13 +330,13 @@ class ResolutionRouter {
throw new Error(`profile resolution: overriding ${JSON.stringify(name)} locally requires a process restart`)
}
}
this.current = compileGeneration(generation)
this.current = compileResolution(successor)
}
private routeScoped(
request: string,
parentRoutes: ParentRoutes,
generation: CompiledGeneration,
resolution: CompiledResolution,
flavor: 'esm' | 'cjs',
nativeResolve?: (searchPaths: readonly string[]) => string,
cacheable = false,
@@ -354,7 +354,7 @@ class ResolutionRouter {
return state
}
const target = generation.entries.get(name)
const target = resolution.entries.get(name)
const candidates: string[] = []
const localSearchPaths: string[] = []
const localPrefix = localRoot + sep
@@ -394,85 +394,85 @@ class ResolutionRouter {
}
}
// The interception layer is `<profileParent>/node_modules`: a generation entry occupies its name
// The interception layer is `<profileParent>/node_modules`: a runtime resolution entry occupies its name
// there, so its subpath misses continue above it; a name without an entry continues at it.
const profileParent = dirname(localRoot)
const eligible = target?.scope === 'installation'
|| (target?.scope === 'profile' && parentRoutes.activeProfile)
const route: ResolutionRoute = eligible
? { kind: 'generation', entry: target, after: join(dirname(profileParent), 'package.json') }
: { kind: 'native-after-generation', parent: join(profileParent, 'package.json') }
? { kind: 'interception', entry: target, after: join(dirname(profileParent), 'package.json') }
: { kind: 'native-after-interception', parent: join(profileParent, 'package.json') }
const state: ResolutionRouteState = { route }
if (route.kind === 'generation') requests.set(request, state)
if (route.kind === 'interception') requests.set(request, state)
return state
}
private routeLocalPackage(
request: string, parentRoutes: ParentRoutes, generation: CompiledGeneration,
request: string, parentRoutes: ParentRoutes, resolution: CompiledResolution,
): ResolutionRouteState | undefined {
const name = barePackageName(request)
if (name === undefined || !parentRoutes.activeProfile || !generation.localPackageNames.has(name)) return undefined
if (name === undefined || !parentRoutes.activeProfile || !resolution.localPackageNames.has(name)) return undefined
const state = { route: { kind: 'native' as const } }
parentRoutes.requests.set(request, state)
return state
}
routeUrl(request: string, parentURL: string): ResolutionRouteState | undefined {
const generation = this.current
let parentRoutes = generation.esmRoutes.get(parentURL)
const resolution = this.current
let parentRoutes = resolution.esmRoutes.get(parentURL)
if (parentRoutes === false) return undefined
const cached = parentRoutes?.requests.get(request)
if (cached !== undefined) return cached
if (parentRoutes === undefined) {
const profileIndex = generation.profileUrls.findIndex(prefix => parentURL.startsWith(prefix))
const activeProfileIndex = generation.activeProfileUrls.findIndex(prefix => parentURL.startsWith(prefix))
const profileIndex = resolution.profileUrls.findIndex(prefix => parentURL.startsWith(prefix))
const activeProfileIndex = resolution.activeProfileUrls.findIndex(prefix => parentURL.startsWith(prefix))
if (profileIndex < 0 && activeProfileIndex < 0) {
generation.esmRoutes.set(parentURL, false)
resolution.esmRoutes.set(parentURL, false)
return undefined
}
let parent: string
try {
parent = fileURLToPath(parentURL)
} catch {
generation.esmRoutes.set(parentURL, false)
resolution.esmRoutes.set(parentURL, false)
return undefined
}
const treeRoot = generation.profilePaths[profileIndex]
const treeRoot = resolution.profilePaths[profileIndex]
const localRoot = treeRoot !== undefined
? profileChild(parent, treeRoot)
: generation.profile[activeProfileIndex]?.slice(0, -1)
: resolution.profile[activeProfileIndex]?.slice(0, -1)
/* v8 ignore next -- one matching index was established above */
if (localRoot === undefined) return undefined
parentRoutes = { parent, localRoot, activeProfile: activeProfileIndex >= 0, requests: new Map() }
generation.esmRoutes.set(parentURL, parentRoutes)
resolution.esmRoutes.set(parentURL, parentRoutes)
}
const local = this.routeLocalPackage(request, parentRoutes, generation)
const local = this.routeLocalPackage(request, parentRoutes, resolution)
if (local !== undefined) return local
return this.routeScoped(request, parentRoutes, generation, 'esm')
return this.routeScoped(request, parentRoutes, resolution, 'esm')
}
routePath(
request: string, parent: string, nativeResolve?: (searchPaths: readonly string[]) => string, cacheable = false,
): ResolutionRouteState | undefined {
const generation = this.current
let parentRoutes = generation.cjsRoutes.get(parent)
const resolution = this.current
let parentRoutes = resolution.cjsRoutes.get(parent)
if (parentRoutes === false) return undefined
const cached = parentRoutes?.requests.get(request)
if (cached !== undefined) return cached
if (parentRoutes === undefined) {
const treeRoot = generation.profilePaths.find(prefix => parent.startsWith(prefix))
const activeProfile = generation.profile.find(prefix => parent.startsWith(prefix))
const treeRoot = resolution.profilePaths.find(prefix => parent.startsWith(prefix))
const activeProfile = resolution.profile.find(prefix => parent.startsWith(prefix))
const localRoot = treeRoot !== undefined ? profileChild(parent, treeRoot) : activeProfile?.slice(0, -1)
if (localRoot !== undefined) {
parentRoutes = { parent, localRoot, activeProfile: activeProfile !== undefined, requests: new Map() }
generation.cjsRoutes.set(parent, parentRoutes)
resolution.cjsRoutes.set(parent, parentRoutes)
}
}
if (parentRoutes === undefined) {
generation.cjsRoutes.set(parent, false)
resolution.cjsRoutes.set(parent, false)
return undefined
}
return this.routeScoped(request, parentRoutes, generation, 'cjs', nativeResolve, cacheable)
return this.routeScoped(request, parentRoutes, resolution, 'cjs', nativeResolve, cacheable)
}
explicitRoute(
@@ -499,11 +499,11 @@ class ResolutionRouter {
const name = barePackageName(specifier)
if (name === undefined) return undefined
const state = this.routeUrl(specifier, parentURL)
if (state?.route.kind === 'generation') return state.route.entry.packageDir
if (state?.route.kind === 'interception') return state.route.entry.packageDir
if (state?.packageDir !== undefined) return state.packageDir
let parent: string
try {
parent = state?.route.kind === 'native-after-generation' ? state.route.parent : fileURLToPath(parentURL)
parent = state?.route.kind === 'native-after-interception' ? state.route.parent : fileURLToPath(parentURL)
} catch {
return undefined
}
@@ -594,19 +594,19 @@ function throwWithoutCjsAnchor(error: unknown, anchor: string): never {
}
/**
* Install one profile generation on Node's default ESM and CommonJS resolvers.
* @param generation - complete package table and profile scope.
* @returns a registration that replaces the generation or restores the native methods.
* Install one runtime resolution as the interception on Node's default ESM and CommonJS resolvers.
* @param resolution - complete package table and profile scope.
* @returns an interception that publishes a successor or restores the native methods.
*/
export function installProfileResolution(
generation: ProfileResolutionGeneration,
): ProfileResolutionRegistration {
const router = new ResolutionRouter(generation)
export function installRuntimeInterception(
resolution: RuntimeResolution,
): RuntimeInterception {
const router = new ResolutionRouter(resolution)
const { esm, esmDefaultResolve, esmConditions, cjs, cjsConditions, modern } = internalModules()
const esmScope = new Map<string, boolean>()
const profilePaths = [
...prefixes(generation.profilesDir),
...(generation.profileDir === undefined ? [] : prefixes(generation.profileDir)),
...prefixes(resolution.profilesDir),
...(resolution.profileDir === undefined ? [] : prefixes(resolution.profileDir)),
]
const profileUrls = profilePaths.map(path => pathToFileURL(path).href)
let recentEsmParent: string | undefined
@@ -656,7 +656,7 @@ export function installProfileResolution(
if (cacheable && !(result instanceof Promise)) state.esm = result
return result
}
const routedParent = pathToFileURL(route.kind === 'generation' ? route.entry.declarer : route.parent).href
const routedParent = pathToFileURL(route.kind === 'interception' ? route.entry.declarer : route.parent).href
const previous = delegatedEsm
delegatedEsm = { parent: routedParent, request }
const restoreImporter = (error: unknown): never => throwWithImporter(error, routedParent, parent)
@@ -724,12 +724,12 @@ export function installProfileResolution(
request: string, routed: Exclude<ResolutionRoute, { kind: 'native' }>,
parent: CommonJsParent, main: boolean, options?: CommonJsOptions,
): string => {
const anchor = routed.kind === 'generation' ? routed.entry.declarer : routed.parent
const anchor = routed.kind === 'interception' ? routed.entry.declarer : routed.parent
const synthetic = new cjs(anchor)
// Late parent assignment preserves Node's require stack without publishing this routing anchor in parent.children.
synthetic.parent = parent
synthetic.filename = anchor
synthetic.paths = routed.kind === 'generation'
synthetic.paths = routed.kind === 'interception'
? packageSearchPaths(routed.entry, request, cjs)
: cjs._nodeModulePaths(dirname(anchor))
try {
@@ -760,7 +760,7 @@ export function installProfileResolution(
if (state === undefined) return resolveFrom(target.parentURL)
if (state.route.kind === 'native') return resolveFrom(target.parentURL)
const route = state.route
if (route.kind === 'native-after-generation') return resolveFrom(pathToFileURL(route.parent).href)
if (route.kind === 'native-after-interception') return resolveFrom(pathToFileURL(route.parent).href)
return resolveFrom(pathToFileURL(route.entry.declarer).href)
}
const wrappedFilename: CommonJsModule['_resolveFilename'] = (request, parent, main, options) => {
@@ -811,7 +811,7 @@ export function installProfileResolution(
if (cacheable) state.cjs = result
return result
}
if (route.kind === 'native-after-generation' && explicit !== undefined && explicitPaths !== undefined) {
if (route.kind === 'native-after-interception' && explicit !== undefined && explicitPaths !== undefined) {
try {
return originalFilename.call(cjs, request, parent, main, {
...options,
@@ -831,10 +831,10 @@ export function installProfileResolution(
try {
expected = resolveRoutedCjs(request, route, parent, main, routedOptions)
} catch (error) {
if (route.kind !== 'generation' || !isUnselectedPackageMiss(error)) throw error
if (route.kind !== 'interception' || !isUnselectedPackageMiss(error)) throw error
try {
expected = resolveRoutedCjs(
request, { kind: 'native-after-generation', parent: route.after }, parent, main, routedOptions,
request, { kind: 'native-after-interception', parent: route.after }, parent, main, routedOptions,
)
} catch (afterError) {
const remaining = explicit === undefined || explicitPaths === undefined
@@ -873,14 +873,14 @@ export function installProfileResolution(
}
/**
* Publish one generation for Harness-owned Workers.
* @param generation - complete package table and profile scope.
* Publish one runtime resolution for Harness-owned Workers.
* @param resolution - complete package table and profile scope.
* @returns a disposer restoring the previous thread environment data.
*/
export function registerWorkerResolution(
generation: ProfileResolutionGeneration,
resolution: RuntimeResolution,
): () => void {
const previous = getEnvironmentData(WORKER_RESOLUTION_KEY)
setEnvironmentData(WORKER_RESOLUTION_KEY, { generation })
setEnvironmentData(WORKER_RESOLUTION_KEY, { resolution })
return () => { setEnvironmentData(WORKER_RESOLUTION_KEY, previous) }
}
@@ -6,11 +6,11 @@ import { join } from 'node:path'
import { Service, type Context } from '@deepseek-ai/cordis'
import {
barePackageName,
installProfileResolution,
installRuntimeInterception,
registerWorkerResolution,
type ProfileResolutionRegistration,
type RuntimeInterception,
} from './resolver.ts'
import type { ProfileResolutionGeneration } from '../profile.ts'
import type { RuntimeResolution } from '../profile.ts'
declare module '@deepseek-ai/cordis' {
interface Context {
@@ -33,10 +33,10 @@ export interface PluginPackage {
manifest: Record<string, unknown>
}
/** Optional runtime resolver installed and owned by {@link PluginPackages}. */
/** Optional runtime interception installed and owned by {@link PluginPackages}. */
export interface PluginPackagesConfig {
/** Complete package table; omit it to expose native package lookup only. */
generation?: ProfileResolutionGeneration
resolution?: RuntimeResolution
}
function readPackage(dir: string, fallbackName: string): PluginPackage | undefined {
@@ -57,14 +57,14 @@ function readPackage(dir: string, fallbackName: string): PluginPackage | undefin
/** Package lookup shared by metadata consumers in one profile process. */
export class PluginPackages extends Service {
private packages = new Map<string, PluginPackage | undefined>()
private readonly resolver: ProfileResolutionRegistration | undefined
private readonly resolver: RuntimeInterception | undefined
private disposeWorkerResolution: (() => void) | undefined
constructor(ctx: Context, config: PluginPackagesConfig = {}) {
super(ctx, 'pluginPackages')
if (config.generation === undefined) return
const resolver = installProfileResolution(config.generation)
this.disposeWorkerResolution = registerWorkerResolution(config.generation)
if (config.resolution === undefined) return
const resolver = installRuntimeInterception(config.resolution)
this.disposeWorkerResolution = registerWorkerResolution(config.resolution)
this.resolver = resolver
ctx.effect(() => () => {
this.disposeWorkerResolution?.()
@@ -73,15 +73,15 @@ export class PluginPackages extends Service {
}
/**
* Publish an additive generation for this process and subsequently created Workers.
* @param generation - fully constructed successor generation.
* Publish an additive successor generation for this process and subsequently created Workers.
* @param successor - fully constructed successor generation.
*/
replace(generation: ProfileResolutionGeneration): void {
replace(successor: RuntimeResolution): void {
if (this.resolver === undefined) throw new Error('plugin-packages: runtime resolution is not installed')
this.resolver.replace(generation)
this.resolver.replace(successor)
this.packages = new Map()
this.disposeWorkerResolution?.()
this.disposeWorkerResolution = registerWorkerResolution(generation)
this.disposeWorkerResolution = registerWorkerResolution(successor)
}
/**
@@ -1,12 +1,12 @@
/** Install an inherited profile resolution generation in one Harness-owned Worker. */
/** Install the inherited runtime resolution in one Harness-owned Worker. */
import { getEnvironmentData } from 'node:worker_threads'
import { installProfileResolution } from './resolver.ts'
import type { ProfileResolutionGeneration } from '../profile.ts'
import { installRuntimeInterception } from './resolver.ts'
import type { RuntimeResolution } from '../profile.ts'
const registration = getEnvironmentData(
'@deepseek-ai/dsh-app-boot/profile-resolution',
) as {
generation: ProfileResolutionGeneration
resolution: RuntimeResolution
} | undefined
if (registration !== undefined) installProfileResolution(registration.generation)
if (registration !== undefined) installRuntimeInterception(registration.resolution)
+13 -13
View File
@@ -15,7 +15,7 @@
* Module resolution is two-anchor by construction: a bundle name resolves
* first from the dsh installation (the launcher's own package), then from the
* profile directory. Pnpm-managed entries in the profile's `node_modules`
* resolve first. A runtime generation supplies packages carried by the
* resolve first. The runtime resolution supplies packages carried by the
* installation and selected bundles to Node's ESM and CommonJS resolvers.
* @module @deepseek-ai/dsh-app-boot/profile
*/
@@ -71,8 +71,8 @@ export interface Profile {
patches: PatchOptions[]
}
/** One package the generation supplies at the interception layer. */
export interface ProfileResolutionEntry {
/** One package the runtime resolution supplies at the interception layer. */
export interface RuntimeResolutionEntry {
/** Bare package name. */
readonly name: string
/** Package directory selected by the existing dependency traversal. */
@@ -86,7 +86,7 @@ export interface ProfileResolutionEntry {
}
/** Complete immutable package table for one profile launch. */
export interface ProfileResolutionGeneration {
export interface RuntimeResolution {
/** Directory containing every profile; its node_modules is the interception layer. */
readonly profilesDir: string
/** Active profile directory, when profile-scope entries were included. */
@@ -94,7 +94,7 @@ export interface ProfileResolutionGeneration {
/** Profile-declared packages installed in the profile's own node_modules. */
readonly localPackageNames: readonly string[]
/** Installation-scope entries followed by profile-scope entries in precedence order. */
readonly entries: readonly ProfileResolutionEntry[]
readonly entries: readonly RuntimeResolutionEntry[]
}
/**
@@ -156,7 +156,7 @@ const PROFILE_PATCH_TEMPLATE = `# Your patch layer for this dsh profile, applied
`
// The hoisted linker gives out-of-tree plugins a flat node_modules whose
// missing peers (cordis and friends) use the runtime generation, so every plugin shares the
// missing peers (cordis and friends) use the runtime resolution, so every plugin shares the
// installation's single cordis instance instead of a duplicate. pnpm ≥10
// reads its settings from pnpm-workspace.yaml, not .npmrc.
const PROFILE_PNPM_WORKSPACE = `packages:
@@ -310,8 +310,8 @@ function collectInstallationScopePackages(
return { packageNames: new Set(links.keys()), packageDirs: links, declarers, versions }
}
/** Inputs for {@link createProfileResolutionGeneration}. */
export interface ProfileResolutionOptions {
/** Inputs for {@link createRuntimeResolution}. */
export interface RuntimeResolutionOptions {
/** Absolute package.json path of the running dsh installation. */
installAnchor: string
/** Loaded profile whose selected bundles may carry profile-local plugins. */
@@ -321,13 +321,13 @@ export interface ProfileResolutionOptions {
}
/**
* Compute a profile resolution generation without writing module-resolution files.
* Compute the runtime resolution without writing module-resolution files.
* @param options - installation anchor, optional loaded profile, and Harness home.
* @returns the complete immutable generation.
* @returns the complete immutable runtime resolution.
*/
export async function createProfileResolutionGeneration(
options: ProfileResolutionOptions,
): Promise<ProfileResolutionGeneration> {
export async function createRuntimeResolution(
options: RuntimeResolutionOptions,
): Promise<RuntimeResolution> {
const { installAnchor, profile, home = resolveDshHome() } = options
const profilesDir = join(home, PROFILES_DIR)
const manifest = readOptionalProfileManifest(profile)
@@ -1,4 +1,4 @@
/** Package metadata queries share the active profile resolution generation. */
/** Package metadata queries share the active profile resolution resolution. */
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
@@ -9,7 +9,7 @@ import { getEnvironmentData } from 'node:worker_threads'
import { Context } from '@deepseek-ai/cordis'
import { afterEach, describe, expect, it } from 'vitest'
import { PluginPackages } from '../src/profile-resolution/service.ts'
import type { ProfileResolutionGeneration } from '../src/profile.ts'
import type { RuntimeResolution } from '../src/profile.ts'
const roots: string[] = []
const contexts: Context[] = []
@@ -36,9 +36,9 @@ function pkg(dir: string, version: string, name = 'metadata-lib'): string {
return join(dir, 'package.json')
}
function generation(
function resolution(
profilesDir: string, profileDir: string, packageDir: string, declarer: string, version: string,
): ProfileResolutionGeneration {
): RuntimeResolution {
return {
profilesDir,
profileDir,
@@ -48,7 +48,7 @@ function generation(
}
describe('profile package metadata service', () => {
it('resolves module URLs and package metadata through the current generation', async () => {
it('resolves module URLs and package metadata through the current resolution', async () => {
const root = mkdtempSync(join(tmpdir(), 'dsh-profile-package-service-'))
roots.push(root)
const profilesDir = join(root, 'profiles')
@@ -62,7 +62,7 @@ describe('profile package metadata service', () => {
contexts.push(ctx)
ctx.baseUrl = pathToFileURL(profileDir).href + '/'
await ctx.plugin(PluginPackages, {
generation: generation(profilesDir, profileDir, first, firstAnchor, '1.0.0'),
resolution: resolution(profilesDir, profileDir, first, firstAnchor, '1.0.0'),
})
expect(ctx.pluginPackages.packageOf('metadata-lib/private', parentURL)).toMatchObject({
@@ -126,7 +126,7 @@ describe('profile package metadata service', () => {
const ctx = new Context()
contexts.push(ctx)
await ctx.plugin(PluginPackages, {
generation: generation(profilesDir, profileDir, missing, join(root, 'owner.json'), '1.0.0'),
resolution: resolution(profilesDir, profileDir, missing, join(root, 'owner.json'), '1.0.0'),
})
expect(ctx.pluginPackages.packageOf(
'metadata-lib', pathToFileURL(join(profileDir, 'entry.mjs')).href,
@@ -134,22 +134,22 @@ describe('profile package metadata service', () => {
})
it('publishes additive generations to the process and future Workers', async () => {
const root = mkdtempSync(join(tmpdir(), 'dsh-package-service-generation-'))
const root = mkdtempSync(join(tmpdir(), 'dsh-package-service-resolution-'))
roots.push(root)
const profilesDir = join(root, 'profiles')
const profileDir = join(profilesDir, 'test')
const first = join(root, 'first')
const firstAnchor = pkg(first, '1.0.0')
const initial = generation(profilesDir, profileDir, first, firstAnchor, '1.0.0')
const initial = resolution(profilesDir, profileDir, first, firstAnchor, '1.0.0')
const key = '@deepseek-ai/dsh-app-boot/profile-resolution'
const previous = getEnvironmentData(key)
const ctx = new Context()
contexts.push(ctx)
await ctx.plugin(PluginPackages, { generation: initial })
await ctx.plugin(PluginPackages, { resolution: initial })
const initialWorkerData = getEnvironmentData(key) as {
generation: ProfileResolutionGeneration
resolution: RuntimeResolution
}
expect(initialWorkerData).toEqual({ generation: initial })
expect(initialWorkerData).toEqual({ resolution: initial })
const added = join(root, 'added')
const addedAnchor = pkg(added, '2.0.0', 'added-metadata')
@@ -162,7 +162,7 @@ describe('profile package metadata service', () => {
}
ctx.pluginPackages.replace(next)
expect(getEnvironmentData(key)).toEqual({
generation: next,
resolution: next,
})
expect(ctx.pluginPackages.packageOf(
'added-metadata', pathToFileURL(join(profileDir, 'entry.mjs')).href,
@@ -1,11 +1,11 @@
/** Worker bootstrap installs only the generation inherited from its parent. */
/** Worker bootstrap installs only the resolution inherited from its parent. */
import { beforeEach, expect, it, vi } from 'vitest'
import type { ProfileResolutionGeneration } from '../src/profile.ts'
import type { RuntimeResolution } from '../src/profile.ts'
const harness = vi.hoisted(() => ({
data: undefined as {
generation: ProfileResolutionGeneration
resolution: RuntimeResolution
} | undefined,
install: vi.fn(),
}))
@@ -15,7 +15,7 @@ vi.mock('node:worker_threads', () => ({
}))
vi.mock('../src/profile-resolution/resolver.ts', () => ({
installProfileResolution: harness.install,
installRuntimeInterception: harness.install,
}))
beforeEach(() => {
@@ -29,14 +29,14 @@ it('does nothing without inherited profile resolution data', async () => {
expect(harness.install).not.toHaveBeenCalled()
})
it('installs the inherited generation', async () => {
const generation: ProfileResolutionGeneration = {
it('installs the inherited resolution', async () => {
const resolution: RuntimeResolution = {
profilesDir: '/profiles',
profileDir: '/profiles/test',
localPackageNames: [],
entries: [],
}
harness.data = { generation }
harness.data = { resolution }
await import('../src/profile-resolution/worker-bootstrap.ts')
expect(harness.install).toHaveBeenCalledWith(generation)
expect(harness.install).toHaveBeenCalledWith(resolution)
})
@@ -1,4 +1,4 @@
/** Runtime profile resolution uses one eager generation for ESM and CommonJS. */
/** Runtime profile resolution uses one eager resolution for ESM and CommonJS. */
import {
existsSync,
@@ -17,19 +17,19 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
import { getEnvironmentData } from 'node:worker_threads'
import { afterEach, describe, expect, it } from 'vitest'
import {
installProfileResolution,
installRuntimeInterception,
registerWorkerResolution,
type ProfileResolutionRegistration,
type RuntimeInterception,
} from '../src/profile-resolution/resolver.ts'
import {
createProfileResolutionGeneration,
createRuntimeResolution,
loadProfile,
type Profile,
type ProfileResolutionGeneration,
type RuntimeResolution,
} from '../src/profile.ts'
const roots: string[] = []
const registrations: ProfileResolutionRegistration[] = []
const registrations: RuntimeInterception[] = []
afterEach(() => {
for (const registration of registrations.splice(0).reverse()) registration.dispose()
@@ -142,7 +142,7 @@ function fixture(name = '@deepseek-ai/dsh-core'): {
// root plays $DSH_HOME; the running dsh lives in a global install outside the profiles tree.
// Node reports resolved module paths through the native realpath: /private/var for a macOS tmpdir under /var,
// and the long directory name for a Windows tmpdir spelled with an 8.3 short name.
const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-profile-generation-')))
const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-profile-resolution-')))
roots.push(root)
const installDir = join(root, 'global', 'node_modules', '@deepseek-ai', 'dsh')
const installed = join(installDir, 'node_modules', name)
@@ -166,8 +166,8 @@ function fixture(name = '@deepseek-ai/dsh-core'): {
}
}
async function generationOf(f: ReturnType<typeof fixture>): Promise<ProfileResolutionGeneration> {
return await createProfileResolutionGeneration({
async function resolutionOf(f: ReturnType<typeof fixture>): Promise<RuntimeResolution> {
return await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
@@ -214,48 +214,48 @@ function lookupMatrix(): LookupCase[] {
return cases
}
/** The lookup order: profile-internal layers, then the generation at the interception layer, then the remaining ancestors. */
function lookupWinner(matrixCase: LookupCase): LookupLayer | 'generation' | 'missing' {
/** The lookup order: profile-internal layers, then the resolution at the interception layer, then the remaining ancestors. */
function lookupWinner(matrixCase: LookupCase): LookupLayer | 'interception' | 'missing' {
if (matrixCase.importer === 'plugin' && matrixCase.layers.includes('private')) return 'private'
if (matrixCase.layers.includes('profile')) return 'profile'
if (matrixCase.kind !== 'unlisted') return 'generation'
if (matrixCase.kind !== 'unlisted') return 'interception'
if (matrixCase.layers.includes('shared')) return 'shared'
if (matrixCase.layers.includes('home')) return 'home'
return 'missing'
}
describe('profile resolution generation', { concurrent: false }, () => {
it('computes an immutable runtime generation without writing profile packages', async () => {
describe('runtime resolution', { concurrent: false }, () => {
it('computes an immutable runtime resolution without writing profile packages', async () => {
const f = fixture()
const generation = await generationOf(f)
expect(generation.entries.find(entry => entry.name === '@deepseek-ai/dsh-core')).toMatchObject({
const resolution = await resolutionOf(f)
expect(resolution.entries.find(entry => entry.name === '@deepseek-ai/dsh-core')).toMatchObject({
packageDir: f.installed,
version: '1.0.0',
declarer: f.installAnchor,
scope: 'installation',
})
expect(existsSync(join(generation.profilesDir, 'node_modules'))).toBe(false)
expect(existsSync(join(resolution.profilesDir, 'node_modules'))).toBe(false)
expect(existsSync(join(f.profile.dir, 'node_modules'))).toBe(false)
expect(Object.isFrozen(generation)).toBe(true)
expect(Object.isFrozen(generation.entries)).toBe(true)
expect(generation.entries.every(Object.isFrozen)).toBe(true)
expect(Object.isFrozen(resolution)).toBe(true)
expect(Object.isFrozen(resolution.entries)).toBe(true)
expect(resolution.entries.every(Object.isFrozen)).toBe(true)
const installationOnly = await createProfileResolutionGeneration({
const installationOnly = await createRuntimeResolution({
installAnchor: f.installAnchor,
home: join(f.root, 'installation-only-home'),
})
expect(installationOnly.profileDir).toBeUndefined()
expect(installationOnly.localPackageNames).toEqual([])
const registration = installProfileResolution(installationOnly)
const registration = installRuntimeInterception(installationOnly)
registrations.push(registration)
expect(createRequire(join(installationOnly.profilesDir, 'entry.cjs'))('@deepseek-ai/dsh-core'))
.toEqual({ marker: 1 })
})
it('fails generation construction before writing when the profile manifest is malformed', async () => {
it('fails resolution construction before writing when the profile manifest is malformed', async () => {
const f = fixture()
file(join(f.profile.dir, 'package.json'), '{')
await expect(generationOf(f)).rejects.toThrow(SyntaxError)
await expect(resolutionOf(f)).rejects.toThrow(SyntaxError)
expect(existsSync(join(f.root, 'profiles', 'node_modules'))).toBe(false)
})
@@ -291,13 +291,13 @@ describe('profile resolution generation', { concurrent: false }, () => {
})
}
expect(createRequire(join(bridge, 'node_modules', 'middle', 'package.json'))('leaf')).toEqual({ marker: 1 })
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor, profile: f.profile, home: f.root,
})
expect(generation.entries.find(entry => entry.name === '@deepseek-ai/dsh')?.declarer).toBe(f.installAnchor)
expect(generation.entries.find(entry => entry.name === 'middle')?.declarer)
expect(resolution.entries.find(entry => entry.name === '@deepseek-ai/dsh')?.declarer).toBe(f.installAnchor)
expect(resolution.entries.find(entry => entry.name === 'middle')?.declarer)
.toBe(join(bridge, 'package.json'))
expect(generation.entries.find(entry => entry.name === 'leaf')).toMatchObject({
expect(resolution.entries.find(entry => entry.name === 'leaf')).toMatchObject({
packageDir: linked ? workspaceLeaf : logicalLeaf,
declarer: join(middle, 'package.json'),
version: linked ? '2.0.0' : '1.0.0',
@@ -334,16 +334,16 @@ describe('profile resolution generation', { concurrent: false }, () => {
patches: [],
})
const generation = await generationOf(f)
expect(generation.entries.find(entry => entry.name === 'ordered-choice')).toMatchObject({
const resolution = await resolutionOf(f)
expect(resolution.entries.find(entry => entry.name === 'ordered-choice')).toMatchObject({
packageDir: installationChoice,
scope: 'installation',
})
const bundleChoice = generation.entries.find(entry => entry.name === 'bundle-choice')
if (bundleChoice === undefined) throw new Error('generation omitted bundle-choice')
const bundleChoice = resolution.entries.find(entry => entry.name === 'bundle-choice')
if (bundleChoice === undefined) throw new Error('resolution omitted bundle-choice')
expect(bundleChoice).toMatchObject({ scope: 'profile' })
expect(realpathSync.native(bundleChoice.packageDir)).toBe(realpathSync.native(firstBundleChoice))
expect(generation.entries.find(entry => entry.name === 'peer-choice')).toMatchObject({
expect(resolution.entries.find(entry => entry.name === 'peer-choice')).toMatchObject({
packageDir: peerChoice,
scope: 'installation',
})
@@ -351,7 +351,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
it('routes ESM and CommonJS through the same installation entry', async () => {
const f = fixture()
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require('@deepseek-ai/dsh-core')).toEqual({ marker: 1 })
@@ -367,7 +367,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
it('routes a scoped CommonJS package through its containing node_modules directory', async () => {
const f = fixture('@scope/tools')
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
@@ -381,7 +381,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
pkg(target, 'real-lib', 5)
rmSync(f.installed, { recursive: true })
symlinkSync(target, f.installed, process.platform === 'win32' ? 'junction' : 'dir')
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(createRequire(join(f.profile.dir, 'entry.cjs'))('aliased-lib')).toEqual({ marker: 5 })
@@ -398,12 +398,12 @@ describe('profile resolution generation', { concurrent: false }, () => {
dir: profileDir,
patchPath: join(profileDir, 'cordis.patch.yml'),
}
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile,
home: f.root,
})
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const require = createRequire(join(profileDir, 'entry.cjs'))
expect(require('@deepseek-ai/dsh-core')).toEqual({ marker: 1 })
@@ -420,7 +420,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const alternative = join(f.root, 'alternative')
const alternativePackage = join(alternative, 'node_modules', '@deepseek-ai/dsh-core')
pkg(alternativePackage, '@deepseek-ai/dsh-core', 2)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(join(f.installed, 'index.cjs'))
@@ -439,7 +439,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
it('keeps earlier explicit CommonJS paths ahead of a managed local failure', async () => {
const f = fixture()
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
@@ -455,7 +455,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const paths = [alternative, f.profile.dir]
expect(require.resolve('@deepseek-ai/dsh-core', { paths })).toBe(selected)
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(require.resolve('@deepseek-ai/dsh-core', { paths })).toBe(selected)
})
@@ -468,7 +468,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
const paths = [invalid, f.profile.dir]
const nativeError = thrownError(() => require.resolve('@deepseek-ai/dsh-core', { paths }))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const runtimeError = thrownError(() => require.resolve('@deepseek-ai/dsh-core', { paths }))
@@ -490,7 +490,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
const paths = [f.profile.dir, alternative]
const nativeError = thrownError(() => require.resolve(name, { paths }))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const runtimeError = thrownError(() => require.resolve(name, { paths }))
@@ -502,18 +502,18 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(runtimeError.message).toBe(nativeError.message)
})
it('keeps a missing fallback legacy main after a generation miss', async () => {
it('keeps a missing fallback legacy main after a resolution miss', async () => {
const f = fixture()
const selected = join(f.root, 'node_modules', '@deepseek-ai/dsh-core')
file(join(selected, 'package.json'), JSON.stringify({ name: '@deepseek-ai/dsh-core', main: './missing.cjs' }))
const alternative = join(f.root, 'alternative')
pkg(join(alternative, 'node_modules', '@deepseek-ai/dsh-core'), '@deepseek-ai/dsh-core', 2)
const generation = await generationOf(f)
const resolution = await resolutionOf(f)
rmSync(f.installed, { recursive: true })
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
const paths = [f.profile.dir, alternative]
const nativeError = thrownError(() => require.resolve('@deepseek-ai/dsh-core', { paths }))
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const runtimeError = thrownError(() => require.resolve('@deepseek-ai/dsh-core', { paths }))
@@ -525,7 +525,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(runtimeError.message).toBe(nativeError.message)
})
it('keeps a profile-local package ahead of the generation', async () => {
it('keeps a profile-local package ahead of the resolution', async () => {
const f = fixture()
file(join(f.profile.dir, 'package.json'), JSON.stringify({
name: 'dsh-profile-web',
@@ -541,9 +541,9 @@ describe('profile resolution generation', { concurrent: false }, () => {
join(f.profile.dir, 'node_modules', 'linked-local'),
process.platform === 'win32' ? 'junction' : 'dir',
)
const generation = await generationOf(f)
expect(generation.localPackageNames).toEqual(['@deepseek-ai/dsh-core', 'linked-local'])
const registration = installProfileResolution(generation)
const resolution = await resolutionOf(f)
expect(resolution.localPackageNames).toEqual(['@deepseek-ai/dsh-core', 'linked-local'])
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require('@deepseek-ai/dsh-core')).toEqual({ marker: 2 })
@@ -573,12 +573,12 @@ describe('profile resolution generation', { concurrent: false }, () => {
}))
const alias = join(f.profile.dir, 'node_modules', 'alias')
pkg(alias, 'real-name', 6)
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
})
expect(generation.localPackageNames).toEqual(['alias'])
expect(resolution.localPackageNames).toEqual(['alias'])
const require = createRequire(join(alias, 'inside.cjs'))
expect(require.resolve('real-name')).toBe(join(alias, 'index.cjs'))
expect(require.resolve('real-name', { paths: [f.profile.dir] })).toBe(join(alias, 'index.cjs'))
@@ -592,7 +592,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(() => resolveFrom('@deepseek-ai/dsh-core', pathToFileURL(join(invalidScope, 'inside.mjs')).href))
.toThrow(/Invalid package config/u)
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(require.resolve('real-name')).toBe(join(alias, 'index.cjs'))
expect(require.resolve('real-name', { paths: [f.profile.dir] })).toBe(join(alias, 'index.cjs'))
@@ -605,14 +605,14 @@ describe('profile resolution generation', { concurrent: false }, () => {
.toThrow(/Invalid package config/u)
})
it('keeps package imports aliases on the generation route', async () => {
it('keeps package imports aliases on the resolution route', async () => {
const f = fixture()
file(join(f.profile.dir, 'package.json'), JSON.stringify({
name: 'dsh-profile-web',
private: true,
imports: { '#@deepseek-ai/dsh-core': '@deepseek-ai/dsh-core' },
}))
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
@@ -623,21 +623,21 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(() => require.resolve('#@deepseek-ai/dsh-core')).toThrow(/Cannot find module/u)
expect(() => resolveFrom('#@deepseek-ai/dsh-core', nativeParent)).toThrow(/Cannot find package/u)
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const runtimeParent = pathToFileURL(join(nested, 'entry-runtime.mjs')).href
expect(require.resolve('#@deepseek-ai/dsh-core')).toBe(join(f.installed, 'index.cjs'))
expect(resolveFrom('#@deepseek-ai/dsh-core', runtimeParent)).toBe(pathToFileURL(join(f.installed, 'index.js')).href)
})
it('imports package aliases through the generation and reports the original importer', async () => {
it('imports package aliases through the resolution and reports the original importer', async () => {
const f = fixture()
file(join(f.profile.dir, 'package.json'), JSON.stringify({
name: 'dsh-profile-web',
private: true,
imports: { '#library': '@deepseek-ai/dsh-core', '#missing': '@deepseek-ai/dsh-core/missing' },
}))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
await expect(importFrom('#library', parent)).resolves.toMatchObject({ marker: 1 })
@@ -654,7 +654,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
private: true,
imports: { '#missing-relative': './missing.cjs' },
}))
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
@@ -664,13 +664,13 @@ describe('profile resolution generation', { concurrent: false }, () => {
const cjsMessage = thrownMessage(() => require.resolve('#missing-relative'))
const esmMessage = thrownMessage(() => resolveFrom('#missing-relative', parent))
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(thrownMessage(() => require.resolve('#missing-relative'))).toBe(cjsMessage)
expect(thrownMessage(() => resolveFrom('#missing-relative', parent))).toBe(esmMessage)
})
it('keeps local and native-after-generation package imports targets in native order', async () => {
it('keeps local and native-after-resolution package imports targets in native order', async () => {
const f = fixture()
file(join(f.profile.dir, 'package.json'), JSON.stringify({
name: 'dsh-profile-web',
@@ -685,7 +685,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(require.resolve('#local')).toBe(join(local, 'index.cjs'))
expect(require.resolve('#ancestor')).toBe(join(ancestor, 'index.cjs'))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(require.resolve('#local')).toBe(join(local, 'index.cjs'))
expect(require.resolve('#ancestor')).toBe(join(ancestor, 'index.cjs'))
@@ -699,7 +699,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
}))
const require = createRequire(join(outside, 'entry.cjs'))
const nativeMessage = thrownMessage(() => require.resolve('#missing'))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(thrownMessage(() => require.resolve('#missing'))).toBe(nativeMessage)
@@ -714,13 +714,13 @@ describe('profile resolution generation', { concurrent: false }, () => {
file(nested, '')
const require = createRequire(nested)
const nativeMessage = thrownMessage(() => require.resolve('#@deepseek-ai/dsh-core'))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(thrownMessage(() => require.resolve('#@deepseek-ai/dsh-core'))).toBe(nativeMessage)
})
it('falls through a missing local CommonJS subpath to the generation', async () => {
it('falls through a missing local CommonJS subpath to the resolution', async () => {
const f = fixture()
file(join(f.profile.dir, 'package.json'), JSON.stringify({
name: 'dsh-profile-web',
@@ -737,9 +737,9 @@ describe('profile resolution generation', { concurrent: false }, () => {
name: '@deepseek-ai/dsh-core', version: '2.0.0', type: 'module', main: './index.cjs',
}))
file(join(local, 'index.cjs'), 'module.exports = { marker: 2 }\n')
const generation = await generationOf(f)
expect(generation.localPackageNames).toEqual(['@deepseek-ai/dsh-core'])
const registration = installProfileResolution(generation)
const resolution = await resolutionOf(f)
expect(resolution.localPackageNames).toEqual(['@deepseek-ai/dsh-core'])
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(createRequire(join(f.profile.dir, 'entry.cjs')).resolve('@deepseek-ai/dsh-core/only-install.cjs'))
@@ -765,7 +765,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
file(join(local, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-core', version: '2.0.0', type: 'module', exports: null,
}))
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
@@ -773,19 +773,19 @@ describe('profile resolution generation', { concurrent: false }, () => {
const require = createRequire(join(local, 'entry.cjs'))
expect(() => require.resolve('@deepseek-ai/dsh-core/only-install.cjs')).toThrow(/Cannot find module/u)
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(require.resolve('@deepseek-ai/dsh-core/only-install.cjs')).toBe(installedSubpath)
})
it('stops a local CommonJS probe before the generation fallback position', async () => {
it('stops a local CommonJS probe before the resolution fallback position', async () => {
const f = fixture()
file(join(f.installed, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-core', version: '1.0.0', type: 'module', main: './index.cjs',
}))
const installedSubpath = join(f.installed, 'only-install.cjs')
file(installedSubpath, 'module.exports = { marker: 8 }\n')
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
@@ -802,14 +802,14 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(thrownError(() => require.resolve('@deepseek-ai/dsh-core/only-install.cjs')))
.toMatchObject({ code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' })
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(require.resolve('@deepseek-ai/dsh-core/only-install.cjs')).toBe(installedSubpath)
})
it('observes a profile-local package installed after an earlier miss', async () => {
const f = fixture()
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
@@ -828,7 +828,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const f = fixture()
pkg(join(f.profile.dir, 'node_modules', 'undeclared-local'), 'undeclared-local', 6)
file(join(f.profile.dir, 'relative.cjs'), 'module.exports = 7\n')
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require('undeclared-local')).toEqual({ marker: 6 })
@@ -859,39 +859,39 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(internal.Module._resolveFilename('node:path', undefined, false)).toBe('node:path')
})
it('keeps a legacy CommonJS package without a manifest ahead of the generation', async () => {
it('keeps a legacy CommonJS package without a manifest ahead of the resolution', async () => {
const f = fixture()
file(join(f.profile.dir, 'node_modules', '@deepseek-ai/dsh-core', 'index.js'), 'module.exports = { marker: 2 }\n')
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(createRequire(join(f.profile.dir, 'entry.cjs'))('@deepseek-ai/dsh-core')).toEqual({ marker: 2 })
})
it('keeps a manifestless local CommonJS subpath ahead of the generation', async () => {
it('keeps a manifestless local CommonJS subpath ahead of the resolution', async () => {
const f = fixture()
const localSubpath = join(f.profile.dir, 'node_modules', '@deepseek-ai/dsh-core', 'sub.cjs')
file(localSubpath, 'module.exports = { marker: 2 }\n')
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require.resolve('@deepseek-ai/dsh-core/sub.cjs')).toBe(localSubpath)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(require.resolve('@deepseek-ai/dsh-core/sub.cjs')).toBe(localSubpath)
})
it('keeps a local extensionless CommonJS package ahead of the generation', async () => {
it('keeps a local extensionless CommonJS package ahead of the resolution', async () => {
const f = fixture()
const local = join(f.profile.dir, 'node_modules', '@deepseek-ai/dsh-core')
file(local, 'module.exports = { marker: 2 }\n')
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(local)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(local)
})
it('keeps a local legacy main outside its package directory ahead of the generation', async () => {
it('keeps a local legacy main outside its package directory ahead of the resolution', async () => {
const f = fixture()
const local = join(f.profile.dir, 'node_modules', '@deepseek-ai/dsh-core')
const outside = join(f.profile.dir, 'node_modules', '@deepseek-ai', 'outside.cjs')
@@ -900,18 +900,18 @@ describe('profile resolution generation', { concurrent: false }, () => {
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(outside)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(outside)
})
it('keeps a missing local legacy main error ahead of the generation', async () => {
it('keeps a missing local legacy main error ahead of the resolution', async () => {
const f = fixture()
const local = join(f.profile.dir, 'node_modules', '@deepseek-ai/dsh-core')
file(join(local, 'package.json'), JSON.stringify({ name: '@deepseek-ai/dsh-core', main: './missing.cjs' }))
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
const nativeError = thrownError(() => require.resolve('@deepseek-ai/dsh-core'))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const runtimeError = thrownError(() => require.resolve('@deepseek-ai/dsh-core'))
@@ -923,10 +923,10 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(runtimeError.message).toBe(nativeError.message)
})
it('keeps a profile-local CommonJS package file ahead of the generation', async () => {
it('keeps a profile-local CommonJS package file ahead of the resolution', async () => {
const f = fixture()
file(join(f.profile.dir, 'node_modules', '@deepseek-ai/dsh-core.js'), 'module.exports = { marker: 2 }\n')
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(createRequire(join(f.profile.dir, 'entry.cjs'))('@deepseek-ai/dsh-core')).toEqual({ marker: 2 })
expect(resolveFrom('@deepseek-ai/dsh-core', pathToFileURL(join(f.profile.dir, 'entry.mjs')).href)).toBe(
@@ -937,7 +937,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
it('preserves the ESM and CommonJS behavior of an empty local package directory', async () => {
const f = fixture()
mkdirSync(join(f.profile.dir, 'node_modules', '@deepseek-ai/dsh-core'), { recursive: true })
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(createRequire(join(f.profile.dir, 'entry.cjs'))('@deepseek-ai/dsh-core')).toEqual({ marker: 1 })
expect(() => resolveFrom(
@@ -957,7 +957,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
patchPath: join(bundleDir, 'cordis.patch.yml'),
patches: [],
})
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(createRequire(join(f.profile.dir, 'entry.cjs'))('bundle-only')).toEqual({ marker: 4 })
const other = join(f.root, 'profiles', 'other', 'entry.cjs')
@@ -970,8 +970,8 @@ describe('profile resolution generation', { concurrent: false }, () => {
.toThrow(/Cannot find module/u)
expect(() => createRequire(other).resolve('missing-explicit', { paths: [dirname(other)] }))
.toThrow(/Cannot find module/u)
file(join(f.root, 'node_modules', 'invalid-after-generation', 'package.json'), '{')
expect(() => createRequire(other).resolve('invalid-after-generation', {
file(join(f.root, 'node_modules', 'invalid-after-resolution', 'package.json'), '{')
expect(() => createRequire(other).resolve('invalid-after-resolution', {
paths: [dirname(other), f.profile.dir],
})).toThrow(/Invalid package config/u)
file(join(f.root, 'node_modules', 'invalid-explicit', 'package.json'), '{')
@@ -980,10 +980,10 @@ describe('profile resolution generation', { concurrent: false }, () => {
})).toThrow(/Invalid package config/u)
})
it('keeps the generation ahead of packages above the shared fallback position', async () => {
it('keeps the resolution ahead of packages above the shared fallback position', async () => {
const f = fixture()
pkg(join(f.root, 'node_modules', '@deepseek-ai/dsh-core'), '@deepseek-ai/dsh-core', 2)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require('@deepseek-ai/dsh-core')).toEqual({ marker: 1 })
@@ -991,7 +991,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
.toMatchObject({ marker: 1 })
})
it('continues the original ancestor lookup after a generation subpath miss', async () => {
it('continues the original ancestor lookup after a resolution subpath miss', async () => {
const f = fixture()
const home = join(f.root, 'home')
const profileDir = join(home, 'profiles', 'web')
@@ -1009,7 +1009,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
}))
const ancestorSubpath = join(home, 'node_modules', '@deepseek-ai/dsh-core', 'sub.cjs')
file(ancestorSubpath, 'module.exports = { marker: 3 }\n')
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile,
home,
@@ -1018,13 +1018,13 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(() => require.resolve('@deepseek-ai/dsh-core')).toThrow(/Cannot find module/u)
expect(require.resolve('@deepseek-ai/dsh-core/sub.cjs')).toBe(ancestorSubpath)
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(join(f.installed, 'index.cjs'))
expect(require.resolve('@deepseek-ai/dsh-core/sub.cjs')).toBe(ancestorSubpath)
})
it('continues explicit CommonJS paths after a generation subpath miss', async () => {
it('continues explicit CommonJS paths after a resolution subpath miss', async () => {
const f = fixture()
file(join(f.installed, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-core', version: '1.0.0', type: 'module', main: './index.cjs',
@@ -1032,7 +1032,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const alternative = join(f.root, 'alternative')
const alternativeSubpath = join(alternative, 'node_modules', '@deepseek-ai/dsh-core', 'sub.cjs')
file(alternativeSubpath, 'module.exports = { marker: 4 }\n')
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(createRequire(join(f.profile.dir, 'entry.cjs')).resolve('@deepseek-ai/dsh-core/sub.cjs', {
@@ -1040,12 +1040,12 @@ describe('profile resolution generation', { concurrent: false }, () => {
})).toBe(alternativeSubpath)
})
it('occupies the interception layer package directory for a generation hit and continues above it on a CommonJS subpath miss', async () => {
it('occupies the interception layer package directory for a resolution hit and continues above it on a CommonJS subpath miss', async () => {
const f = fixture()
file(join(f.installed, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-core', version: '1.0.0', main: './index.cjs',
}))
// The generation entry stands in for profiles/node_modules/@deepseek-ai/dsh-core, so this stale copy is never read.
// The resolution entry stands in for profiles/node_modules/@deepseek-ai/dsh-core, so this stale copy is never read.
const shared = join(f.root, 'profiles', 'node_modules', '@deepseek-ai/dsh-core')
file(join(shared, 'package.json'), JSON.stringify({ name: '@deepseek-ai/dsh-core', version: '9.0.0' }))
file(join(shared, 'sub.cjs'), 'module.exports = { marker: 9 }\n')
@@ -1056,7 +1056,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
// A name without an entry sees the physical directory at the same layer.
pkg(join(f.root, 'profiles', 'node_modules', 'left-pad'), 'left-pad', 9)
pkg(join(f.root, 'node_modules', 'left-pad'), 'left-pad', 3)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(join(f.installed, 'index.cjs'))
@@ -1090,9 +1090,9 @@ describe('profile resolution generation', { concurrent: false }, () => {
const shared = join(f.root, 'profiles', 'node_modules', name)
mkdirSync(dirname(shared), { recursive: true })
symlinkSync(target, shared, process.platform === 'win32' ? 'junction' : 'dir')
const generation = await generationOf(f)
expect(generation.localPackageNames).toEqual([])
const registration = installProfileResolution(generation)
const resolution = await resolutionOf(f)
expect(resolution.localPackageNames).toEqual([])
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const require = createRequire(join(parentDir, 'entry.cjs'))
const parent = pathToFileURL(join(parentDir, 'entry.mjs')).href
@@ -1120,9 +1120,9 @@ describe('profile resolution generation', { concurrent: false }, () => {
mkdirSync(dirname(projected), { recursive: true })
symlinkSync(target, owned, process.platform === 'win32' ? 'junction' : 'dir')
symlinkSync(owned, projected, process.platform === 'win32' ? 'junction' : 'dir')
const generation = await generationOf(f)
expect(generation.localPackageNames).toEqual(['@deepseek-ai/dsh-core'])
const registration = installProfileResolution(generation)
const resolution = await resolutionOf(f)
expect(resolution.localPackageNames).toEqual(['@deepseek-ai/dsh-core'])
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
@@ -1166,9 +1166,9 @@ describe('profile resolution generation', { concurrent: false }, () => {
mkdirSync(dirname(shared), { recursive: true })
symlinkSync(selected, shared, process.platform === 'win32' ? 'junction' : 'dir')
}
const generation = await generationOf(f)
expect(generation.entries.some(entry => [dependency, middle, leaf].includes(entry.name))).toBe(false)
const registration = installProfileResolution(generation)
const resolution = await resolutionOf(f)
expect(resolution.entries.some(entry => [dependency, middle, leaf].includes(entry.name))).toBe(false)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
@@ -1217,11 +1217,11 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(profile.layers.map(layer => layer.packageName)).toEqual(['bundle-b'])
expect(existsSync(join(profileDir, '.dsh-module-fallback'))).toBe(false)
expect(existsSync(join(modules, plugin))).toBe(false)
const generation = await createProfileResolutionGeneration({ installAnchor: f.installAnchor, profile, home: f.root })
expect(generation.entries.find(entry => entry.name === plugin)).toMatchObject({
const resolution = await createRuntimeResolution({ installAnchor: f.installAnchor, profile, home: f.root })
expect(resolution.entries.find(entry => entry.name === plugin)).toMatchObject({
packageDir: join(modules, 'bundle-b', 'node_modules', plugin), version: '2.0.0', scope: 'profile',
})
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(createRequire(join(profileDir, 'entry.cjs'))(plugin)).toEqual({ marker: 2 })
expect(await importFrom(plugin, pathToFileURL(join(profileDir, 'entry.mjs')).href)).toMatchObject({ marker: 2 })
@@ -1245,8 +1245,8 @@ describe('profile resolution generation', { concurrent: false }, () => {
symlinkSync(owned, projected, process.platform === 'win32' ? 'junction' : 'dir')
const ancestor = join(f.root, 'node_modules', 'bridge')
pkg(ancestor, 'bridge', 3)
const generation = await generationOf(f)
expect(generation.entries.find(entry => entry.name === 'bridge'))
const resolution = await resolutionOf(f)
expect(resolution.entries.find(entry => entry.name === 'bridge'))
.toMatchObject({ packageDir: projected, version: '9.0.0', scope: 'profile' })
expect(realpathSync(projected)).toBe(target)
})
@@ -1285,10 +1285,10 @@ describe('profile resolution generation', { concurrent: false }, () => {
pkg(logical[layer], name, LOOKUP_MARKERS[layer])
}
}
const generation = await generationOf(f)
expect(generation.entries.find(entry => entry.name === name)?.scope)
const resolution = await resolutionOf(f)
expect(resolution.entries.find(entry => entry.name === name)?.scope)
.toBe(kind === 'installation' ? 'installation' : kind === 'bundle' ? 'profile' : undefined)
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const importerDir = importer === 'plugin' ? pluginDir : f.profile.dir
const require = createRequire(join(importerDir, 'entry.cjs'))
@@ -1301,11 +1301,11 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(registration.packageDir(name, parent)).toBeUndefined()
return
}
const packageDir = winner === 'generation'
const packageDir = winner === 'interception'
? (kind === 'installation' ? f.installed : bundleLib)
: logical[winner]
const realDir = winner === 'generation' ? packageDir : real[winner]
const marker = winner === 'generation' ? (kind === 'installation' ? 1 : 2) : LOOKUP_MARKERS[winner]
const realDir = winner === 'interception' ? packageDir : real[winner]
const marker = winner === 'interception' ? (kind === 'installation' ? 1 : 2) : LOOKUP_MARKERS[winner]
expect(require(name)).toEqual({ marker })
expect(require.resolve(name)).toBe(join(realDir, 'index.cjs'))
expect(require.resolve(name, { paths: [importerDir] })).toBe(join(realDir, 'index.cjs'))
@@ -1315,7 +1315,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
})
it('uses shared packages in the canonicalized profiles tree before its ancestors', async () => {
const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-profile-generation-symlink-')))
const root = realpathSync.native(mkdtempSync(join(tmpdir(), 'dsh-profile-resolution-symlink-')))
roots.push(root)
const carrier = join(root, 'carrier')
const profilesDir = join(root, 'home', 'profiles')
@@ -1328,7 +1328,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const ancestor = join(carrier, 'node_modules', 'left-pad')
pkg(ancestor, 'left-pad', 3)
pkg(join(root, 'home', 'node_modules', 'left-pad'), 'left-pad', 4)
const registration = installProfileResolution({
const registration = installRuntimeInterception({
profilesDir,
profileDir: join(profilesDir, 'web'),
localPackageNames: [],
@@ -1347,7 +1347,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
conditionalPkg(f.installed, 'conditional-lib', 11, 12)
const local = join(f.profile.dir, 'node_modules', 'conditional-lib')
conditionalPkg(local, 'conditional-lib', 21, 22)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(await importFrom('conditional-lib', pathToFileURL(join(f.profile.dir, 'entry.mjs')).href))
.toMatchObject({ marker: 21 })
@@ -1376,10 +1376,10 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(require.resolve('conditional-lib')).toBe(join(local, 'require.cjs'))
})
it('passes explicit CommonJS conditions to the generation target', async () => {
it('passes explicit CommonJS conditions to the resolution target', async () => {
const f = fixture('conditional-lib')
conditionalPkg(f.installed, 'conditional-lib', 11, 12)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const addon = createRequire(import.meta.url)('node-addon-require-builtin') as { requireBuiltin(id: string): unknown }
const internal = addon.requireBuiltin('internal/modules/cjs/loader') as {
@@ -1406,7 +1406,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
it('does not attach routed CommonJS anchors to the importing module', async () => {
const f = fixture()
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const addon = createRequire(import.meta.url)('node-addon-require-builtin') as { requireBuiltin(id: string): unknown }
const internal = addon.requireBuiltin('internal/modules/cjs/loader') as {
@@ -1435,14 +1435,14 @@ describe('profile resolution generation', { concurrent: false }, () => {
file(join(f.installed, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-core', version: '1.0.0', type: 'module', main: './index.cjs',
}))
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
})
const nativeRequire = createRequire(join(f.profile.dir, 'entry-native.cjs'))
const nativeError = thrownError(() => nativeRequire.resolve('@deepseek-ai/dsh-core/missing.cjs'))
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
const runtimeRequire = createRequire(join(f.profile.dir, 'entry-runtime.cjs'))
const runtimeError = thrownError(() => runtimeRequire.resolve('@deepseek-ai/dsh-core/missing.cjs'))
@@ -1458,12 +1458,12 @@ describe('profile resolution generation', { concurrent: false }, () => {
const f = fixture()
const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
const nativeMessage = thrownMessage(() => resolveFrom('unavailable-lib', parent))
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
})
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(thrownMessage(() => resolveFrom('unavailable-lib', parent))).toBe(nativeMessage)
@@ -1471,9 +1471,9 @@ describe('profile resolution generation', { concurrent: false }, () => {
.toContain(` imported from ${fileURLToPath(parent)}`)
})
it('leaves an invalid generation manifest error to Node', async () => {
it('leaves an invalid resolution manifest error to Node', async () => {
const f = fixture()
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor: f.installAnchor,
profile: f.profile,
home: f.root,
@@ -1482,7 +1482,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
expect(() => resolveFrom('@deepseek-ai/dsh-core', pathToFileURL(f.installAnchor).href))
.toThrow(/Invalid package config/u)
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
registrations.push(registration)
expect(() => resolveFrom('@deepseek-ai/dsh-core', parent)).toThrow(/Invalid package config/u)
@@ -1494,7 +1494,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
name: 'broken-lib',
exports: './missing.js',
}))
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(() => { require('broken-lib') }).toThrow(/Cannot find module|could not find/u)
@@ -1503,20 +1503,20 @@ describe('profile resolution generation', { concurrent: false }, () => {
)).rejects.toThrow(/Cannot find module|Cannot find package/u)
})
it('does not fall back after the generation selects a missing exports target', async () => {
it('does not fall back after the resolution selects a missing exports target', async () => {
const f = fixture()
file(join(f.installed, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-core', version: '1.0.0', type: 'module', exports: './missing.cjs',
}))
pkg(join(f.root, 'node_modules', '@deepseek-ai/dsh-core'), '@deepseek-ai/dsh-core', 2)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(() => createRequire(join(f.profile.dir, 'entry.cjs')).resolve('@deepseek-ai/dsh-core'))
.toThrow(/Cannot find module/u)
})
it('does not fall back after the generation selects a missing legacy main', async () => {
it('does not fall back after the resolution selects a missing legacy main', async () => {
const f = fixture()
file(join(f.installed, 'package.json'), JSON.stringify({
name: '@deepseek-ai/dsh-core', version: '1.0.0', type: 'module', main: './missing.cjs',
@@ -1524,19 +1524,19 @@ describe('profile resolution generation', { concurrent: false }, () => {
unlinkSync(join(f.installed, 'index.cjs'))
unlinkSync(join(f.installed, 'index.js'))
pkg(join(f.root, 'node_modules', '@deepseek-ai/dsh-core'), '@deepseek-ai/dsh-core', 2)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
expect(() => createRequire(join(f.profile.dir, 'entry.cjs')).resolve('@deepseek-ai/dsh-core'))
.toThrow(/valid "main" entry/u)
})
it('publishes an additive generation and replaces its miss cache atomically', async () => {
it('publishes an additive resolution and replaces its miss cache atomically', async () => {
const f = fixture()
const added = join(f.root, 'added')
pkg(added, 'added-lib', 2)
const first = await generationOf(f)
const registration = installProfileResolution(first)
const first = await resolutionOf(f)
const registration = installRuntimeInterception(first)
registrations.push(registration)
const parent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
expect(registration.packageDir('added-lib', parent)).toBeUndefined()
@@ -1557,8 +1557,8 @@ describe('profile resolution generation', { concurrent: false }, () => {
const f = fixture()
const second = join(f.root, 'second')
pkg(second, '@deepseek-ai/dsh-core', 2)
const first = await generationOf(f)
const registration = installProfileResolution(first)
const first = await resolutionOf(f)
const registration = installRuntimeInterception(first)
registrations.push(registration)
const alias = join(f.root, 'dsh-core-alias')
symlinkSync(f.installed, alias, process.platform === 'win32' ? 'junction' : 'dir')
@@ -1609,7 +1609,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
pkg(scopedPackage, '@scope/outside', 6)
const ancestorPackage = join(f.root, 'node_modules', 'ancestor-lib')
pkg(ancestorPackage, 'ancestor-lib', 7)
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const profileParent = pathToFileURL(join(f.profile.dir, 'entry.mjs')).href
const outsideParent = pathToFileURL(join(outside, 'entry.mjs')).href
@@ -1632,12 +1632,12 @@ describe('profile resolution generation', { concurrent: false }, () => {
expect(registration.packageDir('missing', `${pathToFileURL(f.profile.dir).href}/%ZZ`)).toBeUndefined()
})
it('leaves the published generation intact when successor construction fails', async () => {
it('leaves the published resolution intact when successor construction fails', async () => {
const f = fixture()
const first = await generationOf(f)
const registration = installProfileResolution(first)
const first = await resolutionOf(f)
const registration = installRuntimeInterception(first)
registrations.push(registration)
await expect(createProfileResolutionGeneration({
await expect(createRuntimeResolution({
installAnchor: join(f.root, 'missing', 'package.json'),
profile: f.profile,
home: f.root,
@@ -1647,14 +1647,14 @@ describe('profile resolution generation', { concurrent: false }, () => {
)).toBe(f.installed)
})
it('publishes and restores the generation inherited by owned Workers', async () => {
it('publishes and restores the resolution inherited by owned Workers', async () => {
const f = fixture()
const generation = await generationOf(f)
const resolution = await resolutionOf(f)
const key = '@deepseek-ai/dsh-app-boot/profile-resolution'
const previous = getEnvironmentData(key)
const dispose = registerWorkerResolution(generation)
const dispose = registerWorkerResolution(resolution)
try {
expect(getEnvironmentData(key)).toEqual({ generation })
expect(getEnvironmentData(key)).toEqual({ resolution })
} finally {
dispose()
}
@@ -1663,7 +1663,7 @@ describe('profile resolution generation', { concurrent: false }, () => {
it('restores CommonJS resolution when the registration is disposed', async () => {
const f = fixture()
const registration = installProfileResolution(await generationOf(f))
const registration = installRuntimeInterception(await resolutionOf(f))
registrations.push(registration)
const require = createRequire(join(f.profile.dir, 'entry.cjs'))
expect(require.resolve('@deepseek-ai/dsh-core')).toBe(join(f.installed, 'index.cjs'))
+50 -50
View File
@@ -14,7 +14,7 @@ import { pathToFileURL } from 'node:url'
import { afterAll, describe, expect, it, onTestFinished, vi } from 'vitest'
import {
composeEntries,
createProfileResolutionGeneration,
createRuntimeResolution,
initProfile,
loadProfile,
loadProfileDirectory,
@@ -27,9 +27,9 @@ import {
resolveProfileDir,
writeProfileManifest,
type Profile,
type ProfileResolutionGeneration,
type RuntimeResolution,
} from '../src/index.ts'
import { installProfileResolution } from '../src/profile-resolution/resolver.ts'
import { installRuntimeInterception } from '../src/profile-resolution/resolver.ts'
const tempRoots: string[] = []
afterAll(() => {
@@ -93,7 +93,7 @@ function stageProfile(home: string, name: string, bundleAnchor: string): Profile
}
async function importFromGeneration(
generation: ProfileResolutionGeneration, specifier: string,
resolution: RuntimeResolution, specifier: string,
): Promise<Record<string, unknown>> {
const addon = createRequire(import.meta.url)('node-addon-require-builtin') as {
requireBuiltin(id: string): unknown
@@ -103,9 +103,9 @@ async function importFromGeneration(
import(specifier: string, parent: string, attributes: ImportAttributes): Promise<Record<string, unknown>>
}
}
const registration = installProfileResolution(generation)
const registration = installRuntimeInterception(resolution)
try {
const parent = pathToFileURL(join(generation.profilesDir, 'entry.mjs')).href
const parent = pathToFileURL(join(resolution.profilesDir, 'entry.mjs')).href
return await loader.getOrInitializeCascadedLoader().import(specifier, parent, {})
} finally {
registration.dispose()
@@ -134,18 +134,18 @@ describe('isolated profile resolution', () => {
writeFileSync(join(installed, 'package.json'), JSON.stringify({ name: 'pnpm-owned', main: 'index.js' }))
writeFileSync(join(installed, 'index.js'), 'module.exports = "profile-installed"\n')
const generationA = await createProfileResolutionGeneration({ installAnchor: anchorA, profile: profileA, home })
const generationB = await createProfileResolutionGeneration({ installAnchor: anchorB, profile: profileB, home })
let registration = installProfileResolution(generationA)
const resolutionA = await createRuntimeResolution({ installAnchor: anchorA, profile: profileA, home })
const resolutionB = await createRuntimeResolution({ installAnchor: anchorB, profile: profileB, home })
let registration = installRuntimeInterception(resolutionA)
try {
expect(realpathSync.native(createRequire(consumerA).resolve('commander')))
.toBe(realpathSync.native(join(anchorA, '..', 'node_modules', 'commander', 'index.js')))
registration.dispose()
registration = installProfileResolution(generationB)
registration = installRuntimeInterception(resolutionB)
expect(realpathSync.native(createRequire(consumerB).resolve('commander')))
.toBe(realpathSync.native(join(anchorB, '..', 'node_modules', 'commander', 'index.js')))
registration.dispose()
registration = installProfileResolution(generationA)
registration = installRuntimeInterception(resolutionA)
expect(realpathSync.native(createRequire(consumerA).resolve('pnpm-owned'))).toBe(realpathSync.native(join(installed, 'index.js')))
expect(readFileSync(join(installed, 'index.js'), 'utf8')).toContain('profile-installed')
expect(realpathSync.native(createRequire(consumerA).resolve('bundle-only')))
@@ -153,11 +153,11 @@ describe('isolated profile resolution', () => {
expect(existsSync(join(home, 'profiles', 'node_modules'))).toBe(false)
registration.dispose()
registration = installProfileResolution(await createProfileResolutionGeneration({
registration = installRuntimeInterception(await createRuntimeResolution({
installAnchor: anchorA, profile: { ...profileA, layers: [] }, home,
}))
expect(() => createRequire(consumerA).resolve('bundle-only')).toThrow(/Cannot find module/u)
expect(generationB.entries.find(entry => entry.name === 'bundle-only')?.packageDir)
expect(resolutionB.entries.find(entry => entry.name === 'bundle-only')?.packageDir)
.toBe(realpathSync.native(join(bundleAnchor, '..', 'node_modules', 'bundle-only')))
expect(realpathSync.native(createRequire(consumerA).resolve('commander')))
.toBe(realpathSync.native(join(anchorA, '..', 'node_modules', 'commander', 'index.js')))
@@ -381,9 +381,9 @@ describe('loadProfile', () => {
.toEqual([{ id: 'a', name: 'pkg-a', config: { value: 'after' } }])
expect(warn).toHaveBeenCalledWith(expect.stringContaining('skipping profile bundle "broken":'))
expect(readFileSync(join(dir, 'package.json'), 'utf8')).toBe(saved)
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, profile, home })
const resolution = await createRuntimeResolution({ installAnchor: anchor, profile, home })
const unavailable = failure === 'missing package' || failure === 'invalid manifest'
expect(generation.entries.map(entry => entry.name))
expect(resolution.entries.map(entry => entry.name))
.toEqual(['dsh-app', 'before', ...unavailable ? [] : ['broken'], 'after'])
mkdirSync(bundleDir, { recursive: true })
writeFileSync(manifestPath, original)
@@ -419,7 +419,7 @@ describe('composeEntries', () => {
})
})
describe('createProfileResolutionGeneration', () => {
describe('createRuntimeResolution', () => {
it('collects the app and bundle dependencies without writing profile packages', async () => {
const anchor = stageInstallation({
'bundle-a': { patch: '[]\n', deps: { 'dep-of-a': '0.0.0', 'ghost-dep': '0.0.0' } },
@@ -434,11 +434,11 @@ describe('createProfileResolutionGeneration', () => {
mkdirSync(join(modules, 'dep-of-a'), { recursive: true })
writeFileSync(join(modules, 'dep-of-a', 'package.json'), JSON.stringify({ name: 'dep-of-a', version: '0.0.0' }))
const home = tmp()
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, home })
expect(generation.entries.map(entry => entry.name)).toEqual(['dsh-app', 'bundle-a', 'plain-lib', 'dep-of-a'])
expect(generation.entries.find(entry => entry.name === 'dep-of-a')?.packageDir).toBe(join(modules, 'dep-of-a'))
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
expect(resolution.entries.map(entry => entry.name)).toEqual(['dsh-app', 'bundle-a', 'plain-lib', 'dep-of-a'])
expect(resolution.entries.find(entry => entry.name === 'dep-of-a')?.packageDir).toBe(join(modules, 'dep-of-a'))
expect(existsSync(join(home, 'profiles', 'node_modules'))).toBe(false)
await expect(createProfileResolutionGeneration({ installAnchor: anchor, home })).resolves.toEqual(generation)
await expect(createRuntimeResolution({ installAnchor: anchor, home })).resolves.toEqual(resolution)
})
it('keeps selected bundle closures profile-local without overriding installation packages', async () => {
@@ -448,33 +448,33 @@ describe('createProfileResolutionGeneration', () => {
const home = tmp()
const profileA = stageProfile(home, 'a', bundleA)
const profileB = stageProfile(home, 'b', bundleB)
const generationA = await createProfileResolutionGeneration({ installAnchor: installationAnchor, profile: profileA, home })
await expect(createProfileResolutionGeneration({ installAnchor: installationAnchor, profile: profileA, home }))
.resolves.toEqual(generationA)
const generationB = await createProfileResolutionGeneration({ installAnchor: installationAnchor, profile: profileB, home })
const resolutionA = await createRuntimeResolution({ installAnchor: installationAnchor, profile: profileA, home })
await expect(createRuntimeResolution({ installAnchor: installationAnchor, profile: profileA, home }))
.resolves.toEqual(resolutionA)
const resolutionB = await createRuntimeResolution({ installAnchor: installationAnchor, profile: profileB, home })
for (const generation of [generationA, generationB]) {
expect(generation.entries.find(entry => entry.name === 'shared')).toMatchObject({
for (const resolution of [resolutionA, resolutionB]) {
expect(resolution.entries.find(entry => entry.name === 'shared')).toMatchObject({
packageDir: join(installationAnchor, '..', 'node_modules', 'shared'), scope: 'installation',
})
}
expect(existsSync(join(home, 'profiles', 'node_modules'))).toBe(false)
expect(existsSync(join(profileA.dir, 'node_modules', 'shared'))).toBe(false)
expect(existsSync(join(profileB.dir, 'node_modules', 'shared'))).toBe(false)
expect(generationA.entries.find(entry => entry.name === '@scope/bundle-only')).toMatchObject({
expect(resolutionA.entries.find(entry => entry.name === '@scope/bundle-only')).toMatchObject({
packageDir: realpathSync.native(join(bundleA, '..', 'node_modules', '@scope', 'bundle-only')), scope: 'profile',
})
expect(generationB.entries.find(entry => entry.name === '@scope/bundle-only')).toMatchObject({
expect(resolutionB.entries.find(entry => entry.name === '@scope/bundle-only')).toMatchObject({
packageDir: realpathSync.native(join(bundleB, '..', 'node_modules', '@scope', 'bundle-only')), scope: 'profile',
})
const withoutBundles = await createProfileResolutionGeneration({
const withoutBundles = await createRuntimeResolution({
installAnchor: installationAnchor,
profile: { ...profileA, layers: [] },
home,
})
expect(withoutBundles.entries.some(entry => entry.name === '@scope/bundle-only')).toBe(false)
expect(generationB.entries.find(entry => entry.name === '@scope/bundle-only')?.packageDir)
expect(resolutionB.entries.find(entry => entry.name === '@scope/bundle-only')?.packageDir)
.toBe(realpathSync.native(join(bundleB, '..', 'node_modules', '@scope', 'bundle-only')))
})
@@ -512,8 +512,8 @@ describe('createProfileResolutionGeneration', () => {
const previous = Object.getOwnPropertyDescriptor(process, 'pkg')
Object.defineProperty(process, 'pkg', { configurable: true, value: packaged ? {} : undefined })
try {
const generation = await createProfileResolutionGeneration({ installAnchor: installationAnchor, profile, home })
expect(generation.entries.find(entry => entry.name === 'bundle-only')?.packageDir)
const resolution = await createRuntimeResolution({ installAnchor: installationAnchor, profile, home })
expect(resolution.entries.find(entry => entry.name === 'bundle-only')?.packageDir)
.toBe(realpathSync.native(realDependency))
} finally {
if (previous === undefined) Reflect.deleteProperty(process, 'pkg')
@@ -559,10 +559,10 @@ describe('createProfileResolutionGeneration', () => {
patches: [],
}
const generation = await createProfileResolutionGeneration({ installAnchor: installationAnchor, profile, home })
const resolution = await createRuntimeResolution({ installAnchor: installationAnchor, profile, home })
expect(generation.entries.find(entry => entry.name === 'nested-only')?.packageDir).toBe(realpathSync.native(nestedOnly))
expect(generation.entries.find(entry => entry.name === 'explicit-only')?.packageDir).toBe(realpathSync.native(explicitOnly))
expect(resolution.entries.find(entry => entry.name === 'nested-only')?.packageDir).toBe(realpathSync.native(nestedOnly))
expect(resolution.entries.find(entry => entry.name === 'explicit-only')?.packageDir).toBe(realpathSync.native(explicitOnly))
})
it('resolves import-only exports from each package installation', async () => {
@@ -583,9 +583,9 @@ describe('createProfileResolutionGeneration', () => {
}))
writeFileSync(join(nestedDir, 'index.js'), 'export const nested = "selected"\n')
const home = tmp()
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, home })
await expect(importFromGeneration(generation, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
await expect(importFromGeneration(generation, 'nested-esm')).resolves.toMatchObject({ nested: 'selected' })
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
await expect(importFromGeneration(resolution, 'nested-esm')).resolves.toMatchObject({ nested: 'selected' })
})
it('resolves conditional subpath exports through Node', async () => {
@@ -603,9 +603,9 @@ describe('createProfileResolutionGeneration', () => {
mkdirSync(join(bundleDir, 'dist', 'web'), { recursive: true })
writeFileSync(join(bundleDir, 'dist', 'web', 'index.mjs'), 'export const web = true\n')
const home = tmp()
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, home })
await expect(importFromGeneration(generation, 'bundle-a/mini')).resolves.toMatchObject({ mini: true })
await expect(importFromGeneration(generation, 'bundle-a/web')).resolves.toMatchObject({ web: true })
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
await expect(importFromGeneration(resolution, 'bundle-a/mini')).resolves.toMatchObject({ mini: true })
await expect(importFromGeneration(resolution, 'bundle-a/web')).resolves.toMatchObject({ web: true })
})
it('uses the legacy index fallback when a package has no exports or main', async () => {
@@ -615,8 +615,8 @@ describe('createProfileResolutionGeneration', () => {
delete manifest.main
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
const home = tmp()
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, home })
await expect(importFromGeneration(generation, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
})
it('uses Node legacy resolution for an extensionless main entry', async () => {
@@ -626,8 +626,8 @@ describe('createProfileResolutionGeneration', () => {
manifest.main = './index'
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
const home = tmp()
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, home })
await expect(importFromGeneration(generation, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
})
it('fails loud on a missing legacy main entry', async () => {
@@ -637,8 +637,8 @@ describe('createProfileResolutionGeneration', () => {
delete manifest.main
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
rmSync(join(bundleDir, 'index.js'))
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, home: tmp() })
await expect(importFromGeneration(generation, 'bundle-a')).rejects.toMatchObject({ code: 'ERR_MODULE_NOT_FOUND' })
const resolution = await createRuntimeResolution({ installAnchor: anchor, home: tmp() })
await expect(importFromGeneration(resolution, 'bundle-a')).rejects.toMatchObject({ code: 'ERR_MODULE_NOT_FOUND' })
})
it('preserves native ESM export errors and null-map legacy resolution', async () => {
@@ -656,16 +656,16 @@ describe('createProfileResolutionGeneration', () => {
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
if (mode === 'directory') mkdirSync(join(bundleDir, 'mini'))
const home = tmp()
const generation = await createProfileResolutionGeneration({ installAnchor: anchor, home })
const resolution = await createRuntimeResolution({ installAnchor: anchor, home })
if (mode === 'absent-map') {
await expect(importFromGeneration(generation, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
await expect(importFromGeneration(resolution, 'bundle-a')).resolves.toMatchObject({ packageName: 'bundle-a' })
} else {
const specifier = mode === 'null-subpath' ? 'bundle-a/bad' : 'bundle-a'
const code = mode === 'missing' ? 'ERR_MODULE_NOT_FOUND'
: mode === 'directory' ? 'ERR_UNSUPPORTED_DIR_IMPORT'
: mode === 'null' || mode === 'null-subpath' ? 'ERR_PACKAGE_PATH_NOT_EXPORTED'
: 'ERR_INVALID_PACKAGE_TARGET'
await expect(importFromGeneration(generation, specifier)).rejects.toMatchObject({ code })
await expect(importFromGeneration(resolution, specifier)).rejects.toMatchObject({ code })
}
}
})
@@ -9,7 +9,7 @@ import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import {
boot,
createProfileResolutionGeneration,
createRuntimeResolution,
loadOverlayPatches,
loadProfile,
PluginPackages,
@@ -86,7 +86,7 @@ export async function bootProductionProfile(options: ProductionProfileOptions):
const moduleLayers = options.overlayPaths.flatMap((path, index) => (
overlayModuleLayers(path, overlays[index] ?? [])
))
const generation = await createProfileResolutionGeneration({
const resolution = await createRuntimeResolution({
installAnchor,
profile: { ...profile, layers: [...profile.layers, ...moduleLayers] },
})
@@ -98,7 +98,7 @@ export async function bootProductionProfile(options: ProductionProfileOptions):
...overlays.flat(),
],
async (ctx) => {
await ctx.plugin(PluginPackages, { generation })
await ctx.plugin(PluginPackages, { resolution })
await options.prepare?.(ctx)
},
)