Repository navigation
161 lines (147 loc) · 7.4 KB
/
Copy pathsync.yml
File metadata and controls
161 lines (147 loc) · 7.4 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
name: Sync Upstream and Publish
# Whenever the upstream book/ has new commits, this workflow syncs the new
# content into the data API on the next cycle. It can also be triggered manually
# (Actions → Run workflow); force=true republishes even if the SHA is unchanged.
on:
schedule:
- cron: '23 */6 * * *'
workflow_dispatch:
inputs:
force:
description: 'Force republish (ignore unchanged SHA)'
type: boolean
default: false
permissions:
contents: write # commit README count refresh
issues: write
jobs:
sync:
runs-on: ubuntu-latest
env:
API: https://dl.aipalnet.cn
OUT: dist/api
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Fetch upstream
run: |
git remote add upstream https://github.com/eternity4719/HowToLiveBetter.git 2>/dev/null || true
git fetch upstream main
UPSTREAM_SHA=$(git rev-parse upstream/main)
echo "UPSTREAM_SHA=$UPSTREAM_SHA" >> $GITHUB_ENV
echo "Upstream HEAD: ${UPSTREAM_SHA:0:7}"
- name: Compare with live version
id: check
run: |
set -e
FORCE="${{ github.event.inputs.force }}"
# If the API has never been published (/api/meta 404), we must publish
# once, so treat "unreachable" as 0 steps.
LIVE=$(curl -sS --max-time 15 "$API/api/meta" || echo '')
LIVE_SHA=$(echo "$LIVE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).sha||"")}catch{console.log("")}})')
LIVE_STEPS=$(echo "$LIVE" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{console.log(JSON.parse(s).counts?.steps||0)}catch{console.log(0)}})')
echo "Live sha=${LIVE_SHA:-none} steps=$LIVE_STEPS"
echo "LIVE_STEPS=$LIVE_STEPS" >> $GITHUB_ENV
echo "LIVE_SHA=$LIVE_SHA" >> $GITHUB_ENV
if [ "$FORCE" != "true" ] && [ "$LIVE_SHA" = "$UPSTREAM_SHA" ]; then
echo "Upstream has no new commits (${LIVE_SHA:0:7}), skipping publish"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Generate API data
if: steps.check.outputs.skip != 'true'
run: node tools/sync-articles.mjs --emit-json --out "$OUT"
- name: Regression gate
if: steps.check.outputs.skip != 'true'
run: node tools/verify-payload.mjs --dir "$OUT" --prev-steps "$LIVE_STEPS"
- name: Publish to data API
if: steps.check.outputs.skip != 'true'
run: |
set -e
CODE=$(curl -sS -o /tmp/publish.json -w '%{http_code}' --max-time 120 \
-X POST "$API/api/publish" \
-H 'content-type: application/json' \
-H "x-sync-token: ${{ secrets.HTLB_SYNC_TOKEN }}" \
--data-binary @"$OUT/publish.json")
echo "Publish returned $CODE"
cat /tmp/publish.json
[ "$CODE" = "200" ] || [ "$CODE" = "201" ] || exit 1
- name: Post-publish self-check
if: steps.check.outputs.skip != 'true'
run: |
set -e
sleep 3
curl -sS --max-time 15 "$API/api/meta" | node -e '
let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{
const m=JSON.parse(s);
if(!m.version||!m.sourceUpdatedAt) throw new Error("meta missing version/sourceUpdatedAt");
console.log(`Live v${m.version} · ${m.counts.steps} steps · updated ${m.sourceUpdatedAt}`);
})'
curl -sS --max-time 15 "$API/api/steps/b01" | node -e '
let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{
const r=JSON.parse(s);
if(!Array.isArray(r.steps)||!r.steps.length) throw new Error("b01 body empty");
console.log(`b01 body ${r.steps.length} steps · rev ${r.rev}`);
})'
# Keep README intro counts and the repo "About" description in sync with
# the live data. Runs on every cycle (even when publish was skipped) and
# is idempotent: writes only happen when the numbers actually changed.
- name: Refresh counts in README and About
continue-on-error: true
env:
# A GitHub PAT (repo scope): PATCHing the repo About description is
# an admin endpoint the workflow GITHUB_TOKEN cannot reach. Note
# HTLB_SYNC_TOKEN is the data-API publish token, NOT a GitHub PAT.
GH_TOKEN: ${{ secrets.HTLB_ABOUT_PAT }}
run: |
set -e
META=$(curl -sS --max-time 15 "$API/api/meta")
STEPS=$(echo "$META" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{console.log(JSON.parse(s).counts.steps)})')
CHAPTERS=$(echo "$META" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{console.log(JSON.parse(s).counts.articles)})')
echo "Live counts: ${CHAPTERS} chapters / ${STEPS} steps"
# 1) README: intro line "这 N 条建议" (steps) and "全部 N 章正文" (chapters).
# NOTE: the old "N 章、M 条" combined intro line was removed in
# f4b4c9a, so the regex must match the current wording or the
# count silently stops refreshing (step is continue-on-error).
if [ -f README.md ]; then
sed -i -E "s/这 [0-9]+ 条建议/这 ${STEPS} 条建议/" README.md
sed -i -E "s/全部 [0-9]+ 章正文/全部 ${CHAPTERS} 章正文/" README.md
if ! git diff --quiet README.md; then
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.md
git commit -m "docs: README 条数同步为 ${CHAPTERS} 章 ${STEPS} 条"
git push
echo "README counts updated"
else
echo "README counts already up to date"
fi
fi
# 2) Repo About description: replace the stale "N 条"
DESC=$(gh api "repos/${{ github.repository }}" --jq .description)
NEW_DESC=$(printf '%s' "$DESC" | sed -E "s/[0-9]+ 条/${STEPS} 条/")
if [ -n "$NEW_DESC" ] && [ "$NEW_DESC" != "$DESC" ]; then
gh api -X PATCH "repos/${{ github.repository }}" -f description="$NEW_DESC" --jq .description
echo "About updated: $NEW_DESC"
else
echo "About already up to date"
fi
# On failure, open an issue so a silent scheduled failure is not missed.
- name: Failure alert
if: failure()
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh label create sync-failure --color d73a4a --description "Data sync failed" 2>/dev/null || true
gh issue create \
--title "Data sync failed $(date -u +%F' '%H:%M)Z" \
--body "Workflow ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} failed, not published. Check upstream md format or /api/publish availability." \
--label "sync-failure" 2>/dev/null || \
gh issue create \
--title "Data sync failed $(date -u +%F' '%H:%M)Z" \
--body "Workflow ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} failed, not published. Check upstream md format or /api/publish availability."