Add SRI hashes to the CDN snippets in the docs (#3086)

This commit is contained in:
Paweł Kuna authored and GitHub committed 2026-09-21 11:18:58 +02:00
1 parent a9efcc241d
commit bcf5991167
16 files changed
+349 -18

No files matched your search

+1 -1
View File
@@ -107,7 +107,7 @@ pnpm run lint:prettier # formatting
pnpm run bundlewatch # size budgets (tabler.css 80 kB, tabler.min.css 75 kB) pnpm run bundlewatch # size budgets (tabler.css 80 kB, tabler.min.css 75 kB)
``` ```
- A new entry in `$theme-colors`, `$avatar-sizes` and friends must be regenerated into `shared/lib/tokens.ts` with `pnpm run generate-tokens` — the check gate fails otherwise. - A new entry in `$theme-colors`, `$avatar-sizes` and friends must be regenerated into `shared/lib/tokens.ts` with `pnpm run generate:tokens` — the check gate fails otherwise.
- An unused Sass variable fails `lint:scss`; delete it or use it. - An unused Sass variable fails `lint:scss`; delete it or use it.
- Growth past a bundlewatch limit is a decision, not an accident: raise the number in `core/package.json` deliberately and say so in the PR. - Growth past a bundlewatch limit is a decision, not an accident: raise the number in `core/package.json` deliberately and say so in the PR.
+171
View File
@@ -0,0 +1,171 @@
#!/usr/bin/env node
// Regenerates shared/data/sri.json, the Subresource Integrity hashes the docs put into the CDN
// snippets. The hashes are read from the files jsDelivr already serves for the published
// @tabler/core version, never from a local build: a local dist is not the file the browser
// downloads, so its hash would make the browser block the stylesheet or the script.
//
// Run it after a release, before merging `dev` into `main` (docs.tabler.io is built from `main`):
//
// pnpm run generate:sri (once the new version is on npm)
// pnpm run generate:sri --wait (same, but waits for the release to reach npm and the CDN)
// pnpm run check:sri (the committed hashes still match the CDN)
import { createHash } from 'node:crypto'
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { site } from '../shared/lib/site'
interface SriData {
version: string
algorithm: string
files: Record<string, string>
}
const algorithm = 'sha384'
const waitMinutes = 10
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = join(__dirname, '..')
const dataFile = join(repoRoot, 'shared/data/sri.json')
// The files the docs hand out as CDN tags. The plugin bundles in `dist/libs/` are not here: they
// are third-party packages the docs only link to, and each one has its own version.
const files = ['dist/css/tabler.min.css', 'dist/css/tabler.rtl.min.css', 'dist/js/tabler.min.js', 'dist/js/tabler-theme.min.js', ...site.cssPlugins.flatMap((plugin) => [`dist/css/tabler-${plugin}.min.css`, `dist/css/tabler-${plugin}.rtl.min.css`])]
/** One GET, retried once: a single dropped connection should not fail a CI job. */
async function get(url: string): Promise<Response> {
for (let attempt = 1; ; attempt++) {
try {
const response = await fetch(url)
if (response.ok || response.status === 404 || attempt === 2) {
return response
}
console.warn(`generate:sri: ${url} returned ${response.status}, retrying`)
} catch (error: unknown) {
if (attempt === 2) {
throw error
}
console.warn(`generate:sri: ${url} failed (${error instanceof Error ? error.message : String(error)}), retrying`)
}
}
}
/** Whether the version in core/package.json is on npm, and so on the CDN as well. */
async function isPublished(): Promise<boolean> {
return (await get(`https://registry.npmjs.org/@tabler/core/${site.version}`)).ok
}
/** The integrity value of one published file, downloaded from the CDN the docs point at. */
async function hashFile(file: string): Promise<string> {
const url = `${site.cdnUrl}/${file}`
const response = await get(url)
if (!response.ok) {
throw new Error(`generate:sri: ${url} returned ${response.status}`)
}
const body = Buffer.from(await response.arrayBuffer())
return `${algorithm}-${createHash(algorithm).update(body).digest('base64')}`
}
async function collectHashes(): Promise<SriData> {
const hashes = await Promise.all(files.map(async (file) => [file, await hashFile(file)] as const))
return { version: site.version, algorithm, files: Object.fromEntries(hashes) }
}
/**
* `pnpm run check:sri` - the committed hashes have to match what the CDN serves today.
*
* Hashes for an older version are a safe state: the docs render the tags without `integrity`
* until `generate:sri` runs. So they only warn.
*/
async function check(): Promise<void> {
if (!existsSync(dataFile)) {
throw new Error('check:sri: shared/data/sri.json is missing - run `pnpm run generate:sri` and commit the result')
}
const committed: SriData = JSON.parse(readFileSync(dataFile, 'utf8'))
// The version pull request changesets opens bumps core/package.json before anything is on npm.
// There is nothing to compare against yet, and the hashes get regenerated after the release, so
// this is a pass rather than a failure.
if (!(await isPublished())) {
console.log(`check:sri: @tabler/core v${site.version} is not published yet, nothing to compare`)
return
}
if (committed.version !== site.version) {
console.warn(`check:sri: shared/data/sri.json has hashes for v${committed.version}, but @tabler/core is v${site.version} - the docs render the CDN tags without \`integrity\` until you run \`pnpm run generate:sri\``)
return
}
const current = await collectHashes()
const problems: string[] = []
for (const file of files) {
if (committed.files[file] !== current.files[file]) {
problems.push(`${file}: ${committed.files[file] ?? 'missing'} committed, ${current.files[file]} on the CDN`)
}
}
if (problems.length > 0) {
console.error(`check:sri: shared/data/sri.json is out of date, run \`pnpm run generate:sri\`:\n ${problems.join('\n ')}`)
process.exitCode = 1
return
}
console.log(`check:sri: ${files.length} hashes match @tabler/core v${current.version}`)
}
/**
* `--wait`, for running it right after a release: `changeset publish` returns before npm and the
* CDN have the new version everywhere, so poll instead of failing on the first 404.
*/
async function collectWhenPublished(): Promise<SriData> {
const deadline = Date.now() + waitMinutes * 60_000
for (let attempt = 1; ; attempt++) {
try {
if (await isPublished()) {
return await collectHashes()
}
} catch (error: unknown) {
if (Date.now() >= deadline) {
throw error
}
}
if (Date.now() >= deadline) {
throw new Error(`generate:sri: @tabler/core v${site.version} did not show up on the CDN within ${waitMinutes} minutes`)
}
console.log(`generate:sri: waiting for @tabler/core v${site.version} on the CDN (attempt ${attempt})`)
await new Promise((resolve) => setTimeout(resolve, 15_000))
}
}
async function generate(wait: boolean): Promise<void> {
if (!wait && !(await isPublished())) {
throw new Error(`generate:sri: @tabler/core v${site.version} is not on npm yet - run this after \`changeset publish\``)
}
const data = wait ? await collectWhenPublished() : await collectHashes()
writeFileSync(dataFile, `${JSON.stringify(data, null, 2)}\n`)
console.log(`generate:sri: wrote ${files.length} ${algorithm} hashes for @tabler/core v${data.version}`)
}
// Wrapped in main() because the root package is CJS (no top-level await).
const main = async () => {
const args = process.argv.slice(2)
await (args.includes('check') ? check() : generate(args.includes('--wait')))
}
main().catch((error: unknown) => {
console.error(error instanceof Error ? error.message : error)
process.exit(1)
})
+4 -4
View File
@@ -3,8 +3,8 @@
// unions used by components can never drift from the stylesheet source of // unions used by components can never drift from the stylesheet source of
// truth. Values are read through the real Sass compiler (after every merge and // truth. Values are read through the real Sass compiler (after every merge and
// !default), not by parsing the source text. // !default), not by parsing the source text.
// Run: pnpm run generate-tokens — (re)write the file // Run: pnpm run generate:tokens — (re)write the file
// pnpm run check:tokens — fail when the committed file is stale // pnpm run check:tokens — fail when the committed file is stale
import { readFileSync, writeFileSync, existsSync } from 'node:fs' import { readFileSync, writeFileSync, existsSync } from 'node:fs'
import { join, dirname, relative } from 'node:path' import { join, dirname, relative } from 'node:path'
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url'
@@ -64,7 +64,7 @@ compileString(entry, {
}) })
let output = `// Generated from the core SCSS maps by .build/generate-tokens.ts — DO NOT EDIT. let output = `// Generated from the core SCSS maps by .build/generate-tokens.ts — DO NOT EDIT.
// Regenerate with: pnpm run generate-tokens // Regenerate with: pnpm run generate:tokens
` `
for (const token of TOKENS) { for (const token of TOKENS) {
const keys = keysByName.get(token.name) const keys = keysByName.get(token.name)
@@ -88,7 +88,7 @@ const main = async () => {
if (mode === 'check') { if (mode === 'check') {
const current = existsSync(outFile) ? readFileSync(outFile, 'utf8') : '' const current = existsSync(outFile) ? readFileSync(outFile, 'utf8') : ''
if (current !== formatted) { if (current !== formatted) {
console.error(`✖ ${relative(repoRoot, outFile)} is stale — run "pnpm run generate-tokens" and commit the result.`) console.error(`✖ ${relative(repoRoot, outFile)} is stale — run "pnpm run generate:tokens" and commit the result.`)
process.exit(1) process.exit(1)
} }
console.log(`✓ ${relative(repoRoot, outFile)} is up to date.`) console.log(`✓ ${relative(repoRoot, outFile)} is up to date.`)
+2 -2
View File
@@ -3,7 +3,7 @@
// and dark SVGs, the $payment-providers list and the demo data. // and dark SVGs, the $payment-providers list and the demo data.
// //
// Run: pnpm run import:payments // Run: pnpm run import:payments
// Then: pnpm run generate-tokens — refreshes PaymentProvider in shared/lib/tokens.ts // Then: pnpm run generate:tokens — refreshes PaymentProvider in shared/lib/tokens.ts
import { execFileSync } from 'node:child_process' import { execFileSync } from 'node:child_process'
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os' import { tmpdir } from 'node:os'
@@ -86,7 +86,7 @@ try {
console.log(`Imported ${providers.length} payment providers from ${relative(repoRoot, sourceDir)} (${added.length} added, ${removed.length} removed)`) console.log(`Imported ${providers.length} payment providers from ${relative(repoRoot, sourceDir)} (${added.length} added, ${removed.length} removed)`)
if (added.length) console.log(` added: ${added.join(', ')}`) if (added.length) console.log(` added: ${added.join(', ')}`)
if (removed.length) console.log(` removed: ${removed.join(', ')}`) if (removed.length) console.log(` removed: ${removed.join(', ')}`)
console.log('Now run `pnpm run generate-tokens` to refresh shared/lib/tokens.ts') console.log('Now run `pnpm run generate:tokens` to refresh shared/lib/tokens.ts')
} finally { } finally {
if (cloneDir) rmSync(cloneDir, { recursive: true, force: true }) if (cloneDir) rmSync(cloneDir, { recursive: true, force: true })
} }
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env node
// Publishes a release to docs.tabler.io, which is built from `main`. Run it on `dev` once the
// release workflow has put the new version on npm:
//
// 1. refreshes shared/data/sri.json for that version (`generate:sri --wait`),
// 2. commits it and pushes `dev`,
// 3. merges `dev` into `main` and pushes `main`.
//
// The merge happens in a temporary worktree, so the current checkout (and a dev server running
// from it) never switches branches.
//
// Run: pnpm run release:main (does all of the above)
// pnpm run release:main --dry-run (refreshes and commits locally, pushes nothing)
import { execFileSync } from 'node:child_process'
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = join(__dirname, '..')
const sriFile = 'shared/data/sri.json'
const dryRun = process.argv.includes('--dry-run')
const git = (args: string[], cwd = repoRoot): string => execFileSync('git', args, { cwd, encoding: 'utf8' }).trim()
const run = (command: string, args: string[]): void => {
execFileSync(command, args, { cwd: repoRoot, stdio: 'inherit' })
}
const step = (message: string): void => {
console.log(`\nrelease:main: ${message}`)
}
const { version } = JSON.parse(readFileSync(join(repoRoot, 'core', 'package.json'), 'utf8')) as { version: string }
const main = (): void => {
if (git(['branch', '--show-current']) !== 'dev') {
throw new Error('release:main: run this on the `dev` branch')
}
if (git(['status', '--porcelain', '--untracked-files=no']) !== '') {
throw new Error('release:main: the working tree has uncommitted changes - commit or stash them first')
}
step('updating dev from origin')
git(['fetch', 'origin', 'dev', 'main'])
git(['merge', '--ff-only', 'origin/dev'])
step(`refreshing the SRI hashes for @tabler/core v${version}`)
run(join(repoRoot, 'node_modules', '.bin', 'tsx'), [join('.build', 'generate-sri.ts'), '--wait'])
if (git(['status', '--porcelain', '--', sriFile]) === '') {
console.log(`release:main: ${sriFile} is already up to date`)
} else {
git(['commit', '-m', `Update SRI hashes for v${version}`, '--', sriFile])
console.log(`release:main: committed ${sriFile}`)
}
if (dryRun) {
step('--dry-run: stopping before any push')
return
}
step('pushing dev')
run('git', ['push', 'origin', 'dev'])
step('merging dev into main')
const worktree = mkdtempSync(join(tmpdir(), 'tabler-main-'))
try {
git(['worktree', 'add', '--detach', worktree, 'origin/main'])
git(['merge', '--no-edit', '-m', `Merge dev into main for v${version}`, 'dev'], worktree)
execFileSync('git', ['push', 'origin', 'HEAD:main'], { cwd: worktree, stdio: 'inherit' })
} finally {
git(['worktree', 'remove', '--force', worktree])
rmSync(worktree, { recursive: true, force: true })
}
step(`done - docs.tabler.io deploys v${version} from main`)
}
try {
main()
} catch (error: unknown) {
console.error(error instanceof Error ? error.message : error)
process.exit(1)
}
+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/docs": patch
---
Added `integrity` and `crossorigin` attributes to the CDN snippets on the installation and plugin pages.
+19
View File
@@ -51,6 +51,25 @@ pick:
`pnpm run release-notes` prints the body to stdout, so you can read it before `pnpm run release-notes` prints the body to stdout, so you can read it before
the release goes out. Pass a path to write it to a file instead. the release goes out. Pass a path to write it to a file instead.
## After the release
docs.tabler.io is built from `main`. Once the release workflow has published
the new version, run this on `dev`:
```shell
pnpm run release:main
```
It refreshes the `integrity` hashes of the CDN snippets in
`shared/data/sri.json` (they can only be read once the version is on npm),
commits them, pushes `dev`, then merges `dev` into `main` and pushes `main`. The
merge runs in a temporary worktree, so your checkout stays on `dev`. Add
`--dry-run` to stop after the local commit.
Until the hashes are refreshed the docs show the CDN tags without `integrity`.
That is safe, just less strict. `pnpm run check:sri` compares the committed
hashes with the CDN if you want to check them by hand.
## Fixing a published release ## Fixing a published release
Edit the release on GitHub. The workflow only writes the body once, right after Edit the release on GitHub. The workflow only writes the body once, right after
-1
View File
@@ -38,7 +38,6 @@ dist/
packages-zip/ packages-zip/
.env .env
.env*.local .env*.local
sri.json
# TypeScript # TypeScript
*.tsbuildinfo *.tsbuildinfo
+2 -2
View File
@@ -63,12 +63,12 @@ Do not edit `dist/` folders — they are generated by the build.
| `pnpm run check` | Lint plus TypeScript type checks | | `pnpm run check` | Lint plus TypeScript type checks |
| `pnpm run html-diff:baseline` | Snapshot all rendered preview pages (needs the dev server) | | `pnpm run html-diff:baseline` | Snapshot all rendered preview pages (needs the dev server) |
| `pnpm run html-diff` | Byte-compare rendered preview pages against the snapshot | | `pnpm run html-diff` | Byte-compare rendered preview pages against the snapshot |
| `pnpm run generate-tokens` | Regenerate `shared/lib/tokens.ts` from the core SCSS maps | | `pnpm run generate:tokens` | Regenerate `shared/lib/tokens.ts` from the core SCSS maps |
Two of these help with refactors: Two of these help with refactors:
- **`html-diff`** proves a refactor does not change the rendered demo HTML: run `html-diff:baseline` before your change, refactor, then `html-diff` — it reports the first differing line per page and marks whitespace-only differences. - **`html-diff`** proves a refactor does not change the rendered demo HTML: run `html-diff:baseline` before your change, refactor, then `html-diff` — it reports the first differing line per page and marks whitespace-only differences.
- **`generate-tokens`** keeps the TS unions in `shared/lib/tokens.ts` in sync with the SCSS maps (colors, sizes, breakpoints, payment providers, flags). Edit the SCSS map, regenerate, commit both — a stale file fails `pnpm run lint`. - **`generate:tokens`** keeps the TS unions in `shared/lib/tokens.ts` in sync with the SCSS maps (colors, sizes, breakpoints, payment providers, flags). Edit the SCSS map, regenerate, commit both — a stale file fails `pnpm run lint`.
## Before you open a pull request ## Before you open a pull request
@@ -5,8 +5,8 @@ description: Set up Tabler's light, dark, and auto color modes, and learn the th
related: [/ui/getting-started/customize, /ui/plugins/theme-base-colors] related: [/ui/getting-started/customize, /ui/plugins/theme-base-colors]
--- ---
import { site } from '@shared/lib/site.ts';
import { Code } from 'astro:components'; import { Code } from 'astro:components';
import { cdnThemeJsTag } from '@lib/cdn-snippets.ts';
## Overview ## Overview
@@ -48,7 +48,7 @@ Load the theme script right after the opening `<body>` tag, and do not add `defe
<Code <Code
lang="html" lang="html"
code={`<body> code={`<body>
<script src="${site.cdnUrl}/dist/js/tabler-theme.min.js"></script> ${cdnThemeJsTag()}
... ...
</body>`} </body>`}
/> />
@@ -64,6 +64,8 @@ Put the stylesheet in `<head>` and the script at the end of `<body>`. The file t
That's the whole setup. The CSS covers the layout and components, and the script adds the interactive parts such as dropdowns and modals. That's the whole setup. The CSS covers the layout and components, and the script adds the interactive parts such as dropdowns and modals.
The `integrity` attribute lets the browser check that the file it downloaded is the one Tabler published, and `crossorigin="anonymous"` is what makes that check possible on a CDN. Each hash belongs to one file of one version, so when you move to a newer Tabler, copy the tags from this page again instead of only changing the version number in the URL.
If you'd rather not depend on a CDN, you can download the files and serve them from your own project. The [Download](/ui/getting-started/download) page explains how. If you'd rather not depend on a CDN, you can download the files and serve them from your own project. The [Download](/ui/getting-started/download) page explains how.
### Open in your browser ### Open in your browser
+25 -3
View File
@@ -2,15 +2,37 @@
// re-renders them into the /llms.txt endpoints, and the installation page drops the two tags into // re-renders them into the /llms.txt endpoints, and the installation page drops the two tags into
// a full HTML example. // a full HTML example.
import { site } from '@shared/lib/site.ts' import { site } from '@shared/lib/site.ts'
import sri from '@data/sri.json'
/**
* `integrity` and `crossorigin` for a file of the published core package, so the browser can
* verify what the CDN sent. The hashes in shared/data/sri.json come from `pnpm run generate:sri`;
* until that runs for the current version the tags are rendered without them, rather than with a
* hash that would make the browser block the file.
*/
const sriAttrs = (file: string): string => {
const hash = sri.version === site.version ? (sri.files as Record<string, string>)[file] : undefined
return hash ? ` integrity="${hash}" crossorigin="anonymous"` : ''
}
/** `<link>` for one stylesheet of the core package, by its path inside the package. */
const cdnLinkTag = (file: string): string => `<link rel="stylesheet" href="${site.cdnUrl}/${file}"${sriAttrs(file)} />`
/** `<script>` for one script of the core package, by its path inside the package. */
const cdnScriptTag = (file: string): string => `<script src="${site.cdnUrl}/${file}"${sriAttrs(file)}></script>`
/** `<link>` for the core stylesheet. */ /** `<link>` for the core stylesheet. */
export const cdnCssTag = (): string => `<link rel="stylesheet" href="${site.cdnUrl}/dist/css/tabler.min.css" />` export const cdnCssTag = (): string => cdnLinkTag('dist/css/tabler.min.css')
/** `<script>` for the core bundle. */ /** `<script>` for the core bundle. */
export const cdnJsTag = (): string => `<script src="${site.cdnUrl}/dist/js/tabler.min.js"></script>` export const cdnJsTag = (): string => cdnScriptTag('dist/js/tabler.min.js')
/** `<script>` for the color mode script, as shown on the Color modes page. */
export const cdnThemeJsTag = (): string => cdnScriptTag('dist/js/tabler-theme.min.js')
/** Both core tags, as shown by `<CdnImportPackage />`. */ /** Both core tags, as shown by `<CdnImportPackage />`. */
export const cdnPackageSnippet = (): string => `${cdnCssTag()}\n${cdnJsTag()}` export const cdnPackageSnippet = (): string => `${cdnCssTag()}\n${cdnJsTag()}`
/** `<link>` per plugin stylesheet, as shown by `<CdnImportPlugin />`. */ /** `<link>` per plugin stylesheet, as shown by `<CdnImportPlugin />`. */
export const cdnPluginSnippet = (plugins: string[]): string => plugins.map((plugin) => `<link rel="stylesheet" href="${site.cdnUrl}/dist/css/tabler-${plugin}.min.css" />`).join('\n') export const cdnPluginSnippet = (plugins: string[]): string => plugins.map((plugin) => cdnLinkTag(`dist/css/tabler-${plugin}.min.css`)).join('\n')
+2 -1
View File
@@ -24,7 +24,7 @@ import { callouts } from '@components/callouts/index.ts'
import docs from '@data/docs.json' import docs from '@data/docs.json'
import packageManagers from '@data/package-managers.json' import packageManagers from '@data/package-managers.json'
import payments from '@data/payments.json' import payments from '@data/payments.json'
import { cdnCssTag, cdnJsTag, cdnPackageSnippet, cdnPluginSnippet } from './cdn-snippets.ts' import { cdnCssTag, cdnJsTag, cdnPackageSnippet, cdnPluginSnippet, cdnThemeJsTag } from './cdn-snippets.ts'
// Lazy raw imports, same as CodeDocs.astro — node:fs paths break once this is // Lazy raw imports, same as CodeDocs.astro — node:fs paths break once this is
// bundled into dist/.prerender. // bundled into dist/.prerender.
@@ -41,6 +41,7 @@ function resolveCodeTokens(snippet: string): string {
const tokens: Record<string, () => string> = { const tokens: Record<string, () => string> = {
'${cdnCssTag()}': cdnCssTag, '${cdnCssTag()}': cdnCssTag,
'${cdnJsTag()}': cdnJsTag, '${cdnJsTag()}': cdnJsTag,
'${cdnThemeJsTag()}': cdnThemeJsTag,
} }
return Object.entries(tokens) return Object.entries(tokens)
+4 -1
View File
@@ -18,12 +18,14 @@
"format:md": "tsx .build/reformat-mdx.mts", "format:md": "tsx .build/reformat-mdx.mts",
"html-diff": "tsx .build/html-diff.ts check", "html-diff": "tsx .build/html-diff.ts check",
"html-diff:baseline": "tsx .build/html-diff.ts baseline", "html-diff:baseline": "tsx .build/html-diff.ts baseline",
"generate-tokens": "tsx .build/generate-tokens.ts", "generate:tokens": "tsx .build/generate-tokens.ts",
"generate:sri": "tsx .build/generate-sri.ts",
"import:icons": "tsx .build/import-icons.ts", "import:icons": "tsx .build/import-icons.ts",
"import:illustrations": "tsx .build/import-illustrations.ts", "import:illustrations": "tsx .build/import-illustrations.ts",
"import:payments": "tsx .build/import-payments.ts", "import:payments": "tsx .build/import-payments.ts",
"update:icons": "pnpm up -w @tabler/icons@latest && pnpm run import:icons", "update:icons": "pnpm up -w @tabler/icons@latest && pnpm run import:icons",
"check:tokens": "tsx .build/generate-tokens.ts check", "check:tokens": "tsx .build/generate-tokens.ts check",
"check:sri": "tsx .build/generate-sri.ts check",
"check:docs-links": "tsx .build/check-docs-links.ts", "check:docs-links": "tsx .build/check-docs-links.ts",
"check:css-vars": "tsx .build/check-css-vars.ts", "check:css-vars": "tsx .build/check-css-vars.ts",
"check:open-source": "tsx .build/check-open-source.ts", "check:open-source": "tsx .build/check-open-source.ts",
@@ -40,6 +42,7 @@
"format:prettier": "prettier --write \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.{ts,mts}\" \"screenshots/.build/**/*.ts\" \"preview/scss/**/*.scss\" \"docs/scss/**/*.scss\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts,ts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache", "format:prettier": "prettier --write \"core/scss/**/*.scss\" \"core/js/**/*.{js,ts}\" \"core/.build/**/*.{ts,mts,mjs}\" \".build/**/*.{ts,mts}\" \"screenshots/.build/**/*.ts\" \"preview/scss/**/*.scss\" \"docs/scss/**/*.scss\" \"{preview,docs,screenshots,shared}/**/*.astro\" \"{preview,docs,screenshots}/**/*.{mjs,mts,ts}\" \"shared/**/*.{js,mjs,mts,ts}\" --cache",
"check": "pnpm run lint && pnpm run type-check && pnpm run type-check:build", "check": "pnpm run lint && pnpm run type-check && pnpm run type-check:build",
"release-notes": "tsx .build/release-notes.ts", "release-notes": "tsx .build/release-notes.ts",
"release:main": "tsx .build/release-main.ts",
"zip-package": "tsx .build/zip-package.ts", "zip-package": "tsx .build/zip-package.ts",
"start": "pnpm dev" "start": "pnpm dev"
}, },
+22
View File
@@ -0,0 +1,22 @@
{
"version": "1.5.1",
"algorithm": "sha384",
"files": {
"dist/css/tabler.min.css": "sha384-tLWyEXulonaekaXL6+fCZQJv/MujuGIpVTBzuVEpwSqFDEfLkPA/PAYGTSBBJO2X",
"dist/css/tabler.rtl.min.css": "sha384-OnS3EnX7UxAkLI2yYfkqnxg54JzNW/thpEW2NMuobSb0DF7hAWlFoJ2NwyX4Pnzh",
"dist/js/tabler.min.js": "sha384-KeeTrPFG36BhSeZepo1c2EzYKGlF6NX5Gtjyn5iEWVj52/Fy0LLbWLin5AqZne99",
"dist/js/tabler-theme.min.js": "sha384-r/aXxjzfXhMltNwaj2rBQ4iPtGFgkYA7dyJQlqkqL0cNzMqM/z36OttuWx0B1VWz",
"dist/css/tabler-flags.min.css": "sha384-9718xqXlRN3KKvYXJSjptnIWwI0UZDTz/jvavI+LA7Z8JHzfKo9E2CtZWODeVWFG",
"dist/css/tabler-flags.rtl.min.css": "sha384-/bGPya5STpgWA72Jm3g2JAsOtXtqTktOiZ9AjnCi9j2uVvXJkxCjkpsFxxebG8+Q",
"dist/css/tabler-socials.min.css": "sha384-prVQ+s1ucombzkZVHTwrB+Z68JTcU3PpRuVpr4z+NNr3A3HAkuxCZUh44prqPHAp",
"dist/css/tabler-socials.rtl.min.css": "sha384-R8INeoweSrmyzhnlREsmVUq3LkEiWqgwgQgrzDR2BB4QILCBbRSUv0nQVblZoJlg",
"dist/css/tabler-payments.min.css": "sha384-GRX8VBRF1x9TRmijkOOuJN5SIVm8iuTcPkYI4sG1YUtryG+pXnby5NCB7J90JT3r",
"dist/css/tabler-payments.rtl.min.css": "sha384-GRJTMICo/1buLV+5gulL3zmm+vtFpzdVgoWslOWjv5wpq33JSZ4HO8ZlBF1NuLsF",
"dist/css/tabler-vendors.min.css": "sha384-PV1GVwo9+Ko89STPCCqUwPUaP12FVpJA3otCGIqIce0jN0BjQxaQw5i5BRUCcbdx",
"dist/css/tabler-vendors.rtl.min.css": "sha384-bwfRGrur1OO42oUbBpU7miL4QLS6DWzkF8nE1ZpuhFWMrPqQSW+y1Mzb5JXlfaYx",
"dist/css/tabler-marketing.min.css": "sha384-//fabqiLUxL4Gd5mADLqQsRoeGYpSWjLhYUaUY4Ld1xRUb936UHDf7g66pSNwZVi",
"dist/css/tabler-marketing.rtl.min.css": "sha384-jsKlMb77mWkbvW1aLqzhLM5nkbHlYuPAK+kd2cjWM2J6Nrxv8Q9Zu68AWV7MGqhg",
"dist/css/tabler-themes.min.css": "sha384-yjfgW/GqjeXpsI6lXUQcwjm72Zs2trYjmpwPV8bzhkDjSTMDPiqXS4oi2liria1a",
"dist/css/tabler-themes.rtl.min.css": "sha384-MxUXq26zb5Dt2JgsQ5YsWwJRcWdvFvbgCGIz7wUH0aqYkf/IPAGGaihHdObEGD/o"
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Generated from the core SCSS maps by .build/generate-tokens.ts — DO NOT EDIT. // Generated from the core SCSS maps by .build/generate-tokens.ts — DO NOT EDIT.
// Regenerate with: pnpm run generate-tokens // Regenerate with: pnpm run generate:tokens
/** Keys of `$theme-colors` (core/scss/_variables.scss). */ /** Keys of `$theme-colors` (core/scss/_variables.scss). */
export const themeColors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'light', 'dark', 'muted', 'blue', 'azure', 'indigo', 'purple', 'pink', 'red', 'orange', 'yellow', 'lime', 'green', 'teal', 'cyan'] as const export const themeColors = ['primary', 'secondary', 'success', 'info', 'warning', 'danger', 'light', 'dark', 'muted', 'blue', 'azure', 'indigo', 'purple', 'pink', 'red', 'orange', 'yellow', 'lime', 'green', 'teal', 'cyan'] as const