Files
claude-code-haha/scripts/quality-gate/package-smoke/index.test.ts
T

958 lines
55 KiB
TypeScript

import { afterEach, describe, expect, test } from 'bun:test'
import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { basename, dirname, join } from 'node:path'
import {
currentPackageSmokeArch,
currentPackageSmokePlatform,
} from './current'
import {
inspectPackagedArtifacts as inspectPackage,
parseCodesignMetadata,
parseMachOMinimumMacosVersions,
parsePackageSmokeArgs,
} from './index'
// These fixtures contain synthetic Mach-O headers, not runnable executables.
// Resource execution has dedicated cases below with an explicit runner.
function inspectPackagedArtifacts(rootDir: string, options: Parameters<typeof inspectPackage>[1]) {
return inspectPackage(rootDir, {
hostPlatform: 'linux',
...(options.platform === 'windows' ? {
commandRunner: (command: string, args: string[]) => {
expect(command).toBe('bun')
expect(args[0]).toBe('run')
expect(args[1]).toBe(join(rootDir, 'desktop', 'scripts', 'assert-windows-icon.ts'))
const branded = readFileSync(args[2], 'utf8') === 'synthetic branded executable'
return { status: branded ? 0 : 1, stderr: branded ? '' : 'The Windows executable icon differs from the application ICO' }
},
} : {}),
...options,
})
}
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: string | Uint8Array = 'ok') {
const fullPath = join(rootDir, relativePath)
mkdirSync(dirname(fullPath), { recursive: true })
writeFileSync(fullPath, content)
const fileName = basename(fullPath)
if (fileName.startsWith('claude-sidecar-')) {
const ripgrepName = fileName.endsWith('.exe') ? 'rg.exe' : 'rg'
writeFileSync(join(dirname(fullPath), ripgrepName), content)
writeFileSync(
join(dirname(fullPath), 'ripgrep-manifest.json'),
JSON.stringify({ targetTriple: fileName.replace(/^claude-sidecar-/, '').replace(/\.exe$/, '') }),
)
const licensesDir = join(dirname(fullPath), 'ripgrep-licenses')
mkdirSync(licensesDir, { recursive: true })
for (const licenseName of ['COPYING', 'LICENSE-MIT', 'UNLICENSE']) {
writeFileSync(join(licensesDir, licenseName), content)
}
if (fileName.includes('apple-darwin')) {
const helperRoot = join(dirname(fullPath), 'cc-haha-computer-use.app', 'Contents')
mkdirSync(join(helperRoot, 'MacOS'), { recursive: true })
writeFileSync(
join(helperRoot, 'Info.plist'),
'<plist><dict><key>LSMinimumSystemVersion</key><string>14.4</string></dict></plist>',
)
writeFileSync(join(helperRoot, 'MacOS', 'cc-haha-computer-use'), content)
const sequence = join(helperRoot, 'Resources', 'cu-helper_cc-haha-computer-use.bundle', 'LensSequence')
mkdirSync(sequence, { recursive: true })
writeFileSync(join(sequence, 'README.md'), 'Optional cursor frames are absent in this fixture.')
}
}
}
function thinMachO(arch: 'arm64' | 'x64', minimum = '14.4') {
const [major, minor, patch = 0] = minimum.split('.').map(Number)
const encodedMinimum = (major << 16) | (minor << 8) | patch
const bytes = Buffer.alloc(56)
bytes.writeUInt32LE(0xfeedfacf, 0)
bytes.writeUInt32LE(arch === 'arm64' ? 0x0100000c : 0x01000007, 4)
bytes.writeUInt32LE(2, 12)
bytes.writeUInt32LE(1, 16)
bytes.writeUInt32LE(24, 20)
bytes.writeUInt32LE(0x32, 32)
bytes.writeUInt32LE(24, 36)
bytes.writeUInt32LE(1, 40)
bytes.writeUInt32LE(encodedMinimum, 44)
bytes.writeUInt32LE(15 << 16, 48)
return bytes
}
function writeWindowsBranding(rootDir: string, unpackedDir: string) {
// The root-only policy lane mocks the parser process; the desktop parser
// suite separately verifies real synthetic PE resource tables.
const icon = 'synthetic application ICO'
writeFile(rootDir, `${unpackedDir}/Claude Code Haha.exe`, 'synthetic branded executable')
writeFile(rootDir, `${unpackedDir}/resources/app-icon.ico`, icon)
writeFile(rootDir, 'desktop/src-tauri/icons/icon.ico', icon)
}
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', 'windows', '--arch', 'ia32'])).toThrow('x64|arm64')
expect(() => parsePackageSmokeArgs(['--platform', 'macos', '--package-kind', 'installer'])).toThrow('auto|dir|release')
expect(parsePackageSmokeArgs(['--platform', 'macos']).platform).toBe('macos')
expect(parsePackageSmokeArgs(['--platform', 'windows', '--arch', 'arm64']).arch).toBe('arm64')
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()
expect(currentPackageSmokeArch('arm64')).toBe('arm64')
expect(currentPackageSmokeArch('x64')).toBe('x64')
expect(currentPackageSmokeArch('ia32')).toBeNull()
})
test('reads the helper deployment target from the Mach-O load commands', () => {
expect(parseMachOMinimumMacosVersions(thinMachO('arm64', '14.4'))).toEqual(['14.4'])
expect(parseMachOMinimumMacosVersions(thinMachO('x64', '14.0'))).toEqual(['14.0'])
expect(parseMachOMinimumMacosVersions(Buffer.from('not Mach-O'))).toEqual([])
})
test('reads the identity fields required by Computer Use client attestation', () => {
expect(parseCodesignMetadata([
'Identifier=dev.cchaha.cu-helper',
'Authority=Developer ID Application: Example (TEAM123456)',
'Authority=Developer ID Certification Authority',
'Timestamp=Sep 1, 2026 at 18:43:53',
'TeamIdentifier=TEAM123456',
].join('\n'))).toEqual({
identifier: 'dev.cchaha.cu-helper',
authority: 'Developer ID Application: Example (TEAM123456)',
team: 'TEAM123456',
timestamp: 'Sep 1, 2026 at 18:43:53',
})
})
})
describe('final macOS helper cursor resource verification', () => {
const executionLabel = 'macOS relocated cu-helper cursor resource execution'
const structureLabel = 'macOS cu-helper cursor resource directory'
const sequenceRelative = 'Contents/Resources/cu-helper_cc-haha-computer-use.bundle/LensSequence'
function fixture(arch: 'arm64' | 'x64' = 'arm64') {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
const app = 'desktop/build-artifacts/electron/mac/Claude Code Haha.app'
const resources = `${app}/Contents/Resources`
const binaries = `${resources}/app.asar.unpacked/src-tauri/binaries`
const triple = arch === 'arm64' ? 'aarch64-apple-darwin' : 'x86_64-apple-darwin'
const pty = `${resources}/app.asar.unpacked/node_modules/node-pty`
writeFile(rootDir, `${app}/Contents/Info.plist`)
writeFile(rootDir, `${app}/Contents/MacOS/Claude Code Haha`, thinMachO(arch))
writeFile(rootDir, `${resources}/app.asar`)
writeFile(rootDir, `${resources}/app.asar.unpacked/dist/index.html`)
writeFile(rootDir, `${binaries}/claude-sidecar-${triple}`, thinMachO(arch))
writeFile(rootDir, `${pty}/package.json`)
writeFile(rootDir, `${pty}/prebuilds/darwin-${arch}/pty.node`, thinMachO(arch))
writeFile(rootDir, `${pty}/prebuilds/darwin-${arch}/spawn-helper`, thinMachO(arch))
const helper = join(rootDir, binaries, 'cc-haha-computer-use.app')
return { rootDir, helper, sequence: join(helper, sequenceRelative) }
}
test.each(['arm64', 'x64'] as const)('executes the %s final helper from a disposable standalone path with isolated state', async arch => {
const source = fixture(arch)
let temporaryRoot = ''
const report = await inspectPackage(source.rootDir, {
platform: 'macos', arch, packageKind: 'dir', hostPlatform: 'macos', hostArch: arch,
commandRunner: (command, args, options) => {
expect(args).toEqual(['--probe-cursor-resources'])
expect(command.startsWith(source.helper)).toBe(false)
expect(command.replaceAll('\\', '/')).toContain('Relocated Helper.app/Contents/MacOS/cc-haha-computer-use')
expect(options?.timeout).toBe(10_000)
expect(options?.maxBuffer).toBe(1024 * 1024)
temporaryRoot = options!.cwd
expect(options?.env.HOME).toBe(join(temporaryRoot, 'home'))
expect(options?.env.CFFIXED_USER_HOME).toBe(options?.env.HOME)
expect(options?.env.CLAUDE_CONFIG_DIR).toBe(join(temporaryRoot, 'home', '.claude'))
expect(options?.env.OPENAI_API_KEY).toBeUndefined()
const resourceDirectory = join(dirname(dirname(command)), 'Resources/cu-helper_cc-haha-computer-use.bundle/LensSequence')
expect(existsSync(join(resourceDirectory, 'README.md'))).toBe(true)
return { status: 0, stdout: JSON.stringify({ resourceDirectory, frameCount: 0, proceduralFallback: true }) }
},
})
expect(report.passed).toBe(true)
expect(report.passedChecks.some(check => check.label === executionLabel)).toBe(true)
expect(existsSync(temporaryRoot)).toBe(false)
expect(existsSync(source.sequence)).toBe(true)
})
test.each(['missing', 'file', 'external-symlink', 'external-frame-symlink'] as const)('rejects a final package with %s cursor resources before any execution', async mode => {
const source = fixture()
let executed = false
if (mode !== 'external-frame-symlink') rmSync(source.sequence, { recursive: true })
if (mode === 'file') writeFileSync(source.sequence, 'not a directory')
if (mode === 'external-symlink') symlinkSync(source.rootDir, source.sequence, 'dir')
if (mode === 'external-frame-symlink') {
const external = join(source.rootDir, 'build-tree-frame.png')
writeFileSync(external, 'outside the packaged helper')
symlinkSync(external, join(source.sequence, 'frame_0.png'))
}
const report = await inspectPackage(source.rootDir, {
platform: 'macos', arch: 'arm64', packageKind: 'dir', hostPlatform: 'macos', hostArch: 'arm64',
commandRunner: () => { executed = true; throw new Error('must not run an invalid package') },
})
expect(executed).toBe(false)
expect(report.passed).toBe(false)
expect(report.missingChecks.some(check => check.label === structureLabel)).toBe(true)
expect(report.passedChecks.some(check => check.label === executionLabel)).toBe(false)
})
test.each(['crash', 'invalid-json', 'external-directory', 'invalid-frames'] as const)('fails the final package when the resource diagnostic reports %s', async mode => {
const source = fixture()
let temporaryRoot = ''
const report = await inspectPackage(source.rootDir, {
platform: 'macos', arch: 'arm64', packageKind: 'dir', hostPlatform: 'macos', hostArch: 'arm64',
commandRunner: (command, _args, options) => {
temporaryRoot = options!.cwd
if (mode === 'crash') return { status: null, stderr: 'terminated by signal' }
if (mode === 'invalid-json') return { status: 0, stdout: 'not JSON' }
const resourceDirectory = mode === 'external-directory' ? source.sequence
: join(dirname(dirname(command)), 'Resources/cu-helper_cc-haha-computer-use.bundle/LensSequence')
return { status: 0, stdout: JSON.stringify({
resourceDirectory, frameCount: mode === 'invalid-frames' ? -1 : 0, proceduralFallback: true,
}) }
},
})
expect(report.passed).toBe(false)
expect(report.missingChecks.some(check => check.label === executionLabel)).toBe(true)
expect(existsSync(temporaryRoot)).toBe(false)
})
test.each([
{ platform: 'macos', arch: 'arm64', note: 'target x86_64, host arm64' },
{ platform: 'linux', arch: 'x64', note: 'host platform is linux' },
])('records a skipped execution on $platform/$arch without counting it as a passed probe', async host => {
const source = fixture('x64')
let executed = false
const report = await inspectPackage(source.rootDir, {
platform: 'macos', arch: 'x64', packageKind: 'dir', hostPlatform: host.platform, hostArch: host.arch,
commandRunner: () => { executed = true; return { status: 1 } },
})
expect(report.passed).toBe(true)
expect(executed).toBe(false)
expect(report.passedChecks.some(check => check.label === executionLabel)).toBe(false)
expect(report.notes.join('\n')).toContain(`SKIPPED: ${executionLabel}`)
expect(report.notes.join('\n')).toContain(host.note)
})
})
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/dist/index.html')
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)
expect(report.passedChecks.some((check) => check.label === 'macOS unpacked H5 shell')).toBe(true)
})
test('fails macOS inspection when bundled ripgrep is missing', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
const appRoot = 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app'
const sidecarRoot = `${appRoot}/Contents/Resources/app.asar.unpacked/src-tauri/binaries`
writeFile(rootDir, `${appRoot}/Contents/Info.plist`)
writeFile(rootDir, `${appRoot}/Contents/MacOS/Claude Code Haha`)
writeFile(rootDir, `${appRoot}/Contents/Resources/app.asar`)
writeFile(rootDir, `${appRoot}/Contents/Resources/app.asar.unpacked/dist/index.html`)
writeFile(rootDir, `${sidecarRoot}/claude-sidecar-aarch64-apple-darwin`)
writeFile(rootDir, `${appRoot}/Contents/Resources/app.asar.unpacked/node_modules/node-pty/package.json`)
writeFile(rootDir, `${appRoot}/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/pty.node`)
writeFile(rootDir, `${appRoot}/Contents/Resources/app.asar.unpacked/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper`)
rmSync(join(rootDir, sidecarRoot, 'rg'))
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'macos',
arch: 'arm64',
packageKind: 'dir',
})
expect(report.passed).toBe(false)
expect(report.missingChecks.some(
check => check.label === 'macOS bundled ripgrep binary',
)).toBe(true)
})
test('fails closed when an arm64 package contains an x64 cu-helper', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
const appRoot = 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app'
const resources = `${appRoot}/Contents/Resources`
const sidecarRoot = `${resources}/app.asar.unpacked/src-tauri/binaries`
const nodePtyRoot = `${resources}/app.asar.unpacked/node_modules/node-pty`
writeFile(rootDir, `${appRoot}/Contents/Info.plist`)
writeFile(rootDir, `${appRoot}/Contents/MacOS/Claude Code Haha`, thinMachO('arm64'))
writeFile(rootDir, `${resources}/app.asar`)
writeFile(rootDir, `${resources}/app.asar.unpacked/dist/index.html`)
writeFile(rootDir, `${sidecarRoot}/claude-sidecar-aarch64-apple-darwin`, thinMachO('arm64'))
writeFile(rootDir, `${nodePtyRoot}/package.json`)
writeFile(rootDir, `${nodePtyRoot}/prebuilds/darwin-arm64/pty.node`, thinMachO('arm64'))
writeFile(rootDir, `${nodePtyRoot}/prebuilds/darwin-arm64/spawn-helper`, thinMachO('arm64'))
const validReport = await inspectPackagedArtifacts(rootDir, {
platform: 'macos',
arch: 'arm64',
packageKind: 'dir',
})
expect(validReport.passed).toBe(true)
writeFile(
rootDir,
`${sidecarRoot}/cc-haha-computer-use.app/Contents/MacOS/cc-haha-computer-use`,
thinMachO('x64'),
)
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'macos',
arch: 'arm64',
packageKind: 'dir',
})
expect(report.passed).toBe(false)
expect(report.missingChecks.some(
check => check.label === 'macOS arm64 cu-helper Mach-O architecture',
)).toBe(true)
expect(report.notes.join('\n')).toContain('expected arm64, found x86_64')
})
test('fails closed when the helper Mach-O deployment target drifts below 14.4', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
const appRoot = 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app'
const resources = `${appRoot}/Contents/Resources`
const sidecarRoot = `${resources}/app.asar.unpacked/src-tauri/binaries`
const nodePtyRoot = `${resources}/app.asar.unpacked/node_modules/node-pty`
writeFile(rootDir, `${appRoot}/Contents/Info.plist`)
writeFile(rootDir, `${appRoot}/Contents/MacOS/Claude Code Haha`, thinMachO('arm64'))
writeFile(rootDir, `${resources}/app.asar`)
writeFile(rootDir, `${resources}/app.asar.unpacked/dist/index.html`)
writeFile(rootDir, `${sidecarRoot}/claude-sidecar-aarch64-apple-darwin`, thinMachO('arm64'))
writeFile(rootDir, `${nodePtyRoot}/package.json`)
writeFile(rootDir, `${nodePtyRoot}/prebuilds/darwin-arm64/pty.node`, thinMachO('arm64'))
writeFile(rootDir, `${nodePtyRoot}/prebuilds/darwin-arm64/spawn-helper`, thinMachO('arm64'))
writeFile(
rootDir,
`${sidecarRoot}/cc-haha-computer-use.app/Contents/MacOS/cc-haha-computer-use`,
thinMachO('arm64', '14.0'),
)
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'macos',
arch: 'arm64',
packageKind: 'dir',
})
expect(report.passed).toBe(false)
expect(report.missingChecks.some(
check => check.label === 'macOS cu-helper Mach-O deployment target (14.4)',
)).toBe(true)
expect(report.notes.join('\n')).toContain('expected 14.4, found 14.0')
})
test('fails macOS inspection when the H5 shell is not unpacked for the sidecar', 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')
const report = await inspectPackagedArtifacts(rootDir, { platform: 'macos', packageKind: 'dir' })
expect(report.passed).toBe(false)
expect(report.missingChecks.some((check) => check.label === 'macOS unpacked H5 shell')).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('requires one Developer ID signer across host, sidecar, and helper', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
const appRoot = 'desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app'
const resources = `${appRoot}/Contents/Resources`
const sidecarRoot = `${resources}/app.asar.unpacked/src-tauri/binaries`
const nodePtyRoot = `${resources}/app.asar.unpacked/node_modules/node-pty`
writeFile(rootDir, `${appRoot}/Contents/Info.plist`)
writeFile(rootDir, `${appRoot}/Contents/MacOS/Claude Code Haha`, thinMachO('arm64'))
writeFile(rootDir, `${resources}/app.asar`)
writeFile(rootDir, `${resources}/app.asar.unpacked/dist/index.html`)
writeFile(rootDir, `${sidecarRoot}/claude-sidecar-aarch64-apple-darwin`, thinMachO('arm64'))
writeFile(rootDir, `${nodePtyRoot}/package.json`)
writeFile(rootDir, `${nodePtyRoot}/prebuilds/darwin-arm64/pty.node`, thinMachO('arm64'))
writeFile(rootDir, `${nodePtyRoot}/prebuilds/darwin-arm64/spawn-helper`, thinMachO('arm64'))
const inspect = (sidecarAuthority: string) => inspectPackagedArtifacts(rootDir, {
platform: 'macos',
arch: 'arm64',
packageKind: 'dir',
requireMacosGatekeeper: true,
hostPlatform: 'macos',
commandRunner: (command, args) => {
if (command.endsWith('/spctl')) return { status: 0, stdout: 'accepted', stderr: '' }
if (command.endsWith('/codesign') && args[0] === '--verify') {
return { status: 0, stdout: '', stderr: '' }
}
if (command.endsWith('/codesign') && args[0] === '-dv') {
const target = args.at(-1) ?? ''
const isSidecar = target.includes('claude-sidecar-')
const identifier = target.endsWith('cc-haha-computer-use.app')
? 'dev.cchaha.cu-helper'
: isSidecar
? 'com.claude-code-haha.desktop.sidecar'
: 'com.claude-code-haha.desktop'
const authority = isSidecar
? sidecarAuthority
: 'Developer ID Application: Example (TEAM123456)'
return {
status: 0,
stdout: '',
stderr: [
`Identifier=${identifier}`,
`Authority=${authority}`,
'Timestamp=Sep 1, 2026 at 18:43:53',
'TeamIdentifier=TEAM123456',
].join('\n'),
}
}
return { status: 0, stdout: '', stderr: '' }
},
})
const valid = await inspect('Developer ID Application: Example (TEAM123456)')
expect(valid.passedChecks.some(
check => check.label === 'macOS Computer Use signing attestation chain',
)).toBe(true)
const mismatched = await inspect('Developer ID Application: Other (TEAM123456)')
expect(mismatched.passed).toBe(false)
expect(mismatched.notes.join('\n')).toContain('mismatched Developer ID authority/team')
})
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')
writeWindowsBranding(rootDir, 'desktop/build-artifacts/electron/win-unpacked')
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')
writeWindowsBranding(rootDir, 'desktop/build-artifacts/windows-x64/win-unpacked')
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'windows',
arch: 'x64',
packageKind: 'release',
artifactsDir: 'desktop/build-artifacts/windows-x64',
})
expect(report.passed).toBe(true)
expect(report.artifactsDir.replaceAll('\\', '/').endsWith('desktop/build-artifacts/windows-x64')).toBe(true)
})
test('passes Windows arm64 checks only when arm64 sidecar and node-pty native module are present', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/Claude-Code-Haha-0.3.1-arm64.exe')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/Claude-Code-Haha-0.3.1-arm64.exe.blockmap')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app-update.yml')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-pc-windows-msvc.exe')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/win32-arm64/pty.node')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/latest.yml', 'path: Claude-Code-Haha-0.3.1-arm64.exe\n')
writeWindowsBranding(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked')
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'windows',
arch: 'arm64',
packageKind: 'release',
artifactsDir: 'desktop/build-artifacts/windows-arm64',
})
expect(report.passed).toBe(true)
expect(report.passedChecks.some((check) => check.label === 'Windows arm64 unpacked sidecar binary')).toBe(true)
expect(report.passedChecks.some((check) => check.label === 'Windows arm64 node-pty native module')).toBe(true)
})
test('fails Windows arm64 checks when the package only contains x64 native files', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/Claude-Code-Haha-0.3.1-arm64.exe')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/Claude-Code-Haha-0.3.1-arm64.exe.blockmap')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app-update.yml')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-x86_64-pc-windows-msvc.exe')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/win32-x64/pty.node')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/latest.yml', 'path: Claude-Code-Haha-0.3.1-arm64.exe\n')
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'windows',
arch: 'arm64',
packageKind: 'release',
artifactsDir: 'desktop/build-artifacts/windows-arm64',
})
expect(report.passed).toBe(false)
expect(report.missingChecks.some((check) => check.label === 'Windows arm64 unpacked sidecar binary')).toBe(true)
expect(report.missingChecks.some((check) => check.label === 'Windows arm64 node-pty native module')).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')
writeWindowsBranding(rootDir, 'desktop/build-artifacts/electron/win-unpacked')
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.each(['missing-icon', 'wrong-icon', 'wrong-executable'] as const)('rejects Windows branding with %s', async failure => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
const unpackedDir = 'desktop/build-artifacts/electron/win-unpacked'
writeFile(rootDir, `${unpackedDir}/resources/app.asar`)
writeWindowsBranding(rootDir, unpackedDir)
if (failure === 'missing-icon') rmSync(join(rootDir, unpackedDir, 'resources/app-icon.ico'))
if (failure === 'wrong-icon') writeFile(rootDir, `${unpackedDir}/resources/app-icon.ico`, 'wrong icon')
if (failure === 'wrong-executable') writeFile(rootDir, `${unpackedDir}/Claude Code Haha.exe`, 'unbranded executable')
const report = await inspectPackagedArtifacts(rootDir, { platform: 'windows', packageKind: 'dir' })
expect(report.missingChecks.some(check => check.label === 'Windows application and taskbar icons')).toBe(true)
expect(report.notes.join('\n')).toContain('Windows icon inspection failed:')
})
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('does not treat win-arm64-unpacked executables as Windows release installers', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/Claude Code Haha.exe')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app-update.yml')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-pc-windows-msvc.exe')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
writeFile(rootDir, 'desktop/build-artifacts/windows-arm64/win-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/win32-arm64/pty.node')
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'windows',
arch: 'arm64',
packageKind: 'release',
artifactsDir: 'desktop/build-artifacts/windows-arm64',
})
expect(report.passed).toBe(false)
expect(report.packagedArtifacts.some((artifact) => artifact.path.includes('win-arm64-unpacked'))).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.replaceAll('\\', '/').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 release checks for Electron Builder output without AppImage blockmap', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-x86_64.AppImage')
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-amd64.deb')
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-x86_64.rpm')
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app.asar')
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-unpacked/resources/app-update.yml')
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/build/Release/pty.node')
writeFile(rootDir, 'desktop/build-artifacts/electron/latest-linux.yml', 'path: Claude-Code-Haha-0.3.1-linux-x86_64.AppImage\n')
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'linux',
packageKind: 'release',
artifactsDir: 'desktop/build-artifacts/electron',
})
expect(report.passed).toBe(true)
expect(report.missingChecks.some((check) => check.label.includes('blockmap'))).toBe(false)
expect(report.packagedArtifacts.some((artifact) => artifact.label === 'Linux RPM package')).toBe(true)
})
test('accepts Electron Builder linux-arm64-unpacked output directory', async () => {
const rootDir = createRepoRoot()
tempDirs.push(rootDir)
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-arm64.AppImage')
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-arm64.deb')
writeFile(rootDir, 'desktop/build-artifacts/electron/Claude-Code-Haha-0.3.1-linux-aarch64.rpm')
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar')
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app-update.yml')
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar.unpacked/src-tauri/binaries/claude-sidecar-aarch64-unknown-linux-gnu')
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/package.json')
writeFile(rootDir, 'desktop/build-artifacts/electron/linux-arm64-unpacked/resources/app.asar.unpacked/node_modules/node-pty/prebuilds/linux-arm64/pty.node')
writeFile(rootDir, 'desktop/build-artifacts/electron/latest-linux-arm64.yml', 'path: Claude-Code-Haha-0.3.1-linux-arm64.AppImage\n')
const report = await inspectPackagedArtifacts(rootDir, {
platform: 'linux',
packageKind: 'release',
artifactsDir: 'desktop/build-artifacts/electron',
})
expect(report.passed).toBe(true)
expect(report.passedChecks.some((check) => check.path.replaceAll('\\', '/').includes('linux-arm64-unpacked/resources/app.asar'))).toBe(true)
expect(report.packagedArtifacts.some((artifact) => artifact.label === 'Linux RPM package')).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)
})
})