Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,38 @@ jobs:
uses: gtbuchanan/tooling/.github/workflows/pre-commit.yml@main
with:
use-pnpm: true

# Render every template natively on each hosted OS. The render-templates hook
# self-selects via `chezmoi ignored`, so each runner checks only the templates
# deployed on its own OS — real coverage of each platform's own branches,
# which the single-OS pre-commit job above cannot give. android has no hosted
# runner and is covered by render-templates-android instead.
render-templates:
name: Render Templates (${{ matrix.os }})
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false
- uses: gtbuchanan/tooling/.github/actions/mise-setup@main
- run: mise run test:templates
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]

# android has no hosted runner, so render its templates in a Termux container
# (native x86_64 image on the x64 runner — no QEMU). Same test:templates engine
# as every other leg, only with the android ignore set active.
render-templates-android:
name: Render Templates (android)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: gtbuchanan/tooling/.github/actions/mise-setup@main
- run: mise run test:templates-android
name: CI

on:
Expand Down
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,21 @@ Scripts under `.chezmoiscripts/` are platform-gated via `.chezmoiignore`. Each p
directory is excluded on non-matching OSes. Use the template variables above for finer-grained
conditionals within a script (e.g., WSL vs. native Linux, personal vs. ewn).

## Template Render Lint

`scripts/lint-templates.sh` (the hk `render-templates` step) proves every
`*.tmpl` renders, skipping ones chezmoi ignores on the current OS/hosttype (via
`chezmoi ignored`). CI runs it natively per-OS plus, since android has no hosted
runner, in a Termux container (`mise run test:templates-android`); see
`.github/workflows/ci.yml`.

`chezmoi ignored` realizes every `.chezmoiexternal` entry (downloading archives,
SSH-cloning repos) just to enumerate targets, which breaks the offline check on
fresh CI and in the container. chezmoi has no flag to suppress this, so
`home/.chezmoiexternal.yaml.tmpl` carries a `{{ if not (get . "lintSkipExternals") }}`
guard that the lint trips via `--override-data` on that one call. **Do not
remove it** — the rationale is in both files' comments.

## Formatting

- UTF-8, 2-space indent, LF line endings, final newline, trim trailing whitespace.
Expand Down
6 changes: 5 additions & 1 deletion hk.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ local allSteps = (baseSteps) {
check = "gtb verify mise"
}

// Delegates to the test:templates mise task (the single render entrypoint the
// CI legs also call) rather than invoking lint-templates.sh directly, so the
// hook and CI render through one definition. The task self-selects via
// `chezmoi ignored`, checking only this OS's templates.
["render-templates"] {
glob = List("**/*.tmpl")
check = "sh scripts/lint-templates.sh {{files}}"
check = "mise run test:templates -- {{files}}"
}

// PSScriptAnalyzer over PowerShell sources. The `**/*.ps1` glob leaves
Expand Down
7 changes: 7 additions & 0 deletions home/.chezmoiexternal.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
---
{{- /* lintSkipExternals is set true only by scripts/lint-templates.sh (via
--override-data) to render this file empty, so `chezmoi ignored` need not
realize these externals — downloading archives, SSH-cloning repos — merely to
compute the template ignore set. `get` reads it without erroring when unset
(chezmoi runs templates with missingkey=error). Normal apply never sets it. */ -}}
{{- if not (get . "lintSkipExternals") }}
{{- if eq .hosttype "ewn" }}
'.agents/skills/atlassian-cli':
exact: true
Expand Down Expand Up @@ -114,3 +120,4 @@
type: git-repo
url: git@gist.github.com:1a9699e8350d2eacc73de902c29d8ea0.git
{{- end }}
{{- end }}
25 changes: 25 additions & 0 deletions mise-tasks/test/templates
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
#MISE description="Render source templates for the current platform"
#
# The single entrypoint for template rendering: the hk render-templates hook and
# the CI legs all call this. It renders each template through chezmoi for the
# host OS (scripts/lint-templates.sh), so it self-selects via `chezmoi ignored`
# — a Windows runner checks the Windows templates, a Termux host the android
# ones, etc. Given file arguments it renders just those (the hook passes changed
# files); with none it renders every source template (CI).
set -euo pipefail

# Run from the repo root regardless of caller cwd, and without git: the android
# leg renders inside a container where the worktree's .git is an unmounted host
# path, and only cwd-relative paths below are portable there. $0 is this task
# file (…/mise-tasks/test/templates), so two levels up is the root.
cd "$(dirname "$0")/../.."

