77
88permissions :
99 contents : read
10+ pull-requests : read
1011
1112env :
1213 DOCKERHUB_USERNAME : ${{ secrets.DOCKERHUB_USERNAME }}
1314 IMAGE_TAG : ${{ github.sha }}
1415
1516jobs :
16- verify-build-publish :
17+ detect :
1718 runs-on : ubuntu-latest
19+ outputs :
20+ node_packages_json : ${{ steps.affected.outputs.node_packages_json }}
21+ go_packages_json : ${{ steps.affected.outputs.go_packages_json }}
22+ docker_images_json : ${{ steps.affected.outputs.docker_images_json }}
23+ has_node : ${{ steps.affected.outputs.has_node }}
24+ has_go : ${{ steps.affected.outputs.has_go }}
25+ has_code : ${{ steps.affected.outputs.has_code }}
26+ has_docker : ${{ steps.affected.outputs.has_docker }}
27+ deploy_cloudflare : ${{ steps.affected.outputs.deploy_cloudflare }}
28+ notify_deployer : ${{ steps.affected.outputs.notify_deployer }}
29+ needs_db_migrate : ${{ steps.affected.outputs.needs_db_migrate }}
30+ needs_infra_sync : ${{ steps.affected.outputs.needs_infra_sync }}
31+ needs_deployer_update : ${{ steps.affected.outputs.needs_deployer_update }}
32+ needs_env_manual_review : ${{ steps.affected.outputs.needs_env_manual_review }}
33+ changed_files : ${{ steps.affected.outputs.changed_files }}
34+ is_pr_merge : ${{ steps.pr.outputs.is_pr_merge }}
1835 steps :
1936 - uses : actions/checkout@v6
2037 with :
2138 fetch-depth : 0
2239
23- - uses : pnpm/action-setup@v6
40+ - uses : actions/setup-node@v6
41+ with :
42+ node-version : 24
43+
44+ - name : Detect affected modules
45+ id : affected
46+ run : node scripts/ci/affected-modules.mjs --base "${{ github.event.before }}" --head "${{ github.sha }}"
47+
48+ - name : Detect associated pull request
49+ id : pr
50+ env :
51+ GITHUB_TOKEN : ${{ github.token }}
52+ run : |
53+ node <<'NODE' >> "$GITHUB_OUTPUT"
54+ const response = await fetch(`${process.env.GITHUB_API_URL}/repos/${process.env.GITHUB_REPOSITORY}/commits/${process.env.GITHUB_SHA}/pulls`, {
55+ headers: {
56+ accept: 'application/vnd.github+json',
57+ authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
58+ 'x-github-api-version': '2022-11-28',
59+ },
60+ });
61+ const pulls = response.ok ? await response.json() : [];
62+ console.log(`is_pr_merge=${Array.isArray(pulls) && pulls.length > 0}`);
63+ NODE
64+
65+ node-checks :
66+ needs : detect
67+ if : needs.detect.outputs.has_node == 'true' && needs.detect.outputs.is_pr_merge != 'true'
68+ runs-on : ubuntu-latest
69+ strategy :
70+ fail-fast : false
71+ matrix : ${{ fromJson(needs.detect.outputs.node_packages_json) }}
72+ steps :
73+ - uses : actions/checkout@v6
74+
75+ - uses : pnpm/action-setup@v5
2476
2577 - uses : actions/setup-node@v6
2678 with :
2779 node-version : 24.15.0
2880 cache : pnpm
2981
30- - uses : actions/setup-go@v6
31- with :
32- go-version-file : apps/worker/go.mod
33- cache-dependency-path : |
34- apps/worker/go.sum
35- apps/deployer/go.sum
36-
3782 - name : Install dependencies
3883 run : pnpm install --frozen-lockfile
3984
@@ -46,22 +91,69 @@ jobs:
4691 sed -i 's/NODE_ENV=development/NODE_ENV=production/' .env.prod
4792
4893 - name : Check format
49- run : pnpm run format:check
94+ run : pnpm --filter "${{ matrix.package }}" --if-present run format:check
5095
5196 - name : Lint
52- run : pnpm run lint
97+ run : pnpm --filter "${{ matrix.package }}" --if-present run lint
5398
5499 - name : Typecheck
55- run : pnpm run typecheck
100+ run : pnpm --filter "${{ matrix.package }}" --if-present run typecheck
56101
57102 - name : Test
58- run : pnpm run test
103+ run : pnpm env:test -- "pnpm --filter '${{ matrix.package }}' --if-present run test"
104+
105+ - name : Build
106+ run : pnpm env:prod -- "pnpm --filter '${{ matrix.package }}...' --if-present run build"
107+
108+ go-checks :
109+ needs : detect
110+ if : needs.detect.outputs.has_go == 'true' && needs.detect.outputs.is_pr_merge != 'true'
111+ runs-on : ubuntu-latest
112+ strategy :
113+ fail-fast : false
114+ matrix : ${{ fromJson(needs.detect.outputs.go_packages_json) }}
115+ steps :
116+ - uses : actions/checkout@v6
117+
118+ - uses : pnpm/action-setup@v5
119+
120+ - uses : actions/setup-node@v6
121+ with :
122+ node-version : 24
123+
124+ - uses : actions/setup-go@v6
125+ with :
126+ go-version-file : ${{ matrix.path }}/go.mod
127+ cache-dependency-path : ${{ matrix.path }}/go.sum
128+
129+ - name : Check format
130+ run : pnpm --filter "${{ matrix.package }}" run format:check
131+
132+ - name : Lint
133+ run : pnpm --filter "${{ matrix.package }}" run lint
134+
135+ - name : Test
136+ run : pnpm --filter "${{ matrix.package }}" run test
137+
138+ - name : Build
139+ run : pnpm --filter "${{ matrix.package }}" run build
59140
60- - name : Build apps
61- run : pnpm run build
141+ publish-images :
142+ needs :
143+ - detect
144+ - node-checks
145+ - go-checks
146+ if : always() && needs.detect.outputs.has_docker == 'true' && needs.detect.result == 'success' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
147+ runs-on : ubuntu-latest
148+ strategy :
149+ fail-fast : false
150+ matrix : ${{ fromJson(needs.detect.outputs.docker_images_json) }}
151+ steps :
152+ - uses : actions/checkout@v6
62153
63- - name : Build Cloudflare worker
64- run : pnpm run build:cf
154+ - uses : actions/setup-node@v6
155+ with :
156+ node-version : 24
65157
66158 - uses : docker/setup-buildx-action@v4
67159
@@ -70,32 +162,53 @@ jobs:
70162 username : ${{ secrets.DOCKERHUB_USERNAME }}
71163 password : ${{ secrets.DOCKERHUB_TOKEN }}
72164
73- - name : Build and push API image
165+ - name : Build and push ${{ matrix.id }} image
74166 uses : docker/build-push-action@v7
75167 with :
76168 context : .
77- file : apps/api/Dockerfile
169+ file : ${{ matrix.dockerfile }}
78170 push : true
79171 tags : |
80- docker.io/${{ env.DOCKERHUB_USERNAME }}/zhblogs-api:${{ env.IMAGE_TAG }}
81- docker.io/${{ env.DOCKERHUB_USERNAME }}/zhblogs-api:main
172+ docker.io/${{ env.DOCKERHUB_USERNAME }}/${{ matrix.image }}:${{ env.IMAGE_TAG }}
173+ docker.io/${{ env.DOCKERHUB_USERNAME }}/${{ matrix.image }}:main
174+
175+ - name : Prune ${{ matrix.id }} image history
176+ env :
177+ DOCKERHUB_TOKEN : ${{ secrets.DOCKERHUB_TOKEN }}
178+ DOCKERHUB_REPOSITORY : ${{ matrix.image }}
179+ DOCKERHUB_KEEP_TAGS : " 10"
180+ run : node scripts/ci/prune-dockerhub-tags.mjs
181+
182+ deploy-cloudflare :
183+ needs :
184+ - detect
185+ - node-checks
186+ - go-checks
187+ if : always() && needs.detect.outputs.deploy_cloudflare == 'true' && needs.detect.result == 'success' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled')
188+ runs-on : ubuntu-latest
189+ env :
190+ CLOUDFLARE_API_TOKEN : ${{ secrets.CLOUDFLARE_API_TOKEN }}
191+ CLOUDFLARE_ACCOUNT_ID : ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
192+ steps :
193+ - uses : actions/checkout@v6
82194
83- - name : Build and push web image
84- uses : docker/build-push-action@v7
85- with :
86- context : .
87- file : apps/web/Dockerfile
88- push : true
89- tags : |
90- docker.io/${{ env.DOCKERHUB_USERNAME }}/zhblogs-web:${{ env.IMAGE_TAG }}
91- docker.io/${{ env.DOCKERHUB_USERNAME }}/zhblogs-web:main
195+ - uses : pnpm/action-setup@v5
92196
93- - name : Build and push worker image
94- uses : docker/build-push-action@v7
197+ - uses : actions/setup-node@v6
95198 with :
96- context : .
97- file : apps/worker/Dockerfile
98- push : true
99- tags : |
100- docker.io/${{ env.DOCKERHUB_USERNAME }}/zhblogs-worker:${{ env.IMAGE_TAG }}
101- docker.io/${{ env.DOCKERHUB_USERNAME }}/zhblogs-worker:main
199+ node-version : 24
200+ cache : pnpm
201+
202+ - name : Install dependencies
203+ run : pnpm install --frozen-lockfile
204+
205+ - name : Prepare env files
206+ run : |
207+ cp .env.example .env.dev
208+ cp .env.example .env.prod
209+ cp .env.example .env.test
210+ sed -i 's/NODE_ENV=development/NODE_ENV=test/' .env.test
211+ sed -i 's/NODE_ENV=development/NODE_ENV=production/' .env.prod
212+
213+ - name : Deploy Cloudflare worker
214+ run : pnpm env:prod -- "CLOUDFLARE_INCLUDE_PROCESS_ENV=true pnpm -F @zhblogs/cloudflare run deploy"
0 commit comments