Files
Hermes-ui/packages/desktop/scripts/package-runtime.mjs
T

122 lines
3.9 KiB
JavaScript
Raw Normal View History

2026-06-02 08:55:17 +08:00
#!/usr/bin/env node
// Package prepared Python/Node/Git runtime resources into a release asset.
import {
cpSync,
createReadStream,
existsSync,
mkdirSync,
mkdtempSync,
rmSync,
statSync,
writeFileSync,
} from 'node:fs'
import { createHash } from 'node:crypto'
import { arch as osArch, platform as osPlatform, tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawnSync } from 'node:child_process'
const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(__dirname, '..')
const TARGET_OS = process.env.TARGET_OS || osPlatform()
const TARGET_ARCH = process.env.TARGET_ARCH || osArch()
const OS_LABEL = TARGET_OS === 'win32' ? 'win' : TARGET_OS === 'darwin' ? 'mac' : TARGET_OS
const PLATFORM = `${OS_LABEL}-${TARGET_ARCH}`
const OUT_DIR = resolve(ROOT, 'release', 'runtime')
const PY_DIR = resolve(ROOT, 'resources', 'python', PLATFORM)
const NODE_DIR = resolve(ROOT, 'resources', 'node', PLATFORM)
const GIT_DIR = resolve(ROOT, 'resources', 'git', PLATFORM)
const pyBin = TARGET_OS === 'win32'
? resolve(PY_DIR, 'python.exe')
: resolve(PY_DIR, 'bin', 'python3')
function run(command, args, options = {}) {
const result = spawnSync(command, args, { stdio: 'inherit', ...options })
if (result.status !== 0) process.exit(result.status ?? 1)
return result
}
function output(command, args) {
const result = spawnSync(command, args, { encoding: 'utf-8' })
if (result.status !== 0) {
process.stderr.write(result.stderr || result.stdout || '')
process.exit(result.status ?? 1)
}
return result.stdout.trim()
}
async function sha256File(file) {
const hash = createHash('sha256')
await new Promise((resolvePromise, rejectPromise) => {
const stream = createReadStream(file)
stream.on('data', chunk => hash.update(chunk))
stream.on('end', resolvePromise)
stream.on('error', rejectPromise)
})
return hash.digest('hex')
}
for (const dir of [PY_DIR, NODE_DIR]) {
if (!existsSync(dir)) {
console.error(`Runtime directory missing: ${dir}`)
process.exit(1)
}
}
const hermesAgentVersion = output(pyBin, [
'-c',
'import importlib.metadata as m; print(m.version("hermes-agent"))',
])
const assetName = `hermes-runtime-hermes-agent-${hermesAgentVersion}-${PLATFORM}.tar.gz`
const manifestName = `hermes-runtime-${PLATFORM}.json`
mkdirSync(OUT_DIR, { recursive: true })
const stage = mkdtempSync(join(tmpdir(), `hermes-runtime-${PLATFORM}-`))
try {
cpSync(PY_DIR, join(stage, 'python'), { recursive: true, force: true, verbatimSymlinks: true })
cpSync(NODE_DIR, join(stage, 'node'), { recursive: true, force: true, verbatimSymlinks: true })
if (existsSync(GIT_DIR)) {
cpSync(GIT_DIR, join(stage, 'git'), { recursive: true, force: true, verbatimSymlinks: true })
} else {
mkdirSync(join(stage, 'git'), { recursive: true })
writeFileSync(join(stage, 'git', '.placeholder'), 'Git for Windows is only bundled on Windows.\n')
}
const runtimeManifest = {
schema: 1,
platform: PLATFORM,
targetOs: TARGET_OS,
targetArch: TARGET_ARCH,
hermesAgentVersion,
asset: {
name: assetName,
},
}
writeFileSync(join(stage, 'runtime-manifest.json'), JSON.stringify(runtimeManifest, null, 2) + '\n')
const assetPath = resolve(OUT_DIR, assetName)
rmSync(assetPath, { force: true })
run('tar', ['-czf', assetPath, '-C', stage, '.'])
const sha256 = await sha256File(assetPath)
writeFileSync(`${assetPath}.sha256`, `${sha256} ${assetName}\n`)
const platformManifest = {
...runtimeManifest,
createdAt: new Date().toISOString(),
asset: {
name: assetName,
sha256,
size: statSync(assetPath).size,
},
}
writeFileSync(resolve(OUT_DIR, manifestName), JSON.stringify(platformManifest, null, 2) + '\n')
console.log(`Runtime asset: ${assetPath}`)
console.log(`Runtime manifest: ${resolve(OUT_DIR, manifestName)}`)
} finally {
rmSync(stage, { recursive: true, force: true })
}