Skip to content

Commit dd18731

Browse files
committed
nix flake: completed modularization refactor
1 parent 3f2cf03 commit dd18731

File tree

10 files changed

+380
-322
lines changed

10 files changed

+380
-322
lines changed

.github/workflows/update-nix-hashes.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
- name: Update build output hashes
3131
run: |
3232
set -euo pipefail
33-
script/nix/update-hashes
33+
nix/scripts/update-hashes
3434
3535
- name: Verification build (x86_64-linux)
3636
run: |
@@ -44,18 +44,18 @@ jobs:
4444
nix build .#packages.aarch64-linux.default --system aarch64-linux -L
4545
echo "aarch64-linux build successful."
4646
47-
- name: Commit flake.nix changes
47+
- name: Commit Nix hash changes
4848
run: |
4949
set -euo pipefail
5050
51-
if git diff --quiet flake.nix; then
52-
echo "No changes to flake.nix. Hash is already up to date."
51+
if git diff --quiet flake.nix nix/node-modules.nix nix/models-dev.nix; then
52+
echo "No changes to tracked Nix files. Hashes are already up to date."
5353
exit 0
5454
fi
5555
5656
VERSION=$(jq -r '.version' packages/opencode/package.json)
57-
git add flake.nix
58-
git commit -m "nix: update output hashes for v$VERSION"
57+
git add flake.nix nix/node-modules.nix nix/models-dev.nix
58+
git commit -m "nix: update hashes for v$VERSION"
5959
6060
git pull --rebase origin dev
6161
git push origin HEAD:dev

flake.nix

Lines changed: 12 additions & 308 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,13 @@
2323
"aarch64-linux" = "bun-linux-arm64";
2424
"x86_64-linux" = "bun-linux-x64";
2525
};
26+
scripts = ./nix/scripts;
2627
modelsDev = forEachSystem (
2728
system:
2829
let
2930
pkgs = pkgsFor system;
3031
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 { }
4833
);
4934
in
5035
{
@@ -70,299 +55,18 @@
7055
system:
7156
let
7257
pkgs = pkgsFor system;
58+
mkNodeModules = pkgs.callPackage ./nix/node-modules.nix { };
59+
mkPackage = pkgs.callPackage ./nix/opencode.nix { };
7360
in
7461
{
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+
};
36670
}
36771
);
36872

nix/models-dev.nix

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{ stdenvNoCC, fetchurl }:
2+
stdenvNoCC.mkDerivation {
3+
pname = "models-dev";
4+
version = "unstable";
5+
6+
src = fetchurl {
7+
url = "https://models.dev/api.json";
8+
hash = "sha256-Dff3OWJ7pD7LfVbZZ0Gf/QA65uw4ft14mdfBun0qDBg=";
9+
};
10+
11+
dontUnpack = true;
12+
dontBuild = true;
13+
14+
installPhase = ''
15+
mkdir -p $out/dist
16+
cp $src $out/dist/_api.json
17+
'';
18+
}

0 commit comments

Comments
 (0)