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: 74 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Release

on:
release:
types: [published]

permissions:
contents: read

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Verify release tag matches package version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
python - <<'PY'
import os
import tomllib
from pathlib import Path

pyproject = tomllib.loads(Path("pyproject.toml").read_text())
version = pyproject["project"]["version"]
expected_tag = f"v{version}"
release_tag = os.environ["RELEASE_TAG"]

if release_tag != expected_tag:
raise SystemExit(
f"Release tag {release_tag!r} must match pyproject version {version!r} "
f"as {expected_tag!r}."
)
PY

- name: Build and check package
run: |
python -m pip install --upgrade pip
python -m pip install build twine
python -m build --sdist --wheel
python -m twine check dist/*

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

publish:
name: Publish to PyPI
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/dbt-spec-kit/
permissions:
contents: read
id-token: write
steps:
- name: Download distribution artifacts
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/

- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Jaffle-shop AI SDLC walkthrough using the upstream dbt Labs project as the onboarding demo.
- Team onboarding playbook for introducing dbt-spec-kit to analytics engineering teams.
- Documentation tests for README links, walkthrough commands, and OSS file presence.
- PyPI Trusted Publishing release workflow and maintainer release runbook.

### Changed
- Reworked README around the jaffle-shop quickstart, team adoption path, and CI trust boundary.
Expand Down
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ All three must pass before opening a PR.

Use the pull request template and include the smallest reproducible example for behavior changes.

## Release changes

Release PRs must keep `pyproject.toml`, `CHANGELOG.md`, and the Git tag aligned. Publishing is
handled by `.github/workflows/release.yml` through PyPI Trusted Publishing, so maintainers should
not add PyPI API tokens or passwords to GitHub secrets.

See `docs/releasing.md` for the full release runbook.

## How to add a warehouse preset

1. Create `presets/<warehouse>/constitution-additions.md`. Append-only — do not repeal base principles.
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,15 @@ See the full walkthrough: [Jaffle-shop AI SDLC walkthrough](docs/jaffle-shop-ai-

## Install

Requires Python 3.11+. Recommended via [uv](https://docs.astral.sh/uv/):
Requires Python 3.11+. Recommended via [uv](https://docs.astral.sh/uv/).

After the first PyPI release:

```bash
uvx --from dbt-spec-kit dbt-specify init my-project --warehouse snowflake
```

From the GitHub source before the PyPI release:

```bash
uvx --from git+https://github.com/duckcode-ai/dbt-spec-kit.git \
Expand All @@ -66,7 +74,7 @@ uvx --from git+https://github.com/duckcode-ai/dbt-spec-kit.git \
Persistent install:

```bash
uv tool install dbt-spec-kit --from git+https://github.com/duckcode-ai/dbt-spec-kit.git
uv tool install dbt-spec-kit
dbt-specify --version
```

Expand Down Expand Up @@ -121,6 +129,7 @@ Use `dbt-specify ci` when the lifecycle and dbt artifact checks should block a P
- [Enterprise CI](docs/enterprise-ci.md)
- [Brownfield onboarding](docs/brownfield-onboarding.md)
- [EARS cheatsheet](docs/ears-cheatsheet.md)
- [Releasing to PyPI](docs/releasing.md)
- [Snowflake guide](docs/warehouse-guides/snowflake.md)
- [Databricks guide](docs/warehouse-guides/databricks.md)
- [Trino guide](docs/warehouse-guides/trino.md)
Expand Down
8 changes: 8 additions & 0 deletions docs/enterprise-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ See `.dbt-specify/templates/ci/github-actions-dbt-specify.yml` after running ini
`templates/ci/github-actions-dbt-specify.yml` in this repo, for a reusable starting point. The
workflow assumes your dbt adapter and dbt profile are already available through your CI setup.

## Package release workflow

This repository publishes the `dbt-spec-kit` Python package with PyPI Trusted Publishing through
`.github/workflows/release.yml`. The workflow builds source and wheel distributions, checks them
with Twine, and publishes from the GitHub `pypi` environment without storing a PyPI token.

See [Releasing to PyPI](releasing.md) for the maintainer runbook.

## PR evidence example

For the jaffle-shop semantic mart walkthrough, the PR should include:
Expand Down
83 changes: 83 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Releasing to PyPI

dbt-spec-kit publishes to PyPI through PyPI Trusted Publishing. The release workflow does not use a
PyPI password or API token. GitHub Actions requests a short-lived OIDC token, PyPI verifies the
repository, workflow, and environment claims, and then accepts the package upload.

## One-time setup

Configure these two systems before publishing the first release.

### PyPI

Create a pending Trusted Publisher for the package:

- PyPI project name: `dbt-spec-kit`
- Owner: `duckcode-ai`
- Repository name: `dbt-spec-kit`
- Workflow name: `release.yml`
- Environment name: `pypi`

Pending publishers can create the PyPI project on first publish, but they do not reserve the name
before that first successful upload.

### GitHub

In the GitHub repository, create an environment named `pypi`.

Recommended controls:

- Require reviewer approval for deployments to the environment.
- Limit who can approve the environment to the package maintainers.
- Restrict deployments to protected release refs when the repository policy supports it.

The workflow file must live at `.github/workflows/release.yml`, because PyPI checks the workflow
filename against the Trusted Publisher configuration.

## Release process

1. Update `pyproject.toml` to the release version.
2. Move the relevant `CHANGELOG.md` notes from `Unreleased` into the release section.
3. Merge the release PR to `main`.
4. Create and push a tag that exactly matches the package version, prefixed with `v`.

```bash
git checkout main
git pull --ff-only origin main
git tag v1.0.0
git push origin v1.0.0
```

5. Create a GitHub Release from that tag and publish it.
6. Approve the `pypi` environment deployment if GitHub asks for approval.
7. Confirm the package is visible at <https://pypi.org/project/dbt-spec-kit/>.

The release workflow verifies that the GitHub Release tag matches the `pyproject.toml` version. A
release tagged `v1.0.0` must publish package version `1.0.0`; mismatches fail before upload.

## Install after release

Once the first PyPI release is published, users can install from PyPI:

```bash
uvx --from dbt-spec-kit dbt-specify --version
uvx --from dbt-spec-kit dbt-specify init my-project --warehouse snowflake
```

Persistent install:

```bash
uv tool install dbt-spec-kit
dbt-specify --version
```

`pipx install dbt-spec-kit` also works for teams that do not use `uv`.

## If publishing fails

- `invalid-publisher`: Check the PyPI Trusted Publisher values exactly match `duckcode-ai`,
`dbt-spec-kit`, `release.yml`, and `pypi`.
- `tag mismatch`: Update `pyproject.toml` or create the correct `vX.Y.Z` tag.
- `file already exists`: PyPI does not allow replacing an existing version. Bump the version and
publish a new release.
- `environment approval pending`: Approve the `pypi` deployment in GitHub Actions.
11 changes: 11 additions & 0 deletions tests/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,22 @@ def test_launch_ready_oss_files_exist() -> None:
".github/pull_request_template.md",
".github/ISSUE_TEMPLATE/bug_report.md",
".github/ISSUE_TEMPLATE/feature_request.md",
".github/workflows/release.yml",
"docs/releasing.md",
]
for relative_path in required_paths:
assert (ROOT / relative_path).exists(), f"Missing OSS file: {relative_path}"


def test_release_workflow_uses_trusted_publishing() -> None:
workflow = (ROOT / ".github" / "workflows" / "release.yml").read_text()
assert "pypa/gh-action-pypi-publish@release/v1" in workflow
assert "id-token: write" in workflow
assert "name: pypi" in workflow
assert "python -m build --sdist --wheel" in workflow
assert "python -m twine check dist/*" in workflow


def _markdown_links(text: str) -> list[str]:
return re.findall(r"(?<!!)\[[^\]]+\]\(([^)]+)\)", text)

Expand Down
Loading