From 88500dbc0f5cf4f03f3dcf870416ae6ac831394c Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 26 Aug 2026 18:52:55 +0000 Subject: [PATCH] Pin reflex-release at 0.1.0a4 and resync the generated workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump `cli-command` and the Makefile's `RELEASE_VERSION` together, then regenerate the four workflows reflex-release owns. Two upstream fixes land beyond the pin-line churn: - `publish.yml`'s `publish` and `tag-and-release` jobs now carry explicit `if:` status functions. Without one, GitHub evaluates an implicit `success()` over the whole transitive dependency closure rather than the direct `needs` — and with `custom-build` exactly one build path ever runs, so the skipped one reached `collect` straight through and the upload silently never happened. This is the shape xy releases in. - `release_from_changelog.yml`'s health check no longer treats every `skipped` leg as healthy: a leg may be skipped only when `detect` found no packages for it, so a job GitHub skipped despite having work to publish is an error rather than a green run that shipped nothing. The two known upstream findings (the unsanitized `::error::` interpolation in publish.yml's approval gate, and changelog.yml's stale "Two guards" count) are still open at 0.1.0a4; the readiness spec records that. --- .github/workflows/changelog.yml | 12 +++--- .github/workflows/dispatch_release.yml | 14 +++---- .github/workflows/publish.yml | 37 +++++++++++------ .github/workflows/release_from_changelog.yml | 42 +++++++++++++------- Makefile | 2 +- pyproject.toml | 2 +- spec/process/production-readiness.md | 21 ++++++++-- 7 files changed, 85 insertions(+), 45 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 23708fd0..21794d9d 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -1,6 +1,6 @@ # Generated by reflex-release; do not edit by hand. -# Re-run `uvx reflex-release@0.1.0a3 sync` after changing [tool.reflex-release] in -# pyproject.toml. `uvx reflex-release@0.1.0a3 sync --check` fails when this file drifts. +# Re-run `uvx reflex-release@0.1.0a4 sync` after changing [tool.reflex-release] in +# pyproject.toml. `uvx reflex-release@0.1.0a4 sync --check` fails when this file drifts. name: changelog # Two guards on every pull request: @@ -19,7 +19,7 @@ name: changelog # # 3. The generated release workflows must match what the pinned reflex-release # version produces from [tool.reflex-release] — adding a package or editing -# the configuration without re-running `uvx reflex-release@0.1.0a3 sync` fails here rather than +# the configuration without re-running `uvx reflex-release@0.1.0a4 sync` fails here rather than # at release time. permissions: @@ -65,13 +65,13 @@ jobs: env: BASE_REF: origin/${{ github.base_ref }} shell: bash - run: uvx reflex-release@0.1.0a3 check-headings + run: uvx reflex-release@0.1.0a4 check-headings - name: Check for news fragments if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-changelog') }} env: BASE_REF: origin/${{ github.base_ref }} shell: bash - run: uvx reflex-release@0.1.0a3 changelog-check + run: uvx reflex-release@0.1.0a4 changelog-check - name: Check the release workflows are up to date shell: bash - run: uvx reflex-release@0.1.0a3 sync --check + run: uvx reflex-release@0.1.0a4 sync --check diff --git a/.github/workflows/dispatch_release.yml b/.github/workflows/dispatch_release.yml index 0e858f78..e0c5cdf6 100644 --- a/.github/workflows/dispatch_release.yml +++ b/.github/workflows/dispatch_release.yml @@ -1,6 +1,6 @@ # Generated by reflex-release; do not edit by hand. -# Re-run `uvx reflex-release@0.1.0a3 sync` after changing [tool.reflex-release] in -# pyproject.toml. `uvx reflex-release@0.1.0a3 sync --check` fails when this file drifts. +# Re-run `uvx reflex-release@0.1.0a4 sync` after changing [tool.reflex-release] in +# pyproject.toml. `uvx reflex-release@0.1.0a4 sync --check` fails when this file drifts. name: Dispatch release # Kicks off a release by materializing news fragments into the selected @@ -15,7 +15,7 @@ name: Dispatch release # groups share one checkbox: their members only ever release together. # # The package list is generated from [tool.reflex-release] — after adding or -# removing a package, re-run `uvx reflex-release@0.1.0a3 sync`. +# removing a package, re-run `uvx reflex-release@0.1.0a4 sync`. # # Prerelease actions (new-prerelease-*, continued-prerelease) write alpha # versions and push them straight to an r/pre- branch @@ -103,13 +103,13 @@ jobs: PACKAGES: >- ${{ inputs.xy && 'xy' || '' }} shell: bash - run: uvx reflex-release@0.1.0a3 plan + run: uvx reflex-release@0.1.0a4 plan - name: Materialize changelogs env: ACTION: ${{ inputs.action }} RELEASES_JSON: ${{ steps.plan.outputs.releases }} shell: bash - run: uvx reflex-release@0.1.0a3 materialize + run: uvx reflex-release@0.1.0a4 materialize - name: Push prerelease branch if: ${{ !startsWith(inputs.action, 'release-') }} env: @@ -118,7 +118,7 @@ jobs: REF_NAME: ${{ github.ref_name }} RELEASES_JSON: ${{ steps.plan.outputs.releases }} shell: bash - run: uvx reflex-release@0.1.0a3 push-prerelease + run: uvx reflex-release@0.1.0a4 push-prerelease - name: Open release pull request if: ${{ startsWith(inputs.action, 'release-') }} env: @@ -127,4 +127,4 @@ jobs: REF_NAME: ${{ github.ref_name }} RELEASES_JSON: ${{ steps.plan.outputs.releases }} shell: bash - run: uvx reflex-release@0.1.0a3 open-release-pr + run: uvx reflex-release@0.1.0a4 open-release-pr diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cf4d2408..0c6c33ef 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,6 +1,6 @@ # Generated by reflex-release; do not edit by hand. -# Re-run `uvx reflex-release@0.1.0a3 sync` after changing [tool.reflex-release] in -# pyproject.toml. `uvx reflex-release@0.1.0a3 sync --check` fails when this file drifts. +# Re-run `uvx reflex-release@0.1.0a4 sync` after changing [tool.reflex-release] in +# pyproject.toml. `uvx reflex-release@0.1.0a4 sync --check` fails when this file drifts. name: Publish to PyPI run-name: Publish ${{ inputs.package }} ${{ inputs.version }} @@ -116,7 +116,7 @@ jobs: VERSION: ${{ inputs.version }} REF_NAME: ${{ github.ref_name }} shell: bash - run: uvx reflex-release@0.1.0a3 prepare-publish + run: uvx reflex-release@0.1.0a4 prepare-publish # Custom-built packages never reach the `build` job, where this gate # normally runs after the lockstep pin rewrites their metadata. They @@ -129,7 +129,7 @@ jobs: env: PACKAGE: ${{ inputs.package }} shell: bash - run: uvx reflex-release@0.1.0a3 check-dev-pins "$PACKAGE" + run: uvx reflex-release@0.1.0a4 check-dev-pins "$PACKAGE" build: needs: prepare @@ -155,7 +155,7 @@ jobs: PACKAGE: ${{ inputs.package }} VERSION: ${{ needs.prepare.outputs.version }} shell: bash - run: uvx reflex-release@0.1.0a3 pin-lockstep + run: uvx reflex-release@0.1.0a4 pin-lockstep # A *.dev dependency pin references an unpublished version, so it must # never reach released package metadata. Scoped to the package being @@ -166,7 +166,7 @@ jobs: env: PACKAGE: ${{ inputs.package }} shell: bash - run: uvx reflex-release@0.1.0a3 check-dev-pins "$PACKAGE" + run: uvx reflex-release@0.1.0a4 check-dev-pins "$PACKAGE" # The dynamic versioning backend derives each package's version from the # newest reachable tag with the package's prefix, so tagging HEAD locally @@ -278,7 +278,7 @@ jobs: PACKAGE: ${{ inputs.package }} VERSION: ${{ needs.prepare.outputs.version }} shell: bash - run: uvx reflex-release@0.1.0a3 verify-dist + run: uvx reflex-release@0.1.0a4 verify-dist - name: Repository-specific post-build checks if: hashFiles('.github/scripts/publish/post_build.sh') != '' @@ -296,7 +296,7 @@ jobs: VERSION: ${{ needs.prepare.outputs.version }} NOTES_PATH: release_notes.md shell: bash - run: uvx reflex-release@0.1.0a3 extract-notes + run: uvx reflex-release@0.1.0a4 extract-notes # The manifest covers exactly the files that go to PyPI. It lets the # gated publish job re-verify (with coreutils only) that what it uploads @@ -336,7 +336,14 @@ jobs: # dependencies — it only uploads the artifact collected above. publish: needs: [prepare, collect] - if: needs.prepare.outputs.skipped != 'true' + # The status function is load-bearing. With none, GitHub applies an + # implicit success() evaluated over the whole transitive dependency + # closure rather than the direct needs — and exactly one build path ever + # runs, so the skipped one reaches this job straight through the `collect` + # written to absorb it and the upload silently never happens. + if: >- + !cancelled() && needs.collect.result == 'success' && + needs.prepare.outputs.skipped != 'true' runs-on: ubuntu-latest environment: name: pypi @@ -411,6 +418,12 @@ jobs: tag-and-release: needs: [prepare, publish] + # The same transitive implicit success() reaches this job through + # `publish`, so it needs a status function of its own. Not `!failure()`: + # a skipped publish is neither failed nor cancelled, and tolerating it + # would push the tag for a release that uploaded nothing. + if: >- + !cancelled() && needs.publish.result == 'success' runs-on: ubuntu-latest permissions: contents: write @@ -436,7 +449,7 @@ jobs: TAG: ${{ needs.prepare.outputs.tag }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash - run: uvx reflex-release@0.1.0a3 push-tag + run: uvx reflex-release@0.1.0a4 push-tag - name: Create GitHub release env: @@ -449,7 +462,7 @@ jobs: CHECKSUMS_PATH: SHA256SUMS GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash - run: uvx reflex-release@0.1.0a3 create-release + run: uvx reflex-release@0.1.0a4 create-release # One dispatch per published tag, on the tag itself, after the upload, # the tag and the GitHub release: the workflow sees exactly the tree that @@ -462,4 +475,4 @@ jobs: VERSION: ${{ needs.prepare.outputs.version }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} shell: bash - run: uvx reflex-release@0.1.0a3 post-release + run: uvx reflex-release@0.1.0a4 post-release diff --git a/.github/workflows/release_from_changelog.yml b/.github/workflows/release_from_changelog.yml index 8d7825be..b910814d 100644 --- a/.github/workflows/release_from_changelog.yml +++ b/.github/workflows/release_from_changelog.yml @@ -1,6 +1,6 @@ # Generated by reflex-release; do not edit by hand. -# Re-run `uvx reflex-release@0.1.0a3 sync` after changing [tool.reflex-release] in -# pyproject.toml. `uvx reflex-release@0.1.0a3 sync --check` fails when this file drifts. +# Re-run `uvx reflex-release@0.1.0a4 sync` after changing [tool.reflex-release] in +# pyproject.toml. `uvx reflex-release@0.1.0a4 sync --check` fails when this file drifts. name: Release from changelog # The CHANGELOG.md files are the source of truth for publishing. On every push @@ -61,7 +61,7 @@ jobs: env: REF_NAME: ${{ github.ref_name }} shell: bash - run: uvx reflex-release@0.1.0a3 detect + run: uvx reflex-release@0.1.0a4 detect publish: needs: detect @@ -124,23 +124,37 @@ jobs: DETECT: ${{ needs.detect.result }} PUBLISH: ${{ needs.publish.result }} PUBLISH_LAST: ${{ needs.publish-last.result }} + ANY: ${{ needs.detect.outputs.any }} ANY_LAST: ${{ needs.detect.outputs.any_last }} shell: bash run: | set -euo pipefail echo "detect: $DETECT, publish: $PUBLISH, publish-last: $PUBLISH_LAST" failed=0 - # Anything that is not success/skipped (failure, cancelled, - # timed_out, a rejected environment approval, ...) is a failed leg. - for leg in "detect:$DETECT" "publish:$PUBLISH" "publish-last:$PUBLISH_LAST"; do - case "${leg#*:}" in - success | skipped) ;; - *) - echo "::error::release leg '${leg%%:*}' ended '${leg#*:}'." - failed=1 - ;; - esac - done + # A leg is healthy only in the state detect's findings call for: + # 'success' when it had packages to publish, 'skipped' only when it + # had none. Accepting every 'skipped' would report green on a leg + # GitHub skipped despite having work — a skipped job is neither + # failed nor cancelled, so nothing else here would catch it. + check_leg() { + local name=$1 result=$2 had_work=$3 + if [[ "$result" == "success" ]]; then + return 0 + fi + if [[ "$result" == "skipped" ]]; then + if [[ "$had_work" != "true" ]]; then + return 0 + fi + echo "::error::release leg '$name' was skipped even though there were packages to publish." + else + # failure, cancelled, timed_out, a rejected environment approval... + echo "::error::release leg '$name' ended '$result'." + fi + failed=1 + } + check_leg detect "$DETECT" true + check_leg publish "$PUBLISH" "$ANY" + check_leg publish-last "$PUBLISH_LAST" "$ANY_LAST" if [[ "$failed" -eq 1 ]]; then if [[ "$DETECT" != "success" ]]; then echo "::error::Changelog detection did not complete (check for a lockstep violation) — no packages were published." diff --git a/Makefile b/Makefile index c8233aad..7a9700e6 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ NAME ?= # [tool.reflex-release] — keep the two together, or a local `make news` and the # release workflows would run different pipelines. Installed into a throwaway # environment, so the tool is never a dependency of the dev venv. -RELEASE_VERSION ?= 0.1.0a3 +RELEASE_VERSION ?= 0.1.0a4 RELEASE_CLI = uvx reflex-release@$(RELEASE_VERSION) .PHONY: help setup setup-browser check check-full check-browser check-conformance check-docs check-examples check-security check-errors check-api check-import check-ci check-benchmark-harness check-pyplot check-pyplot-speed check-sdist check-wheel check-artifacts check-benchmark-report list-checks test lint format typecheck public-api python-floor js-check rust-check abi-smoke news news-check diff --git a/pyproject.toml b/pyproject.toml index ba150aa6..352fdb86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -249,7 +249,7 @@ pythonpath = ["."] # How the release workflows invoke the tool, pinned to one published version so # a local run and a CI run cannot drift apart. Bump it (and the `Makefile`'s # RELEASE_VERSION) and re-run `reflex-release sync`. -cli-command = "uvx reflex-release@0.1.0a3" +cli-command = "uvx reflex-release@0.1.0a4" # Dispatched once per published tag, on the tag itself, after the upload, the tag # and the GitHub release exist. The docs site ships the version it documents, and diff --git a/spec/process/production-readiness.md b/spec/process/production-readiness.md index 18e01549..89c3e07d 100644 --- a/spec/process/production-readiness.md +++ b/spec/process/production-readiness.md @@ -360,7 +360,7 @@ artifacts carry it. Three consequences worth knowing: | `build_release_artifacts.yml` | called by `publish.yml`, or manual for a dry run | **This repository's own**: the release matrix — eleven platform wheels, the runtime-verified PyEmscripten wheel, and the sdist. | | `deploy-docs-stg.yml` | dispatched by `publish.yml` per published tag, or manual | **This repository's own**: builds and deploys the docs site for the version just published. | -The first four come from `reflex-release` (pinned at `0.1.0a3` in +The first four come from `reflex-release` (pinned at `0.1.0a4` in `[tool.reflex-release] cli-command`), which owns their invariants and tests them where the tool lives. This repository deliberately does not re-assert their contents. The last two are its own, and they are the whole integration surface: @@ -592,7 +592,7 @@ Keep pushing these in low-conflict increments: follow-up is wiring an actual TestPyPI upload into that dry-run path (today it reaches no publish path at all, it doesn't yet push to a test index) plus refreshed benchmark reports. -- `reflex-release` is pinned at `0.1.0a3`, an alpha. Track its releases and bump +- `reflex-release` is pinned at `0.1.0a4`, an alpha. Track its releases and bump the pin as it stabilizes, keeping `cli-command` and the `Makefile`'s `RELEASE_VERSION` on the same version — otherwise `make news` and the release workflows run different pipelines. `sync --check` on every pull request catches @@ -600,8 +600,21 @@ Keep pushing these in low-conflict increments: - Two review findings land in `reflex-release`'s generated workflows, so they are upstream fixes rather than local edits — patching them here would fork a file `sync --check` then reports as drift forever, which is the arrangement this - repository just removed. Both were still open as of `0.1.0a3`, which did pick up - a third (an explicit `shell: bash` on every generated `run` step): + repository just removed. Both were still open as of `0.1.0a4`, which did pick up + two job-level `if` fixes in the publish path instead — the second of them + load-bearing for this repository, since `custom-build` is exactly the shape it + describes: + - `publish.yml`'s `publish` and `tag-and-release` jobs now carry explicit + status functions (`!cancelled() && needs..result == 'success'`). Without + one, GitHub evaluates an implicit `success()` over the whole transitive + dependency closure rather than the direct `needs`, so the build path that + *doesn't* run — here the tool's own `build` job, replaced by + `build_release_artifacts.yml` — would pass straight through `collect` and + the upload would silently never happen. + - `release_from_changelog.yml`'s health check no longer accepts every + `skipped` leg: a leg is healthy as `skipped` only when `detect` found no + packages for it, so a job GitHub skipped despite having work to publish is + now an error rather than a green run that shipped nothing. - `publish.yml`'s approval gate interpolates the captured `gh api` error into a `::error::` message. A `gh` error line containing `::` would be re-parsed as a workflow command, truncating the diagnostic. It cannot leak anything (the job