Skip to content
Merged
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
31 changes: 29 additions & 2 deletions .github/workflows/stainless-builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to run Stainless build for'
required: true
description: 'PR number to run Stainless build for. Leave empty to force-upload the current OpenAPI spec from main.'
required: false
type: number
sdk_install_url:
description: 'Python SDK install URL (optional, for testing specific builds)'
Expand Down Expand Up @@ -65,93 +65,120 @@
# Stainless organization dashboard

jobs:
force-upload:
# Push the OpenAPI spec from main to Stainless without a PR. Triggered
# manually via workflow_dispatch with no pr_number. Skips preview and
# integration tests; just uploads the spec on the main branch.
if: github.event_name == 'workflow_dispatch' && inputs.pr_number == ''
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout main
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: main

- name: Upload OpenAPI spec to Stainless
uses: stainless-api/upload-openapi-spec-action/build@020053e7fbf853281174bd3029eb3bfa7a54c039 # 1.13.0
with:
stainless_api_key: ${{ secrets.STAINLESS_API_KEY }}
org: ${{ env.STAINLESS_ORG }}
project: ${{ env.STAINLESS_PROJECT }}
oas_path: ${{ env.OAS_PATH }}
config_path: ${{ env.CONFIG_PATH }}
fail_on: ${{ env.FAIL_ON }}
branch: main
make_comment: false

compute-branch:
if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.pr_number != '')
runs-on: ubuntu-latest
outputs:
preview_branch: ${{ steps.compute.outputs.preview_branch }}
base_branch: ${{ steps.compute.outputs.base_branch }}
merge_branch: ${{ steps.compute.outputs.merge_branch }}
pr_head_repo: ${{ steps.compute.outputs.pr_head_repo }}
pr_head_ref: ${{ steps.compute.outputs.pr_head_ref }}
pr_head_sha: ${{ steps.compute.outputs.pr_head_sha }}
pr_base_sha: ${{ steps.compute.outputs.pr_base_sha }}
pr_base_ref: ${{ steps.compute.outputs.pr_base_ref }}
pr_title: ${{ steps.compute.outputs.pr_title }}
is_fork_pr: ${{ steps.compute.outputs.is_fork_pr }}
steps:
- name: Fetch PR details for workflow_dispatch
if: github.event_name == 'workflow_dispatch'
id: fetch-pr
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_DATA=$(gh pr view ${{ inputs.pr_number }} --repo ${{ github.repository }} --json headRefName,headRepository,headRefOid,baseRefName,baseRefOid,headRepositoryOwner,title)
echo "pr_data=$PR_DATA" >> "$GITHUB_OUTPUT"

- name: Compute branch names
id: compute
# Pass PR title via environment variable to prevent shell injection.
# Direct interpolation of ${{ github.event.pull_request.title }} into bash
# allows command substitution via backticks or $() in PR titles.
env:
PR_TITLE_FROM_EVENT: ${{ github.event.pull_request.title }}
HEAD_REF_FROM_EVENT: ${{ github.event.pull_request.head.ref }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
# Extract from fetched PR data (jq -r safely handles special characters)
PR_DATA='${{ steps.fetch-pr.outputs.pr_data }}'
FORK_OWNER=$(echo "$PR_DATA" | jq -r '.headRepositoryOwner.login')
REPO_NAME=$(echo "$PR_DATA" | jq -r '.headRepository.name')
HEAD_REPO="${FORK_OWNER}/${REPO_NAME}"
BRANCH_NAME=$(echo "$PR_DATA" | jq -r '.headRefName')
HEAD_SHA=$(echo "$PR_DATA" | jq -r '.headRefOid')
BASE_SHA=$(echo "$PR_DATA" | jq -r '.baseRefOid')
BASE_REF=$(echo "$PR_DATA" | jq -r '.baseRefName')
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
else
# Use pull_request_target event data
HEAD_REPO="${{ github.event.pull_request.head.repo.full_name }}"
BRANCH_NAME="$HEAD_REF_FROM_EVENT"
FORK_OWNER="${{ github.event.pull_request.head.repo.owner.login }}"
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
BASE_SHA="${{ github.event.pull_request.base.sha }}"
BASE_REF="${{ github.event.pull_request.base.ref }}"
# Use environment variable to prevent shell injection from PR titles
PR_TITLE="$PR_TITLE_FROM_EVENT"
fi

BASE_REPO="${{ github.repository }}"

if [ "$HEAD_REPO" != "$BASE_REPO" ]; then
# Fork PR: prefix with fork owner for isolation
if [ -z "$FORK_OWNER" ]; then
echo "Error: Fork PR detected but fork owner is empty" >&2
exit 1
fi
PREVIEW_BRANCH="preview/${FORK_OWNER}/${BRANCH_NAME}"
BASE_BRANCH="preview/base/${FORK_OWNER}/${BRANCH_NAME}"
IS_FORK_PR="true"
else
# Same-repo PR
PREVIEW_BRANCH="preview/${BRANCH_NAME}"
BASE_BRANCH="preview/base/${BRANCH_NAME}"
IS_FORK_PR="false"
fi

{
echo "preview_branch=${PREVIEW_BRANCH}"
echo "base_branch=${BASE_BRANCH}"
echo "merge_branch=${PREVIEW_BRANCH}"
echo "pr_head_repo=${HEAD_REPO}"
echo "pr_head_ref=${BRANCH_NAME}"
echo "pr_head_sha=${HEAD_SHA}"
echo "pr_base_sha=${BASE_SHA}"
echo "pr_base_ref=${BASE_REF}"
echo "pr_title=${PR_TITLE}"
echo "is_fork_pr=${IS_FORK_PR}"
} >> "$GITHUB_OUTPUT"

preview:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
needs: compute-branch
# Skip preview if workflow_dispatch provides sdk_install_url, or if PR is being closed
if: |
Expand Down
Loading