Skip to content

Commit f1fd8ea

Browse files
authored
refactor: nested ci flake with minimal root (#1)
* refactor: move dev tooling into nested ci flake Minimal root flake.nix with zero inputs — exports only module, flakeModule, flakeModules.default. All dev/CI modules relocated to templates/ci/ as a nested flake following the ned pattern. - templates/ci/flake.nix holds all inputs - with-inputs.nix bootstraps from CI lock for shell.nix - Justfile uses CI flake's treefmt-nix formatter - GH Actions workflow uses nix-shell + just ci - test-cases moved to templates/ci/test-cases/ - README and LICENSE updated * feat: unify formatting pipeline across both file APIs Apply the formatting pipeline (treefmt, global formatters, per-file format) to files.files list entries, not just files.file attrset entries. - Add format option to files.files list API - New _formattedFiles internal option processes all entries uniformly - treefmtFormat uses file-based invocation instead of --stdin to match direct treefmt output - Fix unused onChange binding flagged by nixf-diagnose * feat: add demo templates for flake-parts, bare-flake, and no-flake Three templates showing the full API surface: - flake-parts: import-tree, treefmt, formatters, onChange, per-file format overrides, enable toggle, both APIs together - bare-flake: vanilla flake with evalModules, no flake-parts - no-flake: pure default.nix with import
1 parent 79ac025 commit f1fd8ea

38 files changed

Lines changed: 634 additions & 207 deletions

File tree

.github/workflows/check.yaml

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,26 @@
11
{
2-
"jobs": {
3-
"check": {
4-
"runs-on": "ubuntu-latest",
5-
"steps": [
2+
"jobs":
3+
{
4+
"check":
65
{
7-
"uses": "actions/checkout@v4"
6+
"runs-on": "ubuntu-latest",
7+
"steps":
8+
[
9+
{ "uses": "actions/checkout@v4" },
10+
{
11+
"uses": "nixbuild/nix-quick-install-action@master",
12+
"with":
13+
{
14+
"nix_conf": "extra-experimental-features = recursive-nix\nextra-system-features = recursive-nix\nkeep-env-derivations = true\nkeep-outputs = true\n",
15+
},
16+
},
17+
{
18+
"uses": "nix-community/cache-nix-action@main",
19+
"with": { "primary-key": "a-single-key" },
20+
},
21+
{ "run": "nix-shell --run 'just ci'" },
22+
],
823
},
9-
{
10-
"uses": "nixbuild/nix-quick-install-action@master",
11-
"with": {
12-
"nix_conf": "extra-experimental-features = recursive-nix\nextra-system-features = recursive-nix\nkeep-env-derivations = true\nkeep-outputs = true\n"
13-
}
14-
},
15-
{
16-
"uses": "nix-community/cache-nix-action@main",
17-
"with": {
18-
"primary-key": "a-single-key"
19-
}
20-
},
21-
{
22-
"run": "nix flake --accept-flake-config check --print-build-logs --keep-going"
23-
}
24-
]
25-
}
26-
},
27-
"on": {
28-
"push": {},
29-
"workflow_call": {}
30-
}
24+
},
25+
"on": { "push": {}, "workflow_call": {} },
3126
}

Justfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
help:
2+
just -l
3+
4+
fmt *args:
5+
"$(nix build ./templates/ci#formatter.$(nix eval --impure --expr builtins.currentSystem) --override-input files . --no-link --print-out-paths)/bin/treefmt" {{args}}
6+
7+
ci:
8+
just fmt --ci --no-cache
9+
cd templates/ci && nix flake --accept-flake-config check --override-input files ../.. --print-build-logs --keep-going

LICENSE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
MIT License
22

3+
Copyright (c) 2026 Jason Bowman (@sini)
34
Copyright (c) 2025 Shahar "Dawn" Or
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy

README.md

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,35 @@
11
# files
22

3+
[![Nix Flake Check](https://github.com/sini/files/actions/workflows/check.yaml/badge.svg)](https://github.com/sini/files/actions/workflows/check.yaml)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5+
36
A [flake-parts](https://flake.parts) module for managing in-repository
47
generated files. Declare file contents in Nix, write them with one command,
58
and verify they stay in sync via `nix flake check`.
69

7-
Fork of [mightyiam/files](https://github.com/mightyiam/files).
10+
This project is an independent, hard fork of
11+
[mightyiam/files](https://github.com/mightyiam/files). It is maintained entirely
12+
separately and has no affiliation with, nor is it endorsed by, the original
13+
author.
14+
15+
## Table of Contents
16+
17+
- [files](#files)
18+
- [Table of Contents](#table-of-contents)
19+
- [Motivation](#motivation)
20+
- [What this fork adds](#what-this-fork-adds)
21+
- [Quick start](#quick-start)
22+
- [With treefmt](#with-treefmt)
23+
- [With derivation sources](#with-derivation-sources)
24+
- [In a monorepo](#in-a-monorepo)
25+
- [API reference](#api-reference)
26+
- [`files.file`](#filesfile)
27+
- [`files.treefmt`](#filestreefmt)
28+
- [`files.formatters`](#filesformatters)
29+
- [Formatter priority](#formatter-priority)
30+
- [`files.files` (list API)](#filesfiles-list-api)
31+
- [Other options](#other-options)
32+
- [Without flake-parts](#without-flake-parts)
833

934
## Motivation
1035

@@ -17,13 +42,13 @@ it across [den](https://github.com/denful/den)'s template ecosystem:
1742
flake-parts modules.
1843
- **Adopted experimental Nix features** — the `|>` pipe operator requires
1944
`extra-experimental-features = [ "pipe-operators" ]` in every consuming
20-
flake's `nixConfig`. We prefer stable Nix.
45+
flake's `nixConfig`. This created additional user friction.
2146
- **Renamed `path_` to `path` without alias** — a breaking change with no
22-
migration path for existing consumers.
47+
migration path or notification for existing consumers.
2348
- **No convenience API** — the list-of-`{path, drv}` interface requires
2449
boilerplate for the common case of writing text or copying a file. NixOS has
2550
had `environment.etc`-style attrset APIs for years.
26-
- **No multi-flake support** — the writer hardcodes
51+
- **No multi-flake support** — the writer hardcoded
2752
`git rev-parse --show-toplevel` with no way to target a subdirectory,
2853
making it unusable in monorepos.
2954
- **Eager eval breaks `--no-build`**`lib.readFile` at evaluation time
@@ -46,7 +71,8 @@ it across [den](https://github.com/denful/den)'s template ecosystem:
4671
- **Multi-flake repo support**`relativeRoot` for monorepo sub-flakes
4772
- **Lazy checks**`nix flake check --no-build` works on fresh clones
4873
- **Backwards compatibility**`path_` and `gitToplevel` aliases
49-
- **No experimental features**`|>` replaced with `lib.pipe`
74+
- **Stable Nix syntax** - The experimental `|>` operator is replaced with
75+
`lib.pipe`, so you don't need to enable `pipe-operators` in your nixConfig.
5076

5177
## Quick start
5278

@@ -123,7 +149,6 @@ formatter pass through unchanged.
123149
# no formatter matches .txt — passes through unchanged
124150
files.file."LICENSE".text = "MIT";
125151
126-
packages.write-files = config.files.writer.drv;
127152
};
128153
};
129154
}
@@ -179,14 +204,14 @@ All options live under `perSystem.files`.
179204
Attrset of files to manage. The attribute name is the file path relative to
180205
the project root. Use slashes for subdirectories.
181206

182-
| Option | Type | Default | Description |
183-
|--------------|-------------------------------|---------|----------------------------------------------------|
184-
| `enable` | `bool` | `true` | Set `false` to suppress this file. |
185-
| `text` | `nullOr lines` | `null` | Inline text content. Sets `source` automatically. |
186-
| `source` | `path` || Path or derivation for the file content. |
187-
| `executable` | `bool` | `false` | `chmod +x` after writing. |
188-
| `onChange` | `str` or `{ runtimeInputs, script }` | `""` | Shell commands run after all files are written. Runs under `set -euo pipefail`. |
189-
| `format` | `nullOr (name -> drv -> drv)` | `null` | Per-file formatter. Overrides all other formatters. |
207+
| Option | Type | Default | Description |
208+
| ------------ | ------------------------------------ | ------- | ------------------------------------------------------------------------------- |
209+
| `enable` | `bool` | `true` | Set `false` to suppress this file. |
210+
| `text` | `nullOr lines` | `null` | Inline text content. Sets `source` automatically. |
211+
| `source` | `path` || Path or derivation for the file content. |
212+
| `executable` | `bool` | `false` | `chmod +x` after writing. |
213+
| `onChange` | `str` or `{ runtimeInputs, script }` | `""` | Shell commands run after all files are written. Runs under `set -euo pipefail`. |
214+
| `format` | `nullOr (name -> drv -> drv)` | `null` | Per-file formatter. Overrides all other formatters. |
190215

191216
```nix
192217
perSystem = { config, pkgs, ... }: {
@@ -220,10 +245,10 @@ perSystem = { config, pkgs, ... }: {
220245

221246
### `files.treefmt`
222247

223-
| Option | Type | Default | Description |
224-
|-----------|-----------|--------------------|----------------------------------------|
225-
| `enable` | `bool` | `false` | Format entries through `nix fmt`. |
226-
| `package` | `package` | `config.formatter` | Treefmt wrapper to use. |
248+
| Option | Type | Default | Description |
249+
| --------- | --------- | ------------------ | --------------------------------- |
250+
| `enable` | `bool` | `false` | Format entries through `nix fmt`. |
251+
| `package` | `package` | `config.formatter` | Treefmt wrapper to use. |
227252

228253
### `files.formatters`
229254

@@ -247,28 +272,49 @@ files.formatters.json = name: drv:
247272
### `files.files` (list API)
248273

249274
The original list-based API. `files.file` entries merge into this list
250-
automatically. Both APIs can be used together.
275+
automatically. Both APIs can be used together. The formatting pipeline
276+
(treefmt, global formatters, per-file `format`) applies to both APIs.
277+
278+
| Option | Type | Default | Description |
279+
| ------------ | ----------------------------- | ------- | ---------------------------------------------------- |
280+
| `path` | `str` || File path relative to project root. |
281+
| `drv` | `package` || Derivation whose output is the file content. |
282+
| `executable` | `bool` | `false` | `chmod +x` after writing. |
283+
| `format` | `nullOr (name -> drv -> drv)` | `null` | Per-file formatter. Overrides all other formatters. |
284+
| `onChange` | `{ runtimeInputs, script }` | `{}` | Shell commands run after writing if content changed. |
251285

252286
```nix
253287
files.files = [
254-
{ path = "README.md"; drv = pkgs.writeText "README.md" "# Hello"; }
288+
{
289+
path = "README.md";
290+
drv = pkgs.writeText "README.md" "# Hello";
291+
}
292+
{
293+
path = "data/config.json";
294+
drv = pkgs.writers.writeJSON "config.json" { version = 1; };
295+
# per-file formatter works in both APIs
296+
format = name: drv:
297+
pkgs.runCommand "jqfmt-${name}" { nativeBuildInputs = [ pkgs.jq ]; } ''
298+
jq --sort-keys . < ${drv} > $out
299+
'';
300+
}
255301
];
256302
```
257303

258304
### Other options
259305

260-
| Option | Type | Default | Description |
261-
|----------------|------------------|------------|------------------------------------------------|
262-
| `root` | `path` || Root for check comparisons. Auto-set to `self` via flake-parts; set `root = self;` in vanilla flakes. |
263-
| `relativeRoot` | `str` | `""` | Subdir from git root for the writer. |
264-
| `generateApp` | `bool` | `false` | Expose writer as `nix run .#write-files`. |
265-
| `writer.exeFilename` | `singleLineStr` | `"write-files"` | Writer executable name. |
266-
| `writer.drv` | `package` | *(computed)*| The writer derivation (read-only). |
267-
| `checks` | `attrsOf package` | *(computed)*| Per-file check derivations (read-only). |
306+
| Option | Type | Default | Description |
307+
| -------------------- | ----------------- | --------------- | ----------------------------------------------------------------------------------------------------- |
308+
| `root` | `path` | | Root for check comparisons. Auto-set to `self` via flake-parts; set `root = self;` in vanilla flakes. |
309+
| `relativeRoot` | `str` | `""` | Subdir from git root for the writer. |
310+
| `generateApp` | `bool` | `false` | Expose writer as `nix run .#write-files`. |
311+
| `writer.exeFilename` | `singleLineStr` | `"write-files"` | Writer executable name. |
312+
| `writer.drv` | `package` | _(computed)_ | The writer derivation (read-only). |
313+
| `checks` | `attrsOf package` | _(computed)_ | Per-file check derivations (read-only). |
268314

269315
## Without flake-parts
270316

271-
Use `module.nix` directly with `evalModules` — no flake-parts dependency:
317+
Use `files.module` directly with `evalModules` — no flake-parts dependency:
272318

273319
```nix
274320
{
@@ -282,7 +328,7 @@ Use `module.nix` directly with `evalModules` — no flake-parts dependency:
282328
pkgs = nixpkgs.legacyPackages.x86_64-linux;
283329
eval = pkgs.lib.evalModules {
284330
modules = [
285-
(files + "/module.nix")
331+
files.module
286332
{
287333
config._module.args = { inherit pkgs; };
288334
config.files.root = self;
@@ -297,3 +343,6 @@ Use `module.nix` directly with `evalModules` — no flake-parts dependency:
297343
};
298344
}
299345
```
346+
347+
For usage without any flake infrastructure, see
348+
[`templates/no-flake`](templates/no-flake/default.nix).

flake-module.nix

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@
1313
imports = [ ./module.nix ];
1414
config = {
1515
files.root = lib.mkDefault self;
16-
files.treefmt.package = lib.mkIf
17-
(config.files.treefmt.enable && options.formatter.isDefined)
18-
(lib.mkDefault config.formatter);
16+
files.treefmt.package = lib.mkIf (config.files.treefmt.enable && options.formatter.isDefined) (
17+
lib.mkDefault config.formatter
18+
);
1919
checks = config.files.checks;
2020
apps = lib.mkIf config.files.generateApp {
2121
${config.files.writer.exeFilename} = {

flake.nix

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,7 @@
11
{
2-
nixConfig = {
3-
abort-on-warn = true;
4-
allow-import-from-derivation = false;
2+
outputs = _: {
3+
module = ./module.nix;
4+
flakeModule = ./flake-module.nix;
5+
flakeModules.default = ./flake-module.nix;
56
};
6-
7-
inputs = {
8-
flake-parts = {
9-
url = "github:hercules-ci/flake-parts";
10-
inputs.nixpkgs-lib.follows = "nixpkgs";
11-
};
12-
13-
git-hooks = {
14-
url = "github:cachix/git-hooks.nix";
15-
flake = false;
16-
};
17-
18-
import-tree = {
19-
url = "github:vic/import-tree";
20-
flake = false;
21-
};
22-
23-
make-shell = {
24-
url = "github:nicknovitski/make-shell";
25-
flake = false;
26-
};
27-
28-
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
29-
30-
systems.url = "github:nix-systems/default";
31-
32-
treefmt-nix = {
33-
url = "github:numtide/treefmt-nix";
34-
flake = false;
35-
};
36-
};
37-
38-
outputs =
39-
inputs:
40-
{
41-
module = ./module.nix;
42-
flakeModule = ./flake-module.nix;
43-
flakeModules.default = ./flake-module.nix;
44-
}
45-
// inputs.flake-parts.lib.mkFlake {
46-
inherit inputs;
47-
specialArgs.projectRoot = ./.;
48-
} ((import inputs.import-tree) ./modules);
497
}

0 commit comments

Comments
 (0)