|
7 | 7 | }; |
8 | 8 |
|
9 | 9 | outputs = { self, nixpkgs, flake-utils }: |
10 | | - let |
11 | | - # Overlay that adds probitas to pkgs |
12 | | - overlay = final: prev: { |
13 | | - probitas = prev.writeShellApplication { |
14 | | - name = "probitas"; |
15 | | - runtimeInputs = [ prev.deno ]; |
16 | | - text = '' |
17 | | - export DENO_NO_UPDATE_CHECK=1 |
18 | | - exec deno run -A \ |
19 | | - --unstable-kv \ |
20 | | - --config=${self}/deno.json \ |
21 | | - --frozen --lock=${self}/deno.lock \ |
22 | | - ${self}/mod.ts "$@" |
23 | | - ''; |
24 | | - }; |
25 | | - }; |
26 | | - in |
27 | 10 | { |
28 | 11 | # Overlay for easy integration |
29 | | - overlays.default = overlay; |
| 12 | + overlays.default = final: prev: { |
| 13 | + inherit (self.packages.${final.stdenv.hostPlatform.system}) probitas; |
| 14 | + }; |
30 | 15 | } |
31 | 16 | // |
32 | 17 | flake-utils.lib.eachDefaultSystem (system: |
33 | 18 | let |
34 | | - pkgs = import nixpkgs { |
35 | | - inherit system; |
36 | | - overlays = [ overlay ]; |
| 19 | + pkgs = import nixpkgs { inherit system; }; |
| 20 | + |
| 21 | + # Map Nix platform to npm's os/cpu values (Node.js process.platform/process.arch) |
| 22 | + npmPlatform = { |
| 23 | + os = |
| 24 | + if pkgs.stdenv.hostPlatform.isDarwin then "darwin" |
| 25 | + else if pkgs.stdenv.hostPlatform.isLinux then "linux" |
| 26 | + else null; |
| 27 | + cpu = |
| 28 | + if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" |
| 29 | + else if pkgs.stdenv.hostPlatform.isx86_64 then "x64" |
| 30 | + else null; |
| 31 | + }; |
| 32 | + |
| 33 | + # Use `deno cache --vendor` for deterministic output instead of $DENO_DIR cache. |
| 34 | + # The cache approach (`deno install` alone) is non-deterministic due to: |
| 35 | + # - JSR cache metadata with timestamps (normalizing breaks cache lookup) |
| 36 | + # - SQLite databases with non-deterministic page ordering (deleting causes re-downloads) |
| 37 | + deps = pkgs.stdenvNoCC.mkDerivation { |
| 38 | + name = "probitas-deps"; |
| 39 | + src = pkgs.lib.cleanSource ./.; |
| 40 | + nativeBuildInputs = with pkgs; [ |
| 41 | + deno |
| 42 | + jq |
| 43 | + writableTmpDirAsHomeHook |
| 44 | + ]; |
| 45 | + # Remove os/cpu fields from deno.lock for cross-platform deterministic hash |
| 46 | + postPatch = '' |
| 47 | + jq ' |
| 48 | + if .npm then |
| 49 | + .npm |= map_values(del(.os, .cpu)) |
| 50 | + else . |
| 51 | + end |
| 52 | + ' deno.lock > deno.lock.tmp |
| 53 | + mv deno.lock.tmp deno.lock |
| 54 | + ''; |
| 55 | + installPhase = '' |
| 56 | + runHook preInstall |
| 57 | +
|
| 58 | + mkdir -p $out |
| 59 | + # vendor dependencies for deterministic output |
| 60 | + deno cache --vendor --frozen mod.ts jsr:@probitas/probitas@^0 |
| 61 | +
|
| 62 | + cp -r vendor node_modules $out/ |
| 63 | +
|
| 64 | + runHook postInstall |
| 65 | + ''; |
| 66 | + outputHashAlgo = "sha256"; |
| 67 | + outputHashMode = "recursive"; |
| 68 | + outputHash = "sha256-aMxoVbu2g1jb/2ZzGUjSk2V2z43jqSma5EaA7JFyFOI="; |
37 | 69 | }; |
38 | 70 | in |
39 | 71 | { |
40 | 72 | packages = { |
41 | | - inherit (pkgs) probitas; |
42 | | - default = pkgs.probitas; |
| 73 | + default = self.packages.${system}.probitas; |
| 74 | + probitas = pkgs.stdenvNoCC.mkDerivation (finalAttrs: { |
| 75 | + pname = "probitas"; |
| 76 | + version = self.shortRev or self.dirtyShortRev or "dev"; |
| 77 | + src = pkgs.lib.cleanSource ./.; |
| 78 | + |
| 79 | + postPatch = '' |
| 80 | + substituteInPlace deno.json \ |
| 81 | + --replace-fail '"version": "0.0.0"' '"version": "${finalAttrs.version}"' |
| 82 | + ''; |
| 83 | + |
| 84 | + nativeBuildInputs = with pkgs; [ |
| 85 | + deno |
| 86 | + jq |
| 87 | + makeBinaryWrapper |
| 88 | + ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ |
| 89 | + autoPatchelfHook |
| 90 | + ]; |
| 91 | + |
| 92 | + installPhase = '' |
| 93 | + runHook preInstall |
| 94 | +
|
| 95 | + mkdir -p $out/share/probitas |
| 96 | + cp -r src assets $out/share/probitas/ |
| 97 | + cp mod.ts deno.json deno.lock $out/share/probitas/ |
| 98 | +
|
| 99 | + cp -r ${deps}/{vendor,node_modules} $out/share/probitas/ |
| 100 | + # Make node_modules writable to allow removal of platform-specific packages |
| 101 | + chmod -R +w $out/share/probitas/node_modules |
| 102 | +
|
| 103 | + # Remove npm packages with os/cpu constraints that don't match current platform |
| 104 | + # Uses deno.lock as the source of truth for platform-specific packages |
| 105 | + incompatiblePackages=$(jq -r --arg os "${npmPlatform.os}" --arg cpu "${npmPlatform.cpu}" ' |
| 106 | + .npm // {} | to_entries[] | |
| 107 | + select( |
| 108 | + (.value.os != null and (.value.os | index($os) | not)) or |
| 109 | + (.value.cpu != null and (.value.cpu | index($cpu) | not)) |
| 110 | + ) | |
| 111 | + .key | gsub("/"; "+") |
| 112 | + ' "deno.lock") |
| 113 | +
|
| 114 | + for pkg in $incompatiblePackages; do |
| 115 | + rm -rf "$out/share/probitas/node_modules/.deno/$pkg" |
| 116 | + done |
| 117 | +
|
| 118 | + # Clean up broken symlinks left after removing platform-specific packages |
| 119 | + find "$out/share/probitas/node_modules" -xtype l -delete |
| 120 | +
|
| 121 | + makeWrapper ${pkgs.lib.getExe pkgs.deno} $out/bin/probitas \ |
| 122 | + --set DENO_NO_UPDATE_CHECK 1 \ |
| 123 | + --add-flags "run -A" \ |
| 124 | + --add-flags "--unstable-kv" \ |
| 125 | + --add-flags "--vendor" \ |
| 126 | + --add-flags "--frozen" \ |
| 127 | + --add-flags "--config=$out/share/probitas/deno.json" \ |
| 128 | + --add-flags "--lock=$out/share/probitas/deno.lock" \ |
| 129 | + --add-flags "$out/share/probitas/mod.ts" |
| 130 | +
|
| 131 | + runHook postInstall |
| 132 | + ''; |
| 133 | + |
| 134 | + doInstallCheck = true; |
| 135 | + nativeInstallCheckInputs = [ pkgs.versionCheckHook ]; |
| 136 | + |
| 137 | + passthru = { |
| 138 | + inherit deps; |
| 139 | + updateDepsHash = pkgs.writeShellScriptBin "update-probitas-deps" '' |
| 140 | + set -euo pipefail |
| 141 | + cd "$(${pkgs.lib.getExe pkgs.git} rev-parse --show-toplevel)" |
| 142 | +
|
| 143 | + fakehash="${pkgs.lib.fakeHash}" |
| 144 | + curhash=$(nix eval .#probitas.deps.outputHash --raw) |
| 145 | +
|
| 146 | + # Replace current hash with fakeHash |
| 147 | + ${pkgs.gnused}/bin/sed -i "s|\"$curhash\"|\"$fakehash\"|" flake.nix |
| 148 | +
|
| 149 | + # Build with fakeHash to get the correct hash from error output |
| 150 | + set +e |
| 151 | + newhash=$( |
| 152 | + nix build .#probitas.deps --no-link --log-format internal-json 2>&1 >/dev/null \ |
| 153 | + | ${pkgs.gnugrep}/bin/grep "$fakehash" \ |
| 154 | + | ${pkgs.gnugrep}/bin/grep -oP 'sha256-[A-Za-z0-9+/=]+' \ |
| 155 | + | tail -1 |
| 156 | + ) |
| 157 | + set -e |
| 158 | +
|
| 159 | + if [[ -n "$newhash" ]]; then |
| 160 | + ${pkgs.gnused}/bin/sed -i "s|\"$fakehash\"|\"$newhash\"|" flake.nix |
| 161 | + echo "Updated deps hash to: $newhash" |
| 162 | + else |
| 163 | + ${pkgs.gnused}/bin/sed -i "s|\"$fakehash\"|\"$curhash\"|" flake.nix |
| 164 | + echo "Failed to get hash, restored original" |
| 165 | + fi |
| 166 | + ''; |
| 167 | + }; |
| 168 | + |
| 169 | + meta.mainProgram = "probitas"; |
| 170 | + }); |
43 | 171 | }; |
44 | 172 |
|
45 | 173 | apps.default = flake-utils.lib.mkApp { |
46 | | - drv = pkgs.probitas; |
| 174 | + drv = self.packages.${system}.default; |
47 | 175 | }; |
48 | 176 |
|
49 | 177 | devShells.default = pkgs.mkShell { |
|
0 commit comments