Bump & Publish #7
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: Bump & Publish | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release: | |
| description: "Version bump type (major | minor | patch)" | |
| required: true | |
| default: patch | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| jobs: | |
| bump-and-publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| packages: write | |
| if: github.ref == 'refs/heads/dev' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| registry-url: "https://registry.npmjs.org" | |
| scope: "@impulselab" | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10.15.0 | |
| - name: Verify npm auth | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| if [ -z "${NODE_AUTH_TOKEN}" ]; then | |
| echo "NPM token is missing. Set repository secret NPM_TOKEN with publish permissions." >&2 | |
| exit 1 | |
| fi | |
| npm whoami --registry=https://registry.npmjs.org || { echo "npm auth failed. Check token scope." >&2; exit 1; } | |
| - name: Install | |
| run: pnpm install --frozen-lockfile | |
| - name: Configure Git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Bump version in package.json | |
| id: bump | |
| env: | |
| RELEASE: ${{ github.event.inputs.release }} | |
| run: | | |
| bump() { | |
| local file=$1 | |
| local release=$2 | |
| current=$(jq -r '.version' "$file") | |
| IFS='.' read -r major minor patch <<< "$current" | |
| case "$release" in | |
| major) | |
| major=$((major+1)); minor=0; patch=0;; | |
| minor) | |
| minor=$((minor+1)); patch=0;; | |
| patch) | |
| patch=$((patch+1));; | |
| *) echo "Invalid release type: $release"; exit 1;; | |
| esac | |
| new="${major}.${minor}.${patch}" | |
| tmp=$(mktemp) | |
| jq --arg v "$new" '.version = $v' "$file" > "$tmp" && mv "$tmp" "$file" | |
| echo "$new" | |
| } | |
| NEW_VER=$(bump package.json "$RELEASE") | |
| echo "version=$NEW_VER" >> $GITHUB_OUTPUT | |
| - name: Commit version bumps | |
| run: | | |
| git add package.json | |
| git commit -m "chore(release): bump versions (version: ${{ steps.bump.outputs.version }})" | |
| git push | |
| - name: Build workspace | |
| run: pnpm build | |
| - name: Publish @impulselab/testing | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| npm config set //registry.npmjs.org/:_authToken "$NODE_AUTH_TOKEN" | |
| npm config set @impulselab:registry https://registry.npmjs.org/ | |
| pnpm publish --access public --no-git-checks | |
| # Deploy to production by creating tag and updating main branch | |
| - name: Create and push Git tag | |
| run: | | |
| git tag v${{ steps.bump.outputs.version }} | |
| git push origin v${{ steps.bump.outputs.version }} | |
| - name: Update main to tag commit | |
| run: | | |
| git fetch origin main --prune | |
| git checkout -B main | |
| git push origin main --force-with-lease | |
| - name: Send Discord deployment notification | |
| if: success() | |
| env: | |
| DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| run: | | |
| # Extract metadata | |
| TAG_NAME="v${{ steps.bump.outputs.version }}" | |
| TAG_SHA="${{ github.sha }}" | |
| SHORT_SHA=$(echo "$TAG_SHA" | cut -c1-7) | |
| REPO_URL="https://github.com/${{ github.repository }}" | |
| COMMIT_URL="${REPO_URL}/commit/${TAG_SHA}" | |
| WORKFLOW_URL="${REPO_URL}/actions/runs/${{ github.run_id }}" | |
| TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| PROJECT_NAME=$(jq -r '.name' package.json) | |
| # Build Discord embed payload | |
| PAYLOAD=$(cat <<EOF | |
| { | |
| "embeds": [{ | |
| "title": "✅ Production Deployment Successful", | |
| "color": 3066993, | |
| "fields": [ | |
| { | |
| "name": "Project", | |
| "value": "${PROJECT_NAME}", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Environment", | |
| "value": "production", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Tag Version", | |
| "value": "${TAG_NAME}", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Commit", | |
| "value": "[${SHORT_SHA}](${COMMIT_URL})", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Deployed At", | |
| "value": "${TIMESTAMP}", | |
| "inline": true | |
| }, | |
| { | |
| "name": "Workflow Run", | |
| "value": "[View Run](${WORKFLOW_URL})", | |
| "inline": true | |
| } | |
| ], | |
| "timestamp": "${TIMESTAMP}" | |
| }] | |
| } | |
| EOF | |
| ) | |
| # Send to Discord webhook | |
| if [ -z "$DISCORD_WEBHOOK_URL" ]; then | |
| echo "⚠️ DISCORD_WEBHOOK_URL secret is not set. Skipping notification." | |
| exit 0 | |
| fi | |
| HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" \ | |
| "$DISCORD_WEBHOOK_URL") | |
| if [ "$HTTP_STATUS" -eq 204 ] || [ "$HTTP_STATUS" -eq 200 ]; then | |
| echo "✅ Discord notification sent successfully (HTTP $HTTP_STATUS)" | |
| else | |
| echo "⚠️ Failed to send Discord notification (HTTP $HTTP_STATUS)" | |
| exit 1 | |
| fi | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: v${{ steps.bump.outputs.version }} | |
| name: Release v${{ steps.bump.outputs.version }} | |
| body: | | |
| Release v${{ steps.bump.outputs.version }} | |
| **Package Versions:** | |
| - @impulselab/testing: v${{ steps.bump.outputs.version }} | |
| generate_release_notes: true | |
| token: ${{ secrets.GITHUB_TOKEN }} |