Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
53 changes: 27 additions & 26 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:
name: Validate Semantic PR Title
validate-pr-title:
name: Validate PR Title
Comment thread
aaronsteers marked this conversation as resolved.
Outdated
# 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