Promote Slack in athlete emails #3142
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This workflow will build a .NET project | |
| # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net | |
| name: .NET | |
| on: | |
| push: | |
| branches: [ "master" ] | |
| pull_request: | |
| branches: [ "master" ] | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5 | |
| with: | |
| dotnet-version: 10.0.x | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: .node-version | |
| cache: npm | |
| cache-dependency-path: LongevityWorldCup.Website/package-lock.json | |
| - name: Restore frontend dependencies | |
| working-directory: LongevityWorldCup.Website | |
| run: npm ci --ignore-scripts --no-audit --no-fund | |
| - name: Type-check and build frontend | |
| working-directory: LongevityWorldCup.Website | |
| run: npm run build | |
| - name: Verify generated frontend assets stay untracked | |
| shell: bash | |
| run: | | |
| tracked="$(git ls-files -- 'LongevityWorldCup.Website/wwwroot/js')" | |
| if [[ -n "$tracked" ]]; then | |
| echo "$tracked" | |
| exit 1 | |
| fi | |
| - name: Restore dependencies with NuGet audit | |
| run: > | |
| dotnet restore | |
| -p:NuGetAudit=true | |
| -p:NuGetAuditMode=all | |
| -p:NuGetAuditLevel=low | |
| -p:WarningsAsErrors=NU1900%3BNU1901%3BNU1902%3BNU1903%3BNU1904%3BNU1905 | |
| - name: Build | |
| run: dotnet build --no-restore -p:BuildFrontend=false | |
| - name: Verify Node-free publish contract | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| publish_output="$RUNNER_TEMP/longevityworldcup-publish" | |
| deny_node_bin="$RUNNER_TEMP/longevityworldcup-deny-node" | |
| source_manifest="$RUNNER_TEMP/longevityworldcup-source-js.sha256" | |
| published_manifest="$RUNNER_TEMP/longevityworldcup-published-js.sha256" | |
| frontend_manifest() { | |
| local root="$1" | |
| ( | |
| cd "$root" | |
| find . -type f -name '*.js' -print0 \ | |
| | LC_ALL=C sort -z \ | |
| | xargs -0 -r sha256sum | |
| ) | |
| } | |
| rm -rf LongevityWorldCup.Website/node_modules "$publish_output" "$deny_node_bin" | |
| mkdir -p "$deny_node_bin" | |
| printf '#!/usr/bin/env bash\necho "Node.js is forbidden in the BuildFrontend=false publish contract." >&2\nexit 1\n' > "$deny_node_bin/node" | |
| cp "$deny_node_bin/node" "$deny_node_bin/npm" | |
| chmod +x "$deny_node_bin/node" "$deny_node_bin/npm" | |
| frontend_manifest LongevityWorldCup.Website/wwwroot/js > "$source_manifest" | |
| test -s "$source_manifest" | |
| PATH="$deny_node_bin:$PATH" dotnet publish LongevityWorldCup.Website/LongevityWorldCup.Website.csproj \ | |
| --configuration Release \ | |
| --no-restore \ | |
| --output "$publish_output" \ | |
| -p:BuildFrontend=false | |
| frontend_manifest "$publish_output/wwwroot/js" > "$published_manifest" | |
| if ! cmp -s "$source_manifest" "$published_manifest"; then | |
| echo "Published frontend assets differ from the generated source assets." >&2 | |
| diff -u "$source_manifest" "$published_manifest" || true | |
| exit 1 | |
| fi | |
| test -z "$(find "$publish_output" -type f -name '*.ts' -print -quit)" | |
| - name: Install Playwright Chromium | |
| run: pwsh LongevityWorldCup.Tests/bin/Debug/net10.0/playwright.ps1 install chromium | |
| - name: Test with coverage | |
| run: > | |
| dotnet test | |
| --no-build | |
| --verbosity normal | |
| -p:CollectCoverage=true | |
| -p:CoverletOutput=TestResults/Coverage/ | |
| -p:CoverletOutputFormat=cobertura | |
| - name: Summarize .NET coverage | |
| id: coverage | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| coverage_file="$(find . -name coverage.cobertura.xml -print | head -n 1)" | |
| if [[ -z "$coverage_file" ]]; then | |
| echo "coverage.cobertura.xml was not produced." >&2 | |
| exit 1 | |
| fi | |
| python3 - "$coverage_file" <<'PY' | tee coverage-summary.md | |
| import sys | |
| import xml.etree.ElementTree as ET | |
| root = ET.parse(sys.argv[1]).getroot() | |
| line_coverage = float(root.attrib.get("line-rate", "0")) * 100 | |
| branch_coverage = float(root.attrib.get("branch-rate", "0")) * 100 | |
| print("## .NET test coverage") | |
| print() | |
| print(f"- Line coverage: {line_coverage:.1f}%") | |
| print(f"- Branch coverage: {branch_coverage:.1f}%") | |
| PY | |
| cat coverage-summary.md >> "$GITHUB_STEP_SUMMARY" | |
| { | |
| echo "markdown<<DOTNET_COVERAGE_SUMMARY" | |
| cat coverage-summary.md | |
| echo "DOTNET_COVERAGE_SUMMARY" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Comment .NET coverage on PR | |
| if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository | |
| uses: actions/github-script@v9 | |
| env: | |
| COVERAGE_MARKDOWN: ${{ steps.coverage.outputs.markdown }} | |
| with: | |
| script: | | |
| const marker = '<!-- dotnet-coverage-summary -->'; | |
| const body = [marker, process.env.COVERAGE_MARKDOWN].join('\n'); | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| try { | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner, | |
| repo, | |
| issue_number, | |
| per_page: 100 | |
| }); | |
| const existing = comments.find(comment => | |
| comment.user?.type === 'Bot' && comment.body?.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: existing.id, | |
| body | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number, | |
| body | |
| }); | |
| } | |
| } catch (error) { | |
| if (error.status === 403) { | |
| core.warning('Could not update the PR coverage comment with GITHUB_TOKEN; the coverage report is still available in the job summary.'); | |
| } else { | |
| throw error; | |
| } | |
| } |