sozsoft-platform/ui/twSafelistGenerator/index.js
2026-09-08 17:00:15 +03:00

112 lines
4.2 KiB
JavaScript

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)
}
},
)