Skip to content

Commit 6924ad0

Browse files
authored
Merge pull request #36 from compoundingtech/schickling-assistant/2026-07-20-nix-flake
feat(nix): add a self-contained flake building pty-relay from source
2 parents cda25b0 + 892c320 commit 6924ad0

5 files changed

Lines changed: 257 additions & 1 deletion

File tree

.github/workflows/nix.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: Nix
2+
on:
3+
pull_request:
4+
push:
5+
branches: [main]
6+
7+
jobs:
8+
check:
9+
runs-on: ubuntu-latest
10+
timeout-minutes: 20
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: DeterminateSystems/determinate-nix-action@v3
14+
# `nix flake check` rather than `nix build`, so the checks.* gate too.
15+
- run: nix flake check --print-build-logs

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ dist/
33
!browser/dist/
44
*.tsbuildinfo
55
test-results/
6+
result
7+
result-*
68
.env*
79
.claude/
810
pty.toml

flake.lock

Lines changed: 82 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
{
2+
description = "pty-relay — remote access to pty sessions over an end-to-end encrypted WebSocket tunnel";
3+
4+
inputs = {
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6+
flake-utils.url = "github:numtide/flake-utils";
7+
pty.url = "github:compoundingtech/pty";
8+
pty.inputs.nixpkgs.follows = "nixpkgs";
9+
};
10+
11+
outputs =
12+
{
13+
self,
14+
nixpkgs,
15+
flake-utils,
16+
pty,
17+
}:
18+
flake-utils.lib.eachDefaultSystem (
19+
system:
20+
let
21+
pkgs = import nixpkgs { inherit system; };
22+
23+
# Node runtime for both the derivation's npm steps and the bin shim,
24+
# so a build and a run can never disagree on the interpreter.
25+
nodejs = pkgs.nodejs_24;
26+
27+
# Unpublished sibling packages, resolved from their own flakes and
28+
# linked into node_modules at install time. npm records
29+
# `@compoundingtech/pty` as `file:../pty`, which is a dangling link
30+
# inside the sandbox; these store paths are what actually resolve.
31+
# TODO(rust): the Rust rewrite links this natively — drop the map.
32+
siblingPackages = {
33+
"@compoundingtech/pty" = "${pty.packages.${system}.default}/lib/pty";
34+
};
35+
36+
linkSiblings = pkgs.lib.concatStringsSep "\n" (
37+
pkgs.lib.mapAttrsToList (name: path: ''
38+
mkdir -p "$out/lib/pty-relay/node_modules/${builtins.dirOf name}"
39+
rm -rf "$out/lib/pty-relay/node_modules/${name}"
40+
ln -s ${path} "$out/lib/pty-relay/node_modules/${name}"
41+
'') siblingPackages
42+
);
43+
44+
# Single source of truth: package.json. Build identity beyond the
45+
# semver (commit rev, build date) is the org's shared build-identity
46+
# contract, not something this flake invents.
47+
version = (builtins.fromJSON (builtins.readFile ./package.json)).version;
48+
49+
pty-relay = pkgs.buildNpmPackage {
50+
pname = "pty-relay";
51+
inherit version nodejs;
52+
53+
src = self;
54+
55+
# TODO(rust): cargo's lockfile is content-addressed; this vendoring
56+
# hash disappears with the npm dependency tree.
57+
# Regenerate with: nix run nixpkgs#prefetch-npm-deps -- package-lock.json
58+
npmDepsHash = "sha256-wDKiIRJivnTFd0dXCdKw+GoLJA6T53a/5sDCsbxvkUU=";
59+
60+
# pty-relay ships as raw TypeScript executed by Node with native
61+
# type stripping — no compile step. Only the browser bundle has one,
62+
# and the daemon/CLI don't need it.
63+
dontNpmBuild = true;
64+
65+
nativeBuildInputs = [ pkgs.installShellFiles ];
66+
67+
# Installed outside node_modules so Node's type-stripping works on
68+
# src/cli.ts (Node refuses to strip types inside node_modules).
69+
installPhase = ''
70+
runHook preInstall
71+
72+
mkdir -p $out/lib/pty-relay
73+
cp -r . $out/lib/pty-relay
74+
75+
# TODO(rust): sibling linking is an npm-workspace workaround.
76+
${linkSiblings}
77+
78+
# TODO(rust): a compiled binary needs no interpreter shim.
79+
mkdir -p $out/bin
80+
cat > $out/bin/pty-relay <<EOF
81+
#!${pkgs.runtimeShell}
82+
exec ${nodejs}/bin/node --experimental-strip-types \\
83+
$out/lib/pty-relay/src/cli.ts "\$@"
84+
EOF
85+
chmod +x $out/bin/pty-relay
86+
87+
# Generate completions from the binary we just built, so they can
88+
# never lag the shipped command surface.
89+
installShellCompletion --cmd pty-relay \
90+
--bash <($out/bin/pty-relay completions bash) \
91+
--zsh <($out/bin/pty-relay completions zsh) \
92+
--fish <($out/bin/pty-relay completions fish)
93+
94+
runHook postInstall
95+
'';
96+
97+
meta = {
98+
description = "Remote access to pty sessions over an end-to-end encrypted WebSocket tunnel";
99+
homepage = "https://github.com/compoundingtech/pty-relay";
100+
license = pkgs.lib.licenses.mit;
101+
mainProgram = "pty-relay";
102+
};
103+
};
104+
in
105+
{
106+
packages = {
107+
inherit pty-relay;
108+
default = pty-relay;
109+
};
110+
111+
checks = {
112+
# The repo's own `tsc --noEmit`. It only passes once
113+
# @compoundingtech/pty resolves, which is exactly what the built
114+
# output provides — so run it against that rather than the raw src.
115+
typecheck = pkgs.runCommand "pty-relay-typecheck" { } ''
116+
export HOME=$(mktemp -d)
117+
cp -r ${pty-relay}/lib/pty-relay tree
118+
chmod -R u+w tree
119+
cd tree
120+
${nodejs}/bin/node node_modules/typescript/bin/tsc --noEmit
121+
touch $out
122+
'';
123+
124+
# NOTE: the vitest suite is deliberately NOT a check. It runs
125+
# green against this same built tree outside the sandbox, but under
126+
# the nix sandbox test/daemon-runtime.test.ts hangs indefinitely
127+
# (0/14, blocking session-list-view and terminal); the other 69/72
128+
# files pass. Gating `npm test` needs that file made sandbox-safe
129+
# first, so CI covers typecheck + the CLI smoke checks only.
130+
131+
help = pkgs.runCommand "pty-relay-help" { } ''
132+
export HOME=$(mktemp -d)
133+
${pty-relay}/bin/pty-relay --help > /dev/null
134+
touch $out
135+
'';
136+
137+
completions = pkgs.runCommand "pty-relay-completions" { } ''
138+
export HOME=$(mktemp -d)
139+
for shell in bash zsh fish; do
140+
${pty-relay}/bin/pty-relay completions $shell > script
141+
test -s script || { echo "empty $shell completions"; exit 1; }
142+
done
143+
touch $out
144+
'';
145+
};
146+
147+
devShells.default = pkgs.mkShell {
148+
packages = [
149+
nodejs
150+
pty.packages.${system}.default
151+
];
152+
};
153+
}
154+
);
155+
}

vitest.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { defineConfig } from "vitest/config";
33
export default defineConfig({
44
test: {
55
globals: true,
6-
exclude: ["integration/**", "node_modules/**"],
6+
// `result` is the nix build symlink; it contains a full copy of the
7+
// source tree, so without this every test would be collected twice.
8+
exclude: ["integration/**", "node_modules/**", "result/**"],
79
setupFiles: ["./test/setup.ts"],
810
globalSetup: ["./test/setup/vitest-global.ts"],
911
},

0 commit comments

Comments
 (0)