build.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #!/usr/bin/env bun
  2. import { $ } from "bun"
  3. import fs from "fs"
  4. import path from "path"
  5. import { fileURLToPath } from "url"
  6. import { createSolidTransformPlugin } from "@opentui/solid/bun-plugin"
  7. const __filename = fileURLToPath(import.meta.url)
  8. const __dirname = path.dirname(__filename)
  9. const dir = path.resolve(__dirname, "..")
  10. process.chdir(dir)
  11. await import("./generate.ts")
  12. import { Script } from "@opencode-ai/script"
  13. import pkg from "../package.json"
  14. // Load migrations from migration directories
  15. const migrationDirs = (
  16. await fs.promises.readdir(path.join(dir, "migration"), {
  17. withFileTypes: true,
  18. })
  19. )
  20. .filter((entry) => entry.isDirectory() && /^\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}/.test(entry.name))
  21. .map((entry) => entry.name)
  22. .sort()
  23. const migrations = await Promise.all(
  24. migrationDirs.map(async (name) => {
  25. const file = path.join(dir, "migration", name, "migration.sql")
  26. const sql = await Bun.file(file).text()
  27. const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name)
  28. const timestamp = match
  29. ? Date.UTC(
  30. Number(match[1]),
  31. Number(match[2]) - 1,
  32. Number(match[3]),
  33. Number(match[4]),
  34. Number(match[5]),
  35. Number(match[6]),
  36. )
  37. : 0
  38. return { sql, timestamp, name }
  39. }),
  40. )
  41. console.log(`Loaded ${migrations.length} migrations`)
  42. const singleFlag = process.argv.includes("--single")
  43. const baselineFlag = process.argv.includes("--baseline")
  44. const skipInstall = process.argv.includes("--skip-install")
  45. const plugin = createSolidTransformPlugin()
  46. const skipEmbedWebUi = process.argv.includes("--skip-embed-web-ui")
  47. const createEmbeddedWebUIBundle = async () => {
  48. console.log(`Building Web UI to embed in the binary`)
  49. const appDir = path.join(import.meta.dirname, "../../app")
  50. const dist = path.join(appDir, "dist")
  51. await $`bun run --cwd ${appDir} build`
  52. const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: dist })))
  53. .map((file) => file.replaceAll("\\", "/"))
  54. .sort()
  55. const imports = files.map((file, i) => {
  56. const spec = path.relative(dir, path.join(dist, file)).replaceAll("\\", "/")
  57. return `import file_${i} from ${JSON.stringify(spec.startsWith(".") ? spec : `./${spec}`)} with { type: "file" };`
  58. })
  59. const entries = files.map((file, i) => ` ${JSON.stringify(file)}: file_${i},`)
  60. return [
  61. `// Import all files as file_$i with type: "file"`,
  62. ...imports,
  63. `// Export with original mappings`,
  64. `export default {`,
  65. ...entries,
  66. `}`,
  67. ].join("\n")
  68. }
  69. const embeddedFileMap = skipEmbedWebUi ? null : await createEmbeddedWebUIBundle()
  70. const allTargets: {
  71. os: string
  72. arch: "arm64" | "x64"
  73. abi?: "musl"
  74. avx2?: false
  75. }[] = [
  76. {
  77. os: "linux",
  78. arch: "arm64",
  79. },
  80. {
  81. os: "linux",
  82. arch: "x64",
  83. },
  84. {
  85. os: "linux",
  86. arch: "x64",
  87. avx2: false,
  88. },
  89. {
  90. os: "linux",
  91. arch: "arm64",
  92. abi: "musl",
  93. },
  94. {
  95. os: "linux",
  96. arch: "x64",
  97. abi: "musl",
  98. },
  99. {
  100. os: "linux",
  101. arch: "x64",
  102. abi: "musl",
  103. avx2: false,
  104. },
  105. {
  106. os: "darwin",
  107. arch: "arm64",
  108. },
  109. {
  110. os: "darwin",
  111. arch: "x64",
  112. },
  113. {
  114. os: "darwin",
  115. arch: "x64",
  116. avx2: false,
  117. },
  118. // kilocode_change start - Windows ARM64 target
  119. {
  120. os: "win32",
  121. arch: "arm64",
  122. },
  123. // kilocode_change end
  124. {
  125. os: "win32",
  126. arch: "x64",
  127. },
  128. {
  129. os: "win32",
  130. arch: "x64",
  131. avx2: false,
  132. },
  133. // kilocode_change start - added Windows ARM64 target
  134. {
  135. os: "win32",
  136. arch: "arm64",
  137. },
  138. // kilocode_change end
  139. ]
  140. const targets = singleFlag
  141. ? allTargets.filter((item) => {
  142. if (item.os !== process.platform || item.arch !== process.arch) {
  143. return false
  144. }
  145. // When building for the current platform, prefer a single native binary by default.
  146. // Baseline binaries require additional Bun artifacts and can be flaky to download.
  147. if (item.avx2 === false) {
  148. return baselineFlag
  149. }
  150. // also skip abi-specific builds for the same reason
  151. if (item.abi !== undefined) {
  152. return false
  153. }
  154. return true
  155. })
  156. : allTargets
  157. await $`rm -rf dist`
  158. const binaries: Record<string, string> = {}
  159. if (!skipInstall) {
  160. await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
  161. await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
  162. }
  163. for (const item of targets) {
  164. const name = [
  165. pkg.name,
  166. // changing to win32 flags npm for some reason
  167. item.os === "win32" ? "windows" : item.os,
  168. item.arch,
  169. item.avx2 === false ? "baseline" : undefined,
  170. item.abi === undefined ? undefined : item.abi,
  171. ]
  172. .filter(Boolean)
  173. .join("-")
  174. console.log(`building ${name}`)
  175. await $`mkdir -p dist/${name}/bin`
  176. const localPath = path.resolve(dir, "node_modules/@opentui/core/parser.worker.js")
  177. const rootPath = path.resolve(dir, "../../node_modules/@opentui/core/parser.worker.js")
  178. const parserWorker = fs.realpathSync(fs.existsSync(localPath) ? localPath : rootPath)
  179. const workerPath = "./src/cli/cmd/tui/worker.ts"
  180. // Use platform-specific bunfs root path based on target OS
  181. const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/"
  182. const workerRelativePath = path.relative(dir, parserWorker).replaceAll("\\", "/")
  183. await Bun.build({
  184. conditions: ["browser"],
  185. tsconfig: "./tsconfig.json",
  186. plugins: [plugin],
  187. external: ["node-gyp"],
  188. compile: {
  189. autoloadBunfig: false,
  190. autoloadDotenv: false,
  191. autoloadTsconfig: true,
  192. autoloadPackageJson: true,
  193. target: name.replace(pkg.name, "bun") as any,
  194. outfile: `dist/${name}/bin/kilo`, // kilocode_change
  195. execArgv: [`--user-agent=kilo/${Script.version}`, "--use-system-ca", "--"], // kilocode_change
  196. windows: {},
  197. },
  198. files: {
  199. ...(embeddedFileMap ? { "opencode-web-ui.gen.ts": embeddedFileMap } : {}),
  200. },
  201. entrypoints: ["./src/index.ts", parserWorker, workerPath, ...(embeddedFileMap ? ["opencode-web-ui.gen.ts"] : [])],
  202. define: {
  203. KILO_VERSION: `'${Script.version}'`,
  204. KILO_MIGRATIONS: JSON.stringify(migrations),
  205. OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath,
  206. KILO_WORKER_PATH: workerPath,
  207. KILO_CHANNEL: `'${Script.channel}'`,
  208. KILO_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "",
  209. },
  210. })
  211. // kilocode_change start - fix Nix-specific ELF interpreter paths for Linux binaries
  212. if (item.os === "linux") {
  213. const interpreters: Record<string, string> = {
  214. x64: "/lib64/ld-linux-x86-64.so.2",
  215. arm64: "/lib/ld-linux-aarch64.so.1",
  216. "x64-musl": "/lib/ld-musl-x86_64.so.1",
  217. "arm64-musl": "/lib/ld-musl-aarch64.so.1",
  218. }
  219. const key = item.abi === "musl" ? `${item.arch}-musl` : item.arch
  220. const interpreter = interpreters[key]
  221. if (interpreter) {
  222. try {
  223. await $`patchelf --set-interpreter ${interpreter} dist/${name}/bin/kilo`
  224. console.log(`patched interpreter for ${name} -> ${interpreter}`)
  225. } catch {
  226. console.warn(`patchelf not available, skipping interpreter fix for ${name}`)
  227. }
  228. }
  229. }
  230. // kilocode_change end
  231. // Smoke test: only run if binary is for current platform
  232. if (item.os === process.platform && item.arch === process.arch && !item.abi) {
  233. const binaryPath = `dist/${name}/bin/kilo` // kilocode_change
  234. console.log(`Running smoke test: ${binaryPath} --version`)
  235. try {
  236. const versionOutput = await $`${binaryPath} --version`.text()
  237. console.log(`Smoke test passed: ${versionOutput.trim()}`)
  238. } catch (e) {
  239. console.error(`Smoke test failed for ${name}:`, e)
  240. process.exit(1)
  241. }
  242. }
  243. await $`rm -rf ./dist/${name}/bin/tui`
  244. await Bun.file(`dist/${name}/package.json`).write(
  245. JSON.stringify(
  246. {
  247. name,
  248. version: Script.version,
  249. os: [item.os],
  250. cpu: [item.arch],
  251. // kilocode_change start
  252. repository: {
  253. type: "git",
  254. url: "https://github.com/Kilo-Org/kilocode",
  255. },
  256. // kilocode_change end
  257. },
  258. null,
  259. 2,
  260. ),
  261. )
  262. binaries[name] = Script.version
  263. }
  264. if (Script.release) {
  265. const archives: string[] = [] // kilocode_change
  266. for (const key of Object.keys(binaries)) {
  267. const archive = key.replace(pkg.name, "kilo") // kilocode_change
  268. if (key.includes("linux")) {
  269. const out = path.resolve("dist", `${archive}.tar.gz`) // kilocode_change
  270. await $`tar -czf ${out} *`.cwd(`dist/${key}/bin`) // kilocode_change
  271. archives.push(out) // kilocode_change
  272. } else {
  273. const out = path.resolve("dist", `${archive}.zip`) // kilocode_change
  274. await $`zip -r ${out} *`.cwd(`dist/${key}/bin`) // kilocode_change
  275. archives.push(out) // kilocode_change
  276. }
  277. }
  278. await $`gh release upload v${Script.version} ${archives} --clobber` // kilocode_change
  279. }
  280. export { binaries }