From 839c3b9a30bc10ca0356da472993a990a755d453 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 20 Apr 2026 18:20:00 -0700 Subject: [PATCH 1/3] add towncrier to manage changelogs --- .github/workflows/changelog.yml | 84 +++++++++++++++++++ CONTRIBUTING.md | 32 +++++++ news/+towncrier.misc.md | 1 + news/.gitkeep | 0 packages/hatch-reflex-pyi/news/.gitkeep | 0 packages/reflex-base/news/.gitkeep | 0 packages/reflex-components-code/news/.gitkeep | 0 packages/reflex-components-core/news/.gitkeep | 0 .../news/.gitkeep | 0 .../reflex-components-gridjs/news/.gitkeep | 0 .../reflex-components-lucide/news/.gitkeep | 0 .../reflex-components-markdown/news/.gitkeep | 0 .../reflex-components-moment/news/.gitkeep | 0 .../reflex-components-plotly/news/.gitkeep | 0 .../reflex-components-radix/news/.gitkeep | 0 .../news/.gitkeep | 0 .../reflex-components-recharts/news/.gitkeep | 0 .../reflex-components-sonner/news/.gitkeep | 0 packages/reflex-docgen/news/.gitkeep | 0 packages/reflex-ui-shared/news/.gitkeep | 0 packages/reflex-ui/news/.gitkeep | 0 pyproject.toml | 50 ++++++++++- uv.lock | 16 ++++ 23 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/changelog.yml create mode 100644 news/+towncrier.misc.md create mode 100644 news/.gitkeep create mode 100644 packages/hatch-reflex-pyi/news/.gitkeep create mode 100644 packages/reflex-base/news/.gitkeep create mode 100644 packages/reflex-components-code/news/.gitkeep create mode 100644 packages/reflex-components-core/news/.gitkeep create mode 100644 packages/reflex-components-dataeditor/news/.gitkeep create mode 100644 packages/reflex-components-gridjs/news/.gitkeep create mode 100644 packages/reflex-components-lucide/news/.gitkeep create mode 100644 packages/reflex-components-markdown/news/.gitkeep create mode 100644 packages/reflex-components-moment/news/.gitkeep create mode 100644 packages/reflex-components-plotly/news/.gitkeep create mode 100644 packages/reflex-components-radix/news/.gitkeep create mode 100644 packages/reflex-components-react-player/news/.gitkeep create mode 100644 packages/reflex-components-recharts/news/.gitkeep create mode 100644 packages/reflex-components-sonner/news/.gitkeep create mode 100644 packages/reflex-docgen/news/.gitkeep create mode 100644 packages/reflex-ui-shared/news/.gitkeep create mode 100644 packages/reflex-ui/news/.gitkeep diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000000..d252b06b4a0 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,84 @@ +name: changelog + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.id }} + cancel-in-progress: true + +on: + pull_request: + branches: ["main"] + +jobs: + changelog: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Check for skip-changelog label + id: skip + shell: bash + run: | + if ${{ contains(github.event.pull_request.labels.*.name, 'skip-changelog') }}; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "PR has 'skip-changelog' label; bypassing changelog check." + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - uses: actions/checkout@v4 + if: steps.skip.outputs.skip != 'true' + with: + fetch-depth: 0 + - uses: ./.github/actions/setup_build_env + if: steps.skip.outputs.skip != 'true' + with: + python-version: "3.14" + run-uv-sync: true + - name: Determine affected packages + if: steps.skip.outputs.skip != 'true' + id: affected + shell: bash + run: | + set -euo pipefail + changed=$(git diff --name-only origin/main...HEAD) + affected=() + if printf '%s\n' "$changed" | grep -qE '^reflex/'; then + affected+=(".") + fi + for pkg_dir in packages/*/; do + pkg=$(basename "$pkg_dir") + [[ "$pkg" == "integrations-docs" ]] && continue + if printf '%s\n' "$changed" | grep -qE "^packages/$pkg/src/"; then + affected+=("${pkg_dir%/}") + fi + done + printf '%s\n' "${affected[@]}" > affected.txt + echo "Affected packages:" + cat affected.txt + - name: Check for news fragments + if: steps.skip.outputs.skip != 'true' + shell: bash + run: | + set -euo pipefail + if [ ! -s affected.txt ]; then + echo "No packaged source changes in this PR; no changelog fragments required." + exit 0 + fi + failed=0 + while IFS= read -r pkg_dir; do + [ -z "$pkg_dir" ] && continue + echo "::group::towncrier check ($pkg_dir)" + if ! uv run towncrier check --config pyproject.toml --dir "$pkg_dir" --compare-with origin/main; then + failed=1 + fi + echo "::endgroup::" + done < affected.txt + if [ "$failed" -ne 0 ]; then + echo "" + echo "One or more affected packages is missing a news fragment under /news/." + echo "Add a fragment named ..md where is one of:" + echo " breaking, deprecation, feature, bugfix, performance, docs, misc" + echo "Or apply the 'skip-changelog' label if the change is genuinely not user-facing." + exit 1 + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac01b79f010..f2f8580e7d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,6 +58,38 @@ Within the 'test' directory of Reflex you can add to a test file already there o - Any edge cases or potential problem areas. - Any interactions between different parts of the code. +## 📝 Changelog Fragments + +Each PR that changes the source of a published package must add a news fragment describing the change. Fragments are assembled into `CHANGELOG.md` at release time by [towncrier](https://towncrier.readthedocs.io/). + +**Where:** add the fragment under the affected package's `news/` directory. For the main `reflex` package, that's the repo-root `news/`. For sub-packages it's `packages//news/`. + +**Filename:** `..md`, where `` is one of: + +| Type | When to use | +| --- | --- | +| `breaking` | Backwards-incompatible change users need to adapt to | +| `deprecation` | API marked deprecated but still functional | +| `feature` | New user-facing functionality | +| `bugfix` | Fix for an incorrect behavior | +| `performance` | Speed, memory, or startup improvement | +| `docs` | Documentation or docstring changes | +| `misc` | Internal refactor, build, or dependency change that still warrants mention | + +**Content:** one or two sentences, written for users reading release notes (not reviewers of the diff). + +**Create a fragment from the CLI:** + +```bash +uv run towncrier create --config pyproject.toml --dir packages/reflex-ui 1234.feature.md +``` + +Drop `--dir` for a fragment against the main `reflex` package. + +If you don't yet know the PR number, use an [orphan fragment](https://towncrier.readthedocs.io/en/stable/cli.html#towncrier-create) (`+.feature.md`) and rename it after opening the PR. + +**Skipping the fragment check:** for PRs that are genuinely not user-facing (CI-only tweaks, script fixes, test-only changes), apply the `skip-changelog` label on the PR to bypass the changelog CI check. + ## ✅ Making a PR Once you solve a current issue or improvement to Reflex, you can make a PR, and we will review the changes. diff --git a/news/+towncrier.misc.md b/news/+towncrier.misc.md new file mode 100644 index 00000000000..81d7c7c9941 --- /dev/null +++ b/news/+towncrier.misc.md @@ -0,0 +1 @@ +Introduced towncrier-based changelog management. Each PR that changes package source now adds a fragment under the affected package's `news/` directory; fragments are assembled into `CHANGELOG.md` at release time. See CONTRIBUTING.md for the full workflow. diff --git a/news/.gitkeep b/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/hatch-reflex-pyi/news/.gitkeep b/packages/hatch-reflex-pyi/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-base/news/.gitkeep b/packages/reflex-base/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-code/news/.gitkeep b/packages/reflex-components-code/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-core/news/.gitkeep b/packages/reflex-components-core/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-dataeditor/news/.gitkeep b/packages/reflex-components-dataeditor/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-gridjs/news/.gitkeep b/packages/reflex-components-gridjs/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-lucide/news/.gitkeep b/packages/reflex-components-lucide/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-markdown/news/.gitkeep b/packages/reflex-components-markdown/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-moment/news/.gitkeep b/packages/reflex-components-moment/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-plotly/news/.gitkeep b/packages/reflex-components-plotly/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-radix/news/.gitkeep b/packages/reflex-components-radix/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-react-player/news/.gitkeep b/packages/reflex-components-react-player/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-recharts/news/.gitkeep b/packages/reflex-components-recharts/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-components-sonner/news/.gitkeep b/packages/reflex-components-sonner/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-docgen/news/.gitkeep b/packages/reflex-docgen/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-ui-shared/news/.gitkeep b/packages/reflex-ui-shared/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/reflex-ui/news/.gitkeep b/packages/reflex-ui/news/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pyproject.toml b/pyproject.toml index 7ab5355c99b..bc39579bdf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,6 +110,7 @@ dev = [ "sqlmodel", "starlette-admin", "toml", + "towncrier", "uvicorn", ] @@ -166,7 +167,6 @@ target-version = "py310" output-format = "concise" extend-exclude = ["tests/units/reflex_base/utils/pyi_generator/golden"] lint.isort.split-on-trailing-comma = false -preview = true lint.select = ["ALL"] lint.ignore = [ "A", @@ -212,6 +212,7 @@ lint.flake8-bugbear.extend-immutable-calls = [ "reflex_base.utils.types.Unset", "reflex_base.vars.base.Var.create", ] +preview = true [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] @@ -301,6 +302,53 @@ exclude_also = [ [tool.coverage.html] directory = "coverage_html_report" +[tool.towncrier] +# Shared monorepo config. package/name intentionally empty — each invocation +# passes --dir --name --version to target a +# specific package. See https://towncrier.readthedocs.io/en/stable/monorepo.html +package = "" +name = "" +directory = "news" +filename = "CHANGELOG.md" +title_format = "## {version} ({project_date})" +issue_format = "[#{issue}](https://github.com/reflex-dev/reflex/issues/{issue})" +start_string = "\n" + +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking Changes" +showcontent = true + +[[tool.towncrier.type]] +directory = "deprecation" +name = "Deprecations" +showcontent = true + +[[tool.towncrier.type]] +directory = "feature" +name = "Features" +showcontent = true + +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bug Fixes" +showcontent = true + +[[tool.towncrier.type]] +directory = "performance" +name = "Performance" +showcontent = true + +[[tool.towncrier.type]] +directory = "docs" +name = "Documentation" +showcontent = true + +[[tool.towncrier.type]] +directory = "misc" +name = "Miscellaneous" +showcontent = true + [tool.uv] required-version = ">=0.7.0" diff --git a/uv.lock b/uv.lock index 272ee87d12c..5c4e0740b6b 100644 --- a/uv.lock +++ b/uv.lock @@ -3488,6 +3488,7 @@ dev = [ { name = "sqlmodel" }, { name = "starlette-admin" }, { name = "toml" }, + { name = "towncrier" }, { name = "uvicorn" }, ] @@ -3561,6 +3562,7 @@ dev = [ { name = "sqlmodel" }, { name = "starlette-admin" }, { name = "toml" }, + { name = "towncrier" }, { name = "uvicorn" }, ] @@ -4388,6 +4390,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] +[[package]] +name = "towncrier" +version = "25.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "jinja2" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/eb/5bf25a34123698d3bbab39c5bc5375f8f8bcbcc5a136964ade66935b8b9d/towncrier-25.8.0.tar.gz", hash = "sha256:eef16d29f831ad57abb3ae32a0565739866219f1ebfbdd297d32894eb9940eb1", size = 76322, upload-time = "2025-08-30T11:41:55.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/06/8ba22ec32c74ac1be3baa26116e3c28bc0e76a5387476921d20b6fdade11/towncrier-25.8.0-py3-none-any.whl", hash = "sha256:b953d133d98f9aeae9084b56a3563fd2519dfc6ec33f61c9cd2c61ff243fb513", size = 65101, upload-time = "2025-08-30T11:41:53.644Z" }, +] + [[package]] name = "tqdm" version = "4.67.3" From 0b2945e9461d4cfec22e60a81cfe8945ca277e79 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 20 Apr 2026 18:23:49 -0700 Subject: [PATCH 2/3] rename file --- news/{+towncrier.misc.md => 6350.misc.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename news/{+towncrier.misc.md => 6350.misc.md} (100%) diff --git a/news/+towncrier.misc.md b/news/6350.misc.md similarity index 100% rename from news/+towncrier.misc.md rename to news/6350.misc.md From 9b3e993ce521802d17e63c108796f046a7c4380b Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 20 Apr 2026 18:25:43 -0700 Subject: [PATCH 3/3] use env instead of string interpolation --- .github/workflows/changelog.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index d252b06b4a0..e12c0ac72ce 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -19,8 +19,10 @@ jobs: - name: Check for skip-changelog label id: skip shell: bash + env: + SKIP_CHANGELOG: ${{ contains(github.event.pull_request.labels.*.name, 'skip-changelog') }} run: | - if ${{ contains(github.event.pull_request.labels.*.name, 'skip-changelog') }}; then + if [ "$SKIP_CHANGELOG" = "true" ]; then echo "skip=true" >> "$GITHUB_OUTPUT" echo "PR has 'skip-changelog' label; bypassing changelog check." else