diff --git a/.github/workflows/publish-dev-to-testpypi.yml b/.github/workflows/publish-dev-to-testpypi.yml deleted file mode 100644 index 2656b21..0000000 --- a/.github/workflows/publish-dev-to-testpypi.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish dev build to Test PyPI - -# The continuous dev feed: every push to main lands on Test PyPI as a `.devN` -# build. Separate from the release flow, which publishes the tagged version to -# Test PyPI and then PyPI -- both use skip-existing, so they cannot collide. - -on: - workflow_dispatch: - push: - branches: - - main - -permissions: - contents: read - -jobs: - build: - name: Reuse build - uses: ./.github/workflows/build.yml - permissions: - contents: read - - publish-to-test-pypi: - needs: build - uses: reqstool/.github/.github/workflows/python-publish-to-pypi.yml@main - permissions: - id-token: write - with: - target: testpypi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86a7409..caefc0b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -95,43 +95,35 @@ jobs: version: ${{ needs.prepare.outputs.version }} artifact: dist-tagged - publish-to-testpypi: - needs: [prepare, assets] - uses: reqstool/.github/.github/workflows/python-publish-to-pypi.yml@main - permissions: - id-token: write - with: - target: testpypi - artifact: dist-tagged - # PyPI is the only step here that cannot be undone: a version can be yanked but - # never replaced. A release candidate stops at Test PyPI -- pip needs --pre to - # see a prerelease anyway. + # never replaced. Publishes release candidates too, with their pre-release + # identifier -- pip ignores them without --pre, so there is nothing unsafe + # about it landing on the real index; there is no separate staging index to + # route them to instead. Matches npm and Maven Central's existing behavior. publish-to-pypi: - needs: [prepare, publish-to-testpypi] - if: ${{ needs.prepare.outputs.prerelease != 'true' }} - uses: reqstool/.github/.github/workflows/python-publish-to-pypi.yml@main + needs: [prepare, assets] + runs-on: ubuntu-latest + environment: + name: stable + url: https://pypi.org permissions: id-token: write - with: - target: pypi - artifact: dist-tagged + steps: + - uses: reqstool/.github/.github/actions/publish-to-pypi@b10b898cd5a1d552a578dbe4f170f84fb8f98b6c # main 2026-08-23 + with: + artifact: dist-tagged # Last, deliberately. Everything above can fail, and until this runs nothing # resolving "the latest release" can see what was built -- the release is still # a prerelease. Promotion itself is one API call against a release that already # has its artifacts. # - # The guard is `no job failed`, not the default `every job succeeded`: a release - # candidate deliberately skips the publish jobs that a real release runs, and a - # skipped dependency would otherwise cascade and skip this too -- leaving the - # candidate unpromoted, which is right, and every *real* release unpromoted the - # moment any optional job is skipped, which is not. - # - # `!inputs.dry-run` has to be spelled out for the same reason: on a dry run - # every job above is skipped, and "nothing failed" would otherwise be true. + # The guard is `no job failed`, not the default `every job succeeded`: on a dry + # run every job above is skipped, which would make plain success() false too -- + # `!inputs.dry-run` is what actually gates this job then, and `no job failed` + # is what confirms nothing above it errored on a real run. promote: - needs: [prepare, assets, publish-to-testpypi, publish-to-pypi] + needs: [prepare, assets, publish-to-pypi] if: ${{ !inputs.dry-run && !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} uses: reqstool/.github/.github/workflows/common-release-promote.yml@main permissions: