import fs from 'node:fs' import nodePath from 'node:path' import { fileURLToPath } from 'node:url' import type { Plugin } from 'vite' const UI_ROOT = nodePath.dirname(fileURLToPath(import.meta.url)) /** Seed kokunun `ui/` klasorune gore konumu; calisma dizininden bagimsiz cozulur. */ const COMPONENTS_DIR = nodePath.resolve( UI_ROOT, '../configs/seeds/data/App.DeveloperKit.CustomComponents', ) type DesignerNode = { props?: Record; children?: DesignerNode[] } type ComponentRow = { Props?: string; Code?: string } const addTokens = (value: string, classes: Set) => value .split(/\s+/) .filter(Boolean) .forEach((token) => classes.add(token)) const collectFromNodes = (nodes: DesignerNode[] | undefined, classes: Set) => { if (!Array.isArray(nodes)) return for (const node of nodes) { for (const [name, value] of Object.entries(node?.props ?? {})) { if (typeof value === 'string' && /class(Name)?$/i.test(name)) addTokens(value, classes) } collectFromNodes(node?.children, classes) } } const collectFromRow = (row: ComponentRow, classes: Set) => { try { const props = row.Props ? JSON.parse(row.Props) : null collectFromNodes(props?.visualDesigner?.nodes, classes) } catch { // Tasarimci belgesi olmayan (elle yazilmis) bilesen; siniflari asagida kodundan gelir. } for (const match of String(row.Code ?? '').matchAll(/className=\{?["'`]([^"'`]+)["'`]/g)) { addTokens(match[1], classes) } } /** * Custom Component'lerin kullandigi siniflar. Bilesen kodu veritabaninda yasar; derlemede * taranabilen tek kaynak seed dosyasidir ve o dosya `ui/` disindadir. Derleme baglami depo kokunu * gormedigin de (docker, ayri checkout) seed'e ozgu siniflar CSS'e hic girmez ve uygulama sunucuda * stilsiz kalir. Siniflar `safelist.txt`'ye yazilir; dosya depoya islendigi icin her ortamda taranir. */ const collectComponentClasses = () => { // Her bilesen klasorun altinda kendi dosyasindadir; tek dosya bicimi eski kurulumlarda kalir. const files = fs.existsSync(COMPONENTS_DIR) ? fs .readdirSync(COMPONENTS_DIR) .filter((name) => name.endsWith('.json')) .map((name) => nodePath.join(COMPONENTS_DIR, name)) : [`${COMPONENTS_DIR}.json`].filter((file) => fs.existsSync(file)) const classes = new Set() for (const file of files) { try { const seed = JSON.parse(fs.readFileSync(file, 'utf8')) for (const row of seed.Rows ?? []) collectFromRow(row, classes) } catch { // Bozuk bir seed dosyasi derlemeyi durdurmaz; diger bilesenler islenir. } } return [...classes].sort() } /** * `safelist.txt`'yi seed'deki Custom Component siniflariyla tazeler. Tailwind dosyayi `@source` * olarak taradigi icin derlemeden once yazilir; gelistirmede seed klasoru izlenir. Seed koku * gorunmuyorsa onceki liste korunur — eksik bir baglam listeyi silip sorunu buyutmez. */ export default function twSafelistGenerator(path = 'safelist.txt'): Plugin { const safeListPath = nodePath.resolve(UI_ROOT, path) const write = () => { const classes = collectComponentClasses() if (classes.length === 0) return const safeList = classes.join('\n') const current = fs.existsSync(safeListPath) ? fs.readFileSync(safeListPath, 'utf8') : '' if (current !== safeList) fs.writeFileSync(safeListPath, safeList) } return { name: 'sozsoft-tw-safelist', buildStart: write, configureServer(server) { server.watcher.add(COMPONENTS_DIR) server.watcher.on('all', (_event, file) => { if (nodePath.resolve(file).startsWith(COMPONENTS_DIR)) write() }) }, } }