if [ "$#" -gt 0 ]; then
sh scripts/lint-templates.sh "$@"
else
# Every chezmoi source template lives under the chezmoi root (home/). `-exec …
# +` batches them into one invocation and propagates a non-zero render exit;
# portable where bash arrays / `mapfile` (bash 3.2 on macOS) are not.
find home -type f -name '*.tmpl' -exec sh scripts/lint-templates.sh {} +
fi
15 changes: 15 additions & 0 deletions mise-tasks/test/templates-android
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
#MISE description="Render templates in a Termux (android) environment"
#
# android has no hosted CI runner and chezmoi's `.chezmoi.os` comes from GOOS
# (unfakeable), so the android templates can only be rendered by a real Termux
# userland. On a Termux device that's native; elsewhere we borrow one from
# termux/termux-docker. Either way the render itself is the shared test:templates
# engine, so this leg checks exactly what every other platform's leg does.
set -euo pipefail

if [ "$(uname -o 2>/dev/null)" = "Android" ]; then
exec mise run test:templates
else
exec bash scripts/docker-run.sh scripts/render-templates-termux.sh
fi
5 changes: 3 additions & 2 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ minimum_release_age = "3d"
minimum_release_age_excludes = ["npm:@gtbuchanan/*"]

# mise.tasks.toml is generated by `gtb sync mise`; this include is the one
# manual hookup (the verify-mise hk step checks it stays wired).
# manual hookup (the verify-mise hk step checks it stays wired). mise-tasks/
# holds hand-authored file tasks (e.g. test:templates) alongside it.
[task_config]
includes = ["mise.tasks.toml"]
includes = ["mise-tasks", "mise.tasks.toml"]

[tools]
actionlint = "1.7.12"
Expand Down
35 changes: 35 additions & 0 deletions scripts/docker-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
#
# Run a Termux-side script inside termux/termux-docker on a non-Termux host —
# the dev-host / CI entrypoint the `test:templates-android` mise task dispatches
# to (on a Termux device that task runs natively). The image arch is matched to
# the host so it runs natively — no QEMU — on both x86_64 CI runners and Apple
# Silicon. chezmoi reports `.chezmoi.os == android` from any Termux arch, so
# unlike claude-code-termux (which forces aarch64 for its .deb) we never need a
# fixed arch or binfmt emulation. Requires Docker (Docker Desktop on
# Windows/macOS, or dockerd).
#
# scripts/docker-run.sh <script-path-relative-to-repo-root>
set -euo pipefail

if [ "$#" -eq 0 ]; then
echo "usage: scripts/docker-run.sh <script>" >&2
exit 2
fi
script="$1"
root=$(git -C "$(dirname "$0")" rev-parse --show-toplevel)

case "$(uname -m)" in
aarch64 | arm64) tag=aarch64 ;;
*) tag=x86_64 ;;
esac

echo "==> Running $script in termux-docker:$tag…"
# --privileged: termux-docker's entrypoint does namespace/mount setup and the
# image expects Android-runtime syscalls. MSYS_NO_PATHCONV stops Git Bash on
# Windows mangling the bind-mount path. The source is mounted read-only — the
# render only reads it and writes to the container's own tmp.
MSYS_NO_PATHCONV=1 docker run --rm --privileged \
-v "$root:/src:ro" \
"termux/termux-docker:$tag" \
bash "/src/$script"
11 changes: 10 additions & 1 deletion scripts/lint-templates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,17 @@ render() {
# here and may call host-specific tools (e.g. the ewn profile part shells out to
# dcli). `chezmoi ignored` prints home-relative targets; prefix the home dir to
# match `chezmoi target-path` below.
#
# --override-data sets lintSkipExternals so .chezmoiexternal renders empty:
# `ignored` builds full source state, which would otherwise realize every
# external (download archives, SSH-clone repos) just to list ignored targets —
# network + credentials this offline check must not need, and which fail on a
# fresh checkout (CI) and in the android container. Externals don't affect which
# templates are ignored. Only `ignored` realizes them, so the override is scoped
# here — every template (including .chezmoiexternal itself) still renders in full.
home_dir=$(chezmoi execute-template --config "$chezmoi_config" '{{ .chezmoi.homeDir }}')
ignored=$(chezmoi ignored --config "$chezmoi_config" --source "$src_root" |
ignored=$(chezmoi ignored --override-data '{"lintSkipExternals":true}' \
--config "$chezmoi_config" --source "$src_root" |
sed "s|^|$home_dir/|")

# Resolve each source to its target in one call; output order matches the args,
Expand Down
17 changes: 17 additions & 0 deletions scripts/render-templates-termux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
#
# Container-side half of the test:templates-android task: provision chezmoi on a
# fresh termux/termux-docker, then hand off to the shared render engine
# (mise-tasks/test/templates), so the android leg renders through exactly the
# same path as every other platform. Runs INSIDE the container (or on a real
# Termux device, where chezmoi already exists and the install is a no-op). The
# render is git-free — the read-only bind mount's .git may be an unmounted host
# path — so only chezmoi is installed.
set -euo pipefail

if ! command -v chezmoi >/dev/null; then
pkg update -y >/dev/null # seed the apt mirror on a fresh image
pkg install -y chezmoi >/dev/null
fi

exec bash "$(cd "$(dirname "$0")/.." && pwd)/mise-tasks/test/templates"
Loading