71 lines
4.8 KiB
JavaScript
71 lines
4.8 KiB
JavaScript
// SVG recovery from the fixed REA script export only; no original installation/runtime access.
|
|
const fs = require('node:fs'), crypto = require('node:crypto')
|
|
const { parse } = require('@babel/parser')
|
|
const traverse = require('@babel/traverse').default
|
|
const archive = JSON.parse(fs.readFileSync('.tmp/rea-runtime-scripts.json', 'utf8'))
|
|
const cache = new Map()
|
|
const literal = node => !node ? undefined : node.type === 'TemplateLiteral' && !node.expressions.length ? node.quasis[0].value.cooked : ['StringLiteral', 'NumericLiteral', 'BooleanLiteral'].includes(node.type) ? node.value : undefined
|
|
const object = node => Object.fromEntries((node?.properties || []).filter(prop => prop.type === 'ObjectProperty').flatMap(prop => {
|
|
const value = literal(prop.value)
|
|
return value !== undefined ? [[prop.key.name ?? prop.key.value, value]] : []
|
|
}))
|
|
function get(file) {
|
|
if (cache.has(file)) return cache.get(file)
|
|
const source = archive.items.find(item => item.file_path.endsWith(file))?.source?.artifact
|
|
if (!source) return
|
|
if (crypto.createHash('sha256').update(source.text).digest('hex') !== source.sha256) throw Error('REA digest mismatch')
|
|
if (file === 'contexts-BMeWSUHk.js' && source.sha256 !== '5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b') throw Error('REA context identity changed')
|
|
if (file === 'src-DPDj7_qD.js' && source.sha256 !== '747e41d215375e1214c366213aea7e077d85421923029c61cbb6e9562be58de0') throw Error('REA icon identity changed')
|
|
const ast = parse(source.text, { sourceType: 'module' })
|
|
const result = { source, ast }
|
|
cache.set(file, result)
|
|
return result
|
|
}
|
|
function resolve(file, name, depth = 0) {
|
|
const source = get(file)
|
|
if (!source || depth > 8) return
|
|
let imported, declaration
|
|
traverse(source.ast, {
|
|
ImportDeclaration(p) { for (const spec of p.node.specifiers) if (spec.local.name === name && spec.type === 'ImportSpecifier') imported = { file: p.node.source.value.replace('./', ''), member: spec.imported.name } },
|
|
FunctionDeclaration(p) { if (p.node.id?.name === name) declaration = p },
|
|
AssignmentExpression(p) { if (p.node.left.name === name) declaration = p },
|
|
VariableDeclarator(p) { if (p.node.id?.name === name && p.node.init) declaration = p }
|
|
})
|
|
if (imported && get(imported.file)) {
|
|
let local
|
|
traverse(get(imported.file).ast, { ExportNamedDeclaration(p) { for (const spec of p.node.specifiers) if (spec.exported.name === imported.member) local = spec.local.name } })
|
|
if (local) return resolve(imported.file, local, depth + 1)
|
|
}
|
|
if (!declaration) return
|
|
const right = declaration.node.right ?? declaration.node.init
|
|
if (right?.type === 'CallExpression' && typeof literal(right.arguments[0]) === 'string' && right.arguments[1]?.type === 'Identifier') {
|
|
let nodes
|
|
traverse(source.ast, { AssignmentExpression(p) { if (p.node.left.name === right.arguments[1].name && p.node.right.type === 'ArrayExpression') nodes = p.node.right.elements } })
|
|
if (nodes) return { file, sha256: source.source.sha256, props: { viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' }, children: nodes.map(entry => ({ tag: literal(entry.elements[0]), props: object(entry.elements[1]) })) }
|
|
}
|
|
if (right?.type === 'CallExpression' && right.arguments[0]?.type === 'Identifier') return resolve(file, right.arguments[0].name, depth + 1)
|
|
let svg, paths = [], next
|
|
declaration.traverse({ CallExpression(p) {
|
|
const tag = p.node.arguments[0]
|
|
if (tag?.type === 'Identifier') { next = tag.name; return }
|
|
const kind = literal(tag)
|
|
if (!['svg', 'path', 'circle', 'rect', 'line', 'polyline', 'polygon', 'ellipse'].includes(kind)) return
|
|
const props = object(p.node.arguments[1])
|
|
if (kind === 'svg') svg = props
|
|
else paths.push({ tag: kind, props })
|
|
} })
|
|
if (svg && paths.length) return { file, sha256: source.source.sha256, props: svg, children: paths }
|
|
return next ? resolve(file, next, depth + 1) : undefined
|
|
}
|
|
const icons = {}
|
|
for (const [key, binding] of Object.entries({ chevronDown: 'Oo', chevronRight: 'yo', loading: 'xo', check: 'Ea', failure: 'Ba', close: 'Da', assignment: 'Pte' })) {
|
|
icons[key] = resolve('contexts-BMeWSUHk.js', binding) || null
|
|
}
|
|
if (Object.values(icons).some(icon => !icon)) throw Error('A selected source member icon was not recovered')
|
|
const output = 'src/renderer/src/workbuddy/subagents/icons.json'
|
|
const content = JSON.stringify({ version: '5.8.2', evidenceId: archive.evidence_id, icons }, null, 2) + '\n'
|
|
if (process.argv.includes('--check')) {
|
|
if (fs.readFileSync(output, 'utf8') !== content) throw Error('Recovered member icon drift')
|
|
} else fs.writeFileSync(output, content)
|
|
console.log(JSON.stringify({ icons: Object.keys(icons).length, sha256: crypto.createHash('sha256').update(content).digest('hex') }))
|