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)
```
- 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.
- 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
// truth. Values are read through the real Sass compiler (after every merge and
// !default), not by parsing the source text.
// Run: pnpm run generate-tokens — (re)write the file
// pnpm run check:tokens — fail when the committed file is stale
// Run: pnpm run generate:tokens — (re)write the file
// pnpm run check:tokens — fail when the committed file is stale
import { readFileSync, writeFileSync, existsSync } from 'node:fs'
import { join, dirname, relative } from 'node:path'
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.
// Regenerate with: pnpm run generate-tokens
// Regenerate with: pnpm run generate:tokens
`
for (const token of TOKENS) {
const keys = keysByName.get(token.name)
@@ -88,7 +88,7 @@ const main = async () => {
if (mode === 'check') {
const current = existsSync(outFile) ? readFileSync(outFile, 'utf8') : ''
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)
}
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.
//
// 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 { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
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)`)
if (added.length) console.log(` added: ${added.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 {
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
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
Edit the release on GitHub. The workflow only writes the body once, right after
-1
View File
@@ -38,7 +38,6 @@ dist/
packages-zip/
.env
.env*.local
sri.json
# TypeScript
*.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 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 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:
- **`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
@@ -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]
---
import { site } from '@shared/lib/site.ts';
import { Code } from 'astro:components';
import { cdnThemeJsTag } from '@lib/cdn-snippets.ts';
## Overview
@@ -48,7 +48,7 @@ Load the theme script right after the opening `<body>` tag, and do not add `defe
<Code
lang="html"
code={`<body>
<script src="${site.cdnUrl}/dist/js/tabler-theme.min.js"></script>
${cdnThemeJsTag()}
...
</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.
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.
### 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
// a full HTML example.
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. */
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. */
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 />`. */
export const cdnPackageSnippet = (): string => `${cdnCssTag()}\n${cdnJsTag()}`
/** `<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 packageManagers from '@data/package-managers.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
// bundled into dist/.prerender.
@@ -41,6 +41,7 @@ function resolveCodeTokens(snippet: string): string {
const tokens: Record<string, () => string> = {
'${cdnCssTag()}': cdnCssTag,
'${cdnJsTag()}': cdnJsTag,
'${cdnThemeJsTag()}': cdnThemeJsTag,
}
return Object.entries(tokens)
+4 -1
View File
@@ -18,12 +18,14 @@
"format:md": "tsx .build/reformat-mdx.mts",
"html-diff": "tsx .build/html-diff.ts check",
"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:illustrations": "tsx .build/import-illustrations.ts",
"import:payments": "tsx .build/import-payments.ts",
"update:icons": "pnpm up -w @tabler/icons@latest && pnpm run import:icons",
"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:css-vars": "tsx .build/check-css-vars.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",
"check": "pnpm run lint && pnpm run type-check && pnpm run type-check:build",
"release-notes": "tsx .build/release-notes.ts",
"release:main": "tsx .build/release-main.ts",
"zip-package": "tsx .build/zip-package.ts",
"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.
// Regenerate with: pnpm run generate-tokens
// Regenerate with: pnpm run generate:tokens
/** 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