From 2f612b5f522812b91f7fae4b5ab22da553508c5b Mon Sep 17 00:00:00 2001 From: Darius Cepulis Date: Thu, 19 Mar 2026 10:44:47 -0500 Subject: [PATCH] ci(cd): add changelog actions pipeline (#1032) Co-authored-by: Claude Opus 4.6 (1M context) --- .github/workflows/changelog-prose.yml | 109 ++++++++++++++++++++++++++ .github/workflows/release-pr.yml | 64 ++++++++++++++- site/src/content/changelog/.gitkeep | 0 3 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/changelog-prose.yml create mode 100644 site/src/content/changelog/.gitkeep diff --git a/.github/workflows/changelog-prose.yml b/.github/workflows/changelog-prose.yml new file mode 100644 index 00000000..8534a614 --- /dev/null +++ b/.github/workflows/changelog-prose.yml @@ -0,0 +1,109 @@ +name: Changelog Prose + +on: + release: + types: [published] + +permissions: + actions: read + contents: write + pull-requests: write + issues: read + +concurrency: + group: changelog-prose-${{ github.event.release.tag_name }} + cancel-in-progress: true + +jobs: + prose: + if: startsWith(github.event.release.tag_name, '@videojs/core@') + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + fetch-depth: 1 + + - name: Parse version + id: version + run: | + TAG="${{ github.event.release.tag_name }}" + VERSION="${TAG#@videojs/core@}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Guard — check raw changelog exists + id: guard + run: | + FILE="site/src/content/changelog/${{ steps.version.outputs.version }}.md" + if [ ! -f "$FILE" ]; then + echo "::warning::Raw changelog file $FILE not found on main — skipping prose generation" + echo "skip=true" >> "$GITHUB_OUTPUT" + fi + + - name: Generate changelog prose + if: steps.guard.outputs.skip != 'true' + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + claude_args: | + --model sonnet + --max-turns 20 + --allowedTools "Bash(gh:*)" "Bash(git:*)" "Read" "Edit" "Write" "Glob" "Grep" + prompt: | + You are the changelog prose writer for Video.js 10. + + Version: ${{ steps.version.outputs.version }} + Release URL: ${{ github.event.release.html_url }} + + Your task is to rewrite a raw changelog file into polished narrative prose, then open a PR with the result. + + Steps: + + 1. **Load context:** + - Read `.claude/skills/docs/references/writing-style.md` for tone and style rules. + - Read the raw changelog at `site/src/content/changelog/${{ steps.version.outputs.version }}.md`. + - Read other existing `.md` files in `site/src/content/changelog/` (if any) to match their tone and format. + + 2. **Gather PR context:** + - Extract all PR numbers (e.g., `#906`) from the raw changelog body. + - Build a single batched GraphQL query using `gh api graphql` with aliases to fetch all PRs at once: + ``` + { + repository(owner: "videojs", name: "v10") { + pr906: pullRequest(number: 906) { + title + body + closingIssuesReferences(first: 5) { + nodes { number title body parent { number title } } + } + } + ... + } + } + ``` + - This gives you PR title, body, linked issues (with body), and parent epics in one API call. + - Some PR numbers may resolve to null — skip those gracefully. + + 3. **Rewrite the changelog body:** + - Replace the raw bullet list with narrative prose (100–300 words). + - Lead with the 1–3 most impactful changes, informed by epic/issue context. + - Group related PRs into cohesive stories rather than listing them individually. + - Call out breaking changes explicitly with migration guidance if applicable. + - Summarize smaller fixes briefly. + - Skip internal-only changes (CI, tooling, changelog maintenance). + - Follow `writing-style.md` strictly. + - Preserve PR links as inline markdown links in the prose (e.g., [#906](url)). + - Do not fabricate — stick to what the PRs actually say. + + 4. **Write the `description` field:** + - One sentence summarizing the release for SEO/RSS. + - Update the `description: ""` field in the file's YAML frontmatter. + + 5. **Create a PR:** + - Create branch: `docs/changelog-prose-${{ steps.version.outputs.version }}` + - Stage and commit the changed file: `docs(changelog): add prose for ${{ steps.version.outputs.version }}` + - Push the branch and open a PR targeting `main`. + - Include a link to the release in the PR body. + - Keep the PR description concise. diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index d6572ac9..ea563684 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -25,10 +25,72 @@ jobs: OUTPUT: CHANGELOG.md GITHUB_REPO: ${{ github.repository }} + - name: Extract changelog for site + run: | + # Find first versioned release heading (skip [Unreleased]) + HEADING=$(grep -n '^## \[@videojs/core@' CHANGELOG.md | head -1) || true + if [ -z "$HEADING" ]; then + echo "::warning::No versioned release heading found in CHANGELOG.md — skipping site extraction" + exit 0 + fi + + LINE_NUM=$(echo "$HEADING" | cut -d: -f1) + HEADING_TEXT=$(echo "$HEADING" | cut -d: -f2-) + + # Parse version and date from heading: ## [@videojs/core@VERSION] - DATE + VERSION=$(echo "$HEADING_TEXT" | sed -n 's/.*@videojs\/core@\([^]]*\)\].*/\1/p') + DATE=$(echo "$HEADING_TEXT" | sed -n 's/.*- \(.*\)/\1/p') + + if [ -z "$VERSION" ] || [ -z "$DATE" ]; then + echo "::warning::Could not parse version or date from heading — skipping site extraction" + exit 0 + fi + + # Find the next ## [ heading to delimit the body + NEXT_LINE=$(tail -n +"$((LINE_NUM + 1))" CHANGELOG.md | grep -n '^## \[' | head -1 | cut -d: -f1) || true + if [ -n "$NEXT_LINE" ]; then + END_LINE=$((LINE_NUM + NEXT_LINE - 1)) + BODY=$(sed -n "$((LINE_NUM + 1)),$((END_LINE))p" CHANGELOG.md | sed '/^$/N;/^\n$/d') + else + BODY=$(tail -n +"$((LINE_NUM + 1))" CHANGELOG.md) + fi + + # Determine prerelease and breaking + PRERELEASE=false + if echo "$VERSION" | grep -q '-'; then + PRERELEASE=true + fi + + BREAKING=false + if echo "$BODY" | grep -qiF '**breaking**'; then + BREAKING=true + fi + + # Extract compare URL from CHANGELOG footer + COMPARE_URL=$(grep -Fm1 "[@videojs/core@${VERSION}]: " CHANGELOG.md | sed 's/.*]: //' || true) + + # Write the site changelog file + OUTFILE="site/src/content/changelog/${VERSION}.md" + mkdir -p "$(dirname "$OUTFILE")" + cat > "$OUTFILE" <