|
| 1 | +# ============================================================================= |
| 2 | +# Post-Release Automation |
| 3 | +# ============================================================================= |
| 4 | +# |
| 5 | +# Triggered after the pypi.yml workflow succeeds for a release event. Automates: |
| 6 | +# A) Tagging main with the next dev tag (e.g., v0.5.1-dev after v0.5.0) |
| 7 | +# B) Bumping fallback_version on main via PR (main is a protected branch) |
| 8 | +# C) Updating npm lockfile on the release branch via PR |
| 9 | +# |
| 10 | +# ============================================================================= |
| 11 | + |
| 12 | +name: Post-release automation |
| 13 | + |
| 14 | +on: |
| 15 | + workflow_run: |
| 16 | + workflows: ["Build, test, and publish packages"] |
| 17 | + types: |
| 18 | + - completed |
| 19 | + workflow_dispatch: |
| 20 | + inputs: |
| 21 | + tag: |
| 22 | + description: 'Release tag (e.g., v0.6.0). Use to re-run post-release steps after a failure.' |
| 23 | + required: true |
| 24 | + type: string |
| 25 | + |
| 26 | +permissions: |
| 27 | + contents: write |
| 28 | + pull-requests: write |
| 29 | + |
| 30 | +jobs: |
| 31 | + post-release: |
| 32 | + name: Post-release housekeeping |
| 33 | + # Only run when: |
| 34 | + # - workflow_dispatch (manual testing), OR |
| 35 | + # - the pypi.yml workflow completed successfully AND was triggered by a release |
| 36 | + if: | |
| 37 | + github.event_name == 'workflow_dispatch' || ( |
| 38 | + github.event.workflow_run.conclusion == 'success' && |
| 39 | + github.event.workflow_run.event == 'release' |
| 40 | + ) |
| 41 | + runs-on: ubuntu-latest |
| 42 | + steps: |
| 43 | + - name: Checkout repository |
| 44 | + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
| 45 | + with: |
| 46 | + fetch-depth: 0 |
| 47 | + token: ${{ secrets.RELEASE_PAT }} |
| 48 | + |
| 49 | + - name: Parse release version |
| 50 | + id: parse |
| 51 | + env: |
| 52 | + GH_TOKEN: ${{ github.token }} |
| 53 | + run: | |
| 54 | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then |
| 55 | + TAG="${{ inputs.tag }}" |
| 56 | + else |
| 57 | + # The triggering workflow was a release event — find the tag from the |
| 58 | + # commit SHA that the workflow ran on, or fall back to the latest release. |
| 59 | + HEAD_SHA="${{ github.event.workflow_run.head_sha }}" |
| 60 | + TAG=$(git tag --points-at "$HEAD_SHA" 2>/dev/null | grep '^v' | head -1) |
| 61 | + if [ -z "$TAG" ]; then |
| 62 | + # Fallback: query the latest published release via GitHub API |
| 63 | + TAG=$(gh api repos/${{ github.repository }}/releases/latest --jq '.tag_name') |
| 64 | + fi |
| 65 | + if [ -z "$TAG" ]; then |
| 66 | + echo "::error::Could not determine release tag" |
| 67 | + exit 1 |
| 68 | + fi |
| 69 | + fi |
| 70 | +
|
| 71 | + # Strip 'v' prefix |
| 72 | + VERSION="${TAG#v}" |
| 73 | +
|
| 74 | + # Split into components |
| 75 | + MAJOR="${VERSION%%.*}" |
| 76 | + REST="${VERSION#*.}" |
| 77 | + MINOR="${REST%%.*}" |
| 78 | + PATCH="${REST#*.}" |
| 79 | + # Strip any pre-release suffix from patch (e.g., rc1) |
| 80 | + PATCH="${PATCH%%[a-zA-Z-]*}" |
| 81 | +
|
| 82 | + NEXT_PATCH=$((PATCH + 1)) |
| 83 | + NEXT_DEV_TAG="v${MAJOR}.${MINOR}.${NEXT_PATCH}-dev" |
| 84 | + NEXT_FALLBACK="${MAJOR}.${MINOR}.${NEXT_PATCH}.dev0" |
| 85 | + RELEASE_BRANCH="release-${MAJOR}.${MINOR}.x" |
| 86 | +
|
| 87 | + echo "version=${VERSION}" >> $GITHUB_OUTPUT |
| 88 | + echo "major=${MAJOR}" >> $GITHUB_OUTPUT |
| 89 | + echo "minor=${MINOR}" >> $GITHUB_OUTPUT |
| 90 | + echo "patch=${PATCH}" >> $GITHUB_OUTPUT |
| 91 | + echo "next_patch=${NEXT_PATCH}" >> $GITHUB_OUTPUT |
| 92 | + echo "next_dev_tag=${NEXT_DEV_TAG}" >> $GITHUB_OUTPUT |
| 93 | + echo "next_fallback=${NEXT_FALLBACK}" >> $GITHUB_OUTPUT |
| 94 | + echo "release_branch=${RELEASE_BRANCH}" >> $GITHUB_OUTPUT |
| 95 | +
|
| 96 | + echo "Release version: ${VERSION}" |
| 97 | + echo "Next dev tag: ${NEXT_DEV_TAG}" |
| 98 | + echo "Next fallback: ${NEXT_FALLBACK}" |
| 99 | + echo "Release branch: ${RELEASE_BRANCH}" |
| 100 | +
|
| 101 | + # ----------------------------------------------------------------------- |
| 102 | + # Step A: Tag main with the next dev tag |
| 103 | + # ----------------------------------------------------------------------- |
| 104 | + - name: Push dev tag to main |
| 105 | + run: | |
| 106 | + DEV_TAG="${{ steps.parse.outputs.next_dev_tag }}" |
| 107 | +
|
| 108 | + # Check if tag already exists |
| 109 | + if git rev-parse "$DEV_TAG" >/dev/null 2>&1; then |
| 110 | + echo "Tag $DEV_TAG already exists, skipping" |
| 111 | + exit 0 |
| 112 | + fi |
| 113 | +
|
| 114 | + # Tag the HEAD of main |
| 115 | + MAIN_SHA=$(git rev-parse origin/main) |
| 116 | + git tag "$DEV_TAG" "$MAIN_SHA" |
| 117 | + git push origin "$DEV_TAG" |
| 118 | + echo "Pushed tag $DEV_TAG to main ($MAIN_SHA)" |
| 119 | +
|
| 120 | + - name: Set up uv |
| 121 | + uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 |
| 122 | + |
| 123 | + # ----------------------------------------------------------------------- |
| 124 | + # Step B: Bump fallback_version on main and open PR |
| 125 | + # ----------------------------------------------------------------------- |
| 126 | + - name: Create fallback_version bump PR |
| 127 | + env: |
| 128 | + GH_TOKEN: ${{ secrets.RELEASE_PAT }} |
| 129 | + run: | |
| 130 | + NEXT_FALLBACK="${{ steps.parse.outputs.next_fallback }}" |
| 131 | + VERSION="${{ steps.parse.outputs.version }}" |
| 132 | + BRANCH="post-release/bump-fallback-${VERSION}" |
| 133 | +
|
| 134 | + # Check if PR already exists |
| 135 | + EXISTING=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number') |
| 136 | + if [ -n "$EXISTING" ]; then |
| 137 | + echo "PR #${EXISTING} already exists for $BRANCH, skipping" |
| 138 | + exit 0 |
| 139 | + fi |
| 140 | +
|
| 141 | + git checkout -b "$BRANCH" origin/main |
| 142 | +
|
| 143 | + # Update fallback_version in root pyproject.toml |
| 144 | + sed -i "s/^fallback_version = .*/fallback_version = \"${NEXT_FALLBACK}\"/" pyproject.toml |
| 145 | +
|
| 146 | + # Update fallback_version in src/llama_stack_api/pyproject.toml |
| 147 | + sed -i "s/^fallback_version = .*/fallback_version = \"${NEXT_FALLBACK}\"/" src/llama_stack_api/pyproject.toml |
| 148 | +
|
| 149 | + # Bump llama-stack-client minimum version to match the release |
| 150 | + sed -i "s/\"llama-stack-client>=.*\"/\"llama-stack-client>=${VERSION}\"/" pyproject.toml |
| 151 | +
|
| 152 | + # Regenerate lockfile to resolve the updated client version |
| 153 | + uv lock |
| 154 | +
|
| 155 | + # Check if there are changes |
| 156 | + if git diff --quiet; then |
| 157 | + echo "No changes to fallback_version, skipping PR" |
| 158 | + exit 0 |
| 159 | + fi |
| 160 | +
|
| 161 | + git config --local user.name "github-actions[bot]" |
| 162 | + git config --local user.email "github-actions[bot]@users.noreply.github.com" |
| 163 | + git add pyproject.toml src/llama_stack_api/pyproject.toml uv.lock |
| 164 | + git commit -s -m "chore: bump fallback_version to ${NEXT_FALLBACK} after ${VERSION} release" |
| 165 | + git push origin "$BRANCH" |
| 166 | +
|
| 167 | + gh pr create \ |
| 168 | + --base main \ |
| 169 | + --head "$BRANCH" \ |
| 170 | + --title "chore: bump fallback_version to ${NEXT_FALLBACK}" \ |
| 171 | + --body "$(cat <<EOF |
| 172 | + Automated post-release version bump after v${VERSION}. |
| 173 | +
|
| 174 | + Updates fallback_version in both pyproject.toml files to ${NEXT_FALLBACK}. |
| 175 | +
|
| 176 | + This PR was created automatically by the post-release workflow. |
| 177 | + EOF |
| 178 | + )" |
| 179 | + echo "Created PR for fallback_version bump to ${NEXT_FALLBACK}" |
| 180 | +
|
| 181 | + # ----------------------------------------------------------------------- |
| 182 | + # Step C: Update npm lockfile on release branch via PR |
| 183 | + # ----------------------------------------------------------------------- |
| 184 | + - name: Set up Node.js |
| 185 | + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 |
| 186 | + with: |
| 187 | + node-version: '20' |
| 188 | + registry-url: 'https://registry.npmjs.org' |
| 189 | + |
| 190 | + - name: Create npm lockfile update PR for release branch |
| 191 | + env: |
| 192 | + GH_TOKEN: ${{ secrets.RELEASE_PAT }} |
| 193 | + run: | |
| 194 | + VERSION="${{ steps.parse.outputs.version }}" |
| 195 | + RELEASE_BRANCH="${{ steps.parse.outputs.release_branch }}" |
| 196 | + BRANCH="post-release/npm-lockfile-${VERSION}" |
| 197 | +
|
| 198 | + # Check if release branch exists |
| 199 | + if ! git rev-parse "origin/${RELEASE_BRANCH}" >/dev/null 2>&1; then |
| 200 | + echo "Release branch ${RELEASE_BRANCH} does not exist, skipping npm update" |
| 201 | + exit 0 |
| 202 | + fi |
| 203 | +
|
| 204 | + # Check if PR already exists |
| 205 | + EXISTING=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number') |
| 206 | + if [ -n "$EXISTING" ]; then |
| 207 | + echo "PR #${EXISTING} already exists for $BRANCH, skipping" |
| 208 | + exit 0 |
| 209 | + fi |
| 210 | +
|
| 211 | + git checkout -b "$BRANCH" "origin/${RELEASE_BRANCH}" |
| 212 | +
|
| 213 | + # Regenerate uv lockfile so pre-commit passes |
| 214 | + uv lock |
| 215 | +
|
| 216 | + # Update npm lockfile |
| 217 | + cd src/llama_stack_ui |
| 218 | + npm install "llama-stack-client@^${VERSION}" |
| 219 | + cd ../.. |
| 220 | +
|
| 221 | + # Check if there are changes |
| 222 | + if git diff --quiet; then |
| 223 | + echo "No npm lockfile changes, skipping PR" |
| 224 | + exit 0 |
| 225 | + fi |
| 226 | +
|
| 227 | + git config --local user.name "github-actions[bot]" |
| 228 | + git config --local user.email "github-actions[bot]@users.noreply.github.com" |
| 229 | + git add uv.lock src/llama_stack_ui/package.json src/llama_stack_ui/package-lock.json |
| 230 | + git commit -s -m "chore: update llama-stack-client to ^${VERSION} in UI lockfile" |
| 231 | + git push origin "$BRANCH" |
| 232 | +
|
| 233 | + gh pr create \ |
| 234 | + --base "$RELEASE_BRANCH" \ |
| 235 | + --head "$BRANCH" \ |
| 236 | + --title "chore: update llama-stack-client to ^${VERSION} in UI lockfile" \ |
| 237 | + --body "$(cat <<EOF |
| 238 | + Automated post-release npm lockfile update after v${VERSION}. |
| 239 | +
|
| 240 | + Updates llama-stack-client to ^${VERSION} in the UI package lockfile. |
| 241 | +
|
| 242 | + This PR was created automatically by the post-release workflow. |
| 243 | + EOF |
| 244 | + )" |
| 245 | + echo "Created PR for npm lockfile update on ${RELEASE_BRANCH}" |
0 commit comments