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
34 changes: 34 additions & 0 deletions .changeset/objectui-range-from-declarations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
---

Tooling-only (#4843): `scripts/objectui-range.mjs` — the aggregation layer whose output is
pasted into a release page's **Console** section — now reports the frontend changes objectui
**declared** over the pinned range instead of guessing them from conventional-commit types.
Releases nothing; no package changes.

#4731 fixed this failure mode on the `bump-objectui.sh` side; the release page kept the old
guess (`const KEEP = ALL_TYPES ? null : new Set(['feat', 'fix'])`, with `--all` as an
explicit opt-in). 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(...)!`, including `refactor(layout)!: delete
PageNodeRenderer` and burn-ledger batches 2/4/5/6/7, plus `chore(deps): lockstep the
@objectstack family onto 17.0.0-rc.1` — while **listing 5 commits that release nothing** in
objectui (two `fix(ci)`: one with no changeset, one whose changeset has an empty
frontmatter). Breaking changes were the single class structurally unable to appear, in the
artifact that leads with breaking changes.

The two scripts now share **one** criterion rather than each carrying a copy: the
classification moved into an exported `classifyRange()` in
`scripts/objectui-changeset-digest.mjs`, and both `bump-objectui.sh` (the platform release
record) and `objectui-range.mjs` (the release page) go through it. A second copy would
drift, and the first thing it would drift on is the class that already went missing once.

Because the output *is* release-page body text — a reader cannot tell a filtered list from a
complete one — the accounting is now **unconditional**: every run prints how many changesets
released of how many were added across how many commits, plus the excluded counts
(release-nothing changesets, commits carrying no changeset), including when those counts are
zero. `--all` changes meaning from "include every commit type" (the filter is gone) to "also
name the excluded entries, one per line". Headings group by the level objectui declared
(breaking / features / fixes); grouping is presentation and never a filter. Guarded by
`node scripts/objectui-range.mjs --self-test`, folded into the existing
`pnpm check:objectui-changeset` gate.
3 changes: 3 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ jobs:
# throwaway git repos carrying those exact shapes, runs the real scripts
# over them, and asserts the emitted changeset — including that a cap which
# fires announces itself and that an unwalkable range degrades loudly.
# #4843 folded `scripts/objectui-range.mjs` — the release page's Console
# section — onto that same `classifyRange()` criterion, so both self-tests
# run here: one criterion, two consumers, no room to drift apart.
- name: objectui pin-changeset digest guard
run: pnpm check:objectui-changeset

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

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

**What "a frontend change" means here (#4843).** The list comes from the
`.changeset/*.md` files objectui added over the range, via the same `classifyRange()`
in `scripts/objectui-changeset-digest.mjs` that `bump-objectui.sh` uses to write the
pin changeset (#4731) — one criterion, two consumers, so the platform release record
and this page's Console section cannot disagree. A changeset with an empty frontmatter
block is changesets' own "release-nothing" marker and is excluded; so is a commit that
added no changeset. **Both exclusions are counted out loud in the printed markdown**,
because the output *is* release-page body text and a reader cannot otherwise tell a
filtered list from a complete one. Headings group by declared level; grouping is
presentation and never a filter.

This replaced a `feat|fix` guess on commit subjects. On the real range
`7d9734d5e321..785b8a5d432c` that guess dropped 13 commits that actually released —
6 of them breaking `refactor(...)!` — and listed 5 that release nothing.

Without an objectui checkout it still prints the SHA range to inspect. The framework
changesets also embed companion frontend notes inline ("Companion objectui PR
ships…", renderer notes), which are enough to write an accurate Console section on
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"check:durability-log-level": "node scripts/check-durability-degradation-log-level.mjs --self-test && node scripts/check-durability-degradation-log-level.mjs",
"check:startup-registry-verdict": "node scripts/check-startup-registry-verdict.mjs --self-test && node scripts/check-startup-registry-verdict.mjs",
"check:console-sha": "node scripts/check-console-sha.mjs",
"check:objectui-changeset": "node scripts/objectui-changeset-digest.mjs --self-test",
"check:objectui-changeset": "node scripts/objectui-changeset-digest.mjs --self-test && node scripts/objectui-range.mjs --self-test",
"check:release-notes": "node scripts/check-release-notes.mjs",
"check:node-version": "node scripts/check-node-version.mjs",
"check:published-files": "node scripts/check-published-files.mjs --self-test && node scripts/check-published-files.mjs",
Expand Down
89 changes: 70 additions & 19 deletions scripts/objectui-changeset-digest.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,21 @@ export function clampSummary(summary, limit = 180) {
* it — the released frontend change would vanish from the release record, the
* very failure this whole mechanism exists to prevent.
*
* @returns {{ entries: Array<{ path: string, sha: string, subject: string }>, totalCommits: number, commitsWithChangeset: number }}
* `commits` is every non-merge commit in the range (newest first) and
* `commitsWithChangesetShas` the subset that added one, so a caller can NAME
* the commits it is leaving out instead of only counting them (#4843).
*
* @returns {{ entries: Array<{ path: string, sha: string, subject: string }>, commits: Array<{ sha: string, subject: string }>, totalCommits: number, commitsWithChangeset: number, commitsWithChangesetShas: Set<string> }}
*/
export function collectAddedChangesets(objectuiRoot, from, to) {
const totalCommits = git(objectuiRoot, ['log', '--no-merges', '--format=%H', `${from}..${to}`])
const commits = git(objectuiRoot, ['log', '--no-merges', '--format=%H%x09%s', `${from}..${to}`])
.split('\n')
.filter(Boolean).length;
.filter(Boolean)
.map((line) => {
const tab = line.indexOf('\t');
return { sha: line.slice(0, tab), subject: line.slice(tab + 1) };
});
const totalCommits = commits.length;

const raw = git(objectuiRoot, [
'log',
Expand Down Expand Up @@ -164,7 +173,13 @@ export function collectAddedChangesets(objectuiRoot, from, to) {
commitsWithChangeset.add(sha);
entries.push({ path, sha, subject });
}
return { entries, totalCommits, commitsWithChangeset: commitsWithChangeset.size };
return {
entries,
commits,
totalCommits,
commitsWithChangeset: commitsWithChangeset.size,
commitsWithChangesetShas: commitsWithChangeset,
};
}

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

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

const releasing = [];
let releaseNothing = 0;
const releaseNothingEntries = [];
for (const entry of entries) {
const { packages, summary } = parseChangeset(readAt(objectuiRoot, to, entry.sha, entry.path));
const level = highestLevel(packages);
if (!level) {
releaseNothing++;
releaseNothingEntries.push({ ...entry, summary: summary || entry.subject });
continue;
}
releasing.push({
Expand All @@ -236,6 +256,38 @@ export function buildDigest({
const order = { major: 0, minor: 1, patch: 2 };
releasing.sort((a, b) => order[a.level] - order[b.level]);

const noChangesetCommits = commits.filter((c) => !commitsWithChangesetShas.has(c.sha));

return {
releasing,
releaseNothingEntries,
noChangesetCommits,
releaseNothing: releaseNothingEntries.length,
noChangeset: noChangesetCommits.length,
changesetsAdded: entries.length,
totalCommits,
};
}

/**
* Build the digest for a range.
*
* @returns {{ bump: string, declaredLevel: string|null, breaking: number, releasing: Array<object>, releaseNothing: number, noChangeset: number, totalCommits: number, downgradedMajor: boolean, body: string }}
*/
export function buildDigest({
objectuiRoot,
frameworkRoot = REPO_ROOT,
from,
to,
max = DEFAULT_MAX_ENTRIES,
bumpOverride = '',
}) {
const { releasing, releaseNothing, noChangeset, changesetsAdded, totalCommits } = classifyRange({
objectuiRoot,
from,
to,
});

const declaredLevel = releasing.length
? releasing.reduce(
(best, r) => (LEVEL_RANK[r.level] > LEVEL_RANK[best] ? r.level : best),
Expand Down Expand Up @@ -273,7 +325,6 @@ export function buildDigest({
);
}

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

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

Expand Down
Loading
Loading