Skip to content

Commit ffcc1ef

Browse files
committed
Add bump, release and publish workflows
Mirrors the scraper pipeline: bump reads the target version from the changelog, release verifies the tag against the package and attaches the artifacts, publish uploads via trusted publishing so no token is stored. Release also asserts the generated schema records a real version, since the definitions repository pins its interpreter by reading that value and a placeholder would leave it with an uninstallable pin.
1 parent a4608a4 commit ffcc1ef

3 files changed

Lines changed: 226 additions & 0 deletions

File tree

‎.github/workflows/bump.yml‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Bump Version
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
bump:
8+
name: Bump version and push tag
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- uses: actions/checkout@v6
14+
with:
15+
fetch-tags: true
16+
17+
- uses: astral-sh/setup-uv@v7
18+
19+
- name: Read target version from CHANGELOG
20+
id: changelog
21+
run: |
22+
version=$(grep -m1 '^## \[' CHANGELOG.md | sed 's/## \[//;s/\].*//')
23+
if [ -z "$version" ]; then
24+
echo "::error::No '## [x.y.z]' entry found in CHANGELOG.md."
25+
exit 1
26+
fi
27+
echo "value=$version" >> "$GITHUB_OUTPUT"
28+
echo "Target version from CHANGELOG: $version"
29+
30+
- name: Fail if tag already exists
31+
run: |
32+
version="${{ steps.changelog.outputs.value }}"
33+
if git tag --list "v$version" | grep -q .; then
34+
echo "::error::Tag v$version already exists. Add a new version entry to CHANGELOG.md before releasing."
35+
exit 1
36+
fi
37+
echo "Tag v$version does not exist, proceeding."
38+
39+
- name: Set version in pyproject.toml
40+
run: uv version "${{ steps.changelog.outputs.value }}"
41+
42+
- name: Commit the bump, if the version actually moved
43+
uses: EndBug/add-and-commit@v10
44+
with:
45+
add: "pyproject.toml uv.lock"
46+
message: "Bump version to v${{ steps.changelog.outputs.value }}"
47+
48+
- name: Tag and push
49+
run: |
50+
version="${{ steps.changelog.outputs.value }}"
51+
# HEAD is the bump commit when one was made, and otherwise the branch tip,
52+
# which already carries the version. Both are the right thing to tag.
53+
git tag "v$version"
54+
git push origin "v$version"
55+
echo "Tagged v$version at $(git rev-parse --short HEAD)"
56+
57+
- name: Trigger release workflow
58+
run: |
59+
gh workflow run release.yml \
60+
--repo "${{ github.repository }}" \
61+
--field tag="v${{ steps.changelog.outputs.value }}"
62+
env:
63+
GH_TOKEN: ${{ secrets.LNCRAWL_PAT }}

