From 3549c423b9101020c24f0b8024c17962060c1f2b Mon Sep 17 00:00:00 2001 From: natsukium Date: Thu, 25 Dec 2025 12:43:01 +0900 Subject: [PATCH 1/2] Revert "refactor: improve nix packaging with deterministic dependency vendoring" This reverts commit 316068f1641f3d1de80a2b8dfe5f054494d0ca0b. - Revert Nix packaging changes that caused reproducibility issues over time - Deno's lock file mechanism proved insufficient for long-term dependency pinning in Nix builds - Restore original wrapper script approach until a robust solution is found The deterministic dependency vendoring approach introduced in the previous refactor relied on Deno's lock file for reproducibility. However, Deno's ecosystem does not guarantee that dependencies remain fetchable indefinitely, causing Nix builds to fail when upstream packages become unavailable or change. --- flake.nix | 176 ++++++++---------------------------------------------- 1 file changed, 24 insertions(+), 152 deletions(-) diff --git a/flake.nix b/flake.nix index 85f0e6c..1ecb4cc 100644 --- a/flake.nix +++ b/flake.nix @@ -7,171 +7,43 @@ }; outputs = { self, nixpkgs, flake-utils }: + let + # Overlay that adds probitas to pkgs + overlay = final: prev: { + probitas = prev.writeShellApplication { + name = "probitas"; + runtimeInputs = [ prev.deno ]; + text = '' + export DENO_NO_UPDATE_CHECK=1 + exec deno run -A \ + --unstable-kv \ + --config=${self}/deno.json \ + --frozen --lock=${self}/deno.lock \ + ${self}/mod.ts "$@" + ''; + }; + }; + in { # Overlay for easy integration - overlays.default = final: prev: { - inherit (self.packages.${final.stdenv.hostPlatform.system}) probitas; - }; + overlays.default = overlay; } // flake-utils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { inherit system; }; - - # Map Nix platform to npm's os/cpu values (Node.js process.platform/process.arch) - npmPlatform = { - os = - if pkgs.stdenv.hostPlatform.isDarwin then "darwin" - else if pkgs.stdenv.hostPlatform.isLinux then "linux" - else null; - cpu = - if pkgs.stdenv.hostPlatform.isAarch64 then "arm64" - else if pkgs.stdenv.hostPlatform.isx86_64 then "x64" - else null; - }; - - # Use `deno cache --vendor` for deterministic output instead of $DENO_DIR cache. - # The cache approach (`deno install` alone) is non-deterministic due to: - # - JSR cache metadata with timestamps (normalizing breaks cache lookup) - # - SQLite databases with non-deterministic page ordering (deleting causes re-downloads) - deps = pkgs.stdenvNoCC.mkDerivation { - name = "probitas-deps"; - src = pkgs.lib.cleanSource ./.; - nativeBuildInputs = with pkgs; [ - deno - jq - writableTmpDirAsHomeHook - ]; - # Remove os/cpu fields from deno.lock for cross-platform deterministic hash - postPatch = '' - jq ' - if .npm then - .npm |= map_values(del(.os, .cpu)) - else . - end - ' deno.lock > deno.lock.tmp - mv deno.lock.tmp deno.lock - ''; - installPhase = '' - runHook preInstall - - mkdir -p $out - # vendor dependencies for deterministic output - deno cache --vendor --frozen mod.ts jsr:@probitas/probitas@^0 - - cp -r vendor node_modules $out/ - - runHook postInstall - ''; - outputHashAlgo = "sha256"; - outputHashMode = "recursive"; - outputHash = "sha256-7/6p+Gcglet123zKj8ixFaeQHbMdI53b3kXNBnuafuQ="; + pkgs = import nixpkgs { + inherit system; + overlays = [ overlay ]; }; in { packages = { - default = self.packages.${system}.probitas; - probitas = pkgs.stdenvNoCC.mkDerivation (finalAttrs: { - pname = "probitas"; - version = self.shortRev or self.dirtyShortRev or "dev"; - src = pkgs.lib.cleanSource ./.; - - postPatch = '' - substituteInPlace deno.json \ - --replace-fail '"version": "0.0.0"' '"version": "${finalAttrs.version}"' - ''; - - nativeBuildInputs = with pkgs; [ - deno - jq - makeBinaryWrapper - ] ++ pkgs.lib.optionals pkgs.stdenv.hostPlatform.isLinux [ - autoPatchelfHook - ]; - - installPhase = '' - runHook preInstall - - mkdir -p $out/share/probitas - cp -r src assets $out/share/probitas/ - cp mod.ts deno.json deno.lock $out/share/probitas/ - - cp -r ${deps}/{vendor,node_modules} $out/share/probitas/ - # Make node_modules writable to allow removal of platform-specific packages - chmod -R +w $out/share/probitas/node_modules - - # Remove npm packages with os/cpu constraints that don't match current platform - # Uses deno.lock as the source of truth for platform-specific packages - incompatiblePackages=$(jq -r --arg os "${npmPlatform.os}" --arg cpu "${npmPlatform.cpu}" ' - .npm // {} | to_entries[] | - select( - (.value.os != null and (.value.os | index($os) | not)) or - (.value.cpu != null and (.value.cpu | index($cpu) | not)) - ) | - .key | gsub("/"; "+") - ' "deno.lock") - - for pkg in $incompatiblePackages; do - rm -rf "$out/share/probitas/node_modules/.deno/$pkg" - done - - # Clean up broken symlinks left after removing platform-specific packages - find "$out/share/probitas/node_modules" -xtype l -delete - - makeWrapper ${pkgs.lib.getExe pkgs.deno} $out/bin/probitas \ - --set DENO_NO_UPDATE_CHECK 1 \ - --add-flags "run -A" \ - --add-flags "--unstable-kv" \ - --add-flags "--vendor" \ - --add-flags "--frozen" \ - --add-flags "--config=$out/share/probitas/deno.json" \ - --add-flags "--lock=$out/share/probitas/deno.lock" \ - --add-flags "$out/share/probitas/mod.ts" - - runHook postInstall - ''; - - doInstallCheck = true; - nativeInstallCheckInputs = [ pkgs.versionCheckHook ]; - - passthru = { - inherit deps; - updateDepsHash = pkgs.writeShellScriptBin "update-probitas-deps" '' - set -euo pipefail - cd "$(${pkgs.lib.getExe pkgs.git} rev-parse --show-toplevel)" - - fakehash="${pkgs.lib.fakeHash}" - curhash=$(nix eval .#probitas.deps.outputHash --raw) - - # Replace current hash with fakeHash - ${pkgs.gnused}/bin/sed -i "s|\"$curhash\"|\"$fakehash\"|" flake.nix - - # Build with fakeHash to get the correct hash from error output - set +e - newhash=$( - nix build .#probitas.deps --no-link --log-format internal-json 2>&1 >/dev/null \ - | ${pkgs.gnugrep}/bin/grep "$fakehash" \ - | ${pkgs.gnugrep}/bin/grep -oP 'sha256-[A-Za-z0-9+/=]+' \ - | tail -1 - ) - set -e - - if [[ -n "$newhash" ]]; then - ${pkgs.gnused}/bin/sed -i "s|\"$fakehash\"|\"$newhash\"|" flake.nix - echo "Updated deps hash to: $newhash" - else - ${pkgs.gnused}/bin/sed -i "s|\"$fakehash\"|\"$curhash\"|" flake.nix - echo "Failed to get hash, restored original" - fi - ''; - }; - - meta.mainProgram = "probitas"; - }); + inherit (pkgs) probitas; + default = pkgs.probitas; }; apps.default = flake-utils.lib.mkApp { - drv = self.packages.${system}.default; + drv = pkgs.probitas; }; devShells.default = pkgs.mkShell { From a096ff6456570bf610e46be4521203cfcf94dbc1 Mon Sep 17 00:00:00 2001 From: natsukium Date: Thu, 25 Dec 2025 12:43:56 +0900 Subject: [PATCH 2/2] Revert "chore: automate Nix hash update when lock file changes" This reverts commit 73fd886a0a5c9eb8e78b0c57af61dae67941ea93. - Revert Nix packaging changes that caused reproducibility issues over time - Deno's lock file mechanism proved insufficient for long-term dependency pinning in Nix builds - Restore original wrapper script approach until a robust solution is found The deterministic dependency vendoring approach introduced in the previous refactor relied on Deno's lock file for reproducibility. However, Deno's ecosystem does not guarantee that dependencies remain fetchable indefinitely, causing Nix builds to fail when upstream packages become unavailable or change. --- .github/workflows/test.yml | 1 - deno.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c15b0f..0cf4d3c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,6 @@ jobs: - uses: denoland/setup-deno@v2 with: deno-version: ${{ env.DENO_VERSION }} - - uses: nixbuild/nix-quick-install-action@v34 - run: deno fmt --check - run: deno lint - run: deno task check diff --git a/deno.json b/deno.json index 1c710ee..438cf83 100644 --- a/deno.json +++ b/deno.json @@ -27,8 +27,7 @@ "coverage": "deno coverage .coverage --include='src/'", "verify": "deno fmt && deno lint && deno task check && deno task test", "probitas": "deno run -A --unstable-kv --lock ./deno.lock ./mod.ts", - "update-nix-hash": "nix run .#probitas.updateDepsHash", - "update-lock": "rm deno.lock && deno cache -r mod.ts jsr:@probitas/probitas jsr:@probitas/probitas@^0 && deno install && deno task update-nix-hash", + "update-lock": "rm deno.lock && deno cache -r mod.ts jsr:@probitas/probitas jsr:@probitas/probitas@^0", "update-version": "deno run -A .scripts/update_version.ts" }, "imports": {