mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
Introduce the Electron desktop shell alongside the existing React renderer and local Bun server boundary. The migration keeps the DesktopHost contract explicit across Tauri, Electron, and browser runtimes while adding Electron main/preload services for dialogs, shell, notifications, updates, tray/window lifecycle, terminal, preview WebContentsView, app mode, and release/package validation.
The commit also carries the latest local main desktop command updates, including agent slash entries and hidden-by-default markdown thinking details, so the packaged Electron build matches the current main UX surface.
Constraint: React renderer, local Bun server, REST/WebSocket, and sidecar boundaries must remain reusable during the migration
Constraint: macOS dev packages are ad-hoc signed and cannot prove Developer ID notarization or Gatekeeper release launch
Rejected: Browser-only smoke validation | it cannot exercise native dialogs, keychain prompts, notification behavior, or packaged app startup
Confidence: medium
Scope-risk: broad
Directive: Do not remove Tauri host support until signed Electron release artifacts pass native OS smoke on macOS, Windows, and Linux
Tested: bun run check:desktop
Tested: cd desktop && bun run check:electron
Tested: CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron📦dir
Tested: bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron
Tested: Computer Use read packaged Electron app window at desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app
Not-tested: Developer ID signed/notarized Gatekeeper launch
Not-tested: Real OS notification click-to-session action
Not-tested: Windows and Linux packaged app smoke on real hosts
413 lines
26 KiB
TypeScript
413 lines
26 KiB
TypeScript
import { afterEach, describe, expect, test } from 'bun:test'
|
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join } from 'node:path'
|
|
import {
|
|
currentPackageSmokePlatform,
|
|
} from './current'
|
|
import {
|
|
inspectPackagedArtifacts,
|
|
parsePackageSmokeArgs,
|
|
} from './index'
|
|
|
|
function createRepoRoot() {
|
|
const rootDir = mkdtempSync(join(tmpdir(), 'package-smoke-'))
|
|
mkdirSync(join(rootDir, 'desktop'), { recursive: true })
|
|
writeFileSync(
|
|
join(rootDir, 'desktop', 'package.json'),
|
|
JSON.stringify({
|
|
name: 'claude-code-desktop',
|
|
version: '0.3.1',
|
|
build: {
|
|
productName: 'Claude Code Haha',
|
|
},
|
|
}, null, 2),
|
|
)
|
|
return rootDir
|
|
}
|
|
|
|
function writeFile(rootDir: string, relativePath: string, content = 'ok') {
|
|
const fullPath = join(rootDir, relativePath)
|
|
mkdirSync(dirname(fullPath), { recursive: true })
|
|
writeFileSync(fullPath, content)
|
|
}
|
|
|
|
const tempDirs: string[] = []
|
|
|
|
afterEach(() => {
|
|
for (const dir of tempDirs.splice(0)) {
|
|
rmSync(dir, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
describe('package smoke args', () => {
|
|
test('requires a supported platform value', () => {
|
|
expect(() => parsePackageSmokeArgs([])).toThrow('--platform')
|
|
expect(() => parsePackageSmokeArgs(['--platform', 'android'])).toThrow('macos|windows|linux')
|
|
expect(() => parsePackageSmokeArgs(['--platform', 'macos', '--package-kind', 'installer'])).toThrow('auto|dir|release')
|
|
expect(parsePackageSmokeArgs(['--platform', 'macos']).platform).toBe('macos')
|
|
expect(parsePackageSmokeArgs(['--platform', 'macos']).packageKind).toBe('auto')
|
|
expect(parsePackageSmokeArgs(['--platform', 'macos', '--package-kind', 'dir']).packageKind).toBe('dir')
|
|
expect(parsePackageSmokeArgs(['--platform', 'macos', '--require-macos-gatekeeper']).requireMacosGatekeeper).toBe(true)
|
|
})
|
|
|
|
test('maps host platforms to current package-smoke platforms', () => {
|
|
expect(currentPackageSmokePlatform('darwin')).toBe('macos')
|
|
expect(currentPackageSmokePlatform('win32')).toBe('windows')
|
|
expect(currentPackageSmokePlatform('linux')).toBe('linux')
|
|
expect(currentPackageSmokePlatform('freebsd')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('packaged artifact inspection', () => {
|
|
test('passes macOS bundle structure checks and records optional archive artifacts', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Info.plist')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/MacOS/Claude Code Haha')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-arm64.zip')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-arm64.zip.blockmap')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-arm64.dmg')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-arm64.dmg.blockmap')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/latest-mac.yml', [
|
|
'version: 0.3.1',
|
|
'files:',
|
|
' - url: Claude-Code-Haha-0.3.1-arm64.zip',
|
|
' - url: Claude-Code-Haha-0.3.1-arm64.dmg',
|
|
'path: Claude-Code-Haha-0.3.1-arm64.zip',
|
|
].join('\n'))
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'macos' })
|
|
|
|
expect(report.passed).toBe(true)
|
|
expect(report.verificationMode).toBe('bundle-structure')
|
|
expect(report.notes.join('\n')).toContain('No GUI launch was attempted.')
|
|
expect(report.optionalArtifacts.some((artifact) => artifact.path.endsWith('.zip'))).toBe(true)
|
|
expect(report.optionalArtifacts.some((artifact) => artifact.path.endsWith('.dmg'))).toBe(true)
|
|
expect(report.optionalArtifacts.some((artifact) => artifact.path.endsWith('latest-mac.yml'))).toBe(true)
|
|
expect(report.passedChecks.some((check) => check.label.includes('update metadata referenced artifact'))).toBe(true)
|
|
expect(report.passedChecks.some((check) => check.label.includes('macOS update artifact blockmap'))).toBe(true)
|
|
})
|
|
|
|
test('fails macOS archive checks when latest-mac.yml points at missing assets', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Info.plist')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/MacOS/Claude Code Haha')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude Code Haha-0.3.1-arm64-mac.zip')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/latest-mac.yml', 'path: Claude-Code-Haha-0.3.1-arm64-mac.zip\n')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'macos' })
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(report.missingChecks.some((check) => check.label.includes('update metadata referenced artifact'))).toBe(true)
|
|
})
|
|
|
|
test('fails macOS inspection when the installed updater metadata is missing from Resources', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Info.plist')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/MacOS/Claude Code Haha')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-arm64.zip')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/latest-mac.yml', [
|
|
'version: 0.3.1',
|
|
'files:',
|
|
' - url: Claude-Code-Haha-0.3.1-arm64.zip',
|
|
'path: Claude-Code-Haha-0.3.1-arm64.zip',
|
|
].join('\n'))
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'macos' })
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(report.missingChecks.some((check) => check.label === 'macOS app-update.yml')).toBe(true)
|
|
})
|
|
|
|
test('fails release inspection when an update artifact blockmap is missing', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Info.plist')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/MacOS/Claude Code Haha')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-arm64.zip')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/latest-mac.yml', 'path: Claude-Code-Haha-0.3.1-arm64.zip\n')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'macos', packageKind: 'release' })
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(report.missingChecks.some((check) => check.label.includes('macOS update artifact blockmap'))).toBe(true)
|
|
})
|
|
|
|
test('adds codesign diagnostics when macOS Gatekeeper assessment fails', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Info.plist')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/MacOS/Claude Code Haha')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, {
|
|
platform: 'macos',
|
|
packageKind: 'dir',
|
|
requireMacosGatekeeper: true,
|
|
hostPlatform: 'macos',
|
|
commandRunner: (command, args) => {
|
|
if (command.endsWith('/spctl')) {
|
|
return { status: 1, stdout: '', stderr: 'rejected\nsource=Unnotarized Developer ID\n' }
|
|
}
|
|
if (command.endsWith('/codesign')) {
|
|
return { status: 1, stdout: '', stderr: 'code object is not signed at all\n' }
|
|
}
|
|
if (command.endsWith('/xcrun') && args[0] === 'stapler') {
|
|
return { status: 65, stdout: '', stderr: 'The validate action failed!\n' }
|
|
}
|
|
return { status: 0, stdout: '', stderr: '' }
|
|
},
|
|
})
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(report.missingChecks.some((check) => check.label.includes('macOS Gatekeeper launch approval'))).toBe(true)
|
|
expect(report.notes.join('\n')).toContain('spctl Gatekeeper assessment exited with status 1')
|
|
expect(report.notes.join('\n')).toContain('codesign verification exited with status 1')
|
|
expect(report.notes.join('\n')).toContain('codesign signature details exited with status 1')
|
|
expect(report.notes.join('\n')).toContain('notarization ticket validation exited with status 65')
|
|
})
|
|
|
|
test('retries macOS Gatekeeper assessment with a raised file limit when spctl hits open-file limits', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Info.plist')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/MacOS/Claude Code Haha')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-apple-darwin')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper')
|
|
|
|
const commands: string[] = []
|
|
const report = await inspectPackagedArtifacts(rootDir, {
|
|
platform: 'macos',
|
|
packageKind: 'dir',
|
|
requireMacosGatekeeper: true,
|
|
hostPlatform: 'macos',
|
|
commandRunner: (command, args) => {
|
|
commands.push(`${command} ${args.join(' ')}`)
|
|
if (command.endsWith('/spctl')) {
|
|
return { status: 1, stdout: '', stderr: 'Too many open files\n' }
|
|
}
|
|
if (command.endsWith('/zsh')) {
|
|
return { status: 1, stdout: '', stderr: 'bundle format unrecognized, invalid, or unsuitable\n' }
|
|
}
|
|
if (command.endsWith('/codesign')) {
|
|
return { status: 0, stdout: '', stderr: 'valid on disk\n' }
|
|
}
|
|
if (command.endsWith('/xcrun') && args[0] === 'stapler') {
|
|
return { status: 65, stdout: '', stderr: 'does not have a ticket stapled to it\n' }
|
|
}
|
|
return { status: 0, stdout: '', stderr: '' }
|
|
},
|
|
})
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(commands.some(command => command.startsWith('/bin/zsh -lc ulimit -n'))).toBe(true)
|
|
expect(report.missingChecks.some((check) => check.label.includes('bundle format unrecognized'))).toBe(true)
|
|
expect(report.notes.join('\n')).toContain('retried with a raised file descriptor limit')
|
|
expect(report.notes.join('\n')).toContain('spctl Gatekeeper assessment exited with status 1: bundle format unrecognized')
|
|
})
|
|
|
|
test('treats Windows checks as static inspection on non-Windows hosts', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude Code Haha Setup 0.3.1.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude Code Haha Setup 0.3.1.exe.blockmap')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-pc-windows-msvc.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/win32-x64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/latest.yml', 'path: Claude Code Haha Setup 0.3.1.exe\n')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'windows' })
|
|
|
|
expect(report.passed).toBe(true)
|
|
expect(report.verificationMode).toBe('static-artifact')
|
|
expect(report.notes.join('\n')).toContain('not claim installer execution or app launch success')
|
|
})
|
|
|
|
test('passes Windows checks against the canonical build script output directory', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/Claude-Code-Haha-0.3.1-x64.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/Claude-Code-Haha-0.3.1-x64.exe.blockmap')
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/win-unpacked/resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/win-unpacked/resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/win-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-pc-windows-msvc.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/win32-x64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/windows-x64/latest.yml', 'path: Claude-Code-Haha-0.3.1-x64.exe\n')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, {
|
|
platform: 'windows',
|
|
packageKind: 'release',
|
|
artifactsDir: 'desktop/build-artifacts/windows-x64',
|
|
})
|
|
|
|
expect(report.passed).toBe(true)
|
|
expect(report.artifactsDir.endsWith('desktop/build-artifacts/windows-x64')).toBe(true)
|
|
})
|
|
|
|
test('passes Windows directory-only checks for electron-builder --dir output', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/Claude Code Haha.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-pc-windows-msvc.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/win32-x64/pty.node')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'windows', packageKind: 'dir' })
|
|
|
|
expect(report.passed).toBe(true)
|
|
expect(report.notes.join('\n')).toContain('directory-only development package')
|
|
expect(report.notes.join('\n')).toContain('Windows app-update.yml was not required')
|
|
})
|
|
|
|
test('does not treat the win-unpacked app executable as a Windows release installer', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/Claude Code Haha.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-pc-windows-msvc.exe')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/win-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/win32-x64/pty.node')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'windows', packageKind: 'release' })
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(report.missingChecks.some((check) => check.label.includes('.exe installer'))).toBe(true)
|
|
})
|
|
|
|
test('passes Linux checks against the canonical build script output directory', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/Claude-Code-Haha-0.3.1-x64.AppImage')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/Claude-Code-Haha-0.3.1-x64.AppImage.blockmap')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/claude-code-desktop_0.3.1_amd64.deb')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/linux-unpacked/resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/linux-unpacked/resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/linux-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-unknown-linux-gnu')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/linux-x64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-x64/latest-linux.yml', 'path: Claude-Code-Haha-0.3.1-x64.AppImage\n')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, {
|
|
platform: 'linux',
|
|
packageKind: 'release',
|
|
artifactsDir: 'desktop/build-artifacts/linux-x64',
|
|
})
|
|
|
|
expect(report.passed).toBe(true)
|
|
expect(report.artifactsDir.endsWith('desktop/build-artifacts/linux-x64')).toBe(true)
|
|
})
|
|
|
|
test('accepts Linux architecture-specific update metadata from arm64 builds', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/Claude-Code-Haha-0.3.1-arm64.AppImage')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/Claude-Code-Haha-0.3.1-arm64.AppImage.blockmap')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/claude-code-desktop_0.3.1_arm64.deb')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/linux-unpacked/resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/linux-unpacked/resources/app-update.yml')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/linux-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-unknown-linux-gnu')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/linux-arm64/pty.node')
|
|
writeFile(rootDir, 'desktop/build-artifacts/linux-arm64/latest-linux-arm64.yml', 'path: Claude-Code-Haha-0.3.1-arm64.AppImage\n')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, {
|
|
platform: 'linux',
|
|
packageKind: 'release',
|
|
artifactsDir: 'desktop/build-artifacts/linux-arm64',
|
|
})
|
|
|
|
expect(report.passed).toBe(true)
|
|
expect(report.optionalArtifacts.some((artifact) => artifact.path.endsWith('latest-linux-arm64.yml'))).toBe(true)
|
|
expect(report.passedChecks.some((check) => check.label.includes('update metadata referenced artifact'))).toBe(true)
|
|
})
|
|
|
|
test('passes Linux directory-only checks for electron-builder --dir output', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-unknown-linux-gnu')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/linux-x64/pty.node')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'linux', packageKind: 'dir' })
|
|
|
|
expect(report.passed).toBe(true)
|
|
expect(report.notes.join('\n')).toContain('directory-only development package')
|
|
expect(report.notes.join('\n')).toContain('Linux app-update.yml was not required')
|
|
})
|
|
|
|
test('fails Linux inspection when no packaged artifact is present', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'linux' })
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(report.missingChecks.some((check) => check.label.includes('linux packaged artifact'))).toBe(true)
|
|
})
|
|
|
|
test('fails Linux release inspection without linux-unpacked static resources', async () => {
|
|
const rootDir = createRepoRoot()
|
|
tempDirs.push(rootDir)
|
|
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-x64.AppImage')
|
|
writeFile(rootDir, 'desktop/build-artifacts/electron/latest-linux.yml', 'path: Claude-Code-Haha-0.3.1-x64.AppImage\n')
|
|
|
|
const report = await inspectPackagedArtifacts(rootDir, { platform: 'linux', packageKind: 'release' })
|
|
|
|
expect(report.passed).toBe(false)
|
|
expect(report.missingChecks.some((check) => check.label.includes('linux-unpacked'))).toBe(true)
|
|
})
|
|
})
|