53 lines
6.5 KiB
JavaScript
53 lines
6.5 KiB
JavaScript
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import postcss from 'postcss'
|
|
import { parse } from '@babel/parser'
|
|
import traverseModule from '@babel/traverse'
|
|
const traverse = traverseModule.default ?? traverseModule
|
|
const root = process.cwd(), evidence = JSON.parse(fs.readFileSync(path.join(root, '.tmp/rea-runtime-scripts.json'), 'utf8'))
|
|
const cssDir = path.join(root, '.tmp/rea-css-recovered'), target = path.join(root, 'src/renderer/src/components')
|
|
const cache = new Map()
|
|
function source(base) { return evidence.items.find((item) => item.file_path.endsWith(base))?.source.artifact.text }
|
|
function ast(base) { if (!cache.has(base)) cache.set(base, parse(source(base), { sourceType: 'module' })); return cache.get(base) }
|
|
function wrapRule(rule) { let copy=rule.clone(); for (let parent=rule.parent;parent?.type==='atrule';parent=parent.parent) { const wrapper=parent.clone({nodes:[]});wrapper.append(copy);copy=wrapper }return copy.toString() }
|
|
function recoverCss(files, pattern, scope) {
|
|
const output=[], variables=new Set()
|
|
for (const file of files) postcss.parse(fs.readFileSync(path.join(cssDir,file),'utf8')).walkRules((rule) => {
|
|
if (!pattern.test(rule.selector)) return
|
|
if (rule.parent.type==='atrule' && /keyframes/.test(rule.parent.name)) return
|
|
const copy=rule.clone();copy.selectors=copy.selectors.map((selector)=>scope+' '+selector)
|
|
let tree=copy;for(let parent=rule.parent;parent?.type==='atrule';parent=parent.parent){const wrapper=parent.clone({nodes:[]});wrapper.append(tree);tree=wrapper}output.push(tree.toString())
|
|
})
|
|
for (const file of files) postcss.parse(fs.readFileSync(path.join(cssDir,file),'utf8')).walkAtRules((rule)=>{if(/keyframes/.test(rule.name)&&/home|quick-action/.test(rule.params))output.push(rule.toString())})
|
|
function collect(text){for(const match of text.matchAll(/var\((--[\w-]+)/g))variables.add(match[1])}collect(output.join(''))
|
|
const rules=[];postcss.parse(fs.readFileSync(path.join(cssDir,'tokens-Df-i-zsO.css'),'utf8')).walkRules((rule)=>rules.push(rule))
|
|
for(let pass=0;pass<10;pass++)for(const rule of rules)rule.walkDecls((decl)=>{if(variables.has(decl.prop))collect(decl.value)})
|
|
const light=new Map(),dark=new Map();for(const rule of rules){const map=rule.selector.includes('dark')?dark:rule.selector.includes(':root')?light:null;if(map)rule.walkDecls((decl)=>{if(variables.has(decl.prop))map.set(decl.prop,decl.value)})}
|
|
const serialize=(map)=>[...map].map(([key,value])=>key+':'+value).join(';')
|
|
return '/* Recovered from REA CSS ev_377c8790468b3c67dad8577aabb327f98d3140659f0fffdbabfcff5a40cc9e94; conditions and order retained. */\n'+scope+'{'+serialize(light)+'}\n:root[data-theme=dark] '+scope+'{'+serialize(dark)+'}\n'+output.join('\n')+'\n'
|
|
}
|
|
const home=recoverCss(['contexts-BkjwYSZT.css','home-D6iylhBV.css','src-Bh3YZ35H.css'],/\.(?:wb-home-(?:page|header|composer|prompt-list|route)|wb-scene-tabs|wb-segmented|quick-actions)/,'.source-home')
|
|
const picker=recoverCss(['src-CyXHA0IC.css'],/\.cr-(?:model-selector|menu)/,'.source-model-picker')
|
|
fs.writeFileSync(path.join(target,'source-home-recovered.css'),home)
|
|
fs.writeFileSync(path.join(target,'source-model-picker-recovered.css'),picker)
|
|
const icons={}
|
|
for(const [kind,local]of Object.entries({work:'zte',code:'hne',design:'Ote',prompt:'hte'})){
|
|
let member,base
|
|
traverse(ast('contexts-BMeWSUHk.js'),{ImportDeclaration(p){for(const s of p.node.specifiers)if(s.local.name===local){member=s.imported.name;base=p.node.source.value.replace('./','')}}})
|
|
let binding
|
|
traverse(ast(base),{ExportNamedDeclaration(p){for(const s of p.node.specifiers)if(s.exported.name===member)binding=s.local.name}})
|
|
let declaration
|
|
function find(name){traverse(ast(base),{AssignmentExpression(p){if(p.node.left.name===name)declaration=p},FunctionDeclaration(p){if(p.node.id?.name===name)declaration=p}})}find(binding)
|
|
if(declaration?.node.right?.type==='CallExpression'){const inner=declaration.node.right.arguments[0];if(inner?.type==='Identifier')find(inner.name)}
|
|
const svg={props:{},paths:[]}
|
|
declaration?.traverse({CallExpression(p){const args=p.node.arguments;if(args[0]?.type!=='TemplateLiteral')return;const tag=args[0].quasis[0].value.cooked;if(!['svg','path'].includes(tag))return;const values={};for(const prop of args[1]?.properties??[]){if(prop.type!=='ObjectProperty')continue;const key=prop.key.name??prop.key.value,value=prop.value;if(value.type==='TemplateLiteral'&&!value.expressions.length)values[key]=value.quasis[0].value.cooked;else if(['NumericLiteral','StringLiteral'].includes(value.type))values[key]=value.value}if(tag==='svg')svg.props=values;else svg.paths.push(values)}})
|
|
if(svg.paths.length)icons[kind]=svg
|
|
}
|
|
for(const[kind,name]of Object.entries({folder:'FolderIcon',check:'CheckIcon$1',down:'ChevronDownSmallIcon'})){
|
|
let declaration;traverse(ast('src-DbE1_mgM.js'),{AssignmentExpression(p){if(p.node.left.name===name)declaration=p},FunctionDeclaration(p){if(p.node.id?.name===name)declaration=p}})
|
|
const svg={props:{},paths:[]};declaration?.traverse({CallExpression(p){const args=p.node.arguments;if(args[0]?.type!=='TemplateLiteral')return;const tag=args[0].quasis[0].value.cooked;if(!['svg','path'].includes(tag))return;const values={};for(const prop of args[1]?.properties??[]){if(prop.type!=='ObjectProperty')continue;const key=prop.key.name??prop.key.value,value=prop.value;if(value.type==='TemplateLiteral'&&!value.expressions.length)values[key]=value.quasis[0].value.cooked;else if(['NumericLiteral','StringLiteral'].includes(value.type))values[key]=value.value}if(tag==='svg')svg.props=values;else svg.paths.push(values)}});if(svg.paths.length)icons[kind]=svg
|
|
}
|
|
const labels={};traverse(ast('zh-cn-Dfzn8myY.js'),{ObjectProperty(p){const key=p.node.key.value;if(typeof key!=='string'||!(/^(?:home\.|welcome\.mode\.|chatInput\.modelSelector\.)/.test(key)))return;const value=p.node.value;if(value.type==='TemplateLiteral'&&!value.expressions.length)labels[key]=value.quasis[0].value.cooked;else if(value.type==='StringLiteral')labels[key]=value.value}})
|
|
fs.writeFileSync(path.join(target,'source-home-reference.ts'),'// REA '+evidence.evidence_id+'; static labels/SVG only. No source runtime initialization.\nexport const sourceSceneIcons: Record<string,{props:Record<string,string|number>;paths:Record<string,string|number>[]}> = '+JSON.stringify(icons,null,2)+'\nconst labels: Record<string,string> = '+JSON.stringify(labels,null,2)+'\nexport const sourceHomeLabel = (key:string):string => labels[key]??key\n')
|
|
console.log(JSON.stringify({homeBytes:home.length,pickerBytes:picker.length,icons:Object.keys(icons),labels:Object.keys(labels).length}))
|