Files
deepseek-harness/apps/desktop/scripts/test-windows-installer.mjs
T
winewill c47a2c16b4 feat(desktop): load platform packaging settings from local dotenv files
Load target-owned Windows and macOS release settings, validate them before
preparation, and provide a configuration-only check command. Share the local
settings with upload, installer tests, and manual macOS signature checks.

Exercise English and Chinese installer fixtures using visible UI labels and
cover registered installation paths with trailing separators.
2026-09-14 12:47:08 +08:00

94 lines
5.1 KiB
JavaScript

/** Builds an isolated native payload through the production NSIS configuration and exercises its UI. */
import { execFile } from 'node:child_process'
import { randomUUID } from 'node:crypto'
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { promisify } from 'node:util'
import { createWindowsTokenSigner, installWindowsNsisBootstrapSigner, scrubWindowsSigningEnvironment } from './windows-sign.mjs'
import { loadDesktopPackageEnvironment } from './desktop-package-environment.mjs'
if (process.platform !== 'win32' || process.arch !== 'x64') {
throw new Error('Installer UI checks require an interactive Windows x64 desktop')
}
const execute = promisify(execFile)
const appRoot = fileURLToPath(new URL('..', import.meta.url))
const require = createRequire(import.meta.url)
const { build, Platform, Arch } = require('electron-builder')
const { getMakeNsisPath } = require('app-builder-lib/out/toolsets/windows.js')
const guid = randomUUID()
const id = guid.replaceAll('-', '')
const productName = `Harness Installer Test ${id.slice(0, 8)}`
const outputRoot = join(appRoot, '.desktop-build', 'installer-tests')
await mkdir(outputRoot, { recursive: true })
const output = await mkdtemp(join(outputRoot, 'run-'))
const payload = join(output, 'payload')
await mkdir(join(payload, 'resources'), { recursive: true })
const previousEnvironment = { ...process.env }
const signingEnvironment = process.argv.includes('--signed') ? loadDesktopPackageEnvironment('win32') : {}
const sign = process.argv.includes('--signed') ? createWindowsTokenSigner({
certificateFile: signingEnvironment.DSH_DESKTOP_WINDOWS_CER_FILE,
signTool: signingEnvironment.DSH_DESKTOP_WINDOWS_SIGNTOOL,
tokenPin: signingEnvironment.DSH_DESKTOP_WINDOWS_TOKEN_PIN,
keyContainer: signingEnvironment.DSH_DESKTOP_WINDOWS_KEY_CONTAINER,
}) : undefined
try {
Object.assign(process.env, {
DSH_DESKTOP_APP_ID: `com.deepseek.harness.installertest.n${id}`,
DSH_DESKTOP_TARGET_PLATFORM: 'win32', DSH_DESKTOP_TARGET_ARCH: 'x64',
DSH_DESKTOP_UNSIGNED: '1', CSC_IDENTITY_AUTO_DISCOVERY: 'false', ELECTRON_BUILDER_7Z_FILTER: 'BCJ',
})
const { createElectronBuilderConfig } = await import('../electron-builder.config.mjs')
const childOptions = { env: scrubWindowsSigningEnvironment(process.env), windowsHide: true, maxBuffer: 8 * 1024 * 1024 }
await execute('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
join(appRoot, 'scripts', 'prepare-windows-installer.ps1'), '-OutputDirectory', join(output, 'ui'), '-CompileProgressOnly'], childOptions)
const progressTest = join(output, 'ui', 'progress-test.exe')
if (sign) {
await sign({ path: progressTest, hash: 'sha256', isNest: false })
await sign({ path: join(output, 'ui', 'window-frame.dll'), hash: 'sha256', isNest: false })
installWindowsNsisBootstrapSigner({ sign })
}
await execute(progressTest, [], childOptions)
const payloadSource = join(output, 'payload.nsi')
await writeFile(payloadSource, `Unicode true
RequestExecutionLevel user
ManifestDPIAware true
Name "${productName}"
OutFile "${join(payload, `${productName}.exe`)}"
SilentInstall silent
Section
FileOpen $0 "$EXEDIR\\launched.txt" w
FileWrite $0 "launched"
FileClose $0
MessageBox MB_OK "Installer test application is running."
SectionEnd
`)
const compiler = await getMakeNsisPath()
await execute(compiler.path, ['/V2', payloadSource], { ...childOptions, env: { ...childOptions.env, ...compiler.env } })
if (sign) await sign({ path: join(payload, `${productName}.exe`), hash: 'sha256', isNest: false })
const include = join(output, 'include.nsh')
await writeFile(include, `!define INSTALLER_BUILD_DIR "${join(output, 'ui')}"\n!include "${join(appRoot, 'scripts', 'installer.nsh')}"\n`)
const config = createElectronBuilderConfig()
if (sign) {
config.win.forceCodeSigning = true
config.win.signtoolOptions.sign = sign
}
for (const language of ['en_US', 'zh_CN']) {
const languageOutput = join(output, language)
await mkdir(languageOutput)
await build({ projectDir: appRoot, prepackaged: payload, targets: Platform.WINDOWS.createTarget(['nsis'], Arch.x64), publish: 'never',
config: { ...config, productName, artifactName: 'installer-test.exe', directories: { output: languageOutput },
nsis: { ...config.nsis, guid, include, installerLanguages: [language] }, beforeBuild: undefined, afterPack: undefined, afterSign: undefined, artifactBuildCompleted: undefined },
})
const result = await execute('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
join(appRoot, 'tests', 'windows-installer-smoke.ps1'), '-Installer', join(languageOutput, 'installer-test.exe'),
'-ProductName', productName, '-RegistryKey', guid, '-OutputDirectory', languageOutput], childOptions)
process.stdout.write(`${language}\n${result.stdout}`)
}
} finally {
for (const name of Object.keys(process.env)) if (!(name in previousEnvironment)) delete process.env[name]
Object.assign(process.env, previousEnvironment)
process.stdout.write(`Installer test artifacts: ${output}\n`)
}