Drop the backward-compat gates on v2-dev and fix lint and bundlewatch

Remove check:compat and check:compat-fixture with their files and workflow
steps, since 2.0 breaks the 1.x API on purpose. Format the mixins test and
raise the tabler-props budget to 3.8 kB.
This commit is contained in:
Bartek committed 2026-10-02 12:28:02 +02:00
1 parent 3e00ae75b9
commit ad80ded69d
13 files changed
+2 -612

No files matched your search

-172
View File
@@ -1,172 +0,0 @@
// Runs a project written for the last release against that release and against
// the working tree, and fails when the two disagree.
//
// `check-compat` compares names: it knows that `--tblr-primary-rgb` still
// exists, not that `rgba(var(--tblr-primary-rgb), 0.5)` still paints. This check
// covers behaviour: the project in `.build/compat-fixture/` compiles its Sass
// with 1.5 variables and helpers, and its page uses 1.5 markup, the libraries in
// `dist/libs` and the old JavaScript helpers. Every assertion has to give the
// same answer on both packages.
//
// The release is checked too, on purpose: an assertion that fails there is a
// mistake in the fixture, not a break, and would otherwise hide one.
//
// Needs `core/dist`, so run it after the core build.
//
// Usage: tsx .build/check-compat-fixture.ts [--package <dir>]
//
// `--package` points at another unpacked or built `@tabler/core` instead of
// `core/`, for example an older checkout. Without a `dist/` in it only the Sass
// half runs.
import { createReadStream, existsSync, statSync } from 'node:fs'
import { createServer, type Server } from 'node:http'
import { extname, join, resolve } from 'node:path'
import { chromium, type Page } from 'playwright'
import { compile as compileSass } from 'sass'
import { pkgName, releasedPackage } from './released-package'
const fixtureDir = '.build/compat-fixture'
const packageArg = process.argv.indexOf('--package')
const coreDir = packageArg > -1 ? process.argv[packageArg + 1] : 'core'
const MIME: Record<string, string> = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.svg': 'image/svg+xml', '.map': 'application/json' }
type Result = Record<string, string>
// --- sass ------------------------------------------------------------------
function sassResults(pkg: string): Result {
const results: Result = {}
try {
const { css } = compileSass(join(fixtureDir, 'project.scss'), { loadPaths: [pkg, 'node_modules'], style: 'expanded', quietDeps: true, logger: { warn() {}, debug() {} } })
results['sass: the project compiles'] = 'yes'
const fixture = /\.fixture \{([^}]*)\}/.exec(css)?.[1] ?? ''
const value = (name: string): string => new RegExp(`${name}:\\s*([^;]+);`).exec(fixture)?.[1]?.trim() ?? '(missing)'
results['sass: $input-focus-width follows $input-btn-focus-width'] = value('--fixture-width')
results['sass: $theme-colors-rgb has the primary colour'] = value('--fixture-has-rgb-map')
results['sass: theme-color-darker() returns a colour'] = /^(#|rgb|oklch|color)/.test(value('color')) ? 'yes' : value('color')
results['sass: theme-color-lighter() takes the $primary variable'] = /^(#|rgb|oklch|color)/.test(value('border-color')) ? 'yes' : value('border-color')
results['sass: url-svg() builds a data uri'] = value('background-image').startsWith('url(') ? 'yes' : value('background-image')
results['sass: varify() wraps names in var()'] = value('transition-property')
results['sass: media-breakpoint-down-than(md) emits a media query'] = /@media \(max-width: 575\.98px\)\s*\{\s*\.fixture/.test(css) ? 'yes' : 'no'
results['sass: focus-ring($show-border: true) emits a focus style'] = /\.fixture:focus \{[^}]*(outline|box-shadow)/.test(css) ? 'yes' : 'no'
} catch (error) {
results['sass: the project compiles'] = `no — ${String((error as Error).message).split('\n')[0]}`
}
return results
}
// --- page ------------------------------------------------------------------
function serve(pkg: string): Promise<Server> {
const server = createServer((request, response) => {
const path = decodeURIComponent((request.url ?? '/').split('?')[0])
const file = path === '/' ? resolve(fixtureDir, 'page.html') : resolve(pkg, `.${path}`)
if (!existsSync(file) || !statSync(file).isFile()) {
response.writeHead(404).end()
return
}
response.writeHead(200, { 'content-type': MIME[extname(file)] ?? 'application/octet-stream' })
createReadStream(file).pipe(response)
})
return new Promise((done) => server.listen(0, '127.0.0.1', () => done(server)))
}
async function pageResults(pkg: string, page: Page): Promise<Result> {
const results: Result = {}
const problems: string[] = []
page.on('pageerror', (error) => problems.push(error.message))
page.on('response', (response) => {
if (response.status() >= 400) problems.push(`${response.status()} ${new URL(response.url()).pathname}`)
})
const server = await serve(pkg)
const { port } = server.address() as { port: number }
await page.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'networkidle' })
await page.addStyleTag({ content: '*, *::before, *::after { transition: none !important; }' })
const style = (selector: string, property: string): Promise<string> => page.evaluate(([s, p]) => getComputedStyle(document.querySelector(s)!).getPropertyValue(p), [selector, property])
results['page: no script error and no 404'] = problems.length === 0 ? 'yes' : problems.join('; ')
results['page: rgba(var(--tblr-primary-rgb), .5) paints'] = await style('#highlight', 'background-color')
results['page: an empty .legend is a visible dot'] = (await page.evaluate(() => document.querySelector('#legend')!.getBoundingClientRect().width)) > 0 ? 'yes' : 'no'
results['page: .form-hint is a block'] = await style('#hint', 'display')
results['page: .form-hint sits below the control'] = (await page.evaluate(() => document.querySelector('#hint')!.getBoundingClientRect().top >= document.querySelector('#email')!.getBoundingClientRect().bottom)) ? 'yes' : 'no'
await page.focus('#button')
await page.keyboard.press('Shift+Tab')
await page.keyboard.press('Tab')
results['page: --tblr-btn-focus-box-shadow is drawn on focus'] = (await style('#button', 'box-shadow')).includes('rgb(255, 0, 0)') ? 'yes' : await style('#button', 'box-shadow')
results['page: window.tabler.Modal exists'] = await page.evaluate(() => typeof (window as any).tabler?.Modal)
results['page: tabler.tabler.prefix'] = await page.evaluate(() => String((window as any).fixture?.prefix))
results['page: tabler.tabler.getColor() returns a colour'] = (await page.evaluate(() => Boolean((window as any).fixture?.color))) ? 'yes' : 'no'
results['page: tabler.tabler.getColor(name, 0.5) returns a colour'] = (await page.evaluate(() => Boolean((window as any).fixture?.colorHalf))) ? 'yes' : 'no'
for (const name of ['autosize', 'countUp', 'ClipboardJS', 'Litepicker']) {
results[`page: dist/libs provides window.${name}`] = await page.evaluate((n) => typeof (window as any)[n] !== 'undefined', name).then((ok) => (ok ? 'yes' : 'no'))
}
results['page: Litepicker renders with Tabler styles'] = (await page.evaluate(() => {
const picker = document.querySelector('.litepicker')
return picker ? getComputedStyle(picker).getPropertyValue('--litepicker-day-color').trim() !== '' : false
}))
? 'yes'
: 'no'
await page.fill('#message', 'one\ntwo\nthree\nfour\nfive\nsix')
await page.dispatchEvent('#message', 'input')
results['page: data-bs-toggle="autosize" grows the textarea'] = (await page.evaluate(() => (document.querySelector('#message') as HTMLElement).offsetHeight)) > 60 ? 'yes' : 'no'
await page.waitForTimeout(600)
results['page: data-countup ends on the number'] = (await page.textContent('#total'))?.replace(/\D/g, '') ?? ''
await page.click('#open-modal')
await page.waitForTimeout(100)
results['page: data-bs-toggle="modal" opens the modal'] = (await page.evaluate(() => document.querySelector('#modal')!.classList.contains('show'))) ? 'yes' : 'no'
server.close()
return results
}
// --- main ------------------------------------------------------------------
async function main() {
const hasDist = existsSync(join(coreDir, 'dist/css/tabler.css'))
if (!hasDist && packageArg === -1) {
throw new Error('core/dist is missing — build core first: pnpm --filter @tabler/core build')
}
const { dir: released, version } = await releasedPackage()
const browser = await chromium.launch()
const run = async (pkg: string): Promise<Result> => {
const page = await browser.newPage()
const results = { ...sassResults(pkg), ...(hasDist ? await pageResults(pkg, page) : {}) }
await page.close()
return results
}
const before = await run(released)
const after = await run(coreDir)
await browser.close()
const failed: string[] = []
for (const [name, expected] of Object.entries(before)) {
const actual = after[name] ?? '(not run)'
const ok = actual === expected
console.log(`${ok ? '✓' : '✗'} ${name}${ok ? '' : `\n ${pkgName}@${version}: ${expected}\n working tree: ${actual}`}`)
if (!ok) failed.push(name)
}
// The fixture is only worth something if the release passes it.
const broken = Object.entries(before).filter(([, value]) => value.startsWith('no') || value === '(missing)' || value === 'undefined')
for (const [name, value] of broken) console.error(`\n! ${name} fails on ${pkgName}@${version} itself (${value}) — fix the fixture.`)
if (failed.length > 0) console.error(`\n${failed.length} of ${Object.keys(before).length} checks behave differently from ${pkgName}@${version}. See the backward-compat skill.`)
if (failed.length > 0 || broken.length > 0) process.exit(1)
console.log(`\nOK — a ${version} project compiles and behaves the same on the working tree (${Object.keys(before).length} checks).`)
}
main().catch((error) => {
console.error(error)
process.exit(1)
})
-267
View File
@@ -1,267 +0,0 @@
// 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 custom properties of every dist/css/*.css
// sass `$variables`, and `@function` / `@mixin` names and parameters, in scss/
// sass-type a variable that was a Sass colour and is now a string: `darken($x, 5%)`
// in a project stops compiling, and no alias can fix a type
// 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.
//
// 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 | --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'
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 []
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
// Every custom property, not only `--tblr-*`: a vendor name that gained the
// prefix (`--litepicker-*`) is as broken as one that disappeared.
const cssProperty = /(?<![\w-])(--[a-zA-Z][\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
}
// --- sass types ------------------------------------------------------------
// The type of every variable the config module exposes, read by Sass itself.
function sassTypes(root: string): Map<string, string> {
const source = `
@use 'sass:meta';
@use 'scss/config' as config;
a {
@each $name, $value in meta.module-variables('config') {
--#{$name}: #{meta.type-of($value)};
}
}`
const { css } = compileString(source, { loadPaths: [root, 'node_modules'], logger: { warn() {}, debug() {} } })
return new Map([...css.matchAll(/--([\w-]+): (\w+);/g)].map((match) => [match[1], match[2]]))
}
function sassTypeBreaks(released: string, current: string): string[] {
const after = sassTypes(current)
return [...sassTypes(released)].filter(([name, type]) => type === 'color' && after.has(name) && after.get(name) !== 'color').map(([name]) => `sass-type:$${name}`)
}
// --- 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 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: <version>\` 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<string, boolean>() // key → accepted
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))
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)
})
-14
View File
@@ -1,14 +0,0 @@
# 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.
#
# 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
-14
View File
@@ -1,14 +0,0 @@
# Compatibility fixture
A small project written the way a Tabler **1.5** user would write it. It must
compile and behave the same on the last release from npm and on the working
tree. `pnpm run check:compat-fixture` runs it against both.
- `project.scss` configures and reads 1.5 Sass variables, and calls 1.5
functions and mixins.
- `page.html` loads the compiled files from `dist/` with 1.5 markup, the
libraries from `dist/libs`, and the old JavaScript helpers.
Do not update this project when Tabler changes. When a check fails, fix Tabler:
see the `backward-compat` skill. Add to it only when a new kind of break shows
that it missed something a real project does.
-51
View File
@@ -1,51 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Tabler 1.5 project</title>
<link rel="stylesheet" href="/dist/css/tabler.css" />
<link rel="stylesheet" href="/dist/css/tabler-vendors.css" />
<style>
.my-highlight {
background-color: rgba(var(--tblr-primary-rgb), 0.5);
}
.my-button {
--tblr-btn-focus-box-shadow: 0 0 0 4px rgb(255, 0, 0);
}
</style>
</head>
<body>
<div class="page">
<div id="highlight" class="my-highlight">rgba() from the RGB triplet</div>
<span id="legend" class="legend bg-primary"></span>
<label class="form-label" for="email">Email</label>
<input id="email" class="form-control" />
<small id="hint" class="form-hint">We'll never share your email.</small>
<button id="button" class="btn my-button" type="button">Focus me</button>
<textarea id="message" class="form-control" data-bs-toggle="autosize" rows="1"></textarea>
<h1 id="total" data-countup='{"duration":"0.2","enableScrollSpy":false}'>1500</h1>
<input id="api-key" value="secret" />
<button id="copy" class="btn" type="button" data-clipboard-target="#api-key">Copy</button>
<input id="date" class="form-control" />
<button id="open-modal" class="btn" type="button" data-bs-toggle="modal" data-bs-target="#modal">Open</button>
<div class="modal" id="modal" tabindex="-1">
<div class="modal-dialog"><div class="modal-content"><div class="modal-body">Hello</div></div></div>
</div>
</div>
<script src="/dist/libs/autosize/dist/autosize.min.js"></script>
<script src="/dist/libs/countup.js/dist/countUp.umd.js"></script>
<script src="/dist/libs/clipboard/dist/clipboard.min.js"></script>
<script src="/dist/libs/litepicker/dist/litepicker.js"></script>
<script src="/dist/js/tabler.js"></script>
<script>
window.fixture = {
clipboard: new ClipboardJS('#copy'),
picker: new Litepicker({ element: document.getElementById('date'), inlineMode: true }),
color: tabler.tabler.getColor('primary'),
colorHalf: tabler.tabler.getColor('primary', 0.5),
prefix: tabler.tabler.prefix,
}
</script>
</body>
</html>
-31
View File
@@ -1,31 +0,0 @@
// A 1.5 project that compiles Tabler from source. Every variable configured
// here existed in 1.5; configuring one that is gone is a Sass compile error.
@use 'scss/tabler' as tabler with (
$input-btn-focus-width: 0.125rem,
$input-btn-focus-color-opacity: 0.4,
$btn-focus-box-shadow: 0 0 0 3px red,
$form-hint-margin-top: 0.5rem,
$form-switch-checked-bg-position: right center,
$pagination-focus-outline: 0,
$enable-deprecation-messages: false
);
// Reading old variables and calling old helpers, as a project's own partials do.
.fixture {
--fixture-width: #{tabler.$input-focus-width};
--fixture-primary-rgb: #{tabler.to-rgb(tabler.$primary)};
--fixture-has-rgb-map: #{map-has-key(tabler.$theme-colors-rgb, 'primary')};
color: tabler.theme-color-darker(#066fd1);
background: tabler.theme-color-lighter(#066fd1);
border-color: tabler.theme-color-lighter(tabler.$primary);
background-image: tabler.url-svg('<svg viewBox="0 0 1 1"/>');
transition-property: tabler.varify((a, b));
&:focus {
@include tabler.focus-ring($show-border: true);
}
@include tabler.media-breakpoint-down-than(md) {
display: none;
}
}
-36
View File
@@ -1,36 +0,0 @@
// Downloads the last release of `@tabler/core` from npm, once, and returns where
// it was unpacked. `check-compat` compares its public surface with the working
// tree; `check-compat-fixture` runs a 1.5-style project against both.
import { execFileSync } from 'node:child_process'
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
export const pkgName = '@tabler/core'
const coreDir = 'core'
const cacheDir = 'node_modules/.cache/check-compat'
const registry = 'https://registry.npmjs.org'
export async function releasedPackage(): 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 }
}
-8
View File
@@ -29,11 +29,3 @@ 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. On the
# versions pull request it runs with --strict, but workflows there wait for
# a manual approval, so the Release workflow runs --strict too.
- name: Check backward compatibility
env:
STRICT: ${{ github.head_ref == 'changeset-release/dev' && '--strict' || '' }}
run: pnpm run check:compat $STRICT
-5
View File
@@ -38,8 +38,3 @@ jobs:
- name: Check markup classes
run: pnpm run check:markup-classes --html preview/dist --html docs/dist --css preview/dist/dist/css --css preview/dist/dist/libs --css preview/dist/preview/css --css docs/dist/css --css docs/dist/preview/css
# A project written for the last release has to compile and behave the
# same on this build. Runs here because it needs the core build and a browser.
- name: Check a project written for the last release
run: pnpm run check:compat-fixture
-9
View File
@@ -35,15 +35,6 @@ jobs:
- name: Build
run: pnpm run build
# The last gate before npm: nothing the previous release shipped may be
# missing, unless compat-baseline.txt marks it as accepted. It runs here and
# not only on the versions pull request, because workflows on a pull request
# opened by the bot wait for a manual approval and may never run. On the
# push that publishes, the local version is not on npm yet, so the check
# compares with `latest`, which is the release users update from.
- name: Check backward compatibility
run: pnpm run check:compat --strict
- name: Create release Pull Request or publish to NPM
id: changesets
uses: changesets/action@ae32849d5ba541f9ae29e40e22a623bc13562f51 # v2.1.2
+2 -2
View File
@@ -171,7 +171,7 @@
},
{
"path": "./dist/css/tabler-props.min.css",
"maxSize": "3.7 kB"
"maxSize": "3.8 kB"
},
{
"path": "./dist/css/tabler-props.rtl.css",
@@ -179,7 +179,7 @@
},
{
"path": "./dist/css/tabler-props.rtl.min.css",
"maxSize": "3.7 kB"
"maxSize": "3.8 kB"
},
{
"path": "./dist/css/tabler-socials.css",
-1
View File
@@ -149,7 +149,6 @@
}
}
}
}
@include describe('media-print mixin') {
-2
View File
@@ -29,8 +29,6 @@
"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:compat-fixture": "tsx .build/check-compat-fixture.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",