48 lines
3.4 KiB
JavaScript
48 lines
3.4 KiB
JavaScript
import fs from 'node:fs/promises'
|
|
import { createHash } from 'node:crypto'
|
|
import postcss from 'postcss'
|
|
const sha = text => createHash('sha256').update(text).digest('hex')
|
|
const archive = JSON.parse(await fs.readFile('.tmp/rea-runtime-scripts.json', 'utf8'))
|
|
const source = archive.items.find(item => item.file_path.endsWith('contexts-BMeWSUHk.js')).source.artifact
|
|
if (sha(source.text) !== '5f35b7489b107e21ef452c8ee88a95265756f6fc444841eef869228bbc44768b') throw Error('REA context SHA mismatch')
|
|
function definition(name) {
|
|
const start = source.text.indexOf(name + '=')
|
|
let quote = '', escaped = false, depth = 0
|
|
for (let i = start + name.length + 1; i < source.text.length; i++) {
|
|
const char = source.text[i]
|
|
if (quote) { if (escaped) escaped = false; else if (char === '\\') escaped = true; else if (char === quote) quote = ''; continue }
|
|
if (['`', '"', "'"].includes(char)) { quote = char; continue }
|
|
if (!depth && [',', '}'].includes(char)) return { start, end: i, value: source.text.slice(start + name.length + 1, i) }
|
|
if ('([{'.includes(char)) depth++
|
|
if (')]}'.includes(char)) depth--
|
|
}
|
|
throw Error('Unbounded source icon ' + name)
|
|
}
|
|
let icons = `/* REA ${archive.evidence_id}; contexts-BMeWSUHk.js ${source.sha256}. Literal SVG bodies recovered from archived source. */\nimport { useId, type SVGProps } from 'react'\nimport { jsx as sourceJsx, jsxs as sourceJsxs } from 'react/jsx-runtime'\n`
|
|
for (const name of ['SkillsIcon', 'PinIcon', 'UnpinIcon', 'SettingsIcon', 'SearchIcon$1', 'ClearFilledIcon']) {
|
|
const item = definition(name)
|
|
const body = item.value.replace(/^e=>/, '(e: SVGProps<SVGSVGElement>) =>').replaceAll('(0,bV.jsx)', 'sourceJsx').replaceAll('(0,bV.jsxs)', 'sourceJsxs').replaceAll('returnsource', 'return source').replaceAll('_Nt.useId', 'useId').replaceAll('...xV', 'width:16,height:16,xmlns:"http://www.w3.org/2000/svg"')
|
|
if (/bV\.|xV/.test(body)) throw Error('Unresolved source icon binding')
|
|
icons += `// UTF-16 [${item.start}, ${item.end})\nexport const ${name.replace('$1', '')} = ${body}\n`
|
|
}
|
|
await fs.mkdir('src/renderer/src/capabilities', { recursive: true })
|
|
await fs.writeFile('src/renderer/src/capabilities/SkillSourceIcons.tsx', icons)
|
|
const manifest = JSON.parse(await fs.readFile('.tmp/rea-css-recovered/manifest.json', 'utf8'))
|
|
let css = `/* REA ${manifest.sourceEvidenceId}; source modules _1mrhu_ and _s54xa_ only. */\n`
|
|
for (const file of ['contexts-BkjwYSZT.css', 'wb-input-add-CDNucW11.css']) {
|
|
const bytes = await fs.readFile('.tmp/rea-css-recovered/' + file)
|
|
// The export manifest pins the source digest; never silently consume a changed sheet.
|
|
if (!JSON.stringify(manifest).includes(sha(bytes))) throw Error('Unpinned CSS ' + file)
|
|
css += `/* ${file} sha256=${sha(bytes)} */\n`
|
|
const parsed = postcss.parse(bytes.toString())
|
|
parsed.walkRules(rule => {
|
|
if (!rule.selector.includes('_1mrhu_') && !rule.selector.includes('_s54xa_')) return
|
|
const clone = rule.clone()
|
|
clone.selector = clone.selectors.map(selector => '.yc-skill-picker ' + selector).join(',')
|
|
if (rule.parent.type === 'atrule') { const wrapper = rule.parent.clone({ nodes: [] }); wrapper.append(clone); css += wrapper.toString() + '\n' }
|
|
else css += clone.toString() + '\n'
|
|
})
|
|
}
|
|
await fs.writeFile('src/renderer/src/capabilities/skill-picker-source.css', css)
|
|
console.log(JSON.stringify({ source: archive.evidence_id, css: manifest.sourceEvidenceId, cssBytes: css.length }))
|