diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b1d884b..16e174d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -9,5 +9,5 @@ ## Changelog - [ ] Added `changes/..md` -- [ ] This change is internal-only; explain why a maintainer should apply the - `skip-changelog` label: +- [ ] This is dependency maintenance, or it is internal-only and the PR + explains why a maintainer should apply `skip-changelog`: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a22e29b..13f799d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,6 @@ jobs: env: BASE_SHA: ${{ github.event.pull_request.base.sha }} GH_TOKEN: ${{ github.token }} - PR_ACTOR: ${{ github.actor }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | changed_files="$(git diff --name-only "$BASE_SHA" HEAD)" @@ -44,9 +43,11 @@ jobs: run_container=false fi - if [[ "$PR_ACTOR" == "dependabot[bot]" ]] || + labels="$( gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/labels" \ - --jq '.[].name' | grep -Fxq 'skip-changelog'; then + --jq '.[].name' + )" + if grep -Eq '^(dependencies|skip-changelog)$' <<< "$labels"; then require_changelog=false else require_changelog=true diff --git a/.github/workflows/release-to-pypi.yml b/.github/workflows/release-to-pypi.yml index f5848b0..8eef5de 100644 --- a/.github/workflows/release-to-pypi.yml +++ b/.github/workflows/release-to-pypi.yml @@ -53,11 +53,14 @@ jobs: .release-smoke/bin/python -m pip check .release-smoke/bin/vidxp --version - - name: Upload GitHub release assets + - name: Publish GitHub release if: steps.release.outputs.released == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: semantic-release publish --tag "${{ steps.release.outputs.tag }}" + run: | + semantic-release publish --tag "${{ steps.release.outputs.tag }}" + gh release edit "${{ steps.release.outputs.tag }}" \ + --notes-file .release-notes.md - name: Preserve distribution if: steps.release.outputs.released == 'true' diff --git a/.github/workflows/release-to-test-pypi.yml b/.github/workflows/release-to-test-pypi.yml index 7156b14..f9c55e6 100644 --- a/.github/workflows/release-to-test-pypi.yml +++ b/.github/workflows/release-to-test-pypi.yml @@ -48,6 +48,7 @@ jobs: - name: Determine and tag prerelease version id: release env: + BUILD_RELEASE_NOTES: "1" GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: semantic-release version --as-prerelease --prerelease-token b --no-changelog @@ -60,11 +61,14 @@ jobs: .release-smoke/bin/python -m pip check .release-smoke/bin/vidxp --version - - name: Upload GitHub release assets + - name: Publish GitHub release if: steps.release.outputs.released == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: semantic-release publish --tag "${{ steps.release.outputs.tag }}" + run: | + semantic-release publish --tag "${{ steps.release.outputs.tag }}" + gh release edit "${{ steps.release.outputs.tag }}" \ + --notes-file .release-notes.md - name: Preserve distribution if: steps.release.outputs.released == 'true' diff --git a/.gitignore b/.gitignore index 238d0ba..ffcf0a1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /video.mp4 /dist/ /build/ +/.release-notes.md .build /MANIFEST /.pytest_cache/ diff --git a/changes/README.md b/changes/README.md index 1cc4a5b..c264b27 100644 --- a/changes/README.md +++ b/changes/README.md @@ -21,8 +21,10 @@ changes/123.feature.md Add named repositories for selecting shared index locations and devices. ``` -Purely internal changes may omit a fragment only when a maintainer applies the -`skip-changelog` label and the pull request explains why. +Dependency updates marked `dependencies` do not require a fragment. Other +internal changes may omit one when a maintainer applies `skip-changelog` and +the pull request explains why. -Towncrier collects and removes fragments when a stable release is created. -Do not edit `CHANGELOG.md` directly. +Towncrier renders the pending fragments for prerelease notes, then collects and +removes them when a stable release is created. Do not edit `CHANGELOG.md` +directly. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index df703a5..6847f70 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -107,13 +107,14 @@ The fragment should be one sentence written for users. Do not include a heading, version number, commit message, or implementation details. See [`changes/README.md`](../changes/README.md) for an example. -Internal-only maintenance does not need an empty fragment. Explain the reason in -the pull request and ask a maintainer to apply the `skip-changelog` label. CI -requires either a fragment or that label. - -Towncrier collects the pending fragments into `CHANGELOG.md` and removes them -when a stable release is made. Do not edit the changelog or package version in a -feature pull request. +Dependency updates carrying GitHub's `dependencies` label do not require a +fragment. For other internal-only maintenance, explain the reason in the pull +request and ask a maintainer to apply `skip-changelog`. CI requires a fragment +unless one of those labels is present. + +Towncrier renders pending fragments as prerelease notes, then collects them into +`CHANGELOG.md` and removes them when a stable release is made. Do not edit the +changelog or package version in a feature pull request. ## Questions diff --git a/docs/releasing.md b/docs/releasing.md index ad876fe..eaf514e 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -11,7 +11,8 @@ calculate the next version, creates a `b` prerelease tag and GitHub prerelease, builds the distributions, and publishes them to TestPyPI. Commits that do not require a semantic version bump do not publish a package. -Pending Towncrier fragments remain in `changes/` during prereleases. +Towncrier renders the pending fragments into the GitHub prerelease body without +consuming them. The fragments remain in `changes/` for the stable release. ## Stable releases @@ -22,11 +23,12 @@ Pending Towncrier fragments remain in `changes/` during prereleases. 5. Confirm the new tag, GitHub release, PyPI package, and emptied pending fragment set. -The workflow only runs its validation and release jobs for a `main` dispatch. -Both jobs use the same immutable dispatch revision. Python Semantic Release is -the only version authority. During its build step, Towncrier receives that -calculated version, updates `CHANGELOG.md`, and removes the released fragments -before the release commit and tag are created. +The workflow only runs from a `main` dispatch, and every publication job uses +the release commit created from that immutable revision. Python Semantic +Release is the only version authority. Towncrier is the only release-note +renderer. During the stable build it receives the calculated version, renders +the GitHub release body, updates `CHANGELOG.md` with the same section, and +removes the released fragments before the release commit and tag are created. Release and CI tools are declared once in `utils/build-requirements.txt`. Do not duplicate their versions in workflow files. diff --git a/pyproject.toml b/pyproject.toml index c9f25e2..cfc7678 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ select = ["E4", "E7", "E9", "F"] version_toml = ["pyproject.toml:project.version"] tag_format = "v{version}" build_command = "bash ./utils/build_package.sh" -build_command_env = ["BUILD_CHANGELOG"] +build_command_env = ["BUILD_CHANGELOG", "BUILD_RELEASE_NOTES"] assets = ["CHANGELOG.md"] commit_parser = "conventional" major_on_zero = true diff --git a/utils/build_package.sh b/utils/build_package.sh index c145dde..70a8495 100644 --- a/utils/build_package.sh +++ b/utils/build_package.sh @@ -6,6 +6,7 @@ README="README.md" README_BAK="$README.bak" BUILD_DIR="build" DIST_DIR="dist" +RELEASE_NOTES=".release-notes.md" restore_readme() { if [[ -f "$README_BAK" ]]; then @@ -15,14 +16,29 @@ restore_readme() { trap restore_readme EXIT -if [[ "${BUILD_CHANGELOG:-0}" == "1" ]]; then +echo "๐Ÿงน Removing stale package artifacts..." +rm -rf -- "$BUILD_DIR" "$DIST_DIR" "$RELEASE_NOTES" + +if [[ "${BUILD_CHANGELOG:-0}" == "1" || "${BUILD_RELEASE_NOTES:-0}" == "1" ]]; then if [[ -z "${NEW_VERSION:-}" ]]; then - echo "NEW_VERSION is required when BUILD_CHANGELOG=1" >&2 + echo "NEW_VERSION is required when rendering release notes" >&2 exit 1 fi + RELEASE_DATE="$(date -u +%Y-%m-%d)" + echo "๐Ÿ“ฐ Rendering release notes for v${NEW_VERSION}..." + towncrier build \ + --draft \ + --version "$NEW_VERSION" \ + --date "$RELEASE_DATE" > "$RELEASE_NOTES" +fi + +if [[ "${BUILD_CHANGELOG:-0}" == "1" ]]; then echo "๐Ÿ“ฐ Building changelog for v${NEW_VERSION}..." - towncrier build --yes --version "$NEW_VERSION" + towncrier build \ + --yes \ + --version "$NEW_VERSION" \ + --date "$RELEASE_DATE" fi echo "๐Ÿ“ Backing up original README..." @@ -31,9 +47,6 @@ cp "$README" "$README_BAK" echo "๐Ÿ”ง Processing README.md for PyPI rendering..." python utils/fix_readme_links.py "$BASE_URL" "$README" --inplace -echo "๐Ÿงน Removing stale package artifacts..." -rm -rf -- "$BUILD_DIR" "$DIST_DIR" - echo "๐Ÿ“ฆ Building package..." python -m build