feat(scaffold): fully automate release blog post publishing (#810)

* feat(scaffold): fully automate release blog post publishing

Eliminates the manual PR-review step from the release blog scaffold pipeline.
The workflow now commits the generated post directly to sencho-website main.

scaffold-release-post.mjs changes:
- Remove renderPrBody and all body_file handling
- Update findLastAnchor to accept any post with a version: field, not just
  category: release posts. This allows narrative retrospectives to anchor
  the 5-release window.
- Add buildHeadline: generates a post title from the top two added items and
  the total item count, e.g. "Sencho v0.69: Custom OIDC provider, Trivy
  scanning, and 12 more"
- Add buildDescription: auto-generates a ~160-char SEO description from the
  version range and top three added items
- Add calcReadingTime: estimates reading time from changelog word count
- Add coveredVersionsProse: formats "v0.65.0 through v0.69.0"
- Add updateMeta: inserts the new post into src/data/blog/meta.ts for
  Worker-side SEO meta injection
- Remove the tmpdir/os import (no longer needed)

release-blog-template.tsx.tmpl changes:
- Replace all TODO placeholders with template variables (__TITLE__,
  __DESCRIPTION__, __READING_TIME__, __COVERED_VERSIONS_PROSE__)
- Remove the screenshot placeholder section (auto-generated posts have no
  screenshot; the intro links to docs.sencho.io instead)

release-blog-scaffold.yml changes:
- Remove permission-pull-requests: write from app token (no PR created)
- Replace "Commit and open draft PR" step with a simple git commit + push to
  sencho-website main. No duplicate-check step needed.
- Include src/data/blog/meta.ts in the git add so the Worker SEO mapping
  stays in sync automatically.

* fix(ci): use escapeTsxString in updateMeta to handle backslashes in blog post title and description

CodeQL flagged two high-severity alerts: title and description written
into meta.ts via updateMeta were only single-quote-escaped, leaving
raw backslashes unescaped. A value containing a backslash followed by
a special character would produce a malformed escape sequence in the
generated TypeScript source. The escapeTsxString helper already handles
both cases; use it consistently instead of an open-coded replace.
This commit is contained in:
Anso
2026-04-27 15:38:01 -04:00
committed by GitHub
parent 5d376590f8
commit b5acfd8f58
3 changed files with 128 additions and 83 deletions
+7 -32
View File
@@ -1,10 +1,9 @@
name: Release Blog Scaffold
# Fires on every v* tag push. Computes whether this tag completes the next
# every-5th-release window (source of truth = the latest release post's
# `version` field in the sencho-website repo). If yes, opens a DRAFT PR in
# sencho-website containing a pre-filled blog post scaffold; a human writes
# the narrative, adds the screenshot, and marks the PR ready.
# every-5th-release window (source of truth = the latest post with a version:
# field in the sencho-website repo). If yes, generates a complete, TODO-free
# blog post and commits it directly to sencho-website main. No PR is opened.
on:
push:
tags:
@@ -37,7 +36,6 @@ jobs:
Sencho
sencho-website
permission-contents: write
permission-pull-requests: write
- name: Resolve target tag
id: target
@@ -79,42 +77,19 @@ jobs:
--changelog CHANGELOG.md \
--website website-repo
- name: Commit and open draft PR in sencho-website
- name: Commit and push release post to sencho-website
if: steps.scaffold.outputs.scaffold == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
TAG: ${{ steps.target.outputs.tag }}
BRANCH: ${{ steps.scaffold.outputs.branch }}
POST_PATH: ${{ steps.scaffold.outputs.post_path }}
BODY_FILE: ${{ steps.scaffold.outputs.body_file }}
OWNER: ${{ github.repository_owner }}
run: |
set -euo pipefail
cd website-repo
# Short-circuit if a scaffold PR for this branch already exists (e.g.,
# a manual workflow_dispatch retry against the same tag).
existing=$(gh pr list \
--repo "${OWNER}/sencho-website" \
--head "${BRANCH}" \
--state open \
--json number --jq '.[0].number // empty')
if [ -n "${existing}" ]; then
echo "PR already exists for ${BRANCH} (#${existing}); nothing to do."
exit 0
fi
git config user.name "sencho-release-bot[bot]"
git config user.email "sencho-release-bot[bot]@users.noreply.github.com"
git checkout -B "${BRANCH}"
git add "${POST_PATH}" src/data/blog/index.ts
git commit -m "chore: scaffold release post for ${TAG}"
git push -u origin "${BRANCH}" --force-with-lease
gh pr create \
--repo "${OWNER}/sencho-website" \
--base main \
--head "${BRANCH}" \
--title "chore: scaffold release post for ${TAG}" \
--body-file "${BODY_FILE}" \
--draft
git add "${POST_PATH}" src/data/blog/index.ts src/data/blog/meta.ts
git commit -m "chore(blog): auto-publish release post for ${TAG}"
git push origin main
- name: Report skip
if: steps.scaffold.outputs.scaffold != 'true'