sozsoft-platform/ui/twSafelistGenerator.ts

103 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-10-16 21:13:31 +00:00
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<string, unknown>; children?: DesignerNode[] }
type ComponentRow = { Props?: string; Code?: string }
const addTokens = (value: string, classes: Set<string>) =>
value
.split(/\s+/)
.filter(Boolean)
.forEach((token) => classes.add(token))
const collectFromNodes = (nodes: DesignerNode[] | undefined, classes: Set<string>) => {
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<string>) => {
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<string>()
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()
})
},
}
}