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

1401 lines
49 KiB
TypeScript

#!/usr/bin/env bun
import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
import { tmpdir } from 'node:os'
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
import { createSandboxedTestEnvironment } from '../../pr/test-environment'
export type PackageSmokePlatform = 'macos' | 'windows' | 'linux'
export type PackageSmokeArch = 'x64' | 'arm64'
export type VerificationMode = 'bundle-structure' | 'static-artifact'
export type PackageKind = 'auto' | 'dir' | 'release'
type CheckRecord = {
label: string
path: string
}
type InspectOptions = {
platform: PackageSmokePlatform
arch?: PackageSmokeArch
artifactsDir?: string
requireMacosGatekeeper?: boolean
packageKind?: PackageKind
commandRunner?: PackageSmokeCommandRunner
hostPlatform?: string
hostArch?: string
}
export type PackageSmokeArgs = {
platform: PackageSmokePlatform
arch?: PackageSmokeArch
artifactsDir?: string
requireMacosGatekeeper?: boolean
packageKind?: PackageKind
}
export type PackageSmokeReport = {
platform: PackageSmokePlatform
hostPlatform: string
productName: string
version: string
arch?: PackageSmokeArch
verificationMode: VerificationMode
packageKind: PackageKind
artifactsDir: string
packagedArtifacts: CheckRecord[]
optionalArtifacts: CheckRecord[]
passedChecks: CheckRecord[]
missingChecks: CheckRecord[]
notes: string[]
passed: boolean
}
type DesktopMetadata = {
productName: string
version: string
}
type PackageSmokeCommandResult = {
status: number | null
stdout?: string
stderr?: string
}
type PackageSmokeCommandOptions = {
cwd: string
env: Record<string, string>
timeout: number
maxBuffer: number
}
type PackageSmokeCommandRunner = (command: string, args: string[], options?: PackageSmokeCommandOptions) => PackageSmokeCommandResult
function usage() {
return 'Usage: bun run test:package-smoke --platform <macos|windows|linux> [--arch <x64|arm64>] [--package-kind <auto|dir|release>] [--artifacts-dir <path>] [--require-macos-gatekeeper]'
}
function readArgValue(argv: string[], index: number, flag: string) {
const value = argv[index + 1]
if (!value || value.startsWith('--')) {
throw new Error(`Missing value for ${flag}\n${usage()}`)
}
return value
}
export function parsePackageSmokeArgs(argv: string[]): PackageSmokeArgs {
let platform: PackageSmokePlatform | undefined
let arch: PackageSmokeArch | undefined
let artifactsDir: string | undefined
let requireMacosGatekeeper = false
let packageKind: PackageKind = 'auto'
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index]
if (arg === '--platform') {
const value = readArgValue(argv, index, arg)
if (value === 'macos' || value === 'windows' || value === 'linux') {
platform = value
} else {
throw new Error(`Unsupported --platform value: ${value}. Expected macos|windows|linux.\n${usage()}`)
}
index += 1
continue
}
if (arg === '--arch') {
const value = readArgValue(argv, index, arg)
if (value === 'x64' || value === 'arm64') {
arch = value
} else {
throw new Error(`Unsupported --arch value: ${value}. Expected x64|arm64.\n${usage()}`)
}
index += 1
continue
}
if (arg === '--artifacts-dir') {
artifactsDir = readArgValue(argv, index, arg)
index += 1
continue
}
if (arg === '--package-kind') {
const value = readArgValue(argv, index, arg)
if (value === 'auto' || value === 'dir' || value === 'release') {
packageKind = value
} else {
throw new Error(`Unsupported --package-kind value: ${value}. Expected auto|dir|release.\n${usage()}`)
}
index += 1
continue
}
if (arg === '--require-macos-gatekeeper') {
requireMacosGatekeeper = true
continue
}
}
if (!platform) {
throw new Error(`Missing required --platform <macos|windows|linux>\n${usage()}`)
}
return {
platform,
arch,
artifactsDir,
requireMacosGatekeeper,
packageKind,
}
}
function detectHostPlatform() {
if (process.platform === 'darwin') return 'macos'
if (process.platform === 'win32') return 'windows'
if (process.platform === 'linux') return 'linux'
return process.platform
}
function readDesktopMetadata(rootDir: string): DesktopMetadata {
const packageJsonPath = join(rootDir, 'desktop', 'package.json')
const raw = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
version?: string
productName?: string
build?: { productName?: string }
name?: string
}
return {
productName: raw.build?.productName ?? raw.productName ?? raw.name ?? 'app',
version: raw.version ?? 'unknown',
}
}
function toRelative(rootDir: string, targetPath: string) {
return relative(rootDir, targetPath) || '.'
}
function normalizePath(targetPath: string) {
return targetPath.replaceAll('\\', '/')
}
function bundledRipgrepNeedle(platform: PackageSmokePlatform) {
return platform === 'windows' ? '/rg.exe' : '/rg'
}
function addBundledRipgrepLicenseChecks(
report: PackageSmokeReport,
rootDir: string,
sidecarDir: string,
platformLabel: string,
) {
addPresenceCheck(
report,
rootDir,
`${platformLabel} ripgrep build manifest`,
join(sidecarDir, 'ripgrep-manifest.json'),
)
const licensesDir = join(sidecarDir, 'ripgrep-licenses')
for (const fileName of ['COPYING', 'LICENSE-MIT', 'UNLICENSE']) {
addPresenceCheck(
report,
rootDir,
`${platformLabel} ripgrep license (${fileName})`,
join(licensesDir, fileName),
)
}
}
function walkPaths(rootDir: string, options?: { directoriesOnly?: boolean }) {
if (!existsSync(rootDir)) {
return [] as string[]
}
const results: string[] = []
const stack = [rootDir]
while (stack.length > 0) {
const current = stack.pop()
if (!current) continue
let entries: ReturnType<typeof readdirSync> = []
try {
entries = readdirSync(current, { withFileTypes: true })
} catch {
continue
}
for (const entry of entries) {
const fullPath = join(current, entry.name)
if (entry.isDirectory()) {
results.push(fullPath)
stack.push(fullPath)
} else if (!options?.directoriesOnly) {
results.push(fullPath)
}
}
}
return results
}
function findMatches(rootDir: string, matcher: (candidate: string) => boolean, options?: { directoriesOnly?: boolean }) {
return walkPaths(rootDir, options).filter(matcher).sort()
}
function addPresenceCheck(
report: PackageSmokeReport,
rootDir: string,
label: string,
targetPath: string,
) {
const record = {
label,
path: toRelative(rootDir, targetPath),
}
if (existsSync(targetPath)) {
report.passedChecks.push(record)
} else {
report.missingChecks.push(record)
}
}
function addMatchCheck(
report: PackageSmokeReport,
rootDir: string,
label: string,
matches: string[],
fallbackPath: string,
) {
if (matches.length > 0) {
report.passedChecks.push({
label,
path: toRelative(rootDir, matches[0]),
})
return
}
report.missingChecks.push({
label,
path: toRelative(rootDir, fallbackPath),
})
}
type MachOArch = 'arm64' | 'x86_64'
const MACHO_CPU_TYPES: Record<number, MachOArch> = {
[0x0100000c]: 'arm64',
[0x01000007]: 'x86_64',
}
export function parseMachOArchitectures(bytes: Uint8Array): MachOArch[] {
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
if (buffer.length < 8) return []
const architectures = new Set<MachOArch>()
const addCpu = (value: number) => {
const arch = MACHO_CPU_TYPES[value >>> 0]
if (arch) architectures.add(arch)
}
const littleMagic = buffer.readUInt32LE(0)
const bigMagic = buffer.readUInt32BE(0)
if (littleMagic === 0xfeedface || littleMagic === 0xfeedfacf) {
addCpu(buffer.readUInt32LE(4))
return [...architectures]
}
if (bigMagic === 0xfeedface || bigMagic === 0xfeedfacf) {
addCpu(buffer.readUInt32BE(4))
return [...architectures]
}
const fat64 = bigMagic === 0xcafebabf || littleMagic === 0xcafebabf
const fat32 = bigMagic === 0xcafebabe || littleMagic === 0xcafebabe
if (!fat32 && !fat64) return []
const bigEndian = bigMagic === 0xcafebabe || bigMagic === 0xcafebabf
const readU32 = (offset: number) => bigEndian
? buffer.readUInt32BE(offset)
: buffer.readUInt32LE(offset)
const count = readU32(4)
const stride = fat64 ? 32 : 20
for (let index = 0; index < count; index += 1) {
const offset = 8 + index * stride
if (offset + 4 > buffer.length) return []
addCpu(readU32(offset))
}
return [...architectures].sort()
}
function decodeMachOVersion(encoded: number): string {
const major = (encoded >>> 16) & 0xffff
const minor = (encoded >>> 8) & 0xff
const patch = encoded & 0xff
return patch > 0 ? `${major}.${minor}.${patch}` : `${major}.${minor}`
}
function parseThinMachOMinimumVersion(
buffer: Buffer,
start: number,
length: number,
): string | null {
if (length < 28 || start < 0 || start + length > buffer.length) return null
const littleMagic = buffer.readUInt32LE(start)
const bigMagic = buffer.readUInt32BE(start)
const littleEndian = littleMagic === 0xfeedface || littleMagic === 0xfeedfacf
const bigEndian = bigMagic === 0xfeedface || bigMagic === 0xfeedfacf
if (!littleEndian && !bigEndian) return null
const readU32 = (offset: number) => littleEndian
? buffer.readUInt32LE(offset)
: buffer.readUInt32BE(offset)
const is64Bit = (littleEndian ? littleMagic : bigMagic) === 0xfeedfacf
const commandCount = readU32(start + 16)
let cursor = start + (is64Bit ? 32 : 28)
const end = start + length
for (let index = 0; index < commandCount; index += 1) {
if (cursor + 8 > end) return null
const command = readU32(cursor)
const commandSize = readU32(cursor + 4)
if (commandSize < 8 || cursor + commandSize > end) return null
if (command === 0x32 && commandSize >= 24) {
// LC_BUILD_VERSION.minos
return decodeMachOVersion(readU32(cursor + 12))
}
if (command === 0x24 && commandSize >= 16) {
// Legacy LC_VERSION_MIN_MACOSX.version
return decodeMachOVersion(readU32(cursor + 8))
}
cursor += commandSize
}
return null
}
export function parseMachOMinimumMacosVersions(bytes: Uint8Array): string[] {
const buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
if (buffer.length < 8) return []
const bigMagic = buffer.readUInt32BE(0)
const fat64 = bigMagic === 0xcafebabf || bigMagic === 0xbfbafeca
const fat32 = bigMagic === 0xcafebabe || bigMagic === 0xbebafeca
if (!fat32 && !fat64) {
const version = parseThinMachOMinimumVersion(buffer, 0, buffer.length)
return version ? [version] : []
}
const bigEndian = bigMagic === 0xcafebabe || bigMagic === 0xcafebabf
const readU32 = (offset: number) => bigEndian
? buffer.readUInt32BE(offset)
: buffer.readUInt32LE(offset)
const readU64 = (offset: number) => Number(bigEndian
? buffer.readBigUInt64BE(offset)
: buffer.readBigUInt64LE(offset))
const count = readU32(4)
const stride = fat64 ? 32 : 20
const versions = new Set<string>()
for (let index = 0; index < count; index += 1) {
const entry = 8 + index * stride
if (entry + stride > buffer.length) return []
const offset = fat64 ? readU64(entry + 8) : readU32(entry + 8)
const size = fat64 ? readU64(entry + 16) : readU32(entry + 12)
const version = parseThinMachOMinimumVersion(buffer, offset, size)
if (!version) return []
versions.add(version)
}
return [...versions].sort()
}
function addExactMachOArchitectureCheck(
report: PackageSmokeReport,
rootDir: string,
label: string,
targetPath: string,
expected: MachOArch,
) {
const record = { label, path: toRelative(rootDir, targetPath) }
try {
const actual = parseMachOArchitectures(readFileSync(targetPath))
if (actual.length === 1 && actual[0] === expected) {
report.passedChecks.push(record)
return
}
report.notes.push(`${label} expected ${expected}, found ${actual.join(', ') || 'not Mach-O'}.`)
} catch (error) {
report.notes.push(`${label} could not be inspected: ${String(error)}`)
}
report.missingChecks.push(record)
}
function addHelperMinimumSystemCheck(
report: PackageSmokeReport,
rootDir: string,
infoPlistPath: string,
) {
const label = 'macOS cu-helper minimum system version (14.4)'
const record = { label, path: toRelative(rootDir, infoPlistPath) }
try {
const plist = readFileSync(infoPlistPath, 'utf8')
const version = plist.match(
/<key>LSMinimumSystemVersion<\/key>\s*<string>([^<]+)<\/string>/,
)?.[1]?.trim()
if (version === '14.4') {
report.passedChecks.push(record)
return
}
report.notes.push(`${label} expected 14.4, found ${version ?? 'missing'}.`)
} catch (error) {
report.notes.push(`${label} could not be inspected: ${String(error)}`)
}
report.missingChecks.push(record)
}
function addHelperMachOMinimumSystemCheck(
report: PackageSmokeReport,
rootDir: string,
helperExecutable: string,
) {
const label = 'macOS cu-helper Mach-O deployment target (14.4)'
const record = { label, path: toRelative(rootDir, helperExecutable) }
try {
const versions = parseMachOMinimumMacosVersions(readFileSync(helperExecutable))
if (versions.length > 0 && versions.every(version => version === '14.4')) {
report.passedChecks.push(record)
return
}
report.notes.push(`${label} expected 14.4, found ${versions.join(', ') || 'missing'}.`)
} catch (error) {
report.notes.push(`${label} could not be inspected: ${String(error)}`)
}
report.missingChecks.push(record)
}
function parseUpdateMetadataReferences(content: string) {
const references = [] as string[]
const pattern = /^\s*(?:url|path):\s*['"]?([^'"\n]+?)['"]?\s*$/gm
let match: RegExpExecArray | null
while ((match = pattern.exec(content))) {
const value = match[1]?.trim()
if (!value || value.includes('://')) continue
references.push(value)
}
return references
}
function addUpdateMetadataChecks(
report: PackageSmokeReport,
rootDir: string,
metadataFiles: string[],
) {
for (const metadataFile of metadataFiles) {
report.optionalArtifacts.push({
label: 'update metadata',
path: toRelative(rootDir, metadataFile),
})
const metadataDir = dirname(metadataFile)
const references = parseUpdateMetadataReferences(readFileSync(metadataFile, 'utf8'))
for (const reference of references) {
const decodedReference = decodeURIComponent(reference)
addPresenceCheck(
report,
rootDir,
`update metadata referenced artifact (${reference})`,
join(metadataDir, decodedReference),
)
}
}
}
function addBlockmapChecks(
report: PackageSmokeReport,
rootDir: string,
labelPrefix: string,
artifacts: string[],
) {
for (const artifact of artifacts) {
addPresenceCheck(
report,
rootDir,
`${labelPrefix} blockmap (${relative(dirname(artifact), artifact)})`,
`${artifact}.blockmap`,
)
}
}
function findLinuxUnpackedDir(artifactsDir: string) {
const unpackedDirs = findMatches(
artifactsDir,
(candidate) => /\/linux(?:-[a-z0-9_]+)?-unpacked$/.test(normalizePath(candidate)),
{ directoriesOnly: true },
)
return unpackedDirs.sort((left, right) => {
const leftName = normalizePath(left).split('/').pop()
const rightName = normalizePath(right).split('/').pop()
if (leftName === 'linux-unpacked') return -1
if (rightName === 'linux-unpacked') return 1
return left.localeCompare(right)
})[0]
}
function firstDiagnosticLine(output: string) {
return output
.split(/\r?\n/)
.map(line => line.trim())
.find(Boolean)
}
function collectDiagnosticLines(output: string, limit = 3) {
return output
.split(/\r?\n/)
.map(line => line.trim())
.filter(Boolean)
.slice(0, limit)
}
function defaultCommandRunner(command: string, args: string[], options?: PackageSmokeCommandOptions): PackageSmokeCommandResult {
const result = spawnSync(command, args, {
encoding: 'utf8',
...options,
})
return {
status: result.status,
stdout: result.stdout ?? '',
stderr: result.error?.message ?? result.stderr ?? '',
}
}
function assertCursorResourcesContained(helperApp: string, directory: string) {
const app = realpathSync(helperApp)
const pending = [directory]
const visited = new Set<string>()
while (pending.length > 0) {
const target = pending.pop()!
const canonical = realpathSync(target)
const withinApp = relative(app, canonical)
if (isAbsolute(withinApp) || withinApp === '..' || normalizePath(withinApp).startsWith('../')) {
throw new Error(`cursor resource escapes the helper app: ${target}`)
}
if (visited.has(canonical)) continue
visited.add(canonical)
const entry = statSync(target)
if (entry.isDirectory()) pending.push(...readdirSync(target).map(name => join(target, name)))
else if (!entry.isFile()) throw new Error(`cursor resource is not a regular file: ${target}`)
}
}
function addMacosCursorResourceCheck(
report: PackageSmokeReport,
rootDir: string,
helperApp: string,
options: InspectOptions,
) {
const sequenceRelative = 'Contents/Resources/cu-helper_cc-haha-computer-use.bundle/LensSequence'
const sourceDirectory = join(helperApp, sequenceRelative)
const structureLabel = 'macOS cu-helper cursor resource directory'
const executionLabel = 'macOS relocated cu-helper cursor resource execution'
const record = { label: executionLabel, path: toRelative(rootDir, helperApp) }
let probeRoot: string | undefined
try {
if (!statSync(sourceDirectory).isDirectory()) throw new Error('LensSequence is not a directory')
assertCursorResourcesContained(helperApp, sourceDirectory)
report.passedChecks.push({ label: structureLabel, path: toRelative(rootDir, sourceDirectory) })
} catch (error) {
report.missingChecks.push({ label: structureLabel, path: toRelative(rootDir, sourceDirectory) })
report.notes.push(`${structureLabel} failed: ${error instanceof Error ? error.message : String(error)}`)
return
}
if (report.hostPlatform !== 'macos') {
report.notes.push(`SKIPPED: ${executionLabel}; host platform is ${report.hostPlatform}.`)
return
}
const hostArch = options.hostArch ?? process.arch
const hostMachOArch = hostArch === 'x64' ? 'x86_64' : hostArch
const targetArch = report.arch === 'x64' ? 'x86_64' : report.arch
if (targetArch && targetArch !== hostMachOArch) {
report.notes.push(`SKIPPED: ${executionLabel}; target ${targetArch}, host ${hostMachOArch}. Only package structure was checked.`)
return
}
try {
const inner = 'Contents/MacOS/cc-haha-computer-use'
const architectures = parseMachOArchitectures(readFileSync(join(helperApp, inner)))
if (!architectures.includes(hostMachOArch as MachOArch)) {
if (architectures.length === 0) throw new Error('helper has no recognized Mach-O architecture')
report.notes.push(`SKIPPED: ${executionLabel}; binary ${architectures.join(',')}, host ${hostMachOArch}. Only package structure was checked.`)
return
}
probeRoot = mkdtempSync(join(tmpdir(), 'cc-haha-packaged-cursor-'))
const app = join(probeRoot, 'Relocated Helper.app')
cpSync(helperApp, app, { recursive: true, verbatimSymlinks: true })
assertCursorResourcesContained(app, join(app, sequenceRelative))
const expected = realpathSync(join(app, sequenceRelative))
const home = join(probeRoot, 'home')
mkdirSync(home)
const env = createSandboxedTestEnvironment(home, {
PATH: '/usr/bin:/bin:/usr/sbin:/sbin', CFFIXED_USER_HOME: home,
}, {})
const result = (options.commandRunner ?? defaultCommandRunner)(join(app, inner), ['--probe-cursor-resources'], {
cwd: probeRoot, env, timeout: 10_000, maxBuffer: 1024 * 1024,
})
if (result.status !== 0) throw new Error(`probe exited with status ${result.status}: ${result.stderr ?? ''}`)
const resources = JSON.parse(result.stdout ?? '') as Record<string, unknown>
if (!resources || typeof resources.resourceDirectory !== 'string'
|| !isAbsolute(resources.resourceDirectory)
|| realpathSync(resources.resourceDirectory) !== expected) {
throw new Error('probe did not load resources from the relocated final package')
}
if (!Number.isSafeInteger(resources.frameCount) || (resources.frameCount as number) < 0
|| resources.proceduralFallback !== (resources.frameCount === 0)) {
throw new Error('probe returned invalid cursor frame/fallback diagnostics')
}
report.passedChecks.push(record)
report.notes.push(`${executionLabel}: executed ${hostMachOArch}, frames=${resources.frameCount}, proceduralFallback=${resources.proceduralFallback}. No GUI or input was requested.`)
} catch (error) {
report.missingChecks.push(record)
report.notes.push(`${executionLabel} failed: ${error instanceof Error ? error.message : String(error)}`)
} finally {
if (probeRoot) rmSync(probeRoot, { recursive: true, force: true })
}
}
function shellQuote(value: string) {
return `'${value.replace(/'/g, "'\\''")}'`
}
function isTooManyOpenFiles(result: PackageSmokeCommandResult) {
return /Too many open files/i.test(`${result.stdout ?? ''}${result.stderr ?? ''}`)
}
function runMacosGatekeeperAssessment(
appBundle: string,
commandRunner: PackageSmokeCommandRunner,
) {
const initial = commandRunner('/usr/sbin/spctl', ['-a', '-vvv', '-t', 'execute', appBundle])
if (!isTooManyOpenFiles(initial)) {
return { result: initial, retriedAfterTooManyOpenFiles: false }
}
const retry = commandRunner('/bin/zsh', [
'-lc',
`ulimit -n 1048575 2>/dev/null || true; exec /usr/sbin/spctl -a -vvv -t execute ${shellQuote(appBundle)}`,
])
return { result: retry, retriedAfterTooManyOpenFiles: true }
}
function addCommandDiagnostics(
report: PackageSmokeReport,
label: string,
result: PackageSmokeCommandResult,
) {
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`
const lines = collectDiagnosticLines(output)
const status = result.status ?? 'unknown'
if (lines.length === 0) {
report.notes.push(`${label} exited with status ${status} and produced no diagnostic output.`)
return
}
report.notes.push(`${label} exited with status ${status}: ${lines.join(' | ')}`)
}
type CodesignMetadata = {
identifier: string | null
authority: string | null
team: string | null
timestamp: string | null
}
export function parseCodesignMetadata(output: string): CodesignMetadata {
const first = (prefix: string) => output
.split(/\r?\n/)
.find(line => line.startsWith(prefix))
?.slice(prefix.length)
.trim() ?? null
const team = first('TeamIdentifier=')
return {
identifier: first('Identifier='),
authority: first('Authority='),
team: team === 'not set' ? null : team,
timestamp: first('Timestamp='),
}
}
function addMacosComputerUseAttestationCheck(
report: PackageSmokeReport,
rootDir: string,
appBundle: string,
sidecar: string,
helperApp: string,
commandRunner: PackageSmokeCommandRunner,
) {
const label = 'macOS Computer Use signing attestation chain'
const record = { label, path: toRelative(rootDir, helperApp) }
if (report.hostPlatform !== 'macos') {
report.notes.push(`${label} was requested but skipped because host platform is ${report.hostPlatform}.`)
return
}
const targets = [
{ name: 'host', path: appBundle, identifier: 'com.claude-code-haha.desktop', deep: true },
{ name: 'sidecar', path: sidecar, identifier: 'com.claude-code-haha.desktop.sidecar', deep: false },
{ name: 'helper', path: helperApp, identifier: 'dev.cchaha.cu-helper', deep: true },
] as const
const metadata: CodesignMetadata[] = []
for (const target of targets) {
const verifyArgs = ['--verify', ...(target.deep ? ['--deep'] : []), '--strict', '--verbose=2', target.path]
const verify = commandRunner('/usr/bin/codesign', verifyArgs)
if (verify.status !== 0) {
report.missingChecks.push(record)
addCommandDiagnostics(report, `${target.name} codesign verification`, verify)
return
}
const details = commandRunner('/usr/bin/codesign', ['-dv', '--verbose=4', target.path])
if (details.status !== 0) {
report.missingChecks.push(record)
addCommandDiagnostics(report, `${target.name} codesign details`, details)
return
}
const parsed = parseCodesignMetadata(`${details.stdout ?? ''}${details.stderr ?? ''}`)
if (
parsed.identifier !== target.identifier
|| !parsed.authority?.startsWith('Developer ID Application:')
|| !parsed.team
|| !parsed.timestamp
) {
report.missingChecks.push(record)
report.notes.push(
`${label} rejected ${target.name}: identifier=${parsed.identifier ?? 'missing'}, `
+ `authority=${parsed.authority ?? 'missing'}, team=${parsed.team ?? 'missing'}, `
+ `timestamp=${parsed.timestamp ? 'present' : 'missing'}.`,
)
return
}
metadata.push(parsed)
}
if (metadata.length !== targets.length) {
report.missingChecks.push(record)
report.notes.push(`${label} could not collect metadata for every required executable.`)
return
}
const [host, sidecarMetadata, helper] = metadata as [
CodesignMetadata,
CodesignMetadata,
CodesignMetadata,
]
if (
host.authority !== sidecarMetadata.authority
|| host.authority !== helper.authority
|| host.team !== sidecarMetadata.team
|| host.team !== helper.team
) {
report.missingChecks.push(record)
report.notes.push(`${label} rejected mismatched Developer ID authority/team values.`)
return
}
report.passedChecks.push(record)
}
function addMacosGatekeeperCheck(
report: PackageSmokeReport,
rootDir: string,
appBundle: string,
commandRunner: PackageSmokeCommandRunner = defaultCommandRunner,
) {
if (report.hostPlatform !== 'macos') {
report.notes.push(`macOS Gatekeeper assessment was requested but skipped because host platform is ${report.hostPlatform}.`)
return
}
const { result, retriedAfterTooManyOpenFiles } = runMacosGatekeeperAssessment(appBundle, commandRunner)
if (retriedAfterTooManyOpenFiles) {
report.notes.push('spctl Gatekeeper assessment initially failed with Too many open files; retried with a raised file descriptor limit.')
}
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`
const detail = firstDiagnosticLine(output)
const record = {
label: detail ? `macOS Gatekeeper launch approval (${detail})` : 'macOS Gatekeeper launch approval',
path: toRelative(rootDir, appBundle),
}
if (result.status === 0) {
report.passedChecks.push(record)
} else {
report.missingChecks.push(record)
addCommandDiagnostics(report, 'spctl Gatekeeper assessment', result)
addCommandDiagnostics(
report,
'codesign verification',
commandRunner('/usr/bin/codesign', ['--verify', '--deep', '--strict', '--verbose=2', appBundle]),
)
addCommandDiagnostics(
report,
'codesign signature details',
commandRunner('/usr/bin/codesign', ['-dv', '--verbose=4', appBundle]),
)
addCommandDiagnostics(
report,
'notarization ticket validation',
commandRunner('/usr/bin/xcrun', ['stapler', 'validate', appBundle]),
)
}
}
function addInstalledUpdateMetadataCheck(
report: PackageSmokeReport,
rootDir: string,
label: string,
resourcesDir: string,
hasReleaseMetadata: boolean,
) {
if (hasReleaseMetadata) {
addPresenceCheck(report, rootDir, label, join(resourcesDir, 'app-update.yml'))
} else {
report.notes.push(`${label} was not required because no release archive/update metadata was found in this artifact set.`)
}
}
function createReport(
rootDir: string,
platform: PackageSmokePlatform,
arch: PackageSmokeArch | undefined,
metadata: DesktopMetadata,
artifactsDir: string,
verificationMode: VerificationMode,
packageKind: PackageKind,
hostPlatform = detectHostPlatform(),
): PackageSmokeReport {
return {
platform,
hostPlatform,
productName: metadata.productName,
version: metadata.version,
arch,
verificationMode,
packageKind,
artifactsDir,
packagedArtifacts: [],
optionalArtifacts: [],
passedChecks: [],
missingChecks: [],
notes: [],
passed: false,
}
}
function inspectMacosArtifacts(rootDir: string, report: PackageSmokeReport, options: InspectOptions) {
const appBundles = findMatches(
report.artifactsDir,
(candidate) => {
const normalized = normalizePath(candidate)
return normalized.endsWith(`/${report.productName}.app`) && !normalized.includes('/Contents/Frameworks/')
},
{ directoriesOnly: true },
)
const archives = findMatches(report.artifactsDir, (candidate) => candidate.endsWith('.zip') || candidate.endsWith('.dmg'))
const updateMetadata = findMatches(report.artifactsDir, (candidate) => candidate.endsWith('latest-mac.yml'))
const releaseMode = report.packageKind === 'release' || (report.packageKind === 'auto' && (archives.length > 0 || updateMetadata.length > 0))
report.packagedArtifacts.push(...appBundles.map((candidate) => ({
label: 'macOS app bundle',
path: toRelative(rootDir, candidate),
})))
report.optionalArtifacts.push(...archives.map((candidate) => ({
label: candidate.endsWith('.dmg') ? 'macOS dmg archive' : 'macOS zip archive',
path: toRelative(rootDir, candidate),
})))
if (releaseMode) {
addUpdateMetadataChecks(report, rootDir, updateMetadata)
addBlockmapChecks(report, rootDir, 'macOS update artifact', archives)
}
if (appBundles.length === 0) {
report.missingChecks.push({
label: 'macOS app bundle',
path: toRelative(rootDir, join(report.artifactsDir, 'electron')),
})
return
}
const appBundle = appBundles[0]
const contentsDir = join(appBundle, 'Contents')
const resourcesDir = join(contentsDir, 'Resources')
const unpackedDir = join(resourcesDir, 'app.asar.unpacked')
const nodePtyDir = join(unpackedDir, 'node_modules', 'node-pty')
const prebuildsDir = join(nodePtyDir, 'prebuilds')
const sidecarDir = join(unpackedDir, 'src-tauri', 'binaries')
const helperApp = join(sidecarDir, 'cc-haha-computer-use.app')
const helperInfoPlist = join(helperApp, 'Contents', 'Info.plist')
const helperExecutable = join(helperApp, 'Contents', 'MacOS', 'cc-haha-computer-use')
const hostExecutable = join(contentsDir, 'MacOS', report.productName)
addPresenceCheck(report, rootDir, 'macOS Info.plist', join(contentsDir, 'Info.plist'))
addPresenceCheck(report, rootDir, 'macOS app executable', hostExecutable)
addPresenceCheck(report, rootDir, 'macOS app.asar', join(resourcesDir, 'app.asar'))
addPresenceCheck(report, rootDir, 'macOS unpacked H5 shell', join(unpackedDir, 'dist', 'index.html'))
addInstalledUpdateMetadataCheck(
report,
rootDir,
'macOS app-update.yml',
resourcesDir,
releaseMode,
)
addPresenceCheck(report, rootDir, 'macOS node-pty package.json', join(nodePtyDir, 'package.json'))
addPresenceCheck(report, rootDir, 'macOS cu-helper app bundle', helperApp)
addPresenceCheck(report, rootDir, 'macOS cu-helper Info.plist', helperInfoPlist)
addPresenceCheck(report, rootDir, 'macOS cu-helper executable', helperExecutable)
addMacosCursorResourceCheck(report, rootDir, helperApp, options)
if (existsSync(helperInfoPlist)) addHelperMinimumSystemCheck(report, rootDir, helperInfoPlist)
addBundledRipgrepLicenseChecks(report, rootDir, sidecarDir, 'macOS')
addMatchCheck(
report,
rootDir,
'macOS bundled ripgrep binary',
findMatches(sidecarDir, candidate =>
normalizePath(candidate).endsWith(bundledRipgrepNeedle('macos'))),
sidecarDir,
)
if (report.arch) {
const expectedMachOArch: MachOArch = report.arch === 'arm64' ? 'arm64' : 'x86_64'
const targetTriple = report.arch === 'arm64'
? 'aarch64-apple-darwin'
: 'x86_64-apple-darwin'
const nodePtyArch = report.arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64'
const sidecar = join(sidecarDir, `claude-sidecar-${targetTriple}`)
const pty = join(prebuildsDir, nodePtyArch, 'pty.node')
const spawnHelper = join(prebuildsDir, nodePtyArch, 'spawn-helper')
addPresenceCheck(report, rootDir, `macOS ${report.arch} unpacked sidecar binary`, sidecar)
addPresenceCheck(report, rootDir, `macOS ${report.arch} node-pty native module`, pty)
addPresenceCheck(report, rootDir, `macOS ${report.arch} node-pty spawn-helper`, spawnHelper)
if (existsSync(helperExecutable)) {
addHelperMachOMinimumSystemCheck(report, rootDir, helperExecutable)
}
for (const [label, target] of [
['app executable', hostExecutable],
['sidecar', sidecar],
['cu-helper', helperExecutable],
['node-pty native module', pty],
['node-pty spawn-helper', spawnHelper],
] as const) {
if (existsSync(target)) {
addExactMachOArchitectureCheck(
report,
rootDir,
`macOS ${report.arch} ${label} Mach-O architecture`,
target,
expectedMachOArch,
)
}
}
} else {
addMatchCheck(
report,
rootDir,
'macOS unpacked sidecar binary',
findMatches(sidecarDir, (candidate) => normalizePath(candidate).includes('/claude-sidecar-')),
sidecarDir,
)
addMatchCheck(
report,
rootDir,
'macOS node-pty native module',
findMatches(prebuildsDir, (candidate) => normalizePath(candidate).includes('/darwin-') && normalizePath(candidate).endsWith('/pty.node')),
prebuildsDir,
)
addMatchCheck(
report,
rootDir,
'macOS node-pty spawn-helper',
findMatches(prebuildsDir, (candidate) => normalizePath(candidate).includes('/darwin-') && normalizePath(candidate).endsWith('/spawn-helper')),
prebuildsDir,
)
}
report.notes.push('No GUI launch was attempted. Matching macOS helper architectures also run an input-free resource probe from a disposable copy of the final package.')
if (options.requireMacosGatekeeper) {
if (report.arch) {
const targetTriple = report.arch === 'arm64'
? 'aarch64-apple-darwin'
: 'x86_64-apple-darwin'
addMacosComputerUseAttestationCheck(
report,
rootDir,
appBundle,
join(sidecarDir, `claude-sidecar-${targetTriple}`),
helperApp,
options.commandRunner ?? defaultCommandRunner,
)
}
addMacosGatekeeperCheck(report, rootDir, appBundle, options.commandRunner)
} else if (report.hostPlatform === 'macos') {
report.notes.push('macOS Gatekeeper launch approval was not assessed. Add --require-macos-gatekeeper for release-readiness launch policy checks.')
}
if (report.packageKind === 'dir') {
report.notes.push('macOS release archive and update metadata checks were skipped for a directory-only development package.')
} else if (archives.length === 0) {
report.notes.push('No .zip or .dmg archive was found to record alongside the .app bundle.')
} else if (updateMetadata.length === 0) {
report.missingChecks.push({
label: 'macOS update metadata (latest-mac.yml)',
path: toRelative(rootDir, join(report.artifactsDir, 'electron', 'latest-mac.yml')),
})
}
if (report.packageKind === 'release' && archives.length === 0) {
report.missingChecks.push({
label: 'macOS release archive (.zip or .dmg)',
path: toRelative(rootDir, report.artifactsDir),
})
}
}
function inspectWindowsArtifacts(rootDir: string, report: PackageSmokeReport, options: InspectOptions) {
const installers = findMatches(report.artifactsDir, (candidate) => {
const normalized = normalizePath(candidate)
return normalized.endsWith('.exe') && !isInsideWindowsUnpackedDir(normalized)
})
const unpackedDir = findWindowsUnpackedDir(report.artifactsDir, report.arch)
const electronDir = join(report.artifactsDir, 'electron')
const updateMetadata = findMatches(report.artifactsDir, (candidate) => candidate.endsWith('latest.yml'))
const releaseMode = report.packageKind === 'release' || (report.packageKind === 'auto' && (installers.length > 0 || updateMetadata.length > 0))
report.packagedArtifacts.push(...installers.map((candidate) => ({
label: 'Windows installer',
path: toRelative(rootDir, candidate),
})))
if (releaseMode) {
addUpdateMetadataChecks(report, rootDir, updateMetadata)
addBlockmapChecks(report, rootDir, 'Windows update artifact', installers)
addMatchCheck(
report,
rootDir,
'windows packaged artifact (.exe installer)',
installers,
electronDir,
)
} else {
report.notes.push('Windows installer and update metadata checks were skipped for a directory-only development package.')
}
if (unpackedDir) {
const resourcesDir = join(unpackedDir, 'resources')
const executable = join(unpackedDir, `${report.productName}.exe`)
const shellIcon = join(resourcesDir, 'app-icon.ico')
const iconRecord = { label: 'Windows application and taskbar icons', path: toRelative(rootDir, executable) }
let iconProbeRoot: string | undefined
try {
const expectedIconPath = join(rootDir, 'desktop', 'src-tauri', 'icons', 'icon.ico')
const expectedIcon = readFileSync(expectedIconPath)
if (!readFileSync(shellIcon).equals(expectedIcon)) throw new Error('The taskbar ICO differs from the application ICO')
if (!existsSync(executable)) throw new Error('The Windows app executable is missing')
// Desktop packaging installs the builder's PE parser; the root-only
// policy lane must not import desktop dependencies while checking Linux.
iconProbeRoot = mkdtempSync(join(tmpdir(), 'cc-haha-windows-icon-'))
const result = (options.commandRunner ?? defaultCommandRunner)('bun', [
'run', join(rootDir, 'desktop', 'scripts', 'assert-windows-icon.ts'), executable, expectedIconPath,
], {
cwd: rootDir,
env: createSandboxedTestEnvironment(iconProbeRoot, undefined, process.env),
timeout: 30_000,
maxBuffer: 1024 * 1024,
})
if (result.status !== 0) throw new Error(firstDiagnosticLine(result.stderr ?? '') ?? 'The Windows executable icon inspection failed')
report.passedChecks.push(iconRecord)
} catch (error) {
report.missingChecks.push(iconRecord)
report.notes.push(`Windows icon inspection failed: ${error instanceof Error ? error.message : String(error)}`)
} finally {
if (iconProbeRoot) rmSync(iconProbeRoot, { recursive: true, force: true })
}
const unpackedResourcesDir = join(resourcesDir, 'app.asar.unpacked')
const nodePtyDir = join(unpackedResourcesDir, 'node_modules', 'node-pty')
const sidecarDir = join(unpackedResourcesDir, 'src-tauri', 'binaries')
const sidecarNeedle = report.arch === 'arm64'
? '/claude-sidecar-aarch64-pc-windows-msvc.exe'
: report.arch === 'x64'
? '/claude-sidecar-x86_64-pc-windows-msvc.exe'
: '/claude-sidecar-'
const nodePtyNeedle = report.arch === 'arm64'
? '/win32-arm64/'
: report.arch === 'x64'
? '/win32-x64/'
: '/win32-'
addPresenceCheck(report, rootDir, 'Windows app.asar', join(resourcesDir, 'app.asar'))
addInstalledUpdateMetadataCheck(
report,
rootDir,
'Windows app-update.yml',
resourcesDir,
releaseMode,
)
addPresenceCheck(report, rootDir, 'Windows node-pty package.json', join(nodePtyDir, 'package.json'))
addMatchCheck(
report,
rootDir,
report.arch ? `Windows ${report.arch} unpacked sidecar binary` : 'Windows unpacked sidecar binary',
findMatches(sidecarDir, (candidate) => normalizePath(candidate).includes(sidecarNeedle)),
report.arch
? join(sidecarDir, sidecarNeedle.slice(1))
: sidecarDir,
)
addMatchCheck(
report,
rootDir,
report.arch ? `Windows ${report.arch} bundled ripgrep binary` : 'Windows bundled ripgrep binary',
findMatches(sidecarDir, candidate =>
normalizePath(candidate).endsWith(bundledRipgrepNeedle('windows'))),
sidecarDir,
)
addBundledRipgrepLicenseChecks(report, rootDir, sidecarDir, 'Windows')
addMatchCheck(
report,
rootDir,
report.arch ? `Windows ${report.arch} node-pty native module` : 'Windows node-pty native module',
findMatches(join(nodePtyDir, 'prebuilds'), (candidate) => normalizePath(candidate).includes(nodePtyNeedle) && normalizePath(candidate).endsWith('/pty.node')),
join(nodePtyDir, 'prebuilds'),
)
} else {
report.missingChecks.push({
label: 'Windows unpacked directory for static resource inspection',
path: toRelative(rootDir, join(electronDir, report.arch === 'arm64' ? 'win-arm64-unpacked' : 'win-unpacked')),
})
}
report.notes.push('This is a static artifact check only. It does not claim installer execution or app launch success.')
if (releaseMode && updateMetadata.length === 0) {
report.missingChecks.push({
label: 'Windows update metadata (latest.yml)',
path: toRelative(rootDir, join(electronDir, 'latest.yml')),
})
}
if (report.hostPlatform !== 'windows') {
report.notes.push(`Host platform is ${report.hostPlatform}, so Windows verification stayed artifact-only.`)
}
}
function isWindowsUnpackedDirPath(candidate: string): boolean {
return /\/win(?:-[a-z0-9_]+)?-unpacked$/.test(normalizePath(candidate))
}
function isInsideWindowsUnpackedDir(candidate: string): boolean {
return /\/win(?:-[a-z0-9_]+)?-unpacked\//.test(normalizePath(candidate))
}
function findWindowsUnpackedDir(artifactsDir: string, arch?: 'x64' | 'arm64'): string | undefined {
const unpackedDirs = findMatches(
artifactsDir,
isWindowsUnpackedDirPath,
{ directoriesOnly: true },
)
if (arch === 'arm64') {
return unpackedDirs.find((candidate) => normalizePath(candidate).endsWith('/win-arm64-unpacked')) ?? unpackedDirs[0]
}
if (arch === 'x64') {
return unpackedDirs.find((candidate) => normalizePath(candidate).endsWith('/win-unpacked')) ?? unpackedDirs[0]
}
return unpackedDirs[0]
}
function inspectLinuxArtifacts(rootDir: string, report: PackageSmokeReport) {
const packagedArtifacts = findMatches(
report.artifactsDir,
(candidate) => candidate.endsWith('.AppImage')
|| candidate.endsWith('.deb')
|| candidate.endsWith('.rpm'),
)
const unpackedDir = findLinuxUnpackedDir(report.artifactsDir)
const updateMetadata = findMatches(report.artifactsDir, (candidate) => /latest-linux(?:-[a-z0-9]+)?\.yml$/.test(candidate))
const appImageBlockmaps = findMatches(report.artifactsDir, (candidate) => candidate.endsWith('.AppImage.blockmap'))
const releaseMode = report.packageKind === 'release' || (report.packageKind === 'auto' && (packagedArtifacts.length > 0 || updateMetadata.length > 0))
report.packagedArtifacts.push(...packagedArtifacts.map((candidate) => ({
label: candidate.endsWith('.deb')
? 'Linux deb package'
: candidate.endsWith('.rpm')
? 'Linux RPM package'
: 'Linux AppImage',
path: toRelative(rootDir, candidate),
})))
report.optionalArtifacts.push(...appImageBlockmaps.map((candidate) => ({
label: 'Linux AppImage blockmap',
path: toRelative(rootDir, candidate),
})))
if (releaseMode) {
addUpdateMetadataChecks(report, rootDir, updateMetadata)
if (appImageBlockmaps.length === 0 && packagedArtifacts.some(candidate => candidate.endsWith('.AppImage'))) {
report.notes.push('Linux AppImage blockmaps were not required because Electron Builder did not emit them for this artifact set.')
}
}
if (releaseMode) {
addMatchCheck(
report,
rootDir,
'linux packaged artifact (.AppImage, .deb, or .rpm)',
packagedArtifacts,
report.artifactsDir,
)
} else if (!unpackedDir) {
report.missingChecks.push({
label: 'linux packaged artifact (.AppImage, .deb, or .rpm)',
path: toRelative(rootDir, report.artifactsDir),
})
} else {
report.notes.push('No .AppImage, .deb, or .rpm was found; treating linux-unpacked as a directory-only development package.')
}
if (unpackedDir) {
const resourcesDir = join(unpackedDir, 'resources')
const unpackedResourcesDir = join(resourcesDir, 'app.asar.unpacked')
const nodePtyDir = join(unpackedResourcesDir, 'node_modules', 'node-pty')
const sidecarDir = join(unpackedResourcesDir, 'src-tauri', 'binaries')
addPresenceCheck(report, rootDir, 'Linux app.asar', join(resourcesDir, 'app.asar'))
addInstalledUpdateMetadataCheck(
report,
rootDir,
'Linux app-update.yml',
resourcesDir,
releaseMode,
)
addPresenceCheck(report, rootDir, 'Linux node-pty package.json', join(nodePtyDir, 'package.json'))
addMatchCheck(
report,
rootDir,
'Linux unpacked sidecar binary',
findMatches(sidecarDir, (candidate) => normalizePath(candidate).includes('/claude-sidecar-')),
sidecarDir,
)
addBundledRipgrepLicenseChecks(report, rootDir, sidecarDir, 'Linux')
addMatchCheck(
report,
rootDir,
'Linux bundled ripgrep binary',
findMatches(sidecarDir, candidate =>
normalizePath(candidate).endsWith(bundledRipgrepNeedle('linux'))),
sidecarDir,
)
addMatchCheck(
report,
rootDir,
'Linux node-pty native module',
findMatches(nodePtyDir, (candidate) => normalizePath(candidate).endsWith('/pty.node') && (normalizePath(candidate).includes('/linux-') || normalizePath(candidate).includes('/Release/'))),
nodePtyDir,
)
} else {
if (releaseMode) {
report.missingChecks.push({
label: 'Linux unpacked directory (linux-unpacked or linux-*-unpacked) for static resource inspection',
path: toRelative(rootDir, join(report.artifactsDir, 'linux-unpacked')),
})
} else {
report.notes.push('linux-unpacked was not found, so this check only verified packaged artifact presence.')
}
}
report.notes.push('This is a static artifact check only. It does not claim installer execution or app launch success.')
if (releaseMode && updateMetadata.length === 0) {
report.missingChecks.push({
label: 'Linux update metadata (latest-linux*.yml)',
path: toRelative(rootDir, join(report.artifactsDir, 'latest-linux.yml')),
})
}
if (report.hostPlatform !== 'linux') {
report.notes.push(`Host platform is ${report.hostPlatform}, so Linux verification stayed artifact-only.`)
}
}
export async function inspectPackagedArtifacts(rootDir: string, options: InspectOptions): Promise<PackageSmokeReport> {
const resolvedRootDir = resolve(rootDir)
const artifactsDir = options.artifactsDir
? resolve(resolvedRootDir, options.artifactsDir)
: join(resolvedRootDir, 'desktop', 'build-artifacts')
const metadata = readDesktopMetadata(resolvedRootDir)
const verificationMode = options.platform === 'macos' ? 'bundle-structure' : 'static-artifact'
const packageKind = options.packageKind ?? 'auto'
const report = createReport(
resolvedRootDir,
options.platform,
options.arch,
metadata,
artifactsDir,
verificationMode,
packageKind,
options.hostPlatform,
)
if (options.platform === 'macos') {
inspectMacosArtifacts(resolvedRootDir, report, options)
} else if (options.platform === 'windows') {
inspectWindowsArtifacts(resolvedRootDir, report, options)
} else {
inspectLinuxArtifacts(resolvedRootDir, report)
}
report.passed = report.missingChecks.length === 0
return report
}
function printRecord(prefix: string, record: CheckRecord) {
console.log(`${prefix} ${record.label}: ${record.path}`)
}
function printReport(report: PackageSmokeReport) {
console.log(`[package-smoke] platform=${report.platform} host=${report.hostPlatform} mode=${report.verificationMode}`)
if (report.arch) {
console.log(`[package-smoke] arch=${report.arch}`)
}
console.log(`[package-smoke] packageKind=${report.packageKind}`)
console.log(`[package-smoke] product=${report.productName} version=${report.version}`)
console.log(`[package-smoke] artifactsDir=${report.artifactsDir}`)
if (report.packagedArtifacts.length > 0) {
console.log('[package-smoke] packaged artifacts:')
for (const artifact of report.packagedArtifacts) {
printRecord(' -', artifact)
}
}
if (report.optionalArtifacts.length > 0) {
console.log('[package-smoke] optional artifacts:')
for (const artifact of report.optionalArtifacts) {
printRecord(' -', artifact)
}
}
if (report.passedChecks.length > 0) {
console.log('[package-smoke] passed checks:')
for (const check of report.passedChecks) {
printRecord(' -', check)
}
}
if (report.missingChecks.length > 0) {
console.log('[package-smoke] missing checks:')
for (const check of report.missingChecks) {
printRecord(' -', check)
}
}
if (report.notes.length > 0) {
console.log('[package-smoke] notes:')
for (const note of report.notes) {
console.log(` - ${note}`)
}
}
console.log(`[package-smoke] result=${report.passed ? 'PASS' : 'FAIL'}`)
}
if (import.meta.main) {
try {
const args = parsePackageSmokeArgs(process.argv.slice(2))
const report = await inspectPackagedArtifacts(process.cwd(), args)
printReport(report)
process.exit(report.passed ? 0 : 1)
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
console.error(message)
process.exit(1)
}
}