Publish Release #16
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: Publish Release | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_type: | |
| description: "Release type" | |
| required: true | |
| type: choice | |
| options: | |
| - stable | |
| - rc | |
| - beta | |
| default: stable | |
| pre_release_number: | |
| description: "Pre-release number (for rc/beta, leave empty for auto-increment)" | |
| required: false | |
| type: string | |
| dry_run: | |
| description: "Dry run (skip actual publish)" | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write | |
| concurrency: | |
| group: publish-release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| environment: release | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| is_prerelease: ${{ steps.version.outputs.is_prerelease }} | |
| branch: ${{ steps.context.outputs.branch }} | |
| env: | |
| NX_DAEMON: "false" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Validate branch | |
| id: context | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Get current branch | |
| BRANCH="${GITHUB_REF#refs/heads/}" | |
| echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" | |
| # Validate branch is a release branch | |
| if [[ ! "$BRANCH" =~ ^release/[0-9]+\.[0-9]+\.x$ ]]; then | |
| echo "::error::This workflow must be run from a release/X.Y.x branch. Current branch: $BRANCH" | |
| exit 1 | |
| fi | |
| # Extract release line (X.Y) from release/X.Y.x | |
| RELEASE_LINE=$(echo "$BRANCH" | sed 's/release\/\([0-9]*\.[0-9]*\).*/\1/') | |
| echo "release_line=$RELEASE_LINE" >> "$GITHUB_OUTPUT" | |
| echo "Branch: $BRANCH" | |
| echo "Release line: $RELEASE_LINE" | |
| - name: Setup Node | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: ".nvmrc" | |
| cache: "yarn" | |
| registry-url: "https://registry.npmjs.org/" | |
| - name: Update npm CLI for trusted publishing | |
| run: npm install -g npm@latest | |
| - name: Install dependencies | |
| run: yarn install --frozen-lockfile | |
| - name: Compute version | |
| id: version | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| RELEASE_LINE="${{ steps.context.outputs.release_line }}" | |
| RELEASE_TYPE="${{ inputs.release_type }}" | |
| PRE_RELEASE_NUM="${{ inputs.pre_release_number }}" | |
| # Fetch all tags | |
| git fetch --tags | |
| # Use compute-next-patch script | |
| if [ -n "$PRE_RELEASE_NUM" ]; then | |
| VERSION=$(node scripts/compute-next-patch.mjs "$RELEASE_LINE" "$RELEASE_TYPE" "$PRE_RELEASE_NUM") | |
| else | |
| VERSION=$(node scripts/compute-next-patch.mjs "$RELEASE_LINE" "$RELEASE_TYPE") | |
| fi | |
| # Determine if this is a pre-release | |
| IS_PRERELEASE="false" | |
| NPM_TAG="latest" | |
| if [[ "$VERSION" == *"-rc."* ]]; then | |
| IS_PRERELEASE="true" | |
| NPM_TAG="rc" | |
| elif [[ "$VERSION" == *"-beta."* ]]; then | |
| IS_PRERELEASE="true" | |
| NPM_TAG="beta" | |
| fi | |
| # Check if unified tag already exists | |
| if git rev-parse "v$VERSION" >/dev/null 2>&1; then | |
| echo "::error::Tag v$VERSION already exists!" | |
| exit 1 | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "release_type=$RELEASE_TYPE" >> "$GITHUB_OUTPUT" | |
| echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=$NPM_TAG" >> "$GITHUB_OUTPUT" | |
| echo "Version: $VERSION" | |
| echo "Release type: $RELEASE_TYPE" | |
| echo "Is prerelease: $IS_PRERELEASE" | |
| echo "NPM tag: $NPM_TAG" | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Get previous version | |
| id: prev_version | |
| run: | | |
| RELEASE_LINE="${{ steps.context.outputs.release_line }}" | |
| # Get the latest stable tag for this release line | |
| PREV_TAG=$(git tag --list "v${RELEASE_LINE}.*" --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1) | |
| if [ -z "$PREV_TAG" ]; then | |
| # No previous tag in this line, try previous minor | |
| PREV_TAG=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1) | |
| fi | |
| echo "prev_tag=$PREV_TAG" >> "$GITHUB_OUTPUT" | |
| echo "Previous tag: $PREV_TAG" | |
| - name: Generate diff | |
| id: diff | |
| run: | | |
| PREV_TAG="${{ steps.prev_version.outputs.prev_tag }}" | |
| if [ -n "$PREV_TAG" ]; then | |
| DIFF=$(git diff "$PREV_TAG"..HEAD \ | |
| --stat --patch \ | |
| -- '*.ts' '*.js' '*.json' ':!package-lock.json' ':!*.test.ts' ':!*.spec.ts' \ | |
| | head -c 50000) | |
| else | |
| DIFF="Initial release - no previous version to compare" | |
| fi | |
| # Use file to avoid shell escaping issues | |
| echo "$DIFF" > /tmp/diff.txt | |
| - name: Generate AI changelog | |
| id: ai_changelog | |
| if: ${{ inputs.dry_run != true && inputs.release_type == 'stable' }} | |
| continue-on-error: true | |
| uses: actions/github-script@v7 | |
| env: | |
| OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} | |
| OPENAI_MODEL: ${{ vars.OPENAI_MODEL }} | |
| VERSION: v${{ steps.version.outputs.version }} | |
| VERSION_MINOR: ${{ steps.context.outputs.release_line }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| // Skip if API key is missing | |
| if (!process.env.OPENAI_API_KEY) { | |
| core.warning('OPENAI_API_KEY missing; skipping AI changelog'); | |
| core.setOutput('changelog', ''); | |
| core.setOutput('has_card_mdx', 'false'); | |
| return; | |
| } | |
| try { | |
| const diff = fs.readFileSync('/tmp/diff.txt', 'utf8'); | |
| const releaseDate = new Date().toISOString().split('T')[0]; | |
| const version = process.env.VERSION; | |
| const versionNum = version.replace('v', ''); | |
| const prompt = `You are a technical writer for Enclave, a production-ready JavaScript sandbox for AI agent code execution. | |
| The Enclave ecosystem includes: | |
| - @enclave-vm/ast: AST security guard with CVE protection | |
| - @enclave-vm/core: Secure AgentScript execution environment | |
| - @enclave-vm/types: Protocol types and Zod schemas | |
| - @enclave-vm/stream: NDJSON streaming with encryption | |
| - @enclave-vm/broker: Tool broker with session management | |
| - @enclave-vm/client: Browser and Node.js client SDK | |
| - @enclave-vm/react: React hooks and components | |
| - @enclave-vm/runtime: Standalone deployable runtime | |
| Version: ${version} | |
| Release Date: ${releaseDate} | |
| Git diff: | |
| \`\`\` | |
| ${diff.substring(0, 40000)} | |
| \`\`\` | |
| Generate two outputs: | |
| 1. CHANGELOG entry (Keep a Changelog format): | |
| ## [${versionNum}] - ${releaseDate} | |
| ### Added/Changed/Fixed/Security (only include relevant sections) | |
| - Concise description of changes | |
| 2. A SINGLE Mintlify <Card> component (NOT the full file, just the Card): | |
| <Card | |
| title="Enclave ${version}: Brief title" | |
| href="https://github.com/agentfront/enclave/releases/tag/${version}" | |
| cta="View full changelog" | |
| > | |
| **Feature** – Description. | |
| - Details if needed | |
| </Card> | |
| IMPORTANT: For cardMdx, output ONLY the <Card>...</Card> component, nothing else. | |
| Output ONLY valid JSON: {"changelog": "...", "cardMdx": "<Card...>...</Card>"}`; | |
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ | |
| model: process.env.OPENAI_MODEL || 'gpt-4o', | |
| messages: [{ role: 'user', content: prompt }], | |
| response_format: { type: 'json_object' } | |
| }) | |
| }); | |
| if (!response.ok) throw new Error(`OpenAI API error: ${response.status}`); | |
| const data = await response.json(); | |
| const result = JSON.parse(data.choices[0].message.content); | |
| // Write card MDX to file to avoid shell escaping issues | |
| fs.writeFileSync('/tmp/card-mdx.txt', result.cardMdx); | |
| core.setOutput('changelog', result.changelog); | |
| core.setOutput('has_card_mdx', result.cardMdx ? 'true' : 'false'); | |
| } catch (err) { | |
| core.warning(`AI changelog skipped: ${err.message}`); | |
| core.setOutput('changelog', ''); | |
| core.setOutput('has_card_mdx', 'false'); | |
| } | |
| - name: Update package versions | |
| if: ${{ inputs.dry_run != true }} | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| echo "Setting version $VERSION for all libs" | |
| npx nx release version "$VERSION" --git-commit=false --git-tag=false | |
| - name: Update demo app dependency versions | |
| if: ${{ inputs.dry_run != true }} | |
| shell: bash | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| echo "Updating @enclave-vm/* dependencies in apps to $VERSION" | |
| for pkg in apps/*/package.json; do | |
| [ -f "$pkg" ] || continue | |
| # Update any @enclave-vm/* dependency versions | |
| node -e " | |
| const fs = require('fs'); | |
| const p = JSON.parse(fs.readFileSync('$pkg', 'utf8')); | |
| let changed = false; | |
| for (const section of ['dependencies', 'devDependencies', 'peerDependencies']) { | |
| if (!p[section]) continue; | |
| for (const [name, ver] of Object.entries(p[section])) { | |
| if (name.startsWith('@enclave-vm/') && ver !== '$VERSION') { | |
| p[section][name] = '$VERSION'; | |
| changed = true; | |
| } | |
| } | |
| } | |
| if (changed) { | |
| fs.writeFileSync('$pkg', JSON.stringify(p, null, 2) + '\n'); | |
| console.log('Updated: $pkg'); | |
| } else { | |
| console.log('No changes: $pkg'); | |
| } | |
| " | |
| done | |
| - name: Commit version bump | |
| if: ${{ inputs.dry_run != true }} | |
| run: | | |
| if [ -n "$(git status --porcelain)" ]; then | |
| git add -A | |
| git commit -m "chore(release): v${{ steps.version.outputs.version }}" | |
| git push origin HEAD | |
| fi | |
| - name: Build packages | |
| run: | | |
| echo "Building all lib packages..." | |
| yarn nx run-many --targets=build --projects='libs/*' --parallel | |
| - name: Publish to npm | |
| if: ${{ inputs.dry_run != true }} | |
| shell: bash | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| NPM_TAG="${{ steps.version.outputs.npm_tag }}" | |
| echo "Publishing all libs with tag $NPM_TAG..." | |
| npx nx release publish --tag="$NPM_TAG" | |
| echo "Successfully published version ${{ steps.version.outputs.version }}" | |
| - name: Create and push git tag | |
| if: ${{ inputs.dry_run != true }} | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| TAG="v$VERSION" | |
| BRANCH="${{ steps.context.outputs.branch }}" | |
| # Fetch latest to ensure we tag the committed version | |
| git fetch origin "$BRANCH" | |
| git checkout "$BRANCH" | |
| git pull origin "$BRANCH" | |
| git tag -a "$TAG" -m "Release $TAG" | |
| git push origin "$TAG" | |
| echo "Created and pushed tag: $TAG" | |
| - name: Prepare release body | |
| id: release_body | |
| env: | |
| CHANGELOG: ${{ steps.ai_changelog.outputs.changelog }} | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| RELEASE_TYPE="${{ steps.version.outputs.release_type }}" | |
| RELEASE_LINE="${{ steps.context.outputs.release_line }}" | |
| BRANCH="${{ steps.context.outputs.branch }}" | |
| IS_PRERELEASE="${{ steps.version.outputs.is_prerelease }}" | |
| PROJECTS="core,types,stream,broker,client,react,runtime,ast" | |
| # Start building the release body | |
| { | |
| echo "## Release v${VERSION}" | |
| echo "" | |
| echo "**Release type:** ${RELEASE_TYPE}" | |
| echo "**Release line:** ${RELEASE_LINE}.x" | |
| echo "**Branch:** ${BRANCH}" | |
| echo "" | |
| echo "### Published Packages" | |
| echo "" | |
| } > /tmp/release-body.md | |
| # List published packages with npm links | |
| IFS=',' read -ra LIBS <<< "$PROJECTS" | |
| for lib in "${LIBS[@]}"; do | |
| # Get npm package name from package.json | |
| if [ -f "libs/$lib/package.json" ]; then | |
| NPM_NAME=$(node -p "require('./libs/$lib/package.json').name") | |
| echo "- [\`${NPM_NAME}@${VERSION}\`](https://www.npmjs.com/package/${NPM_NAME}/v/${VERSION})" >> /tmp/release-body.md | |
| fi | |
| done | |
| # Add AI-generated changelog if available | |
| if [ -f /tmp/card-mdx.txt ] && [ -s /tmp/card-mdx.txt ] && [ -n "$CHANGELOG" ]; then | |
| echo "" >> /tmp/release-body.md | |
| echo "$CHANGELOG" >> /tmp/release-body.md | |
| fi | |
| # Add pre-release note if applicable | |
| if [ "$IS_PRERELEASE" = "true" ]; then | |
| echo "" >> /tmp/release-body.md | |
| echo "> **Note:** This is a pre-release version." >> /tmp/release-body.md | |
| fi | |
| # Add Card MDX as hidden comment for docs sync (only for stable releases) | |
| # NOTE: Content is sanitized to prevent --> from breaking the HTML comment. | |
| # Consumer must reverse: replace "-->" with "-->" after extraction. | |
| if [ -f /tmp/card-mdx.txt ] && [ -s /tmp/card-mdx.txt ]; then | |
| echo "" >> /tmp/release-body.md | |
| echo "<!--" >> /tmp/release-body.md | |
| echo "CARD_MDX_START" >> /tmp/release-body.md | |
| sed 's/-->/--\>/g' /tmp/card-mdx.txt >> /tmp/release-body.md | |
| echo "CARD_MDX_END" >> /tmp/release-body.md | |
| echo "-->" >> /tmp/release-body.md | |
| fi | |
| - name: Create GitHub Release | |
| if: ${{ inputs.dry_run != true }} | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.version.outputs.version }} | |
| name: v${{ steps.version.outputs.version }} | |
| prerelease: ${{ steps.version.outputs.is_prerelease }} | |
| generate_release_notes: false | |
| body_path: /tmp/release-body.md | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Trigger docs sync | |
| if: ${{ inputs.dry_run != true && steps.version.outputs.is_prerelease == 'false' }} | |
| continue-on-error: true | |
| uses: actions/github-script@v7 | |
| env: | |
| VERSION: v${{ steps.version.outputs.version }} | |
| VERSION_MINOR: ${{ steps.context.outputs.release_line }} | |
| with: | |
| github-token: ${{ secrets.DOCS_SYNC_TOKEN }} | |
| script: | | |
| const tag = process.env.VERSION; | |
| const versionMinor = process.env.VERSION_MINOR; | |
| const sha = context.sha; | |
| console.log(`Triggering docs sync for enclave`); | |
| console.log(` Tag: ${tag}`); | |
| console.log(` SHA: ${sha}`); | |
| console.log(` Version minor: ${versionMinor}`); | |
| try { | |
| await github.rest.repos.createDispatchEvent({ | |
| owner: 'agentfront', | |
| repo: 'docs', | |
| event_type: 'sync-docs', | |
| client_payload: { | |
| repo: 'enclave', | |
| sha: sha, | |
| tag: tag, | |
| version_minor: versionMinor | |
| } | |
| }); | |
| console.log(`Successfully triggered docs sync for ${tag}`); | |
| } catch (error) { | |
| console.error(`Failed to trigger docs sync: ${error.message}`); | |
| // Don't fail the release for docs sync issues | |
| } | |
| - name: Summary | |
| run: | | |
| if [ "${{ inputs.dry_run }}" = "true" ]; then | |
| echo "## Dry Run Summary" >> "$GITHUB_STEP_SUMMARY" | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "> **This was a dry run. No packages were published.**" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "## Release Complete" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| echo "" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Property | Value |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Version | \`${{ steps.version.outputs.version }}\` |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Tag | \`v${{ steps.version.outputs.version }}\` |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Release type | ${{ steps.version.outputs.release_type }} |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| NPM tag | \`${{ steps.version.outputs.npm_tag }}\` |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Pre-release | ${{ steps.version.outputs.is_prerelease }} |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Branch | \`${{ steps.context.outputs.branch }}\` |" >> "$GITHUB_STEP_SUMMARY" | |
| echo "| Packages | All libs/* |" >> "$GITHUB_STEP_SUMMARY" | |
| cherry-pick-version-to-main: | |
| needs: publish | |
| if: > | |
| inputs.dry_run != true && | |
| needs.publish.outputs.is_prerelease == 'false' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check if latest semver | |
| id: check | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ needs.publish.outputs.version }}" | |
| git fetch --tags | |
| # Get all stable version tags, sort by semver, pick highest | |
| LATEST=$(git tag --list 'v*' \ | |
| | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ | |
| | sort -V \ | |
| | tail -1 \ | |
| | sed 's/^v//') | |
| echo "Released version: $VERSION" | |
| echo "Latest stable tag: $LATEST" | |
| if [ "$VERSION" = "$LATEST" ]; then | |
| echo "is_latest=true" >> "$GITHUB_OUTPUT" | |
| echo "This is the latest version — will cherry-pick to main" | |
| else | |
| echo "is_latest=false" >> "$GITHUB_OUTPUT" | |
| echo "Skipping: v$VERSION is not the latest (v$LATEST is newer)" | |
| fi | |
| - name: Configure git | |
| if: steps.check.outputs.is_latest == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Cherry-pick version bump to main | |
| if: steps.check.outputs.is_latest == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="${{ needs.publish.outputs.version }}" | |
| RELEASE_BRANCH="${{ needs.publish.outputs.branch }}" | |
| DEFAULT_BRANCH="${{ github.event.repository.default_branch }}" | |
| # Find the version bump commit on the release branch | |
| VERSION_COMMIT=$(git log "origin/$RELEASE_BRANCH" \ | |
| --grep="chore(release): v${VERSION}" \ | |
| --format="%H" -1) | |
| if [ -z "$VERSION_COMMIT" ]; then | |
| echo "::warning::Could not find version bump commit for v${VERSION}" | |
| exit 0 | |
| fi | |
| echo "Found version bump commit: $VERSION_COMMIT" | |
| git fetch origin "$DEFAULT_BRANCH" | |
| # Skip if the version bump is already on the default branch | |
| if git merge-base --is-ancestor "$VERSION_COMMIT" "origin/$DEFAULT_BRANCH"; then | |
| echo "Version bump commit $VERSION_COMMIT is already on $DEFAULT_BRANCH — skipping cherry-pick" | |
| exit 0 | |
| fi | |
| # Prepare cherry-pick branch | |
| CHERRY_BRANCH="cherry-pick/v${VERSION}-version-to-main" | |
| git checkout "$DEFAULT_BRANCH" | |
| git pull origin "$DEFAULT_BRANCH" | |
| # Clean up existing remote branch if any | |
| git push origin --delete "$CHERRY_BRANCH" 2>/dev/null || true | |
| git checkout -b "$CHERRY_BRANCH" | |
| # Attempt cherry-pick | |
| if git cherry-pick "$VERSION_COMMIT" --no-commit; then | |
| # Check if cherry-pick produced any changes (may be empty if already applied via a different commit) | |
| if [ -z "$(git diff --cached --name-only)" ]; then | |
| echo "Cherry-pick produced no changes — version bump already applied on $DEFAULT_BRANCH" | |
| git reset HEAD 2>/dev/null || true | |
| exit 0 | |
| fi | |
| git commit -m "$(cat <<EOF | |
| chore: sync version to $VERSION | |
| Cherry-picked from $RELEASE_BRANCH (release v$VERSION) | |
| Original commit: $VERSION_COMMIT | |
| EOF | |
| )" | |
| git push origin "$CHERRY_BRANCH" | |
| gh pr create \ | |
| --base "$DEFAULT_BRANCH" \ | |
| --head "$CHERRY_BRANCH" \ | |
| --title "chore: sync version to v${VERSION}" \ | |
| --label "cherry-pick" \ | |
| --label "auto-cherry-pick" \ | |
| --body "$(cat <<EOF | |
| ## Version sync to main | |
| Updates all \`@enclave-vm/*\` package versions to \`${VERSION}\` on \`${DEFAULT_BRANCH}\`. | |
| This cherry-pick was automatically created because \`v${VERSION}\` is the **latest stable release**. | |
| **Source:** \`${RELEASE_BRANCH}\` release v${VERSION} | |
| --- | |
| _Auto-generated by the publish-release workflow._ | |
| EOF | |
| )" | |
| echo "Cherry-pick PR created successfully" | |
| else | |
| # Check if failure is due to empty cherry-pick (already applied) vs actual conflicts | |
| if [ -z "$(git status --porcelain)" ]; then | |
| echo "Cherry-pick is empty — version bump already applied on $DEFAULT_BRANCH" | |
| git cherry-pick --abort 2>/dev/null || true | |
| exit 0 | |
| fi | |
| git cherry-pick --abort || true | |
| echo "::warning::Cherry-pick had conflicts. Creating issue for manual resolution." | |
| gh issue create \ | |
| --title "Manual version sync needed: v${VERSION} to main" \ | |
| --label "cherry-pick" \ | |
| --label "conflict" \ | |
| --label "needs-attention" \ | |
| --body "$(cat <<EOF | |
| ## Manual Version Sync Required | |
| Auto cherry-pick of version bump to \`v${VERSION}\` failed due to conflicts. | |
| ### Manual Steps | |
| \`\`\`bash | |
| git checkout $DEFAULT_BRANCH && git pull | |
| git checkout -b cherry-pick/v${VERSION}-version-to-main | |
| git cherry-pick $VERSION_COMMIT | |
| # Resolve conflicts | |
| git add . && git cherry-pick --continue | |
| git push origin cherry-pick/v${VERSION}-version-to-main | |
| gh pr create --base $DEFAULT_BRANCH --title "chore: sync version to v${VERSION}" | |
| \`\`\` | |
| --- | |
| _Auto-generated by the publish-release workflow._ | |
| EOF | |
| )" | |
| fi |