This repository was archived by the owner on Jul 24, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathflake.nix
More file actions
219 lines (184 loc) · 7.71 KB
/
Copy pathflake.nix
File metadata and controls
219 lines (184 loc) · 7.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
{
description = "convoy - the single front door to a smalltalk agent network";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
pty.url = "github:compoundingtech/pty";
pty.inputs.nixpkgs.follows = "nixpkgs";
smalltalk.url = "github:compoundingtech/smalltalk";
smalltalk.inputs.nixpkgs.follows = "nixpkgs";
smalltalk.inputs.flake-utils.follows = "flake-utils";
# One pty in the closure: convoy links the same build smalltalk was built against.
smalltalk.inputs.pty.follows = "pty";
};
outputs =
{
self,
nixpkgs,
flake-utils,
pty,
smalltalk,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
# package.json is the single source of truth for the version, so a release bump does not need
# a matching edit here.
version = (builtins.fromJSON (builtins.readFile ./package.json)).version;
# npm names of the local `file:` deps that resolve to sibling repos, mapped to the store path
# holding their package root. buildNpmPackage cannot fetch these, so they are linked into
# node_modules after the copy. The keys must match package.json / package-lock.json exactly,
# since that is the specifier Node resolves.
siblingLinks = {
"@compoundingtech/pty" = "${pty.packages.${system}.default}/lib/pty";
"@compoundingtech/smalltalk" = "${smalltalk.packages.${system}.default}/lib/smalltalk";
};
# TODO(rust): a Rust rewrite drops npm entirely, and this hash with it.
# Regenerate with: nix run nixpkgs#prefetch-npm-deps -- package-lock.json
npmDepsHash = "sha256-TKXQoLRqYxoPZA7nbfp1APJdK+UhT6/3F1jZJ7FVVf8=";
completionShells = [
"bash"
"zsh"
"fish"
];
# The sibling `file:` deps as node_modules symlinks. Shared by the package install and the
# typecheck check, so both resolve `@compoundingtech/*` the same way.
linkSiblings = dir: pkgs.lib.concatLines (
pkgs.lib.mapAttrsToList (npmName: storePath: ''
mkdir -p "${dir}/node_modules/$(dirname ${npmName})"
rm -rf "${dir}/node_modules/${npmName}"
ln -s ${storePath} "${dir}/node_modules/${npmName}"
'') siblingLinks
);
convoy = pkgs.buildNpmPackage {
pname = "convoy";
inherit version;
src = self;
inherit npmDepsHash;
nodejs = pkgs.nodejs_24;
npmInstallFlags = [ "--omit=dev" ];
# Sources ship as TypeScript and run through node's type stripping; there is no compile step.
dontNpmBuild = true;
nativeBuildInputs = [ pkgs.installShellFiles ];
installPhase = ''
runHook preInstall
mkdir -p $out/lib/convoy
cp -r . $out/lib/convoy
# TODO(rust): sibling linking exists only because npm cannot resolve `file:` deps in a
# sandbox; a Cargo build has no equivalent.
${linkSiblings "$out/lib/convoy"}
# bin/convoy dynamically imports src/cli.ts, so node has to strip types at load.
# TODO(rust): a compiled binary replaces this shim.
mkdir -p $out/bin
cat > $out/bin/convoy <<EOF
#!${pkgs.runtimeShell}
exec ${pkgs.nodejs_24}/bin/node --experimental-strip-types \\
$out/lib/convoy/bin/convoy "\$@"
EOF
chmod +x $out/bin/convoy
runHook postInstall
'';
# Completions are generated by the binary we just built rather than committed, so they cannot
# drift from the command table in src/command-table.ts. The CLI touches $HOME on startup,
# which is not writable in the sandbox.
postInstall = ''
export HOME=$(mktemp -d)
${pkgs.lib.concatMapStringsSep "\n" (shell: ''
$out/bin/convoy completions ${shell} > completions-${shell}
'') completionShells}
installShellCompletion --cmd convoy \
--bash completions-bash \
--zsh completions-zsh \
--fish completions-fish
'';
meta = {
description = "Stand up and run a crew of coding agents over smalltalk and pty";
homepage = "https://github.com/compoundingtech/convoy";
license = pkgs.lib.licenses.mit;
mainProgram = "convoy";
};
};
in
{
packages.convoy = convoy;
packages.default = convoy;
checks.help = pkgs.runCommand "convoy-help-${version}" { } ''
export HOME=$(mktemp -d)
${convoy}/bin/convoy --help > /dev/null
touch $out
'';
# Guards the completions contract end-to-end on the REAL binary: every shell gets a non-empty
# script, fish still binds to `convoy`, and each script is syntactically valid for its shell.
# The syntax checks matter most for zsh, which is often absent from a dev machine.
checks.completions =
pkgs.runCommand "convoy-completions-${version}"
{
nativeBuildInputs = [
pkgs.bash
pkgs.zsh
pkgs.fish
];
}
''
export HOME=$(mktemp -d)
${pkgs.lib.concatMapStringsSep "\n" (shell: ''
${convoy}/bin/convoy completions ${shell} > ${shell}.completions
grep -q . ${shell}.completions || { echo "empty ${shell} completions" >&2; exit 1; }
${shell} -n ${shell}.completions \
|| { echo "${shell} rejected its own generated completions" >&2; exit 1; }
'') completionShells}
grep -q '^complete -c convoy ' fish.completions \
|| { echo "fish completions do not bind to \`convoy\`" >&2; exit 1; }
touch $out
'';
# The completions parity suite — the tests that keep src/command-table.ts, argv dispatch, and
# the generated scripts from drifting. Sandbox-safe (it only spawns `convoy` and reads
# src/cli.ts), unlike the rest of the vitest suite, which shells out to `git worktree` and
# probes the host and so is deliberately NOT wired up here.
checks.parity = pkgs.buildNpmPackage {
pname = "convoy-completions-parity";
inherit version npmDepsHash;
src = self;
nodejs = pkgs.nodejs_24;
dontNpmBuild = true;
# The suite skips a shell's syntax check when that shell is absent; give it all three.
nativeBuildInputs = [
pkgs.bash
pkgs.zsh
pkgs.fish
];
preBuild = linkSiblings ".";
buildPhase = ''
runHook preBuild
export HOME=$(mktemp -d)
npx vitest run src/completions.test.ts
runHook postBuild
'';
installPhase = "touch $out";
};
# convoy's own `tsc --noEmit`, run against the same sibling deps the package links.
checks.typecheck = pkgs.buildNpmPackage {
pname = "convoy-typecheck";
inherit version;
src = self;
inherit npmDepsHash;
nodejs = pkgs.nodejs_24;
dontNpmBuild = true;
preBuild = linkSiblings ".";
buildPhase = ''
runHook preBuild
npm run typecheck
runHook postBuild
'';
installPhase = "touch $out";
};
devShells.default = pkgs.mkShell {
packages = [
pkgs.nodejs_24
convoy
];
};
}
);
}