diff options
Diffstat (limited to 'docs/handbook/ci/overview.md')
| -rw-r--r-- | docs/handbook/ci/overview.md | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/docs/handbook/ci/overview.md b/docs/handbook/ci/overview.md new file mode 100644 index 0000000000..19d42cfe2a --- /dev/null +++ b/docs/handbook/ci/overview.md @@ -0,0 +1,494 @@ +# CI Infrastructure — Overview + +## Purpose + +The `ci/` directory contains the Continuous Integration infrastructure for the Project Tick monorepo. +It provides reproducible builds, automated code quality checks, commit message validation, +pull request lifecycle management, and code ownership enforcement — all orchestrated through +Nix expressions and JavaScript-based GitHub Actions scripts. + +The CI system is designed around three core principles: + +1. **Reproducibility** — Pinned Nix dependencies ensure identical builds across environments +2. **Conventional Commits** — Enforced commit message format for automated changelog generation +3. **Ownership-driven review** — CODEOWNERS-style file ownership with automated review routing + +--- + +## Directory Structure + +``` +ci/ +├── OWNERS # Code ownership file (CODEOWNERS format) +├── README.md # CI README with local testing instructions +├── default.nix # Nix CI entry point — treefmt, codeowners-validator, shell +├── pinned.json # Pinned Nixpkgs + treefmt-nix revisions (npins format) +├── update-pinned.sh # Script to update pinned.json via npins +├── supportedBranches.js # Branch classification logic for CI decisions +├── codeowners-validator/ # Builds codeowners-validator from source (Go) +│ ├── default.nix # Nix derivation for codeowners-validator +│ ├── owners-file-name.patch # Patch: custom OWNERS file path via OWNERS_FILE env var +│ └── permissions.patch # Patch: remove write-access check (not needed for non-native CODEOWNERS) +└── github-script/ # JavaScript CI scripts for GitHub Actions + ├── run # CLI entry point for local testing (commander-based) + ├── lint-commits.js # Commit message linter (Conventional Commits) + ├── prepare.js # PR preparation: mergeability, branch targeting, touched files + ├── reviews.js # Review lifecycle: post, dismiss, minimize bot reviews + ├── get-pr-commit-details.js # Extract commit SHAs, subjects, changed paths via git + ├── withRateLimit.js # GitHub API rate limiting with Bottleneck + ├── package.json # Node.js dependencies (@actions/core, @actions/github, bottleneck, commander) + ├── package-lock.json # Lockfile for reproducible npm installs + ├── shell.nix # Nix dev shell for github-script (Node.js + gh CLI) + ├── README.md # Local testing documentation + ├── .editorconfig # Editor configuration + ├── .gitignore # Git ignore rules + └── .npmrc # npm configuration +``` + +--- + +## How CI Works End-to-End + +### 1. Triggering + +CI runs are triggered by GitHub Actions workflows (defined in `.github/workflows/`) when +pull requests are opened, updated, or merged against supported branches. The `supportedBranches.js` +module classifies branches to determine which checks to run. + +### 2. Environment Setup + +The CI environment is bootstrapped via `ci/default.nix`, which: + +- Reads pinned dependency revisions from `ci/pinned.json` +- Fetches the exact Nixpkgs tarball at the pinned commit +- Imports `treefmt-nix` for code formatting +- Builds the `codeowners-validator` tool with Project Tick–specific patches +- Exposes a development shell with all CI tools available + +```nix +# ci/default.nix — entry point +let + pinned = (builtins.fromJSON (builtins.readFile ./pinned.json)).pins; +in +{ + system ? builtins.currentSystem, + nixpkgs ? null, +}: +let + nixpkgs' = + if nixpkgs == null then + fetchTarball { + inherit (pinned.nixpkgs) url; + sha256 = pinned.nixpkgs.hash; + } + else + nixpkgs; + + pkgs = import nixpkgs' { + inherit system; + config = { }; + overlays = [ ]; + }; +``` + +### 3. Code Formatting (treefmt) + +The `default.nix` configures `treefmt-nix` with multiple formatters: + +| Formatter | Purpose | Configuration | +|-------------|--------------------------------------|----------------------------------------------| +| `actionlint` | GitHub Actions workflow linting | Enabled, no custom config | +| `biome` | JavaScript/TypeScript formatting | Single quotes, no semicolons, no JSON format | +| `keep-sorted`| Sorted list enforcement | Enabled, no custom config | +| `nixfmt` | Nix expression formatting | Uses `pkgs.nixfmt` | +| `yamlfmt` | YAML formatting | Retains line breaks | +| `zizmor` | GitHub Actions security scanning | Enabled, no custom config | + +Biome is configured with specific style rules: + +```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" +]; +``` + +### 4. Commit Linting + +When a PR is opened or updated, `ci/github-script/lint-commits.js` validates every commit +message against the Conventional Commits specification. It checks: + +- Format: `type(scope): subject` +- No `fixup!`, `squash!`, or `amend!` prefixes (must be rebased before merge) +- No trailing period on subject line +- Lowercase first letter in subject +- Known scopes matching monorepo project directories + +The supported types are: + +```javascript +const CONVENTIONAL_TYPES = [ + 'build', 'chore', 'ci', 'docs', 'feat', 'fix', + 'perf', 'refactor', 'revert', 'style', 'test', +] +``` + +And the known scopes correspond to monorepo directories: + +```javascript +const KNOWN_SCOPES = [ + 'archived', 'cgit', 'ci', 'cmark', 'corebinutils', + 'forgewrapper', 'genqrcode', 'hooks', 'images4docker', + 'json4cpp', 'libnbtplusplus', 'meshmc', 'meta', 'mnv', + 'neozip', 'tomlplusplus', 'repo', 'deps', +] +``` + +### 5. PR Preparation and Validation + +The `ci/github-script/prepare.js` script handles PR lifecycle: + +1. **Mergeability check** — Polls GitHub's mergeability computation with exponential backoff + (5s, 10s, 20s, 40s, 80s retries) +2. **Branch classification** — Classifies base and head branches using `supportedBranches.js` +3. **Base branch suggestion** — For WIP branches, computes the optimal base branch by comparing + merge-base commit distances across `master` and all release branches +4. **Merge conflict detection** — If the PR has conflicts, uses the head SHA directly; otherwise + uses the merge commit SHA +5. **Touched file detection** — Identifies which CI-relevant paths were modified: + - `ci` — any file under `ci/` + - `pinned` — `ci/pinned.json` specifically + - `github` — any file under `.github/` + +### 6. Review Lifecycle Management + +The `ci/github-script/reviews.js` module manages bot reviews: + +- **`postReview()`** — Posts or updates a review with a tracking comment tag + (`<!-- projt review key: <key>; resolved: false -->`) +- **`dismissReviews()`** — Dismisses, minimizes (marks as outdated), or resolves bot reviews + when the underlying issue is fixed +- Reviews are tagged with a `reviewKey` to allow multiple independent review concerns + on the same PR + +### 7. Rate Limiting + +All GitHub API calls go through `ci/github-script/withRateLimit.js`, which uses the +Bottleneck library for request throttling: + +- Read requests: controlled by a reservoir updated from the GitHub rate limit API +- Write requests (`POST`, `PUT`, `PATCH`, `DELETE`): minimum 1 second between calls +- The reservoir keeps 1000 spare requests for other concurrent jobs +- Reservoir is refreshed every 60 seconds +- Requests to `github.com` (not the API), `/rate_limit`, and `/search/` endpoints bypass throttling + +### 8. Code Ownership Validation + +The `ci/codeowners-validator/` builds a patched version of the +[codeowners-validator](https://github.com/mszostok/codeowners-validator) tool: + +- Fetched from GitHub at a specific pinned commit +- Two patches applied: + - `owners-file-name.patch` — Adds support for custom CODEOWNERS file path via `OWNERS_FILE` env var + - `permissions.patch` — Removes the write-access permission check (not needed since Project Tick + uses an `OWNERS` file rather than GitHub's native `CODEOWNERS`) + +This validates the `ci/OWNERS` file against the actual repository structure and GitHub +organization membership. + +--- + +## Component Interaction Flow + +``` +┌─────────────────────────────────────────┐ +│ GitHub Actions Workflow │ +│ (.github/workflows/*.yml) │ +└──────────────┬──────────────────────────┘ + │ triggers + ▼ +┌──────────────────────────────────────────┐ +│ ci/default.nix │ +│ ┌─────────┐ ┌──────────────────────┐ │ +│ │pinned. │ │ treefmt-nix │ │ +│ │json │──│ (formatting checks) │ │ +│ └─────────┘ └──────────────────────┘ │ +│ ┌──────────────────────┐ │ +│ │ codeowners-validator │ │ +│ │ (OWNERS validation) │ │ +│ └──────────────────────┘ │ +└──────────────┬───────────────────────────┘ + │ also triggers + ▼ +┌──────────────────────────────────────────┐ +│ ci/github-script/ │ +│ ┌────────────────┐ ┌───────────────┐ │ +│ │ prepare.js │ │ lint-commits │ │ +│ │ (PR validation) │ │ (commit msg) │ │ +│ └───────┬────────┘ └──────┬────────┘ │ +│ │ │ │ +│ ┌───────▼────────┐ ┌──────▼────────┐ │ +│ │ reviews.js │ │ supported │ │ +│ │ (bot reviews) │ │ Branches.js │ │ +│ └───────┬────────┘ └───────────────┘ │ +│ │ │ +│ ┌───────▼────────┐ │ +│ │ withRateLimit │ │ +│ │ (API throttle) │ │ +│ └────────────────┘ │ +└──────────────────────────────────────────┘ +``` + +--- + +## Key Design Decisions + +### Why Nix for CI? + +Nix ensures that every CI run uses the exact same versions of tools, compilers, and +libraries. The `pinned.json` file locks specific commits of Nixpkgs and treefmt-nix, +eliminating "works on my machine" problems. + +### Why a custom OWNERS file? + +GitHub's native CODEOWNERS has limitations: +- Must be in `.github/CODEOWNERS`, `CODEOWNERS`, or `docs/CODEOWNERS` +- Requires repository write access for all listed owners +- Cannot be extended with custom validation + +Project Tick uses `ci/OWNERS` with the same glob pattern syntax but adds: +- Custom file path support (via the `OWNERS_FILE` environment variable) +- No write-access requirement (via the permissions patch) +- Integration with the codeowners-validator for structural validation + +### Why Bottleneck for rate limiting? + +GitHub Actions can run many jobs in parallel, and each job makes API calls. Without +throttling, a large CI run could exhaust the GitHub API rate limit (5000 requests/hour +for authenticated requests). Bottleneck provides: +- Concurrency control (1 concurrent request by default) +- Reservoir-based rate limiting (dynamically updated from the API) +- Separate throttling for mutative requests (1 second minimum between writes) + +### Why local testing support? + +The `ci/github-script/run` CLI allows developers to test CI scripts locally before +pushing. This accelerates development and reduces CI feedback loops: + +```bash +cd ci/github-script +nix-shell # sets up Node.js + dependencies +gh auth login # authenticate with GitHub +./run lint-commits YongDo-Hyun Project-Tick 123 +./run prepare YongDo-Hyun Project-Tick 123 +``` + +--- + +## Pinned Dependencies + +The CI system pins two external Nix sources: + +| Dependency | Source | Branch | Purpose | +|-------------|----------------------------------------------|--------------------|--------------------------------| +| `nixpkgs` | `github:NixOS/nixpkgs` | `nixpkgs-unstable` | Base package set for CI tools | +| `treefmt-nix`| `github:numtide/treefmt-nix` | `main` | Multi-formatter orchestrator | + +Pins are stored in `ci/pinned.json` in npins v5 format: + +```json +{ + "pins": { + "nixpkgs": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "NixOS", + "repo": "nixpkgs" + }, + "branch": "nixpkgs-unstable", + "revision": "bde09022887110deb780067364a0818e89258968", + "url": "https://github.com/NixOS/nixpkgs/archive/bde09022887110deb780067364a0818e89258968.tar.gz", + "hash": "13mi187zpa4rw680qbwp7pmykjia8cra3nwvjqmsjba3qhlzif5l" + }, + "treefmt-nix": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "numtide", + "repo": "treefmt-nix" + }, + "branch": "main", + "revision": "e96d59dff5c0d7fddb9d113ba108f03c3ef99eca", + "url": "https://github.com/numtide/treefmt-nix/archive/e96d59dff5c0d7fddb9d113ba108f03c3ef99eca.tar.gz", + "hash": "02gqyxila3ghw8gifq3mns639x86jcq079kvfvjm42mibx7z5fzb" + } + }, + "version": 5 +} +``` + +To update pins: + +```bash +cd ci/ +./update-pinned.sh +``` + +This runs `npins --lock-file pinned.json update` to fetch the latest revisions. + +--- + +## Node.js Dependencies (github-script) + +The `ci/github-script/package.json` declares: + +```json +{ + "private": true, + "dependencies": { + "@actions/core": "1.11.1", + "@actions/github": "6.0.1", + "bottleneck": "2.19.5", + "commander": "14.0.3" + } +} +``` + +| Package | Version | Purpose | +|-------------------|----------|-----------------------------------------------| +| `@actions/core` | `1.11.1` | GitHub Actions core utilities (logging, outputs) | +| `@actions/github` | `6.0.1` | GitHub API client (Octokit wrapper) | +| `bottleneck` | `2.19.5` | Rate limiting / request throttling | +| `commander` | `14.0.3` | CLI argument parsing for local `./run` tool | + +These versions are kept in sync with the +[actions/github-script](https://github.com/actions/github-script) action. + +--- + +## Nix Dev Shell + +The `ci/github-script/shell.nix` provides a development environment for working on +the CI scripts locally: + +```nix +{ + system ? builtins.currentSystem, + pkgs ? (import ../../ci { inherit system; }).pkgs, +}: + +pkgs.callPackage ( + { + gh, + importNpmLock, + mkShell, + nodejs, + }: + mkShell { + packages = [ + gh + importNpmLock.hooks.linkNodeModulesHook + nodejs + ]; + + npmDeps = importNpmLock.buildNodeModules { + npmRoot = ./.; + inherit nodejs; + }; + } +) { } +``` + +This gives you: +- `nodejs` — Node.js runtime +- `gh` — GitHub CLI for authentication +- `importNpmLock.hooks.linkNodeModulesHook` — Automatically links `node_modules` from the Nix store + +--- + +## Outputs Exposed by default.nix + +The `ci/default.nix` exposes the following attributes: + +| Attribute | Type | Description | +|----------------------|-----------|--------------------------------------------------| +| `pkgs` | Nixpkgs | The pinned Nixpkgs package set | +| `fmt.shell` | Derivation| Dev shell with treefmt formatter available | +| `fmt.pkg` | Derivation| The treefmt wrapper binary | +| `fmt.check` | Derivation| A check derivation that fails if formatting drifts| +| `codeownersValidator`| Derivation| Patched codeowners-validator binary | +| `shell` | Derivation| Combined CI dev shell (fmt + codeowners-validator)| + +```nix +rec { + inherit pkgs fmt; + codeownersValidator = pkgs.callPackage ./codeowners-validator { }; + + shell = pkgs.mkShell { + packages = [ + fmt.pkg + codeownersValidator + ]; + }; +} +``` + +--- + +## Integration with Root Flake + +The root `flake.nix` provides: + +- Dev shells for all supported systems (`aarch64-linux`, `x86_64-linux`, etc.) +- A formatter (`nixfmt-rfc-style`) +- The CI `default.nix` is imported indirectly via the flake for Nix-based CI runs + +```nix +{ + description = "Project Tick is a project dedicated to providing developers + with ease of use and users with long-lasting software."; + + inputs = { + nixpkgs.url = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"; + }; + ... +} +``` + +--- + +## Summary of CI Checks + +| Check | Tool / Script | Scope | +|--------------------------|---------------------------|------------------------------------| +| Code formatting | treefmt (biome, nixfmt, yamlfmt, actionlint, zizmor) | All source files | +| Commit message format | `lint-commits.js` | All commits in a PR | +| PR mergeability | `prepare.js` | Every PR | +| Base branch targeting | `prepare.js` + `supportedBranches.js` | WIP → development PRs | +| Code ownership validity | `codeowners-validator` | `ci/OWNERS` file | +| GitHub Actions security | `zizmor` (via treefmt) | `.github/workflows/*.yml` | +| Sorted list enforcement | `keep-sorted` (via treefmt)| Files with keep-sorted markers | + +--- + +## Related Documentation + +- [Nix Infrastructure](nix-infrastructure.md) — Deep dive into the Nix expressions +- [Commit Linting](commit-linting.md) — Commit message conventions and validation rules +- [PR Validation](pr-validation.md) — Pull request checks and lifecycle management +- [Branch Strategy](branch-strategy.md) — Branch naming, classification, and release branches +- [CODEOWNERS](codeowners.md) — Ownership file format and validation +- [Formatting](formatting.md) — Code formatting configuration and tools +- [Rate Limiting](rate-limiting.md) — GitHub API rate limiting strategy |
