diff --git a/.github/workflows/template-sync.yaml b/.github/workflows/template-sync.yaml new file mode 100644 index 0000000..6bd093f --- /dev/null +++ b/.github/workflows/template-sync.yaml @@ -0,0 +1,33 @@ +name: 🔄 Template Sync + +on: + schedule: + - cron: "0 6 * * 1" # weekly, Monday 06:00 UTC + workflow_dispatch: + +permissions: {} + +jobs: + template-sync: + # Skipped in the template repo itself (it is the sync source); runs in every + # instance created from this template, where it opens a PR with template + # changes. Outside devantler-tech the org-provided APP_PRIVATE_KEY secret is + # absent and the scheduled run would only fail — so personal-account + # instances are off by default and opt in by adding the secret plus a + # repository variable TEMPLATE_SYNC_ENABLED=true. + if: >- + github.repository != 'devantler-tech/go-template' && + (github.repository_owner == 'devantler-tech' || vars.TEMPLATE_SYNC_ENABLED == 'true') + # The reusable workflow's job opens the sync PR, so the caller must grant it + # write to contents + pull-requests. A reusable workflow cannot be granted + # more than the caller, and the top-level `permissions: {}` grants none, so + # without this the run fails at startup (reusable-workflow permission check) + # — both here and in every instance where the job actually runs. + permissions: + contents: write + pull-requests: write + uses: devantler-tech/actions/.github/workflows/template-sync.yaml@0f27c13a9785608e0bbf9998777d27761b4de617 # v10.0.2 + with: + source-repo-path: devantler-tech/go-template + secrets: + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.templatesyncignore b/.templatesyncignore new file mode 100644 index 0000000..3d3f3e1 --- /dev/null +++ b/.templatesyncignore @@ -0,0 +1,62 @@ +# .templatesyncignore — files an INSTANCE created from this template OWNS. +# +# Same syntax as .gitignore. The weekly template-sync workflow +# (devantler-tech/actions .github/workflows/template-sync.yaml, using +# AndreasAugustin/actions-template-sync) is skipped in this template repo itself +# and runs in every instance created from it — opening a PR that brings template +# changes downstream. It only ever touches files that exist in this template, and +# it SKIPS anything matched here, so an instance's tailored files are never +# clobbered by an upstream sync. +# +# Everything NOT listed here is TEMPLATE-OWNED shared plumbing kept current in +# every instance by template-sync — change it upstream in +# devantler-tech/go-template, never by editing it in an instance. Template-owned +# plumbing includes .github/workflows/{ci,cd,release,template-sync,todos}.yaml, +# the shared lint configs (.mega-linter.yml, .pre-commit-config.yaml, +# .editorconfig, .gitattributes — NOT .golangci.yml, which instances own; see +# below), the mockery pre-commit hook scripts, and the +# CLAUDE.md/GEMINI.md shims. (ci.yaml is deliberately template-owned here — +# unlike the stack-neutral gitops-tenant-template, every Go instance shares the +# same required-checks aggregation, so CI fixes must propagate.) + +# --- Identity & docs (instance-specific) --- +AGENTS.md +README.md +LICENSE +.github/CODEOWNERS +.claude/skills/maintain/SKILL.md + +# --- Release / dependency / lint config the instance tailors --- +# .golangci.yml is instance-owned on purpose: its depguard rule is a strict +# allowlist (stdlib + OpenFeature), so any real project must extend it — a +# synced overwrite would clobber the project's dependency allowlist. +.releaserc +.gitignore +.github/dependabot.yaml +.golangci.yml +cspell.json + +# --- The instance's own Go module and code --- +# The module path, entrypoint, and packages are repointed by +# scripts/rename-placeholders.sh at scaffold time and evolve with the instance; +# a sync must never clobber them. pkg/ is ignored WHOLESALE (not just the +# starter packages) so a template file added later at a path the instance +# already uses can never touch instance code. +go.mod +go.sum +main.go +cmd/ +internal/ +pkg/ + +# --- Scaffolding machinery (template-only; dead code in a live instance) --- +# These run when a NEW repo is created from this template ("use this template" +# copies everything, so scaffolding still arrives at creation time). A +# scaffolded instance never runs them again — ignoring them stops template-sync +# (re-)introducing them downstream, so an instance that deletes them stays clean. +scripts/rename-placeholders.sh +scripts/rename-placeholders.test.sh +.github/workflows/validate-scaffold.yaml + +# --- This ignore-list itself (the instance curates its own ownership) --- +.templatesyncignore diff --git a/AGENTS.md b/AGENTS.md index 2eac06f..3e67650 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,7 +21,7 @@ need. - `AGENTS.md` — the canonical, cross-tool project instructions. - `CLAUDE.md` / `GEMINI.md` — exact one-line `@AGENTS.md` shims; never copy guidance into them. - `.golangci.yml` — golangci-lint v2 config (formatters + `default: all` linters, with a few opt-outs and mock-file exclusions). -- `.github/workflows/` — `ci.yaml` (required-checks aggregation on PRs/merge queue), `cd.yaml` (GoReleaser release on `v*` tags), `validate-scaffold.yaml` (template-repo-only gate that exercises the agent shims, onboarding script, and pre-commit mockery hook — no-ops downstream), `release.yaml`, `todos.yaml`, and `copilot-setup-steps.yml`. +- `.github/workflows/` — `ci.yaml` (required-checks aggregation on PRs/merge queue), `cd.yaml` (GoReleaser release on `v*` tags), `validate-scaffold.yaml` (template-repo-only gate that exercises the agent shims, onboarding script, and pre-commit mockery hook — no-ops downstream), `template-sync.yaml` (skipped in this repo; in instances it opens a weekly PR syncing template-owned plumbing, honouring `.templatesyncignore` — see the README's *Staying current* ownership classes), `release.yaml`, `todos.yaml`, and `copilot-setup-steps.yml`. - `.pre-commit-config.yaml` — local pre-commit hooks: `golangci-lint-fmt` (Go formatting) and mock generation (`mockery`, via `.github/scripts/run-mockery.sh`). - `.github/scripts/run-mockery.sh` — the pre-commit mockery hook's entry point; a guarded no-op until the project adds a `.mockery.yml`/`.mockery.yaml`, then runs `mockery` (so a fresh clone's hook stays green while the generation step is already wired). - `.github/scripts/run-mockery.test.sh` — hermetic test for the mockery hook: runs `run-mockery.sh` under a stripped PATH and asserts the three branches (silent no-op without a config, exit 1 + install hint when mockery is absent, exec when present). Run with `sh .github/scripts/run-mockery.test.sh`; CI runs it via `validate-scaffold.yaml`. diff --git a/README.md b/README.md index cd7f814..d719635 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,34 @@ script leaves the upstream **Use this template** links above untouched, runs `go mod tidy`, and you can review the result with `git diff`. (Prefer to do it by hand? `go mod edit -module github.com//my-project && go mod tidy`.) +## 🔄 Staying current + +A weekly **template-sync** workflow opens a PR in your repository whenever this +template's shared plumbing changes, so instances never drift from the +portfolio's CI/lint/agent-file conventions. It never touches your code: every +file falls into one of three ownership classes: + +- **Template-owned plumbing** — synced downstream by the weekly PR: the + `.github/workflows/` CI/CD/release workflows, the shared lint configs + (`.mega-linter.yml`, `.pre-commit-config.yaml`, `.editorconfig`, + `.gitattributes`), and the `CLAUDE.md`/`GEMINI.md` shims. + Change these upstream in the template, never by hand in an instance. +- **Instance-owned** — listed in [`.templatesyncignore`](.templatesyncignore), + never touched by a sync: your Go module and code (`go.mod`, `go.sum`, + `main.go`, `cmd/`, `internal/`, all of `pkg/`), identity and docs + (`README.md`, `AGENTS.md`, `LICENSE`, `CODEOWNERS`), and the configs you + tailor (`.releaserc`, `.gitignore`, `dependabot.yaml`, `.golangci.yml` — + its depguard allowlist grows with your dependencies — and `cspell.json`). +- **Scaffold-time-only** — the rename script and the template's own + `validate-scaffold.yaml` gate arrive when the repo is created and are ignored + by sync afterwards, so you can delete them and they stay gone. + +The sync workflow no-ops in this template repository itself. In devantler-tech +instances it works out of the box (the org provides the `APP_PRIVATE_KEY` +secret); an instance elsewhere is off by default — opt in by adding an +`APP_PRIVATE_KEY` secret (a GitHub App key that can open PRs) and setting the +repository variable `TEMPLATE_SYNC_ENABLED=true`. + ## 📝 Usage ### Add a dependency