const fs = require('fs') const nodePath = require('path') const plugin = require('tailwindcss/plugin') const generator = require('./generator') const crypto = require('crypto') /** * Uretilen renk/olcu listesiyle Custom Component siniflarini ayiran isaret. Bilesen siniflari * dosyanin sonunda yasar; kaynak seed dosyasi bulunamadiginda onceki liste oldugu gibi korunur. */ const COMPONENT_MARKER = '# --- custom component classes ---' /** Seed kokunun `ui/` klasorune gore konumu; calisma dizininden bagimsiz cozulur. */ const SEEDS_ROOT = nodePath.resolve(__dirname, '../../configs/seeds') const addTokens = (value, classes) => String(value) .split(/\s+/) .filter(Boolean) .forEach((token) => classes.add(token)) const collectFromNodes = (nodes, classes) => { if (!Array.isArray(nodes)) return for (const node of nodes) { for (const [name, value] of Object.entries((node && node.props) || {})) { if (typeof value === 'string' && /class(Name)?$/i.test(name)) addTokens(value, classes) } collectFromNodes(node && node.children, classes) } } const collectFromRow = (row, classes) => { try { const props = row && row.Props ? JSON.parse(row.Props) : null collectFromNodes(props && props.visualDesigner && props.visualDesigner.nodes, classes) } catch { // Tasarimci belgesi olmayan (elle yazilmis) bilesen; siniflari asagida kodundan gelir. } const code = String((row && row.Code) || '') const pattern = /className=\{?["'`]([^"'`]+)["'`]/g let match = pattern.exec(code) while (match) { addTokens(match[1], classes) match = pattern.exec(code) } } /** * 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'e yazilir; dosya depoya islendigi icin her ortamda taranir. */ const collectComponentClasses = () => { if (!fs.existsSync(SEEDS_ROOT)) return [] const files = fs .readdirSync(SEEDS_ROOT, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => nodePath.join(SEEDS_ROOT, entry.name, 'data/App.DeveloperKit.CustomComponents.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 kapsamlar islenir. } } return [...classes].sort() } module.exports = plugin.withOptions( ({ path = 'safelist.txt', patterns = [] }) => ({ theme }) => { // Yol calisma dizinine gore cozulurse depo kokunden calistirilan her arac ikinci bir // safelist dosyasi yaratir; eklentinin konumuna sabitlenir, tek dosya kalir. const safeListPath = nodePath.isAbsolute(path) ? path : nodePath.resolve(__dirname, '..', path) const currentSafeList = fs.existsSync(safeListPath) ? fs.readFileSync(safeListPath).toString() : '' const previousComponents = currentSafeList.split(COMPONENT_MARKER)[1] || '' const seedClasses = collectComponentClasses() const components = seedClasses.length ? seedClasses : previousComponents.split('\n').filter(Boolean) const safeList = [...generator(theme)(patterns), COMPONENT_MARKER, ...components].join( '\n', ) const hash = crypto.createHash('md5').update(JSON.stringify(safeList)).digest('hex') const prevHash = crypto .createHash('md5') .update(JSON.stringify(currentSafeList)) .digest('hex') if (hash !== prevHash) { return fs.writeFileSync(safeListPath, safeList) } }, )