-
Notifications
You must be signed in to change notification settings - Fork 147
Add publish-v1-specs workflow #195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| name: Publish v1 Specs | ||
|
|
||
| # Publishes v1 OpenAPI specs for a given API version and cuts a release, mirroring | ||
| # the corresponding path of pay-server's api-codegen/tools/publish.sh. Specs are | ||
| # pulled from the Statics Commander CDN instead of a local build dir. | ||
| # | ||
| # Modes (see the `mode` input): | ||
| # - private-preview: implemented. Mirrors publish.sh's `beta` path. | ||
| # - public-preview / GA / all: NOT implemented yet. These channels are published | ||
| # today by a separate pipeline (zoolander PublishOpenApiArtifacts -> | ||
| # sync-openapi-artifacts.yml, which only mirrors raw specs — no fixtures, diffs, | ||
| # or tags). They are reserved here for a future change that extends the zoolander | ||
| # LockAndReleasePrivatePreviewSpecs dispatcher to cover them. | ||
| # | ||
| # Triggered by the zoolander LockAndReleasePrivatePreviewSpecs workflow. | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| version: | ||
| description: 'Version name (e.g., 2026-07-08.preview)' | ||
| required: true | ||
| type: string | ||
| mode: | ||
| description: 'Which spec channel to publish' | ||
| required: true | ||
| type: choice | ||
| default: private-preview | ||
| options: | ||
| - private-preview | ||
| - public-preview | ||
| - GA | ||
| - all | ||
| dry_run: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is fine, but should we invert it? Like, |
||
| description: 'Dry run: fetch, build, and diff but do not push to master or tag' | ||
| required: false | ||
| type: boolean | ||
| default: true | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| jobs: | ||
| publish: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Fetch app installation token | ||
| uses: tibdex/github-app-token@v1.5.2 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This (and all other actions) should be using the new action and pinned hashes from #194 I'm actually surprised there wasn't a new CI job that failed on this branch because you're not pinning a version. so maybe double check why it didn't yell at you 😅 |
||
| id: gh-api-token | ||
| with: | ||
| app_id: ${{ secrets.GH_APP_STRIPE_OPENAPI_APP_ID }} | ||
| private_key: ${{ secrets.GH_APP_STRIPE_OPENAPI_PRIVATE_KEY }} | ||
|
|
||
| - name: Checkout openapi | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| token: ${{ steps.gh-api-token.outputs.token }} | ||
| # Full history + tags are required so `git describe --tags` can find the | ||
| # last release tag and `git show <tag>:...` can read the prior spec. | ||
| fetch-depth: 0 | ||
| fetch-tags: true | ||
|
|
||
| - name: Checkout sdk-codegen (provides the diff CLI) | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: stripe/sdk-codegen | ||
| ref: v17.0.0 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this need to pin a specific codegen version? |
||
| path: sdk-codegen | ||
|
|
||
| - name: Setup just | ||
| uses: extractions/setup-just@v2 | ||
|
|
||
| - name: Setup node | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: '24' | ||
|
|
||
| - name: Resolve mode configuration | ||
| env: | ||
| MODE: ${{ inputs.mode }} | ||
| run: | | ||
| set -euo pipefail | ||
| # Only private-preview is implemented today. The other channels exit here | ||
| # until the zoolander dispatcher and CDN keys for them are wired up. | ||
| case "$MODE" in | ||
| private-preview) | ||
| { | ||
| echo "CDN_DIR=private_preview" | ||
| echo "SPEC_FILES=spec3.private_preview.sdk.yaml spec3.private_preview.sdk.json" | ||
| echo "FIXTURE_FILES=fixtures3.private_preview.yaml fixtures3.private_preview.json" | ||
| echo "FIXTURE_MSG=Update fixture data for private preview" | ||
| echo "SPEC_MSG=Update OpenAPI specification for private preview" | ||
| echo "TAG_MSG=This release only includes changes to the private preview spec." | ||
| echo "TEST_BRANCH=dry-run/private-preview" | ||
| } >> "$GITHUB_ENV" | ||
| ;; | ||
| public-preview | GA | all) | ||
| echo "::error::mode '$MODE' is not implemented yet" | ||
| exit 1 | ||
| ;; | ||
| *) | ||
| echo "::error::unknown mode '$MODE'" | ||
| exit 1 | ||
| ;; | ||
| esac | ||
|
|
||
| - name: Download specs from CDN | ||
| env: | ||
| VERSION: ${{ inputs.version }} | ||
| run: | | ||
| set -euo pipefail | ||
| BASE="https://b.stripecdn.com/api-artifacts/assets/openapi/${VERSION}/${CDN_DIR}" | ||
|
|
||
| # Required specs — fail the job if absent. | ||
| for f in $SPEC_FILES; do | ||
| curl -fsSL "$BASE/$f" -o "openapi/$f" | ||
| done | ||
|
|
||
| # Optional fixtures — skip cleanly if the CDN returns 404. | ||
| for f in $FIXTURE_FILES; do | ||
| if ! curl -fsSL "$BASE/$f" -o "openapi/$f"; then | ||
| echo "Optional file $f absent from CDN; skipping." | ||
| rm -f "openapi/$f" | ||
| fi | ||
| done | ||
|
|
||
| - name: Commit specs, generate diffs, and cut release | ||
| env: | ||
| MODE: ${{ inputs.mode }} | ||
| DRY_RUN: ${{ inputs.dry_run }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| git config user.name "Stripe OpenAPI" | ||
| git config user.email "105521251+stripe-openapi[bot]@users.noreply.github.com" | ||
|
|
||
| # --- Commit fixtures (if changed) --- | ||
| for f in $FIXTURE_FILES; do | ||
| if [ -f "openapi/$f" ]; then git add "openapi/$f"; fi | ||
| done | ||
| if [ -n "$(git diff --name-only --staged)" ]; then | ||
| git commit -m "$FIXTURE_MSG" | ||
| fi | ||
|
|
||
| # --- Commit spec (if changed); only cut a release when the spec moved --- | ||
| for f in $SPEC_FILES; do | ||
| git add "openapi/$f" | ||
| done | ||
| if [ -n "$(git diff --name-only --staged)" ]; then | ||
| git commit -m "$SPEC_MSG" | ||
|
|
||
| LAST_TAG=$(git describe --tags --abbrev=0) | ||
| NEW_TAG="v$(( ${LAST_TAG#v} + 1 ))" | ||
| echo "Previous tag ${LAST_TAG}; new tag ${NEW_TAG}" | ||
|
|
||
| # Diff the GA spec (old tag vs. working tree), exactly as publish.sh does. | ||
| SPEC_PATH="openapi/spec3.sdk.yaml" | ||
| OLD_SPEC="/tmp/oldspec.sdk.yaml" | ||
| git show "${LAST_TAG}:${SPEC_PATH}" > "$OLD_SPEC" | ||
|
|
||
| DIFF_DIR="${GITHUB_WORKSPACE}/openapi/upcoming-changes" | ||
| NEW_SPEC="${GITHUB_WORKSPACE}/${SPEC_PATH}" | ||
| mkdir -p "$DIFF_DIR" | ||
|
|
||
| # Build the sdk-codegen CLI once, then invoke it the same way the | ||
| # justfile's `main` recipe does (node dist/main.js with node_modules/.bin | ||
| # on PATH) to avoid re-running yarn+tsc for every language. | ||
| cd "${GITHUB_WORKSPACE}/sdk-codegen" | ||
| just build-tools | ||
| export PATH="${PWD}/node_modules/.bin:${PATH}" | ||
|
|
||
| # `rest.md` is the language-agnostic diff (no --lang), matching publish.sh. | ||
| node ./dist/main.js diff --old-path "$OLD_SPEC" --new-path "$NEW_SPEC" --output-path "$DIFF_DIR/rest.md" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I would use |
||
| for lang in go php node ruby java python dotnet; do | ||
| node ./dist/main.js diff --lang "$lang" --old-path "$OLD_SPEC" --new-path "$NEW_SPEC" --output-path "$DIFF_DIR/$lang.md" | ||
| done | ||
| cd "${GITHUB_WORKSPACE}" | ||
|
|
||
| # --- Commit upcoming-changes (if changed) --- | ||
| git add ./openapi/upcoming-changes | ||
| if [ -n "$(git diff --name-only --staged)" ]; then | ||
| git commit -m "Update upcoming changes" | ||
| fi | ||
|
|
||
| if [ "$DRY_RUN" = "true" ]; then | ||
| # Never create/push a real tag in a dry run — a stray vN tag would corrupt | ||
| # the next real run's `git describe` increment. | ||
| echo "Dry run: would create tag ${NEW_TAG} and push master." | ||
| else | ||
| git tag "$NEW_TAG" -m "$TAG_MSG" | ||
| # Push branch and tag atomically so we never leave a dangling tag. | ||
| git push --atomic origin master "$NEW_TAG" | ||
| fi | ||
| else | ||
| echo "No spec changes for mode '${MODE}'; skipping release." | ||
| fi | ||
|
|
||
| if [ "$DRY_RUN" = "true" ]; then | ||
| # Push all local commits to a disposable scratch branch for inspection | ||
| # instead of master (master and tags are left untouched). Force is expected: | ||
| # each dry run builds a fresh history off the current master. | ||
| echo "Dry run: force-pushing local commits to ${TEST_BRANCH} for inspection." | ||
| git push --force origin "HEAD:refs/heads/${TEST_BRANCH}" | ||
| else | ||
| # Catch-all: pushes a fixtures-only commit; no-op if the atomic push already ran. | ||
| git push origin master | ||
| fi | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is uppercase but the rest are lowercase