refactor: make invalid contract states unrepresentable #137
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Please | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| types: [closed] | |
| branches: [main] | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "0 9 * * 1" | |
| concurrency: | |
| group: release-please-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: false | |
| permissions: {} | |
| jobs: | |
| release-pr: | |
| name: Create or update release PR | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write # Dispatch CI for the token-created release branch. | |
| contents: write # Update the release branch. | |
| issues: write # Create the autorelease label. | |
| pull-requests: write # Create or update the release PR. | |
| concurrency: | |
| group: release-please-plan | |
| cancel-in-progress: true | |
| if: >- | |
| github.event_name != 'pull_request' && | |
| ( | |
| github.event_name != 'push' || | |
| ( | |
| !contains(github.event.head_commit.message, '[skip ci]') && | |
| !contains(github.event.head_commit.message, '[no-release]') && | |
| !contains(github.event.head_commit.message, 'chore(main): release') | |
| ) | |
| ) | |
| steps: | |
| - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| persist-credentials: false | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 | |
| with: | |
| python-version: "3.12" | |
| - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 | |
| with: | |
| node-version: "24.15.0" | |
| package-manager-cache: false | |
| - name: Install git-cliff | |
| uses: taiki-e/install-action@41049aa56687c35e0afa74eed4f09cec4f9afabf # v2 | |
| with: | |
| tool: git-cliff | |
| - name: Determine release baseline | |
| id: version | |
| run: | | |
| set -euo pipefail | |
| current_version=$(uv version --short) | |
| npm_version=$(node -p "require('./npm/package.json').version") | |
| registry_version=$(jq -r '.version' server.json) | |
| registry_package_version=$(jq -r '.packages[0].version' server.json) | |
| if [[ "$current_version" != "$npm_version" ]] || | |
| [[ "$current_version" != "$registry_version" ]] || | |
| [[ "$current_version" != "$registry_package_version" ]]; then | |
| echo "Python, npm, and MCP Registry versions must match $current_version" >&2 | |
| exit 1 | |
| fi | |
| latest_tag=$(git describe --tags --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' 2>/dev/null || true) | |
| if [[ -n "$latest_tag" ]]; then | |
| latest_version="${latest_tag#v}" | |
| if [[ "$current_version" != "$latest_version" ]]; then | |
| echo "::notice::Package version $current_version is awaiting tag v$current_version" | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| baseline="$latest_tag" | |
| else | |
| latest_version="$current_version" | |
| baseline=$( | |
| git log -1 \ | |
| -G "^version = \\\"${current_version}\\\"$" \ | |
| --format=%H \ | |
| -- pyproject.toml | |
| ) | |
| if [[ -z "$baseline" ]]; then | |
| echo "Could not locate the commit that introduced version $current_version" >&2 | |
| exit 1 | |
| fi | |
| fi | |
| commits_since=$(git rev-list --count "${baseline}..HEAD") | |
| if [[ "$commits_since" -eq 0 ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| { | |
| echo "baseline=$baseline" | |
| echo "latest_version=$latest_version" | |
| echo "skip=false" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Check for existing release PR | |
| id: existing-pr | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| branch="release-flameox" | |
| existing=$( | |
| gh pr list \ | |
| --state open \ | |
| --base main \ | |
| --head "$branch" \ | |
| --json number,headRefName \ | |
| --jq '.[0] // empty | "\(.number) \(.headRefName)"' 2>/dev/null || true | |
| ) | |
| if [[ -n "$existing" ]]; then | |
| { | |
| echo "number=${existing%% *}" | |
| echo "branch=${existing#* }" | |
| } >> "$GITHUB_OUTPUT" | |
| else | |
| { | |
| echo "number=" | |
| echo "branch=" | |
| } >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Create release branch | |
| id: branch | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| EXISTING_BRANCH: ${{ steps.existing-pr.outputs.branch }} | |
| run: | | |
| set -euo pipefail | |
| branch="${EXISTING_BRANCH:-release-flameox}" | |
| git checkout -B "$branch" origin/main | |
| echo "branch=$branch" >> "$GITHUB_OUTPUT" | |
| - name: Bump synchronized package versions | |
| if: steps.version.outputs.skip != 'true' | |
| run: | | |
| set -euo pipefail | |
| uv version --bump patch --no-sync | |
| version=$(uv version --short) | |
| npm version "$version" --prefix npm --no-git-tag-version | |
| jq --arg version "$version" \ | |
| '.version = $version | .packages[0].version = $version' \ | |
| server.json > server.json.tmp | |
| mv server.json.tmp server.json | |
| uv lock | |
| { | |
| echo "RELEASE_VERSION=$version" | |
| echo "RELEASE_TAG=v$version" | |
| } >> "$GITHUB_ENV" | |
| - name: Generate changelog | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| BASELINE: ${{ steps.version.outputs.baseline }} | |
| GITHUB_REPO: ${{ github.repository }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| git cliff \ | |
| --config cliff.toml \ | |
| --tag "$RELEASE_TAG" \ | |
| --prepend CHANGELOG.md \ | |
| "${BASELINE}..HEAD" | |
| - name: Commit and push release branch | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_BRANCH: ${{ steps.branch.outputs.branch }} | |
| run: | | |
| gh auth setup-git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add \ | |
| pyproject.toml \ | |
| uv.lock \ | |
| npm/package.json \ | |
| npm/package-lock.json \ | |
| server.json \ | |
| CHANGELOG.md | |
| git commit -m "chore(main): release flameox $RELEASE_VERSION" | |
| git fetch origin \ | |
| "refs/heads/$RELEASE_BRANCH:refs/remotes/origin/$RELEASE_BRANCH" \ | |
| 2>/dev/null || true | |
| git push --force-with-lease origin "$RELEASE_BRANCH" | |
| - name: Create or update release PR | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| BASELINE: ${{ steps.version.outputs.baseline }} | |
| EXISTING_NUMBER: ${{ steps.existing-pr.outputs.number }} | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_BRANCH: ${{ steps.branch.outputs.branch }} | |
| run: | | |
| set -euo pipefail | |
| body=$( | |
| git cliff \ | |
| --config cliff.toml \ | |
| --tag "$RELEASE_TAG" \ | |
| "${BASELINE}..HEAD^" | |
| ) | |
| gh label create autorelease \ | |
| --color "ededed" \ | |
| --description "Automated release pull request" \ | |
| --force | |
| if [[ -n "$EXISTING_NUMBER" ]]; then | |
| gh pr edit "$EXISTING_NUMBER" \ | |
| --title "chore(main): release flameox $RELEASE_VERSION" \ | |
| --body "$body" \ | |
| --add-label autorelease | |
| else | |
| gh pr create \ | |
| --base main \ | |
| --head "$RELEASE_BRANCH" \ | |
| --title "chore(main): release flameox $RELEASE_VERSION" \ | |
| --label autorelease \ | |
| --body "$body" | |
| fi | |
| - name: Dispatch release PR validation | |
| if: steps.version.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_BRANCH: ${{ steps.branch.outputs.branch }} | |
| run: gh workflow run ci.yml --ref "$RELEASE_BRANCH" | |
| create-tag: | |
| name: Create release tag | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write # Dispatch the isolated publishing workflow. | |
| contents: write # Push the verified release tag. | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| github.event.action == 'closed' && | |
| github.event.pull_request.merged == true && | |
| contains(github.event.pull_request.labels.*.name, 'autorelease') | |
| steps: | |
| - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5 | |
| with: | |
| python-version: "3.12" | |
| # This job only reads the project version; avoid creating an empty cache | |
| # that setup-uv later fails to save during post-job cleanup. | |
| enable-cache: false | |
| - name: Resolve and verify release version | |
| id: version | |
| env: | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| run: | | |
| set -euo pipefail | |
| version=$(sed -n 's/^chore(main): release flameox \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)$/\1/p' <<< "$PR_TITLE") | |
| if [[ -z "$version" ]]; then | |
| echo "Could not extract a stable version from release PR title: $PR_TITLE" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$(uv version --short)" != "$version" ]] || | |
| [[ "$(node -p "require('./npm/package.json').version")" != "$version" ]] || | |
| [[ "$(jq -r '.version' server.json)" != "$version" ]] || | |
| [[ "$(jq -r '.packages[0].version' server.json)" != "$version" ]]; then | |
| echo "Merged package and MCP Registry versions do not match $version" >&2 | |
| exit 1 | |
| fi | |
| { | |
| echo "version=$version" | |
| echo "tag=v$version" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Create and push tag | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| gh auth setup-git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$RELEASE_TAG" -m "$RELEASE_TAG" | |
| git push origin "$RELEASE_TAG" | |
| - name: Dispatch release workflow | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| gh workflow run release.yml \ | |
| --ref "$RELEASE_TAG" \ | |
| --field tag="$RELEASE_TAG" |