mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
fix(app-boot): preserve profile modules inside pkg executables
Teach the profile installation fallback to use normal symlinks under Node and real ESM proxy packages under pkg. Each proxy records the source package version, mirrors its explicit runtime subpath exports, and re-exports the virtual /snapshot URLs, so built-in Loader rows and external plugin peers resolve one shared Cordis/module instance from an on-disk profile. Keep proxy healing idempotent, reject foreign real directories, cover root and subpath imports in packaged mode, and expand AggregateError startup diagnostics so concurrent Loader failures retain their individual import causes. This is the reusable packaged-profile mechanism; Python-specific artifact wiring remains in the next commit.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Shared boot glue for `dsh` profiles and the temporarily packaged Python SDK runtime: load the gitignored
|
||||
* Shared boot glue for `dsh` profiles, including the CLI packaged by the Python runtime wheel: load the gitignored
|
||||
* `.env`, install the fail-loud Loader guards, resolve the config path (snapshot-aware), load the
|
||||
* optional user patch layers from the Harness home (`~/.dsh`), expose its path resolver to
|
||||
* config expressions, and drive the Cordis Loader against a leaf `cordis.yml` until the tree settles.
|
||||
@@ -811,7 +811,9 @@ export async function boot(
|
||||
// original activation error instead of only the wrap chain.
|
||||
let deepest: unknown = cause
|
||||
while (deepest instanceof Error && deepest.cause !== undefined) deepest = deepest.cause
|
||||
const stack = deepest instanceof Error && deepest !== cause ? `\n${deepest.stack ?? deepest.message}` : ''
|
||||
const stack = deepest instanceof AggregateError
|
||||
? `\n${deepest.stack ?? deepest.message}\n${deepest.errors.map(formatActivationError).join('\n')}`
|
||||
: deepest instanceof Error && deepest !== cause ? `\n${deepest.stack ?? deepest.message}` : ''
|
||||
throw new Error(`${binName}: ${stage}: ${detail}${stack}`, { cause })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
|
||||
import { createRequire } from 'node:module'
|
||||
import {
|
||||
existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, symlinkSync, unlinkSync, writeFileSync,
|
||||
existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, symlinkSync, unlinkSync, writeFileSync,
|
||||
} from 'node:fs'
|
||||
import { basename, dirname, join } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { applyEntryPatches, type PatchOptions } from '@deepseek-ai/cordis-plugin-include'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
|
||||
@@ -238,22 +239,140 @@ function ensureSymlink(link: string, target: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
interface ModuleProxyManifest {
|
||||
name: string
|
||||
version: string
|
||||
private: true
|
||||
type: 'module'
|
||||
exports: Record<string, string>
|
||||
dsh: { moduleFallback: { targets: Record<string, string> } }
|
||||
}
|
||||
|
||||
interface ModuleProxyRecord {
|
||||
version?: unknown
|
||||
dsh?: { moduleFallback?: { targets?: unknown } }
|
||||
}
|
||||
|
||||
/** Return whether the process reads application modules from pkg's virtual filesystem. */
|
||||
function isPackagedExecutable(): boolean {
|
||||
return (process as NodeJS.Process & { pkg?: unknown }).pkg !== undefined
|
||||
}
|
||||
|
||||
/** Resolve one package specifier from the dsh installation. */
|
||||
function packageEntryFromAnchor(anchor: string, specifier: string): string | undefined {
|
||||
try {
|
||||
return createRequire(anchor).resolve(specifier)
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve every explicit runtime export that an out-of-tree plugin can import. */
|
||||
function packageProxySource(
|
||||
installAnchor: string,
|
||||
packageName: string,
|
||||
packageDir: string,
|
||||
): { version: string; targets: Record<string, string> } {
|
||||
const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')) as {
|
||||
exports?: unknown
|
||||
version?: unknown
|
||||
}
|
||||
if (typeof manifest.version !== 'string' || manifest.version.length === 0) {
|
||||
throw new Error(`dsh: installed package ${packageName} must declare a non-empty version`)
|
||||
}
|
||||
const declared = manifest.exports
|
||||
const subpaths = declared !== null && typeof declared === 'object' && !Array.isArray(declared)
|
||||
&& Object.keys(declared).some(key => key.startsWith('.'))
|
||||
? Object.keys(declared).filter(key => key === '.' || (key.startsWith('./') && !key.includes('*') && key !== './package.json'))
|
||||
: ['.']
|
||||
const targets: Record<string, string> = {}
|
||||
for (const subpath of subpaths) {
|
||||
const specifier = subpath === '.' ? packageName : packageName + subpath.slice(1)
|
||||
const entry = packageEntryFromAnchor(installAnchor, specifier)
|
||||
if (entry !== undefined) targets[subpath] = pathToFileURL(entry).href
|
||||
}
|
||||
return { version: manifest.version, targets }
|
||||
}
|
||||
|
||||
/**
|
||||
* Materialize a real package proxy whose exports retain pkg's virtual module
|
||||
* URL. Files outside the executable cannot traverse a symlink into
|
||||
* `/snapshot`, while an ESM re-export can import that URL and preserves the
|
||||
* executable's single module instance for out-of-tree plugin peers.
|
||||
*/
|
||||
function ensureModuleProxy(
|
||||
link: string,
|
||||
packageName: string,
|
||||
version: string,
|
||||
targets: Record<string, string>,
|
||||
): void {
|
||||
const proxyExports = Object.fromEntries(
|
||||
Object.keys(targets).map((subpath, index) => [subpath, `./entry-${index}.js`]),
|
||||
)
|
||||
const manifest: ModuleProxyManifest = {
|
||||
name: packageName,
|
||||
version,
|
||||
private: true,
|
||||
type: 'module',
|
||||
exports: proxyExports,
|
||||
dsh: { moduleFallback: { targets } },
|
||||
}
|
||||
let stat
|
||||
try {
|
||||
stat = lstatSync(link)
|
||||
} catch {
|
||||
stat = undefined
|
||||
}
|
||||
if (stat?.isSymbolicLink()) {
|
||||
unlinkSync(link)
|
||||
stat = undefined
|
||||
}
|
||||
if (stat !== undefined) {
|
||||
const marker = join(link, 'package.json')
|
||||
let existing: ModuleProxyRecord | undefined
|
||||
try {
|
||||
existing = JSON.parse(readFileSync(marker, 'utf8')) as ModuleProxyRecord
|
||||
} catch {
|
||||
existing = undefined
|
||||
}
|
||||
if (existing?.dsh?.moduleFallback?.targets === undefined) {
|
||||
throw new Error(`dsh: ${link} exists and is not a dsh-managed module proxy; remove it so dsh can manage the installation fallback`)
|
||||
}
|
||||
if (existing.version === version
|
||||
&& JSON.stringify(existing.dsh.moduleFallback.targets) === JSON.stringify(targets)) return
|
||||
rmSync(link, { recursive: true })
|
||||
}
|
||||
mkdirSync(link, { recursive: true })
|
||||
writeFileSync(join(link, 'package.json'), JSON.stringify(manifest, undefined, 2) + '\n')
|
||||
for (const [index, target] of Object.values(targets).entries()) {
|
||||
const specifier = JSON.stringify(target)
|
||||
writeFileSync(
|
||||
join(link, `entry-${index}.js`),
|
||||
`export * from ${specifier}\nimport * as target from ${specifier}\nexport default target.default\n`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maintain the flat module fallback `$DSH_HOME/profiles/node_modules`: one
|
||||
* symlink per package in the dsh app's resolvable dependency CLOSURE (BFS
|
||||
* entry per package in the dsh app's resolvable dependency CLOSURE (BFS
|
||||
* over `dependencies` from the app manifest), each resolved from its own
|
||||
* real location. Node's parent-directory walk from any profile finds this
|
||||
* installation location. Plain Node uses symlinks. A pkg executable writes
|
||||
* small ESM proxy packages instead because the host filesystem cannot follow
|
||||
* a symlink into pkg's virtual `/snapshot` tree; the proxy re-exports the
|
||||
* virtual URL, preserving the executable's single module instance. Node's
|
||||
* parent-directory walk from any profile finds this
|
||||
* directory after the profile's own `node_modules`, so every in-box plugin
|
||||
* resolves without pnpm ever managing it — the exact "bundles come from the
|
||||
* installation" contract. The closure (not just direct dependencies) is
|
||||
* required for out-of-tree plugins: their peer dependencies name Service
|
||||
* Definition packages (`dsh-compaction`, `dsh-invariants`, ...) that the app
|
||||
* reaches only through its Service Provider packages. Symlinked packages
|
||||
* resolve their own dependencies from their real directories (Node's default
|
||||
* symlink-following), so each package needs only its one flat link.
|
||||
* Idempotent: correct links are kept and moved installations are
|
||||
* re-pointed; a stale link to a vanished package stays until its name is
|
||||
* reused (dangling links are invisible to resolution).
|
||||
* reaches only through its Service Provider packages. Both a symlink target
|
||||
* and a proxy's virtual target resolve transitive imports from the original
|
||||
* package directory, so each package needs one flat fallback entry.
|
||||
* Idempotent: correct entries are kept and changed installation targets are
|
||||
* rewritten; under plain Node, a stale dangling link stays until its name is
|
||||
* reused because resolution cannot discover it.
|
||||
* @param installAnchor - absolute path of the dsh app's package.json.
|
||||
* @param home - the Harness home; defaults to {@link resolveDshHome}.
|
||||
*/
|
||||
@@ -287,7 +406,14 @@ export function healProfilesModuleFallback(installAnchor: string, home: string =
|
||||
for (const [packageName, target] of links) {
|
||||
const link = join(modulesDir, packageName)
|
||||
mkdirSync(dirname(link), { recursive: true })
|
||||
ensureSymlink(link, target)
|
||||
if (isPackagedExecutable()) {
|
||||
const source = packageProxySource(installAnchor, packageName, target)
|
||||
if (Object.keys(source.targets).length > 0) {
|
||||
ensureModuleProxy(link, packageName, source.version, source.targets)
|
||||
}
|
||||
} else {
|
||||
ensureSymlink(link, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -771,6 +771,28 @@ describe('boot', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('expands a stackless aggregate at the deepest activation cause', async () => {
|
||||
const dir = tmp()
|
||||
const aggregate = new AggregateError([
|
||||
new Error('first aggregate member'),
|
||||
'second aggregate member',
|
||||
], 'aggregate activation failure')
|
||||
delete (aggregate as { stack?: string }).stack
|
||||
try {
|
||||
await boot(NAME, join(dir, 'cordis.yml'), undefined, () => {
|
||||
throw new Error('wrapped aggregate failure', { cause: aggregate })
|
||||
})
|
||||
expect.fail('boot should reject the aggregate activation failure')
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
const message = (error as Error).message
|
||||
expect(message).toContain(`${NAME}: host preparation failed: wrapped aggregate failure`)
|
||||
expect(message).toContain('aggregate activation failure')
|
||||
expect(message).toContain('first aggregate member')
|
||||
expect(message).toContain('second aggregate member')
|
||||
}
|
||||
})
|
||||
|
||||
it('reports a pending real Loader fiber and the service unresolved in its own context', async () => {
|
||||
const dir = tmp()
|
||||
writeFileSync(join(dir, 'waiting.mjs'), 'export const inject = ["neverProvided"]\nexport function apply() {}\n')
|
||||
|
||||
@@ -36,12 +36,18 @@ function stageInstallation(bundles: Record<string, { patch?: string; deps?: Reco
|
||||
writeFileSync(join(dir, 'package.json'), JSON.stringify({
|
||||
name,
|
||||
version: '0.0.0',
|
||||
type: 'module',
|
||||
main: './index.js',
|
||||
dependencies: spec.deps ?? {},
|
||||
...spec.patch === undefined ? {} : { dsh: { bundle: { patch: './cordis.patch.yml' } } },
|
||||
}))
|
||||
writeFileSync(join(dir, 'index.js'), `export const packageName = ${JSON.stringify(name)}\n`)
|
||||
if (spec.patch !== undefined) writeFileSync(join(dir, 'cordis.patch.yml'), spec.patch)
|
||||
}
|
||||
writeFileSync(join(appDir, 'package.json'), JSON.stringify({ name: 'dsh-app', dependencies: appDeps }))
|
||||
writeFileSync(join(appDir, 'package.json'), JSON.stringify({
|
||||
name: 'dsh-app', version: '0.0.0', type: 'module', main: './index.js', dependencies: appDeps,
|
||||
}))
|
||||
writeFileSync(join(appDir, 'index.js'), 'export const packageName = "dsh-app"\n')
|
||||
return join(appDir, 'package.json')
|
||||
}
|
||||
|
||||
@@ -322,4 +328,95 @@ describe('healProfilesModuleFallback', () => {
|
||||
const fallback = join(home, 'profiles', 'node_modules')
|
||||
expect(lstatSync(join(fallback, 'dsh-app')).isSymbolicLink()).toBe(true)
|
||||
})
|
||||
|
||||
it('writes real ESM proxies for a packaged executable', async () => {
|
||||
const anchor = stageInstallation({ 'bundle-a': { patch: '[]\n' } })
|
||||
const bundleDir = join(anchor, '..', 'node_modules', 'bundle-a')
|
||||
const bundleManifest = JSON.parse(readFileSync(join(bundleDir, 'package.json'), 'utf8')) as Record<string, unknown>
|
||||
bundleManifest.exports = { '.': './index.js', './feature': './feature.js' }
|
||||
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(bundleManifest))
|
||||
writeFileSync(join(bundleDir, 'feature.js'), 'export const feature = "proxied"\n')
|
||||
const home = tmp()
|
||||
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
|
||||
try {
|
||||
healProfilesModuleFallback(anchor, home)
|
||||
const fallback = join(home, 'profiles', 'node_modules')
|
||||
const proxy = join(fallback, 'bundle-a')
|
||||
expect(lstatSync(proxy).isDirectory()).toBe(true)
|
||||
const proxyManifest = JSON.parse(readFileSync(join(proxy, 'package.json'), 'utf8')) as {
|
||||
version: unknown
|
||||
exports: unknown
|
||||
dsh: { moduleFallback: { targets: Record<string, unknown> } }
|
||||
}
|
||||
expect(proxyManifest).toMatchObject({
|
||||
version: '0.0.0',
|
||||
exports: { '.': './entry-0.js', './feature': './entry-1.js' },
|
||||
})
|
||||
expect(proxyManifest.dsh.moduleFallback.targets['.']).toEqual(expect.stringContaining('/bundle-a/index.js'))
|
||||
await expect(import(join(proxy, 'entry-0.js'))).resolves.toMatchObject({ packageName: 'bundle-a' })
|
||||
await expect(import(join(proxy, 'entry-1.js'))).resolves.toMatchObject({ feature: 'proxied' })
|
||||
healProfilesModuleFallback(anchor, home)
|
||||
} finally {
|
||||
delete (process as NodeJS.Process & { pkg?: unknown }).pkg
|
||||
}
|
||||
})
|
||||
|
||||
it('requires a package version before writing a packaged proxy', () => {
|
||||
const anchor = stageInstallation({ 'bundle-a': { patch: '[]\n' } })
|
||||
const bundleDir = join(anchor, '..', 'node_modules', 'bundle-a')
|
||||
const manifest = JSON.parse(readFileSync(join(bundleDir, 'package.json'), 'utf8')) as Record<string, unknown>
|
||||
manifest.version = ''
|
||||
writeFileSync(join(bundleDir, 'package.json'), JSON.stringify(manifest))
|
||||
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
|
||||
try {
|
||||
expect(() => { healProfilesModuleFallback(anchor, tmp()) }).toThrow(
|
||||
'installed package bundle-a must declare a non-empty version',
|
||||
)
|
||||
} finally {
|
||||
delete (process as NodeJS.Process & { pkg?: unknown }).pkg
|
||||
}
|
||||
})
|
||||
|
||||
it('replaces plain-node links and stale managed proxies in packaged mode', () => {
|
||||
const anchor = stageInstallation({ 'bundle-a': { patch: '[]\n' } })
|
||||
const home = tmp()
|
||||
healProfilesModuleFallback(anchor, home)
|
||||
const proxy = join(home, 'profiles', 'node_modules', 'bundle-a')
|
||||
expect(lstatSync(proxy).isSymbolicLink()).toBe(true)
|
||||
|
||||
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
|
||||
try {
|
||||
healProfilesModuleFallback(anchor, home)
|
||||
expect(lstatSync(proxy).isDirectory()).toBe(true)
|
||||
const stale = JSON.parse(readFileSync(join(proxy, 'package.json'), 'utf8')) as {
|
||||
version: string
|
||||
}
|
||||
stale.version = 'stale'
|
||||
writeFileSync(join(proxy, 'package.json'), JSON.stringify(stale))
|
||||
healProfilesModuleFallback(anchor, home)
|
||||
expect(JSON.parse(readFileSync(join(proxy, 'package.json'), 'utf8'))).toMatchObject({
|
||||
version: '0.0.0',
|
||||
})
|
||||
} finally {
|
||||
delete (process as NodeJS.Process & { pkg?: unknown }).pkg
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects foreign packaged fallback directories with valid or invalid metadata', () => {
|
||||
const anchor = stageInstallation({ 'bundle-a': { patch: '[]\n' } })
|
||||
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
|
||||
try {
|
||||
for (const metadata of ['{}', '{']) {
|
||||
const home = tmp()
|
||||
const proxy = join(home, 'profiles', 'node_modules', 'bundle-a')
|
||||
mkdirSync(proxy, { recursive: true })
|
||||
writeFileSync(join(proxy, 'package.json'), metadata)
|
||||
expect(() => { healProfilesModuleFallback(anchor, home) }).toThrow(
|
||||
'exists and is not a dsh-managed module proxy',
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
delete (process as NodeJS.Process & { pkg?: unknown }).pkg
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user