summaryrefslogtreecommitdiff
path: root/docs/handbook/ci/formatting.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/handbook/ci/formatting.md')
-rw-r--r--docs/handbook/ci/formatting.md298
1 files changed, 298 insertions, 0 deletions
diff --git a/docs/handbook/ci/formatting.md b/docs/handbook/ci/formatting.md
new file mode 100644
index 0000000000..9d2ddb35a4
--- /dev/null
+++ b/docs/handbook/ci/formatting.md
@@ -0,0 +1,298 @@
+# Code Formatting
+
+## Overview
+
+Project Tick uses [treefmt](https://github.com/numtide/treefmt) orchestrated through
+[treefmt-nix](https://github.com/numtide/treefmt-nix) to enforce consistent code formatting
+across the entire monorepo. The formatting configuration lives in `ci/default.nix` and
+covers JavaScript, Nix, YAML, GitHub Actions workflows, and sorted-list enforcement.
+
+---
+
+## Configured Formatters
+
+### Summary Table
+
+| Formatter | Language/Files | Key Settings |
+|-------------|-------------------------------|-------------------------------------------|
+| `actionlint` | GitHub Actions YAML | Default (syntax + best practices) |
+| `biome` | JavaScript / TypeScript | Single quotes, optional semicolons |
+| `keep-sorted`| Any (marked sections) | Default |
+| `nixfmt` | Nix expressions | nixfmt-rfc-style |
+| `yamlfmt` | YAML files | Retain line breaks |
+| `zizmor` | GitHub Actions YAML | Security scanning |
+
+---
+
+### actionlint
+
+**Purpose**: Validates GitHub Actions workflow files for syntax errors, type mismatches,
+and best practices.
+
+**Scope**: `.github/workflows/*.yml`
+
+**Configuration**: Default — no custom settings.
+
+```nix
+programs.actionlint.enable = true;
+```
+
+**What it catches**:
+- Invalid workflow syntax
+- Missing or incorrect `runs-on` values
+- Type mismatches in expressions
+- Unknown action references
+
+---
+
+### biome
+
+**Purpose**: Formats JavaScript and TypeScript source files with consistent style.
+
+**Scope**: All `.js` and `.ts` files except `*.min.js`
+
+**Configuration**:
+
+```nix
+programs.biome = {
+ enable = true;
+ validate.enable = false;
+ settings.formatter = {
+ useEditorconfig = true;
+ };
+ settings.javascript.formatter = {
+ quoteStyle = "single";
+ semicolons = "asNeeded";
+ };
+ settings.json.formatter.enabled = false;
+};
+settings.formatter.biome.excludes = [
+ "*.min.js"
+];
+```
+
+**Style rules**:
+
+| Setting | Value | Effect |
+|---------------------|----------------|-------------------------------------------|
+| `useEditorconfig` | `true` | Respects `.editorconfig` (indent, etc.) |
+| `quoteStyle` | `"single"` | Uses `'string'` instead of `"string"` |
+| `semicolons` | `"asNeeded"` | Only inserts `;` where ASI requires it |
+| `validate.enable` | `false` | No lint-level validation, only formatting |
+| `json.formatter` | `disabled` | JSON files are not formatted by biome |
+
+**Exclusions**: `*.min.js` — Minified JavaScript files are never reformatted.
+
+---
+
+### keep-sorted
+
+**Purpose**: Enforces alphabetical ordering in marked sections of any file type.
+
+**Scope**: Files containing `keep-sorted` markers.
+
+```nix
+programs.keep-sorted.enable = true;
+```
+
+**Usage**: Add markers around sections that should stay sorted:
+
+```
+# keep-sorted start
+apple
+banana
+cherry
+# keep-sorted end
+```
+
+---
+
+### nixfmt
+
+**Purpose**: Formats Nix expressions according to the RFC-style convention.
+
+**Scope**: All `.nix` files.
+
+```nix
+programs.nixfmt = {
+ enable = true;
+ package = pkgs.nixfmt;
+};
+```
+
+The `pkgs.nixfmt` package from the pinned Nixpkgs provides the formatter. This
+is `nixfmt-rfc-style`, the official Nix formatting standard.
+
+---
+
+### yamlfmt
+
+**Purpose**: Formats YAML files with consistent indentation and structure.
+
+**Scope**: All `.yml` and `.yaml` files.
+
+```nix
+programs.yamlfmt = {
+ enable = true;
+ settings.formatter = {
+ retain_line_breaks = true;
+ };
+};
+```
+
+**Key setting**: `retain_line_breaks = true` — Preserves intentional blank lines between
+YAML sections, preventing the formatter from collapsing the file into a dense block.
+
+---
+
+### zizmor
+
+**Purpose**: Security scanner for GitHub Actions workflows. Detects injection
+vulnerabilities, insecure defaults, and untrusted input handling.
+
+**Scope**: `.github/workflows/*.yml`
+
+```nix
+programs.zizmor.enable = true;
+```
+
+**What it detects**:
+- Script injection via `${{ github.event.* }}` in `run:` steps
+- Insecure use of `pull_request_target`
+- Unquoted expressions that could be exploited
+- Dangerous permission configurations
+
+---
+
+## treefmt Global Settings
+
+```nix
+projectRootFile = ".git/config";
+settings.verbose = 1;
+settings.on-unmatched = "debug";
+```
+
+| Setting | Value | Purpose |
+|--------------------|---------------|----------------------------------------------|
+| `projectRootFile` | `.git/config` | Identifies repository root for treefmt |
+| `settings.verbose` | `1` | Logs which files each formatter processes |
+| `settings.on-unmatched` | `"debug"` | Files with no matching formatter are logged at debug level |
+
+---
+
+## Running Formatters
+
+### In CI
+
+The formatting check runs as a Nix derivation:
+
+```bash
+nix-build ci/ -A fmt.check
+```
+
+This:
+1. Copies the full source tree (excluding `.git`) into the Nix store
+2. Runs all configured formatters
+3. Fails with a diff if any file would be reformatted
+
+### Locally (Nix Shell)
+
+```bash
+cd ci/
+nix-shell # enter CI dev shell
+treefmt # format all files
+treefmt --check # check without modifying (dry run)
+```
+
+### Locally (Nix Build)
+
+```bash
+# Just check (no modification):
+nix-build ci/ -A fmt.check
+
+# Get the formatter binary:
+nix-build ci/ -A fmt.pkg
+./result/bin/treefmt
+```
+
+---
+
+## Source Tree Construction
+
+The treefmt check operates on a clean copy of the source tree:
+
+```nix
+fs = pkgs.lib.fileset;
+src = fs.toSource {
+ root = ../.;
+ fileset = fs.difference ../. (fs.maybeMissing ../.git);
+};
+```
+
+This:
+- Takes the entire repository directory (`../.` from `ci/`)
+- Excludes the `.git` directory (which is large and irrelevant for formatting)
+- `fs.maybeMissing` handles the case where `.git` doesn't exist (e.g., in tarballs)
+
+The resulting source is passed to`fmt.check`:
+
+```nix
+check = treefmtEval.config.build.check src;
+```
+
+---
+
+## Formatter Outputs
+
+The formatting system exposes three Nix attributes:
+
+```nix
+{
+ shell = treefmtEval.config.build.devShell; # Interactive shell
+ pkg = treefmtEval.config.build.wrapper; # treefmt binary
+ check = treefmtEval.config.build.check src; # CI check derivation
+}
+```
+
+| Attribute | Use Case |
+|------------|--------------------------------------------------------|
+| `fmt.shell` | `nix develop .#fmt.shell` — interactive formatting |
+| `fmt.pkg` | The treefmt wrapper with all formatters bundled |
+| `fmt.check` | `nix build .#fmt.check` — CI formatting check |
+
+---
+
+## Troubleshooting
+
+### "File would be reformatted"
+
+If CI fails with formatting issues:
+
+```bash
+# Enter the CI shell to get the exact same formatter versions:
+cd ci/
+nix-shell
+
+# Format all files:
+treefmt
+
+# Stage and commit the changes:
+git add -u
+git commit -m "style(repo): apply treefmt formatting"
+```
+
+### Editor Integration
+
+For real-time formatting in VS Code:
+
+1. Use the biome extension for JavaScript/TypeScript
+2. Configure single quotes and optional semicolons to match CI settings
+3. Use nixpkgs-fmt or nixfmt for Nix files
+
+### Formatter Conflicts
+
+Each file type has exactly one formatter assigned by treefmt. If a file matches
+multiple formatters, treefmt reports a conflict. The current configuration avoids
+this by:
+- Disabling biome's JSON formatter
+- Having non-overlapping file type coverage