mirror of
https://gh-proxy.org/https://github.com/klinsmaya/workdsh.git
synced 2026-11-10 10:27:55 +08:00
Revert "build: consolidate desktop runtime into asar"
This reverts commit 97e057d4d4.
This commit is contained in:
@@ -285,20 +285,13 @@
|
||||
"build": {
|
||||
"appId": "ai.deepseek.dsh.desktop",
|
||||
"productName": "DSH Desktop",
|
||||
"asar": {
|
||||
"smartUnpack": false
|
||||
},
|
||||
"asar": true,
|
||||
"asarUnpack": [
|
||||
"lib/diagnostic-export-worker.js",
|
||||
"node_modules/@deepseek-ai/dsh/config/agent-presets/**",
|
||||
"node_modules/@deepseek-ai/node-addon-landlock-run-linux-*/**",
|
||||
"node_modules/@img/sharp-*/**",
|
||||
"node_modules/@img/sharp-libvips-*/**",
|
||||
"node_modules/@koromix/koffi-*/**",
|
||||
"node_modules/@vscode/ripgrep-*/**",
|
||||
"node_modules/node-addon-require-builtin-*/**",
|
||||
"node_modules/node-pty/prebuilds/**",
|
||||
"node_modules/pnpm/dist/vendor/**"
|
||||
"package.json",
|
||||
"cordis.patch.yml",
|
||||
"build/**",
|
||||
"lib/**",
|
||||
"node_modules/**"
|
||||
],
|
||||
"afterPack": "./scripts/verify-packaged-runtime.ts",
|
||||
"electronFuses": {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/** Fail-loud verification of the runtime entries sealed into Electron's app.asar. */
|
||||
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { isAbsolute, join, relative, sep } from 'node:path'
|
||||
import { Worker } from 'node:worker_threads'
|
||||
import { listPackage } from '@electron/asar'
|
||||
import AdmZip from 'adm-zip'
|
||||
@@ -56,12 +57,39 @@ export const REQUIRED_PACKAGED_RUNTIME_ENTRIES = [
|
||||
'node_modules/pnpm/bin/pnpm.mjs',
|
||||
] as const
|
||||
|
||||
/** Platform-independent files consumed by an API that requires a physical path. */
|
||||
/** Physical entries required because profile fallback symlinks cannot target ASAR paths. */
|
||||
export const REQUIRED_UNPACKED_RUNTIME_ENTRIES = [
|
||||
'package.json',
|
||||
'cordis.patch.yml',
|
||||
'build/app-icon.png',
|
||||
'build/app-icon-mac.png',
|
||||
'build/tray-iconTemplate.png',
|
||||
'build/tray-icon-blue.png',
|
||||
'lib/main.js',
|
||||
'lib/client.js',
|
||||
'lib/native-ui/profile-create.html',
|
||||
'lib/native-ui/recovery.html',
|
||||
'lib/index.js',
|
||||
'lib/profile.js',
|
||||
'lib/profile-manager.js',
|
||||
'lib/profile-service.js',
|
||||
'lib/pnpm.js',
|
||||
'lib/profiles.js',
|
||||
'lib/diagnostics.js',
|
||||
'lib/diagnostic-export-worker.js',
|
||||
'lib/terminal.js',
|
||||
'lib/update-download.js',
|
||||
'lib/updates.js',
|
||||
'lib/windows-agent-presets.js',
|
||||
'lib/windows-pwsh-sandbox.js',
|
||||
'node_modules/@deepseek-ai/dsh/package.json',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/agent.cordis.yml',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/skills/cordis-plugin-development/SKILL.md',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md',
|
||||
'node_modules/@deepseek-ai/dsh/lib/bin.js',
|
||||
'node_modules/@deepseek-ai/dsh-app-boot/lib/index.js',
|
||||
'node_modules/@deepseek-ai/dsh-web-frontend/dist/index.html',
|
||||
'node_modules/pnpm/bin/pnpm.mjs',
|
||||
] as const
|
||||
|
||||
/** Prebuilt Node-API modules required when the Windows package skips native source rebuilds. */
|
||||
@@ -72,17 +100,6 @@ export const REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES = [
|
||||
'node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll',
|
||||
] as const
|
||||
|
||||
/** Other native executables and libraries required by the Windows x64 package. */
|
||||
export const REQUIRED_WINDOWS_X64_NATIVE_ENTRIES = [
|
||||
'node_modules/@img/sharp-win32-x64/lib/libvips-42.dll',
|
||||
'node_modules/@img/sharp-win32-x64/lib/libvips-cpp-8.18.3.dll',
|
||||
'node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64-0.35.3.node',
|
||||
'node_modules/@koromix/koffi-win32-x64/win32_x64/koffi.node',
|
||||
'node_modules/@vscode/ripgrep-win32-x64/bin/rg.exe',
|
||||
'node_modules/node-addon-require-builtin-win32-x64-msvc/prebuilt/win32-x64-msvc-napi-v9.node',
|
||||
'node_modules/pnpm/dist/vendor/fastlist-0.3.0-x64.exe',
|
||||
] as const
|
||||
|
||||
/** Native and build-only trees that must not inflate a Windows x64 installation. */
|
||||
export const FORBIDDEN_WINDOWS_X64_RUNTIME_PREFIXES = [
|
||||
'node_modules/@vscode/ripgrep-win32-arm64/',
|
||||
@@ -105,12 +122,35 @@ export const REQUIRED_MACOS_UNIVERSAL_ENTRIES = [
|
||||
...MACOS_UNIVERSAL_NATIVE_ENTRIES.map(entry => entry.path),
|
||||
] as const
|
||||
|
||||
/** Package exports that profile fallback links must resolve from the physical application tree. */
|
||||
export const REQUIRED_UNPACKED_PACKAGE_SPECIFIERS = [
|
||||
'dsh-plugin-desktop',
|
||||
'dsh-plugin-desktop/profile',
|
||||
'dsh-plugin-desktop/client',
|
||||
'dsh-plugin-desktop/terminal',
|
||||
'dsh-plugin-desktop/pnpm',
|
||||
'dsh-plugin-desktop/profile-service',
|
||||
'dsh-plugin-desktop/profiles',
|
||||
'dsh-plugin-desktop/diagnostics',
|
||||
'dsh-plugin-desktop/notifications',
|
||||
'dsh-plugin-desktop/updates',
|
||||
'dsh-plugin-desktop/windows-agent-presets',
|
||||
'dsh-plugin-desktop/windows-pwsh-sandbox',
|
||||
'dsh-plugin-desktop/package.json',
|
||||
'@deepseek-ai/dsh-base/package.json',
|
||||
'@deepseek-ai/schemastery/package.json',
|
||||
'@deepseek-ai/dsh-web-app/package.json',
|
||||
] as const
|
||||
|
||||
/** Injectable archive listing seam used by focused tests. */
|
||||
export type ArchiveLister = (archivePath: string, options: { isPack: boolean }) => readonly string[]
|
||||
|
||||
/** Injectable physical-file probe used by focused tests. */
|
||||
export type FileProbe = (filename: string) => boolean
|
||||
|
||||
/** Injectable Node package resolver used by focused tests. */
|
||||
export type PackageResolver = (specifier: string) => string
|
||||
|
||||
/** Inputs understood by the bundled diagnostics Worker. */
|
||||
export interface PackagedDiagnosticWorkerData {
|
||||
readonly logsDir: string
|
||||
@@ -284,72 +324,81 @@ export function verifyPackagedAsar(
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether one archive entry is allowed to exist beside app.asar.
|
||||
* Parent directories are included because Electron Builder materializes them
|
||||
* while unpacking a whitelisted descendant.
|
||||
* Require every ASAR header entry to have a physical counterpart.
|
||||
*
|
||||
* The Desktop packaging contract unpacks every included application file so
|
||||
* profile fallback links and Node ESM resolution never target virtual paths.
|
||||
* Checking the complete header closes the gap left by a curated entry list:
|
||||
* a collector regression cannot silently omit transitive packages such as
|
||||
* yaml, zod, or typebox from app.asar.unpacked.
|
||||
*/
|
||||
export function allowedUnpackedRuntimeEntry(entry: string): boolean {
|
||||
const exact = new Set<string>([
|
||||
...REQUIRED_UNPACKED_RUNTIME_ENTRIES,
|
||||
...REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES,
|
||||
...REQUIRED_WINDOWS_X64_NATIVE_ENTRIES,
|
||||
...REQUIRED_MACOS_UNIVERSAL_ENTRIES,
|
||||
])
|
||||
const prefixes = [
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/',
|
||||
'node_modules/@deepseek-ai/node-addon-landlock-run-linux-',
|
||||
'node_modules/@img/sharp-',
|
||||
'node_modules/@img/sharp-libvips-',
|
||||
'node_modules/@koromix/koffi-',
|
||||
'node_modules/@vscode/ripgrep-',
|
||||
'node_modules/node-addon-require-builtin-',
|
||||
'node_modules/node-pty/prebuilds/',
|
||||
'node_modules/pnpm/dist/vendor/',
|
||||
]
|
||||
if (exact.has(entry) || prefixes.some(prefix => entry.startsWith(prefix))) return true
|
||||
const directory = entry.length === 0 ? '' : `${entry}/`
|
||||
return [...exact].some(path => path.startsWith(directory))
|
||||
|| prefixes.some(prefix => prefix.startsWith(directory))
|
||||
}
|
||||
|
||||
/** Reject ordinary JavaScript and resources leaking back into app.asar.unpacked. */
|
||||
export function verifyUnpackedRuntimeScope(
|
||||
export function verifyUnpackedArchiveMirror(
|
||||
archiveEntries: ReadonlySet<string>,
|
||||
unpackedRoot: string,
|
||||
exists: FileProbe = existsSync,
|
||||
): void {
|
||||
const unexpected = [...archiveEntries].filter(entry =>
|
||||
entry.length > 0
|
||||
&& exists(join(unpackedRoot, entry))
|
||||
&& !allowedUnpackedRuntimeEntry(entry),
|
||||
)
|
||||
if (unexpected.length > 0) {
|
||||
const missing = [...archiveEntries]
|
||||
.filter(entry => entry.length > 0 && !exists(join(unpackedRoot, entry)))
|
||||
if (missing.length > 0) {
|
||||
throw new Error(
|
||||
`dsh-plugin-desktop: unpacked runtime at ${unpackedRoot} contains non-whitelisted physical entries: ${unexpected.join(', ')}`,
|
||||
`dsh-plugin-desktop: unpacked runtime at ${unpackedRoot} is missing ASAR-declared physical entries: ${missing.join(', ')}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify package exports resolve through the physical tree instead of the build workspace.
|
||||
* @param unpackedRoot - absolute path to app.asar.unpacked.
|
||||
* @param resolvePackage - package resolver anchored at the physical root manifest.
|
||||
* @returns Nothing; failure rejects missing exports and paths outside app.asar.unpacked.
|
||||
*/
|
||||
export function verifyUnpackedPackageResolution(
|
||||
unpackedRoot: string,
|
||||
resolvePackage: PackageResolver = createRequire(join(unpackedRoot, 'package.json')).resolve,
|
||||
): void {
|
||||
for (const specifier of REQUIRED_UNPACKED_PACKAGE_SPECIFIERS) {
|
||||
let resolvedPath: string
|
||||
try {
|
||||
resolvedPath = resolvePackage(specifier)
|
||||
} catch (cause) {
|
||||
throw new Error(
|
||||
`dsh-plugin-desktop: packaged runtime at ${unpackedRoot} cannot resolve required package export ${specifier}`,
|
||||
{ cause },
|
||||
)
|
||||
}
|
||||
|
||||
const relativePath = relative(unpackedRoot, resolvedPath)
|
||||
if (
|
||||
!isAbsolute(resolvedPath)
|
||||
|| relativePath === '..'
|
||||
|| relativePath.startsWith(`..${sep}`)
|
||||
|| isAbsolute(relativePath)
|
||||
) {
|
||||
throw new Error(
|
||||
`dsh-plugin-desktop: required package export ${specifier} resolved outside ${unpackedRoot}: ${resolvedPath}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify Electron Builder's completed application before signing begins.
|
||||
* @param context - Electron Builder's afterPack context.
|
||||
* @param list - ASAR listing implementation.
|
||||
* @param exists - physical-file probe for the unpacked CLI dependency tree.
|
||||
* @param resolvePackage - package resolver anchored at the physical root manifest.
|
||||
* @returns Nothing; failure rejects the package before signing.
|
||||
*/
|
||||
export function verifyPackagedRuntime(
|
||||
context: PackagedRuntimeContext,
|
||||
list: ArchiveLister = listPackage,
|
||||
exists: FileProbe = existsSync,
|
||||
resolvePackage?: PackageResolver,
|
||||
): void {
|
||||
const archiveEntries = verifyPackagedAsar(resolvePackagedAsarPath(context), list)
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(context)
|
||||
const requiredPhysicalEntries = context.electronPlatformName === 'win32'
|
||||
? [
|
||||
...REQUIRED_UNPACKED_RUNTIME_ENTRIES,
|
||||
...REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES,
|
||||
...REQUIRED_WINDOWS_X64_NATIVE_ENTRIES,
|
||||
]
|
||||
? [...REQUIRED_UNPACKED_RUNTIME_ENTRIES, ...REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES]
|
||||
: context.electronPlatformName === 'darwin' && context.arch === 4
|
||||
? [...REQUIRED_UNPACKED_RUNTIME_ENTRIES, ...REQUIRED_MACOS_UNIVERSAL_ENTRIES]
|
||||
: REQUIRED_UNPACKED_RUNTIME_ENTRIES
|
||||
@@ -378,7 +427,8 @@ export function verifyPackagedRuntime(
|
||||
)
|
||||
}
|
||||
}
|
||||
verifyUnpackedRuntimeScope(archiveEntries, unpackedRoot, exists)
|
||||
verifyUnpackedArchiveMirror(archiveEntries, unpackedRoot, exists)
|
||||
verifyUnpackedPackageResolution(unpackedRoot, resolvePackage)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/** Profile-relative package resolution for Electron's restricted Node runtime. */
|
||||
|
||||
import Module, { registerHooks } from 'node:module'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import { unpackedAsarPath } from './packaged-runtime-path.ts'
|
||||
import {
|
||||
findOverlayPackage,
|
||||
packageNameFromSpecifier,
|
||||
@@ -9,8 +10,12 @@ import {
|
||||
} from './package-overlay.ts'
|
||||
|
||||
const LOADER_ENTRY_URL = import.meta.resolve('@deepseek-ai/cordis-plugin-loader')
|
||||
const DESKTOP_ENTRY_URL = new URL('../lib/index.js', import.meta.url).href
|
||||
const DESKTOP_PACKAGE_URL = new URL('../package.json', import.meta.url).href
|
||||
const DESKTOP_ENTRY_URL = pathToFileURL(
|
||||
unpackedAsarPath(fileURLToPath(new URL('../lib/index.js', import.meta.url))),
|
||||
).href
|
||||
const DESKTOP_PACKAGE_URL = pathToFileURL(
|
||||
unpackedAsarPath(fileURLToPath(new URL('../package.json', import.meta.url))),
|
||||
).href
|
||||
|
||||
interface CommonJsModuleResolver {
|
||||
_resolveFilename(
|
||||
@@ -33,10 +38,6 @@ function isBareSpecifier(specifier: string): boolean {
|
||||
return !specifier.startsWith('.') && !specifier.startsWith('/') && !URL.canParse(specifier)
|
||||
}
|
||||
|
||||
function overlayParentUrl(source: 'install' | 'profile', profileBaseUrl: string): string {
|
||||
return source === 'profile' ? profileBaseUrl : DESKTOP_ENTRY_URL
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve Cordis Loader bare imports from the selected persistent profile.
|
||||
* @param profileBaseUrl - file URL inside the profile that owns plugin dependencies.
|
||||
@@ -88,7 +89,7 @@ export function installProfilePackageResolver(profileBaseUrl: string): () => voi
|
||||
})
|
||||
const resolved = nextResolve(specifier, {
|
||||
...context,
|
||||
parentURL: overlayParentUrl(overlay.selected.source, profileBaseUrl),
|
||||
parentURL: overlay.selected.source === 'profile' ? profileBaseUrl : DESKTOP_ENTRY_URL,
|
||||
})
|
||||
overlayModuleUrls.add(resolved.url)
|
||||
return resolved
|
||||
@@ -107,18 +108,7 @@ export function installProfilePackageResolver(profileBaseUrl: string): () => voi
|
||||
return resolved
|
||||
} catch (cause) {
|
||||
if ((cause as NodeJS.ErrnoException).code !== 'ERR_MODULE_NOT_FOUND') throw cause
|
||||
const packageName = packageNameFromSpecifier(specifier)
|
||||
const overlay = packageName === undefined
|
||||
? undefined
|
||||
: findOverlayPackage(packageName, {
|
||||
installPackageUrl: DESKTOP_PACKAGE_URL,
|
||||
profilePackageUrl: profileBaseUrl,
|
||||
})
|
||||
if (overlay === undefined) throw cause
|
||||
const resolved = nextResolve(specifier, {
|
||||
...context,
|
||||
parentURL: overlayParentUrl(overlay.selected.source, profileBaseUrl),
|
||||
})
|
||||
const resolved = nextResolve(specifier, { ...context, parentURL: profileBaseUrl })
|
||||
overlayModuleUrls.add(resolved.url)
|
||||
return resolved
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** Runtime-path helpers for Electron ASAR dependencies and explicitly unpacked assets. */
|
||||
/** Physical-path helpers for dependencies Electron Builder removes from app.asar. */
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
@@ -46,7 +46,7 @@ export function unpackedAsarPath(filename: string): string {
|
||||
* Resolve one dependency entry from a built desktop module.
|
||||
* @param moduleUrl - URL of a module emitted below the package's `lib` directory.
|
||||
* @param dependencyEntry - package subpath resolved through Node module lookup.
|
||||
* @returns a logical path suitable for Electron's RunAsNode module loader.
|
||||
* @returns a physical path suitable for Node execution and profile symlinks.
|
||||
*/
|
||||
export function packagedDependencyPath(moduleUrl: string, dependencyEntry: string): string {
|
||||
const segments = dependencyEntry.split('/')
|
||||
@@ -63,5 +63,5 @@ export function packagedDependencyPath(moduleUrl: string, dependencyEntry: strin
|
||||
resolvePackageRoot(moduleUrl, packageName),
|
||||
...segments.slice(packageSegments),
|
||||
)
|
||||
return logicalPath
|
||||
return unpackedAsarPath(logicalPath)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { evaluate, isJsExpr, type EntryOptions } from '@deepseek-ai/cordis-plugi
|
||||
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
|
||||
import {
|
||||
composeEntries,
|
||||
healProfilesModuleFallback,
|
||||
initProfile,
|
||||
loadOptionalPatches,
|
||||
loadOverlayPatches,
|
||||
@@ -54,7 +55,7 @@ const BIN_NAME = DESKTOP_PACKAGE_NAME
|
||||
const REQUIRED_BUNDLES = requiredWebBundles()
|
||||
const REQUIRED_BUNDLE_SET = new Set(REQUIRED_BUNDLES)
|
||||
const OBSOLETE_DESKTOP_BUNDLE_SET = new Set(['@deepseek-ai/dsh-desktop-app'])
|
||||
const INSTALL_ANCHOR = fileURLToPath(new URL('../package.json', import.meta.url))
|
||||
const INSTALL_ANCHOR = unpackedAsarPath(fileURLToPath(new URL('../package.json', import.meta.url)))
|
||||
const DESKTOP_PATCH_PATH = fileURLToPath(new URL('../cordis.patch.yml', import.meta.url))
|
||||
const DIRECTORY_PICKER_ROW_ID = 'directory-picker'
|
||||
const AUTO_PICKER_PACKAGE = '@deepseek-ai/dsh-host-directory-picker-auto'
|
||||
@@ -582,6 +583,7 @@ export function prepareDesktopProfile(
|
||||
const profileDir = profileName === DESKTOP_PROFILE_NAME
|
||||
? ensureDesktopProfile(home)
|
||||
: resolveProfileDir(profileName, home)
|
||||
healProfilesModuleFallback(INSTALL_ANCHOR, home)
|
||||
// `plugin-management` is the community market's user-facing scope. Startup
|
||||
// recovery has its own state file so switching to another provider cannot
|
||||
// reapply a stale community-market disable, while a recovery disable always
|
||||
|
||||
@@ -182,7 +182,7 @@ describe('packaged dsh bootstrap', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('maps only explicitly physical assets to the unpacked Electron tree', () => {
|
||||
it('uses the physical unpacked dependency tree only inside an Electron package', () => {
|
||||
expect(unpackedAsarPath('/Applications/DSH Desktop.app/Contents/Resources/app.asar/node_modules/pkg'))
|
||||
.toBe('/Applications/DSH Desktop.app/Contents/Resources/app.asar.unpacked/node_modules/pkg')
|
||||
expect(unpackedAsarPath('C:\\Program Files\\DSH Desktop\\resources\\app.asar\\node_modules\\pkg'))
|
||||
@@ -194,7 +194,7 @@ describe('packaged dsh bootstrap', () => {
|
||||
.toThrow('relative POSIX path')
|
||||
})
|
||||
|
||||
it('keeps a resolved JavaScript dependency inside ASAR for RunAsNode', () => {
|
||||
it('maps a resolved ASAR dependency to its physical unpacked path', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-desktop-asar-profile-'))
|
||||
const desktopLib = join(root, 'app.asar', 'lib')
|
||||
const dshPackage = join(root, 'app.asar', 'node_modules', '@deepseek-ai', 'dsh')
|
||||
@@ -210,7 +210,7 @@ describe('packaged dsh bootstrap', () => {
|
||||
const moduleUrl = pathToFileURL(join(desktopLib, 'desktop-cli.js')).href
|
||||
expect(packagedDependencyPath(moduleUrl, '@deepseek-ai/dsh/lib/bin.js')).toBe(join(
|
||||
realpathSync(root),
|
||||
'app.asar',
|
||||
'app.asar.unpacked',
|
||||
'node_modules',
|
||||
'@deepseek-ai',
|
||||
'dsh',
|
||||
|
||||
@@ -154,27 +154,6 @@ describe('installProfilePackageResolver', () => {
|
||||
expect(harness.resolve?.('profile-peer', { parentURL: desktopPluginUrl }, nextResolve)).toEqual({ url: profilePeerUrl })
|
||||
})
|
||||
|
||||
it('allows a Profile-selected package to use a missing dependency from the Desktop archive', () => {
|
||||
const profileBaseUrl = 'file:///C:/Users/test/profile/package.json'
|
||||
const profilePluginUrl = 'file:///C:/Users/test/profile/node_modules/plugin/index.js'
|
||||
const desktopPeerUrl = 'file:///Applications/DSH.app/Contents/Resources/app.asar/node_modules/desktop-peer/index.js'
|
||||
harness.sources.set('plugin', 'profile')
|
||||
harness.sources.set('desktop-peer', 'install')
|
||||
installProfilePackageResolver(profileBaseUrl)
|
||||
const nextResolve = vi.fn((specifier: string, context: { parentURL?: string }) => {
|
||||
if (specifier === 'plugin' && context.parentURL === profileBaseUrl) return { url: profilePluginUrl }
|
||||
if (specifier === 'desktop-peer' && context.parentURL?.endsWith('/lib/index.js')) {
|
||||
return { url: desktopPeerUrl }
|
||||
}
|
||||
throw missing(specifier, context.parentURL)
|
||||
})
|
||||
const loaderEntryUrl = import.meta.resolve('@deepseek-ai/cordis-plugin-loader')
|
||||
|
||||
expect(harness.resolve?.('plugin', { parentURL: loaderEntryUrl }, nextResolve)).toEqual({ url: profilePluginUrl })
|
||||
expect(harness.resolve?.('desktop-peer', { parentURL: profilePluginUrl }, nextResolve))
|
||||
.toEqual({ url: desktopPeerUrl })
|
||||
})
|
||||
|
||||
it('does not expose Profile dependencies to unrelated modules', () => {
|
||||
const profileBaseUrl = 'file:///C:/Users/test/profile/package.json'
|
||||
installProfilePackageResolver(profileBaseUrl)
|
||||
@@ -210,7 +189,7 @@ describe('installProfilePackageResolver', () => {
|
||||
)).toBe('ordinary:@deepseek-ai/dsh-client-modules/package.json')
|
||||
expect(resolveFilename(
|
||||
'@deepseek-ai/dsh-client-modules/client.js',
|
||||
{ filename: profileManifestPath },
|
||||
{ filename: '/tmp/dsh-profile/package.json' },
|
||||
false,
|
||||
)).toBe('ordinary:@deepseek-ai/dsh-client-modules/client.js')
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ const manifest = JSON.parse(readFileSync(new URL('package.json', packageRoot), '
|
||||
build?: {
|
||||
productName?: unknown
|
||||
appId?: unknown
|
||||
asar?: unknown
|
||||
asarUnpack?: unknown
|
||||
afterPack?: unknown
|
||||
electronFuses?: unknown
|
||||
@@ -373,7 +372,6 @@ describe('published package surface', () => {
|
||||
expect(config).toContain("diagnostics: 'src/diagnostics.ts'")
|
||||
expect(config).toContain("notifications: 'src/notifications.ts'")
|
||||
expect(config).toContain("'diagnostic-export-worker': 'src/diagnostic-export-worker.ts'")
|
||||
expect(config).toContain('codeSplitting: false')
|
||||
expect(config).toContain("entry: { preload: 'src/preload.ts' }")
|
||||
expect(config).toContain("entryFileNames: 'preload.cjs'")
|
||||
expect(config).toContain("terminal: 'src/terminal.ts'")
|
||||
@@ -569,18 +567,12 @@ describe('published package surface', () => {
|
||||
expect(manifest.version).toBe(workspaceManifest.version)
|
||||
expect(manifest.build?.productName).toBe('DSH Desktop')
|
||||
expect(manifest.build?.appId).toBe('ai.deepseek.dsh.desktop')
|
||||
expect(manifest.build?.asar).toEqual({ smartUnpack: false })
|
||||
expect(manifest.build?.asarUnpack).toEqual([
|
||||
'lib/diagnostic-export-worker.js',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/**',
|
||||
'node_modules/@deepseek-ai/node-addon-landlock-run-linux-*/**',
|
||||
'node_modules/@img/sharp-*/**',
|
||||
'node_modules/@img/sharp-libvips-*/**',
|
||||
'node_modules/@koromix/koffi-*/**',
|
||||
'node_modules/@vscode/ripgrep-*/**',
|
||||
'node_modules/node-addon-require-builtin-*/**',
|
||||
'node_modules/node-pty/prebuilds/**',
|
||||
'node_modules/pnpm/dist/vendor/**',
|
||||
'package.json',
|
||||
'cordis.patch.yml',
|
||||
'build/**',
|
||||
'lib/**',
|
||||
'node_modules/**',
|
||||
])
|
||||
expect(manifest.build?.electronFuses).toEqual({ runAsNode: true })
|
||||
expect(manifest.build?.toolsets).toEqual({ nsis: '1.2.1' })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
@@ -149,14 +149,6 @@ describe('desktop profile composition', {
|
||||
expect(repaired.custom.preserved).toBe(true)
|
||||
})
|
||||
|
||||
it('does not materialize the installation dependency closure in the profiles directory', () => {
|
||||
const home = temporaryHome()
|
||||
|
||||
prepareDesktopProfile(undefined, home, 'win32')
|
||||
|
||||
expect(existsSync(join(home, 'profiles', 'node_modules'))).toBe(false)
|
||||
})
|
||||
|
||||
it('migrates the obsolete Desktop bundle before loading a historical profile', () => {
|
||||
const home = temporaryHome()
|
||||
const dir = ensureDesktopProfile(home)
|
||||
|
||||
@@ -4,22 +4,22 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import AdmZip from 'adm-zip'
|
||||
import {
|
||||
afterPack,
|
||||
allowedUnpackedRuntimeEntry,
|
||||
FORBIDDEN_WINDOWS_X64_RUNTIME_PREFIXES,
|
||||
REQUIRED_MACOS_UNIVERSAL_ENTRIES,
|
||||
REQUIRED_PACKAGED_RUNTIME_ENTRIES,
|
||||
REQUIRED_MACOS_UNIVERSAL_ENTRIES,
|
||||
REQUIRED_UNPACKED_PACKAGE_SPECIFIERS,
|
||||
REQUIRED_UNPACKED_RUNTIME_ENTRIES,
|
||||
REQUIRED_WINDOWS_X64_NATIVE_ENTRIES,
|
||||
REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES,
|
||||
resolvePackagedAsarPath,
|
||||
resolvePackagedUnpackedRoot,
|
||||
smokePackagedDiagnosticWorker,
|
||||
verifyUnpackedArchiveMirror,
|
||||
verifyPackagedRuntime,
|
||||
verifyUnpackedRuntimeScope,
|
||||
type ArchiveLister,
|
||||
type FileProbe,
|
||||
type PackagedDiagnosticWorkerLauncher,
|
||||
type PackageResolver,
|
||||
type PackagedRuntimeContext,
|
||||
type PackagedDiagnosticWorkerLauncher,
|
||||
} from '../scripts/verify-packaged-runtime.ts'
|
||||
import { FORBIDDEN_MACOS_UNIVERSAL_ENTRIES } from '../scripts/mac-universal.ts'
|
||||
|
||||
@@ -40,33 +40,8 @@ function completeArchiveEntries(separator = '/'): string[] {
|
||||
return REQUIRED_PACKAGED_RUNTIME_ENTRIES.map(entry => `${separator}${entry.replaceAll('/', separator)}`)
|
||||
}
|
||||
|
||||
function requiredPhysicalEntries(runtimeContext: PackagedRuntimeContext): readonly string[] {
|
||||
if (runtimeContext.electronPlatformName === 'win32') {
|
||||
return [
|
||||
...REQUIRED_UNPACKED_RUNTIME_ENTRIES,
|
||||
...REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES,
|
||||
...REQUIRED_WINDOWS_X64_NATIVE_ENTRIES,
|
||||
]
|
||||
}
|
||||
if (runtimeContext.electronPlatformName === 'darwin' && runtimeContext.arch === 4) {
|
||||
return [
|
||||
...REQUIRED_UNPACKED_RUNTIME_ENTRIES,
|
||||
...REQUIRED_MACOS_UNIVERSAL_ENTRIES,
|
||||
]
|
||||
}
|
||||
return REQUIRED_UNPACKED_RUNTIME_ENTRIES
|
||||
}
|
||||
|
||||
function physicalProbe(
|
||||
runtimeContext: PackagedRuntimeContext,
|
||||
options: { readonly missing?: string, readonly extra?: string } = {},
|
||||
): FileProbe {
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const present = new Set(requiredPhysicalEntries(runtimeContext)
|
||||
.filter(entry => entry !== options.missing)
|
||||
.map(entry => join(unpackedRoot, entry)))
|
||||
if (options.extra !== undefined) present.add(join(unpackedRoot, options.extra))
|
||||
return filename => present.has(filename)
|
||||
function completePackageResolver(unpackedRoot: string): PackageResolver {
|
||||
return specifier => join(unpackedRoot, 'resolved', `${specifier.replaceAll('/', '-')}.js`)
|
||||
}
|
||||
|
||||
describe('packaged desktop runtime verification', () => {
|
||||
@@ -96,7 +71,10 @@ describe('packaged desktop runtime verification', () => {
|
||||
expect(workerPath).toBe(join(unpackedRoot, 'lib', 'diagnostic-export-worker.js'))
|
||||
expect(readFileSync(join(workerData.logsDir, 'dsh-2000-01-01.log'), 'utf8'))
|
||||
.toBe('packaged worker smoke\n')
|
||||
expect(workerData.appVersion).toBe('packaged-smoke')
|
||||
expect(workerData.maxEvidenceBytes).toBe(1024)
|
||||
const crashDump = readFileSync(join(workerData.crashDumpsDir, 'pending', 'packaged-smoke.dmp'))
|
||||
expect(crashDump.toString('utf8')).toBe('packaged crash dump smoke\n')
|
||||
const outDir = join(workerData.userDataDir, 'diagnostics')
|
||||
mkdirSync(outDir)
|
||||
const output = join(outDir, 'diagnostics-smoke.zip')
|
||||
@@ -127,6 +105,42 @@ describe('packaged desktop runtime verification', () => {
|
||||
expect(calls).toEqual(['static', resolvePackagedUnpackedRoot(runtimeContext)])
|
||||
})
|
||||
|
||||
it('tracks the ConPTY-only native surface shipped by node-pty 1.2', () => {
|
||||
expect(REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES).toEqual([
|
||||
'node_modules/node-pty/prebuilds/win32-x64/conpty.node',
|
||||
'node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node',
|
||||
'node_modules/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe',
|
||||
'node_modules/node-pty/prebuilds/win32-x64/conpty/conpty.dll',
|
||||
])
|
||||
})
|
||||
|
||||
it.each([
|
||||
'lib/main.js.map',
|
||||
'node_modules/example/index.js.map',
|
||||
'node_modules/example/source.ts',
|
||||
'node_modules/example/source.mts',
|
||||
'node_modules/example/source.cts',
|
||||
])('rejects excluded development source %s', (forbidden) => {
|
||||
expect(() => verifyPackagedRuntime(
|
||||
context('/build', 'win32'),
|
||||
() => [...completeArchiveEntries(), `/${forbidden}`],
|
||||
() => true,
|
||||
)).toThrow(`contains excluded development sources: ${forbidden}`)
|
||||
})
|
||||
|
||||
it.each(FORBIDDEN_WINDOWS_X64_RUNTIME_PREFIXES)(
|
||||
'rejects forbidden Windows x64 runtime prefix %s',
|
||||
(prefix) => {
|
||||
const forbidden = `${prefix}payload.bin`
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
context('/build', 'win32'),
|
||||
() => [...completeArchiveEntries(), `/${forbidden}`],
|
||||
() => true,
|
||||
)).toThrow(`contains non-x64 or build-only entries: ${forbidden}`)
|
||||
},
|
||||
)
|
||||
|
||||
it.each([
|
||||
[
|
||||
'darwin',
|
||||
@@ -137,14 +151,25 @@ describe('packaged desktop runtime verification', () => {
|
||||
join('/build', 'resources', 'app.asar'),
|
||||
],
|
||||
])('inspects the %s app.asar path', (platform, expectedPath) => {
|
||||
const runtimeContext = context('/build', platform)
|
||||
const list = vi.fn<ArchiveLister>(() => completeArchiveEntries(platform === 'win32' ? '\\' : '/'))
|
||||
|
||||
verifyPackagedRuntime(runtimeContext, list, physicalProbe(runtimeContext))
|
||||
const exists = vi.fn<FileProbe>(() => true)
|
||||
const unpackedRoot = `${expectedPath}.unpacked`
|
||||
const resolvePackage = vi.fn<PackageResolver>(completePackageResolver(unpackedRoot))
|
||||
|
||||
expect(resolvePackagedAsarPath(runtimeContext)).toBe(expectedPath)
|
||||
verifyPackagedRuntime(context('/build', platform), list, exists, resolvePackage)
|
||||
|
||||
expect(resolvePackagedAsarPath(context('/build', platform))).toBe(expectedPath)
|
||||
expect(list).toHaveBeenCalledOnce()
|
||||
expect(list).toHaveBeenCalledWith(expectedPath, { isPack: false })
|
||||
expect(resolvePackagedUnpackedRoot(runtimeContext)).toBe(`${expectedPath}.unpacked`)
|
||||
expect(resolvePackagedUnpackedRoot(context('/build', platform))).toBe(unpackedRoot)
|
||||
expect(exists).toHaveBeenCalledTimes(
|
||||
REQUIRED_UNPACKED_RUNTIME_ENTRIES.length
|
||||
+ (platform === 'win32' ? REQUIRED_WINDOWS_X64_NODE_PTY_ENTRIES.length : 0)
|
||||
+ completeArchiveEntries().length,
|
||||
)
|
||||
expect(resolvePackage.mock.calls.map(([specifier]) => specifier))
|
||||
.toEqual(REQUIRED_UNPACKED_PACKAGE_SPECIFIERS)
|
||||
})
|
||||
|
||||
it('rejects an unsupported platform instead of guessing an archive layout', () => {
|
||||
@@ -152,80 +177,57 @@ describe('packaged desktop runtime verification', () => {
|
||||
.toThrow('unsupported Electron afterPack platform "mas"')
|
||||
})
|
||||
|
||||
it.each([
|
||||
'lib/main.js.map',
|
||||
'node_modules/example/index.js.map',
|
||||
'node_modules/example/source.ts',
|
||||
'node_modules/example/source.mts',
|
||||
'node_modules/example/source.cts',
|
||||
])('rejects excluded development source %s', (forbidden) => {
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => [...completeArchiveEntries(), `/${forbidden}`],
|
||||
physicalProbe(runtimeContext),
|
||||
)).toThrow(`contains excluded development sources: ${forbidden}`)
|
||||
})
|
||||
|
||||
it.each(FORBIDDEN_WINDOWS_X64_RUNTIME_PREFIXES)(
|
||||
'rejects forbidden Windows x64 runtime prefix %s',
|
||||
(prefix) => {
|
||||
const forbidden = `${prefix}payload.bin`
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => [...completeArchiveEntries(), `/${forbidden}`],
|
||||
physicalProbe(runtimeContext),
|
||||
)).toThrow(`contains non-x64 or build-only entries: ${forbidden}`)
|
||||
},
|
||||
)
|
||||
|
||||
it('allows only native, executable, preset, and Worker paths outside ASAR', () => {
|
||||
expect(allowedUnpackedRuntimeEntry('lib/diagnostic-export-worker.js')).toBe(true)
|
||||
expect(allowedUnpackedRuntimeEntry('node_modules/@deepseek-ai/node-addon-landlock-run-linux-x64/native.node')).toBe(true)
|
||||
expect(allowedUnpackedRuntimeEntry('node_modules/@vscode/ripgrep-win32-x64/bin/rg.exe')).toBe(true)
|
||||
expect(allowedUnpackedRuntimeEntry('node_modules')).toBe(true)
|
||||
expect(allowedUnpackedRuntimeEntry('lib/main.js')).toBe(false)
|
||||
expect(allowedUnpackedRuntimeEntry('node_modules/yaml/dist/index.js')).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects ordinary JavaScript leaking into app.asar.unpacked', () => {
|
||||
const unpackedRoot = join('/build', 'resources', 'app.asar.unpacked')
|
||||
const leaked = 'node_modules/yaml/dist/index.js'
|
||||
|
||||
expect(() => verifyUnpackedRuntimeScope(
|
||||
new Set(['lib/main.js', leaked]),
|
||||
unpackedRoot,
|
||||
filename => filename === join(unpackedRoot, leaked),
|
||||
)).toThrow(`contains non-whitelisted physical entries: ${leaked}`)
|
||||
})
|
||||
|
||||
it('requires both CPU variants from a universal macOS runtime', () => {
|
||||
const runtimeContext = context('/build', 'darwin', 4)
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const missing = 'node_modules/@vscode/ripgrep-darwin-x64/bin/rg'
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
physicalProbe(runtimeContext, { missing }),
|
||||
filename => filename !== join(unpackedRoot, missing),
|
||||
completePackageResolver(unpackedRoot),
|
||||
)).toThrow(`missing required physical entries: ${missing}`)
|
||||
|
||||
const exists = vi.fn<FileProbe>(filename => !FORBIDDEN_MACOS_UNIVERSAL_ENTRIES
|
||||
.some(entry => filename === join(unpackedRoot, entry)))
|
||||
verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
physicalProbe(runtimeContext),
|
||||
exists,
|
||||
completePackageResolver(unpackedRoot),
|
||||
)
|
||||
expect(exists).toHaveBeenCalledTimes(
|
||||
REQUIRED_UNPACKED_RUNTIME_ENTRIES.length
|
||||
+ REQUIRED_MACOS_UNIVERSAL_ENTRIES.length
|
||||
+ FORBIDDEN_MACOS_UNIVERSAL_ENTRIES.length
|
||||
+ completeArchiveEntries().length,
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects any ASAR-declared unpacked dependency missing from the physical tree', () => {
|
||||
const unpackedRoot = join('/build', 'resources', 'app.asar.unpacked')
|
||||
const missing = 'node_modules/yaml/dist/index.js'
|
||||
|
||||
expect(() => verifyUnpackedArchiveMirror(
|
||||
new Set(['lib/main.js', missing, 'node_modules/zod/index.js']),
|
||||
unpackedRoot,
|
||||
filename => filename !== join(unpackedRoot, missing),
|
||||
)).toThrow(`missing ASAR-declared physical entries: ${missing}`)
|
||||
})
|
||||
|
||||
it('rejects a host-architecture node-pty build from a universal app', () => {
|
||||
const runtimeContext = context('/build', 'darwin', 4)
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const forbidden = FORBIDDEN_MACOS_UNIVERSAL_ENTRIES[0]
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
physicalProbe(runtimeContext, { extra: forbidden }),
|
||||
filename => filename === join(unpackedRoot, forbidden)
|
||||
|| !FORBIDDEN_MACOS_UNIVERSAL_ENTRIES
|
||||
.some(entry => filename === join(unpackedRoot, entry)),
|
||||
completePackageResolver(unpackedRoot),
|
||||
)).toThrow(`contains host-architecture build output: ${forbidden}`)
|
||||
})
|
||||
|
||||
@@ -239,29 +241,111 @@ describe('packaged desktop runtime verification', () => {
|
||||
'lib/update-download.js',
|
||||
'lib/windows-agent-presets.js',
|
||||
])('fails loud when required runtime entry %s is absent', (missing) => {
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
const entries = completeArchiveEntries().filter(entry => entry !== `/${missing}`)
|
||||
|
||||
expect(() => verifyPackagedRuntime(runtimeContext, () => entries, physicalProbe(runtimeContext)))
|
||||
expect(() => verifyPackagedRuntime(context('/build', 'win32'), () => entries, () => true))
|
||||
.toThrow(`missing required ASAR entries: ${missing}`)
|
||||
})
|
||||
|
||||
it.each([
|
||||
'package.json',
|
||||
'build/app-icon-mac.png',
|
||||
'build/tray-iconTemplate.png',
|
||||
'lib/terminal.js',
|
||||
'lib/diagnostics.js',
|
||||
'lib/diagnostic-export-worker.js',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/agent.cordis.yml',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/skills/cordis-plugin-development/SKILL.md',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md',
|
||||
'node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64-0.35.3.node',
|
||||
'node_modules/@vscode/ripgrep-win32-x64/bin/rg.exe',
|
||||
'lib/update-download.js',
|
||||
'lib/windows-agent-presets.js',
|
||||
'node_modules/@deepseek-ai/dsh/lib/bin.js',
|
||||
'node_modules/pnpm/bin/pnpm.mjs',
|
||||
'node_modules/node-pty/prebuilds/win32-x64/conpty.node',
|
||||
'node_modules/pnpm/dist/vendor/fastlist-0.3.0-x64.exe',
|
||||
])('fails loud when required physical runtime entry %s is absent', (missing) => {
|
||||
])('fails loud when physical runtime entry %s is absent from app.asar.unpacked', (missing) => {
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const missingPath = join(unpackedRoot, missing)
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
physicalProbe(runtimeContext, { missing }),
|
||||
filename => filename !== missingPath,
|
||||
completePackageResolver(unpackedRoot),
|
||||
)).toThrow(`missing required physical entries: ${missing}`)
|
||||
})
|
||||
|
||||
it('requires the physical Cordis preset and its bundled skills', () => {
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const requiredPresetEntries = [
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/agent.cordis.yml',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/skills/cordis-plugin-development/SKILL.md',
|
||||
'node_modules/@deepseek-ai/dsh/config/agent-presets/cordis/skills/editing-cordis-compositions/SKILL.md',
|
||||
]
|
||||
|
||||
for (const missing of requiredPresetEntries) {
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
filename => filename !== join(unpackedRoot, missing),
|
||||
completePackageResolver(unpackedRoot),
|
||||
)).toThrow(`missing required physical entries: ${missing}`)
|
||||
}
|
||||
})
|
||||
|
||||
it('fails loud when a required package export cannot resolve from app.asar.unpacked', () => {
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const resolvePackage = vi.fn<PackageResolver>((specifier) => {
|
||||
if (specifier === 'dsh-plugin-desktop/profiles') {
|
||||
throw new Error('missing export')
|
||||
}
|
||||
return completePackageResolver(unpackedRoot)(specifier)
|
||||
})
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
() => true,
|
||||
resolvePackage,
|
||||
)).toThrow(
|
||||
`packaged runtime at ${unpackedRoot} cannot resolve required package export dsh-plugin-desktop/profiles`,
|
||||
)
|
||||
})
|
||||
|
||||
it('fails loud when schemastery is absent from app.asar.unpacked', () => {
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const specifier = '@deepseek-ai/schemastery/package.json'
|
||||
const resolvePackage = vi.fn<PackageResolver>((requested) => {
|
||||
if (requested === specifier) throw new Error('missing package')
|
||||
return completePackageResolver(unpackedRoot)(requested)
|
||||
})
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
() => true,
|
||||
resolvePackage,
|
||||
)).toThrow(
|
||||
`packaged runtime at ${unpackedRoot} cannot resolve required package export ${specifier}`,
|
||||
)
|
||||
})
|
||||
|
||||
it('fails loud when a required package export escapes app.asar.unpacked', () => {
|
||||
const runtimeContext = context('/build', 'win32')
|
||||
const unpackedRoot = resolvePackagedUnpackedRoot(runtimeContext)
|
||||
const escapedPath = join('/workspace', 'node_modules', '@deepseek-ai', 'dsh-base', 'lib', 'index.js')
|
||||
const resolvePackage = vi.fn<PackageResolver>((specifier) => {
|
||||
if (specifier === '@deepseek-ai/dsh-base/package.json') return escapedPath
|
||||
return completePackageResolver(unpackedRoot)(specifier)
|
||||
})
|
||||
|
||||
expect(() => verifyPackagedRuntime(
|
||||
runtimeContext,
|
||||
() => completeArchiveEntries(),
|
||||
() => true,
|
||||
resolvePackage,
|
||||
)).toThrow(
|
||||
`required package export @deepseek-ai/dsh-base/package.json resolved outside ${unpackedRoot}: ${escapedPath}`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ export default defineConfig([
|
||||
profiles: 'src/profiles.ts',
|
||||
diagnostics: 'src/diagnostics.ts',
|
||||
notifications: 'src/notifications.ts',
|
||||
'diagnostic-export-worker': 'src/diagnostic-export-worker.ts',
|
||||
runtime: 'src/runtime.ts',
|
||||
'electron-runtime': 'src/electron-runtime.ts',
|
||||
'desktop-runtime-environment': 'src/desktop-runtime-environment.ts',
|
||||
@@ -40,22 +41,6 @@ export default defineConfig([
|
||||
clean: false,
|
||||
sourcemap: true,
|
||||
},
|
||||
{
|
||||
name: `${PACKAGE_NAME}/diagnostic-export-worker`,
|
||||
entry: { 'diagnostic-export-worker': 'src/diagnostic-export-worker.ts' },
|
||||
outDir: 'lib',
|
||||
format: 'esm',
|
||||
platform: 'node',
|
||||
target: 'es2024',
|
||||
fixedExtension: false,
|
||||
dts: false,
|
||||
clean: false,
|
||||
sourcemap: true,
|
||||
outputOptions: {
|
||||
entryFileNames: 'diagnostic-export-worker.js',
|
||||
codeSplitting: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: `${PACKAGE_NAME}/bin`,
|
||||
entry: { bin: 'src/bin.ts' },
|
||||
|
||||
Reference in New Issue
Block a user