Skip to content

Commit f411712

Browse files
committed
build: use secure two-workflow pattern for PR test coverage
--- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: na - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: passed - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent c17b411 commit f411712

File tree

3 files changed

+413
-118
lines changed

3 files changed

+413
-118
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
#/
2+
# @license Apache-2.0
3+
#
4+
# Copyright (c) 2025 The Stdlib Authors.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#/
18+
19+
# Workflow name:
20+
name: publish_coverage_pr
21+
22+
# Workflow triggers:
23+
on:
24+
workflow_run:
25+
workflows:
26+
- run_tests_coverage_pr
27+
types:
28+
- completed
29+
30+
# Global permissions:
31+
permissions:
32+
# Allow read-only access to the repository contents:
33+
contents: read
34+
35+
# Workflow jobs:
36+
jobs:
37+
38+
# Define a job for publishing coverage results...
39+
publish:
40+
41+
# Define a display name:
42+
name: 'Publish PR coverage results'
43+
44+
# Only run if the triggering workflow succeeded:
45+
if: github.event.workflow_run.conclusion == 'success'
46+
47+
# Define the type of virtual host machine:
48+
runs-on: ubuntu-latest
49+
50+
# Define the sequence of job steps...
51+
steps:
52+
# Download PR metadata artifact:
53+
- name: 'Download PR metadata'
54+
id: download-metadata
55+
# Pin action to full length commit SHA
56+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
57+
with:
58+
name: pr-metadata
59+
path: pr-metadata/
60+
run-id: ${{ github.event.workflow_run.id }}
61+
github-token: ${{ secrets.GITHUB_TOKEN }}
62+
continue-on-error: true
63+
64+
# Read PR metadata:
65+
- name: 'Read PR metadata'
66+
id: pr-metadata
67+
if: steps.download-metadata.outcome == 'success'
68+
run: |
69+
pr_number=$(cat pr-metadata/pr_number)
70+
report=$(cat pr-metadata/report)
71+
echo "pr_number=$pr_number" >> $GITHUB_OUTPUT
72+
echo "report=$report" >> $GITHUB_OUTPUT
73+
74+
# Post report as comment to PR:
75+
- name: 'Post report as comment to PR'
76+
if: steps.download-metadata.outcome == 'success'
77+
# Pin action to full length commit SHA
78+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
79+
with:
80+
github-token: ${{ secrets.STDLIB_BOT_PAT_REPO_WRITE }}
81+
script: |
82+
const prNumber = parseInt('${{ steps.pr-metadata.outputs.pr_number }}');
83+
const { data: comments } = await github.rest.issues.listComments({
84+
'issue_number': prNumber,
85+
'owner': context.repo.owner,
86+
'repo': context.repo.repo,
87+
});
88+
89+
const botComment = comments.find( comment => comment.user.login === 'stdlib-bot' && comment.body.includes( '## Coverage Report' ) );
90+
if ( botComment ) {
91+
await github.rest.issues.updateComment({
92+
'owner': context.repo.owner,
93+
'repo': context.repo.repo,
94+
'comment_id': botComment.id,
95+
'body': `${{ steps.pr-metadata.outputs.report }}`
96+
});
97+
} else {
98+
await github.rest.issues.createComment({
99+
'issue_number': prNumber,
100+
'owner': context.repo.owner,
101+
'repo': context.repo.repo,
102+
'body': `${{ steps.pr-metadata.outputs.report }}`
103+
});
104+
}
105+
106+
# Download coverage artifacts:
107+
- name: 'Download coverage artifacts'
108+
id: download-coverage
109+
# Pin action to full length commit SHA
110+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
111+
with:
112+
name: coverage-artifacts
113+
path: artifacts/
114+
run-id: ${{ github.event.workflow_run.id }}
115+
github-token: ${{ secrets.GITHUB_TOKEN }}
116+
continue-on-error: true
117+
118+
# Checkout coverage repository:
119+
- name: 'Checkout coverage repository'
120+
if: steps.download-coverage.outcome == 'success'
121+
# Pin action to full length commit SHA
122+
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
123+
with:
124+
# Code coverage repository:
125+
repository: 'stdlib-js/www-test-code-coverage'
126+
127+
# File path to checkout to:
128+
path: './www-test-code-coverage'
129+
130+
# Specify whether to remove untracked files before checking out the repository:
131+
clean: false
132+
133+
# Limit clone depth to the most recent commit:
134+
fetch-depth: 1
135+
136+
# Token for accessing the repository:
137+
token: ${{ secrets.STDLIB_BOT_FGPAT_REPO_READ }}
138+
139+
# Avoid storing GitHub token in local Git configuration:
140+
persist-credentials: false
141+
142+
# Checkout coverage repository branch for PR:
143+
- name: 'Checkout coverage repository branch'
144+
if: steps.download-coverage.outcome == 'success'
145+
run: |
146+
cd ./www-test-code-coverage
147+
BRANCH_NAME="pr-${{ steps.pr-metadata.outputs.pr_number }}"
148+
git fetch origin $BRANCH_NAME || true
149+
git checkout $BRANCH_NAME || git checkout -b $BRANCH_NAME
150+
151+
# Remove all directories except .github and .git from branch:
152+
find . -mindepth 1 -maxdepth 1 -type d -not -name '.github' -not -name '.git' -exec git rm -rf {} + || true
153+
154+
# Copy artifacts to the repository:
155+
- name: 'Copy artifacts to the repository'
156+
if: steps.download-coverage.outcome == 'success'
157+
run: |
158+
if [ -d "./artifacts" ]; then
159+
cp -R ./artifacts/* ./www-test-code-coverage
160+
161+
# Get commit SHA and timestamp from the workflow run:
162+
commit_sha="${{ github.event.workflow_run.head_sha }}"
163+
commit_timestamp=$(date -u +"%Y-%m-%d %H:%M:%S")
164+
165+
# Append coverage to ndjson files:
166+
files=$(find ./artifacts -name 'index.html')
167+
for file in $files; do
168+
file=${file//artifacts/www-test-code-coverage}
169+
coverage=$(echo -n '['; grep -oP "(?<=class='fraction'>)[0-9]+/[0-9]+" $file | awk -F/ '{ if ($2 != 0) print $1 "," $2 "," ($1/$2)*100; else print $1 "," $2 ",100" }' | tr '\n' ',' | sed 's/,$//'; echo -n ",\"$commit_sha\",\"$commit_timestamp\"]")
170+
echo $coverage >> $(dirname $file)/coverage.ndjson
171+
done
172+
else
173+
echo "The artifacts directory does not exist."
174+
fi
175+
176+
# Import GPG key to sign commits:
177+
- name: 'Import GPG key to sign commits'
178+
if: steps.download-coverage.outcome == 'success'
179+
# Pin action to full length commit SHA
180+
uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
181+
with:
182+
gpg_private_key: ${{ secrets.STDLIB_BOT_GPG_PRIVATE_KEY }}
183+
passphrase: ${{ secrets.STDLIB_BOT_GPG_PASSPHRASE }}
184+
git_user_signingkey: true
185+
git_commit_gpgsign: true
186+
187+
# Commit and push changes:
188+
- name: 'Commit and push changes'
189+
if: steps.download-coverage.outcome == 'success'
190+
env:
191+
REPO_GITHUB_TOKEN: ${{ secrets.STDLIB_BOT_PAT_REPO_WRITE }}
192+
USER_NAME: stdlib-bot
193+
run: |
194+
cd ./www-test-code-coverage
195+
BRANCH_NAME="pr-${{ steps.pr-metadata.outputs.pr_number }}"
196+
git config --local user.email "82920195+stdlib-bot@users.noreply.github.com"
197+
git config --local user.name "stdlib-bot"
198+
git add .
199+
git commit -m "Update artifacts" || exit 0
200+
git push "https://$USER_NAME:$REPO_GITHUB_TOKEN@github.com/stdlib-js/www-test-code-coverage.git" $BRANCH_NAME

0 commit comments

Comments
 (0)