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
1 change: 1 addition & 0 deletions .genignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pyproject.toml
60 changes: 29 additions & 31 deletions .github/workflows/generate-command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ name: Generate SDK
description: Validate generation without creating a PR
type: boolean
default: false
outputs:
has_changes:
description: Whether the generation produced changes vs committed code
value: ${{ jobs.generate.outputs.has_changes }}
drift_summary:
description: Git diff stat summary when drift is detected
value: ${{ jobs.generate.outputs.drift_summary }}

concurrency:
group: ${{ (github.event_name == 'push' || github.event_name == 'schedule') && 'generate-new-pr' || format('generate-{0}', github.run_id) }}
Expand All @@ -64,6 +71,9 @@ jobs:
name: Generate SDK
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
has_changes: ${{ steps.changes.outputs.has_changes }}
drift_summary: ${{ steps.changes.outputs.drift_summary }}
permissions:
contents: write
pull-requests: write
Expand Down Expand Up @@ -161,37 +171,13 @@ jobs:
exit 1
fi

- name: Generate SDK with Speakeasy
- name: Generate SDK
env:
SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }}
VERSION: ${{ steps.resolve-version.outputs.version }}
run: |
echo "Generating with version: $VERSION"
uvx --from=poethepoet poe generate-code

- name: Post-generation patching
run: python3 scripts/post_generate.py

- name: Verify generated code
run: |
if [ -f "pyproject.toml" ]; then
echo "pyproject.toml found (v2 generator confirmed)"
uv sync --no-install-project 2>/dev/null || true
uv run python -c "import airbyte_api; print(f'SDK import OK: {airbyte_api.__name__}')" || echo "::warning::SDK import check failed"
else
echo "::warning::No pyproject.toml found. Generation may not have produced v2 output."
fi

- name: Upload generated SDK as artifact
if: ${{ inputs.dry_run }}
uses: actions/upload-artifact@v7
with:
name: generated_sdk_code
path: |
src/
pyproject.toml
py.typed
retention-days: 7
uv run poe generate-full

- name: Generation Summary
run: |
Expand All @@ -203,16 +189,28 @@ jobs:
fi

