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
33 changes: 33 additions & 0 deletions .github/workflows/template-sync.yaml
Original file line number Diff line number Diff line change
@@ -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 }}
62 changes: 62 additions & 0 deletions .templatesyncignore
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<you>/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
Expand Down
Loading