From 3460091414e867ecc2fb6504fd2cc2c7e52e642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Fri, 25 Sep 2026 11:22:46 +0200 Subject: [PATCH] Stop the compat check from failing after every release (#3189) --- .agents/skills/backward-compat/SKILL.md | 2 +- .build/check-compat.ts | 29 ++++++++++++++++++++++--- .build/compat-baseline.txt | 6 +++-- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/.agents/skills/backward-compat/SKILL.md b/.agents/skills/backward-compat/SKILL.md index a3fc7ba39..bfdecefdb 100644 --- a/.agents/skills/backward-compat/SKILL.md +++ b/.agents/skills/backward-compat/SKILL.md @@ -125,7 +125,7 @@ It reports what the release has and the build lost, one key per line: | `js:tabler.esm.js:tabler` | a top-level export is gone | | `file:dist/libs/litepicker/dist/litepicker.js` | a file is gone from the package | -Fix each one with the patterns from section 3, then run it again. Known breaks are listed in `.build/compat-baseline.txt`; when you fix one, delete its line, or the check fails on the stale entry. Never add a line to the baseline to make a PR green. A line gets ` # accepted: ` only after the maintainers decided to ship that break. CI runs the check after the build. The Release workflow runs it with `--strict` on every push to `dev`, before it opens the versions pull request or publishes, so nothing reaches npm while an unaccepted line is left. +Fix each one with the patterns from section 3, then run it again. Known breaks are listed in `.build/compat-baseline.txt`; when you fix one, delete its line, or the check fails on the stale entry. Never add a line to the baseline to make a PR green. A line gets ` # accepted: ` only after the maintainers decided to ship that break. The file's `# against: ` line names the release the list was written for. Once a newer release is on npm, every listed break shipped in it, so the check ignores the list and only warns; run `pnpm run check:compat --reset` and commit the emptied file. CI runs the check after the build. The Release workflow runs it with `--strict` on every push to `dev`, before it opens the versions pull request or publishes, so nothing reaches npm while an unaccepted line is left. `check:compat` compares names. `check:compat-fixture` covers behaviour: `.build/compat-fixture/` is a small project written the way a user of the last release would write it (`@use … with` on old variables, old functions, `rgba(var(--tblr-primary-rgb), .5)`, an empty `.legend`, the libraries in `dist/libs`, `tabler.tabler.getColor()`), and the check runs it against the release and against the working tree. Every assertion has to give the same answer on both. diff --git a/.build/check-compat.ts b/.build/check-compat.ts index 496f123e8..ae3efa014 100644 --- a/.build/check-compat.ts +++ b/.build/check-compat.ts @@ -20,11 +20,16 @@ // longer breaks, so fixing one means deleting its line. `--strict` also fails // while any unaccepted line is left: the release runs it that way. // +// The baseline names the release it was written against (`# against: 1.6.0`). +// Once a newer release is on npm, every listed break shipped in it, so none of +// them can break against it: the check ignores the stale list and only warns. +// `--reset` empties the list and points it at the current release. +// // Needs `core/dist`, so run it after the core build. See the `backward-compat` // skill for how to fix what it reports. // -// Usage: tsx .build/check-compat.ts [--strict] -import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs' +// Usage: tsx .build/check-compat.ts [--strict | --reset] +import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs' import { join, relative } from 'node:path' import { compileString } from 'sass' import { pkgName, releasedPackage } from './released-package' @@ -32,6 +37,8 @@ import { pkgName, releasedPackage } from './released-package' const coreDir = 'core' const baselineFile = '.build/compat-baseline.txt' const strict = process.argv.includes('--strict') +const reset = process.argv.includes('--reset') +const againstPattern = /^#\s*against:\s*(\S+)\s*$/ function walk(root: string, dir = root): string[] { if (!existsSync(dir)) return [] @@ -210,13 +217,29 @@ async function main() { const { dir: released, version } = await releasedPackage() const breaks = new Set([...cssBreaks(released, coreDir), ...sassBreaks(released, coreDir), ...sassTypeBreaks(released, coreDir), ...jsBreaks(released, coreDir), ...fileBreaks(released, coreDir)]) + const lines = readFileSync(baselineFile, 'utf8').split('\n') + const againstIndex = lines.findIndex((line) => againstPattern.test(line)) + if (againstIndex === -1) throw new Error(`${baselineFile} has no \`# against: \` line`) + const against = againstPattern.exec(lines[againstIndex]!)![1]! + + if (reset) { + writeFileSync(baselineFile, [...lines.slice(0, againstIndex), `# against: ${version}`, ''].join('\n')) + console.log(`${baselineFile} emptied and pointed at ${pkgName}@${version}.`) + return + } + + // Written against an older release: every listed break shipped in the current one. + const stale = against !== version const baseline = new Map() // key → accepted - for (const line of readFileSync(baselineFile, 'utf8').split('\n')) { + for (const line of stale ? [] : lines) { const [entry, comment = ''] = line.split(' # ') const key = entry!.trim() if (!key || key.startsWith('#')) continue baseline.set(key, comment.trim().startsWith('accepted:')) } + if (stale) { + console.warn(`${baselineFile} was written against ${pkgName}@${against}, npm has ${version}: its entries no longer apply. Run \`pnpm run check:compat --reset\` and commit the file.\n`) + } const added = [...breaks].filter((key) => !baseline.has(key)).sort() const fixed = [...baseline.keys()].filter((key) => !breaks.has(key)) diff --git a/.build/compat-baseline.txt b/.build/compat-baseline.txt index 89fd09a17..d954ebfe5 100644 --- a/.build/compat-baseline.txt +++ b/.build/compat-baseline.txt @@ -8,5 +8,7 @@ # to a line only after the maintainers decided to ship the break, for example # an override for a class that a third-party library dropped. # -# Empty since 1.6.0 shipped: the breaks accepted for 1.6 are part of the release -# the check now compares with. +# The line below names the release this list is written against. Once a newer +# release is on npm the list no longer applies: the check only warns, and +# `pnpm run check:compat --reset` empties it and moves the line to that release. +# against: 1.6.0