Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ on:
types: [completed]
branches: ['main', 'beta', 'httpx']

# The release is created with a GitHub App token; the default token only needs
# read access for checkout and `gh release view`.
# The release is created with a GitHub App token. The "Build changelog" step
# commits the rendered CHANGELOG.md back to the release branch, so the default
# token needs write access to contents.
permissions:
contents: read
contents: write

jobs:
create-release:
Expand Down Expand Up @@ -80,6 +81,34 @@ jobs:
exit 1
fi

# Render changelog.d/ fragments into CHANGELOG.md and commit before the
# release is cut. Reuses the version already read from pyproject.toml, so
# there is no separate towncrier version to keep in sync. --generate-notes
# below remains as a fallback for the GitHub release body.
- name: Build changelog
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
VERSION="${{ steps.version.outputs.version }}"
BRANCH="${{ inputs.branch || github.event.workflow_run.head_branch }}"

# Nothing to do if there are no fragments to consume.
if ! ls changelog.d/*.md >/dev/null 2>&1; then
echo "No changelog fragments; skipping towncrier build."
exit 0
fi

pip install --quiet towncrier
towncrier build --yes --version "$VERSION"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md changelog.d/
if ! git diff --cached --quiet; then
git commit -m "docs: update changelog for $VERSION"
git push origin "HEAD:$BRANCH"
fi

- name: Create release
if: steps.check-release.outputs.exists == 'false'
env:
Expand Down
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ venv/
.DS_Store

# Project
.scratch/
.scratch/

# Git worktrees
.worktrees/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog

All notable changes to this library are documented here.

This file is maintained with [towncrier](https://towncrier.readthedocs.io/);
add a news fragment under `changelog.d/` for every user-facing change. See
[CONTRIBUTING.md](CONTRIBUTING.md) for the fragment format.

<!-- towncrier release notes start -->
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ uv sync
- Generated API scope files (`meraki/api/`, `meraki/aio/api/`) are auto-generated from the OpenAPI spec. Changes here will be overwritten. Fix the generator instead.
- Do not vendor or bundle dependencies.

## Changelog Fragments

Every user-facing change needs a news fragment in `changelog.d/`. Do not edit `CHANGELOG.md` by hand; towncrier builds it at release time.

File name: `changelog.d/{issue}.{type}.md`. With no issue, use a slug prefixed with `+`: `changelog.d/+httpx-migration.changed.md`.

Types: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`.

```bash
# Issue-linked
echo "Retry on 429 now honors Retry-After." > changelog.d/1234.fixed.md

# Or via towncrier
uv run towncrier create -c "Retry on 429 now honors Retry-After." 1234.fixed.md

# Preview the rendered changelog without consuming fragments
uv run towncrier build --draft --version "$(grep '^version' pyproject.toml | sed 's/version = "\(.*\)"/\1/')"
```

One fragment per change. Multiple fragments per issue are fine (e.g. `1234.added.md` and `1234.fixed.md`). Fragments are not wiped by library regeneration; they survive until the next release build.

## Running the Generator

```bash
Expand Down
1 change: 1 addition & 0 deletions changelog.d/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!.gitignore
8 changes: 8 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ Starting from `main` at `3.1.0`, `beta` at `3.1.0`, `httpx` at `4.0.0`:

When a new prerelease is detected, the `enable-early-access.yml` workflow runs first (waited on synchronously). This enables early access features on the test organizations so the generated beta and dev SDKs can be tested against them.

## Changelog management

`CHANGELOG.md` is generated by [towncrier](https://towncrier.readthedocs.io/) from news fragments in `changelog.d/`. Contributors add one fragment per user-facing change (see [CONTRIBUTING.md](../CONTRIBUTING.md) for the format); nobody edits `CHANGELOG.md` directly.

The `create-release.yml` workflow runs `towncrier build --yes --version <version>` after version validation and before `gh release create`. The version comes from the same `pyproject.toml` read that drives the release tag, so there is no separate towncrier version to keep in sync. The build renders the fragments into `CHANGELOG.md`, deletes the consumed fragments, and commits both changes to the release branch before the GitHub release is cut.

This runs only at release time, not during regeneration. `regenerate-library.yml` rewrites generated code under `meraki/` but never touches `changelog.d/` or `CHANGELOG.md`, so fragments added on `main`, `beta`, or `httpx` survive regeneration PRs and are consumed only by the next release on that branch. Each branch keeps its own fragments and accumulates them independently across the three-branch model.

## Release branches and PRs

| Stage | Source branch | Release branch | PR target |
Expand Down
44 changes: 44 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dev = [
"flake8>=7.0,<8",
"pre-commit>=4.6.0",
"ruff>=0.15.12",
"towncrier>=24.8.0",
]
generator = ["jinja2==3.1.6"]

Expand All @@ -63,3 +64,46 @@ omit = ["meraki/api/*", "meraki/aio/api/*", "meraki/aio/__init__.py"]
fail_under = 90
show_missing = true
exclude_lines = ["pragma: no cover", "if __name__"]

[tool.towncrier]
directory = "changelog.d"
filename = "CHANGELOG.md"
name = "meraki"
title_format = "## {version} ({project_date})"
issue_format = "[#{issue}](https://github.com/meraki/dashboard-api-python/issues/{issue})"
start_string = "<!-- towncrier release notes start -->\n"
wrap = false
all_bullets = true

# Version is passed via `--version` from a single source of truth (pyproject
# [project].version) at build time, so there is no version pinned here to drift.

[[tool.towncrier.type]]
directory = "added"
name = "Added"
showcontent = true

[[tool.towncrier.type]]
directory = "changed"
name = "Changed"
showcontent = true

[[tool.towncrier.type]]
directory = "deprecated"
name = "Deprecated"
showcontent = true

[[tool.towncrier.type]]
directory = "removed"
name = "Removed"
showcontent = true

[[tool.towncrier.type]]
directory = "fixed"
name = "Fixed"
showcontent = true

[[tool.towncrier.type]]
directory = "security"
name = "Security"
showcontent = true
27 changes: 27 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading