Skip to content

Add maintainer release flow#2

Open
justin808 wants to merge 6 commits intomainfrom
jg-codex/release-flow-parity
Open

Add maintainer release flow#2
justin808 wants to merge 6 commits intomainfrom
jg-codex/release-flow-parity

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Apr 24, 2026

Summary

  • add a local make release maintainer flow with changelog-driven version resolution, release checks, tagging, and optional local PyPI/TestPyPI upload
  • single-source package version metadata in src/react_on_django/__about__.py and document the simplified release process
  • keep the GitHub release workflow as a manual trusted-publishing fallback and add focused release helper regression tests

Validation

  • .venv/bin/python3.14 -m pytest tests/test_release.py -q
  • .venv/bin/python3.14 -m ruff check scripts/release.py tests/test_release.py
  • .venv/bin/python3.14 -m build --no-isolation
  • .venv/bin/python3.14 -m twine check dist/*
  • clean throwaway clone: make release VERSION=0.1.0a1 REPOSITORY=skip YES=1 SKIP_PUSH=1 PYTHON=.venv/bin/python3.14 after committing the tooling and adding a temporary changelog entry; this passed through ruff, full pytest, example/bin/test-ci, build, twine check, release commit, and tag creation

Notes

  • left the existing unrelated working tree changes untouched

Note

Medium Risk
Medium risk because it introduces new release automation (git tagging/pushing and optional PyPI uploads) and changes package version metadata sourcing, which can affect packaging/publishing if misconfigured.

Overview
Adds a maintainer release flow via scripts/release.py and make release, including changelog-driven version resolution, repo cleanliness/branch/tag validation, running ruff/pytest/example e2e checks, building dists, tagging/pushing, and optional twine upload to testpypi/pypi.

Switches package versioning to be single-sourced from src/react_on_django/__about__.py (and pyproject.toml now uses Hatch dynamic versioning), plus adds RELEASING.md and expands CHANGELOG.md scaffolding.

Updates CI to install django-rspack from a pinned Git commit, adds a manual .github/workflows/release.yml trusted-publishing workflow, makes the example bin/dev resolve Python more robustly, and extends asset helper logic/tests to fall back to django_rspack.manifest APIs when top-level helpers are unavailable.

Reviewed by Cursor Bugbot for commit d5b5ad8. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added comprehensive release process documentation for maintainers and changelog guidelines for tracking upcoming project changes.
  • Chores

    • Implemented automated CI/CD release workflow to enable reliable and consistent package publishing.
    • Configured build verification, distribution tooling, and version management systems for professional package releases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

Walkthrough

This pull request introduces a comprehensive release infrastructure for the project. It adds a GitHub Actions CI/CD workflow for publishing packages, a Python release automation script, release process documentation, Makefile targets, version management via a dedicated __about__.py module, and test coverage for the release tooling.

Changes

Cohort / File(s) Summary
Release CI/CD & Workflow
.github/workflows/release.yml
New GitHub Actions workflow that runs unit tests (Ruff, Pytest across Python/Django matrix) and example E2E Playwright tests, validates version consistency with git tags, builds source/wheel distributions, verifies metadata with twine check, and publishes to TestPyPI or PyPI using OIDC trusted publishers.
Release Process Automation
Makefile, scripts/release.py
Adds release and release-dry-run Make targets that invoke a new Python release automation script. The script handles version parsing from CHANGELOG.md or CLI args, git safety checks (dirty worktree, branch validation, tag conflicts), optional linting/testing, distribution building, changelog/version-file updates, git commits/tags, and conditional twine uploads with dry-run support.
Version Management
src/react_on_django/__about__.py, src/react_on_django/__init__.py, pyproject.toml
Introduces a new __about__.py module declaring __version__ = "0.1.0a1". Refactors __init__.py to import version from __about__, adds it to __all__, and updates pyproject.toml to declare version as dynamic (sourced from __about__.py). Also adds project metadata URLs and packaging-related dev dependencies (build, hatchling, twine).
Release Documentation
CHANGELOG.md, RELEASING.md
Adds changelog documentation directive and an [Unreleased] section header. Introduces comprehensive RELEASING.md guide detailing maintainer steps, PEP 440 version format requirements, prerelease rules, release commit/tag procedures, and instructions for configuring twine and PyPI/TestPyPI trusted publishers via OIDC.
Release Test Suite
tests/test_release.py
New pytest suite validating release script functionality: semantic version parsing with prerelease labels, changelog-driven version selection, git dirty-path parsing, release-check command generation, version rewriting in __about__.py, version sort ordering, and repository name parsing and upload-plan defaults.

Sequence Diagram(s)

sequenceDiagram
    actor Maintainer
    participant Make as Make/CLI
    participant ReleaseScript as Release Script
    participant Git
    participant Build as Build System
    participant Twine as Twine/PyPI
    participant GitHub as GitHub Actions

    Maintainer->>Make: make release [VERSION=x.y.z]
    Make->>ReleaseScript: python scripts/release.py
    
    ReleaseScript->>ReleaseScript: Parse version from CHANGELOG or CLI
    ReleaseScript->>Git: Validate branch (main), check dirty worktree
    ReleaseScript->>Git: Verify tag doesn't exist
    ReleaseScript->>ReleaseScript: Run optional checks (Ruff, Pytest, E2E)
    ReleaseScript->>Build: Build source & wheel distributions
    ReleaseScript->>Twine: Run twine check on artifacts
    
    ReleaseScript->>ReleaseScript: Update __about__.py __version__
    ReleaseScript->>Git: Stage & commit CHANGELOG.md, __about__.py
    ReleaseScript->>Git: Create annotated git tag
    
    ReleaseScript->>Twine: Upload artifacts to TestPyPI/PyPI (if selected)
    ReleaseScript->>Git: Push branch + tags to origin
    
    ReleaseScript-->>Maintainer: Release summary & confirmation
    
    Note over GitHub: GitHub Actions workflow triggered on tag<br/>re-runs tests & publishes using OIDC
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 hops excitedly
A release workflow now takes flight,
With versions bumped and tags so tight,
From CHANGELOG to PyPI's shore,
Our package shines forevermore! 📦✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add maintainer release flow' directly and concisely describes the main change: introducing a local release workflow for maintainers via make release, version management, and CI/CD tooling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg-codex/release-flow-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR introduces a local make release maintainer flow with changelog-driven version resolution, single-sourced version metadata in __about__.py, a focused regression test suite, and an optional GitHub Actions trusted-publishing fallback.

  • P1 — version file restored after commit: In prepared_version, the except Exception branch restores VERSION_FILE to its pre-release content even when the exception is thrown after stage_and_commit_release_files has already run (e.g. on a push or upload failure). This leaves __about__.py in the working tree at the old version while the latest local commit holds the release version, making the worktree dirty and breaking any subsequent make release attempt with an "unexpected changes" error.

Confidence Score: 4/5

Safe to merge after resolving the post-commit version-restore bug in prepared_version; all other findings are P2 style/cleanup suggestions.

One P1 defect: prepared_version restores the version file on error even after the release commit exists, leaving the working tree inconsistent with git history and blocking recovery with a clean-worktree check error. The remaining finding (dead push branch in the GitHub Actions workflow) is P2. The overall logic, test coverage, and release flow design are solid.

scripts/release.py — the prepared_version context manager's error-recovery path

Important Files Changed

Filename Overview
scripts/release.py Core release script — well-structured overall, but prepared_version restores the version file on error even after the release commit has been created, leaving the worktree inconsistent with the committed state on push/upload failure (P1)
.github/workflows/release.yml New release workflow — correctly wires unit, e2e, build, and publish jobs; push-event validation branch in "Validate release version" step is dead code since no push trigger is declared
tests/test_release.py New regression tests for the release helper — good coverage of version parsing, changelog extraction, dirty-path detection, and upload plan resolution
pyproject.toml Version single-sourced via dynamic = ["version"] + [tool.hatch.version], build/twine/hatchling added to dev deps for --no-isolation builds, project URLs added
src/react_on_django/init.py Imports __version__ from __about__ and adds it to __all__; clean single-source migration
src/react_on_django/about.py New single-source version file; version set to 0.1.0a1
Makefile Thin wrapper exposing make release and make release-dry-run targets; correctly forwards optional CLI flags
RELEASING.md Comprehensive maintainer release guide; accurately documents the script behaviour and PyPI trusted-publisher setup
CHANGELOG.md Minimal bootstrap entry with [Unreleased] header

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([make release]) --> B[resolve_target_version\nCHANGELOG or --version]
    B --> C[validate_repo_state\nbranch · dirty check · tag · changelog section]
    C --> D[resolve_upload_plan\nskip / testpypi / pypi]
    D --> E[print_release_summary\n+ interactive confirm]
    E --> F[prepared_version\nwrite target version to __about__.py]
    F --> G[run_release_checks\nruff · pytest · npm ci · test-ci]
    G --> H[build_distributions\npython -m build + twine check]
    H --> I{dry_run?}
    I -- yes --> J[restore __about__.py\nclean dist/\nreturn 0]
    I -- no --> K[stage_and_commit_release_files\ngit add + git commit]
    K --> L[create_tag\ngit tag -a vX.Y.Z]
    L --> M{upload_plan.repository?}
    M -- yes --> N[upload_distributions\ntwine upload]
    M -- no --> O{skip_push?}
    N --> O
    O -- no --> P[push_release\ngit push origin branch --follow-tags]
    O -- yes --> Q
    P --> Q[clean_build_artifacts\nrm -rf dist/]
    Q --> R([Release complete])
    F -. exception after commit .-> S[restore __about__.py ⚠️\nleaves worktree inconsistent]
    S --> T([ReleaseError printed])
Loading

Comments Outside Diff (1)

  1. scripts/release.py, line 600-608 (link)

    P1 Version file restored after commit, leaving worktree inconsistent

    If the version was updated by prepared_version (i.e. current_version != target_version) and then push_release or upload_distributions raises after the commit and tag are already created, the except Exception branch restores VERSION_FILE to its pre-release content. The working tree then shows __about__.py as modified (e.g. 0.1.0) while the latest local commit has the release version (0.1.0a1). On the next make release attempt the worktree-cleanliness check will fail with an "unexpected changes" error, blocking recovery.

    The rollback is appropriate before the commit exists, but must be skipped once stage_and_commit_release_files has run. One way to fix this is to track whether the commit was made and skip the restore after that point.

Reviews (1): Last reviewed commit: "Add maintainer release flow" | Re-trigger Greptile

Comment thread .github/workflows/release.yml Outdated
Comment on lines +138 to +143
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && -n "${INPUT_VERSION}" && "${INPUT_VERSION}" != "${PACKAGE_VERSION}" ]]; then
echo "Requested version ${INPUT_VERSION} does not match package version ${PACKAGE_VERSION}."
exit 1
fi

- name: Install build tooling
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unreachable push-trigger validation branch

The push branch of the version check can never execute because the workflow only declares a workflow_dispatch trigger — GITHUB_EVENT_NAME will always be "workflow_dispatch", never "push". The guard on lines 139–142 is dead code. Either add a push: tags: ["v*"] trigger to make it reachable, or remove the push block to avoid confusion.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34bb66eb4f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/release.py
Comment on lines +323 to +325
**os.environ,
"REACT_ON_DJANGO_EXAMPLE_PYTHON": sys.executable,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use effective Python override for example release checks

This sets REACT_ON_DJANGO_EXAMPLE_PYTHON, but the invoked path (example/bin/test-ci -> example/bin/dev) currently hardcodes /workspace/.../.venv/bin/python3.14 and does not read that variable, so make release PYTHON=... still fails on machines without that exact interpreter path. In practice this blocks the new release flow in common maintainer environments unless they happen to match the hardcoded .venv layout.

Useful? React with 👍 / 👎.

Comment thread scripts/release.py
Comment on lines +393 to +397
if selected is not None:
return UploadPlan(repository=selected, source="explicit option")
if yes:
return UploadPlan(repository=None, source="non-interactive default")
return prompt_upload_repository()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Respect explicit skip upload choice

Passing --repository skip is parsed to None, and this branch treats None as "no explicit choice" and re-prompts interactively unless --yes is also set. That means an explicit opt-out of upload is ignored in normal interactive runs, which can lead to accidental uploads if the prompt is answered incorrectly.

Useful? React with 👍 / 👎.

Comment thread scripts/release.py Outdated
Comment on lines +132 to +135
except Exception:
if should_update:
VERSION_FILE.write_text(original)
raise
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid reverting version file after post-commit failures

On any exception inside the release block, this restores __about__.py to its original contents. If the failure happens after the release commit/tag is created (for example during twine upload or git push), the worktree is left dirty and out of sync with HEAD, making retry/recovery error-prone and potentially leading to accidental follow-up commits that undo the version bump.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pyproject.toml (1)

29-33: ⚠️ Potential issue | 🔴 Critical

django-rspack>=0.1.0 does not exist on PyPI and blocks the build.

The package is not available on PyPI (HTTP 404 from the package index API). This breaks pip install -e .[dev] in .github/workflows/release.yml (lines 43, 73) and the documented maintainer setup in RELEASING.md. Publish the package to PyPI, use the correct package name, pin to an available version, or use a VCS URL (e.g., django-rspack @ git+https://...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 29 - 33, The pyproject dependency
"django-rspack>=0.1.0" is not published on PyPI and breaks installs; update the
pyproject.toml dependencies to a valid source by either replacing
"django-rspack>=0.1.0" with the correct PyPI package name and an available
version, replacing it with a VCS spec like "django-rspack @ git+https://..."
pointing to the repo/commit, or removing it if unused; ensure the same
identifier is updated where referenced in CI/release workflows and docs (the
dependency list in pyproject.toml and any occurrences in release
workflows/RELEASING.md).
🧹 Nitpick comments (6)
.github/workflows/release.yml (2)

43-43: Quote the .[dev] pip target.

Unquoted .[dev] is a shell glob (dotfile charclass). Bash with default settings passes it through when there's no match, but the script is fragile against accidental dotfile matches (e.g., .d/.e/.v) and zsh behavior. Use '.[dev]'.

♻️ Proposed fix
-          python -m pip install "Django==${{ matrix.django-version }}" -e .[dev]
+          python -m pip install "Django==${{ matrix.django-version }}" -e '.[dev]'
-          python -m pip install -e .[dev]
+          python -m pip install -e '.[dev]'

Also applies to: 73-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml at line 43, The pip editable extras target -e
.[dev] is unquoted and can be expanded by the shell; change it to use quoted
extras like -e ".[dev]" (or -e '.[dev]') in the pip install command that
currently reads python -m pip install "Django==${{ matrix.django-version }}" -e
.[dev], and apply the same quoting fix to the other occurrence of .[dev] in the
workflow so both editable extras are safely quoted.

131-141: Dead branch: the push check cannot fire under the current on: config.

on: only includes workflow_dispatch, so GITHUB_EVENT_NAME == "push" is never true. Either wire up a push: { tags: ['v*'] } trigger (if tag-based publishing is intended per scripts/release.py tagging) or drop the block to reduce confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 131 - 141, The release workflow
contains a dead branch checking GITHUB_EVENT_NAME == "push" (comparing
GITHUB_REF_NAME to expected_tag) but the workflow's on: only declares
workflow_dispatch, so that branch never runs; either add a push trigger that
matches tags (e.g., push: tags: ['v*']) so the push-based tag check
(expected_tag / GITHUB_REF_NAME) is meaningful, or remove the entire push-check
block (the if that tests GITHUB_EVENT_NAME == "push" and the expected_tag
comparison) to avoid confusion—make the change around the expected_tag /
GITHUB_EVENT_NAME / GITHUB_REF_NAME logic to keep behavior consistent with
scripts/release.py tagging flow.
tests/test_release.py (2)

117-126: Brittle reliance on commands[-1] ordering.

Asserting the E2E step is the last element of release_check_commands(...) couples the test to ordering that may legitimately change. Prefer filtering by command name, e.g., next(c for c in commands if c[0] == ["./bin/test-ci"]), then assert on that tuple.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_release.py` around lines 117 - 126, The test
test_release_check_commands_sets_example_python_for_e2e is brittle because it
assumes the E2E tuple is at commands[-1]; instead locate the tuple by filtering
the iterable returned by release.release_check_commands(skip_checks=False)
(e.g., find the entry where the command list equals ["./bin/test-ci"] or where
the second element's name == "example") and then assert on that found tuple's
environment value; update the test to use next(...) or a comprehension to select
the matching tuple from commands before asserting that
example_test[2]["REACT_ON_DJANGO_EXAMPLE_PYTHON"] == release.sys.executable.

8-15: Consider a module-scoped fixture for load_release_module().

Each test re-executes scripts/release.py via importlib.util.spec_from_file_location, which reimports/re-parses the module (and re-registers sys.modules["react_on_django_release"]) per test. A @pytest.fixture(scope="module") would cut duplication and avoid repeated loads.

♻️ Optional refactor
-def load_release_module():
-    module_path = Path(__file__).resolve().parents[1] / "scripts" / "release.py"
-    spec = importlib.util.spec_from_file_location("react_on_django_release", module_path)
-    module = importlib.util.module_from_spec(spec)
-    assert spec.loader is not None
-    sys.modules[spec.name] = module
-    spec.loader.exec_module(module)
-    return module
+@pytest.fixture(scope="module")
+def release():
+    module_path = Path(__file__).resolve().parents[1] / "scripts" / "release.py"
+    spec = importlib.util.spec_from_file_location("react_on_django_release", module_path)
+    module = importlib.util.module_from_spec(spec)
+    assert spec.loader is not None
+    sys.modules[spec.name] = module
+    spec.loader.exec_module(module)
+    return module

Then drop release = load_release_module() from each test body and accept release as a fixture parameter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_release.py` around lines 8 - 15, The tests repeatedly re-import
scripts/release.py by calling load_release_module() in each test; convert
load_release_module into a module-scoped pytest fixture (e.g., add
`@pytest.fixture`(scope="module") above load_release_module) so the module is
imported once per test module, register it in sys.modules as before and return
the loaded module, then update tests to accept a release fixture parameter
instead of calling load_release_module() inside each test to remove duplication
and repeated imports.
Makefile (1)

6-6: Boolean flag vars treat any non-empty value as truthy (including 0/false).

$(if $(YES),--yes,) (and SKIP_CHECKS/SKIP_PUSH) will pass the flag for YES=0, YES=false, or YES=no. Consider documenting that these vars are "set to any value to enable" (which matches RELEASING.md examples YES=1, SKIP_PUSH=1), or normalize to truthy tokens, e.g.:

♻️ Optional normalization
-release:
-	$(PYTHON) scripts/release.py $(if $(VERSION),--version $(VERSION),) $(if $(REPOSITORY),--repository $(REPOSITORY),) $(if $(YES),--yes,) $(if $(SKIP_CHECKS),--skip-checks,) $(if $(SKIP_PUSH),--skip-push,)
+# Treat these as boolean: enabled only when set to 1/true/yes.
+_truthy = $(filter 1 true yes TRUE YES,$(1))
+release:
+	$(PYTHON) scripts/release.py $(if $(VERSION),--version $(VERSION),) $(if $(REPOSITORY),--repository $(REPOSITORY),) $(if $(call _truthy,$(YES)),--yes,) $(if $(call _truthy,$(SKIP_CHECKS)),--skip-checks,) $(if $(call _truthy,$(SKIP_PUSH)),--skip-push,)

Also applies to: 9-9

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` at line 6, The Makefile currently treats any non-empty value as
truthy for flags YES, SKIP_CHECKS, and SKIP_PUSH when building the release
command invoking $(PYTHON) scripts/release.py; either update the Makefile to
normalize these variables (e.g., explicitly test for common truthy tokens like
"1", "true", or "yes" before expanding to --yes/--skip-checks/--skip-push) or
add a short comment next to the $(PYTHON) ... line documenting that these vars
must be "set to any value to enable" (or list accepted truthy tokens) so
consumers know how to enable the flags correctly when using
VERSION/REPOSITORY/YES/SKIP_CHECKS/SKIP_PUSH.
scripts/release.py (1)

384-397: Optional: surface that --repository is ignored under --dry-run.

Passing --dry-run --repository pypi today silently drops the explicit repository and reports Upload: skip (dry-run). A brief warning (or an explicit argparse conflict) would avoid a surprising silent override in scripts that compose these flags.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/release.py` around lines 384 - 397, The resolve_upload_plan function
currently ignores a provided repository when dry_run is true; update
resolve_upload_plan to detect when repository is non-None and dry_run is True
and surface that the --repository value is being ignored (e.g., emit a warning
via the existing logger or raise an argparse conflict earlier). Specifically, in
resolve_upload_plan (and/or where parse_repository_name is called) add a
conditional before returning the dry-run UploadPlan that logs a clear message
stating the explicit repository was ignored due to --dry-run so users scripting
with --repository won't be surprised; keep the returned
UploadPlan(repository=None, source="dry-run") behavior but ensure the warning
references the provided repository string and the dry_run flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/release.py`:
- Around line 130-138: The current context manager only catches Exception, so
KeyboardInterrupt/BaseException bypasses the cleanup and leaves VERSION_FILE
modified; change the control flow in the generator (the try/yield/except/else
block around VERSION_FILE, should_update and restore_after_success) to ensure
cleanup on all terminations by using a try/finally (or catching BaseException)
with a success flag: set success=False before yield, set success=True in the
normal path after yield, and in the finally block restore
VERSION_FILE.write_text(original) when should_update and (not success or
restore_after_success) so run_release_checks/build_distributions interruptions
still revert __about__.py.
- Around line 517-522: The release flow currently calls
upload_distributions(...) before push_release(...), which can publish artifacts
to PyPI without a matching remote git ref; reorder the steps so you
stage_and_commit_release_files(context.target_version) and
create_tag(context.target_version) as before, then call
push_release(context.branch) (respecting args.skip_push and failing fast if push
fails), and only after a successful push call
upload_distributions(upload_plan.repository); additionally consider gating or
erroring when args.skip_push is true but upload_plan.repository is present to
prevent unpushed uploads.
- Around line 98-116: Update all Path.read_text() and Path.write_text() calls to
explicitly use encoding="utf-8". Specifically, in get_version()/the code that
calls VERSION_FILE.read_text() and in update_version_file() replace
VERSION_FILE.read_text() and VERSION_FILE.write_text(updated) with calls that
pass encoding="utf-8" so the version file I/O uses UTF-8 regardless of locale;
do the same for any other read_text()/write_text() calls in this script (e.g.,
changelog/ABOUT file reads/writes) to cover the lines noted in the review.

---

Outside diff comments:
In `@pyproject.toml`:
- Around line 29-33: The pyproject dependency "django-rspack>=0.1.0" is not
published on PyPI and breaks installs; update the pyproject.toml dependencies to
a valid source by either replacing "django-rspack>=0.1.0" with the correct PyPI
package name and an available version, replacing it with a VCS spec like
"django-rspack @ git+https://..." pointing to the repo/commit, or removing it if
unused; ensure the same identifier is updated where referenced in CI/release
workflows and docs (the dependency list in pyproject.toml and any occurrences in
release workflows/RELEASING.md).

---

Nitpick comments:
In @.github/workflows/release.yml:
- Line 43: The pip editable extras target -e .[dev] is unquoted and can be
expanded by the shell; change it to use quoted extras like -e ".[dev]" (or -e
'.[dev]') in the pip install command that currently reads python -m pip install
"Django==${{ matrix.django-version }}" -e .[dev], and apply the same quoting fix
to the other occurrence of .[dev] in the workflow so both editable extras are
safely quoted.
- Around line 131-141: The release workflow contains a dead branch checking
GITHUB_EVENT_NAME == "push" (comparing GITHUB_REF_NAME to expected_tag) but the
workflow's on: only declares workflow_dispatch, so that branch never runs;
either add a push trigger that matches tags (e.g., push: tags: ['v*']) so the
push-based tag check (expected_tag / GITHUB_REF_NAME) is meaningful, or remove
the entire push-check block (the if that tests GITHUB_EVENT_NAME == "push" and
the expected_tag comparison) to avoid confusion—make the change around the
expected_tag / GITHUB_EVENT_NAME / GITHUB_REF_NAME logic to keep behavior
consistent with scripts/release.py tagging flow.

In `@Makefile`:
- Line 6: The Makefile currently treats any non-empty value as truthy for flags
YES, SKIP_CHECKS, and SKIP_PUSH when building the release command invoking
$(PYTHON) scripts/release.py; either update the Makefile to normalize these
variables (e.g., explicitly test for common truthy tokens like "1", "true", or
"yes" before expanding to --yes/--skip-checks/--skip-push) or add a short
comment next to the $(PYTHON) ... line documenting that these vars must be "set
to any value to enable" (or list accepted truthy tokens) so consumers know how
to enable the flags correctly when using
VERSION/REPOSITORY/YES/SKIP_CHECKS/SKIP_PUSH.

In `@scripts/release.py`:
- Around line 384-397: The resolve_upload_plan function currently ignores a
provided repository when dry_run is true; update resolve_upload_plan to detect
when repository is non-None and dry_run is True and surface that the
--repository value is being ignored (e.g., emit a warning via the existing
logger or raise an argparse conflict earlier). Specifically, in
resolve_upload_plan (and/or where parse_repository_name is called) add a
conditional before returning the dry-run UploadPlan that logs a clear message
stating the explicit repository was ignored due to --dry-run so users scripting
with --repository won't be surprised; keep the returned
UploadPlan(repository=None, source="dry-run") behavior but ensure the warning
references the provided repository string and the dry_run flag.

In `@tests/test_release.py`:
- Around line 117-126: The test
test_release_check_commands_sets_example_python_for_e2e is brittle because it
assumes the E2E tuple is at commands[-1]; instead locate the tuple by filtering
the iterable returned by release.release_check_commands(skip_checks=False)
(e.g., find the entry where the command list equals ["./bin/test-ci"] or where
the second element's name == "example") and then assert on that found tuple's
environment value; update the test to use next(...) or a comprehension to select
the matching tuple from commands before asserting that
example_test[2]["REACT_ON_DJANGO_EXAMPLE_PYTHON"] == release.sys.executable.
- Around line 8-15: The tests repeatedly re-import scripts/release.py by calling
load_release_module() in each test; convert load_release_module into a
module-scoped pytest fixture (e.g., add `@pytest.fixture`(scope="module") above
load_release_module) so the module is imported once per test module, register it
in sys.modules as before and return the loaded module, then update tests to
accept a release fixture parameter instead of calling load_release_module()
inside each test to remove duplication and repeated imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: deea7b76-4bde-4dbe-88cb-b0fd3dc9b2e8

📥 Commits

Reviewing files that changed from the base of the PR and between 68923be and 34bb66e.

📒 Files selected for processing (9)
  • .github/workflows/release.yml
  • CHANGELOG.md
  • Makefile
  • RELEASING.md
  • pyproject.toml
  • scripts/release.py
  • src/react_on_django/__about__.py
  • src/react_on_django/__init__.py
  • tests/test_release.py

Comment thread scripts/release.py Outdated
Comment thread scripts/release.py Outdated
Comment thread scripts/release.py Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Changelog header regex matches too many heading levels
    • Restricted CHANGELOG_HEADER_RE from matching ##+ to exactly ##, so only level-2 release headings are treated as changelog version boundaries.

Create PR

Or push these changes by commenting:

@cursor push a22557ab54
Preview (a22557ab54)
diff --git a/scripts/release.py b/scripts/release.py
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -26,7 +26,7 @@
     r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"
     r"(?:(?P<pre>a|b|rc)(?P<pre_n>\d+))?$"
 )
-CHANGELOG_HEADER_RE = re.compile(r"^##+\s+\[(?P<version>[^\]]+)\](?:\s+-\s+.+)?$")
+CHANGELOG_HEADER_RE = re.compile(r"^##\s+\[(?P<version>[^\]]+)\](?:\s+-\s+.+)?$")
 
 
 class ReleaseError(RuntimeError):

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit d5b5ad8. Configure here.

Comment thread scripts/release.py
r"^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)"
r"(?:(?P<pre>a|b|rc)(?P<pre_n>\d+))?$"
)
CHANGELOG_HEADER_RE = re.compile(r"^##+\s+\[(?P<version>[^\]]+)\](?:\s+-\s+.+)?$")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog header regex matches too many heading levels

Low Severity

CHANGELOG_HEADER_RE uses ##+ which matches headings with two or more # characters. This means ### [Breaking] or any subsection header containing square brackets would be treated as a version boundary. In latest_changelog_version, such a header would cause a ReleaseError from parse_version. In extract_changelog_section, it would prematurely terminate section collection, potentially making changelog_has_section return False for a valid section. The intent is to match only ##-level version headings.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d5b5ad8. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant