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
6 changes: 5 additions & 1 deletion .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,8 @@ jobs:
global-json-file: global.json

- name: πŸ“¦ Restore dependencies
run: dotnet restore Example.slnx
# Bare `dotnet restore` resolves the repo root's single solution file,
# so this workflow stays template-owned and placeholder-independent: it
# keeps working after an instance renames Example.slnx, and template
# syncs can keep propagating hardening/version bumps to it.
run: dotnet restore
36 changes: 36 additions & 0 deletions .github/workflows/template-sync.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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 credentials are
# absent and the scheduled run would only fail β€” so personal-account
# instances are off by default and opt in by supplying their OWN GitHub App
# (one that can open PRs): the APP_PRIVATE_KEY secret plus the APP_CLIENT_ID
# repository variable (the reusable workflow mints its token from that
# variable/secret pair), and setting the repository variable
# TEMPLATE_SYNC_ENABLED=true.
if: >-
github.repository != 'devantler-tech/dotnet-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/dotnet-template
secrets:
APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }}
Comment thread
devantler marked this conversation as resolved.
66 changes: 66 additions & 0 deletions .templatesyncignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# .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/dotnet-template, never by editing it in an instance.
# Template-owned plumbing includes the
# .github/workflows/{ci,publish,release,template-sync,todos,copilot-setup-steps}
# workflows, the shared configs (.editorconfig, .gitattributes,
# .pre-commit-config.yaml), and the CLAUDE.md/GEMINI.md shims. (ci.yaml is
# deliberately template-owned for devantler-tech instances β€” they all share the
# same ruleset-injected build/test workflows and this required-checks
# aggregation, so CI fixes must propagate. An instance OUTSIDE devantler-tech
# does not inherit those org rulesets and must replace ci.yaml with real
# build/test jobs β€” after doing so, add `.github/workflows/ci.yaml` to this
# file, which the instance owns, so a later sync cannot revert it.)

# --- Identity & docs (instance-specific) ---
AGENTS.md
README.md
LICENSE
.github/CODEOWNERS
.claude/skills/maintain/SKILL.md

# --- Release / dependency config the instance tailors ---
.releaserc
.gitignore
.github/dependabot.yaml

# --- Toolchain floor (instance-owned so SDK and TFMs move in lockstep) ---
# AGENTS.md's toolchain-floor policy requires global.json's SDK floor and every
# <TargetFramework> to move together. The project trees below are ignored, so a
# synced global.json bump would move the SDK floor WITHOUT its matching TFMs β€”
# a lockstep violation delivered downstream. The instance owns the whole
# toolchain upgrade instead (the .NET parity to go-template, where the
# instance-owned go.mod carries the Go toolchain floor).
global.json

# --- The instance's own solution and code ---
# The solution, projects, and code are renamed from Example.* by
# scripts/rename-placeholders.sh at scaffold time and evolve with the instance;
# a sync must never clobber them β€” nor re-introduce the template's Example.slnx
# after the rename. src/ and tests/ are ignored WHOLESALE so a template file
# added later at a path the instance already uses can never touch instance code.
Example.slnx
src/
tests/
Comment thread
devantler marked this conversation as resolved.

# --- 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 @@ -12,7 +12,7 @@
- `AGENTS.md` β€” the canonical, cross-tool project instructions.
- `CLAUDE.md` / `GEMINI.md` β€” exact one-line `@AGENTS.md` shims; never copy guidance into them.
- `.editorconfig` β€” formatting and analyzer rules enforced at build.
- `.github/workflows/` β€” `ci.yaml` (required-checks aggregation on PRs/merge queue), `validate-scaffold.yaml` (template-repo-only gate that exercises the agent shims and scaffold-rename script β€” no-ops downstream), `publish.yaml` (publishes the NuGet library on `v*` tags via the reusable `publish-dotnet-library` workflow), `release.yaml`, `sync-labels.yaml`, `todos.yaml`, and `copilot-setup-steps.yml`.
- `.github/workflows/` β€” `ci.yaml` (required-checks aggregation on PRs/merge queue), `validate-scaffold.yaml` (template-repo-only gate that exercises the agent shims and scaffold-rename script β€” no-ops downstream), `publish.yaml` (publishes the NuGet library on `v*` tags via the reusable `publish-dotnet-library` workflow), `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`, `sync-labels.yaml`, `todos.yaml`, and `copilot-setup-steps.yml`.
- `.pre-commit-config.yaml` β€” a [pre-commit](https://pre-commit.com) config with a local `dotnet-format` hook that runs `dotnet format` on staged C# changes (opt in with `pre-commit install`); `.releaserc` β€” semantic-release configuration.
- `scripts/validate-agent-shims.test.sh` β€” hermetic structural check that both tool-specific shims contain exactly `@AGENTS.md` plus one newline. Run with `sh scripts/validate-agent-shims.test.sh`; CI runs it via `validate-scaffold.yaml`.

Expand Down
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,39 @@ dotnet test

Run the script with no argument to derive a PascalCase name from your `origin` GitHub remote. It leaves the **Use this template** links above and the maintenance docs untouched; review the result with `git diff`. (Prefer to rename by hand? Repoint `Example` across the `.slnx`, `src/`, `tests/`, and README references yourself.)

## πŸ”„ 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/publish/release workflows, the shared configs
(`.editorconfig`, `.gitattributes`, `.pre-commit-config.yaml`), 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 solution and code (the `.slnx`, all of `src/`
and `tests/`), the toolchain floor (`global.json` β€” the SDK floor and every
`<TargetFramework>` must move in lockstep, so the whole upgrade is yours),
identity and docs (`README.md`, `AGENTS.md`, `LICENSE`, `CODEOWNERS`), and
the configs you tailor (`.releaserc`, `.gitignore`, `dependabot.yaml`).
- **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 credentials); an
instance elsewhere is off by default β€” opt in by supplying your own GitHub App
(one allowed to open PRs in your repository): add its private key as the
`APP_PRIVATE_KEY` secret, its client ID as the `APP_CLIENT_ID` repository
variable, and set the repository variable `TEMPLATE_SYNC_ENABLED=true`. Note
that outside devantler-tech the synced `ci.yaml` is only an empty required-check
aggregator (the real build/test workflows are injected by devantler-tech org
rulesets): replace it with your own CI and add `.github/workflows/ci.yaml` to
your `.templatesyncignore` so later syncs preserve your version.

## πŸ“ Usage

### Add a project to the solution
Expand Down
Loading