- name: Check for changes
if: ${{ !inputs.dry_run }}
id: changes
run: |
# Restore workflow.lock to HEAD to ignore non-deterministic
# digest changes that cause infinite generate→merge loops.
# Restore non-deterministic Speakeasy lock files to HEAD
# to ignore digest changes that cause infinite generate→merge loops.
git checkout HEAD -- .speakeasy/workflow.lock 2>/dev/null || true
git checkout HEAD -- .speakeasy/gen.lock 2>/dev/null || true
if [ -n "$(git status --porcelain)" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "has_changes=true" | tee -a $GITHUB_OUTPUT
echo "=== Changed files ==="
git status --porcelain
echo
echo "=== Diff stat ==="
SUMMARY=$(git diff --stat)
echo "$SUMMARY"
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
{
echo "drift_summary<<$EOF"
echo "$SUMMARY"
echo "$EOF"
} | tee -a "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "has_changes=false" | tee -a $GITHUB_OUTPUT
fi
Comment thread
aaronsteers marked this conversation as resolved.

# --- PR branch mode: commit and push to the existing PR branch ---
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/pre-release-command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,16 +113,15 @@ jobs:
uses: astral-sh/setup-uv@v5

# ── Set version and build ───────────────────────────────────────
- name: Set pre-release version in pyproject.toml
- name: Set pre-release version
run: |
VERSION="${{ inputs.version }}"
# Use sed to update version in pyproject.toml
sed -i "s/^version = \".*\"/version = \"${VERSION}\"/" pyproject.toml
echo "Updated pyproject.toml version to: $VERSION"
grep 'version' pyproject.toml | head -1
sed -i "s/^__version__: str = \".*\"/__version__: str = \"${VERSION}\"/" src/airbyte_api/_version.py
echo "Updated _version.py to: $VERSION"
grep '__version__' src/airbyte_api/_version.py

- name: Build package
run: uv build
run: uv run poe build

- name: Publish to PyPI
run: uv publish
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
uses: astral-sh/setup-uv@v5

- name: Build package
run: uv build
run: uv run poe build

- name: Publish to PyPI
run: uv publish
Expand Down
51 changes: 26 additions & 25 deletions .github/workflows/semantic-pr-title.yml
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
# Semantic PR Title Validation
#
# This workflow validates that PR titles follow the Conventional Commits format.
# This is required for the semantic-pr-release-drafter to correctly categorize changes.
#
# Valid formats:
# - feat: Add new feature
# - fix: Fix a bug
# - chore: Maintenance task
# - docs: Documentation changes
# - ci: CI/CD changes
# - refactor: Code refactoring
# - test: Test changes
# - perf: Performance improvements
# - feat!: Breaking change (major version bump)
#
# Optional scope: feat(api): Add new endpoint
# This workflow validates PR titles follow conventional commit format.

name: Validate PR Title
name: Semantic PR Title Validation

on:
pull_request:
types: [opened, edited, synchronize]
types: [opened, edited, ready_for_review, synchronize]

permissions:
pull-requests: read
statuses: write
contents: read
pull-requests: write

jobs:
validate:
validate-pr-title:
name: Validate Semantic PR Title
# Skip if 'edited' event but the title wasn't changed (e.g., only description was edited)
if: >
github.event.action != 'edited'
|| (
github.event.changes.title &&
github.event.changes.title.from != ''
)
runs-on: ubuntu-latest
steps:
- name: Validate Semantic PR Title
- name: Check semantic PR title
uses: amannn/action-semantic-pull-request@v6
if: ${{ github.event.pull_request.draft == false }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
Expand All @@ -47,7 +41,14 @@ jobs:
perf
build
revert
requireScope: false
scopes: ""
wip: true
validateSingleCommit: false
style

- name: Check for "do not merge" in PR title
if: ${{ github.event.pull_request.draft == false }}
uses: actions/github-script@v8
with:
script: |
const title = context.payload.pull_request.title.toLowerCase();
if (title.includes('do not merge') || title.includes('do-not-merge')) {
core.setFailed('PR title contains "do not merge" or "do-not-merge". Please remove this before merging.');
}
77 changes: 8 additions & 69 deletions .github/workflows/test-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#
# Jobs:
# 1. validate: Runs the full generation pipeline in dry-run mode
# 2. zero-diff: Compares the dry-run artifacts against the committed code to detect drift.
# 2. zero-diff: Checks for drift using the validate job's outputs (in-place git status).
# If drift is detected, the check fails and posts a comment telling the author to run /generate.
#
# This workflow calls the main generation workflow with dry_run=true to ensure
Expand All @@ -25,7 +25,6 @@ on:
permissions:
contents: write
pull-requests: write
actions: read

jobs:
check-paths:
Expand Down Expand Up @@ -64,87 +63,27 @@ jobs:
contents: read
pull-requests: write
steps:
- name: Checkout PR branch
uses: actions/checkout@v4

- name: Download generated SDK artifact
uses: actions/download-artifact@v8
with:
name: generated_sdk_code
path: /tmp/generated/

- name: Compare generated code against committed code
id: diff-check
- name: Check for generation drift
id: drift-check
run: |
DIFF_SUMMARY=""

echo "=== Comparing generated SDK code ==="
# Compare src/ directory
if [ -d "src/" ] && [ -d "/tmp/generated/src/" ]; then
while IFS= read -r line; do
# Extract relative path from diff output
FILE=$(echo "$line" | sed 's|^Files ||; s| and /tmp/generated/.*||')
ADDED=$(diff -u "$FILE" "/tmp/generated/$FILE" 2>/dev/null | tail -n +3 | grep -c '^+' || echo "0")
REMOVED=$(diff -u "$FILE" "/tmp/generated/$FILE" 2>/dev/null | tail -n +3 | grep -c '^-' || echo "0")
DIFF_SUMMARY="${DIFF_SUMMARY}${FILE} (+${ADDED}/-${REMOVED})"$'\n'
done < <(diff -rq src/ /tmp/generated/src/ 2>&1 | grep "^Files" || true)

# Check for files only in one side
ONLY_LINES=$(diff -rq src/ /tmp/generated/src/ 2>&1 | grep "^Only" || true)
if [ -n "$ONLY_LINES" ]; then
while IFS= read -r line; do
DIR=$(echo "$line" | sed 's|^Only in /tmp/generated/||; s|^Only in ||; s|: |/|')
if echo "$line" | grep -q "^Only in /tmp/generated/"; then
DIFF_SUMMARY="${DIFF_SUMMARY}${DIR} (new file)"$'\n'
else
DIFF_SUMMARY="${DIFF_SUMMARY}${DIR} (deleted)"$'\n'
fi
done <<< "$ONLY_LINES"
fi
elif [ -d "/tmp/generated/src/" ]; then
DIFF_SUMMARY="src/ directory missing in committed code but present in generated output"$'\n'
fi

# Compare pyproject.toml
if [ -f "/tmp/generated/pyproject.toml" ]; then
TOML_DIFF=$(diff -q pyproject.toml /tmp/generated/pyproject.toml 2>&1 || true)
if [ -n "$TOML_DIFF" ]; then
ADDED=$(diff -u pyproject.toml /tmp/generated/pyproject.toml 2>/dev/null | tail -n +3 | grep -c '^+' || echo "0")
REMOVED=$(diff -u pyproject.toml /tmp/generated/pyproject.toml 2>/dev/null | tail -n +3 | grep -c '^-' || echo "0")
DIFF_SUMMARY="${DIFF_SUMMARY}pyproject.toml (+${ADDED}/-${REMOVED})"$'\n'
fi
fi

if [ -n "$DIFF_SUMMARY" ]; then
if [ "${{ needs.validate.outputs.has_changes }}" = "true" ]; then
echo "has_diff=true" >> $GITHUB_OUTPUT
echo "::warning::Generated code drift detected. The committed code does not match what the generation pipeline produces."
echo "$DIFF_SUMMARY"
echo "$DIFF_SUMMARY" > /tmp/diff_summary.txt
else
echo "has_diff=false" >> $GITHUB_OUTPUT
echo "Zero-diff check passed. Committed code matches generation output."
fi

- name: Prepare diff summary
if: steps.diff-check.outputs.has_diff == 'true'
id: diff-summary
run: |
SUMMARY=$(cat /tmp/diff_summary.txt 2>/dev/null || echo "(see job logs for full details)")
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "content<<$EOF" >> $GITHUB_OUTPUT
echo "$SUMMARY" >> $GITHUB_OUTPUT
echo "$EOF" >> $GITHUB_OUTPUT

- name: Find existing drift comment
if: steps.diff-check.outputs.has_diff == 'true'
if: steps.drift-check.outputs.has_diff == 'true'
uses: peter-evans/find-comment@v3
id: find-drift-comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: '<!-- zero-diff-check -->'

- name: Post drift comment on PR
if: steps.diff-check.outputs.has_diff == 'true'
if: steps.drift-check.outputs.has_diff == 'true'
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ github.event.pull_request.number }}
Expand All @@ -159,11 +98,11 @@ jobs:
**To fix:** Comment `/generate` on this PR to regenerate.

