ci(server): fail lint:size and lint:boundaries on stale baseline entries

Both server ratchets only logged an entry the tree had moved past. A
file that shrank could grow back to its old size entry unseen, a
deleted file left its allowance to the next file at its path, and a
boundary violation that was fixed stayed allowed until someone ran
--update. The client ratchets now fail on such entries, so the server
ones do the same.

lint:size fails on an entry above its file's size or for a file that is
gone, and lint:boundaries on an entry that no longer occurs; both tell
to run --update, which still only lowers or drops. Both baselines match
the tree today.
This commit is contained in:
Maurice
2026-10-08 13:58:19 +02:00
parent bd267c67f8
commit 809ec386fc
5 changed files with 77 additions and 18 deletions
+13 -4
View File
@@ -39,7 +39,8 @@
* layer sits below the domains, never the other way round.
*
* Each rule's baseline lists today's violations. A violation not in it fails
* the check; one that is gone is reported so the baseline can shrink.
* the check, and so does an entry that no longer occurs, until --update drops
* it: a stale entry would let the same violation back in unseen.
*
* npm run lint:boundaries check against the baseline (CI)
* npm run lint:boundaries -- --update drop the entries that no longer occur;
@@ -374,9 +375,17 @@ function main(argv) {
failed++;
}
}
const lowerable = RULES.reduce((n, rule) => n + gone[rule].length, 0);
if (lowerable && !update) {
console.log(`${lowerable} baseline entr${lowerable === 1 ? 'y no longer occurs' : 'ies no longer occur'}: run with --update to drop them.`);
for (const rule of RULES) {
for (const key of gone[rule]) {
console.error(`FAIL ${rule}: ${key} is in scripts/import-boundaries-baseline.json but no longer occurs.`);
failed++;
}
}
if (RULES.some((rule) => gone[rule].length)) {
console.error(
'Run npm run lint:boundaries -- --update to drop it with the change that fixed it: ' +
'a stale entry would let the same violation back in unseen.',
);
}
console.log(
`boundaries: ${edges.length} import(s); held at baseline: ` + RULES.map((rule) => `${rule} ${baseline[rule].length}`).join(', '),
+25 -8
View File
@@ -10,7 +10,10 @@
* scripts/size-baseline.json with the length they had, and the check fails
* when one of them grows past its entry. A file that needs to grow is split
* by concern instead (trips into trips/trip-members/trip-membership/
* trip-invite/trip-read-model/calendar is the precedent).
* trip-invite/trip-read-model/calendar is the precedent). An entry above what
* its file holds now, or for a file that is gone, fails as well until
* --update lowers it: otherwise the file could grow back unseen, and a new
* file at a deleted path would inherit its allowance.
*
* Lines are counted the way they read, not the way they are stored: a line
* longer than LINE_WIDTH (prettier's printWidth) counts once per LINE_WIDTH
@@ -111,11 +114,16 @@ export function lowerBaseline(baseline, counts) {
return Object.fromEntries(Object.entries(lowered).sort(([a], [b]) => a.localeCompare(b)));
}
/** Files over their allowance, and baseline entries the files have shrunk below. */
/**
* Files over their allowance, and the baseline entries that allow more than
* their file holds now (shrunk, back under the limit, or gone): exactly the
* entries lowerBaseline would lower or drop.
*/
export function compare(baseline, counts) {
const grown = Object.entries(counts).filter(([file, n]) => n > Math.max(baseline[file] ?? 0, LIMIT));
const lowerable = Object.entries(baseline).filter(([file, n]) => (counts[file] ?? 0) < n);
return { grown, lowerable };
const lowered = lowerBaseline(baseline, counts);
const stale = Object.entries(baseline).filter(([file, n]) => lowered[file] !== n);
return { grown, stale };
}
function main(argv) {
@@ -131,7 +139,7 @@ function main(argv) {
writeFileSync(baselinePath, JSON.stringify(baseline, null, 2) + '\n');
}
const { grown, lowerable } = compare(baseline, counts);
const { grown, stale } = compare(baseline, counts);
for (const [file, n] of grown) {
const entry = baseline[file];
console.error(
@@ -140,13 +148,22 @@ function main(argv) {
'Split a concern into a service, helper or module of its own instead of growing the file.',
);
}
if (lowerable.length && !update) {
console.log(`${lowerable.length} file(s) are shorter than their baseline now: run with --update to lower it.`);
for (const [file, entry] of stale) {
console.error(
`FAIL ${file} is held at ${entry} in scripts/size-baseline.json, ` +
(file in counts ? `but it has ${counts[file]} lines now.` : 'but the file is gone.'),
);
}
if (stale.length) {
console.error(
'Run npm run lint:size -- --update to lower the baseline with the change that made it smaller: ' +
'an entry above the file lets it grow back unseen.',
);
}
console.log(
`size: ${Object.keys(counts).length} file(s), ${Object.keys(baseline).length} over ${LIMIT} lines held at their baseline`,
);
return grown.length ? 1 : 0;
return grown.length || stale.length ? 1 : 0;
}
// Compared by real path, so the check still runs when the script is started through a symlink.