Skip to content

Repo settings as code: rulesets, settings JSON and setup-repo.py #6

Repo settings as code: rulesets, settings JSON and setup-repo.py

Repo settings as code: rulesets, settings JSON and setup-repo.py #6

Workflow file for this run

name: Template CI
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
concurrency:
group: template-ci-${{ github.ref }}
cancel-in-progress: true
jobs:
render:
name: Render and validate both variants
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: "3.12"
- name: Render both variants and check them
# --vcs-ref=HEAD on every copier call is load-bearing. Given a local git
# repo, copier renders the latest *tag* by default, not the checkout --
# so without this the whole job silently validates the released template
# instead of the branch under review, and every assertion below passes
# while proving nothing about the change. It only works today because
# actions/checkout fetches no tags, which is an accident rather than a
# decision, and it makes local runs disagree with CI on a machine that
# does have the tags.
run: |
set -euo pipefail
for kind in app library; do
uvx copier copy --trust --defaults --vcs-ref=HEAD \
--data project_name="sample-$kind" \
--data project_description="A sample $kind" \
--data project_kind="$kind" \
. "/tmp/out-$kind"
python3 -c "import tomllib; tomllib.load(open('/tmp/out-$kind/pyproject.toml','rb'))"
test -f "/tmp/out-$kind/.pre-commit-config.yaml"
test -f "/tmp/out-$kind/.github/workflows/ci.yml"
test -f "/tmp/out-$kind/.claude/hooks/session-start.sh"
# The hook is templated, so it can render into invalid shell, and it
# is useless if copier drops the executable bit.
test -x "/tmp/out-$kind/.claude/hooks/session-start.sh"
bash -n "/tmp/out-$kind/.claude/hooks/session-start.sh"
# shellcheck-py builds from source and its build can't verify the
# MITM CA some sandboxes present once Python 3.13 turns on strict
# certificate checking, which breaks `git commit` outright there. The
# pin below the default is what avoids it, so losing the pin while
# raising default_language_version is the regression to catch.
python3 -c "import sys,yaml; cfg=yaml.safe_load(open(sys.argv[1])); hs=[h for r in cfg['repos'] if 'shellcheck-py' in r['repo'] for h in r['hooks'] if h['id']=='shellcheck']; assert len(hs)==1, hs; v=hs[0].get('language_version'); assert v=='python3.12', f'shellcheck language_version is {v!r}'" "/tmp/out-$kind/.pre-commit-config.yaml"
done
# Repo settings as code is opt-in: the defaults carry none of it, so a
# `copier update --defaults` cannot push it into downstream repos.
for kind in app library; do
test ! -e "/tmp/out-$kind/scripts"
test ! -e "/tmp/out-$kind/.github/rulesets"
test ! -e "/tmp/out-$kind/.github/repo-settings.json"
done
# The applier is tested against a stubbed gh, here in the template repo
# (the file's path has copier syntax in it, so the test loads it by path).
uvx --with pytest --from pytest pytest -q tests/test_setup_repo.py
uvx copier copy --trust --defaults --vcs-ref=HEAD \
--data project_name="sample-repo-settings" \
--data project_description="A sample managing its repo settings" \
--data use_repo_settings=true \
. /tmp/out-repo-settings
# Apply script plus JSON payloads render intact, and the script keeps
# its executable bit.
test -x /tmp/out-repo-settings/scripts/setup_repo.py
python3 -m py_compile /tmp/out-repo-settings/scripts/setup_repo.py
/tmp/out-repo-settings/scripts/setup_repo.py --help > /dev/null
python3 -c "import json,sys; json.load(open(sys.argv[1]))" /tmp/out-repo-settings/.github/rulesets/main.json
python3 -c "import json,sys; json.load(open(sys.argv[1]))" /tmp/out-repo-settings/.github/repo-settings.json
grep -q 'ci / Lint, type-check, and test' /tmp/out-repo-settings/.github/rulesets/main.json
! grep -q 'frontend /' /tmp/out-repo-settings/.github/rulesets/main.json
echo "repo settings opt-in honoured."
# library-only files present for library, absent for app
test -f /tmp/out-library/RELEASING.md
test -f /tmp/out-library/.github/workflows/publish.yml
test ! -e /tmp/out-app/RELEASING.md
test ! -e /tmp/out-app/.github/workflows/publish.yml
echo "Both variants rendered and validated."
# use_typos defaults on, and answering no drops the hook
uvx copier copy --trust --defaults --vcs-ref=HEAD \
--data project_name="sample-no-typos" \
--data project_description="A sample without the typos hook" \
--data use_typos=false \
. /tmp/out-no-typos
grep -q "crate-ci/typos" /tmp/out-app/.pre-commit-config.yaml
! grep -q "crate-ci/typos" /tmp/out-no-typos/.pre-commit-config.yaml
python3 -c "import yaml; yaml.safe_load(open('/tmp/out-no-typos/.pre-commit-config.yaml'))"
echo "typos opt-out honoured."
# use_frontend adds biome.json, the Biome hook, and the node-ci job
uvx copier copy --trust --defaults --vcs-ref=HEAD \
--data project_name="sample-frontend" \
--data project_description="A sample with a frontend" \
--data use_frontend=true \
--data use_repo_settings=true \
. /tmp/out-frontend
test -f /tmp/out-frontend/biome.json
grep -q "biomejs/pre-commit" /tmp/out-frontend/.pre-commit-config.yaml
grep -q "node-ci.yml" /tmp/out-frontend/.github/workflows/ci.yml
python3 -c "import json; json.load(open('/tmp/out-frontend/biome.json'))"
python3 -c "import yaml; yaml.safe_load(open('/tmp/out-frontend/.github/workflows/ci.yml'))"
# and its ruleset requires the frontend job too
python3 -c "import json,sys; json.load(open(sys.argv[1]))" /tmp/out-frontend/.github/rulesets/main.json
grep -q 'frontend / Type-check and build' /tmp/out-frontend/.github/rulesets/main.json
# `recommended` is deprecated in Biome 2.5.5; `preset` is the spelling
# that doesn't emit a notice.
grep -q '"preset": "recommended"' /tmp/out-frontend/biome.json
! grep -q '"recommended": true' /tmp/out-frontend/biome.json
# check-json can't parse the comments tsconfig permits, so a frontend
# scaffold excludes it -- and only a frontend scaffold has one.
grep -q 'exclude: \^frontend/tsconfig' /tmp/out-frontend/.pre-commit-config.yaml
! grep -q 'tsconfig' /tmp/out-app/.pre-commit-config.yaml
# The session hook points corepack at the npm registry, because
# repo.yarnpkg.com is blocked by some sandbox egress proxies. Only
# useful to a frontend scaffold, so only a frontend scaffold gets it.
test -x /tmp/out-frontend/.claude/hooks/session-start.sh
bash -n /tmp/out-frontend/.claude/hooks/session-start.sh
grep -q 'COREPACK_NPM_REGISTRY' /tmp/out-frontend/.claude/hooks/session-start.sh
! grep -q 'COREPACK_NPM_REGISTRY' /tmp/out-app/.claude/hooks/session-start.sh
# and adds none of it by default
test ! -e /tmp/out-app/biome.json
! grep -q "biomejs/pre-commit" /tmp/out-app/.pre-commit-config.yaml
! grep -q "node-ci.yml" /tmp/out-app/.github/workflows/ci.yml
echo "frontend variant rendered and validated."
# the template-update workflow defaults on, with its baseline
test -f /tmp/out-app/.github/workflows/template-update.yml
test -f /tmp/out-app/.copier-answers.yml
python3 -c "import yaml; yaml.safe_load(open('/tmp/out-app/.github/workflows/template-update.yml'))"
# answering no drops the workflow but KEEPS the answers file, so
# `copier update` by hand still works
uvx copier copy --trust --defaults --vcs-ref=HEAD \
--data project_name="sample-no-auto-update" \
--data project_description="A sample without the update workflow" \
--data use_template_update=false \
. /tmp/out-no-auto-update
test ! -e /tmp/out-no-auto-update/.github/workflows/template-update.yml
test -f /tmp/out-no-auto-update/.copier-answers.yml
echo "template-update workflow present by default, opt-out honoured."
- name: Run each rendered project's own hooks
# The actual dogfooding, and the only check here that exercises the
# generated config rather than just asserting files exist. Rendering and
# grepping proves biome.json is present and parses; it cannot tell you
# that Biome accepts what it says.
#
# This was added after a fresh frontend scaffold turned out to fail its
# own stack on the first run: the shipped `includes` used a glob Biome
# normalises, so biome-check rewrote biome.json and every new project
# started red with a diff in a file nobody had touched.
#
# pre-commit enumerates through `git ls-files`, so the render has to be
# a git repo with everything staged or every hook silently skips.
run: |
set -euo pipefail
for out in /tmp/out-app /tmp/out-frontend; do
echo "::group::pre-commit in $out"
git -C "$out" init -q
git -C "$out" add -A
( cd "$out" && uvx pre-commit run --all-files --show-diff-on-failure )
echo "::endgroup::"
done
# The reusable workflows are `workflow_call`-only, so nothing in this repo
# ever executed them: a change to either shipped straight to every consumer
# on the next `v1` move, untested. Both bugs that surfaced while adopting the
# template in a real repo were of exactly that kind -- `corepack enable`
# ordered after setup-node, and a `run:` line that wasn't valid YAML.
#
# These call the workflows for real against fixtures under tests/smoke/. The
# fixtures are deliberately minimal and are NOT a mirror of the scaffold --
# template-ci already checks the scaffold's content. Their job is to prove the
# workflows themselves run end to end.
smoke-python-ci:
name: Smoke-test python-ci.yml
uses: ./.github/workflows/python-ci.yml
with:
working-directory: tests/smoke/python
typecheck-paths: src
smoke-node-ci:
name: Smoke-test node-ci.yml
uses: ./.github/workflows/node-ci.yml
with:
working-directory: tests/smoke/node