Publish to PyPI #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish to PyPI | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag to publish, for example v0.0.12" | |
| required: true | |
| type: string | |
| concurrency: | |
| group: pypi-${{ github.event.release.tag_name || inputs.tag }} | |
| cancel-in-progress: false | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| build: | |
| name: Build package | |
| runs-on: ubuntu-latest | |
| if: > | |
| github.event_name == 'workflow_dispatch' || | |
| (startsWith(github.event.release.tag_name, 'v') && !endsWith(github.event.release.tag_name, '-launcher')) | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event.release.tag_name || inputs.tag }} | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v6 | |
| with: | |
| enable-cache: true | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --extra dev | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: "npm" | |
| cache-dependency-path: editor/package-lock.json | |
| - name: Build editor | |
| working-directory: editor | |
| run: | | |
| npm ci | |
| npm run build | |
| - name: Verify version matches release tag | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| release_tag="${{ github.event.release.tag_name || inputs.tag }}" | |
| package_version="$(python - <<'PY' | |
| import tomllib | |
| with open("pyproject.toml", "rb") as handle: | |
| print(tomllib.load(handle)["project"]["version"]) | |
| PY | |
| )" | |
| if [ "$release_tag" != "v${package_version}" ]; then | |
| echo "release tag ${release_tag} does not match package version v${package_version}" >&2 | |
| exit 1 | |
| fi | |
| - name: Run preflight | |
| run: uv run python -X utf8 tests/run.py preflight | |
| - name: Stage base curriculum into package | |
| run: | | |
| rm -rf src/codaro/curricula | |
| cp -r curricula src/codaro/curricula | |
| test -f src/codaro/curricula/python/__init__.py | |
| - name: Build sdist and wheel | |
| run: uv build --clear | |
| - name: Verify package payload | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python - <<'PY' | |
| from pathlib import Path | |
| import tarfile | |
| import zipfile | |
| wheels = list(Path("dist").glob("codaro-*.whl")) | |
| sdists = list(Path("dist").glob("codaro-*.tar.gz")) | |
| if len(wheels) != 1: | |
| raise SystemExit(f"expected one wheel, found {len(wheels)}") | |
| if len(sdists) != 1: | |
| raise SystemExit(f"expected one sdist, found {len(sdists)}") | |
| with zipfile.ZipFile(wheels[0]) as archive: | |
| wheel_names = set(archive.namelist()) | |
| required_wheel_entries = { | |
| "codaro/webBuild/index.html", | |
| "codaro/curricula/python/__init__.py", | |
| } | |
| missing = sorted(required_wheel_entries - wheel_names) | |
| if missing: | |
| raise SystemExit(f"wheel missing required entries: {missing}") | |
| if not any(name.startswith("codaro/webBuild/_app/") for name in wheel_names): | |
| raise SystemExit("wheel missing codaro/webBuild/_app assets") | |
| if not any(name.startswith("codaro/curricula/python/") and name.endswith(".yaml") for name in wheel_names): | |
| raise SystemExit("wheel missing built-in curriculum YAML files") | |
| with tarfile.open(sdists[0], "r:gz") as archive: | |
| sdist_names = set(archive.getnames()) | |
| if not any(name.endswith("/src/codaro/webBuild/index.html") for name in sdist_names): | |
| raise SystemExit("sdist missing src/codaro/webBuild/index.html") | |
| if not any(name.endswith("/src/codaro/curricula/python/__init__.py") for name in sdist_names): | |
| raise SystemExit("sdist missing built-in curriculum package") | |
| PY | |
| - name: Verify wheel install | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| smoke_dir="${RUNNER_TEMP}/codaro-wheel-smoke" | |
| uv venv "$smoke_dir" --python ${{ env.PYTHON_VERSION }} | |
| uv pip install --python "$smoke_dir/bin/python" dist/*.whl | |
| "$smoke_dir/bin/python" - <<'PY' | |
| import importlib.metadata | |
| import codaro | |
| version = importlib.metadata.version("codaro") | |
| assert version | |
| assert callable(codaro.main) | |
| print(f"codaro {version} wheel import ok") | |
| PY | |
| - name: Verify uv add install | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| smoke_dir="${RUNNER_TEMP}/codaro-uv-add-smoke" | |
| rm -rf "$smoke_dir" | |
| mkdir -p "$smoke_dir" | |
| package_version="$(python - <<'PY' | |
| import tomllib | |
| with open("pyproject.toml", "rb") as handle: | |
| print(tomllib.load(handle)["project"]["version"]) | |
| PY | |
| )" | |
| pushd "$smoke_dir" | |
| uv init --bare | |
| uv add --find-links "${GITHUB_WORKSPACE}/dist" "codaro==${package_version}" | |
| uv run python -X utf8 - <<'PY' | |
| from importlib.resources import files | |
| import importlib.metadata | |
| import codaro | |
| version = importlib.metadata.version("codaro") | |
| assert version | |
| assert callable(codaro.main) | |
| assert (files("codaro") / "webBuild" / "index.html").is_file() | |
| assert (files("codaro") / "curricula" / "python" / "__init__.py").is_file() | |
| assert any((files("codaro") / "curricula" / "python").glob("*.yaml")) | |
| print(f"codaro {version} uv add resources ok") | |
| PY | |
| uv run codaro --help | |
| popd | |
| - name: Upload dist artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| publish: | |
| name: Publish to PyPI | |
| needs: build | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Download dist artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| skip-existing: true |