1- # GitHub Actions Workflow: Deploy Coverage Reports to GitHub Pages
1+ # GitHub Actions Workflow: Coverage Reports for Pull Requests
22#
3- # This workflow runs tests with coverage, generates HTML reports, and deploys
4- # them to GitHub Pages for easy viewing of test coverage metrics .
3+ # This workflow runs tests with coverage on PRs and comments the results.
4+ # Actual deployment to GitHub Pages is handled by docs.yml workflow .
55#
6- # The coverage reports will be available at :
6+ # Coverage reports on the live site :
77# https://structured-world.github.io/gitlab-mcp/coverage/
8- #
9- # Triggers:
10- # - Push to main branch (updates coverage reports)
11- # - Pull requests (generates preview but doesn't deploy)
12- # - Manual workflow dispatch
138
14- name : Deploy Coverage to GitHub Pages
9+ name : Coverage Report
1510
1611on :
17- # Trigger on pushes to main branch
18- push :
19- branches :
20- - main
21- # Only run if relevant files changed
22- paths :
23- - " src/**"
24- - " package.json"
25- - " yarn.lock"
26- - " tsconfig.json"
27- - " jest.config.*"
28- - " .github/workflows/coverage-pages.yml"
29-
30- # Trigger on pull requests for preview
3112 pull_request :
3213 branches :
3314 - main
@@ -38,312 +19,102 @@ on:
3819 - " tsconfig.json"
3920 - " jest.config.*"
4021
41- # Allow manual triggering
42- workflow_dispatch :
43-
44- # Ensure only one deployment runs at a time
4522concurrency :
46- group : pages -${{ github.ref }}
23+ group : coverage -${{ github.ref }}
4724 cancel-in-progress : true
4825
49- # Set permissions for GitHub Pages deployment and PR comments
5026permissions :
5127 contents : read
52- pages : write
53- id-token : write
5428 pull-requests : write
5529 issues : write
56- actions : read
5730
5831jobs :
59- # Job 1: Generate coverage reports
6032 coverage :
61- name : Generate Coverage Reports
33+ name : Generate Coverage Report
6234 runs-on : ubuntu-latest
63-
64- # Set outputs for use in deployment job
65- outputs :
66- coverage-percentage : ${{ steps.coverage.outputs.percentage }}
67- should-deploy : ${{ steps.should-deploy.outputs.result }}
68-
6935 steps :
70- # Step 1: Check out the repository
7136 - name : Checkout repository
72- uses : actions/checkout@v6
37+ uses : actions/checkout@v4
7338 with :
7439 fetch-depth : 0
7540
76- # Step 2: Set up Node.js environment
7741 - name : Set up Node.js
7842 uses : actions/setup-node@v4
7943 with :
80- node-version : " 22"
81- # Don't cache yarn yet - enable corepack first
44+ node-version : 24
8245
83- # Step 3: Enable Corepack for Yarn 4
8446 - name : Enable Corepack
8547 run : corepack enable
8648
87- # Step 4: Install dependencies
8849 - name : Install dependencies
8950 run : yarn install --immutable
9051
91- # Step 5: Run tests with coverage
9252 - name : Run tests with coverage
9353 run : yarn test:cov
9454
95- # Step 6: Extract coverage percentage for badge/display
9655 - name : Extract coverage percentage
9756 id : coverage
9857 run : |
99- # Extract coverage percentage from Jest output
10058 COVERAGE=$(cat coverage/lcov-report/index.html | grep -o 'class="strong">[0-9.]*%' | head -1 | grep -o '[0-9.]*')
10159 echo "percentage=${COVERAGE:-0}" >> $GITHUB_OUTPUT
102- echo "Coverage: ${COVERAGE:-0}%"
103-
104- # Step 7: Create coverage summary for PR comments
105- - name : Generate coverage summary
106- if : github.event_name == 'pull_request'
107- run : |
108- # Extract coverage metrics from JSON summary (more reliable than HTML parsing)
109- if [ -f coverage/coverage-summary.json ]; then
110- STATEMENTS=$(jq -r '.total.statements.pct' coverage/coverage-summary.json)
111- BRANCHES=$(jq -r '.total.branches.pct' coverage/coverage-summary.json)
112- FUNCTIONS=$(jq -r '.total.functions.pct' coverage/coverage-summary.json)
113- LINES=$(jq -r '.total.lines.pct' coverage/coverage-summary.json)
114- else
115- STATEMENTS="N/A"
116- BRANCHES="N/A"
117- FUNCTIONS="N/A"
118- LINES="N/A"
119- fi
120-
121- OVERALL="${{ steps.coverage.outputs.percentage }}"
122-
123- # Format percentages (add % only if value is numeric)
124- format_pct() {
125- if [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
126- echo "${1}%"
127- elif [[ "$1" == "null" ]]; then
128- echo "N/A"
129- else
130- echo "$1"
131- fi
132- }
133-
134- STATEMENTS_FMT=$(format_pct "$STATEMENTS")
135- BRANCHES_FMT=$(format_pct "$BRANCHES")
136- FUNCTIONS_FMT=$(format_pct "$FUNCTIONS")
137- LINES_FMT=$(format_pct "$LINES")
138-
139- # Create a markdown summary of coverage
140- cat > coverage-summary.md << EOF
141- ## 📊 Test Coverage Report
14260
143- **Overall Coverage:** ${OVERALL}%
144-
145- ### Coverage Details
146- | Metric | Percentage |
147- |--------|------------|
148- | Statements | ${STATEMENTS_FMT} |
149- | Branches | ${BRANCHES_FMT} |
150- | Functions | ${FUNCTIONS_FMT} |
151- | Lines | ${LINES_FMT} |
152-
153- **Coverage Report:** [View detailed coverage report](https://structured-world.github.io/gitlab-mcp/coverage/)
154-
155- > This report was generated automatically from your PR changes.
156- EOF
157-
158- # Step 8: Comment coverage summary on PR
15961 - name : Comment coverage on PR
160- if : github.event_name == 'pull_request'
62+ continue-on-error : true
16163 uses : actions/github-script@v7
16264 with :
16365 github-token : ${{ secrets.GITHUB_TOKEN }}
16466 script : |
16567 const fs = require('fs');
166- const summary = fs.readFileSync('coverage-summary.md', 'utf8') ;
68+ let summary;
16769
16870 try {
169- // Find existing coverage comment
170- const comments = await github.rest.issues.listComments({
71+ const json = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8'));
72+ const t = json.total;
73+ summary = [
74+ '## Test Coverage Report',
75+ '',
76+ `**Overall Coverage:** ${t.lines.pct}%`,
77+ '',
78+ '| Metric | Percentage |',
79+ '|--------|------------|',
80+ `| Statements | ${t.statements.pct}% |`,
81+ `| Branches | ${t.branches.pct}% |`,
82+ `| Functions | ${t.functions.pct}% |`,
83+ `| Lines | ${t.lines.pct}% |`,
84+ '',
85+ '[View detailed coverage report](https://structured-world.github.io/gitlab-mcp/coverage/)',
86+ ].join('\n');
87+ } catch {
88+ summary = `## Test Coverage Report\n\n**Coverage:** ${{ steps.coverage.outputs.percentage }}%`;
89+ }
90+
91+ const comments = await github.rest.issues.listComments({
92+ owner: context.repo.owner,
93+ repo: context.repo.repo,
94+ issue_number: context.issue.number,
95+ });
96+
97+ const existing = comments.data.find(c => c.body.includes('## Test Coverage Report'));
98+
99+ if (existing) {
100+ await github.rest.issues.updateComment({
101+ owner: context.repo.owner,
102+ repo: context.repo.repo,
103+ comment_id: existing.id,
104+ body: summary
105+ });
106+ } else {
107+ await github.rest.issues.createComment({
171108 owner: context.repo.owner,
172109 repo: context.repo.repo,
173110 issue_number: context.issue.number,
111+ body: summary
174112 });
175-
176- const existingComment = comments.data.find(comment =>
177- comment.body.includes('## 📊 Test Coverage Report')
178- );
179-
180- if (existingComment) {
181- // Update existing comment
182- await github.rest.issues.updateComment({
183- owner: context.repo.owner,
184- repo: context.repo.repo,
185- comment_id: existingComment.id,
186- body: summary
187- });
188- console.log('Updated existing coverage comment');
189- } else {
190- // Create new comment
191- await github.rest.issues.createComment({
192- owner: context.repo.owner,
193- repo: context.repo.repo,
194- issue_number: context.issue.number,
195- body: summary
196- });
197- console.log('Created new coverage comment');
198- }
199- } catch (error) {
200- console.log('Failed to comment on PR (this may be due to organization permissions):', error.message);
201- // Don't fail the workflow if commenting fails
202113 }
203114
204- # Step 9: Prepare coverage reports for deployment
205- - name : Prepare coverage for deployment
206- run : |
207- # Create deployment directory
208- mkdir -p pages-deploy/coverage
209-
210- # Copy coverage reports to match expected URL structure
211- cp -r coverage/* pages-deploy/coverage/
212-
213- # Create index.html that redirects to coverage report
214- cat > pages-deploy/index.html << 'EOF'
215- <!DOCTYPE html>
216- <html lang="en">
217- <head>
218- <meta charset="UTF-8">
219- <meta name="viewport" content="width=device-width, initial-scale=1.0">
220- <title>Project Nexus MCP - Coverage Reports</title>
221- <meta http-equiv="refresh" content="0; url=./coverage/lcov-report/">
222- <style>
223- body {
224- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
225- max-width: 800px;
226- margin: 2rem auto;
227- padding: 2rem;
228- line-height: 1.6;
229- }
230- .header {
231- text-align: center;
232- margin-bottom: 2rem;
233- }
234- .coverage-badge {
235- display: inline-block;
236- padding: 0.5rem 1rem;
237- background: #28a745;
238- color: white;
239- border-radius: 4px;
240- text-decoration: none;
241- font-weight: bold;
242- }
243- .links {
244- margin-top: 2rem;
245- }
246- .links a {
247- display: inline-block;
248- margin: 0.5rem 1rem 0.5rem 0;
249- padding: 0.5rem 1rem;
250- background: #007bff;
251- color: white;
252- text-decoration: none;
253- border-radius: 4px;
254- }
255- .links a:hover {
256- background: #0056b3;
257- }
258- </style>
259- </head>
260- <body>
261- <div class="header">
262- <h1>Project Nexus MCP</h1>
263- <h2>Test Coverage Reports</h2>
264- <div class="coverage-badge">
265- Coverage: ${{ steps.coverage.outputs.percentage }}%
266- </div>
267- </div>
268-
269- <p>You will be automatically redirected to the coverage report. If not, click the link below:</p>
270-
271- <div class="links">
272- <a href="./coverage/lcov-report/">📊 View Coverage Report</a>
273- <a href="https://github.com/structured-world/gitlab-mcp">📁 View Source Code</a>
274- <a href="https://github.com/structured-world/gitlab-mcp/actions">🔄 View CI/CD</a>
275- </div>
276-
277- <div style="margin-top: 2rem; padding: 1rem; background: #f8f9fa; border-radius: 4px;">
278- <h3>About This Report</h3>
279- <p>This coverage report is automatically generated from the latest tests on the main branch.
280- It shows which parts of the codebase are covered by tests and helps identify areas that may need additional testing.</p>
281-
282- <p><strong>Last Updated:</strong> ${{ steps.coverage.outputs.timestamp || 'Unknown' }}</p>
283- <p><strong>Generated by:</strong> Jest + GitHub Actions</p>
284- </div>
285- </body>
286- </html>
287- EOF
288-
289- # Add timestamp
290- echo "timestamp=$(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_OUTPUT
291-
292- # Step 10: Determine if we should deploy (only on main branch pushes)
293- - name : Determine deployment
294- id : should-deploy
295- run : |
296- if [[ "${{ github.ref }}" == "refs/heads/main" && "${{ github.event_name }}" == "push" ]]; then
297- echo "result=true" >> $GITHUB_OUTPUT
298- else
299- echo "result=false" >> $GITHUB_OUTPUT
300- fi
301-
302- # Step 11: Upload coverage artifacts for PR preview
303115 - name : Upload coverage artifacts
304- uses : actions/upload-artifact@v6
116+ uses : actions/upload-artifact@v4
305117 with :
306118 name : coverage-reports-${{ github.sha }}
307- path : pages-deploy/
308- retention-days : 30
309-
310- # Step 12: Upload pages artifact (for deployment)
311- - name : Upload Pages artifact
312- if : steps.should-deploy.outputs.result == 'true'
313- uses : actions/upload-pages-artifact@v4
314- with :
315- path : pages-deploy/
316-
317- # Job 2: Deploy to GitHub Pages (only on main branch)
318- deploy :
319- name : Deploy to GitHub Pages
320- runs-on : ubuntu-latest
321- needs : coverage
322- if : needs.coverage.outputs.should-deploy == 'true'
323-
324- # Deploy to the github-pages environment
325- environment :
326- name : github-pages
327- url : ${{ steps.deployment.outputs.page_url }}
328-
329- steps :
330- # Step 1: Deploy to GitHub Pages
331- - name : Deploy to GitHub Pages
332- id : deployment
333- uses : actions/deploy-pages@v4
334-
335- # Step 2: Create deployment summary
336- - name : Create deployment summary
337- run : |
338- echo "## 🚀 Coverage Report Deployed" >> $GITHUB_STEP_SUMMARY
339- echo "" >> $GITHUB_STEP_SUMMARY
340- echo "**Coverage Percentage:** ${{ needs.coverage.outputs.coverage-percentage }}%" >> $GITHUB_STEP_SUMMARY
341- echo "" >> $GITHUB_STEP_SUMMARY
342- echo "**📊 Live Coverage Report:** ${{ steps.deployment.outputs.page_url }}" >> $GITHUB_STEP_SUMMARY
343- echo "" >> $GITHUB_STEP_SUMMARY
344- echo "The coverage report has been successfully deployed and is now available at the link above." >> $GITHUB_STEP_SUMMARY
345- echo "" >> $GITHUB_STEP_SUMMARY
346- echo "### Quick Links" >> $GITHUB_STEP_SUMMARY
347- echo "- [Coverage Report](${{ steps.deployment.outputs.page_url }})" >> $GITHUB_STEP_SUMMARY
348- echo "- [Detailed Coverage](${{ steps.deployment.outputs.page_url }}coverage/lcov-report/)" >> $GITHUB_STEP_SUMMARY
349- echo "- [Repository](https://github.com/${{ github.repository }})" >> $GITHUB_STEP_SUMMARY
119+ path : coverage/
120+ retention-days : 14
0 commit comments