fix(plugin): preserve bundled resource identity in executables

This commit is contained in:
Tianyi Cui committed 2026-10-06 23:48:59 +08:00
1 parent 1f8debb552
commit d68ae3bf51
17 files changed
+195 -22

No files matched your search

+1 -1
View File
@@ -27,7 +27,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:618`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:597`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:54`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |
+1 -1
View File
@@ -29,7 +29,7 @@
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:618`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:597`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:604`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:54`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:55`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | - |
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |
+2 -2
View File
@@ -33,8 +33,8 @@
en: c4a301652b851ce9
zh: 784871e19cd9db0a
/deepseek-ai-dsh-app-boot/use-this-package/reading-plugin-display-metadata:
en: 8f122e6130ce30b2
zh: eef4a8b714cf0a9d
en: 3538aeac1a9f8c87
zh: 32c75392227be0e0
/deepseek-ai-dsh-app-boot/use-this-package/startup-and-reload-failures:
en: b2e5cfdf8847d214
zh: 8d65771a3bd8a7c9
+2
View File
@@ -98,6 +98,8 @@ The projector preserves native omission behavior by checking literal defaults ag
`resolvePluginResource(specifier, parentURL)` resolves a plugin module or exported resource to its local filesystem path through the active Node ESM resolver without evaluating it. It throws when the resolver is unavailable or the resource cannot resolve to a local file.
`realModuleFile(path)` canonicalizes an existing module resource in Node and packaged executables. It follows physical file symlinks and preserves archive-backed resources through their canonical containing directory. Missing or inaccessible resources throw.
Use `readPluginMeta(specifier, parentURL)` or `ctx.pluginPackages.metaOf(specifier, parentURL)` to read installed package display text without importing or activating the plugin. Lookup uses the complete package specifier and the caller's resolution base, respecting Node exports. File paths and file URLs return no metadata without resolving resources. For a package-root specifier, missing locale fields fall back to the accessible `package.json`; a subpath specifier never reads a `package.json`. Malformed metadata returns an `error` diagnostic. Results retain translations for Client-side language selection. The reader also loads an image data URL, even when locale text is complete: a package root uses its manifest `icon`, or `<package>/icon` when that field is omitted; a subpath uses `<specifier>/icon`. An icon error preserves valid text alongside the diagnostic. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format.
<a id="startup-and-reload-failures"></a>
+2
View File
@@ -98,6 +98,8 @@ profile patch 可以将 `preset` 设为外层条目 id,把其余普通 patch
`resolvePluginResource(specifier, parentURL)` 通过活动的 Node ESM 解析器将插件模块或导出资源解析为本地文件路径,不执行其代码。解析器不可用或资源无法解析为本地文件时会抛出错误。
`realModuleFile(path)` 在 Node 和打包可执行程序中规范化已有模块资源的路径。它跟随物理文件符号链接,并通过规范化后的所在目录保留归档资源。资源不存在或无法访问时抛出错误。
使用 `readPluginMeta(specifier, parentURL)` 或 `ctx.pluginPackages.metaOf(specifier, parentURL)` 读取已安装包的展示文本,无需导入或激活插件。查询使用完整包标识与调用方的解析基准,并遵循 Node exports。文件路径与文件 URL 不解析资源,直接返回无元信息。包根标识缺失的 locale 字段回退到可访问的 `package.json`;子路径标识从不读取 `package.json`。格式错误的元信息返回 `error` 诊断。结果保留翻译,由 Client 选择语言。即使 locale 文本完整,读取器也会加载图片 data URL:包根使用清单 `icon`,省略该字段时使用 `<包名>/icon`;子路径使用 `<标识>/icon`。图标出错时,保留有效文本并附上诊断。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。
<a id="startup-and-reload-failures"></a>
+1
View File
@@ -29,6 +29,7 @@ import { applyProfilePatches, compileProfilePatches, profilePatchPreset, prepare
export { applyProfilePatches, compileProfilePatches, profilePatchPreset, type ProfilePatch, type ProfilePatchWarning } from './profile-patches.ts'
export { prepareProfileEntries, prepareProfilePatches } from './compatibility-preflight.ts'
export { readPluginMeta, resolvePluginResource } from './package-meta.ts'
export { realModuleFile } from './profile-resolution/legacy-links.ts'
export { generateConfigSchema, type ConfigSchemaDump, type NativeConfigSchema } from './config-schema/index.ts'
export { createConfigProjector, LOADER_EXPRESSION_SCHEMA, type ConfigProjection } from './config-schema/projector.ts'
export { isNativeConfigSchema } from './config-schema/native.ts'
+4 -3
View File
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url'
import { ModuleLoader } from '@deepseek-ai/cordis-plugin-loader'
import type { LocalizedText, PluginLocalizedMeta } from '@deepseek-ai/dsh-package-manifest'
import { barePackageName } from './profile-resolution/resolver.ts'
import { realModuleFile } from './profile-resolution/legacy-links.ts'
const LANGUAGE_ID = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/u
@@ -32,7 +33,7 @@ function iconOf(
label = manifestPath
const root = realpathSync(dirname(manifestPath))
target = resolve(root, icon)
const local = relative(root, realpathSync(target))
const local = relative(root, realModuleFile(target))
if (local === '..' || local.startsWith(`..${sep}`) || isAbsolute(local)) {
throw new Error(`${label}: icon must remain inside its manifest directory`)
}
@@ -40,11 +41,11 @@ function iconOf(
const exported = optionalResourcePath(`${specifier}/icon`, parentURL)
if (exported === undefined) return undefined
target = label = exported
assertPackageOwned(realpathSync(target), packageName)
assertPackageOwned(realModuleFile(target), packageName)
}
const mediaType = ICON_MEDIA_TYPES.get(extname(target).toLowerCase())
if (mediaType === undefined) throw new Error(`${label}: icon must be SVG, PNG, JPEG, or WebP`)
const file = realpathSync(target)
const file = realModuleFile(target)
const stat = statSync(file)
if (!stat.isFile()) throw new Error(`${label}: icon must be a regular file`)
if (stat.size > MAX_ICON_BYTES) throw new Error(`${label}: icon exceeds 256 KiB`)
@@ -1,9 +1,9 @@
/** Compile preset-scoped profile operations to the native Include patch language. */
import { createHash } from 'node:crypto'
import { realpathSync } from 'node:fs'
import { applyEntryPatches, type PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
import { realModuleFile } from './profile-resolution/legacy-links.ts'
/** A native entry patch, optionally applied inside one preset row's `config.plugins`. */
export interface ProfilePatch extends PatchOptions {
@@ -44,7 +44,7 @@ export function cloneProfilePatches(patches: readonly ProfilePatch[]): ProfilePa
* @throws When a preset target is malformed, naming its declaring file and operation.
*/
export function prepareLoadedProfilePatches(patches: ProfilePatch[], file: string, binName: string): ProfilePatch[] {
const canonical = realpathSync(file)
const canonical = realModuleFile(file)
patches.forEach((patch, operation) => {
patchSources.set(patch, { binName, file, operation })
try {
@@ -1,6 +1,7 @@
/** Package directory canonicalization through the active runtime carrier's filesystem. */
/** Package directory and resource canonicalization through the active runtime carrier's filesystem. */
import { realpathSync } from 'node:fs'
import { lstatSync, realpathSync } from 'node:fs'
import { basename, dirname, join } from 'node:path'
/**
* Return whether the process reads application modules from pkg's virtual filesystem.
@@ -18,3 +19,15 @@ function isPackagedExecutable(): boolean {
export function realModuleDirectory(path: string): string {
return isPackagedExecutable() ? realpathSync(path) : realpathSync.native(path)
}
/** Canonicalize a module resource, preserving physical file aliases.
* SEA archive files are symlink-free and support stat and reads, while their containing directories support realpath.
* @param path Existing module resource path.
* @returns Canonical resource path under the active carrier's filesystem.
* @throws When the resource or its directory cannot be inspected or resolved.
*/
export function realModuleFile(path: string): string {
return !isPackagedExecutable() || lstatSync(path).isSymbolicLink()
? realpathSync(path)
: join(realModuleDirectory(dirname(path)), basename(path))
}
@@ -244,6 +244,46 @@ describe('plugin locale display metadata', () => {
})
})
it.each(['manifest', 'export'])('reads a package-owned %s icon when the archive cannot realpath regular files', (source) => {
manifest({ './package.json': './package.json', './icon': './icon.svg' }, source === 'manifest' ? { icon: './icon.svg' } : {})
const icon = join(dir, 'icon.svg')
file(icon, '<svg/>')
// The executable resolver supplies module URLs independently of archive file realpath support.
resolvePluginResource('localized/icon', parentURL)
const original = fs.realpathSync
const previousPkg = Object.getOwnPropertyDescriptor(process, 'pkg')
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
fs.realpathSync = new Proxy(original, {
apply(target, receiver: unknown, args: unknown[]): unknown {
if (String(args[0]).endsWith(join('localized', 'icon.svg'))) {
throw Object.assign(new Error('archive-backed icon has no realpath entry'), { code: 'ENOENT' })
}
return Reflect.apply(target, receiver, args)
},
})
syncBuiltinESMExports()
try {
expect(() => fs.realpathSync(icon)).toThrow('archive-backed')
expect(readPluginMeta('localized', parentURL)).toEqual({ title: 'localized', icon: 'data:image/svg+xml;base64,PHN2Zy8+' })
} finally {
fs.realpathSync = original
if (previousPkg === undefined) Reflect.deleteProperty(process, 'pkg')
else Object.defineProperty(process, 'pkg', previousPkg)
syncBuiltinESMExports()
}
})
it.each(['manifest', 'export'])('refuses a %s icon file symlink owned by another package', (source) => {
manifest({ './package.json': './package.json', './icon': './icon.svg' }, source === 'manifest' ? { icon: './icon.svg' } : {})
const foreign = join(root, 'foreign-package', 'icon.svg')
file(foreign, '<svg/>')
file(join(root, 'foreign-package', 'package.json'), '{"name":"foreign-package"}')
symlinkSync(foreign, join(dir, 'icon.svg'), 'file')
const meta = readPluginMeta('localized', parentURL)
expect(meta?.icon).toBeUndefined()
expect(meta?.error).toContain(source === 'manifest' ? 'icon must remain inside its manifest directory' : 'icon must remain inside its package directory')
})
it.each([
['svg', 'image/svg+xml'], ['png', 'image/png'], ['jpg', 'image/jpeg'],
['jpeg', 'image/jpeg'], ['webp', 'image/webp'], ['SVG', 'image/svg+xml'],
@@ -0,0 +1,78 @@
/** Patch identities work with archive-backed files and preserve physical filesystem aliases. */
import { lstatSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, expect, it, onTestFinished, vi } from 'vitest'
import { loadOverlayPatches, realModuleFile } from '../src/index.ts'
vi.mock('node:fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs')>()
return { ...actual, realpathSync: Object.assign(vi.fn(actual.realpathSync), { native: actual.realpathSync.native }) }
})
afterEach(() => { vi.mocked(realpathSync).mockReset() })
function packagedRuntime(): void {
const previous = Object.getOwnPropertyDescriptor(process, 'pkg')
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
onTestFinished(() => {
if (previous === undefined) Reflect.deleteProperty(process, 'pkg')
else Object.defineProperty(process, 'pkg', previous)
})
}
function fixture(): string {
const root = mkdtempSync(join(tmpdir(), 'profile-patch-carrier-'))
onTestFinished(() => { rmSync(root, { recursive: true, force: true }) })
return root
}
it('loads ordinary and anonymous scoped patches when packaged files support reads and directory realpath', () => {
packagedRuntime()
const root = fixture()
const file = join(root, 'cordis.patch.yml')
writeFileSync(file, JSON.stringify([
{ insert: [{ id: 'base', name: 'base' }] },
{ preset: 'preset-standard', insert: [{ name: 'anonymous' }] },
]))
vi.mocked(realpathSync).mockImplementation((path, options) => {
if (lstatSync(path).isFile()) throw Object.assign(new Error('archive file has no physical realpath'), { code: 'ENOENT' })
return realpathSync.native(path, options)
})
expect(() => realpathSync(file)).toThrow('archive file has no physical realpath')
const first = loadOverlayPatches('test', file)
expect(first[0]).toEqual({ insert: [{ id: 'base', name: 'base' }] })
expect(first[1]?.insert?.[0]).toEqual({ id: expect.any(String) as string, name: 'anonymous' })
expect(loadOverlayPatches('test', file)).toEqual(first)
expect(realModuleFile(file)).toBe(join(realpathSync(root), 'cordis.patch.yml'))
})
it.each([false, true])('keeps directory and file symlinks on the same patch identity (packaged: %s)', (packaged) => {
if (packaged) packagedRuntime()
const root = fixture()
const directory = join(root, 'bundle')
mkdirSync(directory)
const file = join(directory, 'Patch.yml')
writeFileSync(file, JSON.stringify([{ preset: 'preset-standard', insert: [{ name: 'anonymous' }] }]))
const directoryAlias = join(root, 'bundle-alias')
const fileAlias = join(root, 'patch-alias.yml')
symlinkSync(directory, directoryAlias, process.platform === 'win32' ? 'junction' : 'dir')
symlinkSync(file, fileAlias, 'file')
const first = loadOverlayPatches('test', file)
expect(loadOverlayPatches('test', join(directoryAlias, 'Patch.yml'))).toEqual(first)
expect(loadOverlayPatches('test', fileAlias)).toEqual(first)
expect(realModuleFile(fileAlias)).toBe(realpathSync(file))
})
it('keeps ordinary physical resource normalization identical to Node realpath', () => {
const root = fixture()
const file = join(root, 'MixedCase.yml')
writeFileSync(file, '[]\n')
const requested = process.platform === 'win32' ? file.toLowerCase() : file
expect(realModuleFile(requested)).toBe(realpathSync(requested))
})
it('reports missing packaged resources instead of assigning them an identity', () => {
packagedRuntime()
expect(() => realModuleFile(join(fixture(), 'absent.yml'))).toThrow('ENOENT')
})
@@ -1,9 +1,9 @@
/** Raw bundle declarations remain inspectable while their prerequisite layers are disabled. */
import { existsSync, realpathSync } from 'node:fs'
import { existsSync } from 'node:fs'
import { dirname, isAbsolute, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
import { profilePatchPreset, type ProfilePatch, type PluginPackages } from '@deepseek-ai/dsh-app-boot'
import { realModuleFile, profilePatchPreset, type ProfilePatch, type PluginPackages } from '@deepseek-ai/dsh-app-boot'
/** A declared child row and the outer preset operation that contributes it. */
export interface BundleDeclaration {
@@ -39,14 +39,14 @@ export function bundleModuleOwner(
specifier: string, base: string, packages?: Pick<PluginPackages, 'packageOf'>,
): string | undefined {
const known = packages?.packageOf(specifier, base)
if (known !== undefined) return realpathSync(known.manifestPath)
if (known !== undefined) return realModuleFile(known.manifestPath)
if (!specifier.startsWith('file:') && !specifier.startsWith('.') && !isAbsolute(specifier)) return undefined
const file = isAbsolute(specifier) ? specifier : fileURLToPath(new URL(specifier, base))
if (!existsSync(file)) return undefined
let dir = dirname(realpathSync(file))
let dir = dirname(realModuleFile(file))
while (true) {
const manifest = join(dir, 'package.json')
if (existsSync(manifest)) return realpathSync(manifest)
if (existsSync(manifest)) return realModuleFile(manifest)
const parent = dirname(dir)
if (parent === dir) return undefined
dir = parent
+4 -4
View File
@@ -1,6 +1,6 @@
/** Current-profile plugin and bundle management over shared dsh plugin operations. */
import { randomUUID } from 'node:crypto'
import { existsSync, readFileSync, realpathSync } from 'node:fs'
import { existsSync, readFileSync } from 'node:fs'
import { readFile, rm } from 'node:fs/promises'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
@@ -13,7 +13,7 @@ import z from '@deepseek-ai/schemastery'
import { TypertRemoteService, Remote } from '@deepseek-ai/dsh-typert-protocol'
import { pluginEntryId, readPluginInventory } from '@deepseek-ai/dsh-host-plugin-inventory'
import {
readPluginMeta, resolvePluginResource, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries,
readPluginMeta, resolvePluginResource, realModuleFile, readProfileManifest, resolveBundleDir, loadOverlayPatches, composeEntries,
reconcileProfilePatches, readProfilePatches, OPTIONAL_BUNDLES, bundlePatchPaths,
evaluatePluginCompatibility, readProfileCompatibility, readProfileVersionExemptions,
setProfileVersionExemption, PROFILE_COMPATIBILITY_FILENAME, profilePatchPreset, type ProfilePatch,
@@ -690,10 +690,10 @@ export class PluginManager extends TypertRemoteService {
if (presets === undefined || declarations.length === 0) return false
const manifest = join(resolveBundleDir('dsh', name, this.profile.installAnchor, this.profile.dir), 'package.json')
const base = pathToFileURL(manifest).href
const owner = realpathSync(manifest)
const owner = realModuleFile(manifest)
const moduleKey = (specifier: string, parentURL: string): string | undefined => {
if (specifier.startsWith('cordis:')) return specifier
try { return realpathSync(resolvePluginResource(specifier, parentURL)) }
try { return realModuleFile(resolvePluginResource(specifier, parentURL)) }
catch (error) {
// An externally removed module cannot match another resolved file; identical declarations remain protected below.
if (['ENOENT', 'ENOTDIR', 'ERR_MODULE_NOT_FOUND', 'ERR_PACKAGE_PATH_NOT_EXPORTED'].includes(String((error as NodeJS.ErrnoException).code))) return undefined
@@ -48,4 +48,13 @@ it('finds canonical owners for bare, relative, absolute, and file URL modules wi
const linked = join(dir, 'linked')
symlinkSync(packageDir, linked, process.platform === 'win32' ? 'junction' : 'dir')
expect(bundleModuleOwner(pathToFileURL(join(linked, 'lib/index.mjs')).href, base)).toBe(expected)
const otherPackage = join(dir, 'other-package')
mkdirSync(otherPackage)
writeFileSync(join(otherPackage, 'package.json'), '{"name":"other-package"}\n')
const otherModule = join(otherPackage, 'foreign.mjs')
writeFileSync(otherModule, 'export function apply() {}\n')
const fileLink = join(packageDir, 'borrowed.mjs')
symlinkSync(otherModule, fileLink, 'file')
expect(bundleModuleOwner(fileLink, base)).toBe(realpathSync(join(otherPackage, 'package.json')))
})
@@ -1,5 +1,6 @@
/** Scoped bundles use real preset generations, profile reloads, and package-removal admission. */
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import fs, { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { syncBuiltinESMExports } from 'node:module'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
@@ -244,7 +245,8 @@ it('refuses Remove after Off while an old Agent scope retains the bundle, then r
expect(remove).toHaveBeenCalledOnce()
})
it.each([false, true])('removes a released contribution while base presets keep using the same shared module (anonymous=%s)', async (anonymous) => {
it.each([false, true].flatMap(anonymous => [false, true].map(archive => ({ anonymous, archive }))))
('removes a released contribution while base presets keep using the same shared module (anonymous=$anonymous, archive=$archive)', async ({ anonymous, archive }) => {
const { manager, ctx, dir, addon } = await fixture()
const shared = join(dir, 'node_modules', 'shared-fixture')
mkdirSync(shared)
@@ -276,6 +278,28 @@ it.each([false, true])('removes a released contribution while base presets keep
{ entryId: anonymous ? expect.any(String) as string : 'addon-shared', moduleName: 'shared-fixture' },
],
}])
if (archive) {
const original = fs.realpathSync
const previousPkg = Object.getOwnPropertyDescriptor(process, 'pkg')
onTestFinished(() => {
fs.realpathSync = original
if (previousPkg === undefined) Reflect.deleteProperty(process, 'pkg')
else Object.defineProperty(process, 'pkg', previousPkg)
syncBuiltinESMExports()
})
Object.defineProperty(process, 'pkg', { configurable: true, value: {} })
fs.realpathSync = new Proxy(original, {
apply(target, receiver: unknown, args: unknown[]): unknown {
if (['package.json', 'index.mjs'].some(file => String(args[0]).endsWith(join('shared-fixture', file)))) {
throw Object.assign(new Error('archive-backed files have no realpath entry'), { code: 'ENOENT' })
}
return Reflect.apply(target, receiver, args)
},
})
syncBuiltinESMExports()
expect(() => fs.realpathSync(join(shared, 'package.json'))).toThrow('archive-backed')
expect(readFileSync(join(shared, 'index.mjs'), 'utf8')).toContain('export function apply')
}
const remove = vi.spyOn(operations, 'runProfilePnpm').mockImplementation(async () => {
const manifest = readProfileManifest('test', dir)
delete manifest.dependencies?.addon
@@ -22,6 +22,7 @@ describe('Python runtime executable assets', () => {
expect(result.status).toBe(0)
expect(result.stdout).toContain('node_modules/@deepseek-ai/dsh-web-frontend/dist/**/*')
expect(result.stdout).toContain('node_modules/@deepseek-ai/dsh-*/**/*.{svg,png,jpg,jpeg,webp}')
expect(result.stdout).not.toContain('skill-badge/assets/**/*')
expect(result.stdout).toContain('node_modules/@deepseek-ai/dsh-sandbox-windows-acl/assets/**/*')
expect(result.stdout).not.toContain('node_modules/**/*.py')
+2
View File
@@ -48,6 +48,8 @@ const ASSET_GLOBS = [
'node_modules/**/*.mjs',
'node_modules/**/package.json',
'node_modules/**/*.json',
// Plugin display metadata resolves these package-owned images at runtime.
'node_modules/@deepseek-ai/dsh-*/**/*.{svg,png,jpg,jpeg,webp}',
// Package-owned Markdown includes runtime skill instructions.
'node_modules/**/*.md',
'node_modules/**/*.dylib',