‎.github/workflows/publish.yml‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Publish
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
tag:
7+
description: Tag to publish
8+
required: true
9+
type: string
10+
11+
jobs:
12+
publish:
13+
name: Publish to PyPI
14+
runs-on: ubuntu-latest
15+
# Trusted publishing: PyPI verifies this workflow's OIDC identity, so no API token
16+
# is stored anywhere. Requires a publisher configured on PyPI for this repository.
17+
environment: pypi
18+
permissions:
19+
id-token: write
20+
contents: read
21+
steps:
22+
- uses: actions/checkout@v6
23+
with:
24+
ref: ${{ inputs.tag }}
25+
26+
- uses: astral-sh/setup-uv@v7
27+
with:
28+
python-version: "3.9"
29+
30+
- name: Build distributions
31+
run: uv build
32+
33+
- name: Check metadata renders on PyPI
34+
run: uvx twine check dist/*
35+
36+
- name: Publish to PyPI (trusted publishing)
37+
uses: pypa/gh-action-pypi-publish@release/v1

‎.github/workflows/release.yml‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
name: Release
2+
3+
# Triggered explicitly by bump.yml after the tag is pushed (workflow_dispatch),
4+
# or when a v* tag is pushed manually. Using workflow_dispatch as the primary
5+
# path avoids relying on GITHUB_TOKEN tag-push side-effects.
6+
on:
7+
workflow_dispatch:
8+
inputs:
9+
tag:
10+
description: Tag to release
11+
required: true
12+
type: string
13+
push:
14+
tags: ["v*"]
15+
16+
permissions:
17+
contents: write
18+
pull-requests: write
19+
20+
jobs:
21+
ci:
22+
uses: ./.github/workflows/ci.yml
23+
24+
release:
25+
name: Create GitHub Release
26+
runs-on: ubuntu-latest
27+
needs: ci
28+
steps:
29+
- name: Resolve tag
30+
id: tag
31+
run: |
32+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
33+
echo "name=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
34+
else
35+
echo "name=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
36+
fi
37+
38+
- uses: actions/checkout@v6
39+
with:
40+
ref: ${{ steps.tag.outputs.name }}
41+
42+
- uses: astral-sh/setup-uv@v7
43+
with:
44+
python-version: "3.12"
45+
46+
- name: Install
47+
run: uv sync --all-groups --all-extras
48+
49+
- name: Verify tag matches package version
50+
run: |
51+
tag="${{ steps.tag.outputs.name }}"
52+
tag_version="${tag#v}"
53+
pkg_version="$(uv version | awk '{print $NF}')"
54+
echo "tag=$tag_version package=$pkg_version"
55+
if [ "$tag_version" != "$pkg_version" ]; then
56+
echo "::error::Tag $tag does not match package version $pkg_version"
57+
exit 1
58+
fi
59+
60+
- name: Build distributions
61+
run: uv build
62+
63+
- name: Check metadata renders on PyPI
64+
run: uvx twine check dist/*
65+
66+
# The published schema records which version generated it, so a release whose
67+
# schema does not match its own model would hand the definitions repository a pin
68+
# it cannot reproduce.
69+
- name: Verify the generated schema is reproducible
70+
run: |
71+
uv run sourcelib schema > /tmp/schema.json
72+
uv run python - <<'PY'
73+
import json
74+
schema = json.load(open("/tmp/schema.json"))
75+
generator = schema.get("x-generator", "")
76+
print(f"x-generator: {generator}")
77+
assert generator.startswith("lncrawl-sourcelib=="), "schema records no generator"
78+
assert not generator.endswith("==0.0.0"), "generator version is the source-tree placeholder"
79+
PY
80+
81+
- name: Extract changelog notes
82+
run: |
83+
tag="${{ steps.tag.outputs.name }}"
84+
version="${tag#v}"
85+
awk -v ver="$version" '
86+
$0 ~ "^## \\[" ver "\\]" { capture = 1; next }
87+
capture && /^## \[/ { exit }
88+
capture { print }
89+
' CHANGELOG.md | sed '/^\[.*\]: http/d' > release-notes.md
90+
if [ ! -s release-notes.md ]; then
91+
echo "::error::CHANGELOG.md has no entry for $tag."
92+
echo "::error::Add a '## [$version] - YYYY-MM-DD' section and push it to main."
93+
exit 1
94+
fi
95+
echo "----- release notes -----"
96+
cat release-notes.md
97+
98+
- name: Detect pre-release
99+
id: meta
100+
run: |
101+
tag="${{ steps.tag.outputs.name }}"
102+
version="${tag#v}"
103+
if echo "$version" | grep -qE '[0-9](a|b|rc)[0-9]+|\.dev[0-9]+'; then
104+
echo "prerelease=true" >> "$GITHUB_OUTPUT"
105+
else
106+
echo "prerelease=false" >> "$GITHUB_OUTPUT"
107+
fi
108+
109+
- name: Create GitHub Release
110+
uses: softprops/action-gh-release@v2
111+
with:
112+
tag_name: ${{ steps.tag.outputs.name }}
113+
name: ${{ steps.tag.outputs.name }}
114+
body_path: release-notes.md
115+
files: dist/*
116+
fail_on_unmatched_files: true
117+
draft: false
118+
prerelease: ${{ steps.meta.outputs.prerelease == 'true' }}
119+
120+
- name: Trigger publish workflow
121+
run: |
122+
gh workflow run publish.yml \
123+
--repo "${{ github.repository }}" \
124+
--field tag="${{ steps.tag.outputs.name }}"
125+
env:
126+
GH_TOKEN: ${{ secrets.LNCRAWL_PAT }}

0 commit comments

Comments
 (0)