#!/usr/bin/env bun import { $ } from "bun" import fs from "fs" import path from "path" import { fileURLToPath } from "url" import solidPlugin from "@opentui/solid/bun-plugin" const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const dir = path.resolve(__dirname, "..") process.chdir(dir) import { Script } from "@opencode-ai/script" import pkg from "../package.json" // tfcode version const TFCODE_VERSION = pkg.version const TFCODE_NAME = "tfcode" // Fetch models snapshot const modelsUrl = process.env.TFCODE_MODELS_URL || "https://models.dev" const modelsData = process.env.MODELS_DEV_API_JSON ? await Bun.file(process.env.MODELS_DEV_API_JSON).text() : await fetch(`${modelsUrl}/api.json`).then((x) => x.text()) await Bun.write( path.join(dir, "src/provider/models-snapshot.ts"), `// Auto-generated by build.ts - do not edit\nexport const snapshot = ${modelsData} as const\n`, ) console.log("Generated models-snapshot.ts") // Load migrations const migrationDirs = ( await fs.promises.readdir(path.join(dir, "migration"), { withFileTypes: true, }) ) .filter((entry) => entry.isDirectory() && /^\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}/.test(entry.name)) .map((entry) => entry.name) .sort() const migrations = await Promise.all( migrationDirs.map(async (name) => { const file = path.join(dir, "migration", name, "migration.sql") const sql = await Bun.file(file).text() const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name) const timestamp = match ? Date.UTC( Number(match[1]), Number(match[2]) - 1, Number(match[3]), Number(match[4]), Number(match[5]), Number(match[6]), ) : 0 return { sql, timestamp, name } }), ) console.log(`Loaded ${migrations.length} migrations`) const singleFlag = process.argv.includes("--single") const baselineFlag = process.argv.includes("--baseline") const skipInstall = process.argv.includes("--skip-install") const allTargets: { os: string arch: "arm64" | "x64" abi?: "musl" avx2?: false }[] = [ { os: "linux", arch: "arm64" }, { os: "linux", arch: "x64" }, { os: "linux", arch: "x64", avx2: false }, { os: "linux", arch: "arm64", abi: "musl" }, { os: "linux", arch: "x64", abi: "musl" }, { os: "linux", arch: "x64", abi: "musl", avx2: false }, { os: "darwin", arch: "arm64" }, { os: "darwin", arch: "x64" }, { os: "darwin", arch: "x64", avx2: false }, { os: "win32", arch: "arm64" }, { os: "win32", arch: "x64" }, { os: "win32", arch: "x64", avx2: false }, ] const targets = singleFlag ? allTargets.filter((item) => { if (item.os !== process.platform || item.arch !== process.arch) return false if (item.avx2 === false) return baselineFlag if (item.abi !== undefined) return false return true }) : allTargets await $`rm -rf dist` const binaries: Record = {} if (!skipInstall) { await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}` await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}` } for (const item of targets) { const name = [ TFCODE_NAME, item.os === "win32" ? "windows" : item.os, item.arch, item.avx2 === false ? "baseline" : undefined, item.abi === undefined ? undefined : item.abi, ] .filter(Boolean) .join("-") console.log(`Building ${name}`) await $`mkdir -p dist/${name}/bin` const localPath = path.resolve(dir, "node_modules/@opentui/core/parser.worker.js") const rootPath = path.resolve(dir, "../../node_modules/@opentui/core/parser.worker.js") const parserWorker = fs.realpathSync(fs.existsSync(localPath) ? localPath : rootPath) const workerPath = "./src/cli/cmd/tui/worker.ts" const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/" const workerRelativePath = path.relative(dir, parserWorker).replaceAll("\\", "/") await Bun.build({ conditions: ["browser"], tsconfig: "./tsconfig.json", plugins: [solidPlugin], compile: { autoloadBunfig: false, autoloadDotenv: false, autoloadTsconfig: true, autoloadPackageJson: true, target: name.replace(TFCODE_NAME, "bun") as any, outfile: `dist/${name}/bin/tfcode`, execArgv: [`--user-agent=tfcode/${TFCODE_VERSION}`, "--use-system-ca", "--"], windows: {}, }, entrypoints: ["./src/index.ts", parserWorker, workerPath], define: { OPENCODE_VERSION: `'${TFCODE_VERSION}'`, OPENCODE_MIGRATIONS: JSON.stringify(migrations), OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath, OPENCODE_WORKER_PATH: workerPath, OPENCODE_CHANNEL: `'${Script.channel}'`, OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "", }, }) // Smoke test if (item.os === process.platform && item.arch === process.arch && !item.abi) { const binaryPath = `dist/${name}/bin/tfcode` console.log(`Running smoke test: ${binaryPath} --version`) try { const versionOutput = await $`${binaryPath} --version`.text() console.log(`Smoke test passed: ${versionOutput.trim()}`) } catch (e) { console.error(`Smoke test failed for ${name}:`, e) process.exit(1) } } await $`rm -rf ./dist/${name}/bin/tui` await Bun.file(`dist/${name}/package.json`).write( JSON.stringify( { name: `@toothfairyai/${name}`, version: TFCODE_VERSION, os: [item.os], cpu: [item.arch], }, null, 2, ), ) binaries[name] = TFCODE_VERSION } // Package for release if (Script.release || process.env.TFCODE_RELEASE) { console.log("Packaging for release...") for (const key of Object.keys(binaries)) { if (key.includes("linux")) { await $`tar -czf ../../${key}.tar.gz *`.cwd(`dist/${key}/bin`) } else { await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`) } } console.log("Binaries packaged. Upload to Gitea releases manually or use publish.ts") } export { binaries, TFCODE_VERSION }