-
Notifications
You must be signed in to change notification settings - Fork 0
154 lines (139 loc) · 6.24 KB
/
Copy pathsync.yml
File metadata and controls
154 lines (139 loc) · 6.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
name: sync from source repo
on:
workflow_dispatch:
inputs:
source_repo:
description: "Source repo URL (https://github.com/livetemplate/<name>)"
required: true
type: choice
options:
- https://github.com/livetemplate/livetemplate
- https://github.com/livetemplate/client
- https://github.com/livetemplate/lvt
ref:
description: "Git ref (tag or branch) to sync from"
required: true
default: main
# Source repos send this from their release workflows. Payload shape:
# {"source_repo": "https://github.com/livetemplate/livetemplate",
# "ref": "v0.8.24"}
repository_dispatch:
types: [sync-source]
permissions:
contents: write
pull-requests: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
# repository_dispatch client_payload is attacker-controllable by anyone
# who can dispatch to this repo, so it never reaches a run: block via
# ${{ }} interpolation — that would be shell injection into a job holding
# contents: write, pull-requests: write and DOCS_BOT_TOKEN. Values arrive
# as environment variables and are quoted at every use.
- name: Determine source_repo + ref
id: inputs
env:
EVENT_NAME: ${{ github.event_name }}
DISPATCH_REPO: ${{ github.event.client_payload.source_repo }}
DISPATCH_REF: ${{ github.event.client_payload.ref }}
INPUT_REPO: ${{ github.event.inputs.source_repo }}
INPUT_REF: ${{ github.event.inputs.ref }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "repository_dispatch" ]; then
source_repo="$DISPATCH_REPO"
ref="$DISPATCH_REF"
else
source_repo="$INPUT_REPO"
ref="$INPUT_REF"
fi
# workflow_dispatch restricts source_repo to a choice list;
# repository_dispatch has no such gate. Without this allowlist a
# dispatch could aim the sync at an arbitrary repo and open a PR
# pulling that repo's markdown into this site.
case "$source_repo" in
https://github.com/livetemplate/livetemplate|\
https://github.com/livetemplate/client|\
https://github.com/livetemplate/lvt) ;;
*)
echo "::error::refusing to sync from unrecognised source_repo: $source_repo"
exit 1
;;
esac
# Refs become branch names and shell arguments; keep them to the
# characters a tag or branch actually needs.
if ! printf '%s' "$ref" | grep -Eq '^[A-Za-z0-9._/-]+$'; then
echo "::error::refusing to sync from unsafe ref: $ref"
exit 1
fi
echo "source_repo=$source_repo" >> "$GITHUB_OUTPUT"
echo "ref=$ref" >> "$GITHUB_OUTPUT"
- name: Compute slug for branch / PR title
id: slug
env:
SOURCE_REPO: ${{ steps.inputs.outputs.source_repo }}
run: |
set -euo pipefail
echo "slug=${SOURCE_REPO##*/}" >> "$GITHUB_OUTPUT"
- name: Build sync tool
working-directory: cmd/sync
run: go build -o /usr/local/bin/sync .
- name: Run sync
env:
SOURCE_REPO: ${{ steps.inputs.outputs.source_repo }}
SOURCE_REF: ${{ steps.inputs.outputs.ref }}
run: |
set -euo pipefail
sync \
--source-repo="$SOURCE_REPO" \
--ref="$SOURCE_REF" \
--site-root="${GITHUB_WORKSPACE}" \
| tee /tmp/sync-output.log
- name: Check for changes
id: changes
env:
SOURCE_REPO: ${{ steps.inputs.outputs.source_repo }}
SOURCE_REF: ${{ steps.inputs.outputs.ref }}
run: |
if [ -n "$(git status --porcelain content/)" ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "::notice::no content changes from $SOURCE_REPO@$SOURCE_REF"
fi
- name: Open sync PR
if: steps.changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
# Prefer a fine-scoped PAT (DOCS_BOT_TOKEN) so the PR can be
# created in orgs whose policy blocks GITHUB_TOKEN from doing
# so. If no PAT is configured the action will still push the
# branch via GITHUB_TOKEN, then fail at the PR-create step —
# the operator can `gh pr create --head sync/...` manually
# until the PAT is set up. See CONTRIBUTING.md → Sync workflow.
token: ${{ secrets.DOCS_BOT_TOKEN || secrets.GITHUB_TOKEN }}
commit-message: "Sync from ${{ steps.slug.outputs.slug }}@${{ steps.inputs.outputs.ref }}"
# One rolling branch per source repo — deliberately NOT suffixed with
# the ref. create-pull-request is idempotent per branch: when the
# branch already has an open PR it updates that PR in place instead of
# opening another. Suffixing with the ref made every source release
# mint a fresh branch, so unmerged syncs accumulated (nine were open
# at once in July 2026, leaving the site on v0.16.0 reference docs
# while core shipped v0.19.0).
#
# Safe because the page sets are disjoint: no site_url in
# source-of-truth.yaml is claimed by two source repos, so
# sync/livetemplate, sync/lvt and sync/client never touch the same
# file and cannot conflict with each other.
branch: sync/${{ steps.slug.outputs.slug }}
title: "Sync from ${{ steps.slug.outputs.slug }}@${{ steps.inputs.outputs.ref }}"
body: |
Automated sync from `${{ steps.inputs.outputs.source_repo }}` at ref `${{ steps.inputs.outputs.ref }}`.
See [content/_meta/source-of-truth.yaml](../blob/main/content/_meta/source-of-truth.yaml) for the page mapping that drives this sync. Bring concerns about a specific change to the source repo, not this PR — edits made directly here will be overwritten on the next sync.
delete-branch: true
add-paths: content/