#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; 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'); function fail(message) { console.error(`[bundle-opencode] ${message}`); process.exit(1); } 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 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 runtimeDependencies = { ...(packageJson.dependencies && typeof packageJson.dependencies === 'object' ? packageJson.dependencies : {}), ...(packageJson.optionalDependencies && typeof packageJson.optionalDependencies === 'object' ? packageJson.optionalDependencies : {}), }; const platformMap = { darwin: 'darwin', linux: 'linux', win32: 'windows', }; const archMap = { x64: 'x64', arm64: 'arm64', arm: 'arm', }; function copyRuntimeDependency(packageName) { let dependencyPackageJsonPath; try { dependencyPackageJsonPath = packageRequire.resolve(`${packageName}/package.json`); } catch { console.warn(`[bundle-opencode] optional dependency not installed: ${packageName}`); return false; } const dependencyRoot = path.dirname(dependencyPackageJsonPath); const outputPackageDir = path.join(outputDir, 'node_modules', ...packageName.split('/')); fs.rmSync(outputPackageDir, { recursive: true, force: true }); fs.mkdirSync(path.dirname(outputPackageDir), { recursive: true }); fs.cpSync(dependencyRoot, outputPackageDir, { recursive: true, dereference: true, filter: (src) => !src.includes(`${path.sep}.git${path.sep}`), }); return true; } function getNativePackageCandidates() { const platform = platformMap[process.platform] ?? process.platform; const arch = archMap[process.arch] ?? process.arch; const base = `opencode-${platform}-${arch}`; if (arch === 'x64') { return [`${base}-baseline`, base]; } return [base]; } function bundleNativeLauncher() { const binaryName = process.platform === '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 ${process.platform}/${process.arch}. Tried ${getNativePackageCandidates().join(', ')}.`, ); } const copiedDependencies = Object.keys(runtimeDependencies).filter(copyRuntimeDependency); const nativeLauncher = bundleNativeLauncher(); console.log( `[bundle-opencode] bundled opencode-ai@${packageJson.version ?? 'unknown'} with ${copiedDependencies.length} runtime dependency package(s) and native launcher ${nativeLauncher.packageName} -> ${path.relative(root, outputDir)}`, );