diff --git a/.gitea/workflows/build-opencode.yaml b/.gitea/workflows/build-opencode.yaml index 055adba..170d92b 100644 --- a/.gitea/workflows/build-opencode.yaml +++ b/.gitea/workflows/build-opencode.yaml @@ -7,7 +7,32 @@ on: - 'opencode/*' - '.gitea/workflows/build-opencode.yaml' jobs: - "Build opencode docker images": + # "Build opencode docker images": + # runs-on: hostinger + # steps: + # - + # name: Login to Gitea container registry + # uses: docker/login-action@v3 + # with: + # registry: gitea.woggioni.net + # username: woggioni + # password: ${{ secrets.PUBLISHER_TOKEN }} + # - + # name: Build and push opencode images + # uses: docker/build-push-action@v6 + # with: + # builder: multiplatform-builder + # context: "{{defaultContext}}:opencode" + # build-args: | + # OPENCODE_VERSION=1.18.4 + # platforms: | + # linux/amd64 + # push: true + # pull: true + # tags: | + # "gitea.woggioni.net/woggioni/opencode:1.18.4" + # "gitea.woggioni.net/woggioni/opencode:latest" + build-from-source: runs-on: hostinger steps: - @@ -23,13 +48,13 @@ jobs: with: builder: multiplatform-builder context: "{{defaultContext}}:opencode" + file: from-source.dockerfile + ssh: default=/var/lib/gitea-runner/.ssh/id_ed25519 build-args: | - OPENCODE_VERSION=1.18.4 + VERSION=1.18.4 platforms: | linux/amd64 push: true pull: true tags: | - "gitea.woggioni.net/woggioni/opencode:1.18.4" - "gitea.woggioni.net/woggioni/opencode:latest" - + "gitea.woggioni.net/woggioni/opencode:test" diff --git a/opencode/Dockerfile b/opencode/Dockerfile index 02e78b3..21dcf7c 100644 --- a/opencode/Dockerfile +++ b/opencode/Dockerfile @@ -6,7 +6,7 @@ COPY mirrorlist /etc/pacman.d/mirrolist RUN pacman-key --init #RUN pacman-key --refresh-keys -ADD https://gitea.woggioni.net/api/packages/woggioni/arch/repository.key ./repository.key +ADD --keep-git-dir=true https://gitea.woggioni.net/api/packages/woggioni/arch/repository.key ./repository.key RUN pacman-key --add repository.key RUN pacman-key --lsign-key 0D28BF66FDB45D18D8EBEE5D4C91DADCD00B3F77 diff --git a/opencode/build.ts b/opencode/build.ts new file mode 100755 index 0000000..ba3f648 --- /dev/null +++ b/opencode/build.ts @@ -0,0 +1,219 @@ +#!/usr/bin/env bun + +import { $ } from "bun" +import path from "path" +import { fileURLToPath } from "url" +import { createSolidTransformPlugin } from "@opentui/solid/bun-plugin" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const dir = path.resolve(__dirname, "..") + +process.chdir(dir) + +const generated = await import("./generate.ts") + +import { Script } from "@opencode-ai/script" +import pkg from "../package.json" + +const singleFlag = process.argv.includes("--single") +const baselineFlag = process.argv.includes("--baseline") +const skipInstall = process.argv.includes("--skip-install") +const sourcemapsFlag = process.argv.includes("--sourcemaps") +const plugin = createSolidTransformPlugin() +const skipEmbedWebUi = process.argv.includes("--skip-embed-web-ui") + +const createEmbeddedWebUIBundle = async () => { + console.log(`Building Web UI to embed in the binary`) + const appDir = path.join(import.meta.dirname, "../../app") + const dist = path.join(appDir, "dist") + await $`OPENCODE_CHANNEL=${Script.channel} bun run --cwd ${appDir} build` + const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: dist }))) + .map((file) => file.replaceAll("\\", "/")) + .filter((file) => !file.endsWith(".map")) + .sort() + const imports = files.map((file, i) => { + const spec = path.relative(dir, path.join(dist, file)).replaceAll("\\", "/") + return `import file_${i} from ${JSON.stringify(spec.startsWith(".") ? spec : `./${spec}`)} with { type: "file" };` + }) + const entries = files.map((file, i) => ` ${JSON.stringify(file)}: file_${i},`) + return [ + `// Import all files as file_$i with type: "file"`, + ...imports, + `// Export with original mappings`, + `export default {`, + ...entries, + `}`, + ].join("\n") +} + +const embeddedFileMap = skipEmbedWebUi ? null : await createEmbeddedWebUIBundle() +const treeSitterWorker = await Bun.file(fileURLToPath(import.meta.resolve("@opentui/core/parser.worker"))).text() + +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, + } +] + +const targets = singleFlag + ? allTargets.filter((item) => { + if (item.os !== process.platform || item.arch !== process.arch) { + return false + } + + // When building for the current platform, prefer a single native binary by default. + // Baseline binaries require additional Bun artifacts and can be flaky to download. + if (item.avx2 === false) { + return baselineFlag + } + + // also skip abi-specific builds for the same reason + 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"]}` + await $`bun install --os="*" --cpu="*" @ff-labs/fff-bun@${pkg.dependencies["@ff-labs/fff-bun"]}` +} +for (const item of targets) { + const name = [ + pkg.name, + // changing to win32 flags npm for some reason + 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 workerPath = "./src/cli/tui/worker.ts" + const treeSitterWorkerPath = "opentui-tree-sitter-worker.js" + const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/" + + await Bun.build({ + conditions: ["bun", "node"], + tsconfig: "./tsconfig.json", + plugins: [plugin], + external: ["node-gyp"], + format: "esm", + minify: true, + sourcemap: sourcemapsFlag ? "linked" : "none", + splitting: true, + compile: { + autoloadBunfig: false, + autoloadDotenv: false, + autoloadTsconfig: true, + autoloadPackageJson: true, + target: name.replace(pkg.name, "bun") as any, + outfile: `dist/${name}/bin/opencode`, + execArgv: [`--user-agent=opencode/${Script.version}`, "--use-system-ca", "--"], + windows: {}, + }, + files: { + [treeSitterWorkerPath]: treeSitterWorker, + ...(embeddedFileMap ? { "opencode-web-ui.gen.ts": embeddedFileMap } : {}), + }, + entrypoints: [ + "./src/index.ts", + workerPath, + treeSitterWorkerPath, + ...(embeddedFileMap ? ["opencode-web-ui.gen.ts"] : []), + ], + define: { + FFF_LIBC: JSON.stringify(item.abi === "musl" ? "musl" : "gnu"), + OPENCODE_VERSION: `'${Script.version}'`, + OPENCODE_MODELS_DEV: generated.modelsData, + OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + treeSitterWorkerPath, + OPENCODE_WORKER_PATH: workerPath, + OPENCODE_CHANNEL: `'${Script.channel}'`, + OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "", + ...(item.os === "linux" ? { "process.env.OPENTUI_LIBC": JSON.stringify(item.abi ?? "glibc") } : {}), + }, + }) + + // Smoke test: only run if binary is for current platform + if (item.os === process.platform && item.arch === process.arch && !item.abi) { + const binaryPath = `dist/${name}/bin/opencode` + 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, + version: Script.version, + preferUnplugged: true, + os: [item.os], + cpu: [item.arch], + ...(item.abi ? { libc: [item.abi] } : {}), + }, + null, + 2, + ), + ) + binaries[name] = Script.version +} + +if (Script.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`) + } + } +} + +export { binaries } diff --git a/opencode/from-source.dockerfile b/opencode/from-source.dockerfile new file mode 100644 index 0000000..e47f11a --- /dev/null +++ b/opencode/from-source.dockerfile @@ -0,0 +1,51 @@ +FROM oven/bun:1.3.14-alpine AS builder + +#FROM node:25-alpine AS builder +ARG VERSION +ENTRYPOINT sh +RUN apk update +RUN --mount=type=cache,target=/var/cache/apk apk update +RUN --mount=type=cache,target=/var/cache/apk apk add python3 make g++ npm zip git +RUN adduser -D -h /home/builder -u 1126 builder +USER builder +WORKDIR /home/builder +ADD --chown=builder:builder git@github.com:anomalyco/opencode.git#v${VERSION} opencode +WORKDIR /home/builder/opencode +# RUN bun install + +RUN --mount=type=cache,target=/home/builder/.bun/install/cache,uid=1126 bun install +# WORKDIR /home/builder/opencode/packages/opencode +# ENV OPENCODE_CHANNEL=latest +# ENV OPENCODE_VERSION=${VERSION} +# ENV OPENCODE_RELEASE=${VERSION} +#bun run ./script/build.ts --single +# RUN --mount=type=cache,target=/home/builder/.bun/install/cache,uid=1126 ./packages/opencode/script/build.ts +ADD --chown=builder:builder ./build.ts ./packages/opencode/script/build.ts +# RUN --mount=type=cache,target=/home/builder/.bun/install/cache,uid=1126 bun build --compile \ +# --target="bun-linux-x64-musl" \ +# --outfile="dist/opencode-linux-x64-musl/bin/opencode" \ +# --external="node-gyp" \ +# --sourcemap=none \ +# --minify \ +# --splitting \ +# --conditions="bun" \ +# --conditions="node" \ +# --format="esm" \ +# --tsconfig-override="./packages/opencode/tsconfig.json" \ +# --define="FFF_LIBC=\"musl\"" \ +# --define="OPENCODE_LIBC=\"musl\"" \ +# --define="OPENCODE_CHANNEL=\"latest\"" \ +# --define="OPENCODE_VERSION=\"${VERSION}\"" \ +# --define="OTUI_TREE_SITTER_WORKER_PATH=/\$bunfs/root/opentui-tree-sitter-worker.js" \ +# --define="OPENCODE_WORKER_PATH=./packages/opencode/src/cli/tui/worker.ts" \ +# --define="process.env.OPENTUI_LIBC=\"musl\"" \ +# ./packages/opencode/src/index.ts \ +# ./packages/opencode/src/cli/tui/worker.ts + +RUN --mount=type=cache,target=/home/builder/.bun/install/cache,uid=1126 ./packages/opencode/script/build.ts + +#RUN rm ./opencode.tgz + +# RUN adduser -D -h /var/lib/opencode opencode +# USER opencode +# WORKDIR /var/lib/opencode