|
| 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 | +} |
0 commit comments