import { promises as fs } from 'node:fs' import { basename, dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' const scriptDir = dirname(fileURLToPath(import.meta.url)) const repoRoot = resolve(scriptDir, '..', '..') const tmpDir = join(repoRoot, '.tmp') const evidenceFiles = { home: join(tmpDir, 'rea-four-modules-library-stable-20261017.json'), local: join(tmpDir, 'rea-four-modules-library-local-20261017.json') } const prefixes = [ 'knowledge-sidebar', 'main-layout-content', 'workspace-tree', 'wb-home', 'doc-feed', 'wb-feed', 'my-files' ] const styleKeys = [ 'display', 'position', 'font-family', 'font-size', 'font-weight', 'line-height', 'color', 'background-color', 'border-radius', 'border-top-width', 'border-top-color', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'gap', 'align-items', 'justify-content', 'box-shadow', 'overflow', 'max-width', 'min-height', 'transition-property', 'transition-duration', 'transition-timing-function', 'opacity', 'transform' ] function stripComments(source) { return source.replace(/\/\*[\s\S]*?\*\//g, '') } function cssRules(source) { const css = stripComments(source) const stack = [] const rules = [] let boundary = 0 let quote = '' let escaped = false for (let index = 0; index < css.length; index += 1) { const char = css[index] if (quote) { if (escaped) escaped = false else if (char === '\\') escaped = true else if (char === quote) quote = '' continue } if (char === '"' || char === "'") { quote = char continue } if (char === '{') { const prelude = css.slice(boundary, index).trim() stack.push({ prelude, bodyStart: index + 1 }) boundary = index + 1 continue } if (char === '}') { const entry = stack.pop() if (entry) { const body = css.slice(entry.bodyStart, index).trim() if (body && !entry.prelude.startsWith('@')) { rules.push({ context: stack.map((item) => item.prelude).filter((item) => item.startsWith('@')), selector: entry.prelude, body }) } } boundary = index + 1 continue } if (char === ';' && stack.length === 0) boundary = index + 1 } return rules } function matchesPrefix(selector) { return prefixes.some((prefix) => selector.includes(prefix)) } function relevantNode(node) { const bounds = node.bounds ?? [] if (bounds.length < 4 || bounds[2] <= 0 || bounds[3] <= 0) return false if (!node.attributes || !Object.keys(node.attributes).length) return false return true } function layoutNode(node) { const styles = node.styles ?? {} return { nodeIndex: node.node_index, parentIndex: node.parent_index, tag: node.node_name, attributes: node.attributes, bounds: node.bounds, styles: Object.fromEntries(styleKeys.map((key) => [key, styles[key]])) } } async function readEvidence(path) { return JSON.parse(await fs.readFile(path, 'utf8')) } async function main() { const evidence = {} for (const [name, path] of Object.entries(evidenceFiles)) { evidence[name] = await readEvidence(path) } const seenRules = new Set() const recovered = [] const styleSources = new Map() for (const [name, entry] of Object.entries(evidence)) { for (const sheet of entry.normalized_result.styles.items ?? []) { const text = sheet.source?.artifact?.text if (!text || !prefixes.some((prefix) => text.includes(prefix))) continue const sheetName = basename(sheet.file_path ?? 'inline.css') styleSources.set(sheetName, { path: sheet.file_path, bytes: sheet.source.artifact.bytes, sha256: sheet.source.artifact.sha256 }) for (const rule of cssRules(text)) { if (!matchesPrefix(rule.selector)) continue const key = `${rule.context.join('>')}|${rule.selector}|${rule.body}` if (seenRules.has(key)) continue seenRules.add(key) recovered.push({ evidence: name, sheet: sheetName, ...rule }) } } } const css = recovered .map((rule) => { const prefix = rule.context.length ? `${rule.context.join(' { ')} { ` : '' const suffix = rule.context.length ? ` ${'}'.repeat(rule.context.length)}` : '' return `/* ${rule.evidence}:${rule.sheet} */\n${prefix}${rule.selector} { ${rule.body} }${suffix}` }) .join('\n\n') const layout = {} for (const [name, entry] of Object.entries(evidence)) { layout[name] = (entry.normalized_result.visuals.layout.documents ?? []) .flatMap((document) => document.nodes ?? []) .filter(relevantNode) .map(layoutNode) } await fs.mkdir(tmpDir, { recursive: true }) await Promise.all([ fs.writeFile(join(tmpDir, 'library-recovered-rules.css'), `${css}\n`, 'utf8'), fs.writeFile(join(tmpDir, 'library-layout-summary.json'), JSON.stringify({ styleSources: Object.fromEntries(styleSources), layout }, null, 2), 'utf8') ]) process.stdout.write(JSON.stringify({ rules: recovered.length, css: join(tmpDir, 'library-recovered-rules.css'), layout: join(tmpDir, 'library-layout-summary.json'), styleSources }, null, 2)) } main().catch((error) => { console.error(error) process.exitCode = 1 })