diff --git a/.github/workflows/nix-release.yml b/.github/workflows/nix-release.yml new file mode 100644 index 000000000..e7a94bc16 --- /dev/null +++ b/.github/workflows/nix-release.yml @@ -0,0 +1,129 @@ +name: "Update Nix flake" + +# Checks whether `flake.nix` lags behind the latest GitHub release. If it does, +# prefetches the new release's per-platform SRI hashes, rewrites `flake.nix`, +# and opens a PR. +# +# This runs on a schedule instead of `release: published` because prek's releases +# are created with `secrets.GITHUB_TOKEN` (see .github/workflows/release.yml), +# and GitHub does not start new workflow runs from events created by +# GITHUB_TOKEN — so a `release: published` trigger would never fire. A daily +# lag-check is fully decoupled from how releases are created and needs no PAT. +# Run manually any time via workflow_dispatch. + +on: + schedule: + - cron: "17 6 * * *" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +concurrency: + group: nix-flake-release + cancel-in-progress: true + +jobs: + update-flake: + name: Bump flake version + hashes if lagging + runs-on: ubuntu-latest + if: github.repository == 'j178/prek' + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Install Nix + uses: cachix/install-nix-action@8aa03977d8d733052d78f4e008a241fd1dbf36b3 # v31.10.6 + + - name: Check for lag and rewrite flake.nix + env: + # system|asset-substring — one per line. The substring must uniquely + # match the release asset filename for that system (including the + # .tar.gz suffix so it does not match the sibling .sha256 files). + ASSET_MAP: | + x86_64-linux|x86_64-unknown-linux-gnu.tar.gz + aarch64-linux|aarch64-unknown-linux-gnu.tar.gz + x86_64-darwin|x86_64-apple-darwin.tar.gz + aarch64-darwin|aarch64-apple-darwin.tar.gz + run: | + set -euo pipefail + # Latest release tag from the GitHub API (strip leading 'v'). + tag=$(curl -fsSL -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/latest" \ + | python3 -c 'import json,sys; print(json.load(sys.stdin)["tag_name"])') + latest="${tag#v}" + # Version currently pinned in flake.nix. + current=$(python3 -c 'import re; s=open("flake.nix").read(); m=re.search(r"version = \"([^\"]*)\";", s); print(m.group(1))') + echo "flake.nix version: $current | latest release: $latest (tag $tag)" + if [ "$current" = "$latest" ]; then + echo "flake.nix is up to date; nothing to do." + echo "LAGGING=no" >> "$GITHUB_ENV" + exit 0 + fi + echo "LAGGING=yes" >> "$GITHUB_ENV" + echo "VERSION=$latest" >> "$GITHUB_ENV" + export TAG="$tag" + python3 <<'PYEOF' + import json, os, re, subprocess, urllib.request + tag = os.environ["TAG"] + version = tag.lstrip("v") + repo = os.environ["GITHUB_REPOSITORY"] + with urllib.request.urlopen( + f"https://api.github.com/repos/{repo}/releases/latest") as r: + release = json.load(r) + # Drop sibling checksum files (.sha256) so a tarball substring does + # not also match its ".sha256" companion (cargo-dist etc.). + names = {a["name"] for a in release["assets"] + if not a["name"].endswith(".sha256")} + asset_map = {} + for line in os.environ["ASSET_MAP"].splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + sys_, sub = line.split("|", 1) + asset_map[sys_.strip()] = sub.strip() + src = open("flake.nix").read() + src, n = re.subn(r'version = "[^"]*";', f'version = "{version}";', src, count=1) + if n != 1: + raise SystemExit('could not find version = "..." in flake.nix') + for sys_, sub in asset_map.items(): + match = next((n for n in names if sub in n), None) + if not match: + raise SystemExit(f"no asset for {sys_} ({sub}) in {tag}; have: {sorted(names)}") + url = f"https://github.com/{repo}/releases/download/{tag}/{match}" + out = json.loads(subprocess.check_output( + ["nix", "store", "prefetch-file", "--json", "--hash-type", "sha256", url])) + sri = out["hash"] + pat = re.compile(r'("' + re.escape(sys_) + r'" = \{[^}]*\})', re.S) + def repl(m): + b = m.group(1) + b = re.sub(r'file = "[^"]*";', f'file = "{match}";', b, count=1) + b = re.sub(r'sha256 = "[^"]*";', f'sha256 = "{sri}";', b, count=1) + return b + src, n = pat.subn(repl, src, count=1) + if n != 1: + raise SystemExit(f"could not find assets block for {sys_} in flake.nix") + open("flake.nix", "w").write(src) + print(f"bumped flake.nix to {version}: {list(asset_map)}") + PYEOF + + - name: Open PR + if: env.LAGGING == 'yes' + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + commit-message: "chore(nix): bump flake to v${{ env.VERSION }}" + title: "chore(nix): bump flake to v${{ env.VERSION }}" + branch: chore/nix-flake-v${{ env.VERSION }} + base: master + body: | + Auto-generated by the `Update Nix flake` workflow (daily lag-check). + The latest GitHub release is v${{ env.VERSION }} but `flake.nix` was + pinned to an older version. This PR bumps `version` and refreshes + the per-platform SRI hashes by prefetching the new release assets. + + Note: PRs opened by `GITHUB_TOKEN` do not trigger downstream + workflow runs (e.g. CI), so this PR will show no checks. The diff + is a 5-line hash bump with no source changes — safe to merge as-is. diff --git a/.gitignore b/.gitignore index 21723d6a9..c1ff419e3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ site/ # NPM packages npm-artifacts/ npm/.output/ + +# Nix build result symlinks +/result +/result-* diff --git a/README.md b/README.md index b17473b1d..1d6b7c15d 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,22 @@ nix-env -iA nixos.prek nix profile install nixpkgs#prek ``` +For the latest release (Nixpkgs may lag behind), use the flake in this repo: + +```bash +# Run without installing +nix run github:j178/prek + +# Install into your profile +nix profile install github:j178/prek +``` + +The flake tracks `master` and is auto-bumped to the latest release by a +daily [workflow](.github/workflows/nix-release.yml), so `github:j178/prek` +always serves the current release. (Release tags are cut before the bump +lands, so `github:j178/prek/vX.Y.Z` is not a valid pin — use the nixpkgs +package or a specific commit SHA if you need reproducibility.) + diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..4bc527651 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1782175435, + "narHash": "sha256-EMzXKmnOtBQ2MnvpiNOm7E+kOMvdPrIKaeg52Tip2Uk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "89570f24e97e614aa34aa9ab1c927b6578a43775", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..1b181b42a --- /dev/null +++ b/flake.nix @@ -0,0 +1,94 @@ +{ + description = "Better pre-commit, re-engineered in Rust"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = { self, nixpkgs }: let + version = "0.4.5"; + + # ponytail: only the 4 standard Nix systems are exposed; upstream also ships + # musl/arm/armv7/riscv64/s390x tarballs but those map to no stdenv system. + # .github/workflows/nix-release.yml runs a daily lag-check: when the latest + # GitHub release outpaces `version` here, it prefetches new SRI hashes and + # opens a PR — no manual editing required. (Scheduled rather than + # release-triggered because releases are created with GITHUB_TOKEN, which + # does not start new workflow runs.) To expose more systems, add entries + # here and a matching line to the workflow's ASSET_MAP. + assets = { + "x86_64-linux" = { + file = "prek-x86_64-unknown-linux-gnu.tar.gz"; + sha256 = "sha256-3IbhjlMlFt1inuYhq3a8TEdhBSIZsn468eR8v5pxonA="; + }; + "aarch64-linux" = { + file = "prek-aarch64-unknown-linux-gnu.tar.gz"; + sha256 = "sha256-akFDf9aGQd556qw9bdZmf6OFEsk6c/ZhombeKxJVexs="; + }; + "x86_64-darwin" = { + file = "prek-x86_64-apple-darwin.tar.gz"; + sha256 = "sha256-sK/Iu+pp1hu/5JEAOU35nu0VqcRnWwbJk2QX5lqoixY="; + }; + "aarch64-darwin" = { + file = "prek-aarch64-apple-darwin.tar.gz"; + sha256 = "sha256-7Ukgdi8OPbBxY8Q3r2IqHUkF4a1wU2kqSVVRuc6SVJ8="; + }; + }; + + systems = builtins.attrNames assets; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); + + prekFor = system: let + pkgs = nixpkgs.legacyPackages.${system}; + asset = assets.${system}; + in pkgs.stdenv.mkDerivation { + pname = "prek"; + inherit version; + + src = pkgs.fetchurl { + url = "https://github.com/j178/prek/releases/download/v${version}/${asset.file}"; + sha256 = asset.sha256; + }; + + sourceRoot = "."; + + nativeBuildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.autoPatchelfHook ]; + buildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.stdenv.cc.cc.lib ]; + + dontConfigure = true; + dontBuild = true; + + installPhase = '' + runHook preInstall + mkdir -p "$out/bin" + cp prek-*/prek "$out/bin/prek" + chmod +x "$out/bin/prek" + runHook postInstall + ''; + + meta = with pkgs.lib; { + description = "Better pre-commit, re-engineered in Rust"; + homepage = "https://github.com/j178/prek"; + downloadPage = "https://github.com/j178/prek/releases"; + license = licenses.mit; + mainProgram = "prek"; + platforms = systems; + sourceProvenance = [ sourceTypes.binaryNativeCode ]; + }; + }; + in { + packages = forAllSystems (system: rec { + prek = prekFor system; + default = prek; + }); + + apps = forAllSystems (system: { + prek = { + type = "app"; + program = "${prekFor system}/bin/prek"; + }; + default = { + type = "app"; + program = "${prekFor system}/bin/prek"; + }; + }); + }; +}