Skip to content

Commit 61ba4cf

Browse files
[CERT-222] Export Bruno SIS artifacts (#112)
* [CERT-222] Workflow update - export Bruno artifacts * docs: Added artifact readme documentation * Update deprecated dependencies versions * Update other workflows deprecated dependencies * Adds per-commit artifact pre-release * Adds on Mondays artifacts cleanup workflow - for commit tags only * fix: commit tag name prefixed * Release artifacts title and description updated * fix: fix commit prerelease pattern and add detailed logging
1 parent 0ede272 commit 61ba4cf

9 files changed

+515
-18
lines changed

.github/workflows/on-issue-opened.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
steps:
2020
- name: Check if user has repository access
2121
id: check-repo-access
22-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
22+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
2323
with:
2424
script: |
2525
try {
@@ -39,7 +39,7 @@ jobs:
3939
4040
- name: Close community issue with helpful message
4141
if: steps.check-repo-access.outputs.result == 'false'
42-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
42+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
4343
with:
4444
script: |
4545
const message = `Thank you for your interest in Ed-Fi!
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
name: Publish SIS Artifact
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
tags:
7+
- 'v*'
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: write
12+
13+
concurrency:
14+
group: sis-artifact-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
publish-sis-artifact:
19+
name: Build, Validate, and Publish SIS Artifact
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
24+
25+
- name: Use Node.js 24
26+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
27+
with:
28+
node-version: 24
29+
cache: 'npm'
30+
cache-dependency-path: bruno/package-lock.json
31+
32+
- name: Derive SIS metadata
33+
id: meta
34+
shell: bash
35+
run: |
36+
set -euo pipefail
37+
38+
SHORT_SHA="${GITHUB_SHA::7}"
39+
BUILD_TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
40+
41+
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
42+
IS_TAG="true"
43+
ARTIFACT_VERSION="${GITHUB_REF#refs/tags/}"
44+
SOURCE_REF="${GITHUB_REF#refs/tags/}"
45+
TARGET_DOWNLOAD_REF="${ARTIFACT_VERSION}"
46+
else
47+
IS_TAG="false"
48+
ARTIFACT_VERSION="0.0.0-main.${GITHUB_RUN_NUMBER}+${SHORT_SHA}"
49+
SOURCE_REF="${GITHUB_REF_NAME}"
50+
# GitHub prohibits release tags that are bare 40-hex-char strings,
51+
# so prefix with "commit-" to produce a valid tag name.
52+
TARGET_DOWNLOAD_REF="commit-${GITHUB_SHA}"
53+
fi
54+
55+
BASE_NAME="sis-${ARTIFACT_VERSION}"
56+
ZIP_NAME="${BASE_NAME}.zip"
57+
SHA_NAME="${BASE_NAME}.zip.sha256"
58+
META_NAME="sis-${TARGET_DOWNLOAD_REF}.metadata.json"
59+
60+
echo "artifactVersion=${ARTIFACT_VERSION}" >> "$GITHUB_OUTPUT"
61+
echo "buildTimestamp=${BUILD_TIMESTAMP}" >> "$GITHUB_OUTPUT"
62+
echo "sourceRef=${SOURCE_REF}" >> "$GITHUB_OUTPUT"
63+
echo "isTag=${IS_TAG}" >> "$GITHUB_OUTPUT"
64+
echo "targetDownloadRef=${TARGET_DOWNLOAD_REF}" >> "$GITHUB_OUTPUT"
65+
echo "zipName=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
66+
echo "shaName=${SHA_NAME}" >> "$GITHUB_OUTPUT"
67+
echo "metaName=${META_NAME}" >> "$GITHUB_OUTPUT"
68+
69+
echo "--- SIS Artifact Metadata ---"
70+
echo "artifactVersion : ${ARTIFACT_VERSION}"
71+
echo "targetDownloadRef : ${TARGET_DOWNLOAD_REF}"
72+
echo "zipName : ${ZIP_NAME}"
73+
echo "metaName : ${META_NAME}"
74+
echo "buildTimestamp : ${BUILD_TIMESTAMP}"
75+
echo "sourceRef : ${SOURCE_REF}"
76+
echo "isTag : ${IS_TAG}"
77+
78+
- name: Stage minimal content
79+
shell: bash
80+
run: |
81+
set -euo pipefail
82+
83+
rm -rf stage dist validation
84+
mkdir -p stage/bruno dist
85+
86+
cp -R bruno/SIS stage/bruno/
87+
cp bruno/package.json stage/bruno/
88+
cp bruno/package-lock.json stage/bruno/
89+
cp -R bruno/scripts stage/bruno/
90+
cp -R bruno/schemas stage/bruno/
91+
cp -R bruno/templates stage/bruno/
92+
cp bruno/README-User-Manual.md stage/bruno/
93+
cp bruno/README-SIS-Artifact.md stage/bruno/
94+
cp bruno/README-SIS-Artifact-How-to-use-in-AdminApp.md stage/bruno/
95+
cp bruno/README-SIS-Artifact-How-to-use-in-Bruno-CLI.md stage/bruno/
96+
97+
# Defensive exclusions in case local cache artifacts are present.
98+
find stage -type d -name node_modules -prune -exec rm -rf {} +
99+
find stage -type d -name .git -prune -exec rm -rf {} +
100+
find stage -type f \( -name .DS_Store -o -name Thumbs.db \) -delete
101+
102+
- name: Build ZIP and checksum
103+
id: package
104+
shell: bash
105+
run: |
106+
set -euo pipefail
107+
108+
ZIP_NAME='${{ steps.meta.outputs.zipName }}'
109+
SHA_NAME='${{ steps.meta.outputs.shaName }}'
110+
111+
(
112+
cd stage
113+
zip -r "../dist/${ZIP_NAME}" bruno -x "**/node_modules/**" "**/.git/**"
114+
)
115+
116+
(
117+
cd dist
118+
sha256sum "${ZIP_NAME}" > "${SHA_NAME}"
119+
SHA_VALUE="$(cut -d ' ' -f1 "${SHA_NAME}")"
120+
echo "sha256=${SHA_VALUE}" >> "$GITHUB_OUTPUT"
121+
)
122+
123+
- name: Write metadata file
124+
shell: bash
125+
run: |
126+
set -euo pipefail
127+
128+
cat > "dist/${{ steps.meta.outputs.metaName }}" <<EOF
129+
{
130+
"artifactVersion": "${{ steps.meta.outputs.artifactVersion }}",
131+
"gitCommitSha": "${GITHUB_SHA}",
132+
"buildTimestamp": "${{ steps.meta.outputs.buildTimestamp }}",
133+
"sourceRef": "${{ steps.meta.outputs.sourceRef }}",
134+
"zipFileName": "${{ steps.meta.outputs.zipName }}",
135+
"sha256": "${{ steps.package.outputs.sha256 }}"
136+
}
137+
EOF
138+
139+
- name: Contract validation (checksum, extract, required paths)
140+
shell: bash
141+
run: |
142+
set -euo pipefail
143+
144+
ZIP_NAME='${{ steps.meta.outputs.zipName }}'
145+
SHA_NAME='${{ steps.meta.outputs.shaName }}'
146+
147+
(
148+
cd dist
149+
sha256sum --check "${SHA_NAME}"
150+
)
151+
152+
mkdir -p validation/extracted
153+
unzip -q "dist/${ZIP_NAME}" -d validation/extracted
154+
155+
test -d validation/extracted/bruno/SIS
156+
test -f validation/extracted/bruno/package.json
157+
test -f validation/extracted/bruno/package-lock.json
158+
test -d validation/extracted/bruno/scripts
159+
test -d validation/extracted/bruno/schemas
160+
test -d validation/extracted/bruno/templates
161+
test -f validation/extracted/bruno/README-User-Manual.md
162+
test -f validation/extracted/bruno/README-SIS-Artifact.md
163+
test -f validation/extracted/bruno/README-SIS-Artifact-How-to-use-in-AdminApp.md
164+
test -f validation/extracted/bruno/README-SIS-Artifact-How-to-use-in-Bruno-CLI.md
165+
166+
- name: Consumer validation smoke test (npm ci)
167+
shell: bash
168+
run: |
169+
set -euo pipefail
170+
cd validation/extracted/bruno
171+
npm ci --no-audit --no-fund
172+
173+
- name: Upload workflow artifact (main and tags)
174+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
175+
with:
176+
name: sis-${{ steps.meta.outputs.artifactVersion }}
177+
path: |
178+
dist/${{ steps.meta.outputs.zipName }}
179+
dist/${{ steps.meta.outputs.shaName }}
180+
dist/${{ steps.meta.outputs.metaName }}
181+
if-no-files-found: error
182+
183+
- name: Publish GitHub Release assets (tags and main)
184+
env:
185+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
186+
shell: bash
187+
run: |
188+
set -euo pipefail
189+
190+
IS_TAG='${{ steps.meta.outputs.isTag }}'
191+
RELEASE_TAG='${{ steps.meta.outputs.targetDownloadRef }}'
192+
ZIP_PATH="dist/${{ steps.meta.outputs.zipName }}"
193+
SHA_PATH="dist/${{ steps.meta.outputs.shaName }}"
194+
META_PATH="dist/${{ steps.meta.outputs.metaName }}"
195+
196+
echo "--- Publishing GitHub Release Assets ---"
197+
echo "releaseTag : ${RELEASE_TAG}"
198+
echo "isTag : ${IS_TAG}"
199+
echo "zipAsset : ${{ steps.meta.outputs.zipName }}"
200+
echo "shaAsset : ${{ steps.meta.outputs.shaName }}"
201+
echo "metaAsset : ${{ steps.meta.outputs.metaName }}"
202+
203+
if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then
204+
echo "Release '${RELEASE_TAG}' already exists — uploading assets (clobber)."
205+
gh release upload "${RELEASE_TAG}" "${ZIP_PATH}" "${SHA_PATH}" "${META_PATH}" --clobber
206+
else
207+
echo "Release '${RELEASE_TAG}' does not exist — creating and uploading assets."
208+
if [[ "${IS_TAG}" == "true" ]]; then
209+
gh release create "${RELEASE_TAG}" "${ZIP_PATH}" "${SHA_PATH}" "${META_PATH}" \
210+
--title "${RELEASE_TAG}" \
211+
--notes "SIS artifact release for ${RELEASE_TAG}."
212+
else
213+
gh release create "${RELEASE_TAG}" "${ZIP_PATH}" "${SHA_PATH}" "${META_PATH}" \
214+
--title "${RELEASE_TAG:0:20}" \
215+
--notes "Automated SIS artifact for ${RELEASE_TAG}." \
216+
--prerelease
217+
fi
218+
fi
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Cleanup Old Commit Prereleases
2+
3+
on:
4+
schedule:
5+
- cron: '0 6 * * 1' # every Monday at 06:00 UTC
6+
workflow_dispatch:
7+
inputs:
8+
keep_count:
9+
description: 'Number of newest prereleases to keep'
10+
default: '5'
11+
required: false
12+
13+
permissions:
14+
contents: write
15+
16+
jobs:
17+
cleanup:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Delete old commit prereleases (keep last 5, preserve tagged releases)
21+
env:
22+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23+
KEEP: ${{ inputs.keep_count || '5' }}
24+
shell: bash
25+
run: |
26+
set -euo pipefail
27+
28+
echo "--- Cleanup Configuration ---"
29+
echo "repository : ${GITHUB_REPOSITORY}"
30+
echo "keep_count : ${KEEP}"
31+
echo "trigger : ${GITHUB_EVENT_NAME}"
32+
33+
echo "--- Fetching release list ---"
34+
ALL_RELEASES="$(gh release list --repo "$GITHUB_REPOSITORY" --limit 200 --json tagName,isPrerelease,createdAt)"
35+
TOTAL="$(echo "${ALL_RELEASES}" | jq 'length')"
36+
echo "Total releases found : ${TOTAL}"
37+
38+
CANDIDATES="$(echo "${ALL_RELEASES}" | jq -r --argjson keep "$KEEP" '
39+
[.[]
40+
| select(.isPrerelease == true)
41+
| select(.tagName | test("^commit-[0-9a-f]{40}$")) # commit-<sha> tags only
42+
]
43+
| sort_by(.createdAt)')"
44+
45+
CANDIDATE_COUNT="$(echo "${CANDIDATES}" | jq 'length')"
46+
echo "Commit prereleases found : ${CANDIDATE_COUNT}"
47+
echo "Will keep newest : ${KEEP}"
48+
49+
TO_DELETE="$(echo "${CANDIDATES}" | jq -r --argjson keep "$KEEP" '.[:-$keep] | .[].tagName')"
50+
DELETE_COUNT="$(echo "${TO_DELETE}" | grep -c . || true)"
51+
52+
if [[ -z "${TO_DELETE}" ]]; then
53+
echo "--- Nothing to delete (${CANDIDATE_COUNT} prerelease(s) found, keep=${KEEP}) ---"
54+
exit 0
55+
fi
56+
57+
echo "--- Prereleases to delete: ${DELETE_COUNT} ---"
58+
echo "${TO_DELETE}"
59+
60+
echo "--- Deleting ---"
61+
DELETED=0
62+
FAILED=0
63+
while IFS= read -r tag; do
64+
echo "[${DELETED}/${DELETE_COUNT}] Deleting release and tag: ${tag}"
65+
if gh release delete "$tag" --repo "$GITHUB_REPOSITORY" --yes; then
66+
git push origin --delete "refs/tags/$tag" 2>/dev/null \
67+
&& echo " Tag ref deleted from remote." \
68+
|| echo " Tag ref not on remote or already gone — skipping."
69+
DELETED=$((DELETED + 1))
70+
else
71+
echo " WARNING: Failed to delete release '${tag}' — skipping."
72+
FAILED=$((FAILED + 1))
73+
fi
74+
done <<< "${TO_DELETE}"
75+
76+
echo "--- Cleanup Summary ---"
77+
echo "Deleted : ${DELETED}"
78+
echo "Failed : ${FAILED}"
79+
echo "Kept : $((CANDIDATE_COUNT - DELETED))"
80+
if [[ "${FAILED}" -gt 0 ]]; then
81+
echo "ERROR: ${FAILED} deletion(s) failed."
82+
exit 1
83+
fi
84+
85+
86+

.github/workflows/on-pullrequest-lint-bruno.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ jobs:
2424
working-directory: ./bruno
2525
steps:
2626
- name: Checkout
27-
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
27+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2828

29-
- name: Use Node.js 20
30-
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 #6.0.0
29+
- name: Use Node.js 24
30+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
3131
with:
32-
node-version: 20
32+
node-version: 24
3333
cache: 'npm'
3434
cache-dependency-path: bruno/package-lock.json
3535

@@ -49,7 +49,7 @@ jobs:
4949
fi
5050
5151
- name: Upload lint report artifact
52-
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 #v5.0.0
52+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
5353
with:
5454
name: bruno-lint-report
5555
path: bruno/lint-report.json

.github/workflows/on-pullrequest-run-tests.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ jobs:
3838
runs-on: ubuntu-latest
3939
steps:
4040
- name: Checkout
41-
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
41+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
4242

43-
- name: Use Node.js 20
44-
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 #6.0.0
43+
- name: Use Node.js 24
44+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
4545
with:
46-
node-version: 20
46+
node-version: 24
4747
cache: 'npm'
4848
cache-dependency-path: bruno/package-lock.json
4949

@@ -81,7 +81,7 @@ jobs:
8181
8282
- name: Upload QA artifacts
8383
if: always()
84-
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 #v5.0.0
84+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
8585
with:
8686
name: qa-scenarios-results
8787
path: |

0 commit comments

Comments
 (0)