Files
ycode/scripts/rea/recover-project-templates.cjs

67 lines
3.6 KiB
JavaScript

// Static AST decoding of retained REA sources; no recovered code is executed.
const fs = require('node:fs')
const path = require('node:path')
const assert = require('node:assert/strict')
const crypto = require('node:crypto')
const parser = require('@babel/parser')
const ts = {
isStringLiteral: n => n?.type === 'StringLiteral',
isNoSubstitutionTemplateLiteral: n => n?.type === 'TemplateLiteral' && n.expressions.length === 0,
isArrayLiteralExpression: n => n?.type === 'ArrayExpression',
isObjectLiteralExpression: n => n?.type === 'ObjectExpression',
isPropertyAssignment: n => n?.type === 'ObjectProperty',
isBinaryExpression: n => n?.type === 'AssignmentExpression',
isIdentifier: n => n?.type === 'Identifier',
forEachChild: (node, callback) => { for (const value of Object.values(node)) { if (Array.isArray(value)) value.forEach(item => { if (item?.type) callback(item) }); else if (value?.type) callback(value) } }
}
const literal = node => node.type === 'TemplateLiteral' ? node.quasis[0].value.cooked : node.value
const evidence = JSON.parse(fs.readFileSync('.tmp/rea-todo20-project-page.json', 'utf8').replace(/^\uFEFF/, ''))
assert.equal(evidence.evidence_id, 'ev_89843a8b67f411d3297b5f3e428830393cd0fe8712603816cf8b7bbe442064d9')
function source(name) {
const item = evidence.normalized_result.scripts.items.find(item => item.file_path.endsWith(name))
assert.ok(item?.source.included)
const text = item.source.artifact.text
assert.equal(crypto.createHash('sha256').update(text).digest('hex'), item.source.artifact.sha256)
return parser.parse(text, { sourceType: 'module' })
}
function decode(node) {
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return literal(node)
if (ts.isArrayLiteralExpression(node)) return node.elements.map(decode)
if (ts.isObjectLiteralExpression(node)) {
const result = {}
for (const property of node.properties) {
assert.ok(ts.isPropertyAssignment(property))
const key = property.key.name ?? property.key.value
if (key !== 'icon') result[key] = decode(property.value)
}
return result
}
throw Error(`Nonliteral template node: ${node.type}`)
}
const definitions = new Map()
function visit(node) {
if (ts.isBinaryExpression(node) && node.operator === '=' && ts.isIdentifier(node.left) && ['vt', 'gt', 'St', 'bt', 'mt'].includes(node.left.name) && ts.isObjectLiteralExpression(node.right)) definitions.set(node.left.name, decode(node.right))
ts.forEachChild(node, visit)
}
visit(source('project-list-page-DpHRTtk8.js'))
assert.equal(definitions.size, 5)
const translations = new Map()
function strings(node) {
if (ts.isPropertyAssignment(node) && ts.isStringLiteral(node.key) && node.key.value.startsWith('collab.list.template.') && (ts.isStringLiteral(node.value) || ts.isNoSubstitutionTemplateLiteral(node.value))) translations.set(node.key.value, literal(node.value))
ts.forEachChild(node, strings)
}
strings(source('zh-cn-C7BTHfrQ.js'))
const templates = ['vt', 'gt', 'St', 'bt', 'mt'].map(key => {
const item = definitions.get(key)
for (const field of ['title', 'desc', 'prompt']) {
assert.ok(translations.has(item[field]), `Missing translation: ${item[field]}`)
item[field] = translations.get(item[field])
}
return item
})
const output = JSON.stringify(templates, null, 2) + '\n'
const destination = path.resolve('src/renderer/src/projects/project-templates.json')
if (process.argv.includes('--check')) assert.equal(fs.readFileSync(destination, 'utf8'), output)
else fs.writeFileSync(destination, output)
console.log(`PASS project templates: ${templates.length} source configurations and Chinese instructions recovered`)