mirror of
https://github.com/tabler/tabler.git
synced 2026-10-13 21:57:00 +04:00
Add the check:compat gate against the last npm release (#3114)
This commit is contained in:
1 parent
7bfeb12ab7
commit
577f226635
6 files changed
+577
-25
No files matched your search
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: backward-compat
|
||||
description: >-
|
||||
Keep a change to `@tabler/core` backward compatible, so a patch or minor release never breaks a project that updates. Use before renaming, removing or changing the meaning of anything a user can touch — a class, a `--tblr-*` custom property, a Sass variable, mixin or function, a `data-*` attribute or option, a JS export, an event, a file under `dist/`, a default value — and whenever a changeset on `dev` looks like it needs a `major` bump. Also consult it proactively when a cleanup or refactor in `core/` deletes something, since "dead" code is often somebody's API. Covers what counts as public API, the deprecated-alias patterns, how to diff the public surface against the last release, and what to do when a break cannot be avoided.
|
||||
Keep a change to `@tabler/core` backward compatible, so a patch or minor release never breaks a project that updates. Use before renaming, removing or changing the meaning of anything a user can touch — a class, a `--tblr-*` custom property, a Sass variable, mixin or function, a `data-*` attribute or option, a JS export, an event, a file under `dist/`, a default value — and whenever a changeset on `dev` looks like it needs a `major` bump. Also consult it proactively when a cleanup or refactor in `core/` deletes something, since "dead" code is often somebody's API. Covers what counts as public API, the `deprecated` files where compatibility code lives, the alias patterns, the `check:compat` gate, and what to do when a break cannot be avoided.
|
||||
---
|
||||
|
||||
# Backward compatibility
|
||||
@@ -32,16 +32,37 @@ Not public: anything under `preview/`, `docs/`, `shared/`, `.build/`, file layou
|
||||
|
||||
Bug fixes are fine even when they change rendering, as long as the old behaviour was clearly wrong (a knob that did not move in RTL). When a fix changes how a correct page looks, treat it as a default change.
|
||||
|
||||
## 2. How to make the change compatible
|
||||
## 2. Where compatibility code lives
|
||||
|
||||
Everything kept only for old projects goes into files named `deprecated`, so that the next major removes it by deleting files instead of digging through the tree. The Sass module system decides how many files that is:
|
||||
|
||||
| File | Holds | Why it is separate |
|
||||
| --- | --- | --- |
|
||||
| `core/scss/_variables-deprecated.scss` | old `$variables`, each `!default` | `@use … with ($x: …)` on a variable that does not exist is a compile error, so the variable has to stay declared. Forwarded from `_config.scss` **before** `variables`, so a new variable can default to the old one: `$focus-ring-width: $input-btn-focus-width !default;` |
|
||||
| `core/scss/mixins/_deprecated.scss` | old functions and mixins as thin wrappers around the new ones | emits no CSS; forwarded from `_mixins.scss`. Each one calls `@include deprecate('<name>', '<since>', '<removed in>')` from `mixins/bootstrap/_deprecate.scss`, so the user gets a warning when compiling |
|
||||
| `core/scss/_deprecated.scss` | everything that emits CSS: class aliases (`.form-hint`, `.legend:empty`), old custom properties such as `--tblr-*-rgb`, hooks like `box-shadow: var(--btn-focus-box-shadow, none)` | loaded last from `tabler.scss`, next to `_extends.scss`: an alias built with `@extend` needs the whole `core` upstream. The whole file sits inside `@if $enable-deprecated { … }` |
|
||||
| `core/js/src/deprecated.ts` | old exports and globals, such as the `tabler` namespace with `getColor()` | one import to delete in the next major |
|
||||
|
||||
Create a file the first time you need it. `$enable-deprecated: true !default` lives in `_variables.scss`, next to `$enable-deprecation-messages`. A project that has migrated sets it to `false` and gets the bytes back; the next major flips the default, and the one after deletes the files.
|
||||
|
||||
Some compatibility cannot move into those files: a fallback inside a component rule (`mask-image: var(--form-check-bg-image, <default>)`), an old parameter in a mixin signature, a default value that was put back. Mark each of those where it is, with one fixed comment:
|
||||
|
||||
```scss
|
||||
// deprecated(2.0): `$show-border` is the old name of `$offset`
|
||||
```
|
||||
|
||||
`grep -rn "deprecated(2.0)" core/` plus the four files is then the full list of what the next major removes.
|
||||
|
||||
## 3. How to make the change compatible
|
||||
|
||||
### Rename
|
||||
|
||||
Ship both names. The old one becomes an alias that nobody has to touch.
|
||||
|
||||
- **Class**: add the alias to `core/scss/_extends.scss` with `@extend`, next to `.form-hint`. Start the comment with `` `<old>` is deprecated, use `<new>` instead ``.
|
||||
- **Class**: add the alias to `core/scss/_deprecated.scss` with `@extend`. Start the comment with `` `<old>` is deprecated, use `<new>` instead ``.
|
||||
- **Custom property**: keep reading the old name as a fallback — `var(--new, var(--old, <default>))` — or keep emitting the old one with the same value.
|
||||
- **Sass variable**: keep the old variable and default the new one to it: `$new: $old !default;`. For a map key, accept both keys.
|
||||
- **Mixin or function parameter**: add new parameters at the end, with defaults. Never rename a keyword argument; add a second one and map the old onto it.
|
||||
- **Sass variable**: keep the old variable in `_variables-deprecated.scss` and default the new one to it: `$new: $old !default;`. For a map key, accept both keys.
|
||||
- **Mixin or function**: a removed one comes back in `mixins/_deprecated.scss` as a wrapper. For parameters, add new ones at the end, with defaults. Never rename or move an old parameter: callers pass arguments by position and by keyword, so it keeps both its name and its place.
|
||||
- **Data attribute, option, JS export, event**: accept or fire both. The `data-bs-*` / `data-tblr-*` pair in `core/js/src/bootstrap/dom/manipulator.ts` is the pattern.
|
||||
|
||||
### Remove
|
||||
@@ -50,7 +71,7 @@ Do not. Mark it deprecated, stop using it in Tabler's own code, keep it working,
|
||||
|
||||
### Change what a name means
|
||||
|
||||
Do not reuse the name. `.legend` was a dot in 1.5 and became a whole legend item in 1.6, so old markup rendered as nothing. Either give the new thing a new name, or keep the old use working — `.legend:empty` in `_extends.scss` still draws the dot.
|
||||
Do not reuse the name. `.legend` was a dot in 1.5 and became a whole legend item in 1.6, so old markup rendered as nothing. Either give the new thing a new name, or keep the old use working — `.legend:empty` still draws the dot.
|
||||
|
||||
### Add a name
|
||||
|
||||
@@ -68,37 +89,41 @@ Keep the file in `dist/` and the entry in `core/libs.json` until the next major,
|
||||
|
||||
New validation must not throw on input that used to work. Coerce the value, or log with `console.warn` and fall back to the default. A throw during page load stops all of `tabler.js`.
|
||||
|
||||
## 3. Check the public surface before the PR
|
||||
## 4. Check the public surface before the PR
|
||||
|
||||
Build core, then diff the compiled CSS against the last release. Anything in the output is a removed class or custom property and needs an alias or a revert.
|
||||
`check:compat` downloads the last release of `@tabler/core` from npm and compares it with the working tree. It needs `core/dist`, so build core first:
|
||||
|
||||
```shell
|
||||
last=$(npm view @tabler/core version)
|
||||
curl -s "https://cdn.jsdelivr.net/npm/@tabler/core@$last/dist/css/tabler.css" -o /tmp/released.css
|
||||
|
||||
surface() { grep -oE "$1" "$2" | sort -u; }
|
||||
class='\.[a-zA-Z][a-zA-Z0-9_-]*'
|
||||
prop='--tblr-[a-zA-Z0-9_-]+'
|
||||
|
||||
# classes, then custom properties, that the release has and the build lost
|
||||
comm -23 <(surface "$class" /tmp/released.css) <(surface "$class" core/dist/css/tabler.css)
|
||||
comm -23 <(surface -e"$prop" /tmp/released.css) <(surface -e"$prop" core/dist/css/tabler.css)
|
||||
pnpm --filter @tabler/core build
|
||||
pnpm run check:compat
|
||||
```
|
||||
|
||||
For the rest, read the diff against the release tag with the table from section 1 in hand:
|
||||
It reports what the release has and the build lost, one key per line:
|
||||
|
||||
| Key | Means |
|
||||
| --- | --- |
|
||||
| `css:tabler.css:.legend` | a class is gone from that stylesheet |
|
||||
| `css:tabler.css:--tblr-primary-rgb` | a custom property is gone |
|
||||
| `sass:$focus-ring-blur` | a Sass variable is gone |
|
||||
| `sass:@function url-svg` | a function or mixin is gone |
|
||||
| `sass:@mixin focus-ring($show-border)` | that parameter was renamed or moved |
|
||||
| `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: <why>` only after the maintainers decided to ship that break. CI runs the check after the build, and with `--strict` on the versions pull request, so a release cannot be merged while an unaccepted line is left.
|
||||
|
||||
The check cannot see everything. It does not know about data attributes and their options, events, default values, a Sass variable whose type changed, or behaviour. For those, read the diff against the release tag with the table from section 1 in hand:
|
||||
|
||||
```shell
|
||||
git diff "@tabler/core@$last"..HEAD -- core/scss/_variables.scss core/scss/mixins core/js core/libs.json core/package.json .browserslistrc
|
||||
git diff "@tabler/core@$(npm view @tabler/core version)"..HEAD -- core/scss/_variables.scss core/js .browserslistrc
|
||||
```
|
||||
|
||||
Look for deleted `$variables`, changed mixin signatures, removed exports in `core/js/tabler.ts`, removed `libs.json` entries and new or removed dependencies.
|
||||
## 5. Changesets and the upgrade guide
|
||||
|
||||
## 4. Changesets and the upgrade guide
|
||||
|
||||
- A changeset on `dev` is `patch` or `minor`. If the honest bump is `major`, the change is on the wrong branch — go back to section 2.
|
||||
- A changeset on `dev` is `patch` or `minor`. If the honest bump is `major`, the change is on the wrong branch — go back to section 3.
|
||||
- Describe a deprecation as one, for example ``Added `.legend-dot`; an empty `.legend` still works but is deprecated.`` Never write `Removed` for something a user could have used.
|
||||
- In the upgrade guide a deprecation goes under a "renamed" heading with "the old name still works" (see the `upgrade-guide` skill). A minor release should have nothing under a breaking-change heading.
|
||||
|
||||
## 5. When a break cannot be avoided
|
||||
## 6. When a break cannot be avoided
|
||||
|
||||
Some changes have no alias: dropping a whole color model, raising the browser floor, removing a dependency users import. Those belong to the next major release, not to `dev`. Say so in the PR, and do not split the change to sneak half of it into a minor. If you are unsure whether something is a break, assume it is and ask.
|
||||
@@ -121,4 +121,5 @@ pnpm run bundlewatch # size budgets (tabler.css 80 kB, tab
|
||||
- [ ] No class, custom property, Sass variable or mixin parameter removed or renamed without an alias (`backward-compat`)
|
||||
- [ ] sass-true test for a mixin that can break silently
|
||||
- [ ] `lint:scss`, `check:tokens`, `lint:prettier`, `test:scss` clean
|
||||
- [ ] `check:compat` clean after a core build, with no new line in `.build/compat-baseline.txt`
|
||||
- [ ] Docs page and class table updated (`write-docs`, `class-reference`), changeset written
|
||||
@@ -0,0 +1,246 @@
|
||||
// Fails when the build drops something the last release shipped.
|
||||
//
|
||||
// The README promises semver: a project on `^1.0.0` updates without touching its
|
||||
// own code. A removed class, custom property, Sass variable or file breaks that
|
||||
// promise, and nothing else notices — the build is green, the tests pass, and
|
||||
// the first report comes from a user (#3108). This check downloads the last
|
||||
// release of `@tabler/core` from npm and compares its public surface with the
|
||||
// working tree:
|
||||
//
|
||||
// css classes and `--tblr-*` custom properties of every dist/css/*.css
|
||||
// sass `$variables`, and `@function` / `@mixin` names and parameters, in scss/
|
||||
// js top-level exports of dist/js/tabler.esm.js
|
||||
// file every path under dist/ (libs, images, types), and scss/ partials
|
||||
//
|
||||
// Known breaks are listed in compat-baseline.txt. A line ending in
|
||||
// `# accepted: <why>` is a decision; a line without it is a break still to fix.
|
||||
// The check fails on a break that is not listed, and on a listed one that no
|
||||
// longer breaks, so fixing one means deleting its line. `--strict` also fails
|
||||
// while any unaccepted line is left: the release runs it that way.
|
||||
//
|
||||
// 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 { execFileSync } from 'node:child_process'
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { join, relative } from 'node:path'
|
||||
|
||||
const pkgName = '@tabler/core'
|
||||
const coreDir = 'core'
|
||||
const baselineFile = '.build/compat-baseline.txt'
|
||||
const cacheDir = 'node_modules/.cache/check-compat'
|
||||
const strict = process.argv.includes('--strict')
|
||||
|
||||
const registry = 'https://registry.npmjs.org'
|
||||
|
||||
async function releasedDir(): Promise<{ dir: string; version: string }> {
|
||||
const local = JSON.parse(readFileSync(join(coreDir, 'package.json'), 'utf8')).version as string
|
||||
// On the versions PR the local version is not published yet: compare with `latest`.
|
||||
let version = local
|
||||
let response = await fetch(`${registry}/${pkgName}/${version}`)
|
||||
if (response.status === 404) {
|
||||
response = await fetch(`${registry}/${pkgName}/latest`)
|
||||
}
|
||||
if (!response.ok) throw new Error(`npm registry answered ${response.status} for ${pkgName}`)
|
||||
const manifest = (await response.json()) as { version: string; dist: { tarball: string } }
|
||||
version = manifest.version
|
||||
|
||||
const dir = join(cacheDir, version)
|
||||
if (!existsSync(join(dir, 'package', 'package.json'))) {
|
||||
mkdirSync(dir, { recursive: true })
|
||||
const tarball = await fetch(manifest.dist.tarball)
|
||||
if (!tarball.ok) throw new Error(`could not download ${manifest.dist.tarball}: ${tarball.status}`)
|
||||
const archive = join(dir, 'package.tgz')
|
||||
writeFileSync(archive, Buffer.from(await tarball.arrayBuffer()))
|
||||
execFileSync('tar', ['-xzf', archive, '-C', dir])
|
||||
}
|
||||
return { dir: join(dir, 'package'), version }
|
||||
}
|
||||
|
||||
function walk(root: string, dir = root): string[] {
|
||||
if (!existsSync(dir)) return []
|
||||
return readdirSync(dir).flatMap((name) => {
|
||||
const path = join(dir, name)
|
||||
return statSync(path).isDirectory() ? walk(root, path) : [relative(root, path)]
|
||||
})
|
||||
}
|
||||
|
||||
const matches = (source: string, pattern: RegExp): Set<string> => new Set([...source.matchAll(pattern)].map((match) => match[1]!))
|
||||
|
||||
// --- css -------------------------------------------------------------------
|
||||
|
||||
// A class is a dot followed by an identifier; `1.5rem` and `url(a.svg)` are not.
|
||||
const cssClass = /(?<![\w)\]"'-])\.(-?[a-zA-Z_][\w-]*)/g
|
||||
const cssProperty = /(--tblr-[\w-]+)/g
|
||||
|
||||
const stripCssNoise = (css: string): string => css.replace(/\/\*[\s\S]*?\*\//g, '').replace(/url\([^)]*\)/g, 'url()')
|
||||
|
||||
function cssBreaks(released: string, current: string): string[] {
|
||||
const breaks: string[] = []
|
||||
const sheets = walk(join(released, 'dist/css')).filter((file) => file.endsWith('.css') && !file.includes('.min.') && !file.includes('.rtl.'))
|
||||
|
||||
for (const sheet of sheets) {
|
||||
const currentPath = join(current, 'dist/css', sheet)
|
||||
if (!existsSync(currentPath)) continue // reported by fileBreaks
|
||||
const before = stripCssNoise(readFileSync(join(released, 'dist/css', sheet), 'utf8'))
|
||||
const after = stripCssNoise(readFileSync(currentPath, 'utf8'))
|
||||
|
||||
const classesAfter = matches(after, cssClass)
|
||||
for (const name of matches(before, cssClass)) {
|
||||
if (!classesAfter.has(name)) breaks.push(`css:${sheet}:.${name}`)
|
||||
}
|
||||
const propertiesAfter = matches(after, cssProperty)
|
||||
for (const name of matches(before, cssProperty)) {
|
||||
if (!propertiesAfter.has(name)) breaks.push(`css:${sheet}:${name}`)
|
||||
}
|
||||
}
|
||||
return breaks
|
||||
}
|
||||
|
||||
// --- sass ------------------------------------------------------------------
|
||||
|
||||
type Callable = { kind: string; name: string; params: string[] }
|
||||
|
||||
const sassVariable = /^\$([\w-]+)\s*:/gm
|
||||
const sassCallable = /^@(function|mixin)\s+([\w-]+)\s*(?:\(([\s\S]*?)\))?\s*\{/gm
|
||||
|
||||
function splitParams(source: string | undefined): string[] {
|
||||
if (!source) return []
|
||||
const params: string[] = []
|
||||
let depth = 0
|
||||
let current = ''
|
||||
for (const char of source) {
|
||||
if (char === '(') depth++
|
||||
if (char === ')') depth--
|
||||
if (char === ',' && depth === 0) {
|
||||
params.push(current)
|
||||
current = ''
|
||||
} else {
|
||||
current += char
|
||||
}
|
||||
}
|
||||
params.push(current)
|
||||
// `$color: rgba($white, 0.15)` → `$color`; `$args...` → `$args...`
|
||||
return params.map((param) => param.trim().split(':')[0]!.trim()).filter(Boolean)
|
||||
}
|
||||
|
||||
function sassSurface(root: string): { variables: Set<string>; callables: Map<string, Callable> } {
|
||||
const variables = new Set<string>()
|
||||
const callables = new Map<string, Callable>()
|
||||
for (const file of walk(join(root, 'scss'))) {
|
||||
if (!file.endsWith('.scss') || file.startsWith('tests/')) continue
|
||||
const source = readFileSync(join(root, 'scss', file), 'utf8').replace(/\/\/.*$/gm, '')
|
||||
for (const name of matches(source, sassVariable)) variables.add(name)
|
||||
for (const [, kind, name, params] of source.matchAll(sassCallable)) {
|
||||
if (name!.startsWith('_') || name!.startsWith('-')) continue // private by convention
|
||||
callables.set(`${kind} ${name}`, { kind: kind!, name: name!, params: splitParams(params) })
|
||||
}
|
||||
}
|
||||
return { variables, callables }
|
||||
}
|
||||
|
||||
function sassBreaks(released: string, current: string): string[] {
|
||||
const breaks: string[] = []
|
||||
const before = sassSurface(released)
|
||||
const after = sassSurface(current)
|
||||
|
||||
for (const name of before.variables) {
|
||||
if (!after.variables.has(name)) breaks.push(`sass:$${name}`)
|
||||
}
|
||||
for (const [key, callable] of before.callables) {
|
||||
const now = after.callables.get(key)
|
||||
if (!now) {
|
||||
breaks.push(`sass:@${callable.kind} ${callable.name}`)
|
||||
continue
|
||||
}
|
||||
// A caller passes arguments by position or by keyword: an old parameter has
|
||||
// to keep both its name and its place. New ones go after it.
|
||||
callable.params.forEach((param, index) => {
|
||||
if (now.params[index] !== param) breaks.push(`sass:@${callable.kind} ${callable.name}(${param})`)
|
||||
})
|
||||
}
|
||||
return breaks
|
||||
}
|
||||
|
||||
// --- js --------------------------------------------------------------------
|
||||
|
||||
function esmExports(file: string): Set<string> {
|
||||
if (!existsSync(file)) return new Set()
|
||||
const source = readFileSync(file, 'utf8')
|
||||
const names = new Set<string>()
|
||||
for (const [, list] of source.matchAll(/^export\s*\{([^}]*)\}/gm)) {
|
||||
for (const entry of list!.split(',')) {
|
||||
const name = entry
|
||||
.trim()
|
||||
.split(/\s+as\s+/)
|
||||
.pop()
|
||||
if (name) names.add(name)
|
||||
}
|
||||
}
|
||||
for (const name of matches(source, /^export\s+(?:const|let|var|function|class)\s+([\w$]+)/gm)) names.add(name)
|
||||
if (/^export\s+default\b/m.test(source)) names.add('default')
|
||||
return names
|
||||
}
|
||||
|
||||
function jsBreaks(released: string, current: string): string[] {
|
||||
const breaks: string[] = []
|
||||
for (const bundle of ['tabler.esm.js', 'tabler-theme.esm.js']) {
|
||||
const after = esmExports(join(current, 'dist/js', bundle))
|
||||
for (const name of esmExports(join(released, 'dist/js', bundle))) {
|
||||
if (!after.has(name)) breaks.push(`js:${bundle}:${name}`)
|
||||
}
|
||||
}
|
||||
return breaks
|
||||
}
|
||||
|
||||
// --- files -----------------------------------------------------------------
|
||||
|
||||
function fileBreaks(released: string, current: string): string[] {
|
||||
const now = new Set([...walk(join(current, 'dist')).map((file) => `dist/${file}`), ...walk(join(current, 'scss')).map((file) => `scss/${file}`)])
|
||||
const before = [...walk(join(released, 'dist')).map((file) => `dist/${file}`), ...walk(join(released, 'scss')).map((file) => `scss/${file}`)]
|
||||
return before.filter((file) => !file.endsWith('.map') && !file.startsWith('scss/tests/') && !now.has(file)).map((file) => `file:${file}`)
|
||||
}
|
||||
|
||||
// --- main ------------------------------------------------------------------
|
||||
|
||||
async function main() {
|
||||
if (!existsSync(join(coreDir, 'dist/css/tabler.css'))) {
|
||||
throw new Error('core/dist is missing — build core first: pnpm --filter @tabler/core build')
|
||||
}
|
||||
|
||||
const { dir: released, version } = await releasedDir()
|
||||
const breaks = new Set([...cssBreaks(released, coreDir), ...sassBreaks(released, coreDir), ...jsBreaks(released, coreDir), ...fileBreaks(released, coreDir)])
|
||||
|
||||
const baseline = new Map<string, boolean>() // key → accepted
|
||||
for (const line of readFileSync(baselineFile, 'utf8').split('\n')) {
|
||||
const [entry, comment = ''] = line.split(' # ')
|
||||
const key = entry!.trim()
|
||||
if (!key || key.startsWith('#')) continue
|
||||
baseline.set(key, comment.trim().startsWith('accepted:'))
|
||||
}
|
||||
|
||||
const added = [...breaks].filter((key) => !baseline.has(key)).sort()
|
||||
const fixed = [...baseline.keys()].filter((key) => !breaks.has(key))
|
||||
const todo = [...baseline].filter(([key, accepted]) => !accepted && breaks.has(key)).map(([key]) => key)
|
||||
|
||||
for (const key of added) console.error(`✗ ${key}`)
|
||||
if (added.length > 0) {
|
||||
console.error(`\n${added.length} new ${added.length === 1 ? 'break' : 'breaks'} against ${pkgName}@${version}. Keep the old name working as a deprecated alias — see the backward-compat skill.`)
|
||||
}
|
||||
if (fixed.length > 0) {
|
||||
console.error(`\nNo longer ${fixed.length === 1 ? 'a break' : 'breaks'} — delete from ${baselineFile}:\n${fixed.map((key) => ` ${key}`).join('\n')}`)
|
||||
}
|
||||
if (strict && todo.length > 0) {
|
||||
console.error(`\n--strict: ${todo.length} listed ${todo.length === 1 ? 'break is' : 'breaks are'} neither fixed nor accepted:\n${todo.map((key) => ` ${key}`).join('\n')}`)
|
||||
}
|
||||
if (added.length > 0 || fixed.length > 0 || (strict && todo.length > 0)) process.exit(1)
|
||||
|
||||
const accepted = baseline.size - todo.length
|
||||
console.log(`OK — nothing new is missing compared with ${pkgName}@${version} (${todo.length} known breaks still to fix, ${accepted} accepted, in ${baselineFile}).`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,271 @@
|
||||
# What the working tree no longer ships compared with the last release of
|
||||
# @tabler/core - see check-compat.ts. The check fails on any break that is not
|
||||
# listed here, and on a listed one that no longer breaks, so fixing one means
|
||||
# deleting its line.
|
||||
#
|
||||
# A plain line is a break that still has to be fixed before the release:
|
||||
# `check-compat --strict` fails while one is left. Add ` # accepted: <why>`
|
||||
# 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.
|
||||
#
|
||||
# Everything below entered `dev` between 1.5.1 and 1.6.0, before the check existed.
|
||||
|
||||
# RGB triplets of the colors, removed with the move to color-mix() (#3042)
|
||||
css:tabler-props.css:--tblr-azure-lt-rgb
|
||||
css:tabler-props.css:--tblr-azure-rgb
|
||||
css:tabler-props.css:--tblr-bitbucket-lt-rgb
|
||||
css:tabler-props.css:--tblr-bitbucket-rgb
|
||||
css:tabler-props.css:--tblr-blue-lt-rgb
|
||||
css:tabler-props.css:--tblr-blue-rgb
|
||||
css:tabler-props.css:--tblr-cyan-lt-rgb
|
||||
css:tabler-props.css:--tblr-cyan-rgb
|
||||
css:tabler-props.css:--tblr-danger-lt-rgb
|
||||
css:tabler-props.css:--tblr-danger-rgb
|
||||
css:tabler-props.css:--tblr-dark-lt-rgb
|
||||
css:tabler-props.css:--tblr-dark-rgb
|
||||
css:tabler-props.css:--tblr-dribbble-lt-rgb
|
||||
css:tabler-props.css:--tblr-dribbble-rgb
|
||||
css:tabler-props.css:--tblr-facebook-lt-rgb
|
||||
css:tabler-props.css:--tblr-facebook-rgb
|
||||
css:tabler-props.css:--tblr-flickr-lt-rgb
|
||||
css:tabler-props.css:--tblr-flickr-rgb
|
||||
css:tabler-props.css:--tblr-github-lt-rgb
|
||||
css:tabler-props.css:--tblr-github-rgb
|
||||
css:tabler-props.css:--tblr-google-lt-rgb
|
||||
css:tabler-props.css:--tblr-google-rgb
|
||||
css:tabler-props.css:--tblr-green-lt-rgb
|
||||
css:tabler-props.css:--tblr-green-rgb
|
||||
css:tabler-props.css:--tblr-indigo-lt-rgb
|
||||
css:tabler-props.css:--tblr-indigo-rgb
|
||||
css:tabler-props.css:--tblr-info-lt-rgb
|
||||
css:tabler-props.css:--tblr-info-rgb
|
||||
css:tabler-props.css:--tblr-instagram-lt-rgb
|
||||
css:tabler-props.css:--tblr-instagram-rgb
|
||||
css:tabler-props.css:--tblr-light-lt-rgb
|
||||
css:tabler-props.css:--tblr-light-rgb
|
||||
css:tabler-props.css:--tblr-lime-lt-rgb
|
||||
css:tabler-props.css:--tblr-lime-rgb
|
||||
css:tabler-props.css:--tblr-linkedin-lt-rgb
|
||||
css:tabler-props.css:--tblr-linkedin-rgb
|
||||
css:tabler-props.css:--tblr-muted-lt-rgb
|
||||
css:tabler-props.css:--tblr-muted-rgb
|
||||
css:tabler-props.css:--tblr-orange-lt-rgb
|
||||
css:tabler-props.css:--tblr-orange-rgb
|
||||
css:tabler-props.css:--tblr-pink-lt-rgb
|
||||
css:tabler-props.css:--tblr-pink-rgb
|
||||
css:tabler-props.css:--tblr-pinterest-lt-rgb
|
||||
css:tabler-props.css:--tblr-pinterest-rgb
|
||||
css:tabler-props.css:--tblr-primary-lt-rgb
|
||||
css:tabler-props.css:--tblr-primary-rgb
|
||||
css:tabler-props.css:--tblr-purple-lt-rgb
|
||||
css:tabler-props.css:--tblr-purple-rgb
|
||||
css:tabler-props.css:--tblr-red-lt-rgb
|
||||
css:tabler-props.css:--tblr-red-rgb
|
||||
css:tabler-props.css:--tblr-rss-lt-rgb
|
||||
css:tabler-props.css:--tblr-rss-rgb
|
||||
css:tabler-props.css:--tblr-secondary-lt-rgb
|
||||
css:tabler-props.css:--tblr-secondary-rgb
|
||||
css:tabler-props.css:--tblr-success-lt-rgb
|
||||
css:tabler-props.css:--tblr-success-rgb
|
||||
css:tabler-props.css:--tblr-tabler-lt-rgb
|
||||
css:tabler-props.css:--tblr-tabler-rgb
|
||||
css:tabler-props.css:--tblr-teal-lt-rgb
|
||||
css:tabler-props.css:--tblr-teal-rgb
|
||||
css:tabler-props.css:--tblr-twitter-lt-rgb
|
||||
css:tabler-props.css:--tblr-twitter-rgb
|
||||
css:tabler-props.css:--tblr-vimeo-lt-rgb
|
||||
css:tabler-props.css:--tblr-vimeo-rgb
|
||||
css:tabler-props.css:--tblr-vk-lt-rgb
|
||||
css:tabler-props.css:--tblr-vk-rgb
|
||||
css:tabler-props.css:--tblr-warning-lt-rgb
|
||||
css:tabler-props.css:--tblr-warning-rgb
|
||||
css:tabler-props.css:--tblr-x-lt-rgb
|
||||
css:tabler-props.css:--tblr-x-rgb
|
||||
css:tabler-props.css:--tblr-yellow-lt-rgb
|
||||
css:tabler-props.css:--tblr-yellow-rgb
|
||||
css:tabler-props.css:--tblr-youtube-lt-rgb
|
||||
css:tabler-props.css:--tblr-youtube-rgb
|
||||
css:tabler-themes.css:--tblr-primary-rgb
|
||||
css:tabler.css:--tblr-azure-lt-rgb
|
||||
css:tabler.css:--tblr-azure-rgb
|
||||
css:tabler.css:--tblr-bitbucket-lt-rgb
|
||||
css:tabler.css:--tblr-bitbucket-rgb
|
||||
css:tabler.css:--tblr-black-rgb
|
||||
css:tabler.css:--tblr-blue-lt-rgb
|
||||
css:tabler.css:--tblr-blue-rgb
|
||||
css:tabler.css:--tblr-body-bg-rgb
|
||||
css:tabler.css:--tblr-body-color-rgb
|
||||
css:tabler.css:--tblr-cyan-lt-rgb
|
||||
css:tabler.css:--tblr-cyan-rgb
|
||||
css:tabler.css:--tblr-danger-lt-rgb
|
||||
css:tabler.css:--tblr-danger-rgb
|
||||
css:tabler.css:--tblr-dark-lt-rgb
|
||||
css:tabler.css:--tblr-dark-rgb
|
||||
css:tabler.css:--tblr-dribbble-lt-rgb
|
||||
css:tabler.css:--tblr-dribbble-rgb
|
||||
css:tabler.css:--tblr-emphasis-color-rgb
|
||||
css:tabler.css:--tblr-facebook-lt-rgb
|
||||
css:tabler.css:--tblr-facebook-rgb
|
||||
css:tabler.css:--tblr-flickr-lt-rgb
|
||||
css:tabler.css:--tblr-flickr-rgb
|
||||
css:tabler.css:--tblr-github-lt-rgb
|
||||
css:tabler.css:--tblr-github-rgb
|
||||
css:tabler.css:--tblr-google-lt-rgb
|
||||
css:tabler.css:--tblr-google-rgb
|
||||
css:tabler.css:--tblr-green-lt-rgb
|
||||
css:tabler.css:--tblr-green-rgb
|
||||
css:tabler.css:--tblr-indigo-lt-rgb
|
||||
css:tabler.css:--tblr-indigo-rgb
|
||||
css:tabler.css:--tblr-info-lt-rgb
|
||||
css:tabler.css:--tblr-info-rgb
|
||||
css:tabler.css:--tblr-instagram-lt-rgb
|
||||
css:tabler.css:--tblr-instagram-rgb
|
||||
css:tabler.css:--tblr-light-lt-rgb
|
||||
css:tabler.css:--tblr-light-rgb
|
||||
css:tabler.css:--tblr-lime-lt-rgb
|
||||
css:tabler.css:--tblr-lime-rgb
|
||||
css:tabler.css:--tblr-link-color-rgb
|
||||
css:tabler.css:--tblr-link-hover-color-rgb
|
||||
css:tabler.css:--tblr-linkedin-lt-rgb
|
||||
css:tabler.css:--tblr-linkedin-rgb
|
||||
css:tabler.css:--tblr-muted-lt-rgb
|
||||
css:tabler.css:--tblr-muted-rgb
|
||||
css:tabler.css:--tblr-orange-lt-rgb
|
||||
css:tabler.css:--tblr-orange-rgb
|
||||
css:tabler.css:--tblr-pink-lt-rgb
|
||||
css:tabler.css:--tblr-pink-rgb
|
||||
css:tabler.css:--tblr-pinterest-lt-rgb
|
||||
css:tabler.css:--tblr-pinterest-rgb
|
||||
css:tabler.css:--tblr-primary-lt-rgb
|
||||
css:tabler.css:--tblr-primary-rgb
|
||||
css:tabler.css:--tblr-purple-lt-rgb
|
||||
css:tabler.css:--tblr-purple-rgb
|
||||
css:tabler.css:--tblr-red-lt-rgb
|
||||
css:tabler.css:--tblr-red-rgb
|
||||
css:tabler.css:--tblr-rss-lt-rgb
|
||||
css:tabler.css:--tblr-rss-rgb
|
||||
css:tabler.css:--tblr-secondary-bg-rgb
|
||||
css:tabler.css:--tblr-secondary-color-rgb
|
||||
css:tabler.css:--tblr-secondary-lt-rgb
|
||||
css:tabler.css:--tblr-secondary-rgb
|
||||
css:tabler.css:--tblr-status-color-rgb
|
||||
css:tabler.css:--tblr-success-lt-rgb
|
||||
css:tabler.css:--tblr-success-rgb
|
||||
css:tabler.css:--tblr-tabler-lt-rgb
|
||||
css:tabler.css:--tblr-tabler-rgb
|
||||
css:tabler.css:--tblr-teal-lt-rgb
|
||||
css:tabler.css:--tblr-teal-rgb
|
||||
css:tabler.css:--tblr-tertiary-bg-rgb
|
||||
css:tabler.css:--tblr-tertiary-color-rgb
|
||||
css:tabler.css:--tblr-twitter-lt-rgb
|
||||
css:tabler.css:--tblr-twitter-rgb
|
||||
css:tabler.css:--tblr-vimeo-lt-rgb
|
||||
css:tabler.css:--tblr-vimeo-rgb
|
||||
css:tabler.css:--tblr-vk-lt-rgb
|
||||
css:tabler.css:--tblr-vk-rgb
|
||||
css:tabler.css:--tblr-warning-lt-rgb
|
||||
css:tabler.css:--tblr-warning-rgb
|
||||
css:tabler.css:--tblr-white-rgb
|
||||
css:tabler.css:--tblr-x-lt-rgb
|
||||
css:tabler.css:--tblr-x-rgb
|
||||
css:tabler.css:--tblr-yellow-lt-rgb
|
||||
css:tabler.css:--tblr-yellow-rgb
|
||||
css:tabler.css:--tblr-youtube-lt-rgb
|
||||
css:tabler.css:--tblr-youtube-rgb
|
||||
|
||||
# Other custom properties: focus box-shadow tokens (#3098), form hint (#3059), masked icons (#3043)
|
||||
css:tabler-props.css:--tblr-bg-surface-dark
|
||||
css:tabler.css:--tblr-accordion-btn-focus-box-shadow
|
||||
css:tabler.css:--tblr-btn-close-focus-shadow
|
||||
css:tabler.css:--tblr-btn-focus-box-shadow
|
||||
css:tabler.css:--tblr-focus-ring-blur
|
||||
css:tabler.css:--tblr-focus-ring-x
|
||||
css:tabler.css:--tblr-focus-ring-y
|
||||
css:tabler.css:--tblr-form-check-bg-image
|
||||
css:tabler.css:--tblr-form-hint-control-margin-top
|
||||
css:tabler.css:--tblr-form-hint-label-offset
|
||||
css:tabler.css:--tblr-form-hint-margin-top
|
||||
css:tabler.css:--tblr-form-switch-bg
|
||||
css:tabler.css:--tblr-pagination-focus-box-shadow
|
||||
|
||||
# tabler-vendors.css: Litepicker styles (#3062) and overrides for classes of third-party libraries
|
||||
css:tabler-vendors.css:--tblr-bg-forms
|
||||
css:tabler-vendors.css:--tblr-info
|
||||
css:tabler-vendors.css:--tblr-light
|
||||
css:tabler-vendors.css:.apexcharts-yaxis-tick
|
||||
css:tabler-vendors.css:.button-next-month
|
||||
css:tabler-vendors.css:.button-previous-month
|
||||
css:tabler-vendors.css:.container__days
|
||||
css:tabler-vendors.css:.container__main
|
||||
css:tabler-vendors.css:.container__months
|
||||
css:tabler-vendors.css:.datepicker-inline
|
||||
css:tabler-vendors.css:.day-item
|
||||
css:tabler-vendors.css:.fslightbox-slash
|
||||
css:tabler-vendors.css:.fslightbox-slide-number-container
|
||||
css:tabler-vendors.css:.jvm-legend-inner
|
||||
css:tabler-vendors.css:.litepicker
|
||||
css:tabler-vendors.css:.month-item-name
|
||||
css:tabler-vendors.css:.month-item-weekdays-row
|
||||
css:tabler-vendors.css:.month-item-year
|
||||
css:tabler-vendors.css:.ts-input
|
||||
|
||||
# Sass variables
|
||||
sass:$accordion-btn-focus-box-shadow
|
||||
sass:$btn-close-focus-shadow
|
||||
sass:$btn-focus-box-shadow
|
||||
sass:$focus-ring-blur
|
||||
sass:$focus-ring-box-shadow
|
||||
sass:$form-check-input-checked-bg-image-dark
|
||||
sass:$form-check-input-focus-box-shadow
|
||||
sass:$form-hint-control-margin-top
|
||||
sass:$form-hint-label-offset
|
||||
sass:$form-hint-margin-top
|
||||
sass:$form-range-thumb-focus-box-shadow
|
||||
sass:$form-select-focus-box-shadow
|
||||
sass:$form-select-focus-width
|
||||
sass:$form-switch-bg-image
|
||||
sass:$form-switch-bg-image-dark
|
||||
sass:$form-switch-checked-bg-image
|
||||
sass:$form-switch-checked-bg-position
|
||||
sass:$form-switch-color-dark
|
||||
sass:$form-switch-focus-bg-image
|
||||
sass:$input-btn-focus-blur
|
||||
sass:$input-btn-focus-box-shadow
|
||||
sass:$input-btn-focus-color
|
||||
sass:$input-btn-focus-color-opacity
|
||||
sass:$input-btn-focus-width
|
||||
sass:$input-focus-box-shadow
|
||||
sass:$input-focus-width
|
||||
sass:$nav-link-focus-box-shadow
|
||||
sass:$navbar-dark-toggler-icon-bg
|
||||
sass:$navbar-light-toggler-icon-bg
|
||||
sass:$pagination-focus-box-shadow
|
||||
sass:$pagination-focus-outline
|
||||
sass:$theme-colors-rgb
|
||||
|
||||
# Sass functions and mixins; `name($param)` means that parameter moved or was renamed
|
||||
sass:@function breakpoint-prev
|
||||
sass:@function map-get-multiple
|
||||
sass:@function map-merge-multiple
|
||||
sass:@function theme-color-darker
|
||||
sass:@function theme-color-lighter
|
||||
sass:@function url-svg
|
||||
sass:@function varify
|
||||
sass:@mixin focus-ring($show-border)
|
||||
sass:@mixin form-validation-state($focus-box-shadow)
|
||||
sass:@mixin media-breakpoint-down-than
|
||||
|
||||
# JavaScript exports: the `tabler` namespace with getColor(), hexToRgba() and prefix
|
||||
js:tabler.esm.js:tabler
|
||||
|
||||
# Files in the package
|
||||
file:dist/libs/autosize/LICENSE.md
|
||||
file:dist/libs/autosize/dist/autosize.min.js
|
||||
file:dist/libs/clipboard/LICENSE
|
||||
file:dist/libs/clipboard/dist/clipboard.min.js
|
||||
file:dist/libs/countup.js/LICENSE.md
|
||||
file:dist/libs/countup.js/dist/countUp.umd.js
|
||||
file:dist/libs/litepicker/LICENSE.md
|
||||
file:dist/libs/litepicker/dist/litepicker.js
|
||||
file:dist/types/src/tabler.d.ts
|
||||
file:scss/vendor/_litepicker.scss
|
||||
@@ -22,3 +22,11 @@ jobs:
|
||||
# turbo directly: `pnpm run build` also zips the package, which only the release needs
|
||||
- name: Build
|
||||
run: pnpm exec turbo build
|
||||
|
||||
# Nothing the last release shipped may disappear in a minor release. The
|
||||
# versions pull request runs it with --strict, so a release cannot be
|
||||
# merged while compat-baseline.txt still lists a break nobody accepted.
|
||||
- name: Check backward compatibility
|
||||
env:
|
||||
STRICT: ${{ github.head_ref == 'changeset-release/dev' && '--strict' || '' }}
|
||||
run: pnpm run check:compat $STRICT
|
||||
@@ -28,6 +28,7 @@
|
||||
"check:sri": "tsx .build/generate-sri.ts check",
|
||||
"check:docs-links": "tsx .build/check-docs-links.ts",
|
||||
"check:css-vars": "tsx .build/check-css-vars.ts",
|
||||
"check:compat": "tsx .build/check-compat.ts",
|
||||
"check:open-source": "tsx .build/check-open-source.ts",
|
||||
"check:html": "tsx .build/check-html.ts",
|
||||
"check:markup-classes": "tsx .build/check-markup-classes.ts",
|
||||
|
||||
Reference in new issue
Block a user