Blog Draft Pipeline #6
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: Blog Draft (Every 2 Days) | |
| on: | |
| schedule: | |
| # Every 2 days at 8am UTC (odd days: 1st, 3rd, 5th...) | |
| # NOTE: 1-31/2 fires on consecutive days at month boundaries (31st then 1st). | |
| # The pipeline is idempotent (skips if no Detected cards), so extra runs are harmless. | |
| - cron: '0 8 1-31/2 * *' | |
| workflow_dispatch: # Manual trigger | |
| concurrency: | |
| group: blog-draft | |
| cancel-in-progress: false | |
| permissions: | |
| repository-projects: write | |
| contents: write | |
| id-token: write | |
| jobs: | |
| pick-and-draft: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: develop | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Install pipeline dependencies | |
| working-directory: blog/pipeline | |
| run: npm ci | |
| - name: Pick next topic | |
| id: pick | |
| working-directory: blog/pipeline | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| OUTPUT=$(node index.js --pick 2>&1) | |
| echo "$OUTPUT" | |
| PICKED=$(echo "$OUTPUT" | grep '::picked::' | sed 's/::picked:://') | |
| if [ -z "$PICKED" ]; then | |
| echo "No topic picked — all clusters have active work" | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "$PICKED" > /tmp/picked-topic.json | |
| TOPIC=$(echo "$PICKED" | jq -r '.cluster') | |
| CONTENT_TYPE=$(echo "$PICKED" | jq -r '.contentType') | |
| CARD_ID=$(echo "$PICKED" | jq -r '.id') | |
| CARD_BODY=$(echo "$PICKED" | jq -r '.body // ""') | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| echo "topic=$TOPIC" >> $GITHUB_OUTPUT | |
| echo "content_type=$CONTENT_TYPE" >> $GITHUB_OUTPUT | |
| echo "card_id=$CARD_ID" >> $GITHUB_OUTPUT | |
| # Write card body to workspace file so Claude can read it | |
| echo "$CARD_BODY" > "$GITHUB_WORKSPACE/blog/pipeline/.card-body.md" | |
| fi | |
| - name: Move card to Researched | |
| if: steps.pick.outputs.skip != 'true' | |
| working-directory: blog/pipeline | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| CARD_ID: ${{ steps.pick.outputs.card_id }} | |
| run: | | |
| node --input-type=module -e " | |
| import { updateCardStatus } from './project.js'; | |
| updateCardStatus(process.env.CARD_ID, 'researched'); | |
| console.log('Card moved to Researched'); | |
| " | |
| - name: Research and draft | |
| id: draft | |
| if: steps.pick.outputs.skip != 'true' | |
| uses: anthropics/claude-code-action@9469d113c6afd29550c402740f22d1a97dd1209b # v1 | |
| with: | |
| prompt: | | |
| You are writing a blog article for the Ethernal blog (On-Chain Engineering). | |
| A trending topic has been detected by our pipeline: | |
| **Topic:** ${{ steps.pick.outputs.topic }} | |
| **Content Type:** ${{ steps.pick.outputs.content_type }} | |
| Read the file `blog/pipeline/.card-body.md` for the full trend card content with sources to research. | |
| Steps: | |
| 1. Run /blog:research for this topic. Focus on the specific EIPs, ERCs, papers, and forum posts listed in the trend card — those are your primary sources. | |
| 2. Run /blog:draft to write the article. Use the Content Type to determine the format: | |
| - "ERC Tutorial": Code-heavy, include working Solidity, deploy instructions, practical examples | |
| - "EIP Explainer": What it changes, why it matters, code impact with before/after examples | |
| - "Research Deep Dive": Break down the paper/proposal, extract practical insights, include code snippets | |
| - "Upgrade Guide": Step-by-step migration guide with code changes needed | |
| - "Trend Survey": Survey multiple related proposals, compare approaches, include interface examples | |
| Cite the sources from the trend card as references in the article. | |
| 3. IMPORTANT: Set the article frontmatter status to "draft" (not "published"). | |
| 4. Commit the article directly to the develop branch with message "blog: add draft — <article title>". | |
| 5. Push to origin develop. | |
| 6. After pushing, output the article file path relative to the repo root on a line starting with "::article-path::" | |
| Example: ::article-path::blog/src/content/blog/my-article.md | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| settings: | | |
| { | |
| "permissions": { | |
| "allow": ["Read", "Write", "Edit", "Bash", "WebSearch", "WebFetch", "Glob", "Grep", "Skill"] | |
| } | |
| } | |
| - name: Update project card | |
| if: steps.pick.outputs.skip != 'true' | |
| working-directory: blog/pipeline | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| # Search last 5 commits in case Claude pushed multiple | |
| ARTICLE_PATH=$(git log -5 --name-only --pretty=format: | grep 'blog/src/content/blog/' | head -1) | |
| if [ -n "$ARTICLE_PATH" ]; then | |
| echo "Article: $ARTICLE_PATH" | |
| CARD_ID="${{ steps.pick.outputs.card_id }}" ARTICLE_PATH="$ARTICLE_PATH" node --input-type=module -e " | |
| import { updateCardStatus, setArticlePath } from './project.js'; | |
| updateCardStatus(process.env.CARD_ID, 'drafting'); | |
| setArticlePath(process.env.CARD_ID, process.env.ARTICLE_PATH); | |
| console.log('Card updated'); | |
| " | |
| else | |
| echo "WARNING: Could not find article path in last commit" | |
| fi | |
| - name: Reset card on failure | |
| if: failure() && steps.draft.outcome == 'failure' && steps.pick.outputs.skip != 'true' | |
| working-directory: blog/pipeline | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| CARD_ID: ${{ steps.pick.outputs.card_id }} | |
| run: | | |
| node --input-type=module -e " | |
| import { updateCardStatus } from './project.js'; | |
| updateCardStatus(process.env.CARD_ID, 'detected'); | |
| console.log('Card reset to Detected after failure'); | |
| " |