mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 08:03:33 +00:00
154 lines
4.8 KiB
JavaScript
154 lines
4.8 KiB
JavaScript
import { spawn } from 'node:child_process'
|
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve } from 'node:path'
|
|
|
|
const repoRoot = resolve(import.meta.dirname, '..', '..')
|
|
const packageRoot = join(repoRoot, 'packages', 'orca-server')
|
|
const temporaryRoot = mkdtempSync(join(tmpdir(), 'orca-server-package-'))
|
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
|
|
try {
|
|
const packOutput = await runCapture(npmCommand, [
|
|
'pack',
|
|
packageRoot,
|
|
'--json',
|
|
'--pack-destination',
|
|
temporaryRoot
|
|
])
|
|
const [packed] = JSON.parse(packOutput)
|
|
if (!packed || typeof packed.filename !== 'string') {
|
|
throw new Error('npm pack returned no tarball')
|
|
}
|
|
verifyInventory(packed)
|
|
|
|
const installRoot = join(temporaryRoot, 'install')
|
|
mkdirSync(installRoot)
|
|
writeFileSync(
|
|
join(installRoot, 'package.json'),
|
|
JSON.stringify({ private: true, name: 'orca-server-package-verifier', version: '0.0.0' })
|
|
)
|
|
await run(npmCommand, [
|
|
'install',
|
|
join(temporaryRoot, packed.filename),
|
|
'--no-audit',
|
|
'--no-fund',
|
|
'--prefix',
|
|
installRoot
|
|
])
|
|
|
|
const installedRoot = join(installRoot, 'node_modules', '@stablyai', 'orca')
|
|
const installedManifest = JSON.parse(readFileSync(join(installedRoot, 'package.json'), 'utf8'))
|
|
verifyManifest(installedManifest)
|
|
const cliPath = join(installedRoot, 'dist', 'cli.js')
|
|
const help = await runCapture(npmCommand, [
|
|
'--prefix',
|
|
installRoot,
|
|
'exec',
|
|
'--',
|
|
'orca',
|
|
'--help'
|
|
])
|
|
if (!help.includes('Usage: orca-ide')) {
|
|
throw new Error('installed CLI help is unavailable')
|
|
}
|
|
const version = (
|
|
await runCapture(npmCommand, ['--prefix', installRoot, 'exec', '--', 'orca-ide', '--version'])
|
|
).trim()
|
|
if (version !== installedManifest.version) {
|
|
throw new Error('installed CLI version is inconsistent')
|
|
}
|
|
await run(process.execPath, [
|
|
join(repoRoot, 'config', 'scripts', 'run-node-server-runtime-verifier.mjs'),
|
|
'--cli',
|
|
cliPath
|
|
])
|
|
process.stdout.write(
|
|
`Node server package verification passed (${packed.size} bytes packed, ${packed.unpackedSize} bytes unpacked)\n`
|
|
)
|
|
} finally {
|
|
rmSync(temporaryRoot, { force: true, recursive: true })
|
|
}
|
|
|
|
function verifyInventory(packed) {
|
|
const files = new Map(packed.files.map((file) => [file.path, file]))
|
|
for (const required of [
|
|
'LICENSE',
|
|
'README.md',
|
|
'package.json',
|
|
'dist/cli.js',
|
|
'dist/daemon-entry.js',
|
|
'dist/LICENSE',
|
|
'dist/web/web-index.html'
|
|
]) {
|
|
if (!files.has(required)) {
|
|
throw new Error(`tarball is missing ${required}`)
|
|
}
|
|
}
|
|
if ((files.get('dist/cli.js')?.mode & 0o111) === 0) {
|
|
throw new Error('CLI is not executable')
|
|
}
|
|
if (packed.unpackedSize > 100 * 1024 * 1024) {
|
|
throw new Error('tarball exceeds 100 MiB')
|
|
}
|
|
for (const path of files.keys()) {
|
|
if (/electron|agent-browser|serve-sim|sherpa-onnx|\.map$/i.test(path)) {
|
|
throw new Error(`tarball contains forbidden file: ${path}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
function verifyManifest(manifest) {
|
|
if (manifest.name !== '@stablyai/orca') {
|
|
throw new Error('installed package name is inconsistent')
|
|
}
|
|
if (manifest.license !== 'MIT') {
|
|
throw new Error('installed package license is inconsistent')
|
|
}
|
|
if (manifest.bin?.orca !== 'dist/cli.js' || manifest.bin?.['orca-ide'] !== 'dist/cli.js') {
|
|
throw new Error('installed package bins are inconsistent')
|
|
}
|
|
const dependencies = Object.keys(manifest.dependencies ?? {})
|
|
if (dependencies.some((name) => name === 'electron' || name === 'electron-updater')) {
|
|
throw new Error('installed package depends on Electron')
|
|
}
|
|
}
|
|
|
|
function run(command, args) {
|
|
return new Promise((resolveRun, rejectRun) => {
|
|
const child = spawn(command, args, { stdio: 'inherit' })
|
|
child.once('error', rejectRun)
|
|
child.once('exit', (code, signal) => {
|
|
if (signal) {
|
|
rejectRun(new Error(`${command} exited with signal ${signal}`))
|
|
} else if (code !== 0) {
|
|
rejectRun(new Error(`${command} exited with code ${code}`))
|
|
} else {
|
|
resolveRun()
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function runCapture(command, args) {
|
|
return new Promise((resolveRun, rejectRun) => {
|
|
const child = spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] })
|
|
let stdout = ''
|
|
let stderr = ''
|
|
child.stdout.setEncoding('utf8')
|
|
child.stderr.setEncoding('utf8')
|
|
child.stdout.on('data', (chunk) => (stdout += chunk))
|
|
child.stderr.on('data', (chunk) => (stderr += chunk))
|
|
child.once('error', rejectRun)
|
|
child.once('exit', (code, signal) => {
|
|
if (signal) {
|
|
rejectRun(new Error(`${command} exited with signal ${signal}`))
|
|
} else if (code !== 0) {
|
|
rejectRun(new Error(`${command} exited with code ${code}: ${stderr}`))
|
|
} else {
|
|
resolveRun(stdout)
|
|
}
|
|
})
|
|
})
|
|
}
|