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
39 changes: 39 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: PR title

# With squash merging the PR title becomes the commit subject on main, which is
# what semantic-release parses to pick the next version. An unparseable title
# is therefore a release that silently does not happen.
on:
pull_request:
branches: [main]
types: [opened, edited, synchronize, reopened]

concurrency:
group: pr-title-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
pull-requests: read

jobs:
lint:
name: conventional title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# The types already in use on main, plus revert.
types: |
feat
fix
docs
refactor
test
ci
style
chore
perf
build
revert
45 changes: 45 additions & 0 deletions .github/workflows/pypi-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: PyPI publish

# Reusable build and publish. Two callers pass a version in:
# - release.yml on a human-pushed v* tag
# - semantic-release.yml after semantic-release cuts a new version
#
# It lives here rather than in either caller because a tag pushed with
# GITHUB_TOKEN does not trigger workflows, so semantic-release cannot rely on
# release.yml's tag trigger to publish the version it just cut.
on:
workflow_call:
inputs:
version:
description: Version to build and publish, without the leading v (e.g. 1.2.3).
required: true
type: string

permissions:
contents: read

jobs:
publish:
name: build & publish
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v10.1.0
with:
python-version: "3.12"

# The version is not committed back to main, so the version in
# pyproject.toml is stale. Set the released version just for this build.
- name: Set version
run: uv version "${{ inputs.version }}" --frozen

- name: Build
run: uv build

- name: Publish
run: uv publish
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
47 changes: 26 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
name: Release CodeStripper to PyPi
name: Release

# Fires only on tags pushed by a human: a manual tag cut as an escape hatch.
# Routine releases come from semantic-release.yml, whose GITHUB_TOKEN-pushed
# tags deliberately do not trigger this workflow — that is what keeps a
# release from being published twice.
on:
push:
branches:
- 'releases/**'
tags:
- 'v*'
tags: ["v*"]

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
release:
strategy:
matrix:
python-version: ["3.12"]
version:
name: version from tag
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v10.1.0
with:
python-version: ${{ matrix.python-version }}
enable-cache: true
- name: Build
run: uv build
- name: Publish
run: uv publish
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
- id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"

publish:
needs: version
uses: ./.github/workflows/pypi-publish.yml
with:
version: ${{ needs.version.outputs.version }}
secrets: inherit
57 changes: 57 additions & 0 deletions .github/workflows/semantic-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Semantic release

on:
push:
branches: [main]

# Never cancel a release mid-flight: a half-run that has tagged but not yet
# published would need cleaning up by hand.
concurrency:
group: semantic-release-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
release:
name: version
runs-on: ubuntu-latest
permissions:
contents: write # create the tag and the GitHub release
issues: write # comment "released in vX.Y.Z" on issues
pull-requests: write # and on the PRs that fed the release
outputs:
published: ${{ steps.semantic.outputs.new_release_published }}
version: ${{ steps.semantic.outputs.new_release_version }}

steps:
- uses: actions/checkout@v4
with:
# semantic-release walks back to the previous tag to decide the bump,
# so a shallow clone makes it compute the wrong version.
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v4
with:
node-version: 22

# Pinned to an exact version: this action publishes no floating major
# tags, so @v6 would not resolve. All three plugins in .releaserc.json
# ship with the semantic-release core it bundles.
- name: Semantic release
id: semantic
uses: cycjimmy/semantic-release-action@v6.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# The tag above was pushed with GITHUB_TOKEN, so it will not trigger
# release.yml. Publish to PyPI here instead.
publish:
needs: release
if: needs.release.outputs.published == 'true'
uses: ./.github/workflows/pypi-publish.yml
with:
version: ${{ needs.release.outputs.version }}
secrets: inherit
9 changes: 9 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"branches": ["main"],
"tagFormat": "v${version}",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,4 +282,16 @@ default_tags: Set[Type[SingleTag]] = {
TestCloseTag
}
```
> :warning: **Only the `SingleTag`(s) (including `RangeOpenTag` and `RangeCloseTag`) need to be added, not the `RangeTag`**
> :warning: **Only the `SingleTag`(s) (including `RangeOpenTag` and `RangeCloseTag`) need to be added, not the `RangeTag`**
## Releases

Releases are automated with [semantic-release](https://semantic-release.gitbook.io/). Pull requests are squash merged, so the PR title becomes the commit on `main` and must follow [Conventional Commits](https://www.conventionalcommits.org/) (checked on every PR):

| PR title | Release |
|----------|---------|
| `fix: ...` | patch (1.2.3 → 1.2.4) |
| `feat: ...` | minor (1.2.3 → 1.3.0) |
| `feat!: ...` or a `BREAKING CHANGE:` footer | major (1.2.3 → 2.0.0) |
| `docs:`, `chore:`, `ci:`, `build:`, `refactor:`, `test:`, `style:`, `perf:` | no release |

On every merge to `main` the next version is determined, tagged (`vX.Y.Z`), a GitHub release is created and the package is published to PyPI. The version is set during the build and is not committed, so the version in `pyproject.toml` is not the released version.
Loading