diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index e755c29c73..5b15a78aa2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -48,9 +48,7 @@ on: - "scripts/**" permissions: - contents: write - pull-requests: write - issues: write + contents: read concurrency: group: documentation-${{ github.ref }} @@ -76,6 +74,7 @@ jobs: uses: actions/checkout@v7 with: fetch-depth: 0 + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@v7 @@ -192,12 +191,16 @@ jobs: if: | github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/develop') + permissions: + contents: write + pull-requests: write steps: - name: Checkout repository uses: actions/checkout@v7 with: fetch-depth: 0 + persist-credentials: true - name: Set up Node.js uses: actions/setup-node@v7 @@ -264,6 +267,7 @@ jobs: uses: actions/checkout@v7 with: fetch-depth: 0 + persist-credentials: true - name: Set up Node.js uses: actions/setup-node@v7 @@ -278,23 +282,30 @@ jobs: run: mkdir -p .github/reports/mermaid-audit - name: Run Mermaid diagram fixes + id: mermaid if: ${{ env.AUDIT_SCOPE == 'all' || env.AUDIT_SCOPE == 'mermaid' }} run: node scripts/fix-mermaid-diagrams.js continue-on-error: true - name: Validate Mermaid colour contrast + id: contrast if: ${{ env.AUDIT_SCOPE == 'all' || env.AUDIT_SCOPE == 'mermaid' }} run: npm run validate:mermaid-contrast continue-on-error: true - name: Run staleness updates + id: staleness if: ${{ env.AUDIT_SCOPE == 'all' || env.AUDIT_SCOPE == 'staleness' }} run: node scripts/fix-staleness-dates.js continue-on-error: true - name: Generate maintenance report - if: success() + if: always() run: | + MERMAID_STATUS="${{ steps.mermaid.outcome || 'skipped' }}" + CONTRAST_STATUS="${{ steps.contrast.outcome || 'skipped' }}" + STALENESS_STATUS="${{ steps.staleness.outcome || 'skipped' }}" + cat > .github/reports/mermaid-audit/maintenance-report.md <<'EOF' # Documentation Maintenance Report @@ -302,6 +313,14 @@ jobs: **Scope**: ${{ env.AUDIT_SCOPE }} **Dry Run**: ${{ env.DRY_RUN }} + ## Processing Status + + | Task | Status | + |------|--------| + | Mermaid Diagrams | ${MERMAID_STATUS} | + | Contrast Validation | ${CONTRAST_STATUS} | + | Staleness Updates | ${STALENESS_STATUS} | + ## Changes Summary - Mermaid diagrams: Updated accessibility attributes and colour-contrast-safe palette values diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b92dcdd7b..f885b5c75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,6 +114,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **`documentation.yml` workflow security hardening and correctness fixes** — Scoped workflow permissions to individual jobs: global demoted to `contents: read`; `regenerate` and `maintain` jobs receive only the write scopes they need. Added `persist-credentials: false` for the read-only audit checkout and `persist-credentials: true` for write jobs. Fixed maintenance report accuracy: per-step outcomes for mermaid, contrast, and staleness tasks are now captured and displayed instead of always showing success. Fixed README resolver over-inclusion: root README is now only added to the regeneration list when subdirectory files change. ([PR #1387](https://github.com/lightspeedwp/.github/pull/1387), [#1386](https://github.com/lightspeedwp/.github/issues/1386), [Epic #1227](https://github.com/lightspeedwp/.github/issues/1227)) + - **Changelog automation: Section headers destroyed on merge** — The merge-entries workflow was discarding section headers during deduplication, corrupting changelog structure on every PR merge. Fixed deduplication logic to preserve headers and limited scope to [Unreleased] section only. ([PR #1276](https://github.com/lightspeedwp/.github/pull/1276), [#1275](https://github.com/lightspeedwp/.github/issues/1275)) - **Meta-agent workflow: Missing npm dependency and direct push to protected branch** — Added missing npm ci step and routed commits through auto-merged PR instead of direct push to respect branch protection. ([PR #1073](https://github.com/lightspeedwp/.github/pull/1073), [#1072](https://github.com/lightspeedwp/.github/issues/1072)) diff --git a/scripts/workflows/resolve-readme-files.cjs b/scripts/workflows/resolve-readme-files.cjs index 9a5673d934..f16116e2d8 100644 --- a/scripts/workflows/resolve-readme-files.cjs +++ b/scripts/workflows/resolve-readme-files.cjs @@ -1,7 +1,13 @@ #!/usr/bin/env node /** - * Resolve impacted README files from git diff - * Outputs space-separated list of README files to GITHUB_OUTPUT + * Resolve impacted README files from git diff. + * + * Output format: Comma-separated list of README file paths written to GITHUB_OUTPUT. + * This matches the format expected by GitHub Actions workflow variable interpolation + * when passing values to shell commands via ${{ steps.readmes.outputs.files }}. + * + * Example output: + * files=README.md,.github/projects/README.md,docs/README.md */ const { execFileSync } = require("child_process"); @@ -29,6 +35,7 @@ function getChangedFiles(baseSha, headSha) { function resolveReadmeFiles(changedFiles) { const readmes = new Set(); + let hasSubdirChanges = false; changedFiles.forEach((file) => { const dir = path.dirname(file); @@ -39,12 +46,17 @@ function resolveReadmeFiles(changedFiles) { readmes.add(readmeInDir); } - // Check for root README.md if not in root already - if (dir !== "." && fs.existsSync("README.md")) { - readmes.add("README.md"); + // Track if changes are in subdirectories (not root) + if (dir !== ".") { + hasSubdirChanges = true; } }); + // Only add root README if files in subdirectories changed AND root README exists + if (hasSubdirChanges && fs.existsSync("README.md")) { + readmes.add("README.md"); + } + return Array.from(readmes).sort(); } diff --git a/scripts/workflows/resolve-readme-files.sh b/scripts/workflows/resolve-readme-files.sh index 8bfd4d4d4e..8e09949cc2 100644 --- a/scripts/workflows/resolve-readme-files.sh +++ b/scripts/workflows/resolve-readme-files.sh @@ -1,6 +1,10 @@ #!/bin/bash # Resolve impacted README files from git diff # Usage: resolve-readme-files.sh +# +# Output format: Comma-separated list of README file paths. +# This script is provided for reference; prefer resolve-readme-files.js +# for security (execFileSync) and consistency. set -euo pipefail @@ -14,22 +18,32 @@ if [ -z "$CHANGED" ]; then fi TMP=$(mktemp) +trap "rm -f '$TMP'" EXIT + +HAS_SUBDIR_CHANGES=0 + while IFS= read -r file; do [ -z "$file" ] && continue dir=$(dirname "$file") + if [ -f "$dir/README.md" ]; then echo "$dir/README.md" >> "$TMP" fi - if [ "$dir" != "." ] && [ -f "README.md" ]; then - echo "README.md" >> "$TMP" + + if [ "$dir" != "." ]; then + HAS_SUBDIR_CHANGES=1 fi done <> "$TMP" +fi + if [ -f "$TMP" ]; then - sort -u "$TMP" | tr '\n' ',' | sed 's/,$//' + sort -u "$TMP" | tr '\n' ',' | sed 's/,$//' else echo "" fi -rm -f "$TMP"