diff --git a/.github/workflows/create-release-branch.yml b/.github/workflows/create-release-branch.yml index 75fb5ab8d16..38af4c5f5d0 100644 --- a/.github/workflows/create-release-branch.yml +++ b/.github/workflows/create-release-branch.yml @@ -133,265 +133,8 @@ jobs: echo "- **Manual step required:** add the branch-protection rule in **Settings → Branches**." } >> "$GITHUB_STEP_SUMMARY" - # --------------------------------------------------------------------------- - # Q1 of the docs version transition: archive the OUTGOING stable version onto - # the freshly-cut branch. Runs right after the cut (same run, already knows the - # incoming branch name). Opens a review-ready PR labelled `docs`; a human - # reviews and merges it. This does NOT flip the live docs deploy — that's Q2, - # still the manual retire+publish PR pair (see #22062 / #22063). See - # docs-version-bump.yml for the manual escape hatch. - # - # The PR is opened with the default GITHUB_TOKEN and left for manual approval. - # Note: a GITHUB_TOKEN-opened PR does not itself trigger pull_request-based CI - # (Actions recursion guard). The archive is already build-verified in this job - # before the PR opens; a reviewer merges after inspecting the diff. If a - # release branch's protection ever requires status checks that must run on the - # PR, either re-trigger them (close/reopen) or open via the release App token. - # - # Correctness: the snapshot is generated from the OUTGOING release branch's - # own docs (release/N.), never from the just-cut branch — which was cut - # from main and already holds the NEXT version's content. And it ports the - # outgoing branch's FULL versioned state (versions.json + all versioned_docs + - # all versioned_sidebars) plus the fresh snapshot, so no older version silently - # drops off the site at the next cut. - archive-previous-docs-version: - needs: [create-release-branch] - # Real cut → open a PR. Dry-run → validate the port + build, but open nothing. - if: >- - needs.create-release-branch.outputs.outcome == 'success' || - needs.create-release-branch.outputs.outcome == 'dry-run' - runs-on: ubuntu-latest - timeout-minutes: 25 - # contents:write to push the PR branch; pull-requests:write to open it. - permissions: - contents: write - pull-requests: write - outputs: - outcome: ${{ steps.pr.outputs.outcome || steps.dryrun.outputs.outcome || steps.plan.outputs.outcome }} - pr_url: ${{ steps.pr.outputs.pr_url }} - detail: ${{ steps.pr.outputs.detail || steps.dryrun.outputs.detail || steps.plan.outputs.detail }} - steps: - - name: Determine outgoing series to archive - id: plan - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - INCOMING_BRANCH: ${{ inputs.release_branch }} - run: | - set -euo pipefail - # Defensive re-validate (job 1 already checked) and capture MAJOR.MINOR. - if [[ ! "$INCOMING_BRANCH" =~ ^release/([0-9]+)\.([0-9]+)$ ]]; then - echo "::error::incoming '$INCOMING_BRANCH' is not release/N.N"; exit 1 - fi - in_major="${BASH_REMATCH[1]}"; in_minor="${BASH_REMATCH[2]}" - - # Enumerate existing release/N.N branches (fail-closed: a gh API error - # aborts the job rather than guessing). Exact regex excludes patch-style - # names like release/3.0.10; numeric per-component sort so 3.10 > 3.9. - all=$(gh api "repos/$REPO/branches" --paginate --jq '.[].name') - series=$(printf '%s\n' "$all" \ - | grep -E '^release/[0-9]+\.[0-9]+$' \ - | sed 's#^release/##' \ - | sort -t. -k1,1n -k2,2n || true) - - # Highest series strictly below the incoming one (list is ascending, so - # the last match wins). Robust across major bumps (e.g. 3.9 → 4.0). - outgoing="" - while IFS= read -r s; do - [ -n "$s" ] || continue - maj="${s%%.*}"; min="${s##*.}" - if [ "$maj" -lt "$in_major" ] || { [ "$maj" -eq "$in_major" ] && [ "$min" -lt "$in_minor" ]; }; then - outgoing="$s" - fi - done <<< "$series" - - if [ -z "$outgoing" ]; then - echo "No release/* series below $INCOMING_BRANCH — nothing to archive (first release branch)." - echo "outcome=skipped" >> "$GITHUB_OUTPUT" - echo "detail=no previous series to archive" >> "$GITHUB_OUTPUT" - exit 0 - fi - { - echo "outgoing_branch=release/$outgoing" - echo "archive_label=v$outgoing" - echo "outcome=proceed" - } >> "$GITHUB_OUTPUT" - echo "Will archive v$outgoing (docs from release/$outgoing) onto $INCOMING_BRANCH." - - - name: Checkout outgoing branch (authoritative docs for the archived version) - if: steps.plan.outputs.outcome == 'proceed' - uses: actions/checkout@v7 - with: - ref: ${{ steps.plan.outputs.outgoing_branch }} - path: outgoing - persist-credentials: false - - - uses: actions/setup-node@v7 - if: steps.plan.outputs.outcome == 'proceed' - with: - node-version: '20' - - - uses: actions/setup-python@v7 - if: steps.plan.outputs.outcome == 'proceed' - with: - python-version: '3.11' - - # Docusaurus config runs branch-controlled JS (config + plugins) and needs - # an authenticated call for its release lookup. Mint a read-only token for - # it rather than handing it this job's contents:write GITHUB_TOKEN. - - name: Generate read-only token for docs build - id: docs_token - if: steps.plan.outputs.outcome == 'proceed' - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 ## v3.2.0 - with: - client-id: ${{ vars.RELEASE_BOT_APP_ID }} - private-key: ${{ secrets.RELEASE_BOT_APP_KEY }} - repositories: "erigon" - permission-contents: read - - - name: Snapshot outgoing docs and capture full versioned state - if: steps.plan.outputs.outcome == 'proceed' - working-directory: outgoing/docs/site - env: - ARCHIVE_LABEL: ${{ steps.plan.outputs.archive_label }} - # docs:version loads docusaurus.config.ts, whose release lookup fails - # closed; without a token it runs on the anonymous rate limit. Use the - # read-only token so branch-controlled config never sees write scope. - GITHUB_TOKEN: ${{ steps.docs_token.outputs.token }} - run: | - set -euo pipefail - npm ci - # docusaurus keys the snapshot off the argument, not current.label, so - # this freezes the outgoing branch's live docs as $ARCHIVE_LABEL and - # prepends it to that branch's versions.json (newest-first). - npx docusaurus docs:version "$ARCHIVE_LABEL" - # Stash the COMPLETE versioned state outside the workspace so the next - # checkout's clean step can't wipe it. This state already carries the - # prior archived versions + the freshly-frozen one. - dest="$RUNNER_TEMP/versioned-state" - rm -rf "$dest"; mkdir -p "$dest" - cp versions.json "$dest/versions.json" - cp -R versioned_docs "$dest/versioned_docs" - cp -R versioned_sidebars "$dest/versioned_sidebars" - echo "Captured versioned state:"; cat "$dest/versions.json" - - - name: Checkout target branch - if: steps.plan.outputs.outcome == 'proceed' - uses: actions/checkout@v7 - with: - # Real cut → the new branch. Dry-run → the base commit (identical docs - # content to what the new branch would hold), so the port + build are - # exercised without the branch existing. - ref: ${{ inputs.dry_run && needs.create-release-branch.outputs.base_sha || inputs.release_branch }} - path: incoming - persist-credentials: false - - - name: Port versioned state, enforce cap, regenerate llms, build-verify - if: steps.plan.outputs.outcome == 'proceed' - working-directory: incoming/docs/site - env: - # Same reasoning as the snapshot step: `npm run build` loads the same - # branch-controlled config, so it gets the read-only token too. - GITHUB_TOKEN: ${{ steps.docs_token.outputs.token }} - run: | - set -euo pipefail - src="$RUNNER_TEMP/versioned-state" - # Full-state port: replace the target's archived state wholesale. - rm -rf versioned_docs versioned_sidebars versions.json - cp "$src/versions.json" versions.json - cp -R "$src/versioned_docs" versioned_docs - cp -R "$src/versioned_sidebars" versioned_sidebars - - # 5-archived-version cap (versions.json is newest-first; prune the rest). - count=$(jq 'length' versions.json) - if [ "$count" -gt 5 ]; then - for v in $(jq -r '.[5:][]' versions.json); do - echo "Pruning archived version $v" - rm -rf "versioned_docs/version-$v" - rm -f "versioned_sidebars/version-$v-sidebars.json" - done - jq '.[:5]' versions.json > versions.json.tmp && mv versions.json.tmp versions.json - fi - - npm ci - python3 scripts/generate-llms.py - # onBrokenLinks/onBrokenAnchors are 'throw' → a bad ported link fails - # here, before any PR is opened. - npm run build - - - name: Open archive PR - id: pr - if: steps.plan.outputs.outcome == 'proceed' && !inputs.dry_run - working-directory: incoming - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - INCOMING_BRANCH: ${{ inputs.release_branch }} - OUTGOING_BRANCH: ${{ steps.plan.outputs.outgoing_branch }} - ARCHIVE_LABEL: ${{ steps.plan.outputs.archive_label }} - RUN_ID: ${{ github.run_id }} - run: | - set -euo pipefail - if git diff --quiet && git diff --cached --quiet; then - echo "No changes — $ARCHIVE_LABEL is already archived on $INCOMING_BRANCH. Skipping PR." - echo "outcome=skipped" >> "$GITHUB_OUTPUT" - echo "detail=$ARCHIVE_LABEL already archived on $INCOMING_BRANCH" >> "$GITHUB_OUTPUT" - exit 0 - fi - # Suffix the run id so re-runs don't collide with an existing remote branch. - pr_branch="docs/auto-archive-$ARCHIVE_LABEL-$RUN_ID" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git checkout -b "$pr_branch" - git add -A - git commit -m "docs(site): archive $ARCHIVE_LABEL for $INCOMING_BRANCH" - # persist-credentials:false on checkout → authenticate this push explicitly. - git push "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" "HEAD:refs/heads/$pr_branch" - url=$(gh pr create \ - --base "$INCOMING_BRANCH" \ - --head "$pr_branch" \ - --label "docs" \ - --title "docs(site): archive $ARCHIVE_LABEL on $INCOMING_BRANCH" \ - --body "Automated docs archive opened when \`$INCOMING_BRANCH\` was cut (\`create-release-branch.yml\`). Labelled \`docs\` for manual review and merge. - - - Snapshotted **$ARCHIVE_LABEL** from its authoritative branch \`$OUTGOING_BRANCH\` (not from \`$INCOMING_BRANCH\`, which already holds the next version's docs). - - Ported the full versioned state (versions.json + all versioned_docs/ + versioned_sidebars/) and enforced the 5-archived-version cap. - - Regenerated llms.txt artifacts; \`npm run build\` passed in CI before this PR was opened. - - This does **not** flip the live docs deploy. Making $INCOMING_BRANCH the default on docs.erigon.tech is the separate manual step (retire the old \`docs-deploy.yml\` + publish, per #22062 / #22063).") - { - echo "pr_url=$url" - echo "outcome=pr_opened" - echo "detail=opened $url" - } >> "$GITHUB_OUTPUT" - { - echo "### Docs archive PR opened" - echo "- $ARCHIVE_LABEL archived onto \`$INCOMING_BRANCH\`: $url" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Dry-run summary - id: dryrun - if: steps.plan.outputs.outcome == 'proceed' && inputs.dry_run - env: - ARCHIVE_LABEL: ${{ steps.plan.outputs.archive_label }} - INCOMING_BRANCH: ${{ inputs.release_branch }} - OUTGOING_BRANCH: ${{ steps.plan.outputs.outgoing_branch }} - run: | - # Emit a terminal outcome so the job output isn't the non-terminal - # 'proceed' from the plan step, and notify can report the dry-run. - { - echo "outcome=dry-run" - echo "detail=dry-run: validated $ARCHIVE_LABEL (from $OUTGOING_BRANCH) → $INCOMING_BRANCH, no PR opened" - } >> "$GITHUB_OUTPUT" - { - echo "### Dry-run: docs archive validated" - echo "" - echo "- Would archive **$ARCHIVE_LABEL** (from \`$OUTGOING_BRANCH\`) onto **$INCOMING_BRANCH**." - echo "- Full versioned-state port + \`npm run build\` succeeded. No PR opened (dry-run)." - } >> "$GITHUB_STEP_SUMMARY" - notify: - needs: [create-release-branch, archive-previous-docs-version] + needs: [create-release-branch] if: always() runs-on: ubuntu-latest steps: @@ -406,9 +149,6 @@ jobs: FROM_REF: ${{ inputs.from_ref }} ACTOR: ${{ github.actor }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - ARCHIVE_OUTCOME: ${{ needs.archive-previous-docs-version.outputs.outcome }} - ARCHIVE_PR_URL: ${{ needs.archive-previous-docs-version.outputs.pr_url }} - ARCHIVE_DETAIL: ${{ needs.archive-previous-docs-version.outputs.detail }} run: | set -euo pipefail if [ -z "${DISCORD_WEBHOOK:-}" ]; then @@ -419,15 +159,6 @@ jobs: now=$(date -u '+%Y-%m-%d %H:%M:%S UTC') ts=$(date -u '+%Y-%m-%dT%H:%M:%SZ') - # Summarize the docs-archive job (Q1) for the audit embed. - if [ -n "${ARCHIVE_PR_URL:-}" ]; then - archive_field="[Archive PR opened]($ARCHIVE_PR_URL)" - elif [ "${ARCHIVE_OUTCOME:-}" = "skipped" ] || [ "${ARCHIVE_OUTCOME:-}" = "dry-run" ]; then - archive_field=":information_source: ${ARCHIVE_DETAIL:-${ARCHIVE_OUTCOME}}" - else - archive_field="n/a" - fi - # A cancelled/skipped upstream job (e.g. environment approval denied or # timed out) reports as failure to the team. mention="@here" @@ -458,7 +189,6 @@ jobs: --arg when "$now" \ --arg ts "$ts" \ --arg url "$RUN_URL" \ - --arg archive "$archive_field" \ '{ content: $mention, allowed_mentions: {parse: ["everyone"]}, @@ -474,7 +204,6 @@ jobs: {name: "Commit", value: ("`" + $sha + "`"), inline: false}, {name: "Triggered by", value: ("[" + $actor + "](https://github.com/" + $actor + ")"), inline: true}, {name: "When (UTC)", value: $when, inline: true}, - {name: "Docs archive", value: $archive, inline: false}, {name: "Run", value: ("[Open workflow run](" + $url + ")"), inline: false} ], footer: {text: "erigon • create-release-branch"}