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,
|
2026-10-19 14:35:08 +00:00
|
|
|
'../configs/seeds/data/Sas.DeveloperKit.CustomComponents',
|
2026-10-16 21:13:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|