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
74 changes: 61 additions & 13 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ on:

permissions:
contents: write
pull-requests: write

concurrency:
group: publish-${{ github.event.pull_request.base.ref }}
cancel-in-progress: true

jobs:
publish:
if: github.event.pull_request.merged == true
if: >
github.event.pull_request.merged == true &&
!(
github.event.pull_request.base.ref == 'main' &&
startsWith(github.event.pull_request.head.ref, 'release/')
)
runs-on: ubuntu-latest
env:
UV_PREVIEW: "1"
Expand All @@ -29,7 +35,6 @@ jobs:
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.base.ref }}
token: ${{ secrets.RELEASE_PAT }}

- name: Set up Python
uses: actions/setup-python@v5
Expand All @@ -46,26 +51,31 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Configure push credentials
if: github.event.pull_request.base.ref == 'main'
run: |
echo "::add-mask::${{ secrets.RELEASE_PAT }}"
git remote set-url origin https://x-access-token:${{ secrets.RELEASE_PAT }}@github.com/${{ github.repository }}.git

- name: Prepare release on main
if: github.event.pull_request.base.ref == 'main'
id: prepare_main
run: uv run python scripts/dev.py prepare-release

- name: Create release branch
if: github.event.pull_request.base.ref == 'main'
run: git checkout -b release/tmp-${{ github.run_id }}

- name: Determine release version
if: github.event.pull_request.base.ref == 'main'
id: version_info
run: |
VERSION=$(python -c "import pathlib, re; text=pathlib.Path('pyproject.toml').read_text(); import re; match=re.search(r'^version = \"([^\"]+)\"', text, re.MULTILINE); print(match.group(1))")
echo "new_version=$VERSION" >> "$GITHUB_OUTPUT"
echo "NEW_VERSION=$VERSION" >> "$GITHUB_ENV"
git branch -M release/v$VERSION

- name: Show repository status
if: github.event.pull_request.base.ref == 'main' && steps.prepare_main.conclusion == 'success'
run: git status --short

- name: Push version bump commit and tags
if: github.event.pull_request.base.ref == 'main' && steps.prepare_main.conclusion == 'success'
run: |
git push https://x-access-token:${{ secrets.RELEASE_PAT }}@github.com/${{ github.repository }}.git HEAD:${{ github.event.pull_request.base.ref }}
git push https://x-access-token:${{ secrets.RELEASE_PAT }}@github.com/${{ github.repository }}.git --tags
- name: Push release branch
if: github.event.pull_request.base.ref == 'main'
run: git push origin HEAD

- name: Publish to TestPyPI
if: github.event.pull_request.base.ref == 'test-pypi'
Expand All @@ -79,3 +89,41 @@ jobs:
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
run: uv run python scripts/dev.py publish

- name: Open release pull request
if: github.event.pull_request.base.ref == 'main'
id: create_release_pr
uses: actions/github-script@v7
with:
script: |
const version = process.env.NEW_VERSION;
const head = `release/v${version}`;
const title = `Release ${version}`;
const body = [
`Automated release for version ${version}.`,
'',
'This pull request was created by the release workflow.'
].join('\n');
const { data: pr } = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
head,
base: 'main',
body,
maintainer_can_modify: true
});
core.setOutput('pr_number', pr.number);

- name: Enable auto-merge on release PR
if: github.event.pull_request.base.ref == 'main'
uses: actions/github-script@v7
with:
script: |
const prNumber = parseInt('${{ steps.create_release_pr.outputs.pr_number }}', 10);
await github.rest.pulls.enableAutoMerge({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
merge_method: 'squash'
});
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,20 @@ client = DataCollective(environment='staging')

The repository uses branch-specific GitHub Actions for releases:

- When a pull request is merged into `main`, the workflow runs the full check suite, bumps the version, and pushes the resulting commit and git tag directly to `main`.
- When a pull request is merged into `main`, the workflow runs the full check suite, bumps the version, and opens a `release/vX.Y.Z` pull request back onto `main`. Auto-merge is enabled on that PR, so once required checks pass the version commit lands on `main` automatically.
- Merge the updated `main` into `test-pypi` to deploy that version to TestPyPI (`uv run python scripts/dev.py publish-test` runs automatically).
- After validating on TestPyPI, merge `main` into `pypi` to deploy to the production PyPI index (`uv run python scripts/dev.py publish` runs automatically).

Recommended local prep before opening release pull requests:

1. Run `uv run python scripts/dev.py all` to make sure checks pass without modifying files.
2. Optionally run `uv run python scripts/dev.py prepare-release` locally if you want to rehearse the bump; the workflow performs the same steps when `main` changes.
3. Follow the branch merge order (`main` ➜ `test-pypi`, `main` ➜ `pypi`) so TestPyPI always receives the version before production.

### Merge Flow
- Follow the branch merge order (`main` ➜ `test-pypi`, `main` ➜ `pypi`) so TestPyPI always receives the version before production.
Required GitHub Actions secrets:

- `TEST_PYPI_API_TOKEN` – token for publishing to TestPyPI (username `__token__`).
- `PYPI_API_TOKEN` – token for publishing to PyPI (username `__token__`).

## License

Expand Down
Loading