diff --git a/.github/workflows/docspublish.yml b/.github/workflows/docspublish.yml index cb5d78e751..2c03a099df 100644 --- a/.github/workflows/docspublish.yml +++ b/.github/workflows/docspublish.yml @@ -16,6 +16,15 @@ jobs: fetch-depth: 0 - name: Set up Python uses: astral-sh/setup-uv@v7 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: '1.25.1' + - name: Set up VHS + run: | + sudo apt update + sudo apt install -y ffmpeg ttyd + go install github.com/charmbracelet/vhs@latest - name: Install dependencies run: | uv --version @@ -27,7 +36,7 @@ jobs: run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - git add docs/images/cli_help + git add docs/images/cli_help docs/images/cli_interactive if [[ -n "$(git status --porcelain)" ]]; then git commit -m "docs(cli/screenshots): update CLI screenshots" -m "[skip ci]" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01570a526a..ba24a07e66 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,7 +56,7 @@ repos: - tomli - repo: https://github.com/commitizen-tools/commitizen - rev: v4.13.7 # automatically updated by Commitizen + rev: v4.13.8 # automatically updated by Commitizen hooks: - id: commitizen - id: commitizen-branch diff --git a/CHANGELOG.md b/CHANGELOG.md index db8403be20..7ab2bc2481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v4.13.8 (2026-02-18) + +### Fix + +- **config**: fix contains_commitizen_section failing for completely empty files + ## v4.13.7 (2026-02-09) ### Fix diff --git a/commitizen/__version__.py b/commitizen/__version__.py index 03dab05ffc..061dda4064 100644 --- a/commitizen/__version__.py +++ b/commitizen/__version__.py @@ -1 +1 @@ -__version__ = "4.13.7" +__version__ = "4.13.8" diff --git a/commitizen/config/json_config.py b/commitizen/config/json_config.py index 3951c52854..688a6b9fec 100644 --- a/commitizen/config/json_config.py +++ b/commitizen/config/json_config.py @@ -27,7 +27,10 @@ def __init__(self, *, data: bytes | str, path: Path) -> None: def contains_commitizen_section(self) -> bool: with self.path.open("rb") as json_file: - config_doc = json.load(json_file) + try: + config_doc = json.load(json_file) + except json.JSONDecodeError: + return False return config_doc.get("commitizen") is not None def init_empty_config_content(self) -> None: diff --git a/commitizen/config/yaml_config.py b/commitizen/config/yaml_config.py index 0e79735f2f..1e9610e17a 100644 --- a/commitizen/config/yaml_config.py +++ b/commitizen/config/yaml_config.py @@ -35,7 +35,7 @@ def init_empty_config_content(self) -> None: def contains_commitizen_section(self) -> bool: with self.path.open("rb") as yaml_file: config_doc = yaml.load(yaml_file, Loader=yaml.FullLoader) - return config_doc.get("commitizen") is not None + return config_doc is not None and config_doc.get("commitizen") is not None def _parse_setting(self, data: bytes | str) -> None: """We expect to have a section in cz.yaml looking like diff --git a/docs/README.md b/docs/README.md index 313d5494d2..b4b97884ab 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ [![Codecov](https://img.shields.io/codecov/c/github/commitizen-tools/commitizen.svg?style=flat-square)](https://codecov.io/gh/commitizen-tools/commitizen) [![prek](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/j178/prek/master/docs/assets/badge-v0.json&style=flat-square&color=brightgreen)](https://github.com/j178/prek) -![Using Commitizen cli](images/demo.gif) +![Using Commitizen cli](images/cli_interactive/commit.gif) --- diff --git a/docs/commands/commit.md b/docs/commands/commit.md index 54e0c8b07a..5e93a2274f 100644 --- a/docs/commands/commit.md +++ b/docs/commands/commit.md @@ -4,7 +4,7 @@ ## Overview -![Using Commitizen cli](../images/demo.gif) +![Using Commitizen cli](../images/cli_interactive/commit.gif) The `commit` command provides an interactive way to create structured commits. Use either: diff --git a/docs/commands/init.md b/docs/commands/init.md index b673ba1276..122e1bef5f 100644 --- a/docs/commands/init.md +++ b/docs/commands/init.md @@ -14,7 +14,7 @@ cz init When you run `cz init`, Commitizen will guide you through an interactive setup process: -![init](../images/init.gif) +![init](../images/cli_interactive/init.gif) ## Configuration File diff --git a/docs/images/cli_interactive/commit.gif b/docs/images/cli_interactive/commit.gif new file mode 100644 index 0000000000..12b62cd223 Binary files /dev/null and b/docs/images/cli_interactive/commit.gif differ diff --git a/docs/images/cli_interactive/init.gif b/docs/images/cli_interactive/init.gif new file mode 100644 index 0000000000..2f8a01f8b0 Binary files /dev/null and b/docs/images/cli_interactive/init.gif differ diff --git a/docs/images/commit.tape b/docs/images/commit.tape new file mode 100644 index 0000000000..42aa25a023 --- /dev/null +++ b/docs/images/commit.tape @@ -0,0 +1,116 @@ +Output cli_interactive/commit.gif + +Require cz + +# Use bash for cross-platform compatibility (macOS, Linux, Windows) +Set Shell bash + +Set FontSize 16 +Set Width 878 +Set Height 568 +Set Padding 20 +Set TypingSpeed 50ms + +Set Theme { + "name": "Commitizen", + "black": "#232628", + "red": "#fc4384", + "green": "#b3e33b", + "yellow": "#ffa727", + "blue": "#75dff2", + "magenta": "#ae89fe", + "cyan": "#708387", + "white": "#d5d5d0", + "brightBlack": "#626566", + "brightRed": "#ff7fac", + "brightGreen": "#c8ed71", + "brightYellow": "#ebdf86", + "brightBlue": "#75dff2", + "brightMagenta": "#ae89fe", + "brightCyan": "#b1c6ca", + "brightWhite": "#f9f9f4", + "background": "#1e1e2e", + "foreground": "#afafaf", + "cursor": "#c7c7c7" +} + +# Hide initial shell prompt +Hide + +# Wait for terminal to be ready +Sleep 1s + +# Set a clean, simple prompt (while hidden) +Type "PS1='$ '" +Enter +Sleep 300ms + +# Create a clean temporary directory for recording +Type "rm -rf /tmp/commitizen-demo && mkdir -p /tmp/commitizen-demo && cd /tmp/commitizen-demo" +Enter +Sleep 500ms + +# Initialize git repository +Type "git init" +Enter +Sleep 500ms + +Type "git checkout -b awesome-feature" +Enter +Sleep 500ms + +# Create a dummy file to commit +Type "echo 'test content' > example.py" +Enter +Sleep 300ms + +Type "git add example.py" +Enter +Sleep 300ms + +# Clear the screen to start fresh +Type "clear" +Enter +Sleep 500ms + +# Show commands from here +Show + +# Now run cz commit +Type "cz commit" +Sleep 500ms +Enter + +# Wait for first prompt to appear +Sleep 1s + +# Question 1: Select the type of change (move down to "feat") +Down +Sleep 500ms +Enter +Sleep 1s + +# Question 2: Scope (optional, skip) +Enter +Sleep 1s + +# Question 3: Subject +Type "awesome new feature" +Sleep 500ms +Enter +Sleep 1s + +# Question 4: Is this a BREAKING CHANGE? (No) +Enter +Sleep 1s + +# Question 5: Body (optional, skip) +Enter +Sleep 1s + +# Question 6: Footer (optional, skip) +Enter +Sleep 1s + +# Wait for commit success message +Sleep 2s diff --git a/docs/images/demo.gif b/docs/images/demo.gif deleted file mode 100644 index 39dcdc9e91..0000000000 Binary files a/docs/images/demo.gif and /dev/null differ diff --git a/docs/images/init.gif b/docs/images/init.gif deleted file mode 100644 index f2cdf6b310..0000000000 Binary files a/docs/images/init.gif and /dev/null differ diff --git a/docs/images/init.tape b/docs/images/init.tape new file mode 100644 index 0000000000..cd115c6abb --- /dev/null +++ b/docs/images/init.tape @@ -0,0 +1,111 @@ +Output cli_interactive/init.gif + +Require cz + +# Use bash for cross-platform compatibility (macOS, Linux, Windows) +Set Shell bash + +Set FontSize 16 +Set Width 878 +Set Height 568 +Set Padding 20 +Set TypingSpeed 50ms + +Set Theme { + "name": "Commitizen", + "black": "#232628", + "red": "#fc4384", + "green": "#b3e33b", + "yellow": "#ffa727", + "blue": "#75dff2", + "magenta": "#ae89fe", + "cyan": "#708387", + "white": "#d5d5d0", + "brightBlack": "#626566", + "brightRed": "#ff7fac", + "brightGreen": "#c8ed71", + "brightYellow": "#ebdf86", + "brightBlue": "#75dff2", + "brightMagenta": "#ae89fe", + "brightCyan": "#b1c6ca", + "brightWhite": "#f9f9f4", + "background": "#1e1e2e", + "foreground": "#afafaf", + "cursor": "#c7c7c7" +} + +# Hide initial shell prompt +Hide + +# Wait for terminal to be ready +Sleep 1s + +# Set a clean, simple prompt +Type "PS1='$ '" +Enter +Sleep 300ms + + +# Create a clean temporary directory for recording +Type "rm -rf /tmp/commitizen-example && mkdir -p /tmp/commitizen-example && cd /tmp/commitizen-example" +Enter +Sleep 500ms + +# Clear the screen to start fresh +Type "clear" +Enter +Sleep 500ms + +# Show commands from here +Show + +# Now run cz init in the clean environment +Type "cz init" +Sleep 500ms +Enter + +# Wait for welcome message and first prompt +Sleep 500ms +Sleep 1s +# Question 1: Please choose a supported config file +# Default is .cz.toml, just press Enter +Enter +Sleep 1s + +# Question 2: Please choose a cz (commit rule) +# Default is cz_conventional_commits, just press Enter +Enter +Sleep 1s + +# Question 3: Choose the source of the version +# Default is "commitizen: Fetch and set version in commitizen config, just press Enter" +Enter +Sleep 1s + +# Question 4: Choose version scheme +# Default is semver, just press Enter +Enter +Sleep 1s + +# Question 5: Please enter the correct version format +# Default is "$version", just press Enter +Enter +Sleep 1s + +# Question 6: Create changelog automatically on bump +# Default is Yes, just press Enter +Enter +Sleep 1s + +# Question 7: Keep major version zero (0.x) during breaking changes +# Default is Yes, just press Enter +Enter +Sleep 1s + +# Question 8: What types of pre-commit hook you want to install? +# Default is [commit-msg], just press Enter to accept +Enter +Sleep 1s + +# Wait for completion message +Sleep 3s diff --git a/pyproject.toml b/pyproject.toml index d0d96e9dbf..2670a0d857 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "commitizen" -version = "4.13.7" +version = "4.13.8" description = "Python commitizen client tool" authors = [{ name = "Santiago Fraire", email = "santiwilly@gmail.com" }] maintainers = [ @@ -252,7 +252,7 @@ known-first-party = ["commitizen", "tests"] convention = "google" [tool.mypy] -files = ["commitizen", "tests"] +files = ["commitizen", "tests", "scripts"] disallow_untyped_decorators = true disallow_subclassing_any = true warn_return_any = true @@ -302,7 +302,10 @@ all.help = "Run all tasks" all.sequence = ["format", "lint", "check-commit", "cover"] "doc:screenshots".help = "Render documentation screenshots" -"doc:screenshots".script = "scripts.gen_cli_help_screenshots:gen_cli_help_screenshots" +"doc:screenshots".parallel = [ + { script = "scripts.gen_cli_help_screenshots:gen_cli_help_screenshots" }, + { script = "scripts.gen_cli_interactive_gifs:gen_cli_interactive_gifs" }, +] "doc:build".help = "Build the documentation" "doc:build".cmd = "mkdocs build" diff --git a/scripts/gen_cli_help_screenshots.py b/scripts/gen_cli_help_screenshots.py index 35dee8a972..fce95eb9cd 100644 --- a/scripts/gen_cli_help_screenshots.py +++ b/scripts/gen_cli_help_screenshots.py @@ -15,7 +15,7 @@ def gen_cli_help_screenshots() -> None: cz_commands = ( command["name"] if isinstance(command["name"], str) else command["name"][0] - for command in data["subcommands"]["commands"] + for command in data["subcommands"]["commands"] # type: ignore[index] ) for cmd in chain( ["cz --help"], (f"cz {cz_command} --help" for cz_command in cz_commands) @@ -37,8 +37,5 @@ def _export_cmd_as_svg(cmd: str, file_path: Path) -> None: print("Saved to:", file_path.as_posix()) -# TODO: generate the screenshot of cz init interactive mode -# TODO: generate the screenshot of cz commit interactive mode - if __name__ == "__main__": gen_cli_help_screenshots() diff --git a/scripts/gen_cli_interactive_gifs.py b/scripts/gen_cli_interactive_gifs.py new file mode 100644 index 0000000000..d60c476059 --- /dev/null +++ b/scripts/gen_cli_interactive_gifs.py @@ -0,0 +1,39 @@ +import subprocess +from pathlib import Path + + +def gen_cli_interactive_gifs() -> None: + """Generate GIF screenshots for interactive commands using VHS.""" + vhs_dir = Path(__file__).parent.parent / "docs" / "images" + output_dir = Path(__file__).parent.parent / "docs" / "images" / "cli_interactive" + output_dir.mkdir(parents=True, exist_ok=True) + + vhs_files = list(vhs_dir.glob("*.tape")) + + if not vhs_files: + print("No VHS tape files found in docs/images/, skipping") + return + + for vhs_file in vhs_files: + print(f"Processing: {vhs_file.name}") + try: + subprocess.run( + ["vhs", vhs_file.name], + check=True, + cwd=vhs_dir, + ) + gif_name = vhs_file.stem + ".gif" + print(f"✓ Generated {gif_name}") + except FileNotFoundError: + print( + "✗ VHS is not installed. Please install it from: " + "https://github.com/charmbracelet/vhs" + ) + raise + except subprocess.CalledProcessError as e: + print(f"✗ Error processing {vhs_file.name}: {e}") + raise + + +if __name__ == "__main__": + gen_cli_interactive_gifs() diff --git a/tests/test_conf.py b/tests/test_conf.py index 3b79376031..e558f6c393 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -242,6 +242,22 @@ def test_load_empty_pyproject_toml_from_config_argument(self, tmpdir): with pytest.raises(ConfigFileIsEmpty): config.read_cfg(filepath="./not_in_root/pyproject.toml") + def test_load_empty_json_from_config_argument(self, tmpdir): + with tmpdir.as_cwd(): + _not_root_path = tmpdir.mkdir("not_in_root").join(".cz.json") + _not_root_path.write("") + + with pytest.raises(ConfigFileIsEmpty): + config.read_cfg(filepath="./not_in_root/.cz.json") + + def test_load_empty_yaml_from_config_argument(self, tmpdir): + with tmpdir.as_cwd(): + _not_root_path = tmpdir.mkdir("not_in_root").join(".cz.yaml") + _not_root_path.write("") + + with pytest.raises(ConfigFileIsEmpty): + config.read_cfg(filepath="./not_in_root/.cz.yaml") + class TestWarnMultipleConfigFiles: @pytest.mark.parametrize( diff --git a/uv.lock b/uv.lock index c2bfe993d6..12e9c4dfb0 100644 --- a/uv.lock +++ b/uv.lock @@ -195,7 +195,7 @@ wheels = [ [[package]] name = "commitizen" -version = "4.13.7" +version = "4.13.8" source = { editable = "." } dependencies = [ { name = "argcomplete" },