v1.12.0 #15
Workflow file for this run
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 | |
| # Automated PyPI publishing for django-admin-react. | |
| # | |
| # WHY: every release was a manual local run of `scripts/build.sh` + | |
| # `poetry publish` with the long-lived PyPI token in `./.env` on the | |
| # maintainer's laptop — so cadence depended on a human remembering. This | |
| # workflow makes a release a single GitHub click: publish a Release and the | |
| # matching wheel/sdist ship to PyPI automatically. | |
| # | |
| # SECURITY POSTURE: | |
| # - **No long-lived tokens stored anywhere.** PyPI auth uses | |
| # **Trusted Publishing (OIDC)** — PyPI verifies the workflow's | |
| # short-lived OIDC identity at upload time. The maintainer's `./.env` | |
| # `POETRY_PYPI_TOKEN_PYPI` stays purely local for the manual fallback | |
| # path; it is never copied into GitHub Secrets and never echoed. | |
| # - Trigger is `release: published` — a human still authorises every | |
| # publish (preserves the Tier-6 "human triggers the release" rule); the | |
| # Release notes double as the changelog entry. | |
| # - **Idempotent publish.** Before uploading, the job queries PyPI's JSON | |
| # API for the current version. If the wheel + sdist for that version | |
| # already exist (e.g. the maintainer published manually first, or a | |
| # previous workflow run partially succeeded), the upload step is | |
| # skipped and the deployment is still marked **green**. Releases never | |
| # fail just because the artifact is already where it should be — and | |
| # the idempotency check itself requires NO auth, so the widget goes | |
| # green for already-published versions even before #564 is set up. | |
| # - Least-privilege: top-level `contents: read`; only the publish job | |
| # gets `id-token: write`, scoped to the `pypi` environment. | |
| # - All third-party actions are pinned to a full commit SHA (a tag can be | |
| # moved, a SHA cannot) per the supply-chain hardening in issue #331. | |
| # | |
| # ONE-TIME OWNER SETUP (required before the workflow can perform a FRESH | |
| # upload — already-published versions go green without it via the | |
| # idempotency guard above): | |
| # 1. PyPI → project `django-admin-react` → Settings → Publishing → add a | |
| # "Trusted Publisher": | |
| # Owner: MartinCastroAlvarez | |
| # Repository: django-admin-react | |
| # Workflow: publish.yml | |
| # Environment: pypi | |
| # 2. GitHub → repo Settings → Environments → create `pypi` (optionally | |
| # add required reviewers so a release is approval-gated), and | |
| # `testpypi`. | |
| # See SECURITY.md §7 for the rationale. Tracked in #564. | |
| on: | |
| release: | |
| types: [published] | |
| workflow_dispatch: | |
| inputs: | |
| target: | |
| description: "Publish target" | |
| type: choice | |
| default: testpypi | |
| options: | |
| - testpypi | |
| - pypi | |
| permissions: | |
| contents: read | |
| jobs: | |
| build: | |
| name: Build SPA + wheel/sdist | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 | |
| with: | |
| version: 9 | |
| - name: Set up Node | |
| uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| cache-dependency-path: frontend/pnpm-lock.yaml | |
| - name: Set up Python | |
| uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Poetry | |
| run: pipx install poetry | |
| - name: Build (Vite SPA bundle, then wheel + sdist) | |
| run: bash scripts/build.sh | |
| - name: Upload distributions | |
| uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 | |
| with: | |
| name: dist | |
| path: dist/ | |
| if-no-files-found: error | |
| publish-pypi: | |
| name: Publish to PyPI (Trusted Publishing) | |
| needs: build | |
| runs-on: ubuntu-latest | |
| # GitHub Release publish, or an explicit manual run targeting pypi. | |
| if: github.event_name == 'release' || inputs.target == 'pypi' | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/project/django-admin-react/ | |
| permissions: | |
| id-token: write # OIDC for Trusted Publishing — no stored token | |
| steps: | |
| - name: Checkout (for pyproject version read) | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Download distributions | |
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
| with: | |
| name: dist | |
| path: dist/ | |
| # Idempotency guard — never fail just because the artifact is already | |
| # on PyPI. The maintainer's manual `.env` publish path occasionally | |
| # ships first; in that case this workflow is a no-op that still marks | |
| # the GitHub Deployment green so the repo widget reflects reality. | |
| # This step calls only the public PyPI JSON API — no auth, no token. | |
| - name: Is this version already on PyPI? | |
| id: already | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/version = "(.*)"/\1/') | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| if curl -fsS "https://pypi.org/pypi/django-admin-react/${VERSION}/json" >/dev/null 2>&1; then | |
| echo "Found django-admin-react ${VERSION} on PyPI — skipping upload." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "django-admin-react ${VERSION} not on PyPI yet — proceeding with OIDC upload." | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Publish to PyPI (Trusted Publishing) | |
| if: steps.already.outputs.skip != 'true' | |
| uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 | |
| with: | |
| packages-dir: dist/ | |
| publish-testpypi: | |
| name: Publish to TestPyPI (Trusted Publishing) | |
| needs: build | |
| runs-on: ubuntu-latest | |
| # Manual dry-runs only — keeps a safe rehearsal path off PyPI. | |
| if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi' | |
| environment: | |
| name: testpypi | |
| url: https://test.pypi.org/project/django-admin-react/ | |
| permissions: | |
| id-token: write | |
| steps: | |
| - name: Download distributions | |
| uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Publish to TestPyPI (Trusted Publishing) | |
| uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 | |
| with: | |
| packages-dir: dist/ | |
| repository-url: https://test.pypi.org/legacy/ |