Skip to content

Post Release to X

Post Release to X #13

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 }}