Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/workflows/changeset-presence.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: Changeset Presence

# Demands the DECLARATION that objectstack#4731 / #4843 made the criterion for
# "which frontend changes shipped": a change to the source of a package the
# release covers must add a `.changeset/*.md`. An empty frontmatter counts — what
# is required is one sentence written while the author still knows what the change
# does, not a release. Full rationale, the measured history, and the exemption's
# exact spelling: `scripts/check-changeset-presence.mjs`.
#
# Why this is a SECOND changeset workflow rather than a wider trigger on the
# first. `changeset-guard.yml` runs only when `.changeset/**` changes, and that
# inversion is deliberate and documented in its own header: `ci.yml` and
# `lint.yml` both list `.changeset/**` under `paths-ignore`, so a PR that adds
# ONLY a changeset starts nothing else, and that guard exists to see exactly that
# PR. A PR which FORGOT its changeset does not touch `.changeset/**` at all, so
# the one check that could notice is the one guaranteed not to run. Widening those
# paths would break the case it was built for. Hence two workflows, opposite
# directions: that one polices the level of a declaration that exists, this one
# polices the existence of a declaration at all.
#
# Hence also: no `paths` and no `paths-ignore` here, deliberately — the same
# choice `control-bytes.yml` and `docs-links.yml` made and for a stronger reason.
# A path filter on the trigger skips the WHOLE workflow (GitHub has no per-job
# path filter), so the context is never CREATED on a pull request that does not
# match, and a required context that is never created leaves the PR pending rather
# than failing it — in the queue, until the ruleset's 60-minute timeout. That is
# objectui#3523's second half, and it is why the four gates moved their filters
# into the jobs. This gate reports on every pull request instead: it decides from
# the diff, inside the script, and says so when nothing is owed. A `paths` filter
# would additionally be a second copy of the script's guarded surface, free to
# drift from it — and the surface is derived from `.changeset/config.json`
# precisely so there is only one. `scripts/__tests__/check-changeset-presence.test.ts`
# fails if a filter is ever added.
#
# It needs no install and no build — a checkout, `setup-node`, and one `node` call
# over `git diff` — so keep it that way if you add checks to it.

on:
pull_request:
branches: [main, develop]
# Merge queue (objectui#3523 — see `ci.yml`'s trigger block for the full note
# and the measurements behind it). A required context that does not report on a
# queue build stalls the queue until the ruleset's 60-minute status-check
# timeout fails it, so a gate that carries no path filter — and therefore CAN
# be required — has to subscribe. `types:` is named although `checks_requested`
# is currently the only one GitHub defines.
#
# The script needs no per-event branch to work here: it resolves the base as
# the merge base with the target branch, and on a queue build (no
# `GITHUB_BASE_REF`, no `github.event.pull_request`) that falls through to the
# merge base with `origin/main`, which is the commit the queue built the group
# on. `ci.yml`'s `pnpm check:i18n-drift` step already resolves its base exactly
# this way on this event.
merge_group:
types: [checks_requested]

# Deliberately NOT subscribed:
#
# - `push` to `main`. There is nothing left to demand: the change has landed,
# and failing the push would only paint `main` red at the author of the next
# commit. The pull request and the queue build are where a declaration can
# still be written.
# - `workflow_dispatch`. A manual run has no revision range to judge, and this
# gate fails loudly rather than inventing one. Locally it is
# `node scripts/check-changeset-presence.mjs`, which defaults to this branch
# against its merge base and reads the working tree, so an author gets the
# answer before committing.

concurrency:
group: changeset-presence-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
changeset-presence:
name: Changeset Declaration
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
# The gate compares this change against its MERGE BASE with the target
# branch, so it needs history — checkout's default is a depth-1 clone
# where `git merge-base` has nothing to find. An unresolvable base is a
# hard failure in the script, never a skip, so getting this wrong is a
# red build rather than a silent pass; it is spelled out here so it
# stays that way. Same requirement, same reason, as the `fetch-depth: 0`
# on `ci.yml`'s `type-check` job for `pnpm check:i18n-drift`.
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22.x'

