Merge pull request #4686 from deepseek-harness/fix/desktop-dev-runtime-target

fix(desktop): resolve the development runtime directory from the build target
This commit is contained in:
Xu Hanxiang authored and GitHub committed 2026-09-23 17:43:43 +08:00
commit c5dc0853d6
14 files changed
+135 -17

No files matched your search

+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write apps/desktop/README.md
README.md: 87b16ac8fd7ec269e8c02a34a32e6593b38aec0a
README.zh.md: 895fdd972148c90bad00916a35b21cebff8842e8
README.md: ec3f2f44d5e49db00bd595588a0594bcee0a1538
README.zh.md: 50fba340a0edc114a75d4b6363b8e6c42be0c45f
+2 -1
View File
@@ -399,7 +399,7 @@ The downloaded bytes are inert, and the installation call is recorded rather tha
## Low-level development overrides
An unpackaged Electron process uses `.desktop-build/development/project` under its application directory as its development project. `DSH_DESKTOP_PNPM_ENTRY` and `DSH_DESKTOP_DSH_DIR` select explicit runtime resources. Packaged applications ignore these variables, resolve signed resources from `process.resourcesPath`, and use the managed Desktop profile.
An unpackaged Electron process uses `.desktop-build/development/project` under its application directory as its development project. `DSH_DESKTOP_PNPM_ENTRY` and `DSH_DESKTOP_DSH_DIR` are optional overrides with application-path defaults. `DSH_DESKTOP_PRIMARY_RUNTIME_DIR` is required for every unpackaged launch: the development launchers (`dev:desktop`, `start:desktop`, and the workspace-update qualification runner) set it to the primary-runtime directory of the target they prepared, and a launch without it fails with the fatal startup dialog. The launcher must set it because the shell cannot derive that directory from `process.arch`: the build target fixes Windows to x64 while the host may be arm64. Packaged applications ignore these variables, resolve signed resources from `process.resourcesPath`, and use the managed Desktop profile.
## Known limitations
@@ -408,6 +408,7 @@ An unpackaged Electron process uses `.desktop-build/development/project` under i
- Release signing, notarization, update hosting, and previous-version installed-artifact qualification require the production release environment.
- Dependency lifecycle scripts follow pnpm’s build permissions; Desktop provides no separate approval dialog.
- The desktop shell shares sessions, settings, credentials, workspaces, and storage under `$DSH_HOME` with CLI dsh, while executable packages, plugin activation, and lockfiles remain separate.
- Unpackaged startup on an Electron win32-arm64 host now succeeds, but the payload remains x64: the architecture check in `packages/skill/tool-workspace-dependencies/src/index.ts` compares the recorded payload architecture against the host `process.arch`, so the `load_workspace_dependencies` tool can still reject the primary runtime.
Sign in opens the configured platform page in the system browser. The Host owns PKCE and a temporary loopback callback, saves the credential before entering the workspace, and redirects the browser to the platform completion page. Opened and copied authorization links carry the effective Desktop theme as `theme=light` or `theme=dark`; `system` resolves at the time of the action. Cancel withdraws the local attempt even if the platform page later approves it. Settings offers Account sign-out; without a separate API key, sign-out returns to the welcome window. Packaged applications register dsh://open to show the window without passing credentials. On macOS, the development launcher prepares an ad-hoc-signed `Harness Dev.app` under `.desktop-build/development`, declares `dsh` in its Info.plist, and registers it with Launch Services. It loads the current workspace and records the selected development home, browser-data path, and debug settings for cold starts. Starting this bundle makes it the default `dsh://` handler; starting the packaged application registers the packaged handler again. The generated bundle does not contain account tokens and requires the workspace and prepared runtime to remain available.
+2 -1
View File
@@ -399,7 +399,7 @@ node apps/desktop/node_modules/pnpm/bin/pnpm.mjs --dir apps/desktop run test:upd
## 底层开发覆盖项
未打包的 Electron 进程使用应用目录下的 `.desktop-build/development/project` 作为开发项目。`DSH_DESKTOP_PNPM_ENTRY` 和 `DSH_DESKTOP_DSH_DIR` 用于选择明确的运行时资源。打包应用会忽略这些变量,从 `process.resourcesPath` 解析签名资源,并使用受管 Desktop profile。
未打包的 Electron 进程使用应用目录下的 `.desktop-build/development/project` 作为开发项目。`DSH_DESKTOP_PNPM_ENTRY` 和 `DSH_DESKTOP_DSH_DIR` 是带应用路径默认值的可选覆盖项。每次未打包启动都必须设置 `DSH_DESKTOP_PRIMARY_RUNTIME_DIR`:开发启动器(`dev:desktop`、`start:desktop` 及工作区更新验证运行器)会把它设置为自己已准备目标的 primary-runtime 目录;缺少该变量的启动会以致命启动对话框失败。启动器必须设置它,因为壳无法从 `process.arch` 推导该目录:构建目标将 Windows 固定为 x64,而宿主可能是 arm64。打包应用会忽略这些变量,从 `process.resourcesPath` 解析签名资源,并使用受管 Desktop profile。
## 已知限制
@@ -408,6 +408,7 @@ node apps/desktop/node_modules/pnpm/bin/pnpm.mjs --dir apps/desktop run test:upd
- 发布签名、公证、更新托管和跨上一版本的已安装产物验证需要生产发布环境。
- 依赖的生命周期脚本遵循 pnpm 的构建权限;Desktop 不提供单独的审批对话框。
- 桌面壳与 CLI dsh 共享 `$DSH_HOME` 下的会话、设置、凭据、工作区和存储,但可执行包、插件激活和锁文件彼此隔离。
- 在 Electron win32-arm64 宿主上,未打包启动现在可以成功,但载荷仍为 x64:`packages/skill/tool-workspace-dependencies/src/index.ts` 的架构校验会把载荷记录的架构与宿主 `process.arch` 比较,因此 `load_workspace_dependencies` 工具仍可能拒绝 primary runtime。
登录会在系统浏览器中打开配置的平台页面。Host 负责 PKCE 和临时本机回调,在进入工作区前保存凭证,再将浏览器跳转到平台完成页。打开和复制的授权链接通过 `theme=light` 或 `theme=dark` 携带当前生效的 Desktop 主题;`system` 在执行操作时解析。即使平台页面随后批准,取消仍会撤销本地尝试。设置中的账号页面提供退出;没有独立 API Key 时,退出后返回欢迎窗。打包应用注册 dsh://open,只显示窗口而不传递凭证。macOS 开发启动器在 `.desktop-build/development` 下准备经临时签名的 `Harness Dev.app`,在 Info.plist 中声明 `dsh` 并注册到 Launch Services。它加载当前工作区,并记录选定的开发 home、浏览器数据路径和调试设置,以供冷启动使用。启动此应用会将其设为 `dsh://` 默认处理程序;启动打包应用会重新注册打包版处理程序。生成的应用包不包含账号 token,依赖工作区和已准备的运行环境继续存在。
@@ -36,6 +36,17 @@ export function resolveDesktopBuildTarget(
*/
export function desktopTargetBuildPaths(target: DesktopAutoUpdateTarget): DesktopTargetBuildPaths
/**
* Return the platform and architecture of the payload one release target prepares.
* Windows is prepared as x64 only, so this differs from the build host on an arm64 Windows machine.
* @param target - Supported Desktop target name.
* @returns Platform and architecture of the prepared payload.
*/
export function desktopTargetPlatform(target: DesktopAutoUpdateTarget): {
readonly platform: 'darwin' | 'win32'
readonly arch: 'arm64' | 'x64'
}
/**
* Resolve the paths owned by the target selected in a packaging environment.
* @param env - Packaging environment.
@@ -48,3 +59,16 @@ export function resolveDesktopTargetBuildPaths(
hostPlatform?: NodeJS.Platform,
hostArch?: string,
): DesktopTargetBuildPaths
/**
* Resolve the primary-runtime directory an unpackaged development launch uses.
* @param env - Packaging environment.
* @param hostPlatform - Build-host platform used when no target override exists.
* @param hostArch - Build-host architecture used when no target override exists.
* @returns Primary-runtime directory prepared for the selected target.
*/
export function developmentRuntimeDirectory(
env?: NodeJS.ProcessEnv,
hostPlatform?: NodeJS.Platform,
hostArch?: string,
): string
+38 -3
View File
@@ -29,15 +29,19 @@ export function resolveDesktopBuildTarget(
return /** @type {'mac-arm64' | 'mac-x64' | 'win-x64'} */ (target)
}
function assertSupportedTarget(target) {
if (!SUPPORTED_TARGETS.has(target)) {
throw new Error(`desktop build paths: unsupported target ${String(target)}`)
}
}
/**
* Return the mutable preparation and artifact directories owned by one release target.
* @param {'mac-arm64' | 'mac-x64' | 'win-x64'} target - Supported Desktop target name.
* @returns {{ root: string, artifacts: string, unsignedArtifacts: string, runtime: string, packageSet: string, dsh: string, dshPnpm: string, electron: string, packedDsh: string, packedVendor: string, packedLandlock: string, downloads: string }} Target paths plus the shared immutable download cache.
*/
export function desktopTargetBuildPaths(target) {
if (!SUPPORTED_TARGETS.has(target)) {
throw new Error(`desktop build paths: unsupported target ${String(target)}`)
}
assertSupportedTarget(target)
const root = join(BUILD_ROOT, 'targets', target)
const packed = join(root, 'packed')
return {
@@ -56,6 +60,20 @@ export function desktopTargetBuildPaths(target) {
}
}
/**
* Return the platform and architecture of the payload one release target prepares.
* Windows is prepared as x64 only, so this differs from the build host on an arm64 Windows machine.
* @param {'mac-arm64' | 'mac-x64' | 'win-x64'} target - Supported Desktop target name.
* @returns {{ platform: 'darwin' | 'win32', arch: 'arm64' | 'x64' }} Platform and architecture of the prepared payload.
*/
export function desktopTargetPlatform(target) {
assertSupportedTarget(target)
return {
platform: /** @type {'darwin' | 'win32'} */ (target === 'win-x64' ? 'win32' : 'darwin'),
arch: /** @type {'arm64' | 'x64'} */ (target === 'mac-arm64' ? 'arm64' : 'x64'),
}
}
/**
* Resolve the paths owned by the target selected in a packaging environment.
* @param {NodeJS.ProcessEnv} env - Packaging environment.
@@ -70,3 +88,20 @@ export function resolveDesktopTargetBuildPaths(
) {
return desktopTargetBuildPaths(resolveDesktopBuildTarget(env, hostPlatform, hostArch))
}
/**
* Resolve the primary-runtime directory an unpackaged development launch uses.
* The build target fixes Windows to x64, so the shell cannot derive this directory from
* the architecture of the process that launched it.
* @param {NodeJS.ProcessEnv} env - Packaging environment.
* @param {NodeJS.Platform} hostPlatform - Build-host platform used when no target override exists.
* @param {string} hostArch - Build-host architecture used when no target override exists.
* @returns {string} Primary-runtime directory prepared for the selected target.
*/
export function developmentRuntimeDirectory(
env = process.env,
hostPlatform = process.platform,
hostArch = process.arch,
) {
return join(resolveDesktopTargetBuildPaths(env, hostPlatform, hostArch).runtime, 'primary-runtime')
}
+3
View File
@@ -7,6 +7,7 @@ import { join, resolve } from 'node:path'
import { parseArgs } from 'node:util'
import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
import type { DesktopRelease } from '../src/release.ts'
import { developmentRuntimeDirectory, resolveDesktopBuildTarget } from './desktop-build-paths.mjs'
import { prepareDevelopmentProject } from './development-project.ts'
import { prepareDevelopmentApp } from './development-app.ts'
import { preparePrimaryRuntime } from './prepare-primary-runtime.ts'
@@ -67,6 +68,7 @@ async function launchElectron(): Promise<void> {
const environment: NodeJS.ProcessEnv = {
...process.env,
DSH_HOME: home,
DSH_DESKTOP_PRIMARY_RUNTIME_DIR: process.env.DSH_DESKTOP_PRIMARY_RUNTIME_DIR ?? developmentRuntimeDirectory(),
DSH_DESKTOP_HOST_INSPECT_PORT: String(hostPort),
DSH_DESKTOP_OPEN_DEVTOOLS: process.env.DSH_DESKTOP_OPEN_DEVTOOLS ?? '1',
ELECTRON_ENABLE_LOGGING: process.env.ELECTRON_ENABLE_LOGGING ?? '1',
@@ -115,6 +117,7 @@ async function main(): Promise<void> {
hostDir: join(REPOSITORY_ROOT, 'apps', 'desktop-host'),
dependencyDir: join(REPOSITORY_ROOT, 'node_modules', '.pnpm', 'node_modules'),
release,
target: resolveDesktopBuildTarget(),
})
await preparePrimaryRuntime()
await launchElectron()
+6 -2
View File
@@ -16,6 +16,8 @@ import { dirname, join } from 'node:path'
import { createDevelopmentProjectMetadata } from '../src/project-manager.ts'
import type { DesktopRelease } from '../src/release.ts'
import { DESKTOP_RUNTIME_FILE, type DesktopRuntimeDescriptor } from '../src/runtime-tree.ts'
import type { DesktopAutoUpdateTarget } from './desktop-auto-update-environment.mjs'
import { desktopTargetPlatform } from './desktop-build-paths.mjs'
interface PackageManifest {
readonly name?: string
@@ -35,6 +37,8 @@ export interface DevelopmentProjectOptions {
readonly dependencyDir: string
/** Release identity written into the disposable project metadata. */
readonly release: DesktopRelease
/** Build target whose prepared payload the disposable project runs against. */
readonly target: DesktopAutoUpdateTarget
}
function readManifest(path: string): PackageManifest {
@@ -115,7 +119,7 @@ function mirrorWorkspaceDependencies(roots: readonly string[], destinationRoot:
/**
* Replace one disposable project with links to the current built workspace.
* @param options - Project destination, CLI package, and release identity.
* @param options - Project destination, CLI package, release identity, and build target.
* @returns the absolute project directory supplied by the caller.
*/
export function prepareDevelopmentProject(options: DevelopmentProjectOptions): string {
@@ -159,7 +163,7 @@ export function prepareDevelopmentProject(options: DevelopmentProjectOptions): s
return typeof manifest.version === 'string' ? [{ name, version: manifest.version, path: `node_modules/${name}` }] : []
})
const runtime: DesktopRuntimeDescriptor = { schemaVersion: 1, release: options.release,
platform: process.platform, arch: process.arch, sharedPackages, files: [] }
...desktopTargetPlatform(options.target), sharedPackages, files: [] }
writeFileSync(join(options.projectDir, DESKTOP_RUNTIME_FILE), `${JSON.stringify(runtime, undefined, 2)}\n`)
return options.projectDir
}
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url'
import { createDevelopmentProjectMetadata, createPluginProfile } from '../src/project-manager.ts'
import { removeOwnedDirectory } from '../src/owned-directory.ts'
import { DESKTOP_HOST_PROTOCOL_VERSION } from '../src/host-protocol.ts'
import { desktopTargetPlatform, developmentRuntimeDirectory, resolveDesktopBuildTarget } from './desktop-build-paths.mjs'
const repo = resolve(import.meta.dirname, '../../..')
const interactive = process.argv.includes('--interactive')
@@ -27,10 +28,11 @@ try {
nodeVersion: execFileSync(electron, ['-p', 'process.versions.node'],
{ encoding: 'utf8', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' }, windowsHide: true }).trim(),
pnpmVersion: pnpm.version, hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION }
const target = resolveDesktopBuildTarget()
createDevelopmentProjectMetadata(project, release)
createPluginProfile(profile)
await writeFile(join(project, 'desktop-runtime.json'), JSON.stringify({
schemaVersion: 1, release, platform: process.platform, arch: process.arch, files: [],
schemaVersion: 1, release, ...desktopTargetPlatform(target), files: [],
sharedPackages: ['@deepseek-ai/dsh', '@deepseek-ai/dsh-desktop-host']
.map(name => ({ name, version: manifest.version, path: `node_modules/${name}` })),
}))
@@ -43,7 +45,6 @@ try {
for (const owner of [application, project]) {
await symlink(join(repo, 'node_modules/.pnpm/node_modules'), join(owner, 'node_modules'), process.platform === 'win32' ? 'junction' : 'dir')
}
const target = `${process.platform === 'darwin' ? 'mac' : 'win'}-${process.arch}`
const targetRoot = join(application, '.desktop-build/targets', target)
await mkdir(targetRoot, { recursive: true })
await symlink(join(repo, 'apps/desktop/.desktop-build/targets', target, 'runtime'), join(targetRoot, 'runtime'),
@@ -61,7 +62,7 @@ try {
...(interactive ? ['--interactive'] : [])], {
cwd: root, env: { ...environment, DSH_HOME: join(root, 'home'), USERPROFILE: root, HOME: root,
TEMP: root, TMP: root, TMPDIR: root, DSH_WORKSPACE_UPDATE_ROOT: root, DSH_WORKSPACE_UPDATE_TOKEN: randomUUID(),
DSH_DESKTOP_OPEN_DEVTOOLS: '0' },
DSH_DESKTOP_PRIMARY_RUNTIME_DIR: developmentRuntimeDirectory(), DSH_DESKTOP_OPEN_DEVTOOLS: '0' },
// Hiding the GUI process suppresses its first window and can suspend renderer frame callbacks.
stdio: 'inherit', windowsHide: false,
})
+12 -2
View File
@@ -139,6 +139,14 @@ function runtimeResources(): RuntimeResources {
return { node, nodeBin, pnpm, dsh }
}
function developmentPrimaryRuntime(): string {
const directory = process.env.DSH_DESKTOP_PRIMARY_RUNTIME_DIR
if (directory === undefined || directory === '') {
throw new Error('dsh desktop: DSH_DESKTOP_PRIMARY_RUNTIME_DIR is required for an unpackaged launch')
}
return directory
}
function developmentHostInspectPort(enabled: boolean): number | undefined {
const configured = process.env.DSH_DESKTOP_HOST_INSPECT_PORT
if (!enabled || configured === undefined || configured === '') return undefined
@@ -289,6 +297,9 @@ async function main(): Promise<void> {
const resources = runtimeResources()
const paths = resolveDesktopPaths()
const development = !app.isPackaged
const primaryRuntime = development
? developmentPrimaryRuntime()
: join(process.resourcesPath, 'runtime', 'primary-runtime')
const activeProject = paths.profile
const manager = new DesktopProjectManager(paths, resources)
let quitting = false
@@ -370,8 +381,7 @@ async function main(): Promise<void> {
const hostInspectPort = developmentHostInspectPort(development)
const host = new DesktopHostProcess(resources.node, resources.dsh, activeProject,
hostInspectPort, process.env, onFailure,
development ? join(app.getAppPath(), '.desktop-build', 'targets', `${process.platform === 'darwin' ? 'mac' : 'win'}-${process.arch}`, 'runtime', 'primary-runtime')
: join(process.resourcesPath, 'runtime', 'primary-runtime'),
primaryRuntime,
resources, (next) => { platformView.setSession(next) })
return {
start: async () => {
@@ -2,6 +2,8 @@ import { join, sep } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
desktopTargetBuildPaths,
desktopTargetPlatform,
developmentRuntimeDirectory,
resolveDesktopBuildTarget,
} from '../scripts/desktop-build-paths.mjs'
@@ -39,6 +41,22 @@ describe('desktop build paths', () => {
expect(arm64.downloads).not.toContain(`${sep}targets${sep}`)
})
it('resolves the development primary runtime from the build target rather than the host architecture', () => {
expect(developmentRuntimeDirectory({}, 'darwin', 'arm64'))
.toContain(join('targets', 'mac-arm64', 'runtime', 'primary-runtime'))
expect(developmentRuntimeDirectory({}, 'darwin', 'x64'))
.toContain(join('targets', 'mac-x64', 'runtime', 'primary-runtime'))
expect(developmentRuntimeDirectory({}, 'win32', 'arm64'))
.toContain(join('targets', 'win-x64', 'runtime', 'primary-runtime'))
})
it('maps every target to the platform and architecture of the payload it prepares', () => {
expect(desktopTargetPlatform('mac-arm64')).toEqual({ platform: 'darwin', arch: 'arm64' })
expect(desktopTargetPlatform('mac-x64')).toEqual({ platform: 'darwin', arch: 'x64' })
expect(desktopTargetPlatform('win-x64')).toEqual({ platform: 'win32', arch: 'x64' })
expect(() => desktopTargetPlatform('linux-x64' as 'mac-x64')).toThrow(/unsupported target/u)
})
it('resolves environment overrides and rejects unsupported targets', () => {
expect(resolveDesktopBuildTarget({
DSH_DESKTOP_TARGET_PLATFORM: 'darwin',
@@ -46,9 +46,10 @@ describe('desktop development project', () => {
writeFileSync(join(host, 'lib/index.js'), '')
writeFileSync(join(dependency, 'package.json'), JSON.stringify({ name: 'unhoisted', version: '1.2.3' }))
symlinkSync(dependency, join(cli, 'node_modules/unhoisted'), process.platform === 'win32' ? 'junction' : 'dir')
const project = prepareDevelopmentProject({ projectDir: join(root, 'runtime'), cliDir: cli, hostDir: host, dependencyDir: hoisted, release: release() })
const project = prepareDevelopmentProject({ projectDir: join(root, 'runtime'), cliDir: cli, hostDir: host, dependencyDir: hoisted, release: release(), target: 'mac-arm64' })
expect(realpathSync(join(project, 'node_modules/unhoisted'))).toBe(realpathSync(dependency))
const descriptor = JSON.parse(readFileSync(join(project, 'desktop-runtime.json'), 'utf8')) as { sharedPackages: unknown[] }
const descriptor = JSON.parse(readFileSync(join(project, 'desktop-runtime.json'), 'utf8')) as { platform: string; arch: string; sharedPackages: unknown[] }
expect(descriptor).toMatchObject({ platform: 'darwin', arch: 'arm64' })
expect(descriptor.sharedPackages).toContainEqual({ name: 'unhoisted', version: '1.2.3', path: 'node_modules/unhoisted' })
})
@@ -76,6 +77,7 @@ describe('desktop development project', () => {
hostDir: host,
dependencyDir: dependencies,
release: release(),
target: 'win-x64',
})
expect(realpathSync(join(project, 'node_modules', '@deepseek-ai', 'dsh'))).toBe(realpathSync(cli))
expect(realpathSync(join(project, 'node_modules', '@deepseek-ai', 'dsh-desktop-host'))).toBe(realpathSync(host))
@@ -88,6 +90,8 @@ describe('desktop development project', () => {
}
expect(manifest.dependencies['@deepseek-ai/dsh']).toBe('1.2.3')
expect(manifest.dependencies['@deepseek-ai/dsh-desktop-host']).toBe('1.2.3')
const descriptor = JSON.parse(readFileSync(join(project, 'desktop-runtime.json'), 'utf8')) as { platform: string; arch: string }
expect(descriptor).toMatchObject({ platform: 'win32', arch: 'x64' })
const manager = new DesktopProjectManager(resolveDesktopPaths(join(root, 'home')), {
dsh: project,
})
@@ -115,6 +119,7 @@ describe('desktop development project', () => {
hostDir: host,
dependencyDir: dependencies,
release: release(),
target: 'mac-x64',
})).toThrow(/must be @deepseek-ai\/dsh@1\.2\.3/u)
})
})
+13 -1
View File
@@ -333,6 +333,7 @@ beforeEach(() => {
vi.spyOn(console, 'info').mockImplementation(() => {})
vi.stubEnv('DSH_DESKTOP_PNPM_ENTRY', 'test-pnpm')
vi.stubEnv('DSH_DESKTOP_DSH_DIR', 'test-runtime')
vi.stubEnv('DSH_DESKTOP_PRIMARY_RUNTIME_DIR', 'test-primary-runtime')
vi.stubGlobal('process', { ...process, platform: 'win32', arch: 'x64', resourcesPath: 'desktop-test-resources' })
vi.stubEnv('DSH_DESKTOP_HOST_INSPECT_PORT', undefined)
vi.stubEnv('DSH_DESKTOP_DEV_PROJECT_DIR', undefined)
@@ -1621,13 +1622,24 @@ describe('desktop main startup', () => {
harness.prepared.resolve()
await harness.hostStarted.promise
const project = join(harness.app.getAppPath(), '.desktop-build', 'development', 'project')
expect(harness.hosts[0]).toMatchObject({ node: process.execPath, runtime: project, profile: 'desktop-test-profile' })
expect(harness.hosts[0]).toMatchObject({ node: process.execPath, runtime: project,
primaryRuntime: 'test-primary-runtime', profile: 'desktop-test-profile' })
expect(harness.applyRelease).toHaveBeenCalledOnce()
harness.hosts[0]!.ready.resolve()
await harness.navigated.promise
expect(harness.dialog.showErrorBox).not.toHaveBeenCalled()
})
it('fails an unpackaged launch that receives no primary runtime directory', async () => {
harness.app.isPackaged = false
vi.stubEnv('DSH_DESKTOP_PRIMARY_RUNTIME_DIR', undefined)
await import('../src/main.ts')
await harness.dialogShown.promise
const options = harness.dialog.showMessageBox.mock.calls[0]![0] as MessageBoxOptions
expect(options.detail).toContain('DSH_DESKTOP_PRIMARY_RUNTIME_DIR is required for an unpackaged launch')
expect(harness.hosts).toHaveLength(0)
})
it('keeps startup errors in the existing window without a retry handler', async () => {
await import('../src/main.ts')
await harness.preparing.promise
+3
View File
@@ -98,6 +98,9 @@ describe.skipIf(!existsSync(builtHost))('built Desktop welcome flow', () => {
nodeVersion: process.versions.node,
hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
},
// This suite assembles a synthetic project on hosts that prepare no Desktop target, and
// nothing it exercises compares the descriptor's platform/arch outside packaging.
target: 'mac-x64',
})
cpSync(join(repository, 'packages/skill/skill-office/assets'), join(root, 'runtime/office-skills'), { recursive: true })
const nodeBin = join(root, 'runtime/primary-runtime/dependencies/node/bin')
@@ -148,6 +148,7 @@ it('starts the Host for welcome onboarding and opens the workspace on skip witho
vi.stubEnv('DSH_DESKTOP_NODE_BINARY', '/runtime/node')
vi.stubEnv('DSH_DESKTOP_PNPM_ENTRY', '/runtime/pnpm')
vi.stubEnv('DSH_DESKTOP_DSH_DIR', '/runtime/dsh')
vi.stubEnv('DSH_DESKTOP_PRIMARY_RUNTIME_DIR', '/runtime/primary-runtime')
vi.stubEnv('DSH_DESKTOP_HOST_INSPECT_PORT', undefined)
vi.stubEnv('DSH_DESKTOP_OPEN_DEVTOOLS', '0')
vi.stubEnv('DSH_DESKTOP_MANDATORY_UPDATE_CONFIG', undefined)