mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-05-14 17:35:06 +00:00
224 lines
8.5 KiB
YAML
224 lines
8.5 KiB
YAML
# Publish ``reticulum-meshchatx`` sdist and wheel to PyPI (Trusted Publishing).
|
|
# Builds the Vite frontend and offline bundles via the reusable frontend workflow,
|
|
# places them under ``meshchatx/public/``, then runs ``python -m build``.
|
|
#
|
|
# Tag runs also emit SLSA generic provenance (``generator_generic_slsa3``) for the
|
|
# exact ``dist/`` digests and optional Cosign ``*.cosign.bundle`` files next to each
|
|
# distribution (same scripts as ``build-release.yml``; skips if ``COSIGN_PRIVATE_KEY`` unset).
|
|
# PyPI upload uses a staging directory so bundles are not sent to the index.
|
|
#
|
|
# PyPI Trusted Publisher must reference this file as ``pypi.yml`` and use the
|
|
# ``pypi`` GitHub Environment (with required reviewers if you enabled protection).
|
|
#
|
|
# Pinned first-party actions (bump tag and SHA together when upgrading):
|
|
# actions/checkout@v6.0.1 8e8c483db84b4bee98b60c0593521ed34d9990e8
|
|
# actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405
|
|
# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4
|
|
# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0
|
|
#
|
|
# SLSA generator (must stay @vX.Y.Z semver per upstream):
|
|
# slsa-framework/slsa-github-generator/generator_generic_slsa3.yml@v2.1.0
|
|
#
|
|
# Third-party pin (resolve before bumping ``release/v1``):
|
|
# curl -sS "https://api.github.com/repos/pypa/gh-action-pypi-publish/commits/release/v1" | jq -r '.sha'
|
|
# pypa/gh-action-pypi-publish@release/v1 -> cef221092ed1bacb1cc03d23a2d87d1d172e277b
|
|
|
|
name: Publish to PyPI
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
- "!v*-*"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
id-token: write
|
|
|
|
concurrency:
|
|
group: pypi-${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
NODE_OPTIONS: --max-old-space-size=8192
|
|
PYTHON_VERSION: "3.14"
|
|
COSIGN_VERSION: "3.0.6"
|
|
|
|
jobs:
|
|
frontend:
|
|
name: Build frontend artifact
|
|
uses: ./.github/workflows/frontend-build.yml
|
|
permissions:
|
|
contents: read
|
|
with:
|
|
artifact_name: meshchatx-frontend-pypi-${{ github.run_id }}-${{ github.run_attempt }}
|
|
retention_days: 2
|
|
|
|
build:
|
|
name: Build Python distributions
|
|
needs: frontend
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
outputs:
|
|
hashes: ${{ steps.slsa-hashes.outputs.hashes }}
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
env:
|
|
FRONTEND_ARTIFACT_NAME: ${{ needs.frontend.outputs.artifact_name }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
|
|
- name: Download frontend artifact
|
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
|
with:
|
|
name: ${{ env.FRONTEND_ARTIFACT_NAME }}
|
|
path: meshchatx/public
|
|
|
|
- name: Verify frontend bundle in tree
|
|
run: |
|
|
set -euo pipefail
|
|
test -f meshchatx/public/index.html
|
|
test -d meshchatx/public/assets
|
|
test -d meshchatx/public/reticulum-docs-bundled/current
|
|
|
|
- name: Install build
|
|
run: python -m pip install -U pip "build>=1.2.0"
|
|
|
|
- name: Build sdist and wheel
|
|
run: python -m build
|
|
|
|
- name: Verify wheel contains frontend assets
|
|
run: |
|
|
set -euo pipefail
|
|
whl=(dist/*.whl)
|
|
if [[ "${#whl[@]}" -ne 1 ]]; then
|
|
echo "Expected exactly one wheel in dist/" >&2
|
|
ls -la dist >&2
|
|
exit 1
|
|
fi
|
|
export WHEEL_PATH="${whl[0]}"
|
|
python - <<'PY'
|
|
import os
|
|
import sys
|
|
import zipfile
|
|
|
|
path = os.environ["WHEEL_PATH"]
|
|
with zipfile.ZipFile(path) as zf:
|
|
names = zf.namelist()
|
|
if "meshchatx/public/index.html" not in names:
|
|
print("missing: meshchatx/public/index.html", file=sys.stderr)
|
|
sys.exit(1)
|
|
if not any(n.startswith("meshchatx/public/assets/") for n in names):
|
|
print("missing: meshchatx/public/assets/", file=sys.stderr)
|
|
sys.exit(1)
|
|
print("wheel contains meshchatx/public frontend paths")
|
|
PY
|
|
|
|
- name: SLSA subject hashes (PyPI dists)
|
|
id: slsa-hashes
|
|
if: github.ref_type == 'tag'
|
|
run: bash scripts/ci/github-slsa-hashes-dist.sh
|
|
|
|
- name: Cosign attestations (sdist + wheels)
|
|
if: github.ref_type == 'tag'
|
|
env:
|
|
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
|
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
|
GITHUB_SERVER_URL: ${{ github.server_url }}
|
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
GITHUB_SHA: ${{ github.sha }}
|
|
GITHUB_REF: ${{ github.ref }}
|
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
|
GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
|
|
GITHUB_WORKFLOW: ${{ github.workflow }}
|
|
GITHUB_WORKFLOW_FILE: pypi.yml
|
|
COSIGN_VERSION: ${{ env.COSIGN_VERSION }}
|
|
run: |
|
|
set -eu
|
|
if [ -z "${COSIGN_PRIVATE_KEY:-}" ]; then
|
|
echo "Skipping cosign attestations (no COSIGN_PRIVATE_KEY)."
|
|
exit 0
|
|
fi
|
|
sh scripts/ci/setup-cosign.sh "${COSIGN_VERSION}"
|
|
printf '%s\n' "$COSIGN_PRIVATE_KEY" > /tmp/cosign.key
|
|
chmod 600 /tmp/cosign.key
|
|
export COSIGN_KEY_PATH=/tmp/cosign.key
|
|
sh scripts/ci/attest-release-assets.sh ./dist
|
|
rm -f /tmp/cosign.key
|
|
|
|
- name: Store distribution packages
|
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
|
|
with:
|
|
name: python-package-distributions
|
|
path: dist/
|
|
if-no-files-found: error
|
|
|
|
slsa-provenance-pypi:
|
|
name: SLSA provenance (PyPI dists)
|
|
needs: build
|
|
if: github.ref_type == 'tag'
|
|
permissions:
|
|
id-token: write
|
|
contents: write
|
|
actions: read
|
|
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
|
with:
|
|
base64-subjects: ${{ needs.build.outputs.hashes }}
|
|
upload-assets: false
|
|
provenance-name: meshchatx-pypi-${{ github.ref_name }}.intoto.jsonl
|
|
|
|
publish-to-pypi:
|
|
name: Publish to PyPI
|
|
if: >-
|
|
github.ref_type == 'tag' &&
|
|
needs.build.result == 'success' &&
|
|
needs.slsa-provenance-pypi.result == 'success'
|
|
needs:
|
|
- build
|
|
- slsa-provenance-pypi
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
environment:
|
|
name: pypi
|
|
url: https://pypi.org/project/meshchatx/
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
id-token: write
|
|
steps:
|
|
- name: Download distributions
|
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
|
|
with:
|
|
name: python-package-distributions
|
|
path: dist/
|
|
|
|
- name: Stage PyPI uploads (exclude Cosign bundles)
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p pypi-upload
|
|
shopt -s nullglob
|
|
files=(dist/*.whl dist/*.tar.gz)
|
|
if [[ "${#files[@]}" -eq 0 ]]; then
|
|
echo "No .whl or .tar.gz in dist/" >&2
|
|
ls -la dist >&2
|
|
exit 1
|
|
fi
|
|
cp -v "${files[@]}" pypi-upload/
|
|
|
|
- name: Publish to PyPI
|
|
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
|
|
with:
|
|
packages-dir: pypi-upload/
|