-
-
Notifications
You must be signed in to change notification settings - Fork 0
196 lines (177 loc) · 8.11 KB
/
Copy pathdata-sync.yml
File metadata and controls
196 lines (177 loc) · 8.11 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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
name: Data sync
# Two rhythms. Every day, the fast-moving measures (trends, teammates, rates by
# game duration) feed the long history: the API only goes back thirty days, and
# only a regular run keeps them beyond that. Every Monday, the full sync also
# rereads the wiki catalogue (heroes, skins, items, patches, combos), translates
# it, and checks that the site builds.
#
# The data is committed straight to main, production, with no human step: a
# daily pull request would never be reviewed, and two data branches would
# conflict on the same files.
#
# The push uses the SYNC_DEPLOY_KEY deploy key, the only one allowed to bypass
# main's protection. A push made with a key triggers workflows: Docker image
# deploys the site, Align develop carries the data over to develop. Without a
# key (unprotected main), the push goes through GITHUB_TOKEN, which triggers
# nothing: both workflows are then called explicitly.
on:
schedule:
# Every day except Monday, 3:30 UTC: measures only.
- cron: "30 3 * * 0,2-6"
# Monday, same time: full sync.
- cron: "30 3 * * 1"
workflow_dispatch:
inputs:
complete:
description: Full sync (wiki catalogue included)
type: boolean
default: false
permissions: {}
# One sync at a time: two runs would push the same files.
concurrency:
group: data-sync
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: write
timeout-minutes: 180
outputs:
commit: ${{ steps.commit.outputs.sha }}
with-key: ${{ steps.key.outputs.present }}
env:
COMPLETE: ${{ github.event.schedule == '30 3 * * 1' || inputs.complete == true }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Credentials kept: the commit step pushes to main.
with: # zizmor: ignore[artipacked]
ref: main
# When empty, checkout falls back to GITHUB_TOKEN.
ssh-key: ${{ secrets.SYNC_DEPLOY_KEY }}
- name: Push with the deploy key?
id: key
run: echo "present=${{ secrets.SYNC_DEPLOY_KEY != '' }}" >> "$GITHUB_OUTPUT"
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: npm
- run: npm ci --no-audit --no-fund
- name: Reread the sources and download the images
id: complete
if: env.COMPLETE == 'true'
# Wiki or API down: fall back to the measures alone, below.
continue-on-error: true
run: npm run sync -- --images
# Monthly lists and test-server notes: a failure keeps the previous data.
- name: Events calendar (StarLight, Collector)
if: steps.complete.outcome == 'success'
continue-on-error: true
run: node scripts/events.mjs
- name: Advance Server patch notes
if: steps.complete.outcome == 'success'
continue-on-error: true
run: node scripts/advance-server.mjs
# Liquipedia, under its rate limits: about 20 s when nothing changed.
- name: Esports tournaments and pro statistics
if: steps.complete.outcome == 'success'
continue-on-error: true
run: node scripts/esports.mjs
# This file comes from develop but runs main's code: until main has the
# English script names, fall back to the French ones it still uses.
- name: Translate the imported content
if: steps.complete.outcome == 'success'
run: |
npm run "$(node -p "require('./package.json').scripts.translate ? 'translate' : 'traduire'")"
- name: Take the watch snapshot
if: steps.complete.outcome == 'success'
run: |
npm run "$(node -p "require('./package.json').scripts.watch ? 'watch' : 'veille'")"
- name: Check that the site builds with the new data
if: steps.complete.outcome == 'success'
run: npm run build
- name: Daily measures (trends, teammates, game durations)
if: env.COMPLETE != 'true' || steps.complete.outcome == 'failure'
run: |
# The community API sometimes fails. The script then keeps the
# previous measures and the history gathered so far; retry once a
# quarter of an hour later. A lasting outage only leaves a warning:
# the API's thirty days are picked up again at the next run.
for attempt in 1 2; do
if npm run sync:evolution; then exit 0; fi
if [ "$attempt" = 1 ]; then sleep 900; fi
done
echo "::warning::Measures API unavailable: history unchanged today."
- name: Discard an unreadable data file
run: |
# A run interrupted in the middle of a write must not break anything on main.
for f in $(git ls-files -m -o --exclude-standard -- src/data | grep '\.json$'); do
if ! node -e "JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8'))" "$f"; then
echo "::warning::$f unreadable, previous version kept"
git checkout -- "$f" 2>/dev/null || rm -f "$f"
fi
done
- name: Commit the data to main
id: commit
run: |
# Data, images, translations and their caches: nothing else (the build
# must not slip anything into the commit).
git add -A -- src/data public/visuels content src/i18n/messages 'scripts/*.json'
# sync.json holds the run timestamp: on its own, it is not worth a
# commit. Both paths: main still has the French one, and an exclude
# that matches nothing silently excludes nothing.
if git diff --cached --quiet -- . ':(exclude)src/data/game/sync.json' ':(exclude)src/data/jeu/synchro.json'; then
echo "No new data."
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if [ "${{ steps.complete.outcome }}" = success ]; then
git commit -q -m "Sync game data from the wiki and the stats API ($(date -u +%F))"
else
git commit -q -m "Record hero rate history for $(date -u +%F)"
fi
# main may have moved during the sync: replay on top of it. A conflict
# means a release changed the synced files meanwhile (renamed or
# reshaped data): this run's data was produced for the old main, so it
# is dropped rather than forced in; running the sync again regenerates
# it with the new code.
for _ in 1 2 3; do
if ! git pull -q --rebase origin main; then
git rebase --abort 2> /dev/null || true
echo "::error::main changed the synced files during this run (a release?): this run's data is discarded. Run the sync again."
exit 1
fi
if git push -q origin HEAD:main; then
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
git log -1 --stat --format='%h %s' | tail -3
exit 0
fi
sleep 15
done
echo "::error::Push to main refused: main is protected and SYNC_DEPLOY_KEY is missing, or is not allowed to bypass the protection."
exit 1
# Without a key, the push triggered no workflow: deploy and align develop
# from here. With the key, the push took care of it.
deploy:
needs: sync
if: needs.sync.outputs.commit != '' && needs.sync.outputs.with-key != 'true'
# Local path: the workflow of the current commit, as intended.
uses: ./.github/workflows/docker.yml # zizmor: ignore[self-repository]
with:
ref: ${{ needs.sync.outputs.commit }}
deploy: true
secrets:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
permissions:
contents: read
align:
needs: sync
if: needs.sync.outputs.commit != '' && needs.sync.outputs.with-key != 'true'
# Local path: the workflow of the current commit, as intended.
uses: ./.github/workflows/align-develop.yml # zizmor: ignore[self-repository]
secrets:
SYNC_DEPLOY_KEY: ${{ secrets.SYNC_DEPLOY_KEY }}
permissions:
contents: write