Handy/scripts/check-nix-deps.ts
Evgeny Khudoba 7056edce28
fix(nix): replace manual hash management with bun2nix (#1021)
* fix(nix): replace manual hash management with bun2nix

Eliminate the recurring problem of Nix build hashes breaking whenever
bun dependencies change or the bun version in nixpkgs updates.

Changes:
- Add bun2nix flake input (pinned to v2.0.1) for per-package fetchurl
  expressions from bun.lock, replacing the single FOD hash approach
- Use allowBuiltinFetchGit for cargo git dependencies, removing manual
  outputHashes that required updates on every git dep change
- Add scripts/check-nix-deps.ts (cross-platform, runs via bun) that
  auto-regenerates .nix/bun.nix when bun.lock changes, triggered by
  the postinstall hook in package.json
- Update CI workflow to verify bun.nix is in sync and evaluate flake
- Remove scripts/update-nix-hashes.sh (no longer needed)

* style: format check-nix-deps.ts with prettier

* ci(nix): make nix-check job a required check

* fix(nix): address review feedback

- Fix bun2nix input pinning: use path syntax (github:owner/repo/tag)
  instead of ?tag= query parameter so flake.lock records the ref
  and `nix flake update` respects the pin
- Make check-nix-deps.ts exit with 0 on bun2nix failure so that
  `bun install` is not blocked for non-Nix developers (CI validates
  bun.nix independently)
- Fix stale reference to check-nix-deps.sh in flake.nix comment

* ci(nix): add full nix build step to CI workflow

Add `nix build .#handy -L --show-trace` after flake evaluation to
catch runtime build errors (broken dependencies, sandbox issues,
compilation failures) that flake eval alone cannot detect.

* fix(nix): update bun2nix pin from 2.0.1 to 2.0.8

bun2nix 2.0.1 has a bug in cache-entry-creator that causes
"ln: failed to create symbolic link '/p': Permission denied"
during the build. Version 2.0.8 fixes this.
2026-03-13 18:45:46 +08:00

81 lines
2.8 KiB
TypeScript

// scripts/check-nix-deps.ts — Keep .nix/bun.nix in sync with bun.lock
//
// Handy uses bun2nix to generate per-package Nix fetchurl expressions from
// bun.lock. This replaces the old FOD (Fixed-Output Derivation) approach
// where a single hash covered the entire node_modules — that hash would
// break whenever the bun version in nixpkgs changed, even without any
// dependency updates.
//
// How it works:
// 1. Computes sha256 of bun.lock
// 2. Compares with stored hash in .nix/bun-lock-hash
// 3. If they match — nothing to do (~2ms)
// 4. If they differ — runs `bunx bun2nix` to regenerate .nix/bun.nix
//
// When it runs:
// - Automatically via "postinstall" in package.json — triggers after every
// bun install / bun add / bun remove / bun update
// - Can also be run manually: bun scripts/check-nix-deps.ts
//
// What to commit:
// If the script regenerated .nix/bun.nix, commit it together with bun.lock:
// git add bun.lock .nix/bun.nix .nix/bun-lock-hash
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { join, resolve } from "path";
const root = resolve(import.meta.dirname, "..");
const nixDir = join(root, ".nix");
const lockFile = join(root, "bun.lock");
const hashFile = join(nixDir, "bun-lock-hash");
const nixFile = join(nixDir, "bun.nix");
// No bun.lock — nothing to do
if (!existsSync(lockFile)) process.exit(0);
// Ensure .nix directory exists
mkdirSync(nixDir, { recursive: true });
// Compute sha256 of the current bun.lock
const currentHash = new Bun.CryptoHasher("sha256")
.update(readFileSync(lockFile))
.digest("hex");
// Read the previously stored hash (empty if first run)
const storedHash = existsSync(hashFile)
? readFileSync(hashFile, "utf-8").trim()
: "";
// If hashes match, bun.nix is up to date — nothing to do
if (currentHash === storedHash) process.exit(0);
// bun.lock has changed — regenerate the Nix dependency file
console.log(
`[check-nix-deps] bun.lock has changed, regenerating ${nixFile}...`,
);
const result = Bun.spawnSync(["bunx", "bun2nix", "-o", nixFile], {
cwd: root,
stdio: ["inherit", "inherit", "inherit"],
});
if (result.exitCode !== 0) {
console.warn(
"[check-nix-deps] Warning: bunx bun2nix failed. .nix/bun.nix may be outdated.",
);
console.warn(
"[check-nix-deps] Nix users: run `bunx bun2nix -o .nix/bun.nix` manually.",
);
console.warn(
"[check-nix-deps] Non-Nix users: this is safe to ignore, CI will catch it.",
);
// Exit 0 so that `bun install` is not blocked for non-Nix developers.
// CI validates bun.nix independently.
process.exit(0);
}
writeFileSync(hashFile, currentHash + "\n");
console.log(`[check-nix-deps] Updated ${nixFile}`);
console.log(
"[check-nix-deps] Don't forget to commit: .nix/bun.nix .nix/bun-lock-hash",
);