Skip to content

Merge pull request #48 from Integration-Automation/dev #15

Merge pull request #48 from Integration-Automation/dev

Merge pull request #48 from Integration-Automation/dev #15

Workflow file for this run

name: CI (stable)
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: "0 3 * * *"
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install tooling
run: |
python -m pip install --upgrade pip
pip install ruff mypy
- name: Ruff check
run: ruff check automation_file tests
- name: Ruff format check
run: ruff format --check automation_file tests
- name: Mypy
run: mypy automation_file
pytest:
needs: lint
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
python-version: [ "3.10", "3.11", "3.12" ]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run pytest with coverage
run: python -m pytest tests/ -v --tb=short --cov=automation_file --cov-report=term-missing --cov-report=xml
- name: Upload coverage artifact
if: matrix.python-version == '3.12'
uses: actions/upload-artifact@v4
with:
name: coverage-xml
path: coverage.xml
publish:
needs: pytest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Bump patch version in stable.toml and dev.toml
id: version
run: |
python - <<'PY'
import os
import pathlib
import re
def bump(path: pathlib.Path) -> str:
text = path.read_text(encoding="utf-8")
match = re.search(r'^version = "(\d+)\.(\d+)\.(\d+)"', text, re.MULTILINE)
if match is None:
raise SystemExit(f"no version line found in {path}")
major, minor, patch = (int(g) for g in match.groups())
new = f"{major}.{minor}.{patch + 1}"
path.write_text(text.replace(match.group(0), f'version = "{new}"', 1), encoding="utf-8")
return new
stable_version = bump(pathlib.Path("stable.toml"))
dev_version = bump(pathlib.Path("dev.toml"))
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fp:
fp.write(f"version={stable_version}\n")
fp.write(f"dev_version={dev_version}\n")
print(f"stable.toml -> {stable_version}")
print(f"dev.toml -> {dev_version}")
PY
- name: Push signed bump commit to a release branch
id: bump_commit
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
run: |
python - <<'PY'
import base64
import json
import os
import subprocess
import urllib.error
import urllib.request
head_oid = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()
repo = os.environ["GITHUB_REPOSITORY"]
token = os.environ["GH_TOKEN"]
version = os.environ["VERSION"]
branch = f"release/bump-v{version}"
def api(path, method="GET", body=None):
req = urllib.request.Request(
f"https://api.github.com/{path}",
data=(json.dumps(body).encode("utf-8") if body is not None else None),
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"Content-Type": "application/json",
},
method=method,
)
with urllib.request.urlopen(req) as resp:
raw = resp.read()
return json.loads(raw) if raw else None
# Delete any pre-existing release branch (from a prior partial run)
# so the bump always starts from main's HEAD. Avoids STALE_DATA from
# createCommitOnBranch and prevents stacking multiple bump commits.
# Any open PR on the stale branch gets auto-closed when the branch
# is deleted, which is fine — we always open a fresh PR below.
try:
api(f"repos/{repo}/git/refs/heads/{branch}", method="DELETE")
print(f"deleted stale branch: {branch}")
except urllib.error.HTTPError as error:
if error.code not in (404, 422): # ref doesn't exist; nothing to clean
raise
api(
f"repos/{repo}/git/refs",
method="POST",
body={"ref": f"refs/heads/{branch}", "sha": head_oid},
)
expected_oid = head_oid
def b64(path: str) -> str:
with open(path, "rb") as fp:
return base64.b64encode(fp.read()).decode("ascii")
mutation = """
mutation($input: CreateCommitOnBranchInput!) {
createCommitOnBranch(input: $input) {
commit { oid url }
}
}
"""
payload = {
"query": mutation,
"variables": {
"input": {
"branch": {
"repositoryNameWithOwner": repo,
"branchName": branch,
},
"message": {
"headline": f"Bump version to v{version} [skip ci]",
},
"expectedHeadOid": expected_oid,
"fileChanges": {
"additions": [
{"path": "stable.toml", "contents": b64("stable.toml")},
{"path": "dev.toml", "contents": b64("dev.toml")},
]
},
}
},
}
request = urllib.request.Request(
"https://api.github.com/graphql",
data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request) as resp:
body = json.loads(resp.read())
if body.get("errors"):
raise SystemExit(f"GraphQL error: {body['errors']}")
commit = body["data"]["createCommitOnBranch"]["commit"]
print(f"bump commit: {commit['oid']} -> {commit['url']}")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fp:
fp.write(f"branch={branch}\n")
fp.write(f"oid={commit['oid']}\n")
PY
- name: Open PR for the version bump
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
BRANCH: ${{ steps.bump_commit.outputs.branch }}
run: |
gh pr create \
--base main \
--head "$BRANCH" \
--title "Bump version to v${VERSION} [skip ci]" \
--body "Automated patch bump emitted by the stable publish workflow. [skip ci]"
- name: Try to auto-merge the bump PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: ${{ steps.bump_commit.outputs.branch }}
run: |
gh pr merge "$BRANCH" --squash --auto --delete-branch \
|| echo "auto-merge unavailable; PR left open for manual merge"
- name: Use stable.toml as pyproject.toml
run: cp stable.toml pyproject.toml
- name: Build sdist and wheel
run: python -m build
- name: Twine check
run: twine check dist/*
- name: Twine upload to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload --non-interactive dist/*
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUMP_OID: ${{ steps.bump_commit.outputs.oid }}
run: |
gh release create "v${{ steps.version.outputs.version }}" dist/* \
--title "v${{ steps.version.outputs.version }}" \
--generate-notes \
--target "$BUMP_OID"