feat(changelog): Add `changelog.output_dir` configuration option by bilelomrani1 · Pull Request #1406 · python-semantic-release/python-semantic-release

Purpose

This PR adds a new changelog.output_dir configuration option that allows users to specify the destination directory for changelog output.

This feature is particularly valuable for monorepo setups where PSR runs from a package subdirectory but needs to write changelogs to a consolidated documentation directory, and also when users use shared remote templates (see #1404) across multiple projects but still want fine grained control over where the changelog is written.

Rationale

In monorepo environments, users often want to:

  • Run PSR from a package directory (e.g., packages/pkg1/)
  • Write changelogs to a centralized docs folder (e.g., docs/source/pkg1/changelog.md)

Before this PR, achieving this required complex shell scripts to copy/move changelog files. The previous monorepo documentation reflected this complexity, requiring users to understand intricate template customization just to place changelogs in a different directory.

The new output_dir option provides a simple way to specify the changelog destination:

# packages/pkg1/pyproject.toml
[tool.semantic_release.changelog]
output_dir = "../../docs/source/pkg1"

This single line replaces what previously required custom templates and shell scripts. The Advanced Example in the monorepo documentation has been completely rewritten to use output_dir, reducing complexity significantly while enabling the same (and more) functionality.

Bug Fix: Custom Templates Now Render to CWD

This PR also fixes an inconsistency when using custom template directories from a subdirectory.

Before:

# changelog_writer.py
project_dir = Path(runtime_ctx.repo_dir)  # Always repo root
...
destination_dir=project_dir  # Hardcoded to repo root

After:

# changelog_writer.py
output_dir = runtime_ctx.output_dir  # Resolved from "." relative to CWD
...
destination_dir=output_dir  # Respects CWD

The old behavior was inconsistent: when running from a subdirectory (e.g., packages/pkg1/), template_dir was resolved relative to CWD, but destination_dir was always hardcoded to the repository root. This caused templates to be read from the subdirectory but output to be written to the repo root.

With this fix, both template reading and output writing are consistent: relative to CWD when output_dir="." (default), or to the explicitly configured output_dir. I'm expecting this to be related to #845, and could potentially fix it.

How did you test?

Unit Tests (tests/unit/semantic_release/cli/test_config.py)

  • test_output_dir_default_resolves_to_repo_root - Default behavior
  • test_output_dir_inside_repo_accepted - Valid paths accepted
  • test_output_dir_outside_repo_rejected - Security: paths outside repo rejected
  • test_output_dir_with_parent_traversal_rejected - Security: path traversal blocked
  • test_output_dir_and_changelog_file_with_dir_rejected - Ambiguous config rejected
  • test_output_dir_with_bare_changelog_filename_from_subdirectory_accepted - Monorepo scenario works

End-to-End Tests (tests/e2e/cmd_changelog/test_changelog.py)

  • test_changelog_generated_in_output_dir - Changelog written to specified directory
  • test_changelog_update_mode_reads_from_output_dir - Update mode reads existing changelog from correct location
  • test_changelog_file_with_directory_component_backward_compatibility - Existing changelog_file with directory component still works

Backward Compatibility

  • Existing configurations with changelog_file = "docs/CHANGELOG.md" (directory component) continue to work unchanged
  • The validation only triggers when output_dir is explicitly set alongside a changelog_file with directory component

How to Verify

  1. Basic usage: Set output_dir in config and run semantic-release changelog

    [tool.semantic_release.changelog]
    output_dir = "docs"

    Verify changelog is written to docs/CHANGELOG.md

  2. Monorepo scenario: From a package subdirectory with:

    [tool.semantic_release.changelog]
    output_dir = "../../docs/source/pkg1"

    Run semantic-release changelog and verify output location

  3. Update mode: Create an existing changelog in output_dir, run with mode = "update", verify it reads and updates the correct file

  4. Backward compatibility: Existing config with changelog_file = "docs/CHANGELOG.md" (no output_dir) should continue working


PR Completion Checklist

  • Reviewed & followed the Contributor Guidelines

  • Changes Implemented & Validation pipeline succeeds

  • Commits follow the Conventional Commits standard
    and are separated into the proper commit type and scope (recommended order: test, build, feat/fix, docs)

  • Appropriate Unit tests added/updated

  • Appropriate End-to-End tests added/updated

  • Appropriate Documentation added/updated and syntax validated for sphinx build (see Contributor Guidelines)