mirror of
https://github.com/shadcn-ui/ui.git
synced 2026-06-11 09:51:40 +00:00
147 lines
3.6 KiB
TypeScript
147 lines
3.6 KiB
TypeScript
#!/usr/bin/env tsx
|
|
import * as fs from "fs"
|
|
import * as path from "path"
|
|
import { iconLibraries, type IconLibraryName } from "shadcn/icons"
|
|
|
|
type IconUsage = Record<IconLibraryName, Set<string>>
|
|
|
|
function findTsxFiles(dir: string) {
|
|
const files: string[] = []
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name)
|
|
if (entry.isDirectory()) {
|
|
files.push(...findTsxFiles(fullPath))
|
|
} else if (entry.isFile() && entry.name.endsWith(".tsx")) {
|
|
files.push(fullPath)
|
|
}
|
|
}
|
|
|
|
return files
|
|
}
|
|
|
|
function scanIconUsage() {
|
|
const iconUsage: IconUsage = Object.keys(iconLibraries).reduce((acc, key) => {
|
|
acc[key as IconLibraryName] = new Set()
|
|
return acc
|
|
}, {} as IconUsage)
|
|
|
|
const registryBasesDir = path.join(process.cwd(), "registry/bases")
|
|
const files = findTsxFiles(registryBasesDir)
|
|
const libraryNames = Object.values(iconLibraries)
|
|
.map((lib) => lib.name)
|
|
.join("|")
|
|
const iconPlaceholderRegex = new RegExp(
|
|
`<IconPlaceholder\\s+([^>]*?)(?:${libraryNames})=["']([^"']+)["']([^>]*?)\\/?>`,
|
|
"g"
|
|
)
|
|
|
|
for (const file of files) {
|
|
const content = fs.readFileSync(file, "utf-8")
|
|
|
|
let match
|
|
while ((match = iconPlaceholderRegex.exec(content)) !== null) {
|
|
const fullMatch = match[0]
|
|
|
|
for (const [libraryName, config] of Object.entries(iconLibraries)) {
|
|
const attrMatch = fullMatch.match(
|
|
new RegExp(`${config.name}=["']([^"']+)["']`)
|
|
)
|
|
if (attrMatch) {
|
|
iconUsage[libraryName as IconLibraryName].add(attrMatch[1])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return iconUsage
|
|
}
|
|
|
|
function generateIconFiles(iconUsage: IconUsage) {
|
|
const outputDir = path.join(process.cwd(), "registry/icons")
|
|
|
|
const written: string[] = []
|
|
|
|
Object.entries(iconLibraries).forEach(([libraryName, config]) => {
|
|
const icons = Array.from(iconUsage[libraryName as IconLibraryName]).sort()
|
|
|
|
if (icons.length === 0) {
|
|
return
|
|
}
|
|
|
|
const content = `// Auto-generated by scripts/build-icons.ts
|
|
${icons.map((icon) => `export { ${icon} } from "${config.export}"`).join("\n")}
|
|
`
|
|
|
|
const filename = `__${libraryName}__.ts`
|
|
const filepath = path.join(outputDir, filename)
|
|
|
|
// Skip unchanged files to avoid mtime bumps that trigger
|
|
// unnecessary Turbopack invalidations in watch mode.
|
|
if (
|
|
fs.existsSync(filepath) &&
|
|
fs.readFileSync(filepath, "utf-8") === content
|
|
) {
|
|
return
|
|
}
|
|
|
|
fs.writeFileSync(filepath, content)
|
|
written.push(` - ${config.title}: ${icons.length} icons`)
|
|
})
|
|
|
|
if (written.length > 0) {
|
|
console.log("✓ Generated icon files:")
|
|
written.forEach((line) => console.log(line))
|
|
}
|
|
}
|
|
|
|
function main() {
|
|
const iconUsage = scanIconUsage()
|
|
generateIconFiles(iconUsage)
|
|
}
|
|
|
|
const isWatchMode = process.argv.includes("--watch")
|
|
|
|
if (isWatchMode) {
|
|
const REGISTRY_DIR = path.join(process.cwd(), "registry/bases")
|
|
|
|
async function startWatcher() {
|
|
const { default: chokidar } = await import("chokidar")
|
|
|
|
main()
|
|
|
|
const watcher = chokidar.watch(REGISTRY_DIR, {
|
|
ignored: /(^|[/\\])\../,
|
|
persistent: true,
|
|
ignoreInitial: true,
|
|
})
|
|
|
|
const rebuild = (filename: string) => {
|
|
if (!filename.endsWith(".tsx")) return
|
|
|
|
try {
|
|
main()
|
|
} catch (error) {
|
|
console.error("❌ Icons build failed:", error)
|
|
}
|
|
}
|
|
|
|
watcher.on("error", (error) => {
|
|
console.error("❌ Watcher error:", error)
|
|
})
|
|
|
|
watcher.on("change", rebuild)
|
|
watcher.on("add", rebuild)
|
|
|
|
process.on("SIGINT", async () => {
|
|
await watcher.close()
|
|
process.exit(0)
|
|
})
|
|
}
|
|
|
|
startWatcher()
|
|
} else {
|
|
main()
|
|
}
|