@@ -3,15 +3,65 @@ name: Build, Test, and Deploy EssentialCSharp.Web
33on :
44 push :
55 branches : ["main"]
6+ pull_request :
7+ branches : ["main"]
8+ merge_group :
69 workflow_dispatch :
710
811permissions :
9- id-token : write
1012 contents : read
1113
14+ concurrency :
15+ group : ${{ github.event_name == 'pull_request' && format('build-test-deploy-pr-{0}', github.event.pull_request.number) || github.run_id }}
16+ cancel-in-progress : true
17+
1218jobs :
19+ frontend-build :
20+ name : Build frontend (${{ matrix.runner }})
21+ runs-on : ${{ matrix.runner }}
22+ timeout-minutes : 20
23+ strategy :
24+ fail-fast : false
25+ matrix :
26+ runner :
27+ - ubuntu-latest
28+ - macos-latest
29+ - windows-latest
30+
31+ steps :
32+ - uses : actions/checkout@v7
33+
34+ - name : Verify Node version matches the Docker build
35+ shell : bash
36+ run : |
37+ expected_node_version="$(tr -d '[:space:]' < EssentialCSharp.Web/.nvmrc)"
38+ docker_node_version="$(sed -nE 's/^FROM node:([0-9]+)-.*/\1/p' EssentialCSharp.Web/Dockerfile | head -n 1)"
39+ if [[ "$expected_node_version" != "$docker_node_version" ]]; then
40+ echo "::error::EssentialCSharp.Web/.nvmrc specifies Node $expected_node_version, but the Dockerfile uses Node $docker_node_version."
41+ exit 1
42+ fi
43+
44+ - name : Set up Node.js
45+ uses : actions/setup-node@v7
46+ with :
47+ node-version-file : EssentialCSharp.Web/.nvmrc
48+ cache : npm
49+ cache-dependency-path : EssentialCSharp.Web/package-lock.json
50+
51+ - name : Install npm dependencies
52+ working-directory : EssentialCSharp.Web
53+ run : npm ci
54+
55+ - name : Build frontend
56+ working-directory : EssentialCSharp.Web
57+ run : npm run build
58+
1359 build-and-test :
60+ name : Build and test (Ubuntu)
61+ if : github.event_name == 'push' || github.event_name == 'workflow_dispatch'
62+ needs : frontend-build
1463 runs-on : ubuntu-latest
64+ timeout-minutes : 45
1565 environment : " BuildAndUploadImage"
1666
1767 steps :
2575 env :
2676 NUGET_AUTH_TOKEN : ${{ secrets.AZURE_DEVOPS_PAT }}
2777
28- - name : Set up Node.js
29- uses : actions/setup-node@v7
30- with :
31- node-version : 24
32- cache : npm
33- cache-dependency-path : EssentialCSharp.Web/package-lock.json
34-
3578 - name : Set up dependency caching for faster builds
3679 uses : actions/cache@v6
3780 id : nuget-cache
4891 run : dotnet restore
4992
5093 - name : Build with dotnet
51- run : dotnet build -p:ContinuousIntegrationBuild=True -p:ReleaseDateAttribute=True --configuration Release --no-restore
94+ run : dotnet build -p:ContinuousIntegrationBuild=True -p:ReleaseDateAttribute=True -p:SkipFrontendBuild=true - -configuration Release --no-restore
5295
5396 - name : Expose GitHub Actions Runtime
5497 uses : actions/github-script@v9
@@ -112,9 +155,127 @@ jobs:
112155 name : essentialcsharpwebimage
113156 path : ${{ github.workspace }}/essentialcsharpwebimage.tar
114157
158+ pr-build-and-test :
159+ name : Build and test (${{ matrix.runner }})
160+ if : github.event_name == 'pull_request' || github.event_name == 'merge_group'
161+ needs : frontend-build
162+ runs-on : ${{ matrix.runner }}
163+ timeout-minutes : 30
164+ strategy :
165+ fail-fast : false
166+ matrix :
167+ include :
168+ - runner : ubuntu-latest
169+ collect_coverage : true
170+ - runner : macos-latest
171+ collect_coverage : false
172+ - runner : windows-latest
173+ collect_coverage : false
174+
175+ steps :
176+ - uses : actions/checkout@v7
177+
178+ - name : Set up .NET
179+ uses : actions/setup-dotnet@v6
180+ with :
181+ global-json-file : global.json
182+
183+ - name : Set up NuGet cache
184+ uses : actions/cache@v6
185+ with :
186+ path : |
187+ ~/.nuget/packages
188+ ${{ github.workspace }}/**/obj/project.assets.json
189+ key : ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
190+ restore-keys : |
191+ ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
192+ ${{ runner.os }}-nuget-
193+
194+ - name : Restore .NET dependencies
195+ run : dotnet restore /p:AccessToNugetFeed=false
196+
197+ - name : Build .NET
198+ run : dotnet build --configuration Release --no-restore /p:AccessToNugetFeed=false /p:SkipFrontendBuild=true
199+
200+ - name : Restore local .NET tools
201+ if : matrix.collect_coverage
202+ run : dotnet tool restore
203+
204+ - name : Check for pending EF Core model changes
205+ if : matrix.collect_coverage
206+ run : dotnet tool run dotnet-ef -- migrations has-pending-model-changes --project EssentialCSharp.Web --configuration Release --no-build
207+ env :
208+ ASPNETCORE_ENVIRONMENT : Development
209+
210+ - name : Expose GitHub Actions Runtime
211+ if : matrix.collect_coverage
212+ uses : actions/github-script@v9
213+ with :
214+ script : |
215+ core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env['ACTIONS_RUNTIME_TOKEN']);
216+ core.exportVariable('ACTIONS_RESULTS_URL', process.env['ACTIONS_RESULTS_URL']);
217+
218+ - name : Run .NET tests with coverage
219+ if : matrix.collect_coverage
220+ run : dotnet test --no-build --configuration Release --report-trx --coverage --results-directory ${{ runner.temp }}
221+
222+ - name : Run .NET tests
223+ if : ${{ !matrix.collect_coverage }}
224+ run : dotnet test --no-build --configuration Release
225+
226+ - name : Convert TRX to VS Playlist
227+ if : ${{ failure() && matrix.collect_coverage }}
228+ uses : BenjaminMichaelis/trx-to-vsplaylist@v4
229+ with :
230+ trx-file-path : ' ${{ runner.temp }}/*.trx'
231+ output-directory : ' ${{ runner.temp }}/vsplaylists'
232+
233+ container-validation :
234+ name : Build container image
235+ if : github.event_name == 'pull_request' || github.event_name == 'merge_group'
236+ needs : [frontend-build, pr-build-and-test]
237+ runs-on : ubuntu-latest
238+ timeout-minutes : 30
239+
240+ steps :
241+ - uses : actions/checkout@v7
242+
243+ - name : Set up Docker Buildx
244+ uses : docker/setup-buildx-action@v4
245+ id : buildx
246+
247+ - name : Restore BuildKit cache mounts
248+ uses : actions/cache@v6
249+ id : buildkit-cache
250+ with :
251+ path : buildkit-cache
252+ key : buildkit-cache-${{ hashFiles('EssentialCSharp.Web/Dockerfile', 'Directory.Packages.props', '**/*.csproj', 'EssentialCSharp.Web/package-lock.json') }}
253+ restore-keys : buildkit-cache-
254+
255+ - name : Inject BuildKit cache mounts
256+ uses : reproducible-containers/buildkit-cache-dance@v3
257+ with :
258+ builder : ${{ steps.buildx.outputs.name }}
259+ dockerfile : EssentialCSharp.Web/Dockerfile
260+ cache-dir : buildkit-cache
261+ skip-extraction : ${{ steps.buildkit-cache.outputs.cache-hit }}
262+
263+ - name : Build container image
264+ uses : docker/build-push-action@v7
265+ with :
266+ file : ./EssentialCSharp.Web/Dockerfile
267+ context : .
268+ push : false
269+ cache-from : |
270+ type=gha,scope=essentialcsharpweb-main
271+ type=gha,scope=essentialcsharpweb-pr
272+ cache-to : type=gha,mode=min,scope=essentialcsharpweb-pr
273+ build-args : ACCESS_TO_NUGET_FEED=false
274+
115275 deploy-development :
116- if : github.event_name != 'pull_request_target' && github.event_name != 'pull_request '
276+ if : github.event_name == 'push' || github.event_name == 'workflow_dispatch '
117277 runs-on : ubuntu-latest
278+ timeout-minutes : 45
118279 needs : build-and-test
119280 concurrency :
120281 group : deploy-development
@@ -172,8 +333,9 @@ jobs:
172333 az account clear
173334
174335 deploy-production :
175- if : github.event_name != 'pull_request_target' && github.event_name != 'pull_request '
336+ if : github.event_name == 'push' || github.event_name == 'workflow_dispatch '
176337 runs-on : ubuntu-latest
338+ timeout-minutes : 45
177339 needs : [deploy-development]
178340 concurrency :
179341 group : deploy-production
0 commit comments