From abab6b02558da8391524141cf42475a6fc51e49f Mon Sep 17 00:00:00 2001 From: JacobPEvans <20714140+JacobPEvans-personal@users.noreply.github.com> Date: Sun, 31 May 2026 12:01:05 -0400 Subject: [PATCH] feat(nix): expose reusable dev-hygiene flake-module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make dryvist/.github a lean Nix flake (nixpkgs + treefmt-nix + git-hooks only) exposing flakeModules.dev-hygiene — the org's treefmt + git-hooks + zizmor config as one importable flake-parts module, pre-bound to this repo's inputs and its own zizmor.yml. Consuming Nix repos import it instead of inlining or pulling nix-devenv's heavier closure. Includes the CHANGELOG.md treefmt exclude (release-please owns that file). Plain-source consumers (flake = false, e.g. for zizmor.yml) are unaffected. Assisted-by: Claude:claude-opus-4-8 --- flake.lock | 108 ++++++++++++++++++++++++++++++++++ flake.nix | 31 ++++++++++ nix/dev-hygiene.nix | 138 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/dev-hygiene.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..80044f0 --- /dev/null +++ b/flake.lock @@ -0,0 +1,108 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", + "repo": "flake-compat", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778507602, + "narHash": "sha256-kTwur1wV+01SdqskVMSo6JMEpg71ps3HpbFY2GsflKs=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "61ab0e80d9c7ab14c256b5b453d8b3fb0189ba0a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1779560665, + "narHash": "sha256-tpyBcxPpcQb8ukyNF7DoCwfSY3VPsxHoYwj00Cayv5o=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "64c08a7ca051951c8eae34e3e3cb1e202fe36786", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "treefmt-nix": "treefmt-nix" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c7b0666 --- /dev/null +++ b/flake.nix @@ -0,0 +1,31 @@ +{ + description = "dryvist org-wide config + reusable Nix dev-hygiene flake-module"; + + # Lean by design: only what the dev-hygiene module needs (no devenv / + # crate2nix / devshell), so consumers importing flakeModules.dev-hygiene get + # a small flake.lock closure. Plain-source consumers (flake = false, e.g. for + # zizmor.yml) are unaffected by this flake.nix. + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + git-hooks = { + url = "github:cachix/git-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { self, ... }@inputs: + { + # Reusable flake-parts modules for consuming Nix repos. The dev-hygiene + # module is pre-bound to this repo's treefmt-nix/git-hooks and its own + # zizmor.yml; see nix/dev-hygiene.nix for the consumer snippet. + flakeModules.dev-hygiene = import ./nix/dev-hygiene.nix { + inherit (inputs) treefmt-nix git-hooks; + zizmorConfig = "${self}/zizmor.yml"; + }; + }; +} diff --git a/nix/dev-hygiene.nix b/nix/dev-hygiene.nix new file mode 100644 index 0000000..9dcc986 --- /dev/null +++ b/nix/dev-hygiene.nix @@ -0,0 +1,138 @@ +# Org-wide dev-hygiene flake-parts module. +# +# Wires together, for any consuming Nix repo: +# * treefmt-nix — formatter set (nixfmt, deadnix, statix, prettier, shfmt, +# taplo) with the org's standard options +# * git-hooks — pre-commit hooks (deadnix, statix, file hygiene, +# markdownlint, zizmor) plus treefmt against the local wrapper +# * zizmor — the `unpinned-uses` trusted-publisher policy from this repo's +# own zizmor.yml, injected as `--config` (zizmorConfig) +# +# Consumer: +# { +# inputs.dryvist-github.url = "github:dryvist/.github"; +# outputs = inputs@{ flake-parts, ... }: +# flake-parts.lib.mkFlake { inherit inputs; } { +# imports = [ inputs.dryvist-github.flakeModules.dev-hygiene ]; +# # ... per-system / project config ... +# }; +# } +# +# That's it: no `inputs.treefmt-nix`, no `inputs.git-hooks`, no local +# treefmt config, no local zizmor.yml in consumers. +# +# This file is a function that dryvist/.github's flake.nix calls eagerly with +# its OWN inputs, returning a flake-parts module. `treefmt-nix`/`git-hooks` +# references therefore resolve against dryvist/.github's pinned inputs, not the +# consumer's — so consumers import a pre-bound module with a lean closure. +{ + treefmt-nix, + git-hooks, + zizmorConfig, +}: +{ ... }: +{ + imports = [ + treefmt-nix.flakeModule + git-hooks.flakeModule + ]; + + perSystem = + { + config, + pkgs, + ... + }: + { + treefmt = { + projectRootFile = "flake.nix"; + programs = { + nixfmt.enable = true; + deadnix.enable = true; + statix.enable = true; + prettier.enable = true; + shfmt.enable = true; + taplo.enable = true; + }; + settings = { + # CHANGELOG.md is owned by release-please, which regenerates it with + # `*` bullets each release; excluding it keeps prettier from fighting + # the generator (and failing the treefmt/pre-commit checks). Mirrors + # the markdownlint-cli2 exclude below. + global.excludes = [ "CHANGELOG.md" ]; + formatter.prettier.options = [ + "--print-width" + "100" + ]; + formatter.shfmt.options = [ + "--indent" + "2" + ]; + }; + }; + + pre-commit.settings.hooks = { + # Nix-source hygiene + deadnix.enable = true; + statix.enable = true; + + # Generic file hygiene + check-yaml.enable = true; + check-toml.enable = true; + check-json.enable = true; + check-merge-conflicts.enable = true; + check-added-large-files = { + enable = true; + args = [ "--maxkb=500" ]; + }; + end-of-file-fixer.enable = true; + # `--markdown-linebreak-ext=md` preserves Markdown's two-space line + # breaks (where trailing whitespace is semantic, not garbage). + trim-trailing-whitespace = { + enable = true; + args = [ "--markdown-linebreak-ext=md" ]; + }; + + # Secret detection + detect-private-keys.enable = true; + + # Markdown linting. markdownlint-cli2 has no native git-hooks.nix + # wrapper, so configure it as a custom hook backed by the nixpkgs + # package. CHANGELOG.md is auto-generated by release-please; its + # commit-hash URLs trigger MD013 and per-release section headings + # trigger MD024. + markdownlint-cli2 = { + enable = true; + name = "markdownlint-cli2"; + entry = "${pkgs.markdownlint-cli2}/bin/markdownlint-cli2"; + files = "\\.md$"; + excludes = [ "^CHANGELOG\\.md$" ]; + language = "system"; + pass_filenames = true; + }; + + # treefmt drives the wrapper produced by the treefmt-nix module above, + # so the same formatter set runs whether you call `nix fmt` or + # `pre-commit run`. + treefmt = { + enable = true; + package = config.treefmt.build.wrapper; + }; + + # GitHub Actions workflow security via zizmor. `--config` points at the + # org-wide trusted-publisher policy shipped alongside this module in + # dryvist/.github, so consumers don't ship their own zizmor.yml. + zizmor = { + enable = true; + files = "^\\.github/workflows/.*\\.ya?ml$"; + args = [ + "--persona=regular" + "--min-severity=medium" + "--min-confidence=medium" + "--config" + zizmorConfig + ]; + }; + }; + }; +}