|
23 | 23 | "aarch64-linux" = "bun-linux-arm64"; |
24 | 24 | "x86_64-linux" = "bun-linux-x64"; |
25 | 25 | }; |
| 26 | + scripts = ./nix/scripts; |
26 | 27 | modelsDev = forEachSystem ( |
27 | 28 | system: |
28 | 29 | let |
29 | 30 | pkgs = pkgsFor system; |
30 | 31 | in |
31 | | - pkgs.stdenvNoCC.mkDerivation { |
32 | | - pname = "models-dev"; |
33 | | - version = "unstable"; |
34 | | - |
35 | | - src = pkgs.fetchurl { |
36 | | - url = "https://models.dev/api.json"; |
37 | | - hash = "sha256-Dff3OWJ7pD7LfVbZZ0Gf/QA65uw4ft14mdfBun0qDBg="; |
38 | | - }; |
39 | | - |
40 | | - dontUnpack = true; |
41 | | - dontBuild = true; |
42 | | - |
43 | | - installPhase = '' |
44 | | - mkdir -p $out/dist |
45 | | - cp $src $out/dist/_api.json |
46 | | - ''; |
47 | | - } |
| 32 | + pkgs.callPackage ./nix/models-dev.nix { } |
48 | 33 | ); |
49 | 34 | in |
50 | 35 | { |
|
70 | 55 | system: |
71 | 56 | let |
72 | 57 | pkgs = pkgsFor system; |
| 58 | + mkNodeModules = pkgs.callPackage ./nix/node-modules.nix { }; |
| 59 | + mkPackage = pkgs.callPackage ./nix/opencode.nix { }; |
73 | 60 | in |
74 | 61 | { |
75 | | - default = pkgs.callPackage ( |
76 | | - { |
77 | | - lib, |
78 | | - stdenv, |
79 | | - stdenvNoCC, |
80 | | - bun, |
81 | | - makeBinaryWrapper, |
82 | | - }: |
83 | | - stdenvNoCC.mkDerivation (finalAttrs: { |
84 | | - pname = "opencode"; |
85 | | - version = packageJson.version; |
86 | | - |
87 | | - src = ./.; |
88 | | - |
89 | | - node_modules = |
90 | | - let |
91 | | - canonicalizeScript = ./script/nix/canonicalize-node-modules.ts; |
92 | | - in |
93 | | - stdenvNoCC.mkDerivation { |
94 | | - pname = "opencode-node_modules"; |
95 | | - inherit (finalAttrs) version src; |
96 | | - |
97 | | - impureEnvVars = |
98 | | - lib.fetchers.proxyImpureEnvVars |
99 | | - ++ [ |
100 | | - "GIT_PROXY_COMMAND" |
101 | | - "SOCKS_SERVER" |
102 | | - ]; |
103 | | - |
104 | | - nativeBuildInputs = [ bun pkgs.cacert pkgs.curl pkgs.python3 ]; |
105 | | - |
106 | | - dontConfigure = true; |
107 | | - |
108 | | - buildPhase = '' |
109 | | - runHook preBuild |
110 | | - export HOME=$(mktemp -d) |
111 | | - export BUN_INSTALL_CACHE_DIR=$(mktemp -d) |
112 | | - bun install \ |
113 | | - --frozen-lockfile \ |
114 | | - --ignore-scripts \ |
115 | | - --no-progress |
116 | | -
|
117 | | - cat > optional-packages.txt <<'EOF' |
118 | | -@parcel/watcher-linux-arm64-glibc |
119 | | -@opentui/core-linux-arm64 |
120 | | -EOF |
121 | | -
|
122 | | - python3 <<'PY' > optional-metadata.txt |
123 | | -import pathlib |
124 | | -import re |
125 | | -import sys |
126 | | -
|
127 | | -lock = pathlib.Path("bun.lock").read_text() |
128 | | -targets = [line.strip() for line in pathlib.Path("optional-packages.txt").read_text().splitlines() if line.strip()] |
129 | | -
|
130 | | -for name in targets: |
131 | | - version_pattern = rf'"{re.escape(name)}": "([^"]+)"' |
132 | | - version_match = re.search(version_pattern, lock) |
133 | | - if not version_match: |
134 | | - print(f"missing-version\t{name}") |
135 | | - sys.exit(1) |
136 | | - version = version_match.group(1) |
137 | | - sha_pattern = rf'"{re.escape(name)}@{re.escape(version)}"[^\n]*"(sha512-[^"]+)"' |
138 | | - sha_match = re.search(sha_pattern, lock) |
139 | | - if not sha_match: |
140 | | - print(f"missing-sha\t{name}\t{version}") |
141 | | - sys.exit(1) |
142 | | - print(f"{name}\t{version}\t{sha_match.group(1)}") |
143 | | -PY |
144 | | -
|
145 | | - while IFS=$'\t' read -r name version sha; do |
146 | | - [ -z "$name" ] && continue |
147 | | - scope="''${name%%/*}" |
148 | | - remainder="''${name#*/}" |
149 | | - if [ "$scope" = "$name" ]; then |
150 | | - scope="" |
151 | | - remainder="$name" |
152 | | - fi |
153 | | -
|
154 | | - base="''${remainder##*/}" |
155 | | - encoded_scope="''${scope//@/%40}" |
156 | | -
|
157 | | - url="https://registry.npmjs.org/''${remainder}/-/''${base}-''${version}.tgz" |
158 | | - dest="node_modules/''${remainder}" |
159 | | - if [ -n "$scope" ]; then |
160 | | - url="https://registry.npmjs.org/''${encoded_scope}/''${remainder}/-/''${base}-''${version}.tgz" |
161 | | - dest="node_modules/''${scope}/''${remainder}" |
162 | | - fi |
163 | | -
|
164 | | - tmp=$(mktemp) |
165 | | - curl --fail --location --silent --show-error --tlsv1.2 "$url" -o "$tmp" |
166 | | - python3 - "$tmp" "$sha" <<'PY' |
167 | | -import base64 |
168 | | -import hashlib |
169 | | -import pathlib |
170 | | -import sys |
171 | | -
|
172 | | -path = pathlib.Path(sys.argv[1]) |
173 | | -expected = sys.argv[2] |
174 | | -data = path.read_bytes() |
175 | | -digest = base64.b64encode(hashlib.sha512(data).digest()).decode() |
176 | | -if expected.startswith('sha512-'): |
177 | | - expected = expected.split('-', 1)[1] |
178 | | -if digest != expected: |
179 | | - print(f"hash mismatch: expected {expected}, got {digest}", file=sys.stderr) |
180 | | - sys.exit(1) |
181 | | -PY |
182 | | -
|
183 | | - mkdir -p "$dest" |
184 | | - tar -xzf "$tmp" -C "$dest" --strip-components=1 package |
185 | | - rm -f "$tmp" |
186 | | - done < optional-metadata.txt |
187 | | -
|
188 | | - rm -f optional-packages.txt optional-metadata.txt |
189 | | -
|
190 | | - cp ${canonicalizeScript} canonicalize-node-modules.ts |
191 | | - bun --bun canonicalize-node-modules.ts |
192 | | - rm -f canonicalize-node-modules.ts |
193 | | -
|
194 | | - runHook postBuild |
195 | | - ''; |
196 | | - |
197 | | - installPhase = '' |
198 | | - runHook preInstall |
199 | | - mkdir -p $out |
200 | | - while IFS= read -r dir; do |
201 | | - rel="''${dir#./}" |
202 | | - dest="$out/$rel" |
203 | | - mkdir -p "$(dirname "$dest")" |
204 | | - cp -R "$dir" "$dest" |
205 | | - done < <(find . -type d -name node_modules -prune) |
206 | | - runHook postInstall |
207 | | - ''; |
208 | | - |
209 | | - dontFixup = true; |
210 | | - |
211 | | - outputHashAlgo = "sha256"; |
212 | | - outputHashMode = "recursive"; |
213 | | - outputHash = "sha256-s/UTz8BTYDOZpF9P6nZr0b7fNOS7Nv7hUfpihJgsSqE="; |
214 | | - }; |
215 | | - |
216 | | - nativeBuildInputs = [ |
217 | | - bun |
218 | | - makeBinaryWrapper |
219 | | - ]; |
220 | | - |
221 | | - configurePhase = '' |
222 | | - runHook preConfigure |
223 | | - cp -R ${finalAttrs.node_modules}/. . |
224 | | - runHook postConfigure |
225 | | - ''; |
226 | | - |
227 | | - env.MODELS_DEV_API_JSON = "${modelsDev.${system}}/dist/_api.json"; |
228 | | - |
229 | | - buildPhase = '' |
230 | | - runHook preBuild |
231 | | -
|
232 | | - cat > bun-build.ts <<'EOF' |
233 | | - import solidPlugin from "./packages/opencode/node_modules/@opentui/solid/scripts/solid-plugin" |
234 | | - import path from "path" |
235 | | - import fs from "fs" |
236 | | -
|
237 | | - const version = "@VERSION@" |
238 | | - const repoRoot = process.cwd() |
239 | | - const packageDir = path.join(repoRoot, "packages/opencode") |
240 | | -
|
241 | | - const parserWorker = fs.realpathSync( |
242 | | - path.join(packageDir, "./node_modules/@opentui/core/parser.worker.js"), |
243 | | - ) |
244 | | - const dir = packageDir |
245 | | - const workerPath = "./src/cli/cmd/tui/worker.ts" |
246 | | - const target = process.env["BUN_COMPILE_TARGET"] |
247 | | -
|
248 | | - if (!target) { |
249 | | - throw new Error("BUN_COMPILE_TARGET not set") |
250 | | - } |
251 | | -
|
252 | | - process.chdir(packageDir) |
253 | | -
|
254 | | - const result = await Bun.build({ |
255 | | - conditions: ["browser"], |
256 | | - tsconfig: "./tsconfig.json", |
257 | | - plugins: [solidPlugin], |
258 | | - sourcemap: "external", |
259 | | - entrypoints: ["./src/index.ts", parserWorker, workerPath], |
260 | | - define: { |
261 | | - OPENCODE_VERSION: `'@VERSION@'`, |
262 | | - OTUI_TREE_SITTER_WORKER_PATH: "/$bunfs/root/" + path.relative(dir, parserWorker).replace(/\\/g, "/"), |
263 | | - OPENCODE_CHANNEL: "'latest'", |
264 | | - }, |
265 | | - compile: { |
266 | | - target, |
267 | | - outfile: "opencode", |
268 | | - execArgv: ["--user-agent=opencode/" + version, "--env-file=\"\"", "--"], |
269 | | - windows: {}, |
270 | | - }, |
271 | | - }) |
272 | | -
|
273 | | - if (!result.success) { |
274 | | - console.error("Build failed!") |
275 | | - for (const log of result.logs) { |
276 | | - console.error(log) |
277 | | - } |
278 | | - throw new Error("Compilation failed") |
279 | | - } |
280 | | -
|
281 | | - // Nix packaging needs a real file for Worker() lookups, so emit a JS bundle alongside the binary. |
282 | | - const workerBundle = await Bun.build({ |
283 | | - entrypoints: [workerPath], |
284 | | - tsconfig: "./tsconfig.json", |
285 | | - plugins: [solidPlugin], |
286 | | - target: "bun", |
287 | | - outdir: "./.opencode-worker", |
288 | | - sourcemap: "none", |
289 | | - }) |
290 | | -
|
291 | | - if (!workerBundle.success) { |
292 | | - console.error("Worker build failed!") |
293 | | - for (const log of workerBundle.logs) { |
294 | | - console.error(log) |
295 | | - } |
296 | | - throw new Error("Worker compilation failed") |
297 | | - } |
298 | | -
|
299 | | - const workerOutput = workerBundle.outputs.find((output) => output.kind === "entry-point") |
300 | | - if (!workerOutput) { |
301 | | - throw new Error("Worker build produced no entry-point output") |
302 | | - } |
303 | | -
|
304 | | - const workerTarget = path.join(packageDir, "opencode-worker.js") |
305 | | - const workerSource = workerOutput.path |
306 | | - await Bun.write(workerTarget, Bun.file(workerSource)) |
307 | | - fs.rmSync(path.dirname(workerSource), { recursive: true, force: true }) |
308 | | -
|
309 | | - console.log("Build successful!") |
310 | | - EOF |
311 | | -
|
312 | | - substituteInPlace bun-build.ts \ |
313 | | - --replace '@VERSION@' "${finalAttrs.version}" |
314 | | -
|
315 | | - export BUN_COMPILE_TARGET=${bunTarget.${stdenvNoCC.hostPlatform.system}} |
316 | | - bun --bun bun-build.ts |
317 | | -
|
318 | | - runHook postBuild |
319 | | - ''; |
320 | | - |
321 | | - dontStrip = true; |
322 | | - |
323 | | - installPhase = '' |
324 | | - runHook preInstall |
325 | | -
|
326 | | - # The binary is created in the package directory after chdir |
327 | | - cd packages/opencode |
328 | | - if [ ! -f opencode ]; then |
329 | | - echo "ERROR: opencode binary not found in $(pwd)" |
330 | | - ls -la |
331 | | - exit 1 |
332 | | - fi |
333 | | - if [ ! -f opencode-worker.js ]; then |
334 | | - echo "ERROR: opencode worker bundle not found in $(pwd)" |
335 | | - ls -la |
336 | | - exit 1 |
337 | | - fi |
338 | | -
|
339 | | - install -Dm755 opencode $out/bin/opencode |
340 | | - install -Dm644 opencode-worker.js $out/bin/opencode-worker.js |
341 | | - runHook postInstall |
342 | | - ''; |
343 | | - |
344 | | - postFixup = lib.optionalString stdenvNoCC.hostPlatform.isLinux '' |
345 | | - wrapProgram $out/bin/opencode \ |
346 | | - --set LD_LIBRARY_PATH "${lib.makeLibraryPath [ stdenv.cc.cc.lib ]}" |
347 | | - ''; |
348 | | - |
349 | | - meta = { |
350 | | - description = "AI coding agent built for the terminal"; |
351 | | - longDescription = '' |
352 | | - OpenCode is a terminal-based agent that can build anything. |
353 | | - It combines a TypeScript/JavaScript core with a Go-based TUI |
354 | | - to provide an interactive AI coding experience. |
355 | | - ''; |
356 | | - homepage = "https://github.com/sst/opencode"; |
357 | | - license = lib.licenses.mit; |
358 | | - platforms = [ |
359 | | - "aarch64-linux" |
360 | | - "x86_64-linux" |
361 | | - ]; |
362 | | - mainProgram = "opencode"; |
363 | | - }; |
364 | | - }) |
365 | | - ) { }; |
| 62 | + default = mkPackage { |
| 63 | + version = packageJson.version; |
| 64 | + src = ./.; |
| 65 | + scripts = scripts; |
| 66 | + target = bunTarget.${system}; |
| 67 | + modelsDev = "${modelsDev.${system}}/dist/_api.json"; |
| 68 | + mkNodeModules = mkNodeModules; |
| 69 | + }; |
366 | 70 | } |
367 | 71 | ); |
368 | 72 |
|
|
0 commit comments