From c484226c10e69865ad61d9f3cdd5bf723e4268a2 Mon Sep 17 00:00:00 2001 From: Davide Silvestri <75379892+silvestrid@users.noreply.github.com> Date: Thu, 16 Apr 2026 09:28:34 +0200 Subject: [PATCH] chore: integrate changelog with backend pyproject.toml and just file (#5195) * chore: integrate changelog with backend pyproject.toml and just file * address copliot feedback * address feedback --- backend/pyproject.toml | 3 + backend/uv.lock | 37 ++++++ changelog/.flake8 | 9 -- changelog/Makefile | 107 ------------------ changelog/README.md | 27 +---- .../fix_reusable_password_reset_tokens.json | 2 +- changelog/pytest.ini | 2 - changelog/requirements.txt | 8 -- changelog/setup.py | 18 --- changelog/src/handler.py | 22 ++-- .../tests/changelog/test_changelog_handler.py | 31 +++++ .../test_changelog_legacy_converter.py | 64 ----------- justfile | 12 ++ 13 files changed, 105 insertions(+), 237 deletions(-) delete mode 100644 changelog/.flake8 delete mode 100644 changelog/Makefile delete mode 100644 changelog/pytest.ini delete mode 100644 changelog/requirements.txt delete mode 100644 changelog/setup.py delete mode 100644 changelog/tests/changelog/test_changelog_legacy_converter.py diff --git a/backend/pyproject.toml b/backend/pyproject.toml index df5c5b4a2d..4c65fa90aa 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -152,6 +152,9 @@ dev = [ "build", "rust-just>=1.46.0", ] +changelog = [ + "typer==0.24.1", +] [tool.hatch.metadata] allow-direct-references = true diff --git a/backend/uv.lock b/backend/uv.lock index 47243a2850..ed0848f43d 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -29,6 +29,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/99/fc813cd978842c26c82534010ea849eee9ab3a13ea2b74e95cb9c99e747b/amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2", size = 50944, upload-time = "2024-11-12T19:55:41.782Z" }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -275,6 +284,9 @@ dependencies = [ ] [package.dev-dependencies] +changelog = [ + { name = "typer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] dev = [ { name = "argh", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "backports-cached-property", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -405,6 +417,7 @@ requires-dist = [ ] [package.metadata.requires-dev] +changelog = [{ name = "typer", specifier = "==0.24.1" }] dev = [ { name = "argh", specifier = "==0.31.3" }, { name = "backports-cached-property", specifier = "==1.0.2" }, @@ -3494,6 +3507,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/2c/ca6dd598b384bc1ce581e24aaae0f2bed4ccac57749d5c3befbb5e742081/service_identity-24.2.0-py3-none-any.whl", hash = "sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85", size = 11364, upload-time = "2024-10-26T07:21:56.302Z" }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -3683,6 +3705,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/05/bdb6318120cac9bf97779674f49035e0595d894b42d4c43b60637bafdb1f/txaio-25.12.2-py3-none-any.whl", hash = "sha256:5f6cd6c6b397fc3305790d15efd46a2d5b91cdbefa96543b4f8666aeb56ba026", size = 31208, upload-time = "2025-12-09T04:30:27.811Z" }, ] +[[package]] +name = "typer" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "rich", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "shellingham", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, +] + [[package]] name = "types-pyyaml" version = "6.0.12.20250915" diff --git a/changelog/.flake8 b/changelog/.flake8 deleted file mode 100644 index 1c5f3de8e2..0000000000 --- a/changelog/.flake8 +++ /dev/null @@ -1,9 +0,0 @@ -[flake8] -extend-ignore = E203, W503, F541, E501 -max-doc-length = 88 -per-file-ignores = - tests/*: F841 -exclude = - .git, - __pycache__, - diff --git a/changelog/Makefile b/changelog/Makefile deleted file mode 100644 index 9bfb6f0834..0000000000 --- a/changelog/Makefile +++ /dev/null @@ -1,107 +0,0 @@ -# 'realpath' command may be not available in Mac OS. -# In that case we use 'grealpath' from 'coreutils' package. -ifeq ($(shell uname -s),Darwin) - REALPATH := grealpath -em -else - REALPATH := realpath -em -endif - -# Get the Python interpreter path -PYTHON:=$(shell which python3.14 || which python3 || which python) - -# Get the directory of the current Makefile. -# If the Makefile is executed from another directory (e.g, using the '-C') flag -# $(WORKDIR) will contain the absolute path to the current Makefile directory, -# in this case '..../baserow/changelog/' (with the trailing '/') -WORKDIR := $(dir $($(REALPATH) $(lastword $(MAKEFILE_LIST)))) -ARGS := $(filter-out $(firstword $(MAKECMDGOALS)),$(MAKECMDGOALS)) -REQUIREMENTS := $(WORKDIR)requirements.txt -VENV := $(WORKDIR)venv -PIP := $(VENV)/bin/python -m pip - -# Executables -CHANGELOG := $(VENV)/bin/python $(WORKDIR)src/changelog.py -BANDIT := $(VENV)/bin/bandit -BLACK := $(VENV)/bin/black -FLAKE8 := $(VENV)/bin/flake8 -ISORT := $(VENV)/bin/isort -PYTEST := $(VENV)/bin/pytest - -# Display the help message. -# If no targets are specified this will be executed by default. -help: - @echo "make commands available" - @echo " add add a new changelog entry" - @echo " release add a new release. Usage: 'make release -- 1.2.3'" - @echo " purge delete all changelog entries" - @echo " generate generate a new 'changelog.md' file without making a new release" - @echo "" - @echo " lint run lint/style tools" - @echo " format run black to reformat the code" - @echo " sort sort the imports" - @echo " test run the test suite" - @echo "" - @echo " clean remove Python virtual environment directory" - -# Create a new Python virtual environment and install the dependencies. -# When finished, the 'touch' command updates the modified date of the 'VENV' -# folder, avoiding unneeded rebuilds -$(VENV) $(PIP): $(PYTHON) $(REQUIREMENTS) - $(PYTHON) -m venv $(VENV) - $(PIP) install -r $(REQUIREMENTS) - $(PIP) install . - touch $(VENV) - -$(BANDIT) $(BLACK) $(FLAKE8) $(ISORT) $(PYTEST): $(VENV) - -# Add a new changelog entry -.PHONY: add -add: $(VENV) - $(CHANGELOG) add $(ARGS) - -# Add a new release -.PHONY: release -release: $(VENV) - $(CHANGELOG) release $(ARGS) - -# Delete all changelog entries -.PHONY: purge -purge: $(VENV) - $(CHANGELOG) purge $(ARGS) - -# Generate a new 'changelog.md' file -.PHONY: generate -generate: $(VENV) - $(CHANGELOG) generate $(ARGS) - -# Check the code for linting errors -.PHONY: lint -lint: $(BANDIT) $(BLACK) $(FLAKE8) $(ISORT) - $(FLAKE8) src tests - $(BLACK) . --extend-exclude='/generated/' --check - $(ISORT) --check --skip generated --profile black src tests - $(BANDIT) -r src - -# Reformat the code according to black style -.PHONY: format -format: $(BLACK) - $(BLACK) . || exit; - -# Sort the Python import statements -.PHONY: sort -sort: $(ISORT) - $(ISORT) --skip generated --profile black src tests || exit; - -# Run the test suite -.PHONY: test -test: $(PYTEST) - $(PYTEST) tests || exit; - -# Remove the virtual environment directory -.PHONY: clean -clean: - -rm -r $(VENV) - -# Prevent make from interpreting the arguments as targets -%: - @: diff --git a/changelog/README.md b/changelog/README.md index b09ee50962..9d4c7c27ca 100644 --- a/changelog/README.md +++ b/changelog/README.md @@ -2,27 +2,12 @@ This util allows you to generate changelog entries without causing merge conflicts. ## Getting started -### Setup environment -Go into the `changelog` folder and run the following commands -#### Create virtual environment -```shell -python3 -m venv venv -``` - -#### Activate environment -```shell -source venv/bin/activate -``` - -#### Install dependencies -```shell -python3 -m pip install -r requirements.txt -``` +All changelog commands run from the repo root via `just`: ### Add a new entry ```shell -python3 ./src/changelog.py add +just changelog add ``` The command will ask you for the required information to create a new changelog entry. @@ -33,7 +18,7 @@ your workflow. ### Make a release ```shell -python3 ./src/changelog.py release +just changelog release ``` The command will do the following: @@ -46,7 +31,7 @@ After you made a release you can move the `changelog.md` file to the root of the ## Additional commands ### Purge ```shell -python3 ./src/changelog.py purge +just changelog purge ``` This command will delete: @@ -58,7 +43,7 @@ Be careful when running `purge` since it will delete these files permanently! ### Generate ```shell -python3 ./src/changelog.py generate +just changelog generate ``` This command will generate a new `changelog.md` file without making a new release. @@ -97,4 +82,4 @@ via the CLI. ### What should I do if I need to make changes to an existing release? If you have generated a new release, and you notice afterwards that you meant to change one of the entries before making the release you can change the content of the changelog -entry in the JSON file directly and then run `python3 ./src/changelog.py generate` +entry in the JSON file directly and then run `just changelog generate` diff --git a/changelog/entries/unreleased/bug/fix_reusable_password_reset_tokens.json b/changelog/entries/unreleased/bug/fix_reusable_password_reset_tokens.json index df1426345e..efd037f08f 100644 --- a/changelog/entries/unreleased/bug/fix_reusable_password_reset_tokens.json +++ b/changelog/entries/unreleased/bug/fix_reusable_password_reset_tokens.json @@ -3,7 +3,7 @@ "message": "Fix password reset tokens not being invalidated after use, allowing persistent account takeover. Tokens are now single-use, token expiry reduced to 1 hour, and a confirmation email is sent on every password change.", "issue_origin": "github", "issue_number": 5165, - "domain": "backend", + "domain": "core", "bullet_points": [], "created_at": "2026-04-10" } diff --git a/changelog/pytest.ini b/changelog/pytest.ini deleted file mode 100644 index ce20569b30..0000000000 --- a/changelog/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[pytest] -pythonpath = "src" diff --git a/changelog/requirements.txt b/changelog/requirements.txt deleted file mode 100644 index e7b0168c26..0000000000 --- a/changelog/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -bandit==1.7.8 -black==26.3.1 -flake8==7.0.0 -isort==5.13.2 -pygit2==1.14.1 -pytest==8.1.1 -typer==0.12.3 -pyfakefs==5.4.1 diff --git a/changelog/setup.py b/changelog/setup.py deleted file mode 100644 index 904f0725fa..0000000000 --- a/changelog/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python -import os - -from setuptools import find_packages, setup - -PROJECT_DIR = os.path.dirname(__file__) - - -setup( - name="changelog", - url="https://baserow.io", - author="Bram Wiepjes (Baserow)", - author_email="bram@baserow.io", - platforms=["linux"], - package_dir={"": "src"}, - packages=find_packages("src"), - include_package_data=True, -) diff --git a/changelog/src/handler.py b/changelog/src/handler.py index 23d658e763..fc17f13602 100644 --- a/changelog/src/handler.py +++ b/changelog/src/handler.py @@ -1,15 +1,16 @@ +import glob import json import os import shutil -import glob +import subprocess from datetime import datetime, timezone from json import JSONDecodeError from pathlib import Path +from shutil import which from typing import Dict, List, Optional, Union -from domains import domain_types from changelog_entry import changelog_entry_types -from pygit2 import Repository +from domains import domain_types LINE_BREAK_CHARACTER = "\n" INDENT_CHARACTER = " " @@ -250,11 +251,18 @@ def generate_entry_file_name( @staticmethod def get_issue_number() -> Union[int, None]: - potential_issue_number = Repository(".").head.shorthand.split("-")[0] - try: - return int(potential_issue_number) - except ValueError: + git_path = which("git") + if git_path is None: + return None + branch = subprocess.run( # noqa: S603 + [git_path, "rev-parse", "--abbrev-ref", "HEAD"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() + return int(branch.split("-")[0]) + except (OSError, subprocess.CalledProcessError, ValueError): return None def write_release_meta_data(self, name: str): diff --git a/changelog/tests/changelog/test_changelog_handler.py b/changelog/tests/changelog/test_changelog_handler.py index 5cded44b9c..05d9cfd9c0 100644 --- a/changelog/tests/changelog/test_changelog_handler.py +++ b/changelog/tests/changelog/test_changelog_handler.py @@ -1,4 +1,6 @@ import json +import subprocess +from unittest.mock import patch from src.changelog_entry import BugChangelogEntry from src.handler import MAXIMUM_FILE_NAME_MESSAGE_LENGTH, ChangelogHandler @@ -162,3 +164,32 @@ def test_is_release_name_unique(fs): assert ChangelogHandler().is_release_name_unique("exists") is False assert ChangelogHandler().is_release_name_unique("not exists") is True + + +def test_get_issue_number_numeric_branch(): + result = subprocess.CompletedProcess(args=[], returncode=0, stdout="123-my-feature\n") + with patch("src.handler.which", return_value="/usr/bin/git"), patch( + "src.handler.subprocess.run", return_value=result + ): + assert ChangelogHandler.get_issue_number() == 123 + + +def test_get_issue_number_non_numeric_branch(): + result = subprocess.CompletedProcess(args=[], returncode=0, stdout="feature-branch\n") + with patch("src.handler.which", return_value="/usr/bin/git"), patch( + "src.handler.subprocess.run", return_value=result + ): + assert ChangelogHandler.get_issue_number() is None + + +def test_get_issue_number_git_not_found(): + with patch("src.handler.which", return_value=None): + assert ChangelogHandler.get_issue_number() is None + + +def test_get_issue_number_git_fails(): + with patch("src.handler.which", return_value="/usr/bin/git"), patch( + "src.handler.subprocess.run", + side_effect=subprocess.CalledProcessError(128, "git"), + ): + assert ChangelogHandler.get_issue_number() is None diff --git a/changelog/tests/changelog/test_changelog_legacy_converter.py b/changelog/tests/changelog/test_changelog_legacy_converter.py deleted file mode 100644 index fc1ce84226..0000000000 --- a/changelog/tests/changelog/test_changelog_legacy_converter.py +++ /dev/null @@ -1,64 +0,0 @@ -import re - -from _pytest.fixtures import fixture -from changelog_legacy_converter import main - -from changelog import purge, release - -legacy_changelog = open("../changelog.md", "r") - -""" -WARNING -These test will alter your file system. Don't run them if you have unsaved -changes! - -You will also need to call this file from the root (/changelog) dir to make -the paths work! -""" - - -@fixture -def changelog(): - # Make sure everything is deleted first - purge() - - # Generate the changelog json files - main() - - # Make a release to generate the changelog.md - release("Add", "./src") - - return open("./src/changelog.md", "r") - - -def get_unique_tokens_from_file(file): - tokens = set() - for line in file: - words = re.split(" |_", line) - - words_sanitised = [ - "".join(e for e in word if e.isalnum()).lower() for word in words - ] - - tokens = tokens.union(set(words_sanitised)) - return tokens - - -def test_token_match(changelog): - tokens_to_ignore = {"unreleased"} - - tokens_legacy = get_unique_tokens_from_file(legacy_changelog).union( - tokens_to_ignore - ) - tokens_generated = get_unique_tokens_from_file(changelog).union(tokens_to_ignore) - - assert tokens_legacy == tokens_generated - - -# Note: commented out for now since we need some more sanitising to make this -# test work properly -# def test_lines_match(): -# # Load the generated changelog -# generated_changelog = open("changelog.md", "r") -# -# assert set(generated_changelog.readlines()) == set(legacy_changelog.readlines()) diff --git a/justfile b/justfile index 2218be8be3..9029cc437b 100644 --- a/justfile +++ b/justfile @@ -1174,6 +1174,18 @@ env-clear: echo "echo 'No .env.local found'" fi +# Run changelog command (e.g., just changelog add, just changelog release 2.3.0) +[group('5 - utilities')] +[doc("Changelog: just changelog ")] +changelog *args: + cd backend && uv run --group changelog python ../changelog/src/changelog.py {{ args }} + +# Run changelog tests +[group('4 - testing')] +[doc("Run changelog unit tests")] +changelog-test *args: + cd backend && PYTHONPATH="../changelog/src:../changelog:${PYTHONPATH:-}" uv run --group changelog --group dev pytest ../changelog/tests/ {{ args }} + # ============================================================================= # CI Docker Image Testing # =============================================================================