67 lines
3.6 KiB
JavaScript
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`)
|