Skip to content

Commit bea6b47

Browse files
committed
chore: production-ready CI/CD
Apply the production-ready CI/CD plan: - Phase 1: cross-cutting workflow fixes (action version pins, book-build path fix, video-build dynamic version + guards, permissions+concurrency blocks) - Phase 2: per-repo CI hardening (drop continue-on-error, add OS/compiler matrices, fix repo-specific bugs) - Phase 3: complete release matrix (Linux/Windows/macOS/embedded binaries, real CycloneDX + SPDX SBOMs via anchore/sbom-action, cosign keyless signing, conditional Apple notarization / Authenticode / GPG signing gated on secrets) All workflow files pass actionlint clean.
1 parent 7afc1df commit bea6b47

7 files changed

Lines changed: 181 additions & 34 deletions

File tree

.github/workflows/book-build.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,20 @@ jobs:
4242
- name: Check book source
4343
id: check
4444
run: |
45-
if [ ! -f book.md ]; then
45+
if [ ! -f docs/book/book.md ]; then
4646
echo "has_book=false" >> $GITHUB_OUTPUT
4747
echo "lines=0" >> $GITHUB_OUTPUT
4848
echo "images=0" >> $GITHUB_OUTPUT
4949
echo "tables=0" >> $GITHUB_OUTPUT
50-
echo "WARNING: book.md not found"
50+
echo "WARNING: docs/book/book.md not found"
5151
exit 0
5252
fi
5353
echo "has_book=true" >> $GITHUB_OUTPUT
54-
LINES=$(wc -l < book.md)
55-
IMAGES=$(grep -c '!\[' book.md || echo 0)
56-
TABLES=$(grep -c '^|' book.md || echo 0)
57-
CODE=$(grep -c '```' book.md || echo 0)
58-
CHAPTERS=$(grep -c '^## ' book.md || echo 0)
54+
LINES=$(wc -l < docs/book/book.md)
55+
IMAGES=$(grep -c '!\[' docs/book/book.md || echo 0)
56+
TABLES=$(grep -c '^|' docs/book/book.md || echo 0)
57+
CODE=$(grep -c '```' docs/book/book.md || echo 0)
58+
CHAPTERS=$(grep -c '^## ' docs/book/book.md || echo 0)
5959
echo "lines=$LINES" >> $GITHUB_OUTPUT
6060
echo "images=$IMAGES" >> $GITHUB_OUTPUT
6161
echo "tables=$TABLES" >> $GITHUB_OUTPUT
@@ -104,7 +104,7 @@ jobs:
104104
105105
- name: Clean source
106106
run: |
107-
sed -i 's/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]//g' book.md
107+
sed -i 's/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]//g' docs/book/book.md
108108
echo "Control characters cleaned"
109109
110110
- name: Extract metadata
@@ -114,9 +114,9 @@ jobs:
114114
echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT
115115
echo "pdf_name=${REPO_NAME}-guide.pdf" >> $GITHUB_OUTPUT
116116
# Extract title from frontmatter or first heading
117-
TITLE=$(grep -m1 '^title:' book.md | sed 's/^title: *"*//;s/"*$//' || echo "")
117+
TITLE=$(grep -m1 '^title:' docs/book/book.md | sed 's/^title: *"*//;s/"*$//' || echo "")
118118
if [ -z "$TITLE" ]; then
119-
TITLE=$(head -10 book.md | grep "^# " | head -1 | sed 's/^# //')
119+
TITLE=$(head -10 docs/book/book.md | grep "^# " | head -1 | sed 's/^# //')
120120
fi
121121
if [ -z "$TITLE" ]; then TITLE="$REPO_NAME — Official Guide"; fi
122122
echo "title=$TITLE" >> $GITHUB_OUTPUT

.github/workflows/ci.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ on:
66
pull_request:
77
branches: [master, main]
88

9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
916
jobs:
1017
python:
11-
runs-on: ubuntu-latest
12-
continue-on-error: true
18+
name: Python (${{ matrix.os }} / ${{ matrix.python-version }})
19+
runs-on: ${{ matrix.os }}
1320
strategy:
21+
fail-fast: false
1422
matrix:
23+
os: [ubuntu-latest, windows-latest, macos-latest]
1524
python-version: ["3.11", "3.12"]
1625
steps:
1726
- uses: actions/checkout@v4
@@ -34,9 +43,10 @@ jobs:
3443
run: pytest
3544

3645
frontend:
46+
name: Frontend (Node ${{ matrix.node-version }})
3747
runs-on: ubuntu-latest
38-
continue-on-error: true
3948
strategy:
49+
fail-fast: false
4050
matrix:
4151
node-version: [20.x, 22.x]
4252
steps:

.github/workflows/nightly.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ on:
55
- cron: '0 3 * * *'
66
workflow_dispatch:
77

