63 lines
3.5 KiB
JavaScript
63 lines
3.5 KiB
JavaScript
import { execFileSync, spawnSync } from 'node:child_process'
|
|
import { existsSync, readdirSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { assertWithinSize, readPeMachine } from './release-tools.mjs'
|
|
|
|
const platform = process.argv[2] ?? (process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'windows' : '')
|
|
const root = process.argv[3] ?? (platform === 'macos'
|
|
? 'src-tauri/target/aarch64-apple-darwin/release/bundle'
|
|
: 'src-tauri/target/release/bundle')
|
|
const requireSigning = process.env.REQUIRE_SIGNING === 'true'
|
|
const maxBinaryBytes = Number(process.env.MAX_RELEASE_BINARY_MB ?? 40) * 1024 * 1024
|
|
const maxInstallerBytes = Number(process.env.MAX_RELEASE_INSTALLER_MB ?? 40) * 1024 * 1024
|
|
|
|
function walk(dir) {
|
|
if (!existsSync(dir)) return []
|
|
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
const path = join(dir, entry.name)
|
|
return entry.isDirectory() ? walk(path) : [path]
|
|
})
|
|
}
|
|
|
|
function fail(message) {
|
|
console.error(`[release] ${message}`)
|
|
process.exit(1)
|
|
}
|
|
|
|
const files = walk(root)
|
|
if (files.length === 0) fail(`No release artifacts found under ${root}`)
|
|
|
|
if (platform === 'macos') {
|
|
const appExecutable = files.find(path => /\.app\/Contents\/MacOS\/[^/]+$/.test(path))
|
|
const dmg = files.find(path => path.includes('/dmg/') && path.endsWith('.dmg'))
|
|
if (!appExecutable || !dmg) fail('macOS release must contain an .app executable and a .dmg')
|
|
assertWithinSize(appExecutable, maxBinaryBytes)
|
|
assertWithinSize(dmg, maxInstallerBytes)
|
|
const architectures = execFileSync('lipo', ['-archs', appExecutable], { encoding: 'utf8' }).trim().split(/\s+/)
|
|
if (!architectures.includes('arm64')) fail(`Expected arm64 app, found: ${architectures.join(', ')}`)
|
|
if (requireSigning) {
|
|
execFileSync('codesign', ['--verify', '--deep', '--strict', appExecutable.slice(0, appExecutable.indexOf('.app/') + 4)], { stdio: 'inherit' })
|
|
const inspection = spawnSync('codesign', ['-dv', '--verbose=4', appExecutable.slice(0, appExecutable.indexOf('.app/') + 4)], { encoding: 'utf8' })
|
|
if (inspection.status !== 0) fail(`Unable to inspect macOS signature: ${inspection.stderr}`)
|
|
const detail = `${inspection.stdout}\n${inspection.stderr}`
|
|
if (/Signature=adhoc/.test(detail)) fail('macOS application is only ad-hoc signed')
|
|
execFileSync('spctl', ['--assess', '--type', 'execute', '--verbose=2', appExecutable.slice(0, appExecutable.indexOf('.app/') + 4)], { stdio: 'inherit' })
|
|
}
|
|
console.log(`[release] macOS arm64 artifacts verified: ${root}`)
|
|
} else if (platform === 'windows') {
|
|
const executables = files.filter(path => path.toLowerCase().endsWith('.exe'))
|
|
const installers = files.filter(path => /\.(?:msi|exe)$/i.test(path))
|
|
if (executables.length === 0 || installers.length === 0) fail('Windows release must contain an x64 executable or installer')
|
|
for (const executable of executables) {
|
|
assertWithinSize(executable, maxBinaryBytes)
|
|
if (readPeMachine(executable) !== 0x8664) fail(`${executable} is not an x86_64 PE executable`)
|
|
if (requireSigning) {
|
|
execFileSync('powershell', ['-NoProfile', '-Command', `$s = Get-AuthenticodeSignature -LiteralPath '${executable.replaceAll("'", "''")}'; if ($s.Status -ne 'Valid') { throw \"Invalid signature: $($s.Status)\" }`], { stdio: 'inherit' })
|
|
}
|
|
}
|
|
for (const installer of installers) assertWithinSize(installer, maxInstallerBytes)
|
|
console.log(`[release] Windows x64 artifacts verified: ${root}`)
|
|
} else {
|
|
fail(`Unsupported platform: ${platform}`)
|
|
}
|