Skip to content

template-sync-wf

template-sync-wf #5

name: template-sync-wf
on:
schedule:
- cron: "0 7 * * 1" # Mondays 07:00 UTC
workflow_dispatch: {}
permissions:
contents: write
pull-requests: write
concurrency:
group: template-sync
cancel-in-progress: false
# Repo-level ENV defaults; override in the GENERATED repo via Actions → Variables
env:
# The official template repo — hardcoded, since this is the source of truth
TEMPLATE_REPO: deresegetachew/systemcraft-stack-npmlib
TEMPLATE_REF: main
# Turn the sync on/off in generated repos:
TEMPLATE_SYNC_ENABLED: ${{ vars.TEMPLATE_SYNC_ENABLED || 'true' }}
# Exclusions — space-separated paths/patterns relative to template root.
# By default we DO NOT sync: packages, .changeset/.changesets, LICENSE/Licenses, node_modules, .gitattributes.
TEMPLATE_EXCLUDE: ${{ vars.TEMPLATE_EXCLUDE || 'packages pnpm-lock.yml pnpm-lock.yaml .changeset .changesets LICENSE LICENSES node_modules .gitattributes README.md' }}
# Branch naming
TARGET_BRANCH: ${{ vars.TARGET_BRANCH || 'main' }}
SYNC_BRANCH: ${{ vars.SYNC_BRANCH || 'chore/sync-template' }}
# Optional label(s) — when set via repo Variables this can be a comma-separated string
TEMPLATE_SYNC_LABEL: ${{ vars.TEMPLATE_SYNC_LABEL || 'template-sync,[skip changeset check]' }}
jobs:
sync:
name: Sync from template systemcraft/play-npmlib (exclude packages, licenses, changesets)
# IMPORTANT: do NOT run on the template repo itself
if: ${{ github.repository != 'deresegetachew/systemcraft-stack-npmlib' }}
runs-on: ubuntu-latest
steps:
- name: Check if enabled
if: ${{ env.TEMPLATE_SYNC_ENABLED != 'true' }}
run: |
echo "Template sync disabled (TEMPLATE_SYNC_ENABLED != 'true'). Exiting."
exit 0
- uses: actions/checkout@v5
with:
fetch-depth: 0
ref: ${{ env.TARGET_BRANCH }}
- name: Fetch template repository
run: |
set -euo pipefail
git clone --depth=1 --no-checkout "https://github.com/${{ env.TEMPLATE_REPO }}.git" /tmp/template
cd /tmp/template
git checkout "${{ env.TEMPLATE_REF }}"
# rsync rsync stands for remote synchronization — it’s a file-copying and syncing utility that can efficiently mirror one directory tree into another,
# only copying changed files and deleting ones that no longer exist on the source (if you tell it to).
- name: Build rsync exclusion flags
id: excludes
shell: bash
run: |
set -euo pipefail
flags=""
for p in $TEMPLATE_EXCLUDE; do
flags="$flags --exclude=$p"
done
# Never try to copy .git
flags="$flags --exclude=.git"
echo "flags=$flags" >> "$GITHUB_OUTPUT"
- uses: ./.github/actions/setup-ci-git-identity
with:
purpose: 'GPG-signed template sync commits'
git-user-name: ${{ vars.CI_GPG_USER_NAME }}
git-user-email: ${{ vars.CI_GPG_USER_EMAIL }}
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
- name: Copy template content (exclude configured paths)
run: |
set -euo pipefail
rsync -a --delete ${{ steps.excludes.outputs.flags }} /tmp/template/ ./
# Stage changes if any; leave them staged for create-pull-request to commit
git add -A
if git diff --staged --quiet; then
echo "✅ Everything is already up to date with the template!"
echo "ℹ️ No changes detected, so no PR was created."
echo "If you expected changes, ensure the template repo and ref are correct:"
echo " - Template: $TEMPLATE_REPO"
echo " - Branch: $TEMPLATE_REF"
echo ""
echo "To force a sync, you can trigger this workflow manually from the Actions tab."
exit 0
fi
- name: Open/Update PR
uses: peter-evans/create-pull-request@v7
with:
title: "chore: sync from template ${{ env.TEMPLATE_REPO }}"
body: |
Automated sync from **${{ env.TEMPLATE_REPO }}** @ `${{ env.TEMPLATE_REF }}`
**Excluded paths:** `${{ env.TEMPLATE_EXCLUDE }}`
Turn off via **Actions → Variables**: set `TEMPLATE_SYNC_ENABLED=false`.
branch: ${{ env.SYNC_BRANCH }}
base: ${{ env.TARGET_BRANCH }}
labels: ${{ env.TEMPLATE_SYNC_LABEL }}
delete-branch: false
commit-message: "chore: sync from ${{ env.TEMPLATE_REPO }}@${{ env.TEMPLATE_REF }} (excluding: ${{ env.TEMPLATE_EXCLUDE }})"
sign-commits: true
committer: "${{ vars.CI_GPG_USER_NAME }} <${{ vars.CI_GPG_USER_EMAIL }}>"
author: "${{ vars.CI_GPG_USER_NAME }} <${{ vars.CI_GPG_USER_EMAIL }}>"
token: ${{ secrets.BOT_TOKEN }}