99permissions :
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
1318jobs :
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') }}
0 commit comments