Skip to content

Commit 50c2f64

Browse files
fix(tooling): objectui-range reads objectui's declared changesets, not commit types (#4843) (#4905)
`scripts/objectui-range.mjs` — whose output is pasted into a release page's Console section — kept the `feat|fix` subject-line guess that #4731 removed from `bump-objectui.sh`. Measured on the same real range (7d9734d5e321..785b8a5d432c, 53 non-merge objectui commits) the default output dropped 13 commits that actually released, 6 of them breaking `refactor(...)!`, and listed 5 that release nothing in objectui. The two scripts now share ONE criterion instead of each carrying a copy: the classification moved into an exported `classifyRange()` in objectui-changeset-digest.mjs, used by both the pin changeset (the platform release record) and this release-page aggregation. Because the output IS release-page body text, the accounting is unconditional — every run states how many changesets released of how many were added across how many commits, plus the excluded counts (release-nothing changesets, changeset-less commits), zeros included. `--all` now names those entries instead of switching a filter off. Headings group by declared level; grouping is presentation, never a filter. Guarded by `objectui-range.mjs --self-test`, folded into the existing `pnpm check:objectui-changeset` gate. Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent c5f16e3 commit 50c2f64

6 files changed

Lines changed: 528 additions & 102 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
---
3+
4+
Tooling-only (#4843): `scripts/objectui-range.mjs` — the aggregation layer whose output is
5+
pasted into a release page's **Console** section — now reports the frontend changes objectui
6+
**declared** over the pinned range instead of guessing them from conventional-commit types.
7+
Releases nothing; no package changes.
8+
9+
#4731 fixed this failure mode on the `bump-objectui.sh` side; the release page kept the old
10+
guess (`const KEEP = ALL_TYPES ? null : new Set(['feat', 'fix'])`, with `--all` as an
11+
explicit opt-in). Measured on the same real range (`7d9734d5e321..785b8a5d432c`, 53
12+
non-merge objectui commits), the default output **dropped 13 commits that actually
13+
released** — 6 of them breaking `refactor(...)!`, including `refactor(layout)!: delete
14+
PageNodeRenderer` and burn-ledger batches 2/4/5/6/7, plus `chore(deps): lockstep the
15+
@objectstack family onto 17.0.0-rc.1` — while **listing 5 commits that release nothing** in
16+
objectui (two `fix(ci)`: one with no changeset, one whose changeset has an empty
17+
frontmatter). Breaking changes were the single class structurally unable to appear, in the
18+
artifact that leads with breaking changes.
19+
20+
The two scripts now share **one** criterion rather than each carrying a copy: the
21+
classification moved into an exported `classifyRange()` in
22+
`scripts/objectui-changeset-digest.mjs`, and both `bump-objectui.sh` (the platform release
23+
record) and `objectui-range.mjs` (the release page) go through it. A second copy would
24+
drift, and the first thing it would drift on is the class that already went missing once.
25+
26+
Because the output *is* release-page body text — a reader cannot tell a filtered list from a
27+
complete one — the accounting is now **unconditional**: every run prints how many changesets
28+
released of how many were added across how many commits, plus the excluded counts
29+
(release-nothing changesets, commits carrying no changeset), including when those counts are
30+
zero. `--all` changes meaning from "include every commit type" (the filter is gone) to "also
31+
name the excluded entries, one per line". Headings group by the level objectui declared
32+
(breaking / features / fixes); grouping is presentation and never a filter. Guarded by
33+
`node scripts/objectui-range.mjs --self-test`, folded into the existing
34+
`pnpm check:objectui-changeset` gate.

.github/workflows/lint.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,9 @@ jobs:
233233
# throwaway git repos carrying those exact shapes, runs the real scripts
234234
# over them, and asserts the emitted changeset — including that a cap which
235235
# fires announces itself and that an unwalkable range degrades loudly.
236+
# #4843 folded `scripts/objectui-range.mjs` — the release page's Console
237+
# section — onto that same `classifyRange()` criterion, so both self-tests
238+
# run here: one criterion, two consumers, no room to drift apart.
236239
- name: objectui pin-changeset digest guard
237240
run: pnpm check:objectui-changeset
238241

docs/releases-maintenance.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,32 @@ platform version maps to.
7171
Backend content comes from the spec/package changesets. Frontend content comes from
7272
objectui's history for the SHA range bundled in that release. Because `.objectui-sha`
7373
is version-controlled, `scripts/objectui-range.mjs` computes that range from any two
74-
framework revisions and prints the feat/fix commits grouped by type + the largest
75-
touched areas — ready to paste into a Console section:
74+
framework revisions and prints the frontend changes **objectui declared** over it —
75+
grouped by declared level (breaking / features / fixes), with the largest touched
76+
areas — ready to paste into a Console section:
7677

7778
```bash
7879
# frontend delta bundled between two framework revisions (needs ../objectui)
7980
node scripts/objectui-range.mjs <old-rev> <new-rev> # e.g. the two release commits
8081
node scripts/objectui-range.mjs --from <sha> --to <sha> --json # explicit SHAs / tooling
82+
node scripts/objectui-range.mjs <old-rev> <new-rev> --all # also name what ships nothing
8183
```
8284

85+
**What "a frontend change" means here (#4843).** The list comes from the
86+
`.changeset/*.md` files objectui added over the range, via the same `classifyRange()`
87+
in `scripts/objectui-changeset-digest.mjs` that `bump-objectui.sh` uses to write the
88+
pin changeset (#4731) — one criterion, two consumers, so the platform release record
89+
and this page's Console section cannot disagree. A changeset with an empty frontmatter
90+
block is changesets' own "release-nothing" marker and is excluded; so is a commit that
91+
added no changeset. **Both exclusions are counted out loud in the printed markdown**,
92+
because the output *is* release-page body text and a reader cannot otherwise tell a
93+
filtered list from a complete one. Headings group by declared level; grouping is
94+
presentation and never a filter.
95+
96+
This replaced a `feat|fix` guess on commit subjects. On the real range
97+
`7d9734d5e321..785b8a5d432c` that guess dropped 13 commits that actually released —
98+
6 of them breaking `refactor(...)!` — and listed 5 that release nothing.
99+
83100
Without an objectui checkout it still prints the SHA range to inspect. The framework
84101
changesets also embed companion frontend notes inline ("Companion objectui PR
85102
ships…", renderer notes), which are enough to write an accurate Console section on

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"check:durability-log-level": "node scripts/check-durability-degradation-log-level.mjs --self-test && node scripts/check-durability-degradation-log-level.mjs",
4848
"check:startup-registry-verdict": "node scripts/check-startup-registry-verdict.mjs --self-test && node scripts/check-startup-registry-verdict.mjs",
4949
"check:console-sha": "node scripts/check-console-sha.mjs",
50-
"check:objectui-changeset": "node scripts/objectui-changeset-digest.mjs --self-test",
50+
"check:objectui-changeset": "node scripts/objectui-changeset-digest.mjs --self-test && node scripts/objectui-range.mjs --self-test",
5151
"check:release-notes": "node scripts/check-release-notes.mjs",
5252
"check:node-version": "node scripts/check-node-version.mjs",
5353
"check:published-files": "node scripts/check-published-files.mjs --self-test && node scripts/check-published-files.mjs",

scripts/objectui-changeset-digest.mjs

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,21 @@ export function clampSummary(summary, limit = 180) {
125125
* it — the released frontend change would vanish from the release record, the
126126
* very failure this whole mechanism exists to prevent.
127127
*
128-
* @returns {{ entries: Array<{ path: string, sha: string, subject: string }>, totalCommits: number, commitsWithChangeset: number }}
128+
* `commits` is every non-merge commit in the range (newest first) and
129+
* `commitsWithChangesetShas` the subset that added one, so a caller can NAME
130+
* the commits it is leaving out instead of only counting them (#4843).
131+
*
132+
* @returns {{ entries: Array<{ path: string, sha: string, subject: string }>, commits: Array<{ sha: string, subject: string }>, totalCommits: number, commitsWithChangeset: number, commitsWithChangesetShas: Set<string> }}
129133
*/
130134
export function collectAddedChangesets(objectuiRoot, from, to) {
131-
const totalCommits = git(objectuiRoot, ['log', '--no-merges', '--format=%H', `${from}..${to}`])
135+
const commits = git(objectuiRoot, ['log', '--no-merges', '--format=%H%x09%s', `${from}..${to}`])
132136
.split('\n')
133-
.filter(Boolean).length;
137+
.filter(Boolean)
138+
.map((line) => {
139+
const tab = line.indexOf('\t');
140+
return { sha: line.slice(0, tab), subject: line.slice(tab + 1) };
141+
});
142+
const totalCommits = commits.length;
134143

135144
const raw = git(objectuiRoot, [
136145
'log',
@@ -164,7 +173,13 @@ export function collectAddedChangesets(objectuiRoot, from, to) {
164173
commitsWithChangeset.add(sha);
165174
entries.push({ path, sha, subject });
166175
}
167-
return { entries, totalCommits, commitsWithChangeset: commitsWithChangeset.size };
176+
return {
177+
entries,
178+
commits,
179+
totalCommits,
180+
commitsWithChangeset: commitsWithChangeset.size,
181+
commitsWithChangesetShas: commitsWithChangeset,
182+
};
168183
}
169184

170185
/** Read a changeset's content at `to`, falling back to the commit that added it. */
@@ -196,31 +211,36 @@ export function inPreMode(frameworkRoot) {
196211
}
197212

198213
/**
199-
* Build the digest for a range.
214+
* THE criterion: which objectui changes over `from..to` actually ship in the
215+
* frontend release, as objectui DECLARED it — plus a full account of what is
216+
* being left out and why.
200217
*
201-
* @returns {{ bump: string, declaredLevel: string|null, breaking: number, releasing: Array<object>, releaseNothing: number, noChangeset: number, totalCommits: number, downgradedMajor: boolean, body: string }}
218+
* This is the single shared implementation. `bump-objectui.sh` (via
219+
* `buildDigest` below, #4731) and `scripts/objectui-range.mjs` (#4843) both go
220+
* through it, so the platform release record and the release page's Console
221+
* section can never disagree about what "a releasing frontend change" means.
222+
* Two copies of this rule would drift, and the first thing they would drift on
223+
* is the class that already went missing once: breaking `refactor(...)!`.
224+
*
225+
* Nothing here reads a commit type. Grouping output BY type is presentation and
226+
* belongs to the caller; it must never become a filter again.
227+
*
228+
* @returns {{ releasing: Array<object>, releaseNothingEntries: Array<object>, noChangesetCommits: Array<object>, releaseNothing: number, noChangeset: number, changesetsAdded: number, totalCommits: number }}
202229
*/
203-
export function buildDigest({
204-
objectuiRoot,
205-
frameworkRoot = REPO_ROOT,
206-
from,
207-
to,
208-
max = DEFAULT_MAX_ENTRIES,
209-
bumpOverride = '',
210-
}) {
211-
const { entries, totalCommits, commitsWithChangeset } = collectAddedChangesets(
230+
export function classifyRange({ objectuiRoot, from, to }) {
231+
const { entries, commits, totalCommits, commitsWithChangesetShas } = collectAddedChangesets(
212232
objectuiRoot,
213233
from,
214234
to,
215235
);
216236

217237
const releasing = [];
218-
let releaseNothing = 0;
238+
const releaseNothingEntries = [];
219239
for (const entry of entries) {
220240
const { packages, summary } = parseChangeset(readAt(objectuiRoot, to, entry.sha, entry.path));
221241
const level = highestLevel(packages);
222242
if (!level) {
223-
releaseNothing++;
243+
releaseNothingEntries.push({ ...entry, summary: summary || entry.subject });
224244
continue;
225245
}
226246
releasing.push({
@@ -236,6 +256,38 @@ export function buildDigest({
236256
const order = { major: 0, minor: 1, patch: 2 };
237257
releasing.sort((a, b) => order[a.level] - order[b.level]);
238258

259+
const noChangesetCommits = commits.filter((c) => !commitsWithChangesetShas.has(c.sha));
260+
261+
return {
262+
releasing,
263+
releaseNothingEntries,
264+
noChangesetCommits,
265+
releaseNothing: releaseNothingEntries.length,
266+
noChangeset: noChangesetCommits.length,
267+
changesetsAdded: entries.length,
268+
totalCommits,
269+
};
270+
}
271+
272+
/**
273+
* Build the digest for a range.
274+
*
275+
* @returns {{ bump: string, declaredLevel: string|null, breaking: number, releasing: Array<object>, releaseNothing: number, noChangeset: number, totalCommits: number, downgradedMajor: boolean, body: string }}
276+
*/
277+
export function buildDigest({
278+
objectuiRoot,
279+
frameworkRoot = REPO_ROOT,
280+
from,
281+
to,
282+
max = DEFAULT_MAX_ENTRIES,
283+
bumpOverride = '',
284+
}) {
285+
const { releasing, releaseNothing, noChangeset, changesetsAdded, totalCommits } = classifyRange({
286+
objectuiRoot,
287+
from,
288+
to,
289+
});
290+
239291
const declaredLevel = releasing.length
240292
? releasing.reduce(
241293
(best, r) => (LEVEL_RANK[r.level] > LEVEL_RANK[best] ? r.level : best),
@@ -273,7 +325,6 @@ export function buildDigest({
273325
);
274326
}
275327

276-
const noChangeset = Math.max(0, totalCommits - commitsWithChangeset);
277328
const omitted = [];
278329
if (releaseNothing > 0) {
279330
omitted.push(
@@ -286,7 +337,7 @@ export function buildDigest({
286337

287338
const accounting =
288339
`Derived from the changesets objectui declared over the range — ` +
289-
`${releasing.length} releasing of ${entries.length} changeset${entries.length === 1 ? '' : 's'} added ` +
340+
`${releasing.length} releasing of ${changesetsAdded} changeset${changesetsAdded === 1 ? '' : 's'} added ` +
290341
`across ${totalCommits} non-merge commit${totalCommits === 1 ? '' : 's'}` +
291342
(omitted.length ? `; omitted: ${omitted.join(', ')} (they ship no package code).` : '.');
292343

0 commit comments

Comments
 (0)