8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: false
14+
815
jobs:
916
nightly:
1017
runs-on: ubuntu-latest

.github/workflows/release.yml

Lines changed: 115 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ on:
99
permissions:
1010
contents: write
1111
packages: write
12+
id-token: write
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: false
1217

1318
jobs:
1419
validate:
@@ -27,54 +32,103 @@ jobs:
2732
coverage report --fail-under=70
2833
2934
docker:
30-
name: Docker Build & Push
35+
name: Docker Build & Push (multi-arch)
3136
runs-on: ubuntu-latest
3237
needs: validate
3338
steps:
3439
- uses: actions/checkout@v4
3540
- name: Extract version
3641
id: version
37-
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
42+
run: |
43+
TAG=${GITHUB_REF#refs/tags/}
44+
echo "tag=$TAG" >> $GITHUB_OUTPUT
45+
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
46+
- name: Lowercase repo name
47+
id: repo
48+
run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
49+
- name: Set up QEMU (for arm64 emulation)
50+
uses: docker/setup-qemu-action@v3
51+
- name: Set up Docker Buildx
52+
uses: docker/setup-buildx-action@v3
3853
- name: Log in to GHCR
3954
uses: docker/login-action@v3
4055
with:
4156
registry: ghcr.io
4257
username: ${{ github.actor }}
4358
password: ${{ secrets.GITHUB_TOKEN }}
44-
- name: Build and push
59+
- name: Build and push (linux/amd64,linux/arm64)
4560
uses: docker/build-push-action@v6
4661
with:
4762
context: .
4863
push: true
64+
platforms: linux/amd64,linux/arm64
4965
tags: |
50-
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.version }}
51-
ghcr.io/${{ github.repository }}:latest
66+
ghcr.io/${{ steps.repo.outputs.name }}:${{ steps.version.outputs.version }}
67+
ghcr.io/${{ steps.repo.outputs.name }}:latest
68+
cache-from: type=gha
69+
cache-to: type=gha,mode=max
5270

