test(spec): 别名一致性闸门收编 44 个 strictUnknownKeyError 直调点 —— probe 撞车维度首测 (#5483) #9528
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Automation | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled, unlabeled] | |
| jobs: | |
| pr-size: | |
| name: Check PR Size | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Add size label | |
| uses: codelytv/pr-size-labeler@v1.10.4 | |
| with: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| xs_label: 'size/xs' | |
| xs_max_size: '10' | |
| s_label: 'size/s' | |
| s_max_size: '100' | |
| m_label: 'size/m' | |
| m_max_size: '500' | |
| l_label: 'size/l' | |
| l_max_size: '1000' | |
| xl_label: 'size/xl' | |
| fail_if_xl: 'false' | |
| message_if_xl: 'This PR is very large. Consider breaking it into smaller PRs for easier review.' | |
| files_to_ignore: 'pnpm-lock.yaml package-lock.json yarn.lock' | |
| auto-label: | |
| name: Auto Label | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - name: Label based on changed files | |
| uses: actions/labeler@v7.0.0 | |
| with: | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| configuration-path: .github/labeler.yml | |
| changeset-check: | |
| name: Check Changeset | |
| runs-on: ubuntu-latest | |
| # Two exemptions, both meaning "this PR declares no release of its own": | |
| # - the `skip-changeset` label — the author's explicit opt-out; | |
| # - the Changesets release PR (`changeset-release/main`, pushed by | |
| # changesets/action). That PR is the CONSUMING side: it applies pending | |
| # changesets into versions and CHANGELOGs and adds none, so the gate | |
| # below can only ever fail it. It did, on every `chore: version | |
| # packages` PR (#4422 / #4894), leaving the release blocked on a check | |
| # that was structurally unsatisfiable. | |
| # Pin the author as well as the branch name, so a hand-pushed branch of | |
| # that name cannot borrow the exemption as an escape hatch. | |
| # | |
| # The LABEL half of this expression is a fast path, NOT the authority (#5580). | |
| # `github.event.pull_request.labels` is a snapshot frozen when the event | |
| # fired, so a label applied seconds after `gh pr create` is invisible to the | |
| # `opened` run -- and `rerun_failed_jobs` replays that SAME payload | |
| # (pm-dispatch Operational notes 5), so the resulting red run can never be | |
| # re-run green. It is permanently red by construction: three PRs in one day | |
| # (#5467 -- this gate's own fix PR -- plus #5501 and #5577) each left a stale | |
| # red that a human or agent had to stop and explain away. | |
| # The authority is the live re-read in the first step below. This expression | |
| # only short-circuits the case where the payload ALREADY shows the label, so | |
| # the common path still costs no runner at all. The branch/author half needs | |
| # no such treatment: head_ref and the PR author cannot change under a rerun. | |
| # | |
| # Keeping the fast path leaves ONE stale cell, in the opposite direction: a | |
| # label REMOVED after the event fired still short-circuits this run, which is | |
| # then permissive on the strength of a snapshot. That one is self-correcting | |
| # and was left deliberately -- removing a label always fires an `unlabeled` | |
| # event of its own, and the run it starts sees no label in either place and | |
| # enforces. The direction #5580 is about has no such rescue: the `labeled` | |
| # run's green verdict does not clear the `opened` run's red one. | |
| if: >- | |
| !contains(github.event.pull_request.labels.*.name, 'skip-changeset') | |
| && !(github.head_ref == 'changeset-release/main' | |
| && github.event.pull_request.user.login == 'github-actions[bot]') | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| # The label read the frozen payload could not do. It runs BEFORE checkout | |
| # on purpose: when the label is there, every step below is skipped and the | |
| # whole job costs one API call, so converging on the live state is cheaper | |
| # than the stale red it replaces. | |
| # | |
| # The direction of the tolerance is deliberate: an unreadable label list | |
| # (API error, no PR number) resolves to `skip=false`, i.e. ENFORCE. A gate | |
| # that could not read its input has verified nothing, and handing out an | |
| # exemption on that basis is the #4690 anti-pattern -- a check that skips | |
| # silently, exits 0 and reads as "no violations". The failure is announced | |
| # as a warning and the changeset count below decides. | |
| - name: Re-read this PR's labels live (the event payload can predate them) | |
| id: labels | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| run: | | |
| if [ -z "$PR_NUMBER" ]; then | |
| echo "::warning::No PR number on this event, so the labels could not be re-read. Enforcing the changeset check." | |
| echo 'skip=false' >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # The pulls endpoint carries the PR's full label set inline, and a GET | |
| # on it is covered by this job's own pull-requests permission -- no | |
| # pagination, no wider scope than the job already declares. | |
| if ! LABELS=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq '.labels[].name'); then | |
| echo "::warning::Could not read the labels of PR #$PR_NUMBER, so this run cannot see a 'skip-changeset' applied after the event fired. Enforcing the changeset check." | |
| echo 'skip=false' >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "Labels on PR #$PR_NUMBER right now: ${LABELS:-(none)}" | |
| # Whole-line fixed match, fed by a here-string rather than a pipe. | |
| # `-x -F` because the payload expression this replaces, `contains(<array>, | |
| # 'skip-changeset')`, matches an array ELEMENT exactly -- a substring | |
| # match would newly exempt a PR labelled e.g. `skip-changeset-audit`. | |
| # The here-string (as in release.yml) keeps `grep -q` out of a pipeline: | |
| # -q closes the pipe on the first hit, so a piped writer can take | |
| # SIGPIPE and, under `set -o pipefail`, flip this test to false for a | |
| # long enough label list. | |
| if grep -qxF 'skip-changeset' <<<"$LABELS"; then | |
| echo "::notice::'skip-changeset' is on PR #$PR_NUMBER (read live, not from the event payload), so this PR declares no release of its own and the changeset check is exempt." | |
| echo 'skip=true' >> "$GITHUB_OUTPUT" | |
| else | |
| echo 'skip=false' >> "$GITHUB_OUTPUT" | |
| fi | |
| # Every step from here down carries the same guard rather than the job | |
| # carrying one `if:`, because a job-level `if:` cannot read a step of its | |
| # own job. Repeating it beats the alternatives: a separate gate job would | |
| # add a check row and a brand-new way to go red to a repo already fighting | |
| # check-list noise, and testing the label inside the counting step would | |
| # pay for checkout + install before discovering the PR is exempt. | |
| - name: Checkout repository | |
| if: steps.labels.outputs.skip != 'true' | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| if: steps.labels.outputs.skip != 'true' | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| - name: Enable Corepack | |
| if: steps.labels.outputs.skip != 'true' | |
| run: corepack enable | |
| - name: Install dependencies | |
| if: steps.labels.outputs.skip != 'true' | |
| run: pnpm install --frozen-lockfile | |
| - name: Check for a changeset added by this PR | |
| if: steps.labels.outputs.skip != 'true' | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| if [ ! -d ".changeset" ]; then | |
| echo "::warning::.changeset directory not found. Skipping changeset check." | |
| exit 0 | |
| fi | |
| # Count changesets THIS PR adds (diff against the base commit), NOT | |
| # the whole .changeset directory. A global `find | wc -l` is unsound: | |
| # in pre-release (RC) mode `changeset version` RETAINS every consumed | |
| # .md file, so the directory is permanently non-empty and the gate can | |
| # never go red. #3373 merged a real spec/api-surface fix with no | |
| # changeset while this step happily reported "Found 104 changeset(s)". | |
| # Diffing against BASE_SHA ignores that residue and sees only what the | |
| # PR itself introduced. | |
| # | |
| # An empty-frontmatter changeset still COUNTS here, and that is | |
| # deliberate and unchanged — it remains a legal "this PR releases | |
| # nothing" declaration. What #5292 corrected is the PRESCRIPTION, not | |
| # the count: this comment used to call it "on par with the | |
| # skip-changeset label", and the failure message below used to offer | |
| # the two as equals. They are not equal downstream. The label is a | |
| # gate-level exemption that produces no input for changesets/action; | |
| # an empty changeset is a real input to it. See the message for the | |
| # consequence. | |
| ADDED=$(git diff --name-only --diff-filter=A "$BASE_SHA" HEAD -- '.changeset/*.md' \ | |
| | grep -v '/README\.md$' | wc -l | tr -d '[:space:]') | |
| if [ "$ADDED" -eq 0 ]; then | |
| # The full comparison goes to the job log — that is what an author | |
| # reading `gh run view --log-failed`, or expanding this step in the | |
| # UI, actually sees. The ::error:: annotation after it is the | |
| # one-line version for the Checks tab. Both lead with the label. | |
| # Terminator sits at this block's base indentation on purpose: YAML | |
| # strips that much from every line, so `MSG` lands in column 0 of | |
| # the generated script. Do not re-indent it. | |
| cat <<'MSG' | |
| This PR adds no changeset. There are three ways forward, and they are NOT | |
| equivalent. Pick by what the PR actually releases: | |
| 1. It releases something | |
| -> run 'pnpm changeset' and name the packages it releases. | |
| 2. It releases nothing (.github/, .claude/, docs/, content/, examples/, | |
| tests-only, and the like) | |
| -> apply the 'skip-changeset' label. <<< PREFERRED | |
| The label is a gate-level exemption. It produces NO input for | |
| changesets/action, so it cannot affect a release. | |
| 3. An empty-frontmatter changeset also satisfies this gate and stays | |
| legal -- but it is a LAST RESORT, not the quick way past a red check. | |
| Unlike the label it is a REAL INPUT to changesets/action: when every | |
| pending changeset is empty, the action takes its | |
| "hasChangesets && !hasNonEmptyChangesets" branch, prints | |
| "All changesets are empty; not creating PR", and returns in 0 seconds | |
| -- no version PR, no publish, and the Release run still goes GREEN. | |
| That is #4898, which silently stalled 17.0.0-rc.2. It also buys you | |
| nothing the label does not: an empty changeset names no package, so | |
| its body reaches no CHANGELOG. | |
| If you are unsure, take route 2. A wrong 'skip-changeset' label is caught by | |
| review; a wrong empty changeset is caught by nobody. | |
| MSG | |
| echo "::error::This PR adds no changeset. If it releases nothing, apply the 'skip-changeset' label (preferred); otherwise run 'pnpm changeset' and name the packages. An empty-frontmatter changeset also passes this gate, but it is NOT equivalent to the label -- it is a real input to changesets/action, and an all-empty set stalls the release silently and greenly (#4898). Full comparison in this step's log." | |
| exit 1 | |
| fi | |
| echo "This PR adds $ADDED changeset(s)." | |
| - name: Guard against accidental major bumps (launch window) | |
| # Every publishable package is in one Changesets "fixed" (lockstep) group, | |
| # so a single `major` bump promotes the ENTIRE monorepo to a new major | |
| # version. During the launch window we ship breaking changes as `minor`. | |
| # Add the `allow-major` PR label when a whole-stack major is intended. | |
| # | |
| # The first clause keeps this step exempt exactly when the changeset check | |
| # above is: before #5580 the `skip-changeset` label skipped the whole job, | |
| # this step included, and a live-read label must not quietly re-arm it. | |
| # | |
| # The second clause still reads the frozen payload, and so still carries | |
| # the #5580 race in its own right: an `allow-major` applied after the event | |
| # fired is invisible to this run and a rerun replays the same payload. | |
| # It is DORMANT while Changesets is in pre-release mode, because | |
| # check-changeset-no-major.mjs stands aside for the whole RC window (see | |
| # its RC EXEMPTION note), so the label is currently never needed. Tracked | |
| # as #5620 rather than fixed here: #5580 scoped this change to the | |
| # `skip-changeset` read, and widening a green gate's exemption path under | |
| # cover of another issue is how exemptions grow unnoticed. | |
| if: >- | |
| steps.labels.outputs.skip != 'true' | |
| && !contains(github.event.pull_request.labels.*.name, 'allow-major') | |
| run: node scripts/check-changeset-no-major.mjs |