Skip to content

fix(objectql): autonumber 是运行时拥有的字段,写路径不再接受调用者提交的单号 (#5503) #9575

fix(objectql): autonumber 是运行时拥有的字段,写路径不再接受调用者提交的单号 (#5503)

fix(objectql): autonumber 是运行时拥有的字段,写路径不再接受调用者提交的单号 (#5503) #9575

Workflow file for this run

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