Post Release to X #13
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: Post Release to X | |
| on: | |
| workflow_run: | |
| workflows: ["Release"] | |
| types: [completed] | |
| workflow_dispatch: | |
| jobs: | |
| notify: | |
| runs-on: ubuntu-latest | |
| # Run if: workflow_dispatch (manual) OR workflow_run completed successfully | |
| if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Get latest release with changes | |
| id: release | |
| run: | | |
| # Get the latest @evolution-sdk release with actual changes (not just dependency updates) | |
| RELEASES=$(gh api /repos/${{ github.repository }}/releases --jq '[.[] | select(.tag_name | startswith("@evolution-sdk/"))]') | |
| # Variables to track packages | |
| FALLBACK_TAG="" | |
| FALLBACK_URL="" | |
| FALLBACK_VERSION="" | |
| EVOLUTION_TAG="" | |
| EVOLUTION_URL="" | |
| EVOLUTION_VERSION="" | |
| for i in $(seq 0 10); do | |
| RELEASE=$(echo $RELEASES | jq ".[$i]") | |
| if [ "$RELEASE" = "null" ]; then | |
| break | |
| fi | |
| CREATED=$(echo $RELEASE | jq -r .created_at) | |
| CREATED_TIMESTAMP=$(date -d "$CREATED" +%s) | |
| NOW=$(date +%s) | |
| AGE=$((NOW - CREATED_TIMESTAMP)) | |
| # If created in last hour, check if it has real changes (not just dependency updates) | |
| if [ $AGE -lt 3600 ]; then | |
| BODY=$(echo $RELEASE | jq -r .body) | |
| RELEASE_TAG=$(echo $RELEASE | jq -r .tag_name) | |
| RELEASE_URL=$(echo $RELEASE | jq -r .html_url) | |
| # Check for actual changes (not just "Updated dependencies") | |
| CHANGES_SECTION=$(echo "$BODY" | grep -A 100 "Patch Changes\|Minor Changes\|Major Changes" | tail -n +2) | |
| REAL_CHANGES=$(echo "$CHANGES_SECTION" | grep -v "Updated dependencies" | grep -v "^[[:space:]]*-[[:space:]]*@" | grep -v "^[[:space:]]*$" | head -1) | |
| if [ -n "$REAL_CHANGES" ]; then | |
| # Extract version number (remove scoped package prefix) | |
| VERSION=${RELEASE_TAG##*@} | |
| # Check if this is the main evolution package | |
| if [[ "$RELEASE_TAG" == "@evolution-sdk/evolution@"* ]]; then | |
| if [ -z "$EVOLUTION_TAG" ]; then | |
| EVOLUTION_TAG=$RELEASE_TAG | |
| EVOLUTION_URL=$RELEASE_URL | |
| EVOLUTION_VERSION=$VERSION | |
| fi | |
| else | |
| # Save as fallback if we haven't found one yet | |
| if [ -z "$FALLBACK_TAG" ]; then | |
| FALLBACK_TAG=$RELEASE_TAG | |
| FALLBACK_URL=$RELEASE_URL | |
| FALLBACK_VERSION=$VERSION | |
| fi | |
| fi | |
| fi | |
| fi | |
| done | |
| # Prioritize evolution package, fall back to other packages | |
| if [ -n "$EVOLUTION_TAG" ]; then | |
| echo "tag=$EVOLUTION_TAG" >> $GITHUB_OUTPUT | |
| echo "url=$EVOLUTION_URL" >> $GITHUB_OUTPUT | |
| echo "version=$EVOLUTION_VERSION" >> $GITHUB_OUTPUT | |
| echo "found=true" >> $GITHUB_OUTPUT | |
| elif [ -n "$FALLBACK_TAG" ]; then | |
| echo "tag=$FALLBACK_TAG" >> $GITHUB_OUTPUT | |
| echo "url=$FALLBACK_URL" >> $GITHUB_OUTPUT | |
| echo "version=$FALLBACK_VERSION" >> $GITHUB_OUTPUT | |
| echo "found=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "found=false" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract code from CHANGELOG | |
| if: steps.release.outputs.found == 'true' | |
| id: extract | |
| run: | | |
| VERSION="${{ steps.release.outputs.version }}" | |
| # Extract first code block from this version's section | |
| awk -v ver="$VERSION" ' | |
| index($0, "## " ver) == 1 { found_version=1; next } | |
| found_version && /^## [0-9]/ { exit } | |
| found_version && /```typescript/ { in_code=1; next } | |
| found_version && /```/ && in_code { exit } | |
| in_code { print } | |
| ' packages/evolution/CHANGELOG.md > /tmp/snippet.ts | |
| if [ -s /tmp/snippet.ts ]; then | |
| echo "has_code=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "has_code=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Setup Node.js | |
| if: steps.release.outputs.found == 'true' && steps.extract.outputs.has_code == 'true' | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Generate code image | |
| if: steps.release.outputs.found == 'true' && steps.extract.outputs.has_code == 'true' | |
| run: | | |
| npx -y carbon-now-cli /tmp/snippet.ts \ | |
| --save-to . \ | |
| --save-as release-snippet \ | |
| --headless \ | |
| --settings '{"theme":"blackboard","backgroundColor":"#FD4B1B","windowTheme":"none","windowControls":true,"fontFamily":"JetBrains Mono","fontSize":"14px","lineNumbers":false,"paddingVertical":"40px","paddingHorizontal":"40px","dropShadow":true,"dropShadowOffsetY":"10px","dropShadowBlurRadius":"30px","exportSize":"2x","watermark":false}' | |
| - name: Post to X with image | |
| if: steps.release.outputs.found == 'true' && steps.extract.outputs.has_code == 'true' | |
| run: | | |
| npm install twitter-api-v2 | |
| node -e " | |
| const { TwitterApi } = require('twitter-api-v2'); | |
| const client = new TwitterApi({ | |
| appKey: process.env.TWITTER_API_KEY, | |
| appSecret: process.env.TWITTER_API_SECRET, | |
| accessToken: process.env.TWITTER_ACCESS_TOKEN, | |
| accessSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET, | |
| }); | |
| (async () => { | |
| const mediaId = await client.v1.uploadMedia('./release-snippet.png'); | |
| await client.v2.tweet({ | |
| text: 'Evolution SDK v${{ steps.release.outputs.version }} out now\n${{ steps.release.outputs.url }}\nHappy building!', | |
| media: { media_ids: [mediaId] } | |
| }); | |
| })() | |
| .then(() => process.exit(0)) | |
| .catch(err => { | |
| console.error(err); | |
| process.exit(1); | |
| }); | |
| " | |
| env: | |
| TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }} | |
| TWITTER_API_SECRET: ${{ secrets.TWITTER_API_SECRET }} | |
| TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} | |
| TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} | |
| - name: Post to X without image | |
| if: steps.release.outputs.found == 'true' && steps.extract.outputs.has_code == 'false' | |
| run: | | |
| npm install twitter-api-v2@1 | |
| node -e " | |
| const { TwitterApi } = require('twitter-api-v2'); | |
| const client = new TwitterApi({ | |
| appKey: process.env.TWITTER_API_KEY, | |
| appSecret: process.env.TWITTER_API_SECRET, | |
| accessToken: process.env.TWITTER_ACCESS_TOKEN, | |
| accessSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET, | |
| }); | |
| (async () => { | |
| await client.v2.tweet({ | |
| text: 'Evolution SDK v${{ steps.release.outputs.version }} out now\n${{ steps.release.outputs.url }}\nHappy building!' | |
| }); | |
| })() | |
| .then(() => process.exit(0)) | |
| .catch(err => { | |
| console.error(err); | |
| process.exit(1); | |
| }); | |
| " | |
| env: | |
| TWITTER_API_KEY: ${{ secrets.TWITTER_API_KEY }} | |
| TWITTER_API_SECRET: ${{ secrets.TWITTER_API_SECRET }} | |
| TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} | |
| TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} |