From 977c7c10bcda8b57ee21d318f122f88bd43cccf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= Date: Fri, 12 Jun 2026 11:02:44 +0200 Subject: [PATCH 1/2] chore(nix): Apply strict formatting --- nix/general/devshell.nix | 44 +++--- nix/general/pre-commit-hooks.nix | 222 ++++++++++++++++--------------- nix/rust/devshell.nix | 75 ++++++----- nix/rust/toolchain.nix | 3 +- 4 files changed, 176 insertions(+), 168 deletions(-) diff --git a/nix/general/devshell.nix b/nix/general/devshell.nix index cca136a..dc96395 100644 --- a/nix/general/devshell.nix +++ b/nix/general/devshell.nix @@ -5,31 +5,33 @@ ... }: importingFlake: { - config.perSystem = { config, ... }: { - devshells.standards = { - name = lib.mkDefault "engineering-standards"; + config.perSystem = + { config, ... }: + { + devshells.standards = { + name = lib.mkDefault "engineering-standards"; # Include all packages from pre-commit in the shell - # - This allows easy access to the individual checks, if developers are interested in running them - # - `prek install` doesn't install the wrapped binary, so when executing the hooks, all runtimePkgs - # would be missing if they were not in the shell - packages = builtins.map (package: package.data) config.prek-pre-commit.package.runtimePkgs; + # - This allows easy access to the individual checks, if developers are interested in running them + # - `prek install` doesn't install the wrapped binary, so when executing the hooks, all runtimePkgs + # would be missing if they were not in the shell + packages = builtins.map (package: package.data) config.prek-pre-commit.package.runtimePkgs; commands = [ - { - name = "prek"; - help = "Run pre-commit hooks on currently staged changes"; - category = "[[lints and checks]]"; - package = config.prek-pre-commit.package.wrapper; - } + { + name = "prek"; + help = "Run pre-commit hooks on currently staged changes"; + category = "[[lints and checks]]"; + package = config.prek-pre-commit.package.wrapper; + } - { - name = "prek -s main -o HEAD"; - help = "Run pre-commit hooks on all commits in the current branch"; - category = "[[lints and checks]]"; - package = config.prek-pre-commit.package.wrapper; - } - ]; + { + name = "prek -s main -o HEAD"; + help = "Run pre-commit hooks on all commits in the current branch"; + category = "[[lints and checks]]"; + package = config.prek-pre-commit.package.wrapper; + } + ]; + }; }; - }; } diff --git a/nix/general/pre-commit-hooks.nix b/nix/general/pre-commit-hooks.nix index 0e1496d..16d326d 100644 --- a/nix/general/pre-commit-hooks.nix +++ b/nix/general/pre-commit-hooks.nix @@ -5,116 +5,118 @@ ... }: importingFlake: { - config.perSystem = { pkgs, self', ... }: { - prek-pre-commit = { - package.runtimePkgs = lib.attrValues { - inherit (pkgs) editorconfig-checker typos; - filegen-activate = self'.apps.filegen-activate.meta.package; - }; - - workspaces.".".repos = [ - { - repo = "builtin"; - - # Regularly check this list for useful new lints, these - # are generally pretty cheap since they're implemented in - # rust and run in the same process: - # - # https://prek.j178.dev/builtin/#supported-hooks_1 - hooks = [ - # TODO: We want to use this, but it conflicts with rustfmt in places - # In the future, we can simply make rustfmt run as a pre-commit hook - # after trailing-whitespace, and it won't be a problem. - # { id = "trailing-whitespace"; } - { id = "check-added-large-files"; } - { id = "check-case-conflict"; } - { id = "check-illegal-windows-names"; } - { id = "end-of-file-fixer"; } - - # This needs very specific per-repo configuration, so we - # don't globally enable it. - # - # {id = "file-contents-sorter";} - - { id = "fix-byte-order-marker"; } - { id = "check-json"; } - { id = "check-json5"; } - - # We should use treefmt-nix for this instead. + config.perSystem = + { pkgs, self', ... }: + { + prek-pre-commit = { + package.runtimePkgs = lib.attrValues { + inherit (pkgs) editorconfig-checker typos; + filegen-activate = self'.apps.filegen-activate.meta.package; + }; + + workspaces.".".repos = [ + { + repo = "builtin"; + + # Regularly check this list for useful new lints, these + # are generally pretty cheap since they're implemented in + # rust and run in the same process: # - # { id = "pretty-format-json"; } - - { id = "check-toml"; } - { id = "check-vcs-permalinks"; } - - # TODO: We would like to use this, but we need to tweak it - # a little for helm charts. - # - # { id = "check-yaml"; } - - { id = "check-xml"; } - { - id = "mixed-line-ending"; - args = [ "--fix=lf" ]; - } - { id = "check-symlinks"; } - { id = "destroyed-symlinks"; } - { id = "check-merge-conflict"; } - { id = "detect-private-key"; } - - # Branch protection rules should be set on the git forge - # instead. - # - # { id = "no-commit-to-branch"; } - - { id = "check-shebang-scripts-are-executable"; } - { id = "check-executables-have-shebangs"; } - ]; - } - - { - repo = "local"; - hooks = [ - { - id = "typos"; - name = "typos"; - description = "Check the repository for spelling mistakes"; - - entry = "typos"; - args = [ - "--write-changes" - "--force-exclude" - ]; - - language = "system"; - types = [ "text" ]; - } - - # TODO: Currently too invasive for some downstreams, we - # need to either tweak the configuration or make it - # somewhat overridable. - # - # { - # id = "editorconfig"; - # name = "editorconfig"; - # description = "Ensure all files in the project match editorconfig rules"; - - # entry = "editorconfig-checker"; - # language = "system"; - # } - - { - id = "filegen"; - name = "filegen"; - description = "Ensure that files set up with the filegen module are up-to-date"; - pass_filenames = false; - - entry = "filegen-apply-script"; - language = "system"; - } - ]; - } - ]; + # https://prek.j178.dev/builtin/#supported-hooks_1 + hooks = [ + # TODO: We want to use this, but it conflicts with rustfmt in places + # In the future, we can simply make rustfmt run as a pre-commit hook + # after trailing-whitespace, and it won't be a problem. + # { id = "trailing-whitespace"; } + { id = "check-added-large-files"; } + { id = "check-case-conflict"; } + { id = "check-illegal-windows-names"; } + { id = "end-of-file-fixer"; } + + # This needs very specific per-repo configuration, so we + # don't globally enable it. + # + # {id = "file-contents-sorter";} + + { id = "fix-byte-order-marker"; } + { id = "check-json"; } + { id = "check-json5"; } + + # We should use treefmt-nix for this instead. + # + # { id = "pretty-format-json"; } + + { id = "check-toml"; } + { id = "check-vcs-permalinks"; } + + # TODO: We would like to use this, but we need to tweak it + # a little for helm charts. + # + # { id = "check-yaml"; } + + { id = "check-xml"; } + { + id = "mixed-line-ending"; + args = [ "--fix=lf" ]; + } + { id = "check-symlinks"; } + { id = "destroyed-symlinks"; } + { id = "check-merge-conflict"; } + { id = "detect-private-key"; } + + # Branch protection rules should be set on the git forge + # instead. + # + # { id = "no-commit-to-branch"; } + + { id = "check-shebang-scripts-are-executable"; } + { id = "check-executables-have-shebangs"; } + ]; + } + + { + repo = "local"; + hooks = [ + { + id = "typos"; + name = "typos"; + description = "Check the repository for spelling mistakes"; + + entry = "typos"; + args = [ + "--write-changes" + "--force-exclude" + ]; + + language = "system"; + types = [ "text" ]; + } + + # TODO: Currently too invasive for some downstreams, we + # need to either tweak the configuration or make it + # somewhat overridable. + # + # { + # id = "editorconfig"; + # name = "editorconfig"; + # description = "Ensure all files in the project match editorconfig rules"; + + # entry = "editorconfig-checker"; + # language = "system"; + # } + + { + id = "filegen"; + name = "filegen"; + description = "Ensure that files set up with the filegen module are up-to-date"; + pass_filenames = false; + + entry = "filegen-apply-script"; + language = "system"; + } + ]; + } + ]; + }; }; - }; } diff --git a/nix/rust/devshell.nix b/nix/rust/devshell.nix index 2682c3d..44f80e1 100644 --- a/nix/rust/devshell.nix +++ b/nix/rust/devshell.nix @@ -1,4 +1,5 @@ -{ inputs, ... }: importingFlake: { +{ inputs, ... }: +importingFlake: { perSystem = { config, @@ -10,49 +11,51 @@ }: lib.mkMerge [ { - devshells.rust = { extraModulesPath, ... }: { - imports = [ - "${extraModulesPath}/language/c.nix" - "${extraModulesPath}/language/rust.nix" - ]; + devshells.rust = + { extraModulesPath, ... }: + { + imports = [ + "${extraModulesPath}/language/c.nix" + "${extraModulesPath}/language/rust.nix" + ]; - language = { - c = { - libraries = [ pkgs.openssl ]; - includes = [ pkgs.openssl ]; - }; + language = { + c = { + libraries = [ pkgs.openssl ]; + includes = [ pkgs.openssl ]; + }; - rust.enableDefaultToolchain = false; - }; + rust.enableDefaultToolchain = false; + }; - packages = lib.attrValues { - inherit (pkgs) - # We have some projects that use cargo workspaces, this tool makes - # matching up dependencies between subcrates easier. - cargo-autoinherit + packages = lib.attrValues { + inherit (pkgs) + # We have some projects that use cargo workspaces, this tool makes + # matching up dependencies between subcrates easier. + cargo-autoinherit - # We use nextest for testing, this cargo extension needs to be - # installed for testing most of our projects - cargo-nextest - ; + # We use nextest for testing, this cargo extension needs to be + # installed for testing most of our projects + cargo-nextest + ; - inherit (self'.packages) famedly-rust-toolchain; + inherit (self'.packages) famedly-rust-toolchain; - # To check dependencies are actually used - cargo-udeps = pkgs.callPackage ./packages/cargo-udeps.nix { inherit inputs; }; - }; + # To check dependencies are actually used + cargo-udeps = pkgs.callPackage ./packages/cargo-udeps.nix { inherit inputs; }; + }; - env = [ - { - name = "RUST_SRC_PATH"; - value = "${self'.packages.famedly-rust-toolchain}/lib/rustlib/src/rust/library"; - } - ]; + env = [ + { + name = "RUST_SRC_PATH"; + value = "${self'.packages.famedly-rust-toolchain}/lib/rustlib/src/rust/library"; + } + ]; - # TODO: Find a better way to inherit devshell - # configurations. - commands = lib.filter (command: command.name != "menu") config.devshells.standards.commands; - }; + # TODO: Find a better way to inherit devshell + # configurations. + commands = lib.filter (command: command.name != "menu") config.devshells.standards.commands; + }; } (lib.mkIf (lib.hasSuffix "linux" system) { diff --git a/nix/rust/toolchain.nix b/nix/rust/toolchain.nix index d265b3a..217224e 100644 --- a/nix/rust/toolchain.nix +++ b/nix/rust/toolchain.nix @@ -1,4 +1,5 @@ -{ inputs, ... }: importingFlake: { +{ inputs, ... }: +importingFlake: { perSystem = { lib, pkgs, ... }: let From b4033565dd2e81940f5dcb007f9f92636352444c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= Date: Fri, 12 Jun 2026 11:06:07 +0200 Subject: [PATCH 2/2] feat(general): Add autoformatters with treefmt --- .config/filegen-manifest.json | 13 +++++-- .gitattributes | 1 + .pre-commit-config.yaml | 5 +++ flake.lock | 21 +++++++++++ flake.nix | 5 +++ nix/default.nix | 1 + nix/general/default.nix | 1 + nix/general/devshell.nix | 29 +++++++++------ nix/general/formatting.nix | 34 ++++++++++++++++++ nix/general/pre-commit-hooks.nix | 28 ++++++++++++--- nix/modules/filegen.nix | 18 ++++++++-- nix/nix/default.nix | 2 +- nix/nix/formatting.nix | 6 ++++ treefmt.toml | 61 ++++++++++++++++++++++++++++++++ 14 files changed, 203 insertions(+), 22 deletions(-) create mode 100644 nix/general/formatting.nix create mode 100644 nix/nix/formatting.nix create mode 100644 treefmt.toml diff --git a/.config/filegen-manifest.json b/.config/filegen-manifest.json index 715da83..f73dbea 100644 --- a/.config/filegen-manifest.json +++ b/.config/filegen-manifest.json @@ -6,7 +6,7 @@ "deactivate": null, "ignore-modification": null, "permissions": "600", - "source": "/nix/store/i9wgm4ljangrsv39rjdqyq9j96hj29zm-pre-commit-config.yaml", + "source": "/nix/store/0ssc8kv2q4p28ff36m1n8y8s9306h2ib-pre-commit-config.yaml", "target": "./.pre-commit-config.yaml", "type": "copy" }, @@ -24,9 +24,18 @@ "deactivate": null, "ignore-modification": null, "permissions": "600", - "source": "/nix/store/mv9zlzz043051izx92fxk6ibz6fn21yd-gitattributes", + "source": "/nix/store/p0hgnx38vpmxa9gd8xlyws7fi01x60w2-gitattributes", "target": ".gitattributes", "type": "copy" + }, + { + "clobber": null, + "deactivate": null, + "ignore-modification": null, + "permissions": "600", + "source": "/nix/store/zwsnbfj6jmwxx1n5gv7wz5cca3a41zh1-treefmt.toml", + "target": "treefmt.toml", + "type": "copy" } ], "version": 3 diff --git a/.gitattributes b/.gitattributes index 5daa663..48e4c36 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ .pre-commit-config.yaml linguist-generated .github/workflows/check-pre-commit-hooks.yml linguist-generated .gitattributes linguist-generated +treefmt.toml linguist-generated .config/filegen-manifest.json linguist-generated diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40030cd..c3f064a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,4 +37,9 @@ repos: language: system name: filegen pass_filenames: false + - description: Format *all* files + entry: treefmt + id: treefmt + language: system + name: treefmt repo: local diff --git a/flake.lock b/flake.lock index f4b849e..b432a7c 100644 --- a/flake.lock +++ b/flake.lock @@ -96,6 +96,7 @@ "github-actions-nix": "github-actions-nix", "nixpkgs": "nixpkgs", "rust-overlay": "rust-overlay", + "treefmt": "treefmt", "wrappers": "wrappers" } }, @@ -119,6 +120,26 @@ "type": "github" } }, + "treefmt": { + "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" + } + }, "wrappers": { "inputs": { "nixpkgs": [ diff --git a/flake.nix b/flake.nix index e538921..d62a119 100644 --- a/flake.nix +++ b/flake.nix @@ -25,6 +25,11 @@ url = "github:BirdeeHub/nix-wrapper-modules"; inputs.nixpkgs.follows = "nixpkgs"; }; + + treefmt = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = diff --git a/nix/default.nix b/nix/default.nix index 2f1df7a..5fbd3b3 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -8,6 +8,7 @@ importingFlake: { imports = [ inputs.devshell.flakeModule inputs.github-actions-nix.flakeModules.default + inputs.treefmt.flakeModule flakeModules.prek-pre-commit (importApply ./general args) diff --git a/nix/general/default.nix b/nix/general/default.nix index c2adcb2..1bab9e5 100644 --- a/nix/general/default.nix +++ b/nix/general/default.nix @@ -8,6 +8,7 @@ importingFlake: { imports = [ ./action-versions.nix (importApply ./devshell.nix args) + ./formatting.nix (importApply ./pre-commit-hooks.nix args) ./workflows/check-pre-commit-hooks.nix diff --git a/nix/general/devshell.nix b/nix/general/devshell.nix index dc96395..49268eb 100644 --- a/nix/general/devshell.nix +++ b/nix/general/devshell.nix @@ -6,32 +6,39 @@ }: importingFlake: { config.perSystem = - { config, ... }: + { config, pkgs, ... }: { devshells.standards = { name = lib.mkDefault "engineering-standards"; # Include all packages from pre-commit in the shell - # - This allows easy access to the individual checks, if developers are interested in running them - # - `prek install` doesn't install the wrapped binary, so when executing the hooks, all runtimePkgs - # would be missing if they were not in the shell - packages = builtins.map (package: package.data) config.prek-pre-commit.package.runtimePkgs; + # - This allows easy access to the individual checks, if developers are interested in running them + # - `prek install` doesn't install the wrapped binary, so when executing the hooks, all runtimePkgs + # would be missing if they were not in the shell + packages = builtins.map (package: package.data) config.prek-pre-commit.package.runtimePkgs; commands = [ { - name = "prek"; - help = "Run pre-commit hooks on currently staged changes"; + name = "nix fmt"; + help = "Auto-format all files in the project."; category = "[[lints and checks]]"; - package = config.prek-pre-commit.package.wrapper; + package = pkgs.nix; } { - name = "prek -s main -o HEAD"; - help = "Run pre-commit hooks on all commits in the current branch"; + name = "prek"; + help = "Run pre-commit hooks on currently staged changes"; category = "[[lints and checks]]"; package = config.prek-pre-commit.package.wrapper; } - ]; + + { + name = "prek -s main -o HEAD"; + help = "Run pre-commit hooks on all commits in the current branch"; + category = "[[lints and checks]]"; + package = config.prek-pre-commit.package.wrapper; + } + ]; }; }; } diff --git a/nix/general/formatting.nix b/nix/general/formatting.nix new file mode 100644 index 0000000..a863e2a --- /dev/null +++ b/nix/general/formatting.nix @@ -0,0 +1,34 @@ +{ + perSystem = + { config, ... }: + { + treefmt = { + # `prek` is in charge of running these kinds of checks, we don't + # want to run formatters with `nix flake check`. + flakeCheck = false; + + settings = { + allowMissingFormatter = false; + walk = "git"; + + excludes = config.filegen.generatedFiles; + }; + + programs = { + prettier.enable = true; + sqlfluff.enable = true; + shfmt.enable = true; + }; + + settings.formatter.prettier.excludes = [ "*.md" ]; + }; + + filegen.settings.files = [ + { + type = "copy"; + target = "treefmt.toml"; + source = config.treefmt.build.configFile; + } + ]; + }; +} diff --git a/nix/general/pre-commit-hooks.nix b/nix/general/pre-commit-hooks.nix index 16d326d..221e64d 100644 --- a/nix/general/pre-commit-hooks.nix +++ b/nix/general/pre-commit-hooks.nix @@ -6,13 +6,22 @@ }: importingFlake: { config.perSystem = - { pkgs, self', ... }: + { + config, + pkgs, + self', + ... + }: { prek-pre-commit = { - package.runtimePkgs = lib.attrValues { - inherit (pkgs) editorconfig-checker typos; - filegen-activate = self'.apps.filegen-activate.meta.package; - }; + package.runtimePkgs = lib.attrValues ( + { + inherit (pkgs) editorconfig-checker typos; + filegen-activate = self'.apps.filegen-activate.meta.package; + treefmt = config.treefmt.package; + } + // config.treefmt.build.programs + ); workspaces.".".repos = [ { @@ -114,6 +123,15 @@ importingFlake: { entry = "filegen-apply-script"; language = "system"; } + + { + id = "treefmt"; + name = "treefmt"; + description = "Format *all* files"; + + entry = "treefmt"; + language = "system"; + } ]; } ]; diff --git a/nix/modules/filegen.nix b/nix/modules/filegen.nix index 0e43339..2a866bf 100644 --- a/nix/modules/filegen.nix +++ b/nix/modules/filegen.nix @@ -144,6 +144,14 @@ in type = types.package; }; + generatedFiles = lib.mkOption { + description = '' + The list of generated files. + ''; + readOnly = true; + type = types.listOf types.str; + }; + scripts = { activate = lib.mkOption { description = '' @@ -170,10 +178,14 @@ in let cfg = config.filegen; new-manifest = pkgs.writers.writeJSON "filegen-manifest.json" ( - config.filegen.settings // { inherit (smfh) version; } + cfg.settings // { inherit (smfh) version; } ); in { + filegen.generatedFiles = map (file: lib.removePrefix "./" file.target) ( + cfg.settings.files ++ [ { target = ".config/filegen-manifest.json"; } ] + ); + filegen.settings.files = [ { # We generate a .gitattributes file, which tells the GitHub diff @@ -185,14 +197,14 @@ in target = ".gitattributes"; source = pkgs.writeTextFile { name = ".gitattributes"; - text = lib.pipe (cfg.settings.files ++ [ { target = ".config/filegen-manifest.json"; } ]) [ - (map (file: lib.removePrefix "./" file.target)) + text = lib.pipe cfg.generatedFiles [ (map (target: "${target} linguist-generated")) lib.concatLines ]; }; } ]; + apps = let activate = pkgs.writers.writeNuBin "filegen-apply-script" '' diff --git a/nix/nix/default.nix b/nix/nix/default.nix index 38f1b13..5f81849 100644 --- a/nix/nix/default.nix +++ b/nix/nix/default.nix @@ -5,7 +5,7 @@ ... }: importingFlake: { - imports = [ ]; + imports = [ ./formatting.nix ]; options.perSystem = flake-parts-lib.mkPerSystemOption ({ options.famedly.standards.nix.projects = lib.mkOption { diff --git a/nix/nix/formatting.nix b/nix/nix/formatting.nix new file mode 100644 index 0000000..722fa09 --- /dev/null +++ b/nix/nix/formatting.nix @@ -0,0 +1,6 @@ +{ + perSystem.treefmt = { + programs.nixfmt.enable = true; + settings.formatter.nixfmt.options = [ "--strict" ]; + }; +} diff --git a/treefmt.toml b/treefmt.toml new file mode 100644 index 0000000..1eaf684 --- /dev/null +++ b/treefmt.toml @@ -0,0 +1,61 @@ +allowMissingFormatter = false +excludes = [ + "*.lock", + "*.patch", + "package-lock.json", + "go.mod", + "go.sum", + ".gitattributes", + ".gitignore", + ".gitmodules", + ".hgignore", + ".svnignore", + "LICENSE", + ".pre-commit-config.yaml", + ".github/workflows/check-pre-commit-hooks.yml", + ".gitattributes", + "treefmt.toml", + ".config/filegen-manifest.json", +] +walk = "git" + +[formatter.nixfmt] +command = "/nix/store/01945jlqwsgq7a9nbw00y0p0x6027yy3-nixfmt-1.2.0/bin/nixfmt" +excludes = [] +includes = ["*.nix"] +options = ["--strict"] + +[formatter.prettier] +command = "/nix/store/yk6kxy9ahaxw9xrq16r5wphz4k92zcb2-prettier-3.8.3/bin/prettier" +excludes = ["*.md"] +includes = [ + "*.cjs", + "*.css", + "*.html", + "*.js", + "*.json", + "*.json5", + "*.jsx", + "*.md", + "*.mdx", + "*.mjs", + "*.scss", + "*.ts", + "*.tsx", + "*.vue", + "*.yaml", + "*.yml", +] +options = ["--write"] + +[formatter.shfmt] +command = "/nix/store/hyjd216vnfpm7md7llw5vjakd5n2xj1d-shfmt-3.13.1/bin/shfmt" +excludes = [] +includes = ["*.sh", "*.bash", "*.envrc", "*.envrc.*"] +options = ["-w", "-i", "2", "-s"] + +[formatter.sqlfluff] +command = "/nix/store/r1d77w9ywqp8wgczsbxa0xrbl5ipj0vx-sqlfluff-4.2.1/bin/sqlfluff" +excludes = [] +includes = ["*.sql"] +options = ["format", "--disable-progress-bar", "--processes", "0"]