main.yml 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. name: Build
  2. on:
  3. push:
  4. branches:
  5. - master
  6. pull_request:
  7. schedule:
  8. - cron: "17 5 * * *" # Runs at 05:17 UTC
  9. - cron: "17 17 * * *" # Runs at 17:17 UTC
  10. workflow_dispatch:
  11. concurrency:
  12. group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  13. cancel-in-progress: true
  14. jobs:
  15. build:
  16. name: Build
  17. runs-on: ubuntu-24.04-arm
  18. steps:
  19. - uses: smorimoto/tune-github-hosted-runner-network@v1
  20. # https://github.com/actions/runner-images/issues/1187
  21. - uses: actions/checkout@v7
  22. with:
  23. persist-credentials: false
  24. - name: Grab Building Folder
  25. id: ramdisk
  26. run: |
  27. echo "build_dir=previous-build-${{ github.run_id }}-${{ github.run_number }}" >> $GITHUB_OUTPUT
  28. - name: Get current date
  29. id: date
  30. run: |
  31. echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
  32. echo "year=$(date +'%Y')" >> $GITHUB_OUTPUT
  33. echo "month=$(date +'%m')" >> $GITHUB_OUTPUT
  34. echo "day=$(date +'%d')" >> $GITHUB_OUTPUT
  35. echo "hour=$(date +'%H')" >> $GITHUB_OUTPUT
  36. echo "minute=$(date +'%M')" >> $GITHUB_OUTPUT
  37. echo "second=$(date +'%S')" >> $GITHUB_OUTPUT
  38. # Download previous build and restore cache.db in the background,
  39. # while the Node.js toolchain is set up in the foreground
  40. - name: Download Previous Build
  41. id: download_previous_build
  42. uses: actions/checkout@v6
  43. background: true
  44. with:
  45. repository: SukkaLab/ruleset.skk.moe
  46. # during a race condition the dist repo may be private, use token to clone
  47. token: ${{ secrets.GIT_TOKEN }}
  48. persist-credentials: false
  49. filter: "tree:0" # we don't care about git history here
  50. fetch-tags: false
  51. path: ${{ steps.ramdisk.outputs.build_dir }}
  52. - name: Restore cache.db
  53. uses: actions/cache/restore@v6
  54. id: cache-db-restore
  55. background: true
  56. with:
  57. path: |
  58. .cache
  59. key: ${{ runner.os }}-v3-${{ steps.date.outputs.year }}-${{ steps.date.outputs.month }}-${{ steps.date.outputs.day }} ${{ steps.date.outputs.hour }}:${{ steps.date.outputs.minute }}:${{ steps.date.outputs.second }}
  60. # If source files changed but packages didn't, rebuild from a prior cache.
  61. restore-keys: |
  62. ${{ runner.os }}-v3-${{ steps.date.outputs.year }}-${{ steps.date.outputs.month }}-${{ steps.date.outputs.day }} ${{ steps.date.outputs.hour }}:${{ steps.date.outputs.minute }}:
  63. ${{ runner.os }}-v3-${{ steps.date.outputs.year }}-${{ steps.date.outputs.month }}-${{ steps.date.outputs.day }} ${{ steps.date.outputs.hour }}:
  64. ${{ runner.os }}-v3-${{ steps.date.outputs.year }}-${{ steps.date.outputs.month }}-${{ steps.date.outputs.day }}
  65. ${{ runner.os }}-v3-${{ steps.date.outputs.year }}-${{ steps.date.outputs.month }}-
  66. ${{ runner.os }}-v3-${{ steps.date.outputs.year }}-
  67. ${{ runner.os }}-v3-
  68. # Setup Node.js and pnpm in foreground
  69. - uses: pnpm/action-setup@v6
  70. with:
  71. run_install: false
  72. - uses: actions/setup-node@v6
  73. with:
  74. node-version-file: ".node-version"
  75. cache: "pnpm"
  76. - run: pnpm config set --location=global minimumReleaseAge 0
  77. - run: pnpm install
  78. # Foreground pnpm + Node.js setup complete, ensure previous build and cache.db are ready
  79. - wait: [download_previous_build, cache-db-restore]
  80. # Double check build folder before starting build
  81. - name: Setup build folder
  82. run: |
  83. if [ ! -d ${{ steps.ramdisk.outputs.build_dir }}/.git ]; then
  84. echo ".git not found"
  85. exit 1
  86. fi
  87. rm -rf "${{ steps.ramdisk.outputs.build_dir }}/.git"
  88. if [ ! -d ${{ steps.ramdisk.outputs.build_dir }}/List ]; then
  89. echo "List not found"
  90. exit 1
  91. fi
  92. echo "public directory is ready: ${{ steps.ramdisk.outputs.build_dir }}"
  93. - run: pnpm run build
  94. env:
  95. PUBLIC_DIR: ${{ steps.ramdisk.outputs.build_dir }}
  96. - name: Pre-deploy check
  97. # If the public directory doesn't exist, the build should fail.
  98. # If the public directory is empty, the build should fail.
  99. run: |
  100. if [ ! -d ${{ steps.ramdisk.outputs.build_dir }} ]; then
  101. echo "public directory not found"
  102. exit 1
  103. fi
  104. if [ ! "$(ls -A ${{ steps.ramdisk.outputs.build_dir }})" ]; then
  105. echo "public directory is empty"
  106. exit 1
  107. fi
  108. if [ ! -f .BUILD_FINISHED ]; then
  109. echo ".BUILD_FINISHED not found"
  110. exit 1
  111. fi
  112. echo "public directory is ready: ${{ steps.ramdisk.outputs.build_dir }}"
  113. # Upload artifact and save cache.db in parallel
  114. - uses: actions/upload-artifact@v7
  115. id: upload_build_artifact
  116. background: true
  117. with:
  118. name: build-artifact-${{ github.sha }}-${{ github.run_number }}
  119. path: ${{ steps.ramdisk.outputs.build_dir }}
  120. if-no-files-found: error
  121. retention-days: 1
  122. compression-level: 4
  123. include-hidden-files: false
  124. - name: Cache cache.db
  125. if: always()
  126. uses: actions/cache/save@v6
  127. with:
  128. path: |
  129. .cache
  130. key: ${{ runner.os }}-v3-${{ steps.date.outputs.year }}-${{ steps.date.outputs.month }}-${{ steps.date.outputs.day }} ${{ steps.date.outputs.hour }}:${{ steps.date.outputs.minute }}:${{ steps.date.outputs.second }}
  131. - wait: upload_build_artifact
  132. diff_deployment_on_pr:
  133. if: github.ref != 'refs/heads/master'
  134. needs:
  135. - build
  136. name: Diff output
  137. runs-on: ubuntu-slim
  138. steps:
  139. - uses: actions/download-artifact@v8
  140. with:
  141. name: build-artifact-${{ github.sha }}-${{ github.run_number }}
  142. path: public
  143. - name: Diff
  144. run: |
  145. git clone --filter=tree:0 --no-tags https://${GH_USER}:${GH_TOKEN}@github.com/SukkaLab/ruleset.skk.moe.git ./deploy-git >/dev/null 2>&1
  146. cd ./deploy-git
  147. git fetch origin master >/dev/null 2>&1
  148. rm -rf ./*
  149. cp -rf ../public/* ./
  150. git --no-pager diff --minimal
  151. env:
  152. GH_USER: ${{ secrets.GIT_USER }}
  153. GH_TOKEN: ${{ secrets.GIT_TOKEN }}
  154. deploy:
  155. needs:
  156. - build
  157. name: Deploy
  158. if: github.ref == 'refs/heads/master'
  159. runs-on: ubuntu-slim
  160. # matrix is a tricky way to define a variable directly in the action yaml
  161. strategy:
  162. matrix:
  163. wranglerVersion: ["3.114.17"]
  164. steps:
  165. - name: Get NPM cache directory path
  166. id: npm_cache_path
  167. shell: sh
  168. run: echo dir=$(npm config get cache) >> $GITHUB_OUTPUT
  169. - uses: actions/cache@v6
  170. with:
  171. path: |
  172. ${{ steps.npm_cache_path.outputs.dir }}
  173. node_modules
  174. key: deploy-to-cloudflare-npm-${{ runner.os }}-${{ runner.arch }}-wrangler-${{ matrix.wranglerVersion }}
  175. restore-keys: |
  176. deploy-to-cloudflare-npm-${{ runner.os }}-${{ runner.arch }}-wrangler-
  177. - uses: actions/download-artifact@v8
  178. with:
  179. name: build-artifact-${{ github.sha }}-${{ github.run_number }}
  180. path: public
  181. - name: Prepare GitHub Pages deployment marker
  182. id: deploy_marker
  183. run: |
  184. echo "marker_content=${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> $GITHUB_OUTPUT
  185. - name: Remove build artifact
  186. uses: geekyeggo/delete-artifact@v6
  187. background: true
  188. with:
  189. name: build-artifact-${{ github.sha }}-${{ github.run_number }}
  190. # Deploy to Cloudflare, GitLab, and GitHub in parallel
  191. - name: Deploy to Cloudflare Pages
  192. id: deploy_cloudflare
  193. uses: cloudflare/wrangler-action@v3
  194. background: true
  195. with:
  196. apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
  197. accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
  198. command: pages deploy public --project-name=sukkaw-ruleset --commit-dirty=true --branch=main
  199. wranglerVersion: ${{ matrix.wranglerVersion }}
  200. - name: Upload Dist to GitLab
  201. id: deploy_gitlab
  202. background: true
  203. run: |
  204. git clone --filter=tree:0 --no-tags https://${GITLAB_TOKEN_NAME}:${GITLAB_TOKEN}@gitlab.com/SukkaW/ruleset.skk.moe.git ./deploy-git-gitlab
  205. cd ./deploy-git-gitlab
  206. git config push.default matching
  207. git config user.email "${GITLAB_EMAIL}"
  208. git config user.name "${GITLAB_USER}"
  209. rm -rf ./*
  210. cp -rf ../public/* ./
  211. git add --all .
  212. git commit -m "deploy: https://github.com/SukkaW/Surge/commit/${GITHUB_SHA}"
  213. git push --quiet --force origin HEAD:master
  214. cd ..
  215. rm -rf ./deploy-git-gitlab
  216. env:
  217. GITLAB_EMAIL: ${{ secrets.GITLAB_EMAIL }}
  218. GITLAB_USER: ${{ secrets.GITLAB_USER }}
  219. GITLAB_TOKEN_NAME: ${{ secrets.GITLAB_TOKEN_NAME }}
  220. GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
  221. - name: Upload Dist to GitHub
  222. id: upload_dist_to_github
  223. background: true
  224. run: |
  225. gh repo unarchive SukkaLab/ruleset.skk.moe --yes
  226. git clone --filter=tree:0 --no-tags https://${GH_USER}:${GH_TOKEN}@github.com/SukkaLab/ruleset.skk.moe.git ./deploy-git-github
  227. cd ./deploy-git-github
  228. git config push.default matching
  229. git config user.email "${GH_EMAIL}"
  230. git config user.name "${GH_USER}"
  231. rm -rf ./*
  232. cp -rf ../public/* ./
  233. echo "ruleset-mirror.skk.moe" > CNAME
  234. echo "${{ steps.deploy_marker.outputs.marker_content }}" > deploy-check.txt
  235. git add --all .
  236. git commit -m "deploy: https://github.com/SukkaW/Surge/commit/${GITHUB_SHA}"
  237. git push --quiet --force origin HEAD:master
  238. cd ..
  239. rm -rf ./deploy-git-github
  240. env:
  241. GH_EMAIL: ${{ secrets.GIT_EMAIL }}
  242. GH_USER: ${{ secrets.GIT_USER }}
  243. GH_TOKEN: ${{ secrets.GIT_TOKEN }}
  244. - wait: upload_dist_to_github
  245. - name: Wait for GitHub Pages to serve deployed marker
  246. if: steps.upload_dist_to_github.outcome == 'success'
  247. id: wait_for_github_pages
  248. run: |
  249. expected="${{ steps.deploy_marker.outputs.marker_content }}"
  250. marker_url="https://ruleset-mirror.skk.moe/deploy-check.txt"
  251. for _ in $(seq 1 30); do
  252. content=$(curl --fail --silent --show-error --location "${marker_url}" || true)
  253. if [ "${content}" = "${expected}" ]; then
  254. echo "GitHub Pages is serving the correct deploy marker"
  255. exit 0
  256. fi
  257. sleep 10
  258. done
  259. echo "Timed out waiting for GitHub Pages deployment"
  260. exit 1
  261. - name: Archive dist repo
  262. if: steps.wait_for_github_pages.outcome == 'success'
  263. run: |
  264. gh repo archive SukkaLab/ruleset.skk.moe --yes
  265. env:
  266. GH_TOKEN: ${{ secrets.GIT_TOKEN }}
  267. - wait-all: