diff --git a/.github/workflows/incubate-website-deps.yml b/.github/workflows/incubate-website-deps.yml new file mode 100644 index 0000000000..5157904ede --- /dev/null +++ b/.github/workflows/incubate-website-deps.yml @@ -0,0 +1,89 @@ +name: Incubate website dependency changes + +on: + pull_request: + branches: [main] + paths: + - 'website/package.json' + - 'website/package-lock.json' + schedule: + - cron: '0 7 * * *' # 7am UTC nightly + workflow_dispatch: + +permissions: + contents: read + actions: write # needed for `gh run rerun` + +concurrency: + group: incubate-website-deps-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: false + +jobs: + check: + runs-on: ubuntu-22.04 + env: + INCUBATION_HOURS: 72 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout + if: github.event_name == 'pull_request' + uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 + with: + fetch-depth: 0 # full history needed to compute commit timestamp + + - name: Verify 72-hour incubation + if: github.event_name == 'pull_request' + shell: bash + run: | + set -euo pipefail + git fetch --no-tags origin "${GITHUB_BASE_REF}" + THRESHOLD_SECONDS=$(( INCUBATION_HOURS * 3600 )) + # Confirm this PR branch actually touches the dep files. + HAS_DEP_CHANGE=$(git log -1 --format=%H \ + "origin/${GITHUB_BASE_REF}..HEAD" \ + -- website/package.json website/package-lock.json || true) + if [ -z "${HAS_DEP_CHANGE}" ]; then + echo "No website dependency changes on this branch relative to ${GITHUB_BASE_REF}." + exit 0 + fi + # Use the wall-clock creation time of the *earliest* run for this workflow + # on this PR — unaffected by force-pushed commit timestamps. + EARLIEST_RUN_TS=$(gh run list \ + --repo "${GITHUB_REPOSITORY}" \ + --workflow "${{ github.workflow }}" \ + --event pull_request \ + --branch "${{ github.head_ref }}" \ + --limit 100 \ + --json databaseId,createdAt \ + --jq 'sort_by(.createdAt) | .[0].createdAt' ) + EARLIEST_RUN_EPOCH=$(date -d "${EARLIEST_RUN_TS}" +%s) + AGE=$(( $(date +%s) - EARLIEST_RUN_EPOCH )) + AGE_HOURS=$(( AGE / 3600 )) + if [ "${AGE}" -lt "${THRESHOLD_SECONDS}" ]; then + REMAINING=$(( (THRESHOLD_SECONDS - AGE + 3599) / 3600 )) + echo "::error::Website dependency files were changed ${AGE_HOURS}h ago. Incubation period is ${INCUBATION_HOURS}h. Wait ~${REMAINING}h, then re-run this check." + exit 1 + fi + echo "Incubation complete: last website dep change was ${AGE_HOURS}h ago." + + - name: Re-run previously-failed PR checks (nightly) + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + shell: bash + run: | + set -euo pipefail + # Find recent failed runs of THIS workflow on pull_request events and re-trigger them. + # As wall-clock time advances, the git-log age in each rerun grows past the 72h threshold + # and the run passes — flipping the required check green without requiring a force-push. + gh run list \ + --repo "${GITHUB_REPOSITORY}" \ + --workflow "${{ github.workflow }}" \ + --event pull_request \ + --status failure \ + --created ">$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%SZ')" \ + --limit 100 \ + --json databaseId \ + --jq '.[].databaseId' \ + | while read -r run_id; do + echo "Re-running failed run ${run_id}" + gh run rerun "${run_id}" --repo "${GITHUB_REPOSITORY}" || true + done diff --git a/handbook/engineering/website.md b/handbook/engineering/website.md index a6910375c4..df79966d80 100644 --- a/handbook/engineering/website.md +++ b/handbook/engineering/website.md @@ -134,6 +134,11 @@ Every week, the website maintainer looks for any new [code scanning alerts](http - If the alert is for a dependency that runs in production, the maintainer will upgrade it to a version that is not affected by the vulnerability. - If the alert is for a devDependency or a dependency of a devDependency, the maintainer will dismiss the alert as a false-positive, because it does not affect the production environment. +## Incubate website dependency changes + +Pull requests that modify `website/package.json` or `website/package-lock.json` must wait 72 hours after the most recent commit to either file before they can merge to `main`. This incubation period gives the maintainer time to spot regressions or supply-chain concerns introduced by a dependency bump before it reaches the website's production environment. + +The `Incubate website dependency changes` GitHub Actions workflow enforces this as a required status check. The check runs on every PR that touches those files and re-evaluates open PRs nightly, once 72 hours have elapsed the check turns green automatically. Pushing a new commit to either file resets the clock.