#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import { createRequire } from 'node:module'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(__dirname, '..'); const require = createRequire(import.meta.url); const outputDir = path.join(root, 'build', 'opencode-ai'); const rootPackageJson = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); const targetArgs = process.argv.slice(2); for (const arg of targetArgs) { if (!/^--(?:platform|arch)=\S+$/.test(arg)) { throw new Error(`Unknown bundle target option: ${arg}`); } } function targetOption(name, fallback) { const prefix = `--${name}=`; const option = targetArgs.find((arg) => arg.startsWith(prefix)); return option ? option.slice(prefix.length) : fallback; } const targetPlatform = targetOption('platform', process.platform); const targetArch = targetOption('arch', process.arch); function fail(message) { console.error(`[bundle-opencode] ${message}`); process.exit(1); } if (targetPlatform !== process.platform) { fail(`Cross-OS OpenCode staging is not supported: host ${process.platform}, target ${targetPlatform}.`); } function resolvePackageRoot(packageName) { try { return path.dirname(require.resolve(`${packageName}/package.json`, { paths: [root], })); } catch { return null; } } const packageRoot = resolvePackageRoot('opencode-ai'); if (!packageRoot) { fail('Missing dependency "opencode-ai". Run npm install or pnpm install before packaging.'); } const packageJsonPath = path.join(packageRoot, 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const declaredVersion = rootPackageJson.devDependencies?.['opencode-ai'] ?? rootPackageJson.dependencies?.['opencode-ai']; if (declaredVersion !== packageJson.version) { fail( `The opencode-ai dependency must be pinned exactly to the bundled version. Declared ${declaredVersion ?? 'missing'}, installed ${packageJson.version ?? 'unknown'}.`, ); } const packageRequire = createRequire(packageJsonPath); const bin = packageJson.bin; const hasOpencodeBin = typeof bin === 'string' || (bin && typeof bin === 'object' && typeof bin.opencode === 'string'); if (!hasOpencodeBin) { fail('Installed "opencode-ai" package does not expose an "opencode" binary.'); } fs.rmSync(outputDir, { recursive: true, force: true }); fs.mkdirSync(path.dirname(outputDir), { recursive: true }); fs.cpSync(packageRoot, outputDir, { recursive: true, dereference: true, filter: (src) => { if (src.includes(`${path.sep}.git${path.sep}`)) return false; if (path.relative(packageRoot, src).split(path.sep)[0] === 'node_modules') return false; return true; }, }); const platformMap = { darwin: 'darwin', linux: 'linux', win32: 'windows', }; const archMap = { x64: 'x64', arm64: 'arm64', arm: 'arm', }; function getNativePackageCandidates() { const platform = platformMap[targetPlatform] ?? targetPlatform; const arch = archMap[targetArch] ?? targetArch; const base = `opencode-${platform}-${arch}`; if (arch === 'x64') { return [`${base}-baseline`, base]; } return [base]; } function bundleNativeLauncher() { const binaryName = targetPlatform === 'win32' ? 'opencode.exe' : 'opencode'; const outputBinary = path.join(outputDir, 'bin', binaryName); for (const packageName of getNativePackageCandidates()) { let dependencyPackageJsonPath; try { dependencyPackageJsonPath = packageRequire.resolve(`${packageName}/package.json`); } catch { continue; } const dependencyRoot = path.dirname(dependencyPackageJsonPath); const nativeBinary = path.join(dependencyRoot, 'bin', binaryName); if (!fs.existsSync(nativeBinary)) { continue; } fs.copyFileSync(nativeBinary, outputBinary); fs.chmodSync(outputBinary, 0o755); return { packageName, outputBinary }; } fail( `Missing native opencode binary for ${targetPlatform}/${targetArch}. Tried ${getNativePackageCandidates().join(', ')}.`, ); } const nativeLauncher = bundleNativeLauncher(); const verificationEnv = { ...process.env, PATH: process.platform === 'win32' ? [process.env.SystemRoot && path.join(process.env.SystemRoot, 'System32'), process.env.SystemRoot] .filter(Boolean) .join(path.delimiter) : '/usr/bin:/bin', }; delete verificationEnv.NODE_PATH; delete verificationEnv.BUN_INSTALL; const versionProbe = spawnSync(nativeLauncher.outputBinary, ['--version'], { cwd: outputDir, encoding: 'utf8', env: verificationEnv, }); if (versionProbe.error || versionProbe.status !== 0) { fail( `Bundled native launcher failed its self-contained version probe: ${versionProbe.error?.message ?? versionProbe.stderr ?? versionProbe.stdout}`, ); } const reportedVersion = `${versionProbe.stdout ?? ''}${versionProbe.stderr ?? ''}`.trim(); if (reportedVersion !== packageJson.version) { fail(`Bundled native launcher reported ${reportedVersion || 'no version'}, expected ${packageJson.version}.`); } console.log( `[bundle-opencode] bundled and verified self-contained opencode-ai@${packageJson.version} launcher ${nativeLauncher.packageName} -> ${path.relative(root, outputDir)}`, );