Files
deepseek-harness/apps/desktop/scripts/dev.ts
T
mektpoy 80e4da0e3d fix(desktop): take the development project target as an explicit input
`prepareDevelopmentProject()` resolved the build target from the host
platform and architecture, so a Linux host threw `unsupported target
linux-x64` while writing the runtime descriptor. Pass the target in
instead: the launcher supplies the target it prepared, and isolated tests
supply a fixed supported target.

Extract `desktopTargetPlatform()` into desktop-build-paths.mjs so the
target-to-payload mapping has one home, and use it for the qualification
runner, which previously derived both its target directory and its
descriptor from the host architecture.

Correct the README limitation to name the module that now performs the
architecture check.
2026-09-23 16:03:17 +08:00

130 lines
5.7 KiB
TypeScript

/** Build and launch the unpackaged Electron shell against the current workspace. */
import { spawn, execFileSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
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'
const APP_ROOT = resolve(import.meta.dirname, '..')
const REPOSITORY_ROOT = resolve(APP_ROOT, '..', '..')
const BUILD_ROOT = join(APP_ROOT, '.desktop-build')
const DEVELOPMENT_ROOT = join(BUILD_ROOT, 'development')
interface PackageManifest {
readonly version?: string
}
function packageVersion(path: string, subject: string): string {
const manifest = JSON.parse(readFileSync(path, 'utf8')) as PackageManifest
if (typeof manifest.version !== 'string') throw new Error(`desktop development: ${subject} has no version`)
return manifest.version
}
function debugPort(name: string, fallback: number): number {
const value = process.env[name]
if (value === undefined || value === '') return fallback
const port = Number(value)
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
throw new Error(`desktop development: ${name} must be an integer from 1 through 65535`)
}
return port
}
async function run(command: string, args: readonly string[], cwd: string, environment = process.env): Promise<void> {
await new Promise<void>((resolvePromise, reject) => {
const child = spawn(command, args, { cwd, env: environment, stdio: 'inherit' })
child.once('error', reject)
child.once('exit', (code, signal) => {
if (code === 0) resolvePromise()
else reject(new Error(`desktop development: ${args.join(' ')} exited with ${String(code ?? signal)}`))
})
})
}
async function runPackageScript(script: string, cwd: string): Promise<void> {
const packageManager = process.env.npm_execpath
if (packageManager === undefined || packageManager === '') {
throw new Error('desktop development: invoke this launcher through pnpm run dev:desktop or start:desktop')
}
await run(process.execPath, [packageManager, 'run', script], cwd)
}
async function launchElectron(): Promise<void> {
const require = createRequire(import.meta.url)
const electron: unknown = require('electron')
if (typeof electron !== 'string') throw new Error('desktop development: electron executable is unavailable')
const mainPort = debugPort('DSH_DESKTOP_MAIN_INSPECT_PORT', 9229)
const rendererPort = debugPort('DSH_DESKTOP_RENDERER_DEBUG_PORT', 9222)
const hostPort = debugPort('DSH_DESKTOP_HOST_INSPECT_PORT', 9230)
const home = resolve(process.env.DSH_HOME ?? join(DEVELOPMENT_ROOT, 'home'))
const userData = join(DEVELOPMENT_ROOT, 'electron-user-data')
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',
}
console.log(`desktop development: DSH_HOME=${home}`)
console.log(`desktop development: inspectors main=${String(mainPort)}, renderer=${String(rendererPort)}, host=${String(hostPort)}`)
if (process.platform === 'darwin') {
const executable = prepareDevelopmentApp({ electron, appRoot: APP_ROOT, directory: DEVELOPMENT_ROOT, home, userData,
mainPort, rendererPort, hostPort, openDevtools: environment.DSH_DESKTOP_OPEN_DEVTOOLS! })
await run(executable, [], APP_ROOT, environment)
return
}
await run(electron, [
`--inspect=127.0.0.1:${String(mainPort)}`,
`--remote-debugging-port=${String(rendererPort)}`,
`--user-data-dir=${userData}`,
APP_ROOT,
], APP_ROOT, environment)
}
async function main(): Promise<void> {
const { values } = parseArgs({ options: { 'skip-build': { type: 'boolean', default: false } } })
if (!values['skip-build']) {
await runPackageScript('build', REPOSITORY_ROOT)
await runPackageScript('build', APP_ROOT)
}
for (const path of [
join(APP_ROOT, 'lib', 'main.js'),
join(REPOSITORY_ROOT, 'apps', 'desktop-host', 'lib', 'index.js'),
]) {
if (!existsSync(path)) throw new Error(`desktop development: missing built artifact ${path}`)
}
const version = packageVersion(join(APP_ROOT, 'package.json'), 'desktop package')
const pnpmVersion = packageVersion(join(APP_ROOT, 'node_modules', 'pnpm', 'package.json'), 'pnpm package')
const release: DesktopRelease = {
schemaVersion: 1,
version,
hostProtocolVersion: DESKTOP_HOST_PROTOCOL_VERSION,
nodeVersion: execFileSync(createRequire(import.meta.url)('electron') as string, ['-p', 'process.versions.node'],
{ encoding: 'utf8', env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } }).trim(),
pnpmVersion,
}
prepareDevelopmentProject({
projectDir: join(DEVELOPMENT_ROOT, 'project'),
cliDir: join(REPOSITORY_ROOT, 'apps', 'cli'),
hostDir: join(REPOSITORY_ROOT, 'apps', 'desktop-host'),
dependencyDir: join(REPOSITORY_ROOT, 'node_modules', '.pnpm', 'node_modules'),
release,
target: resolveDesktopBuildTarget(),
})
await preparePrimaryRuntime()
await launchElectron()
}
await main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error)
process.exitCode = 1
})