Merge pull request #1015 from kaitranntt/dev #205
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 | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| release: | |
| if: ${{ github.ref == 'refs/heads/main' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # PAT_TOKEN required: semantic-release pushes version bump commits to main, | |
| # which needs branch protection bypass. github.token cannot push to protected branches. | |
| token: ${{ secrets.PAT_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: '1.3.9' | |
| - name: Install dependencies | |
| run: | | |
| bun install --frozen-lockfile | |
| cd ui && bun install --frozen-lockfile | |
| - name: Build package | |
| run: bun run build:all | |
| - name: Validate (typecheck + lint + format + tests) | |
| run: bun run validate | |
| - name: Release | |
| id: release | |
| env: | |
| HUSKY: 0 | |
| # PAT_TOKEN bypasses branch protection so semantic-release can push | |
| # version bump + CHANGELOG commits directly to main | |
| GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} | |
| GH_TOKEN: ${{ secrets.PAT_TOKEN }} | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| set +e | |
| OUTPUT=$(bunx semantic-release 2>&1) | |
| STATUS=$? | |
| set -e | |
| echo "$OUTPUT" | |
| # Strip ANSI color codes before matching (semantic-release outputs colored text) | |
| CLEAN=$(echo "$OUTPUT" | sed 's/\x1b\[[0-9;]*m//g') | |
| if [[ "$STATUS" -ne 0 ]]; then | |
| echo "released=false" >> $GITHUB_OUTPUT | |
| exit "$STATUS" | |
| fi | |
| if echo "$CLEAN" | grep -q "Published GitHub release"; then | |
| echo "released=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "released=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Notify Discord | |
| if: success() && steps.release.outputs.released == 'true' | |
| env: | |
| DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| run: | | |
| if [ -z "$DISCORD_WEBHOOK_URL" ]; then | |
| echo "DISCORD_WEBHOOK_URL not set, skipping" | |
| exit 0 | |
| fi | |
| node scripts/send-discord-release.cjs production "$DISCORD_WEBHOOK_URL" | |
| - name: Cleanup labels and close released issues | |
| if: success() && steps.release.outputs.released == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION=$(jq -r '.version' package.json) | |
| RELEASE_BODY=$(gh release view "v${VERSION}" --repo "${{ github.repository }}" --json body --jq '.body' 2>/dev/null || echo "") | |
| PREVIOUS_STABLE_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]" --sort=-v:refname | \ | |
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | \ | |
| grep -vx "v${VERSION}" | head -1 || echo "") | |
| RANGE="${PREVIOUS_STABLE_TAG:+${PREVIOUS_STABLE_TAG}..HEAD~1}" | |
| if [[ -z "$RANGE" ]]; then | |
| RANGE="HEAD~50..HEAD~1" | |
| fi | |
| COMMIT_TEXT=$(git log $RANGE --pretty=format:"%s%n%b" 2>/dev/null || true) | |
| PR_CANDIDATES=$(printf '%s\n' "$COMMIT_TEXT" | \ | |
| grep -oE "Merge pull request #[0-9]+|\\(#[0-9]+\\)" | \ | |
| grep -oE "[0-9]+" | sort -u || true) | |
| PR_TEXT="" | |
| for PR_NUM in $PR_CANDIDATES; do | |
| DETAILS=$(gh pr view "$PR_NUM" --repo "${{ github.repository }}" --json title,body --jq '.title + "\n" + (.body // "")' 2>/dev/null || true) | |
| if [[ -n "$DETAILS" ]]; then | |
| PR_TEXT="${PR_TEXT}"$'\n'"${DETAILS}" | |
| fi | |
| done | |
| RELEASE_ISSUES_FROM_BODY=$(printf '%s\n' "$RELEASE_BODY" | \ | |
| perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ | |
| grep -oE '#[0-9]+' | tr -d '#' || true) | |
| RELEASE_ISSUES_FROM_COMMITS=$(printf '%s\n' "$COMMIT_TEXT" | \ | |
| perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ | |
| grep -oE '#[0-9]+' | tr -d '#' || true) | |
| RELEASE_ISSUES_FROM_PRS=$(printf '%s\n' "$PR_TEXT" | \ | |
| perl -ne 'if (/(fixes|closes|resolves|refs?)(.*)/i) { print "$2\n"; }' | \ | |
| grep -oE '#[0-9]+' | tr -d '#' || true) | |
| RELEASE_ISSUES=$(printf '%s\n%s\n%s\n' \ | |
| "$RELEASE_ISSUES_FROM_BODY" \ | |
| "$RELEASE_ISSUES_FROM_COMMITS" \ | |
| "$RELEASE_ISSUES_FROM_PRS" | sort -u || true) | |
| RESOLVED_ISSUES_FROM_BODY=$(printf '%s\n' "$RELEASE_BODY" | \ | |
| perl -ne 'if (/(fixes|closes|resolves)(.*)/i) { print "$2\n"; }' | \ | |
| grep -oE '#[0-9]+' | tr -d '#' || true) | |
| RESOLVED_ISSUES_FROM_COMMITS=$(printf '%s\n' "$COMMIT_TEXT" | \ | |
| perl -ne 'if (/(fixes|closes|resolves)(.*)/i) { print "$2\n"; }' | \ | |
| grep -oE '#[0-9]+' | tr -d '#' || true) | |
| RESOLVED_ISSUES_FROM_PRS=$(printf '%s\n' "$PR_TEXT" | \ | |
| perl -ne 'if (/(fixes|closes|resolves)(.*)/i) { print "$2\n"; }' | \ | |
| grep -oE '#[0-9]+' | tr -d '#' || true) | |
| RESOLVED_ISSUES=$(printf '%s\n%s\n%s\n' \ | |
| "$RESOLVED_ISSUES_FROM_BODY" \ | |
| "$RESOLVED_ISSUES_FROM_COMMITS" \ | |
| "$RESOLVED_ISSUES_FROM_PRS" | sort -u || true) | |
| if [[ -z "$RELEASE_ISSUES" ]]; then | |
| echo "No release-scoped issues found for v${VERSION}" | |
| exit 0 | |
| fi | |
| for NUM in $RELEASE_ISSUES; do | |
| echo "Cleaning release state on issue #$NUM" | |
| gh issue edit "$NUM" \ | |
| --remove-label "released-dev" \ | |
| --remove-label "pending-release" \ | |
| --repo "${{ github.repository }}" 2>/dev/null || true | |
| HAS_RELEASED=$(gh issue view "$NUM" --repo "${{ github.repository }}" --json labels --jq '[.labels[].name | select(. == "released")] | length' 2>/dev/null || echo "0") | |
| if [[ "$HAS_RELEASED" -gt 0 ]] && printf '%s\n' "$RESOLVED_ISSUES" | grep -qx "$NUM"; then | |
| gh issue close "$NUM" \ | |
| --comment "[bot] Closing issue because this fix/feature is now in stable release (@latest)." \ | |
| --repo "${{ github.repository }}" || true | |
| fi | |
| done |