diff --git a/.github/workflows/openapi-spec-updated.yml b/.github/workflows/openapi-spec-updated.yml new file mode 100644 index 0000000..23bddc9 --- /dev/null +++ b/.github/workflows/openapi-spec-updated.yml @@ -0,0 +1,119 @@ +name: openapi-spec-updated + +# Regenerates the typed client when the ClickFunnels platform publishes a new +# OpenAPI spec. +# +# Trigger: the platform's publishing pipeline sends a repository_dispatch event +# of type `openapi-spec-updated` after a deploy, carrying the published spec's +# SHA-256 in `client_payload.sha256` (the same value the spec endpoint returns +# in its `X-OpenAPI-SHA256` response header). `workflow_dispatch` allows manual +# runs, with an optional expected checksum. +# +# The job fetches the spec from the live API, verifies the checksum, runs the +# existing `make generate` pipeline, sanity-builds, and opens a DRAFT pull +# request when the committed generated files changed. It never pushes to main +# and never merges anything; when the client already matches the spec it exits +# quietly (the codegen is deterministic, so the diff itself is the dedupe). +# +# Repository configuration (Settings -> Secrets and variables -> Actions): +# OPENAPI_SPEC_URL secret - full URL of the spec endpoint +# (GET /api/v2/openapi.yaml) +# OPENAPI_SPEC_TOKEN secret - an API bearer token authorized to read it + +on: + repository_dispatch: + types: [openapi-spec-updated] + workflow_dispatch: + inputs: + sha256: + description: Expected spec SHA-256 (verified when provided) + required: false + type: string + +permissions: + contents: write # push the sync branch + pull-requests: write # open the draft PR + +concurrency: + group: openapi-spec-sync + cancel-in-progress: false + +jobs: + regenerate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + # `make generate` shells out to npx for the OpenAPI 3.1 -> 3.0 down-convert. + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Fetch the published spec + env: + SPEC_URL: ${{ secrets.OPENAPI_SPEC_URL }} + SPEC_TOKEN: ${{ secrets.OPENAPI_SPEC_TOKEN }} + run: | + if [ -z "$SPEC_URL" ] || [ -z "$SPEC_TOKEN" ]; then + echo "OPENAPI_SPEC_URL / OPENAPI_SPEC_TOKEN are not configured in this repository's Actions settings." >&2 + exit 1 + fi + curl -fsSL -H "Authorization: Bearer $SPEC_TOKEN" "$SPEC_URL" -o /tmp/openapi.yaml + sha256sum /tmp/openapi.yaml + + - name: Verify the announced checksum + env: + EXPECTED_SHA256: ${{ github.event.client_payload.sha256 || inputs.sha256 }} + run: | + if [ -z "$EXPECTED_SHA256" ]; then + echo "No expected sha256 provided - skipping verification (manual run)." + exit 0 + fi + # A mismatch usually means this fetch raced the deploy that published + # the spec; re-run the workflow once the deploy has settled. + echo "$EXPECTED_SHA256 /tmp/openapi.yaml" | sha256sum -c + + - name: Regenerate the client + run: make generate SPEC_SRC=/tmp/openapi.yaml + + - name: Sanity build and tests + run: | + make vet + make test + make build + + - name: Open a draft PR when the generated files changed + env: + GH_TOKEN: ${{ github.token }} + TRIGGER: ${{ github.event_name }} + SOURCE: ${{ github.event.client_payload.source || 'manual' }} + run: | + if git diff --quiet -- internal/api/api.gen.go cmd/operations.gen.go; then + echo "Generated client already matches the published spec - nothing to do." + exit 0 + fi + spec_sha=$(sha256sum /tmp/openapi.yaml | cut -d' ' -f1) + branch="openapi-sync/${spec_sha:0:12}" + if gh api "repos/${GITHUB_REPOSITORY}/branches/${branch}" > /dev/null 2>&1; then + echo "Branch ${branch} already exists - a sync PR for this spec is already open." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add internal/api/api.gen.go cmd/operations.gen.go + git commit -m "Regenerate client from published OpenAPI spec (${spec_sha:0:12})" + git push origin "$branch" + gh pr create --draft \ + --title "Regenerate client from published OpenAPI spec (${spec_sha:0:12})" \ + --body "Automated sync from the published OpenAPI spec. + + - Spec SHA-256: \`${spec_sha}\` + - Trigger: \`${TRIGGER}\` (source: \`${SOURCE}\`) + - Pipeline: \`make generate\` (down-convert -> normalize -> oapi-codegen), then \`make vet && make test && make build\` + + Review the generated diff before merging; this PR is never auto-merged."