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
27 changes: 26 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:

unit:
runs-on: ubuntu-latest
name: unit (${{ matrix.python-version }})
strategy:
fail-fast: false
matrix:
Expand All @@ -63,8 +64,9 @@ jobs:
- name: Run unit tests
run: uv run pytest tests/unit -q

integration-build-docs:
integration:
runs-on: ubuntu-latest
name: integration (${{ matrix.postgres-image }})
strategy:
fail-fast: false
matrix:
Expand All @@ -91,6 +93,29 @@ jobs:
PGPKG_TEST_POSTGRES_IMAGE: ${{ matrix.postgres-image }}
run: uv run pytest tests/integration -q

build-docs:
runs-on: ubuntu-latest
needs:
- quality
- unit
- integration
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install dependencies
run: uv sync --extra dev --extra diff

- name: Build distributions
run: uv build --out-dir dist

Expand Down
46 changes: 17 additions & 29 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@ on:
types: [published]
workflow_dispatch:
inputs:
repository:
description: Publish destination
required: true
default: testpypi
type: choice
options:
- testpypi
- pypi
expected_version:
description: Required for manual PyPI publish (e.g. 0.1.0)
required: false
Expand Down Expand Up @@ -49,39 +41,35 @@ jobs:

- name: Smoke test wheel install
run: |
set -euo pipefail
uv venv .venv-smoke
uv pip install --python .venv-smoke/bin/python dist/*.whl
.venv-smoke/bin/pgpkg --help

- name: Smoke test generated wrapper
run: |
set -euo pipefail
smoke_python="$PWD/.venv-smoke/bin/python"
smoke_wrapper="$PWD/.venv-smoke/bin/sampleext-migrator"
tmpdir="$(mktemp -d)"
cp -R tests/fixtures/sample_project/. "$tmpdir/"
uv run pgpkg stageversion 0.1.0 --project-root "$tmpdir"
uv run pgpkg wheel --project-root "$tmpdir" --output-dir "$tmpdir/wrapper"
pushd "$tmpdir/wrapper"
uv build --out-dir dist
uv pip install --python "$smoke_python" dist/*.whl
"$smoke_wrapper" info
popd

- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/*
if-no-files-found: error

publish-testpypi:
if: github.event_name == 'workflow_dispatch' && inputs.repository == 'testpypi'
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
environment:
name: testpypi
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist

- name: Publish to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/

publish-pypi:
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.repository == 'pypi')
if: github.event_name == 'release' || github.event_name == 'workflow_dispatch'
needs: build
runs-on: ubuntu-latest
permissions:
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ pip-wheel-metadata/
# local-only markdown notes
*_SETUP.md
*_PLAN.md
!GITHUB_PYPI_SETUP.md
!PROJECT_PLAN.md

# hookify rules (personal dev tooling, never commit)
.claude/*.local.md
16 changes: 13 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,35 @@

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project follows Semantic Versioning.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0] - 2026-04-27
## [0.1.0] - 2026-05-05

### Added
- Core migration toolkit commands: `stageversion`, `makemigration`, `graph`, `migrate`, `wheel`, `bundle`, `info`, `verify`.
- Python API facade for staging, planning, migration, and verification flows.
- Wrapper scaffold flow with bundled migration artifact (`tar.zst`) and sample wrapper project.
- Runtime tracking configuration via `[tool.pgpkg.tracking]` and pluggable `version_source` support for application-owned version tables.
- `stageversion --also-write` plus `makemigration --prepend-file`, `--append-file`, and `--append-sql` for custom packaging and wrapper migration flows.
- Deterministic bundle artifacts that preserve tracking schema/table and configured version-source metadata.
- Unit and integration test suites plus wrapper end-to-end test.
- MkDocs documentation site with architecture, API, CLI, layout, and quickstart guides.
- CI workflows for quality/unit/integration+build/docs and release publishing.

### Changed
- Documentation now standardizes install and release examples on `uv` and documents custom tracking/runtime packaging constraints.
- Publish workflow hardened with wheel install smoke test before publish.
- Publish workflow now smoke-tests a generated wrapper package before uploading distributions.
- Publish workflow checks release tag/version parity before PyPI publish.
- Release workflow and setup docs now target PyPI only; TestPyPI setup is deferred until access is available.
- Docs deployment workflow aligned to main-branch release path and updated Pages action versions.
- Integration tests now support configurable PostgreSQL image for CI matrix validation.

### Fixed
- Tracking writes now survive migration SQL that changes the active database role.

### Security
- Trusted publishing workflow configured for TestPyPI/PyPI environments.
- Trusted publishing workflow configured for the PyPI environment.

[0.1.0]: https://github.com/bitner/pgpkg/releases/tag/v0.1.0
26 changes: 12 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,31 @@ uv run python -m twine check dist/*
uv run mkdocs build --strict
```

3. For a smoke release, run the `Publish` workflow manually with `repository=testpypi`.
3. Create release tag `v0.1.0` matching `src/pgpkg/__init__.py::__version__`.

4. Verify TestPyPI install path in a clean venv:
4. Publish to PyPI by creating a GitHub Release from that tag.

```bash
python -m venv .venv-testpypi
. .venv-testpypi/bin/activate
python -m pip install --upgrade pip
python -m pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple pgpkg
pgpkg --help
```
Optional manual path:
- Run `Publish` with `expected_version=0.1.0` only if version parity is already confirmed.

5. Create release tag `vX.Y.Z` matching `src/pgpkg/__init__.py::__version__`.
5. Verify the PyPI install path in a clean venv:

6. Publish to PyPI by creating a GitHub Release from that tag (or by running `Publish` with `repository=pypi` only if version parity is confirmed).
```bash
uv venv .venv-pypi
uv pip install --python .venv-pypi/bin/python pgpkg
.venv-pypi/bin/pgpkg --help
```

## Trusted publishing setup

Before the publish workflow can work, configure trusted publishing for both environments:
Before the publish workflow can work, configure trusted publishing for the PyPI environment:

- GitHub environment `testpypi` bound to the TestPyPI project
- GitHub environment `pypi` bound to the PyPI project

No API tokens are required when trusted publishing is configured correctly.

## Release policy guardrails

- The release tag version must match the built package version exactly.
- TestPyPI install smoke test is required before first PyPI publish.
- The publish build job smoke-tests both the CLI wheel and a generated wrapper before upload.
- If workflow_dispatch is used for PyPI publish, confirm version parity before running it.
138 changes: 138 additions & 0 deletions GITHUB_PYPI_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# GitHub + PyPI Setup Guide

This checklist takes a local `pgpkg` repository to a production-ready GitHub repository with automated PyPI publishing.

## 1) Create and push the GitHub repository

1. Create an empty GitHub repo (for example, `pgpkg`).
2. Add the remote and push:

```bash
git remote add origin git@github.com:<OWNER>/pgpkg.git
git push -u origin <BRANCH>
```

3. Push all long-lived branches you want preserved.

## 2) Repository settings (recommended hygiene)

1. Enable branch protection on the default branch:
- Require pull request before merging
- Require status checks to pass
- Require branches to be up to date before merging
2. Require these checks from CI:
- `quality`
- `unit (3.11)`
- `unit (3.12)`
- `unit (3.13)`
- `integration (postgres:14-alpine)`
- `integration (postgres:15-alpine)`
- `integration (postgres:16-alpine)`
- `integration (postgres:17-alpine)`
- `build-docs`
3. Enable "Automatically delete head branches" after merge.
4. Enable Dependabot alerts and security updates.
5. In Settings -> Pages, set Source to `GitHub Actions` so `.github/workflows/docs-pages.yml` can publish the MkDocs site.

## 3) PyPI account and project prerequisites

You need an account on:
- https://pypi.org

### Required access

- You must be an Owner or Maintainer for the project on PyPI.
- You must have admin rights on the GitHub repository to configure environments and workflows.

## 4) Preferred publishing model: Trusted Publishing (OIDC)

This repository already includes `.github/workflows/publish-pypi.yml`, which is configured for trusted publishing.

### 4a) Configure GitHub environments

Create these environments in GitHub:
- `pypi`

Optional hardening:
- Add required reviewers for `pypi` environment
- Restrict deployment branches/tags

### 4b) Configure trusted publisher on PyPI

In PyPI project settings, add a trusted publisher with:
- Owner: `<OWNER>`
- Repository: `pgpkg`
- Workflow filename: `publish-pypi.yml`
- Environment: `pypi`

## 5) Credentials you actually need

With trusted publishing configured correctly:
- No PyPI API token is needed in GitHub secrets.
- No username/password is needed in CI.
- The only required "credential" in CI is GitHub's OIDC identity (`id-token: write`, already set in workflow).

You still need:
- GitHub account with repo admin rights
- PyPI account with project owner/maintainer rights

## 6) Optional fallback model: API token publishing

Only use this if trusted publishing cannot be enabled.

1. Create a project-scoped API token in PyPI.
2. Add these repository secrets:
- `PYPI_API_TOKEN`
3. Update publish workflow to pass `password: ${{ secrets.<TOKEN_NAME> }}` to `pypa/gh-action-pypi-publish`.

## 7) First release flow

1. Run local gate:

```bash
uv run pre-commit run --all-files
uv run ty check src tests
uv run pytest -q
uv build --out-dir dist
uv run python -m twine check dist/*
uv run mkdocs build --strict
```

2. Create GitHub Release tag `v0.1.0` to trigger PyPI publish.

Optional manual path:
- GitHub Actions -> `Publish` -> `Run workflow` -> `expected_version=0.1.0`

3. Verify install from PyPI:

```bash
uv venv /tmp/pgpkg-smoke
uv pip install --python /tmp/pgpkg-smoke/bin/python pgpkg
/tmp/pgpkg-smoke/bin/pgpkg --help
```

The `Publish` workflow's build job also smoke-tests a generated wrapper wheel,
so a successful publish already validates both the base CLI wheel and the
wrapper packaging path before upload.

TestPyPI is intentionally not wired up in this repository right now. If access
is restored later, add a separate environment and publish job rather than
reusing the PyPI lane implicitly.

## 8) Post-setup metadata cleanup

Add canonical URLs in `pyproject.toml`:

```toml
[project.urls]
Homepage = "https://github.com/<OWNER>/pgpkg"
Repository = "https://github.com/<OWNER>/pgpkg"
Documentation = "https://<OWNER>.github.io/pgpkg/"
Issues = "https://github.com/<OWNER>/pgpkg/issues"
```

## 9) Quick troubleshooting

- "invalid-publisher" or "publisher not trusted": trusted publisher fields do not exactly match repo/workflow/environment. For this repo, confirm `bitner/pgpkg`, workflow `publish-pypi.yml`, and environment `pypi` exactly.
- Publish job not starting: verify environment name in workflow matches configured environment.
- Artifact missing in publish job: ensure build job used `uv build --out-dir dist` and uploaded `dist/*`.
Loading
Loading