Skip to content

Auto-publish scheduled posts #954

Auto-publish scheduled posts

Auto-publish scheduled posts #954

Workflow file for this run

name: Auto-publish scheduled posts
# Runs hourly. Reads docs/blog/.schedule.yml, finds entries whose publish_at
# has passed, removes `draft: true` from each target post, marks the entry
# as published, and commits. The resulting commit to `main` triggers the
# normal deploy workflow.
on:
schedule:
- cron: "5 * * * *" # five-past every hour, UTC
workflow_dispatch:
permissions:
contents: write
issues: write
actions: write # to trigger deploy.yml via workflow_dispatch
concurrency:
group: schedule-mutation
cancel-in-progress: false
jobs:
publish-due:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: main
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install PyYAML
run: pip install pyyaml
- name: Publish due entries
id: run
run: |
set -e
python scripts/schedule_lib.py publish-due --actor "auto-publisher" | tee result.json
published_count=$(python -c "import json; d=json.load(open('result.json')); print(len(d.get('published', [])))")
echo "count=$published_count" >> "$GITHUB_OUTPUT"
- name: Commit and push
if: steps.run.outputs.count != '0'
env:
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -e
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
slugs=$(python -c "import json; d=json.load(open('result.json')); print(', '.join(p['slug'] for p in d['published']))")
git add docs/blog/
git commit -m "auto-publish: $slugs" \
-m "Triggered by hourly scheduler." \
-m "Run: $RUN_URL"
git push origin main
- name: Trigger deploy
# A push made with the default GITHUB_TOKEN does NOT fire deploy.yml's
# `on: push`. workflow_dispatch is exempt from that rule, so kick it
# explicitly to ship the just-published posts.
if: steps.run.outputs.count != '0'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run deploy.yml --ref main
- name: Close scheduling issues
id: closed
if: steps.run.outputs.count != '0'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const result = JSON.parse(fs.readFileSync('result.json', 'utf8'));
// The Jac site serves posts at a stable slug-only URL, so the live
// URL is the same link the tracker/docs issues already advertised.
const published = (result.published || []).map(p => ({
slug: p.slug,
url: `https://blogs.jaseci.org/blog/posts/${p.slug}`,
}));
core.setOutput('published', JSON.stringify(published));
const open = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner, repo: context.repo.repo,
state: 'open', labels: 'blog-schedule', per_page: 100,
});
for (const pub of published) {
const found = open.find(i => (i.body || '').includes(`slug=${pub.slug} `));
if (!found) continue;
const msg = pub.url ? `🎉 Published — now live at ${pub.url}` : '🎉 Published.';
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: found.number, body: msg,
});
await github.rest.issues.update({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: found.number, state: 'closed',
});
}
- name: Mint cross-repo token
id: app-token
if: steps.run.outputs.count != '0' && vars.ISSUE_BOT_APP_ID != ''
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.ISSUE_BOT_APP_ID }}
private-key: ${{ secrets.ISSUE_BOT_PRIVATE_KEY }}
owner: ${{ vars.DOCS_REPO_OWNER || 'jaseci-labs' }}
repositories: ${{ vars.DOCS_REPO_NAME || 'jaseci' }}
- name: Comment live URL on docs issues
if: steps.run.outputs.count != '0' && vars.ISSUE_BOT_APP_ID != ''
uses: actions/github-script@v7
env:
DOCS_OWNER: ${{ vars.DOCS_REPO_OWNER || 'jaseci-labs' }}
DOCS_NAME: ${{ vars.DOCS_REPO_NAME || 'jaseci' }}
PUBLISHED: ${{ steps.closed.outputs.published }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const owner = process.env.DOCS_OWNER, repo = process.env.DOCS_NAME;
const published = JSON.parse(process.env.PUBLISHED || '[]');
const open = await github.paginate(github.rest.issues.listForRepo, {
owner, repo, state: 'open', labels: 'blog-docs-sync', per_page: 100,
});
for (const pub of published) {
if (!pub.url) continue;
const found = open.find(i => (i.body || '').includes(`slug=${pub.slug} `));
if (!found) continue;
await github.rest.issues.createComment({
owner, repo, issue_number: found.number,
body: `The post is now live: ${pub.url}\n\nThis is the final, dated URL — please point any \`coming-soon\` links here.`,
});
}
- name: Job summary
if: always()
run: |
{
echo "### Auto-publish run"
echo
echo '```json'
cat result.json 2>/dev/null || echo "{}"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"