Move the import scripts to the repository root (#2940)

This commit is contained in:
Paweł Kuna authored and GitHub committed 2026-09-02 23:27:54 +02:00
1 parent c1e1fdf9db
commit b90554b8ee
12 files changed
+163 -87

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env node
// Regenerates shared/data/icons.json and icons-info.json from the installed
// @tabler/icons package. `pnpm run update:icons` bumps the package first.
//
// Run: pnpm run import:icons
import { readFileSync, writeFileSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
interface IconTag {
styles: {
outline?: unknown
filled?: unknown
}
}
interface IconEntry {
name: string
svg: {
outline: string | null
filled: string | null
}
}
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = join(__dirname, '..')
const iconsPackage = join(repoRoot, 'node_modules/@tabler/icons')
const iconsTags: Record<string, IconTag> = JSON.parse(readFileSync(join(iconsPackage, 'icons.json'), 'utf8'))
const { version }: { version: string } = JSON.parse(readFileSync(join(iconsPackage, 'package.json'), 'utf8'))
const prepareSvgFile = (svg: string): string => {
return svg.replace(/\n/g, '').replace(/>\s+</g, '><').replace(/\s+/g, ' ')
}
const svgList: Record<string, IconEntry> = {}
for (const iconName in iconsTags) {
const iconData = iconsTags[iconName]
svgList[iconName] = {
name: iconName,
svg: {
outline: iconData.styles.outline ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/outline/${iconName}.svg`), 'utf8')) : null,
filled: iconData.styles.filled ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/filled/${iconName}.svg`), 'utf8')) : null,
},
}
}
writeFileSync(
join(repoRoot, 'shared/data/icons-info.json'),
JSON.stringify({
version,
count: Object.values(svgList).reduce((acc, icon) => {
return acc + (icon.svg.outline ? 1 : 0) + (icon.svg.filled ? 1 : 0)
}, 0),
}),
)
writeFileSync(join(repoRoot, 'shared/data/icons.json'), JSON.stringify(svgList))
console.log(`import-icons: imported ${Object.keys(svgList).length} icons from @tabler/icons v${version}`)
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env node
// Imports illustrations from the tabler/tabler-illustrations repo: the light and
// dark PNGs, the name list and the version. The free inline SVGs in
// shared/data/free-illustrations.json are hand-picked and stay as they are.
//
// Run: pnpm run import:illustrations
import { execFileSync } from 'node:child_process'
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, dirname, relative } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const repoRoot = join(__dirname, '..')
const imageDir = join(repoRoot, 'shared', 'static', 'illustrations')
const dataFile = join(repoRoot, 'shared', 'data', 'illustrations.json')
const infoFile = join(repoRoot, 'shared', 'data', 'illustrations-info.json')
let cloneDir: string | undefined
function resolveSourceDir(): string {
const candidates = [process.env.TABLER_ILLUSTRATIONS_DIR, join(repoRoot, '..', 'tabler-illustrations')].filter((dir): dir is string => Boolean(dir))
for (const dir of candidates) {
if (existsSync(join(dir, 'dist', 'png', 'light'))) return dir
}
cloneDir = mkdtempSync(join(tmpdir(), 'tabler-illustrations-'))
console.log('No local checkout found, cloning tabler/tabler-illustrations…')
execFileSync('git', ['clone', '--depth', '1', 'https://github.com/tabler/tabler-illustrations.git', cloneDir], { stdio: 'inherit' })
return cloneDir
}
const pngNames = (dir: string) =>
readdirSync(dir)
.filter((file) => file.endsWith('.png'))
.sort()
.map((file) => file.slice(0, -'.png'.length))
try {
const sourceDir = resolveSourceDir()
const lightDir = join(sourceDir, 'dist', 'png', 'light')
const darkDir = join(sourceDir, 'dist', 'png', 'dark')
const packageFile = join(sourceDir, 'package.json')
for (const path of [lightDir, darkDir, packageFile]) {
if (!existsSync(path)) throw new Error(`import-illustrations: missing ${path} — not a tabler-illustrations checkout?`)
}
const illustrations = pngNames(lightDir)
const darkIllustrations = pngNames(darkDir)
if (illustrations.join() !== darkIllustrations.join()) {
const light = new Set(illustrations)
const dark = new Set(darkIllustrations)
const onlyLight = illustrations.filter((name) => !dark.has(name))
const onlyDark = darkIllustrations.filter((name) => !light.has(name))
throw new Error(`import-illustrations: light/dark sets differ (only light: ${onlyLight.join(', ') || '—'}; only dark: ${onlyDark.join(', ') || '—'})`)
}
const previous = existsSync(join(imageDir, 'light')) ? new Set(pngNames(join(imageDir, 'light'))) : new Set<string>()
for (const variant of ['light', 'dark'] as const) {
const targetDir = join(imageDir, variant)
rmSync(targetDir, { recursive: true, force: true })
mkdirSync(targetDir, { recursive: true })
for (const name of illustrations) {
copyFileSync(join(variant === 'light' ? lightDir : darkDir, `${name}.png`), join(targetDir, `${name}.png`))
}
}
writeFileSync(dataFile, JSON.stringify(illustrations))
const { version }: { version: string } = JSON.parse(readFileSync(packageFile, 'utf8'))
writeFileSync(infoFile, JSON.stringify({ version, count: illustrations.length }))
const added = illustrations.filter((name) => !previous.has(name))
const removed = [...previous].filter((name) => !illustrations.includes(name))
console.log(`Imported ${illustrations.length} illustrations v${version} 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(', ')}`)
} finally {
if (cloneDir) rmSync(cloneDir, { recursive: true, force: true })
}
+4 -16
View File
@@ -1,16 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
// Imports payment provider assets from the tabler/tabler-payments repo, so the // Imports payment provider assets from the tabler/tabler-payments repo: the light
// payments plugin never drifts from the icon source of truth: // and dark SVGs, the $payment-providers list and the demo data.
// - core/img/payments/ <- src/light/*.svg + src/dark/*.svg (as *-dark.svg)
// - $payment-providers in core/scss/_variables.scss
// - shared/data/payments.json <- payments.json (name + logo, demo data)
// //
// Source checkout resolution order: // Run: pnpm run import:payments
// 1. TABLER_PAYMENTS_DIR env var
// 2. ../tabler-payments (sibling checkout)
// 3. fresh shallow clone into a temp dir (the repo is private — needs git auth)
//
// 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'
@@ -37,8 +29,7 @@ function resolveSourceDir(): string {
return cloneDir return cloneDir
} }
// Provider order everywhere follows the SVG filename (with extension), so // Sorted by file name, so 'amazon-pay' comes before 'amazon' — as in an `ls`.
// 'amazon-pay' sorts before 'amazon' — same as an `ls` of the source dir.
const svgNames = (dir: string) => const svgNames = (dir: string) =>
readdirSync(dir) readdirSync(dir)
.filter((file) => file.endsWith('.svg')) .filter((file) => file.endsWith('.svg'))
@@ -64,7 +55,6 @@ try {
throw new Error(`import-payments: light/dark sets differ (only light: ${onlyLight.join(', ') || '—'}; only dark: ${onlyDark.join(', ') || '—'})`) throw new Error(`import-payments: light/dark sets differ (only light: ${onlyLight.join(', ') || '—'}; only dark: ${onlyDark.join(', ') || '—'})`)
} }
// 1. core/img/payments — clean replace, dark variants get a `-dark` suffix
const previous = existsSync(imgDir) ? new Set(svgNames(imgDir).map((name) => name.replace(/-dark$/, ''))) : new Set<string>() const previous = existsSync(imgDir) ? new Set(svgNames(imgDir).map((name) => name.replace(/-dark$/, ''))) : new Set<string>()
rmSync(imgDir, { recursive: true, force: true }) rmSync(imgDir, { recursive: true, force: true })
mkdirSync(imgDir, { recursive: true }) mkdirSync(imgDir, { recursive: true })
@@ -73,14 +63,12 @@ try {
copyFileSync(join(darkDir, `${name}.svg`), join(imgDir, `${name}-dark.svg`)) copyFileSync(join(darkDir, `${name}.svg`), join(imgDir, `${name}-dark.svg`))
} }
// 2. $payment-providers in core/scss/_variables.scss
const scss = readFileSync(scssFile, 'utf8') const scss = readFileSync(scssFile, 'utf8')
const listPattern = /(\$payment-providers: \(\n)[\s\S]*?(\n\);)/ const listPattern = /(\$payment-providers: \(\n)[\s\S]*?(\n\);)/
if (!listPattern.test(scss)) throw new Error(`import-payments: $payment-providers list not found in ${scssFile}`) if (!listPattern.test(scss)) throw new Error(`import-payments: $payment-providers list not found in ${scssFile}`)
const listBody = providers.map((name) => ` '${name}'`).join(',\n') const listBody = providers.map((name) => ` '${name}'`).join(',\n')
writeFileSync(scssFile, scss.replace(listPattern, `$1${listBody}$2`)) writeFileSync(scssFile, scss.replace(listPattern, `$1${listBody}$2`))
// 3. shared/data/payments.json — demo data keeps its `{ name, logo }` shape
const meta = JSON.parse(readFileSync(metaFile, 'utf8')) as { name: string; logo: string }[] const meta = JSON.parse(readFileSync(metaFile, 'utf8')) as { name: string; logo: string }[]
const names = new Map(meta.map((entry) => [entry.logo, entry.name])) const names = new Map(meta.map((entry) => [entry.logo, entry.name]))
for (const logo of names.keys()) { for (const logo of names.keys()) {
@@ -0,0 +1,5 @@
---
"@tabler/preview": patch
---
Moved the `import:icons` and `import:illustrations` scripts to the repository root, next to `import:payments`.
+2 -1
View File
@@ -6,6 +6,7 @@ summary: Tabler Illustrations is a collection of customizable SVG illustrations
--- ---
import { Image } from 'astro:assets' import { Image } from 'astro:assets'
import { site } from '@shared/lib/site'
import coverLight from '../../images/cover-illustrations.jpg' import coverLight from '../../images/cover-illustrations.jpg'
import coverDark from '../../images/cover-illustrations-dark.jpg' import coverDark from '../../images/cover-illustrations-dark.jpg'
@@ -14,7 +15,7 @@ import coverDark from '../../images/cover-illustrations-dark.jpg'
<Image src={coverDark} alt="Tabler Illustrations" class="hide-theme-light" width={800} densities={[1, 2]} loading="eager" /> <Image src={coverDark} alt="Tabler Illustrations" class="hide-theme-light" width={800} densities={[1, 2]} loading="eager" />
</figure> </figure>
Tabler Illustrations is a set of 130 illustrations for empty states, error pages, onboarding screens and marketing sections. Every illustration comes in a light and a dark variant, so it fits both color modes of your interface. Tabler Illustrations is a set of { site.illustrationsCount } illustrations for empty states, error pages, onboarding screens and marketing sections. Every illustration comes in a light and a dark variant, so it fits both color modes of your interface.
## What's inside ## What's inside
+5 -1
View File
@@ -18,7 +18,10 @@
"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",
"import-payments": "tsx .build/import-payments.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:tokens": "tsx .build/generate-tokens.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",
@@ -46,6 +49,7 @@
"@changesets/cli": "^3.0.1", "@changesets/cli": "^3.0.1",
"@mdx-js/mdx": "^3.1.1", "@mdx-js/mdx": "^3.1.1",
"@stylistic/stylelint-config": "^2.0.0", "@stylistic/stylelint-config": "^2.0.0",
"@tabler/icons": "^3.46.0",
"@types/node": "^26.4.0", "@types/node": "^26.4.0",
"adm-zip": "^0.6.0", "adm-zip": "^0.6.0",
"autoprefixer": "^10.5.4", "autoprefixer": "^10.5.4",
+3 -3
View File
@@ -24,6 +24,9 @@ importers:
'@stylistic/stylelint-config': '@stylistic/stylelint-config':
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.0(stylelint@16.26.1(supports-color@5.5.0)(typescript@7.0.2)) version: 2.0.0(stylelint@16.26.1(supports-color@5.5.0)(typescript@7.0.2))
'@tabler/icons':
specifier: ^3.46.0
version: 3.46.0
'@types/node': '@types/node':
specifier: ^26.4.0 specifier: ^26.4.0
version: 26.4.0 version: 26.4.0
@@ -276,9 +279,6 @@ importers:
'@astrojs/mdx': '@astrojs/mdx':
specifier: ^7.0.8 specifier: ^7.0.8
version: 7.0.8(@astrojs/markdown-satteri@0.3.8)(astro@7.2.9(@astrojs/markdown-remark@7.2.4(supports-color@5.5.0))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.4.0)(@vercel/functions@3.9.5(ws@8.21.0))(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(supports-color@5.5.0) version: 7.0.8(@astrojs/markdown-satteri@0.3.8)(astro@7.2.9(@astrojs/markdown-remark@7.2.4(supports-color@5.5.0))(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.3)(@types/node@26.4.0)(@vercel/functions@3.9.5(ws@8.21.0))(sass@1.103.1)(terser@5.51.2)(tsx@4.23.13)(yaml@2.9.0))(supports-color@5.5.0)
'@tabler/icons':
specifier: ^3.46.0
version: 3.46.0
'@types/js-beautify': '@types/js-beautify':
specifier: ^1.14.3 specifier: ^1.14.3
version: 1.14.3 version: 1.14.3
-44
View File
@@ -1,44 +0,0 @@
#!/usr/bin/env node
// Regenerate shared/data/icons.json and icons-info.json from the installed
// @tabler/icons package. `pnpm run import-icons` refreshes the data files from
// the installed version; `pnpm run update-icons` bumps the package first.
import { readFileSync, writeFileSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const iconsPackage = join(__dirname, '../node_modules/@tabler/icons')
const iconsTags = JSON.parse(readFileSync(join(iconsPackage, 'icons.json'), 'utf8'))
const { version } = JSON.parse(readFileSync(join(iconsPackage, 'package.json'), 'utf8'))
const prepareSvgFile = (svg) => {
return svg.replace(/\n/g, '').replace(/>\s+</g, '><').replace(/\s+/g, ' ')
}
let svgList = {}
for (let iconName in iconsTags) {
let iconData = iconsTags[iconName]
svgList[iconName] = {
name: iconName,
svg: {
outline: iconData.styles.outline ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/outline/${iconName}.svg`), 'utf8')) : null,
filled: iconData.styles.filled ? prepareSvgFile(readFileSync(join(iconsPackage, `icons/filled/${iconName}.svg`), 'utf8')) : null,
},
}
}
writeFileSync(
join(__dirname, '../../shared/data/icons-info.json'),
JSON.stringify({
version,
count: Object.values(svgList).reduce((acc, icon) => {
return acc + (icon.svg.outline ? 1 : 0) + (icon.svg.filled ? 1 : 0)
}, 0),
}),
)
writeFileSync(join(__dirname, '../../shared/data/icons.json'), JSON.stringify(svgList))
console.log(`import-icons: imported ${Object.keys(svgList).length} icons from @tabler/icons v${version}`)
-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/env node
// Regenerate shared/data/illustrations.json from the PNG files in
// shared/static/illustrations/light. Run `pnpm run import-illustrations`
// after adding or removing illustration files.
import { globSync, writeFileSync } from 'node:fs'
import { join, basename, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const illustrations = globSync(join(__dirname, '../../shared/static/illustrations/light/*.png'))
.map((file) => basename(file, '.png'))
.sort()
writeFileSync(join(__dirname, '../../shared/data/illustrations.json'), JSON.stringify(illustrations))
console.log(`import-illustrations: imported ${illustrations.length} illustrations`)
-4
View File
@@ -18,9 +18,6 @@
"build": "pnpm run assets && astro build", "build": "pnpm run assets && astro build",
"preview": "astro preview", "preview": "astro preview",
"clean": "shx rm -rf dist public/dist public/preview public/css .astro tmp-assets", "clean": "shx rm -rf dist public/dist public/preview public/css .astro tmp-assets",
"import-icons": "node .build/import-icons.mjs",
"update-icons": "pnpm up @tabler/icons@latest && pnpm run import-icons",
"import-illustrations": "node .build/import-illustrations.mjs",
"type-check": "astro check" "type-check": "astro check"
}, },
"dependencies": { "dependencies": {
@@ -31,7 +28,6 @@
"@astrojs/check": "^0.9.4", "@astrojs/check": "^0.9.4",
"@astrojs/markdown-satteri": "^0.3.8", "@astrojs/markdown-satteri": "^0.3.8",
"@astrojs/mdx": "^7.0.8", "@astrojs/mdx": "^7.0.8",
"@tabler/icons": "^3.46.0",
"@types/js-beautify": "^1.14.3", "@types/js-beautify": "^1.14.3",
"js-beautify": "^2.0.3", "js-beautify": "^2.0.3",
"prettier": "^3.9.6", "prettier": "^3.9.6",
+1
View File
@@ -0,0 +1 @@
{"version":"1.16.0","count":125}
+4
View File
@@ -1,5 +1,6 @@
import corePackage from '@tabler/core/package.json' import corePackage from '@tabler/core/package.json'
import iconsInfo from '../data/icons-info.json' import iconsInfo from '../data/icons-info.json'
import illustrationsInfo from '../data/illustrations-info.json'
const version = corePackage.version const version = corePackage.version
@@ -19,6 +20,9 @@ export const site = {
// From shared/data/icons-info.json. // From shared/data/icons-info.json.
iconsCount: 5986, iconsCount: 5986,
iconsVersion: iconsInfo.version, iconsVersion: iconsInfo.version,
// From shared/data/illustrations-info.json.
illustrationsCount: illustrationsInfo.count,
illustrationsVersion: illustrationsInfo.version,
descriptionShort: 'Premium and Open Source dashboard template with responsive and high quality UI.', descriptionShort: 'Premium and Open Source dashboard template with responsive and high quality UI.',
description: 'Tabler is packed with beautifully crafted components and powerful features. Jump in and start building a stunning dashboard — all for free!', description: 'Tabler is packed with beautifully crafted components and powerful features. Jump in and start building a stunning dashboard — all for free!',
themeColor: '#066fd1', themeColor: '#066fd1',