From 0bdd45378f28112d7a530e241bf9db686aa3c337 Mon Sep 17 00:00:00 2001 From: david morales <118213696+davidmoralesz@users.noreply.github.com> Date: Fri, 9 Oct 2026 18:25:49 -0600 Subject: [PATCH] chore(devtools): sync with project-template v0.4.1 --- .devtools/scripts/template-sync.sh | 66 +++++++++++++++++++++--------- .template-version | 2 +- AGENTS.md | 2 +- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/.devtools/scripts/template-sync.sh b/.devtools/scripts/template-sync.sh index 7fb428a..10f7bad 100755 --- a/.devtools/scripts/template-sync.sh +++ b/.devtools/scripts/template-sync.sh @@ -13,6 +13,11 @@ # blocks inside files the project otherwise owns. Paths listed in this repo's # .template-ignore are left alone. # +# A project can also opt in to profiles, one per line in its .template-profiles. A +# profile is a second manifest, .template/manifest., whose files and blocks are +# read from .template/profiles// instead of the template's root, so a language's +# files do not sit in every new project. +# # Exit codes: 0 in step (or synced), 1 drift found by --check, 2 anything else. # The template-drift workflow depends on that split, so a failure of this script is # never mistaken for "no drift". @@ -39,7 +44,7 @@ while [[ $# -gt 0 ]]; do --check) mode=check; shift ;; --from) from="${2:?--from needs a directory}"; shift 2 ;; --ref) ref="${2:?--ref needs a tag}"; shift 2 ;; - -h | --help) sed -n '2,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;; + -h | --help) sed -n '2,25p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;; *) die "unknown argument: $1" ;; esac done @@ -63,8 +68,7 @@ if [[ -z "${from}" ]]; then || die "cannot clone ${url} at ${ref}" fi -manifest="${from}/.template/manifest" -[[ -f "${manifest}" ]] || die "${from} has no .template/manifest; is it the template?" +[[ -f "${from}/.template/manifest" ]] || die "${from} has no .template/manifest; is it the template?" if [[ -z "${ref}" ]]; then ref="$(git -C "${from}" describe --tags --always 2>/dev/null || echo unknown)" fi @@ -107,8 +111,12 @@ report() { fi } +# Where the manifest being applied reads its files from: the template's root for the base +# manifest, a profile's directory for a profile. +src_root="${from}" + sync_file() { - local path="$1" src="${from}/$1" + local path="$1" src="${src_root}/$1" [[ -f "${src}" ]] || die "manifest lists ${path}, but the template does not have it" local same=true @@ -140,7 +148,7 @@ sync_file() { } sync_block() { - local path="$1" name="$2" src="${from}/$1" + local path="$1" name="$2" src="${src_root}/$1" [[ -f "${src}" ]] || die "manifest lists ${path}, but the template does not have it" block_of "${src}" "${name}" >"${work}/theirs" @@ -180,20 +188,40 @@ sync_block() { changed=$((changed + 1)) } -while read -r kind path name _; do - case "${kind}" in - '' | '#'*) continue ;; - esac - if ignored "${path}"; then - echo "– ${path}: ignored by .template-ignore" - continue - fi - case "${kind}" in - file) sync_file "${path}" ;; - block) sync_block "${path}" "${name:?manifest: block ${path} needs a name}" ;; - *) die "manifest: unknown entry '${kind} ${path}'" ;; - esac -done <"${manifest}" +# apply_manifest : sync, or check, every entry in it. +apply_manifest() { + local kind path name _ + while read -r kind path name _; do + case "${kind}" in + '' | '#'*) continue ;; + esac + if ignored "${path}"; then + echo "– ${path}: ignored by .template-ignore" + continue + fi + case "${kind}" in + file) sync_file "${path}" ;; + block) sync_block "${path}" "${name:?manifest: block ${path} needs a name}" ;; + *) die "manifest: unknown entry '${kind} ${path}'" ;; + esac + done <"$1" +} + +apply_manifest "${from}/.template/manifest" + +# Profiles come after the base manifest, in the order the project lists them. +if [[ -f .template-profiles ]]; then + while read -r profile _; do + case "${profile}" in + '' | '#'*) continue ;; + esac + [[ "${profile}" =~ ^[a-z0-9][a-z0-9-]*$ ]] || die ".template-profiles: '${profile}' is not a profile name" + [[ -f "${from}/.template/manifest.${profile}" ]] \ + || die ".template-profiles lists '${profile}', but the template has no .template/manifest.${profile}" + src_root="${from}/.template/profiles/${profile}" + apply_manifest "${from}/.template/manifest.${profile}" + done <.template-profiles +fi recorded="$(cat .template-version 2>/dev/null || true)" diff --git a/.template-version b/.template-version index 7becae1..5aff472 100644 --- a/.template-version +++ b/.template-version @@ -1 +1 @@ -v0.3.2 +v0.4.1 diff --git a/AGENTS.md b/AGENTS.md index 9b199e8..e6b9c2b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,7 +71,7 @@ weekly workflow reports as an issue. | CI behaviour (lint, PR checks, release) | `studiobimo/.github`, `.github/workflows/` | the `@v1` tag moving | | Branch and PR-size rules | `studiobimo/.github`, `.devtools/` | the `@v1` tag moving; lefthook refetches it daily | | Shared hooks and tool versions | `studiobimo/project-template` | `make -C .devtools sync` | -| Files and blocks listed in the template's `.template/manifest` | `studiobimo/project-template` | `make -C .devtools sync` | +| Files and blocks listed in the template's `.template/manifest`, and in the manifest of each profile in `.template-profiles` | `studiobimo/project-template` | `make -C .devtools sync` | A managed block sits between `>>> template:` and `<<< template:` marker lines, like this section. Edit outside the markers freely; inside them, change the template instead. If a