From 87e2df2d3af2762ab663d50a11b91658af0421d8 Mon Sep 17 00:00:00 2001 From: YuYu1015 Date: Mon, 12 Oct 2026 00:19:40 +0800 Subject: [PATCH] ci: keep the station copies in the commit that changes them station.csv and station.json are generated from their -dev files. The bot did that after a merge, pushing a "chore: update station.csv from station-dev.csv" commit straight to main with CI skipped, so every station fix landed as two commits: 95 of the last 300 on main were the bot's. Do it inside the pull request instead. When a same-repository pull request opens or moves, the bot reruns both generators in each of its commits, amends any copy that changed into that commit (author kept, committer github-actions[bot]) and pushes the branch back with a lease on the head it saw. The push goes out with PAT_TOKEN, because commits pushed with GITHUB_TOKEN start no workflow run and the required checks would never report. A rebase merge then lands one commit per change. A copy is still never written by hand, and that is now enforced before a merge instead of reverted after one. station-csv-check and station-json-check fail any pull request whose copy differs from what the generator makes of its -dev file. A pull request that leaves the copy alone passes even if it is stale: a fork gets no secrets, so the bot cannot push to it, and its copies are generated on main after the merge as before. On main, a copy that does not match is regenerated and pushed, which covers a direct push by a ruleset bypass actor. That replaces protect-station-*.yml, which let a hand edit merge and then force-pushed a revert to main, judging commits by author name and email: any GitHub noreply address passed, and a push with CI skipped in its message never ran it at all. All four workflows become one, so the two jobs cannot race to push to main. station-csv-check and station-json-check keep their names, which the ruleset requires, and pass a pull request that does not touch the -dev file or the copy, as before. --- .github/workflows/protect-station-csv.yml | 98 -------- .github/workflows/protect-station-json.yml | 98 -------- .github/workflows/station-check.yml | 66 ----- .github/workflows/station-json-check.yml | 66 ----- .github/workflows/station.yml | 269 +++++++++++++++++++++ 5 files changed, 269 insertions(+), 328 deletions(-) delete mode 100644 .github/workflows/protect-station-csv.yml delete mode 100644 .github/workflows/protect-station-json.yml delete mode 100644 .github/workflows/station-check.yml delete mode 100644 .github/workflows/station-json-check.yml create mode 100644 .github/workflows/station.yml diff --git a/.github/workflows/protect-station-csv.yml b/.github/workflows/protect-station-csv.yml deleted file mode 100644 index f20b090..0000000 --- a/.github/workflows/protect-station-csv.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Protect station.csv - -on: - push: - paths: - - "resource/station.csv" - -jobs: - protect: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - token: ${{ secrets.PAT_TOKEN }} - fetch-depth: 0 - - - name: Check if committer is bot and revert if needed - # 一律經由環境變數傳入,不可寫成 "${{ ... }}" 嵌在 script 裡:GitHub 會先把 - # 表達式文字替換進 script 才交給 bash,而分支名稱、作者名稱與提交訊息都由 - # 推送者決定,嵌入就等於讓推送者在持有 PAT_TOKEN 的 step 裡執行任意指令 - env: - PAT_TOKEN: ${{ secrets.PAT_TOKEN }} - BRANCH: ${{ github.ref_name }} - ACTOR: ${{ github.actor }} - COMMITTER_EMAIL: ${{ github.event.commits[0].author.email }} - COMMITTER_NAME: ${{ github.event.commits[0].author.name }} - COMMIT_MESSAGE: ${{ github.event.commits[0].message }} - COMMIT_SHA: ${{ github.event.commits[0].sha }} - run: | - echo "檢查分支: $BRANCH" - - # 首先檢查是否在 main 分支上 - if [ "$BRANCH" != "main" ]; then - echo "❌ 錯誤: 不允許直接修改 resource/station.csv" - exit 1 - fi - - echo "檢查提交者信息:" - echo " Actor: $ACTOR" - echo " 提交者: $COMMITTER_NAME ($COMMITTER_EMAIL)" - echo " 提交訊息: $COMMIT_MESSAGE" - echo " 提交 SHA: $COMMIT_SHA" - - # 檢查是否為 bot 提交 - IS_BOT=false - - # 檢查 actor 是否為 bot - if [[ "$ACTOR" == *"[bot]"* ]] || [[ "$ACTOR" == "github-actions[bot]" ]]; then - IS_BOT=true - fi - - # 檢查提交者 email 是否為 GitHub Actions - if [[ "$COMMITTER_EMAIL" == *"noreply@github.com"* ]] || \ - [[ "$COMMITTER_EMAIL" == "action@github.com" ]]; then - IS_BOT=true - fi - - # 檢查提交者名稱 - if [[ "$COMMITTER_NAME" == "GitHub Action" ]] || \ - [[ "$COMMITTER_NAME" == *"[bot]"* ]]; then - IS_BOT=true - fi - - # 檢查提交訊息是否包含 [skip ci](我們的 workflow 使用這個標記) - if [[ "$COMMIT_MESSAGE" == *"[skip ci]"* ]]; then - IS_BOT=true - fi - - if [ "$IS_BOT" = false ]; then - echo "❌ 錯誤: resource/station.csv 只能由 bot 或 GitHub Actions 更新" - echo " 當前提交者: $COMMITTER_NAME ($COMMITTER_EMAIL)" - echo " 操作者: $ACTOR" - echo "" - echo "正在 revert resource/station.csv 檔案..." - - # 配置 git - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # 設置 remote URL 包含 PAT token - git remote set-url origin https://x-access-token:$PAT_TOKEN@github.com/${{ github.repository }}.git - - # 只 revert station.csv 檔案(從上一個提交恢復) - git checkout HEAD~1 -- resource/station.csv - - # 提交恢復的檔案 - git add resource/station.csv - git commit -m "Revert unauthorized change to station.csv [skip ci]" - - # 強制推送 - git push origin $BRANCH --force - - echo "✅ 已 revert resource/station.csv 並強制推送" - exit 1 - fi - - echo "✅ 允許: 提交來自 bot 或 GitHub Actions" diff --git a/.github/workflows/protect-station-json.yml b/.github/workflows/protect-station-json.yml deleted file mode 100644 index 73f8859..0000000 --- a/.github/workflows/protect-station-json.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Protect station.json - -on: - push: - paths: - - "resource/station.json" - -jobs: - protect: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - token: ${{ secrets.PAT_TOKEN }} - fetch-depth: 0 - - - name: Check if committer is bot and revert if needed - # 一律經由環境變數傳入,不可寫成 "${{ ... }}" 嵌在 script 裡:GitHub 會先把 - # 表達式文字替換進 script 才交給 bash,而分支名稱、作者名稱與提交訊息都由 - # 推送者決定,嵌入就等於讓推送者在持有 PAT_TOKEN 的 step 裡執行任意指令 - env: - PAT_TOKEN: ${{ secrets.PAT_TOKEN }} - BRANCH: ${{ github.ref_name }} - ACTOR: ${{ github.actor }} - COMMITTER_EMAIL: ${{ github.event.commits[0].author.email }} - COMMITTER_NAME: ${{ github.event.commits[0].author.name }} - COMMIT_MESSAGE: ${{ github.event.commits[0].message }} - COMMIT_SHA: ${{ github.event.commits[0].sha }} - run: | - echo "檢查分支: $BRANCH" - - # 首先檢查是否在 main 分支上 - if [ "$BRANCH" != "main" ]; then - echo "❌ 錯誤: 不允許直接修改 resource/station.json" - exit 1 - fi - - echo "檢查提交者信息:" - echo " Actor: $ACTOR" - echo " 提交者: $COMMITTER_NAME ($COMMITTER_EMAIL)" - echo " 提交訊息: $COMMIT_MESSAGE" - echo " 提交 SHA: $COMMIT_SHA" - - # 檢查是否為 bot 提交 - IS_BOT=false - - # 檢查 actor 是否為 bot - if [[ "$ACTOR" == *"[bot]"* ]] || [[ "$ACTOR" == "github-actions[bot]" ]]; then - IS_BOT=true - fi - - # 檢查提交者 email 是否為 GitHub Actions - if [[ "$COMMITTER_EMAIL" == *"noreply@github.com"* ]] || \ - [[ "$COMMITTER_EMAIL" == "action@github.com" ]]; then - IS_BOT=true - fi - - # 檢查提交者名稱 - if [[ "$COMMITTER_NAME" == "GitHub Action" ]] || \ - [[ "$COMMITTER_NAME" == *"[bot]"* ]]; then - IS_BOT=true - fi - - # 檢查提交訊息是否包含 [skip ci](我們的 workflow 使用這個標記) - if [[ "$COMMIT_MESSAGE" == *"[skip ci]"* ]]; then - IS_BOT=true - fi - - if [ "$IS_BOT" = false ]; then - echo "❌ 錯誤: resource/station.json 只能由 bot 或 GitHub Actions 更新" - echo " 當前提交者: $COMMITTER_NAME ($COMMITTER_EMAIL)" - echo " 操作者: $ACTOR" - echo "" - echo "正在 revert resource/station.json 檔案..." - - # 配置 git - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # 設置 remote URL 包含 PAT token - git remote set-url origin https://x-access-token:$PAT_TOKEN@github.com/${{ github.repository }}.git - - # 只 revert station.json 檔案(從上一個提交恢復) - git checkout HEAD~1 -- resource/station.json - - # 提交恢復的檔案 - git add resource/station.json - git commit -m "Revert unauthorized change to station.json [skip ci]" - - # 強制推送 - git push origin $BRANCH --force - - echo "✅ 已 revert resource/station.json 並強制推送" - exit 1 - fi - - echo "✅ 允許: 提交來自 bot 或 GitHub Actions" diff --git a/.github/workflows/station-check.yml b/.github/workflows/station-check.yml deleted file mode 100644 index 7d19611..0000000 --- a/.github/workflows/station-check.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Station CSV Check - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - "resource/station-dev.csv" - pull_request: - types: [opened, synchronize, reopened] - -jobs: - station-csv-check: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - # PR 只需讀取權限,且 fork 的 PR 拿不到 secrets - token: ${{ github.event_name == 'pull_request' && github.token || secrets.PAT_TOKEN }} - fetch-depth: 0 - persist-credentials: true - sparse-checkout: | - resource/station-dev.csv - resource/station.csv - scripts/station-check.ts - sparse-checkout-cone-mode: false - - # 此 job 為必要檢查,所有 PR 都會觸發;未修改 resource/station-dev.csv 時直接通過 - - name: Detect changes - id: changes - run: | - if [ "${{ github.event_name }}" != "pull_request" ] || \ - git diff --no-renames --name-only HEAD^1 HEAD | grep -qx "resource/station-dev.csv"; then - echo "changed=true" >> "$GITHUB_OUTPUT" - else - echo "未修改 resource/station-dev.csv,略過檢查" - echo "changed=false" >> "$GITHUB_OUTPUT" - fi - - - name: Setup Bun - if: steps.changes.outputs.changed == 'true' - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Run station check - if: steps.changes.outputs.changed == 'true' - run: bun run scripts/station-check.ts - - - name: Commit and push changes - if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' - env: - PAT_TOKEN: ${{ secrets.PAT_TOKEN }} - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # 設置 remote URL 包含 PAT token - git remote set-url origin https://x-access-token:$PAT_TOKEN@github.com/${{ github.repository }}.git - - git add resource/station.csv - git diff --staged --quiet || git commit -m "chore: update station.csv from station-dev.csv [skip ci]" - git push origin main diff --git a/.github/workflows/station-json-check.yml b/.github/workflows/station-json-check.yml deleted file mode 100644 index 5d4bf0a..0000000 --- a/.github/workflows/station-json-check.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Station JSON Check - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - "resource/station-dev.json" - pull_request: - types: [opened, synchronize, reopened] - -jobs: - station-json-check: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - # PR 只需讀取權限,且 fork 的 PR 拿不到 secrets - token: ${{ github.event_name == 'pull_request' && github.token || secrets.PAT_TOKEN }} - fetch-depth: 0 - persist-credentials: true - sparse-checkout: | - resource/station-dev.json - resource/station.json - scripts/station-json-check.ts - sparse-checkout-cone-mode: false - - # 此 job 為必要檢查,所有 PR 都會觸發;未修改 resource/station-dev.json 時直接通過 - - name: Detect changes - id: changes - run: | - if [ "${{ github.event_name }}" != "pull_request" ] || \ - git diff --no-renames --name-only HEAD^1 HEAD | grep -qx "resource/station-dev.json"; then - echo "changed=true" >> "$GITHUB_OUTPUT" - else - echo "未修改 resource/station-dev.json,略過檢查" - echo "changed=false" >> "$GITHUB_OUTPUT" - fi - - - name: Setup Bun - if: steps.changes.outputs.changed == 'true' - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Run station JSON check - if: steps.changes.outputs.changed == 'true' - run: bun run scripts/station-json-check.ts - - - name: Commit and push changes - if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' - env: - PAT_TOKEN: ${{ secrets.PAT_TOKEN }} - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - - # 設置 remote URL 包含 PAT token - git remote set-url origin https://x-access-token:$PAT_TOKEN@github.com/${{ github.repository }}.git - - git add resource/station.json - git diff --staged --quiet || git commit -m "chore: update station.json from station-dev.json [skip ci]" - git push origin main diff --git a/.github/workflows/station.yml b/.github/workflows/station.yml new file mode 100644 index 0000000..77d28f9 --- /dev/null +++ b/.github/workflows/station.yml @@ -0,0 +1,269 @@ +name: Station + +# resource/station.csv 與 resource/station.json 是 bot 維護的副本:由 +# scripts/station-check.ts、scripts/station-json-check.ts 驗證 station-dev.* 後產生 +# (csv 多一欄依位置排序的 loc_code,json 是壓縮過的 dev 檔)。人只改 -dev 檔。 +# +# 副本跟著產生它的 commit 一起進 main。PR 開啟或更新時,bot 在 PR 自己的 commit 裡 +# 重新產生副本,推回 PR 分支;rebase 合併之後,每次修改在 main 上就是一個 commit, +# 不會再跟著一個 bot 補上的 "chore: update station.csv …"。 +# +# 推上 main 之後再產生一次,是給 bot 推不回去的情況兜底:fork 的 PR 拿不到 secret, +# 它的副本會在合併後由這裡補上;有人直接改了副本,也會在這裡被蓋回產生的結果。 + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: [main] + paths: + - "resource/station-dev.csv" + - "resource/station.csv" + - "resource/station-dev.json" + - "resource/station.json" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: station-${{ github.event.pull_request.number || github.ref }} + # PR 的新 push 取代舊的 run。main 則排隊:每次都從當下最新的 main 產生,兩個 run + # 不會同時推,後推的那個也就不會被拒絕。 + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + # 把副本改進 PR 自己的 commit。 + sync: + # 只處理同 repo 的分支:fork 的 PR 拿不到 PAT_TOKEN,也推不回它的分支。 + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + outputs: + pushed: ${{ steps.push.outputs.pushed }} + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + persist-credentials: false + + - uses: oven-sh/setup-bun@v2 + + # 每個 commit 跑一次產生器,副本有變才 amend 進那個 commit:作者不變, + # committer 是 bot,沒動到的 commit 連 SHA 都不變。某個 commit 驗證不過時產生器 + # 不寫檔,那個 commit 原樣保留,錯誤留給下面的檢查回報。 + - name: Regenerate the copies in each commit + env: + BASE_REF: ${{ github.base_ref }} + GIT_COMMITTER_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: 41898282+github-actions[bot]@users.noreply.github.com + run: | + base="$(git merge-base "origin/$BASE_REF" HEAD)" + + # rebase 會把 merge commit 攤平,改寫的就不只是副本了。 + if [ -n "$(git rev-list --merges "$base..HEAD")" ]; then + echo "::notice::分支裡有 merge commit,不改寫;副本會在合併後由 bot 補上" + exit 0 + fi + + cat >"$RUNNER_TEMP/regen.sh" <<'EOF' + bun scripts/station-check.ts >/dev/null 2>&1 + bun scripts/station-json-check.ts >/dev/null 2>&1 + for f in resource/station.csv resource/station.json; do + [ -e "$f" ] && git add -- "$f" + done + git diff --cached --quiet || git commit --amend --no-edit --no-verify --quiet + EOF + + # 會失敗的典型情況是某個 commit 只手改了副本:蓋回產生的結果之後它變成空 + # commit,amend 拒絕。不改寫,交給下面的檢查擋下。 + if ! git rebase --exec "bash $RUNNER_TEMP/regen.sh" "$base"; then + git rebase --abort + echo "::warning::重新產生副本時 rebase 失敗,不改寫分支;副本是否正確由 station-*-check 判斷" + fi + + - name: Push the branch back + id: push + env: + # PAT,不是 GITHUB_TOKEN:GITHUB_TOKEN 推上去的 commit 不會觸發新的 run, + # PR 的必要檢查就永遠等不到結果。 + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + HEAD_REF: ${{ github.head_ref }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + REPO: ${{ github.repository }} + run: | + if [ "$(git rev-parse HEAD)" = "$HEAD_SHA" ]; then + echo "副本已經是最新的" + echo "pushed=false" >>"$GITHUB_OUTPUT" + exit 0 + fi + if [ -z "$PAT_TOKEN" ]; then + echo "::warning::這個 run 拿不到 PAT_TOKEN,推不回分支;副本會在合併後由 bot 補上" + echo "pushed=false" >>"$GITHUB_OUTPUT" + exit 0 + fi + + # lease 釘在這次 run 看到的 head:作者在這之間又推了,就不蓋掉,交給那次 + # push 觸發的 run。hook 一律關掉,因為前一步跑過 PR 裡的程式。 + if git -c core.hooksPath=/dev/null push --no-verify \ + --force-with-lease="refs/heads/$HEAD_REF:$HEAD_SHA" \ + "https://x-access-token:$PAT_TOKEN@github.com/$REPO.git" "HEAD:refs/heads/$HEAD_REF"; then + echo "pushed=true" >>"$GITHUB_OUTPUT" + echo "已把副本改進 PR 的 commit 並推回 $HEAD_REF,檢查會在新的 run 重跑:" + git --no-pager log --format=' %h %an | %s' "$(git merge-base "origin/$GITHUB_BASE_REF" HEAD)..HEAD" + else + echo "::notice::$HEAD_REF 在這次 run 之後又有新的 push,交給那次的 run" + echo "pushed=false" >>"$GITHUB_OUTPUT" + fi + + # 必要檢查,所有 PR 都會跑;-dev 檔與副本都沒改到就直接通過。 + # + # 副本只能是產生器的輸出。PR 改了副本,內容就必須跟 -dev 檔產生的完全一樣,否則 + # 擋下:手改的副本合併不進 main。PR 沒碰副本、但改了 -dev 檔(fork 的 PR,bot + # 推不回去)時副本只是過期,放行,合併後由 update-copies 補上。 + station-csv-check: + needs: sync + # sync 推回了分支,這次 run 的 commit 就不再是 PR 的 head,檢查交給推送觸發的 + # run。sync 沒跑(fork)或失敗時照樣要檢查:前面的 job 沒成功就被略過的 job + # 會回報成功,必要檢查不能這樣放行。 + if: ${{ !cancelled() && github.event_name == 'pull_request' && needs.sync.outputs.pushed != 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + # PR 的 merge commit 與它的第一個 parent(base),比對這個 PR 改了什麼。 + fetch-depth: 2 + persist-credentials: false + sparse-checkout: | + resource/station-dev.csv + resource/station.csv + scripts/station-check.ts + sparse-checkout-cone-mode: false + + - name: Detect changes + id: changes + run: | + files="$(git diff --no-renames --name-only HEAD^1 HEAD)" + dev=false copy=false + grep -qx "resource/station-dev.csv" <<<"$files" && dev=true + grep -qx "resource/station.csv" <<<"$files" && copy=true + echo "dev=$dev" >>"$GITHUB_OUTPUT" + echo "copy=$copy" >>"$GITHUB_OUTPUT" + if [ "$dev" = false ] && [ "$copy" = false ]; then + echo "未修改 resource/station-dev.csv 與 resource/station.csv,略過檢查" + fi + + - name: Setup Bun + if: steps.changes.outputs.dev == 'true' || steps.changes.outputs.copy == 'true' + uses: oven-sh/setup-bun@v2 + + - name: Run station check + if: steps.changes.outputs.dev == 'true' || steps.changes.outputs.copy == 'true' + env: + COPY_CHANGED: ${{ steps.changes.outputs.copy }} + run: | + bun run scripts/station-check.ts + if git diff --quiet -- resource/station.csv; then + exit 0 + fi + if [ "$COPY_CHANGED" = true ]; then + echo "::error file=resource/station.csv::station.csv 只能由 bot 產生,這個 PR 裡的內容跟 station-dev.csv 產生的不一樣。請改 station-dev.csv,並撤銷對 station.csv 的修改" + git --no-pager diff --stat -- resource/station.csv + exit 1 + fi + echo "::warning::resource/station.csv 還沒跟著 station-dev.csv 更新,合併後由 bot 補上" + + station-json-check: + needs: sync + # 條件與 station-csv-check 相同,原因見那裡。 + if: ${{ !cancelled() && github.event_name == 'pull_request' && needs.sync.outputs.pushed != 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 2 + persist-credentials: false + sparse-checkout: | + resource/station-dev.json + resource/station.json + scripts/station-json-check.ts + sparse-checkout-cone-mode: false + + - name: Detect changes + id: changes + run: | + files="$(git diff --no-renames --name-only HEAD^1 HEAD)" + dev=false copy=false + grep -qx "resource/station-dev.json" <<<"$files" && dev=true + grep -qx "resource/station.json" <<<"$files" && copy=true + echo "dev=$dev" >>"$GITHUB_OUTPUT" + echo "copy=$copy" >>"$GITHUB_OUTPUT" + if [ "$dev" = false ] && [ "$copy" = false ]; then + echo "未修改 resource/station-dev.json 與 resource/station.json,略過檢查" + fi + + - name: Setup Bun + if: steps.changes.outputs.dev == 'true' || steps.changes.outputs.copy == 'true' + uses: oven-sh/setup-bun@v2 + + - name: Run station JSON check + if: steps.changes.outputs.dev == 'true' || steps.changes.outputs.copy == 'true' + env: + COPY_CHANGED: ${{ steps.changes.outputs.copy }} + run: | + bun run scripts/station-json-check.ts + if git diff --quiet -- resource/station.json; then + exit 0 + fi + if [ "$COPY_CHANGED" = true ]; then + echo "::error file=resource/station.json::station.json 只能由 bot 產生,這個 PR 裡的內容跟 station-dev.json 產生的不一樣。請改 station-dev.json,並撤銷對 station.json 的修改" + git --no-pager diff --stat -- resource/station.json + exit 1 + fi + echo "::warning::resource/station.json 還沒跟著 station-dev.json 更新,合併後由 bot 補上" + + # main 上的兜底:副本跟 -dev 檔對不上就重新產生並推上去。PR 帶著副本合併時, + # 這裡什麼都不會做。 + update-copies: + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + # 分支當下的 head,不是觸發的那個 commit:排在前面的 run 可能剛推了一個。 + ref: ${{ github.ref_name }} + persist-credentials: false + + - uses: oven-sh/setup-bun@v2 + + - name: Regenerate the copies + env: + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + REPO: ${{ github.repository }} + run: | + # 驗證不過的檔案不會被寫,另一個照樣更新,最後再讓 job 失敗。 + failed=0 + bun run scripts/station-check.ts || failed=1 + bun run scripts/station-json-check.ts || failed=1 + + changed="$(git diff --name-only -- resource/station.csv resource/station.json)" + if [ -z "$changed" ]; then + echo "副本已經是最新的" + exit "$failed" + fi + if [ "$GITHUB_REF" != refs/heads/main ]; then + echo "::notice::不在 main,只檢查不推送:$changed" + exit "$failed" + fi + + case "$changed" in + resource/station.csv) msg="chore: update station.csv from station-dev.csv [skip ci]" ;; + resource/station.json) msg="chore: update station.json from station-dev.json [skip ci]" ;; + *) msg="chore: update station.csv and station.json from their -dev files [skip ci]" ;; + esac + git add resource/station.csv resource/station.json + git -c user.name='github-actions[bot]' \ + -c user.email='41898282+github-actions[bot]@users.noreply.github.com' \ + commit -q -m "$msg" + git push "https://x-access-token:$PAT_TOKEN@github.com/$REPO.git" HEAD:main + exit "$failed"