```
${{ steps.diff-summary.outputs.content }}
${{ needs.validate.outputs.drift_summary }}
```

- name: Fail if drift detected
if: steps.diff-check.outputs.has_diff == 'true'
if: steps.drift-check.outputs.has_diff == 'true'
run: |
echo "::error::Generated code drift detected. Run /generate on this PR to fix."
exit 1
15 changes: 7 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,23 @@ Do not guess or iterate blindly. Download the artifact first.
- `.github/speakeasy/dummy-compose.yml` — Speakeasy CLI version pin
- `.speakeasy/workflow.yaml` — Speakeasy workflow configuration
- `gen.yaml` — Speakeasy generator configuration
- `pyproject.toml` — Human-managed (excluded from Speakeasy via `.genignore`)
- `CONTRIBUTING.md`, `AGENTS.md`, `README.md` — Documentation

## Files You Must NOT Edit By Hand

- `src/airbyte_api/` — Generated by Speakeasy
- `pyproject.toml` — Generated by Speakeasy (version managed by release drafter)
- `py.typed` — Generated by Speakeasy

## Build Commands

```bash
uvx --from=poethepoet poe generate-code # Generate SDK from spec
uvx --from=poethepoet poe post-generate # Run post-generation patches
uvx --from=poethepoet poe generate-full # Full pipeline
uvx --from=poethepoet poe lint # Lint checks
uvx --from=poethepoet poe fix # Auto-fix lint/format
uvx --from=poethepoet poe test # Run tests
uvx --from=poethepoet poe typecheck # Type checking
uv run poe generate-full # Full pipeline (generate + readme + patches)
uv run poe build # Build the Python package
uv run poe lint # Lint checks
uv run poe fix # Auto-fix lint/format
uv run poe test # Run tests
uv run poe typecheck # Type checking
```

## Regenerating the SDK
Expand Down
13 changes: 6 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,12 @@ The Speakeasy CLI version is pinned in [`.github/speakeasy/dummy-compose.yml`](h
Build tasks are defined in [`poe_tasks.toml`](https://github.com/airbytehq/airbyte-api-python-sdk/blob/main/poe_tasks.toml) and run via [poethepoet](https://poethepoet.natn.io/):

```bash
uvx --from=poethepoet poe generate-code # Generate SDK from spec
uvx --from=poethepoet poe post-generate # Run post-generation patches
uvx --from=poethepoet poe generate-full # Full pipeline (generate + patches)
uvx --from=poethepoet poe lint # Run linting checks
uvx --from=poethepoet poe fix # Auto-fix lint/formatting
uvx --from=poethepoet poe test # Run tests
uvx --from=poethepoet poe typecheck # Run type checking
uv run poe generate-full # Full pipeline (generate + readme + patches)
uv run poe build # Build the Python package
uv run poe lint # Run linting checks
uv run poe fix # Auto-fix lint/formatting
uv run poe test # Run tests
uv run poe typecheck # Run type checking
```

## How to Report Issues
Expand Down
Loading
Loading