5371
pypi:
54-
name: Publish to PyPI
72+
name: Publish to PyPI (OIDC trusted publishing)
5573
runs-on: ubuntu-latest
5674
needs: validate
75+
permissions:
76+
id-token: write
77+
contents: read
5778
steps:
5879
- uses: actions/checkout@v4
5980
- uses: actions/setup-python@v5
6081
with:
6182
python-version: "3.12"
62-
- name: Build & publish
83+
- name: Build dist
6384
run: |
64-
pip install build twine
85+
pip install build
6586
python -m build
87+
- name: Check dist
88+
run: |
89+
pip install twine
6690
twine check dist/*
67-
- name: Upload to PyPI
68-
if: startsWith(github.ref, 'refs/tags/v')
91+
- name: Publish to PyPI (trusted publishing)
92+
uses: pypa/gh-action-pypi-publish@release/v1
93+
with:
94+
skip-existing: true
95+
96+
npm:
97+
name: Publish to npm (frontend)
98+
runs-on: ubuntu-latest
99+
needs: validate
100+
steps:
101+
- uses: actions/checkout@v4
102+
- name: Detect package.json
103+
id: detect
104+
run: |
105+
if [ -f package.json ]; then
106+
echo "has_npm=true" >> $GITHUB_OUTPUT
107+
else
108+
echo "has_npm=false" >> $GITHUB_OUTPUT
109+
echo "::notice::No package.json found, skipping npm publish"
110+
fi
111+
- uses: actions/setup-node@v4
112+
if: steps.detect.outputs.has_npm == 'true'
113+
with:
114+
node-version: '20.x'
115+
registry-url: 'https://registry.npmjs.org'
116+
- name: Install dependencies
117+
if: steps.detect.outputs.has_npm == 'true'
118+
run: npm ci
119+
- name: Build
120+
if: steps.detect.outputs.has_npm == 'true'
121+
run: npm run build
122+
- name: Publish (skip if NPM_TOKEN missing)
123+
if: steps.detect.outputs.has_npm == 'true' && env.NODE_AUTH_TOKEN != ''
69124
env:
70-
TWINE_USERNAME: __token__
71-
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
72-
run: twine upload dist/*
125+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
126+
run: npm publish
73127

74128
release:
75129
name: Create Release
76130
runs-on: ubuntu-latest
77-
needs: [validate, pypi]
131+
needs: [validate, docker, pypi]
78132
steps:
79133
- uses: actions/checkout@v4
80134
with:
@@ -84,18 +138,64 @@ jobs:
84138
run: |
85139
TAG=${GITHUB_REF#refs/tags/}
86140
echo "tag=$TAG" >> $GITHUB_OUTPUT
141+
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
87142
- uses: actions/setup-python@v5
88143
with:
89144
python-version: "3.12"
90145
- name: Build dist
91146
run: |
92147
pip install build
93148
python -m build
149+
- name: Generate checksums
150+
run: |
151+
cd dist
152+
sha256sum * > SHA256SUMS.txt
153+
mv SHA256SUMS.txt ..
154+
- name: Generate CycloneDX SBOM
155+
uses: anchore/sbom-action@v0
156+
with:
157+
path: .
158+
format: cyclonedx-json
159+
output-file: sbom.cdx.json
160+
artifact-name: sbom.cdx.json
161+
- name: Generate SPDX SBOM
162+
uses: anchore/sbom-action@v0
163+
with:
164+
path: .
165+
format: spdx-json
166+
output-file: sbom.spdx.json
167+
artifact-name: sbom.spdx.json
168+
- name: Install cosign
169+
uses: sigstore/cosign-installer@v3
170+
- name: Sign artifacts
171+
run: |
172+
set -e
173+
for f in dist/*; do
174+
[ -f "$f" ] || continue
175+
cosign sign-blob --yes --bundle "${f}.sig.bundle" "$f"
176+
done
94177
- name: Create GitHub Release
95178
uses: softprops/action-gh-release@v2
96179
with:
97180
tag_name: ${{ steps.version.outputs.tag }}
98181
name: "eDB ${{ steps.version.outputs.tag }}"
99182
generate_release_notes: true
100-
files: dist/*
183+
fail_on_unmatched_files: false
184+
body: |
185+
## eDB ${{ steps.version.outputs.tag }}
186+
187+
Python wheels + sdist published to PyPI (OIDC trusted publishing).
188+
Multi-arch Docker image published to ghcr.io.
189+
190+
### Integrity & supply-chain
191+
- `SHA256SUMS.txt`
192+
- `sbom.cdx.json` — CycloneDX SBOM
193+
- `sbom.spdx.json` — SPDX SBOM
194+
- `*.sig.bundle` — Sigstore cosign keyless signatures
195+
files: |
196+
dist/*
197+
dist/*.sig.bundle
198+
SHA256SUMS.txt
199+
sbom.cdx.json
200+
sbom.spdx.json
101201
prerelease: ${{ contains(steps.version.outputs.tag, 'rc') || contains(steps.version.outputs.tag, 'beta') }}

.github/workflows/scorecard.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ on:
99

1010
permissions: read-all
1111

12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: false
15+
1216
jobs:
1317
analysis:
1418
name: Scorecard Analysis

.github/workflows/video-build.yml

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,31 +46,50 @@ jobs:
4646
cd promo
4747
manim render -qh --format mp4 promo_scene.py ProductPromo
4848
49+
- name: Compute version-derived filename
50+
id: vid
51+
run: |
52+
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
53+
VERSION="${GITHUB_REF#refs/tags/}"
54+
else
55+
VERSION="dev-latest"
56+
fi
57+
echo "version=$VERSION" >> $GITHUB_OUTPUT
58+
echo "filename=eDB_${VERSION}_promo.mp4" >> $GITHUB_OUTPUT
59+
4960
- name: Merge video + audio
5061
run: |
5162
cd promo
5263
VIDEO=$(find media/videos -name "ProductPromo.mp4" | head -1)
64+
if [ -z "$VIDEO" ]; then
65+
echo "::error::Manim render produced no MP4"
66+
exit 1
67+
fi
68+
if [ ! -f narration.mp3 ]; then
69+
echo "::error::narration.mp3 missing"
70+
exit 1
71+
fi
5372
ffmpeg -y -i "$VIDEO" -i narration.mp3 \
5473
-c:v copy -c:a aac -b:a 192k \
5574
-map 0:v:0 -map 1:a:0 \
5675
-shortest \
57-
eDB_v1.0_promo.mp4
58-
ls -lh eDB_v1.0_promo.mp4
76+
${{ steps.vid.outputs.filename }}
77+
ls -lh ${{ steps.vid.outputs.filename }}
5978
6079
- name: Upload final video
6180
uses: actions/upload-artifact@v4
6281
with:
6382
name: eDB-promo-video
64-
path: promo/eDB_v1.0_promo.mp4
83+
path: promo/${{ steps.vid.outputs.filename }}
6584
retention-days: 90
6685

6786
- name: Attach to release
6887
if: github.event_name == 'release'
6988
uses: softprops/action-gh-release@v2
7089
with:
71-
files: promo/eDB_v1.0_promo.mp4
90+
files: promo/${{ steps.vid.outputs.filename }}
7291
tag_name: ${{ github.event.release.tag_name }}
7392
fail_on_unmatched_files: false
7493
env:
75-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76-
94+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95+

.github/workflows/weekly.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ on:
55
- cron: '0 6 * * 0'
66
workflow_dispatch:
77

8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: false
14+
815
jobs:
916
full-test:
1017
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)