# Reads `.changeset/config.json` and `git diff`, and nothing else — no
# install, no network. The guarded surface is every workspace package named
# in the `fixed` group, so it follows the release configuration instead of
# being a hand-written glob: `@object-ui/console` lives at `apps/console`,
# outside `packages/`, and is both the most-edited published package here
# and the one the platform's `bump-objectui.sh` writes a changeset for.
- name: Verify a changeset declares this change
run: node scripts/check-changeset-presence.mjs
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ export const SchemaRenderer = ({ schema }: { schema: UIComponent }) => {
- `.gitignore` 已锚定 `/*.png` 等防兜底,并额外忽略根级 `/--*` —— 名字以 `--` 开头的根文件必然是把 CLI 参数当成了输出文件名(#3193:一张叫 `--full-page` 的 68KB 截图被提交进来,因为没有 `.png` 后缀,`/*.png` 兜不住)。兜底只是最后一道,仍要主动清。
- 删这类文件要用 `--` 断开参数解析:`rm -- ./--full-page`、`git rm -- './--full-page'`。
- 任务结束:停**自己起的**后台服务(见下方"服务纪律";别按端口杀别人的)、清 `.playwright-mcp/`。
- 改完代码提交时:功能改进(feature)需写 changeset(`pnpm changeset`);纯 bug 修复不需要。
- 改完代码提交时:**只要改了发版包的 `src/`(`.changeset/config.json` 的 `fixed` 组,含 `apps/console`),就必须新增一个 `.changeset/*.md`** —— 这一条由 `.github/workflows/changeset-presence.yml` 机械强制(objectui#3387),`pnpm changeset` 写正常 bump,**纯内部改动/只动测试就写空 frontmatter(`---` 紧跟 `---`)显式声明"不发版"**,那是合法的一等通过写法。要的是"声明一次",不是强制发版。
- 别再按"feature 要写、bug 修复不用"来判断 —— 正是这个旧判据让三条用户可见的修复(`19716b5bf` fix(charts)、`5e7ef1141` fix(i18n)、`0e50440` #3518)搭顺风车发了出去,任何 CHANGELOG/版本号/发布记录里都查不到:平台侧的发布判据(objectstack#4731/#4843)读的就是本仓声明的 changeset。
- 本地先自查:`node scripts/check-changeset-presence.mjs`(未提交的 changeset 也算)。

### 怎么跑测试(有两种写法会静默假绿 —— 现已机械拦截)

Expand Down
39 changes: 31 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,20 +496,43 @@ Changesets is a tool that helps us:

### When to Create a Changeset

Create a changeset when your PR makes changes to any package in `packages/`:
**If your PR changes the `src/` of a package the release covers, it must add a `.changeset/*.md` —
this is enforced by CI** (`changeset-presence.yml`, objectui#3387). "The release covers it" means
the package is named in the `fixed` group of `.changeset/config.json`, which includes
`@object-ui/console` at `apps/console` as well as everything under `packages/`; `@object-ui/site`
and the examples are in `ignore` and are not gated.

- ✅ **DO create a changeset for**:
What the gate asks for is a **declaration**, not a release:

- ✅ **Score a bump** (`patch` / `minor` — never `major`, see below) for:
- New features
- Bug fixes
- Breaking changes
- Breaking changes (scored `minor`, with the break described in the body)
- Performance improvements
- API changes

- ❌ **DON'T create a changeset for**:
- Documentation updates only
- Changes to examples or apps
- Internal refactoring with no user-facing changes
- Test updates without code changes
- ✅ **Declare that it releases nothing** — a changeset with an **empty frontmatter** — for:
- Internal refactoring with no user-facing change
- Test-only changes under a package's `src/`
- Dead-code removal

```md
---
---

Removed the orphaned SystemObjectViewPage; no published behaviour changes.
```

This is a first-class pass, not a workaround. It costs one line and it puts the reason in the
repository, where the next reader finds it — which is the whole point: three user-visible fixes
shipped with no changeset and therefore appear in no CHANGELOG, version number or release note
anywhere (objectui#3387).

- ❌ **No changeset is needed at all for** changes that touch no released package's `src/`:
documentation, CI configuration, repo-level scripts, the examples, `apps/site`.

Run `node scripts/check-changeset-presence.mjs` locally to get the same answer CI will give,
including for a changeset you have written but not yet committed.

### How to Create a Changeset

Expand Down
Loading
Loading