// lib/render.mjs — literal-safe template render (replaces scope-lock/render.sh) // // 核心要求:{{key}} 占位替换;值中含 $、{、}、}} 不被二次解释(字面插入); // 先剥离 HTML 注释(模板引导文本);缺少变量则 throw(不静默留空)。 export function render(template, vars) { const withoutComments = template.replace(//g, '') return withoutComments.replace(/\{\{(\w+)\}\}/g, (_, key) => { // 用 Object.hasOwn 而非 `key in vars`:避免 {{constructor}} / {{toString}} 等 // 沿原型链命中继承属性、静默渲染出垃圾(应按"缺变量"抛错)。 if (!Object.hasOwn(vars, key)) throw new Error(`render: missing var "${key}"`) return String(vars[key]) // 字面插入,不二次解释 $ 或 {} }) } // 入口判定用 pathToFileURL 规范化 process.argv[1],使其与 import.meta.url 编码一致 // (路径含空格/非 ASCII/Windows 反斜杠时,字面 `file://${argv[1]}` 比较会失配)。 const { pathToFileURL } = await import('node:url') if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { const { readFileSync, writeFileSync } = await import('node:fs') const [tplPath, jsonPath, outPath] = process.argv.slice(2) const tpl = readFileSync(tplPath, 'utf8') const vars = JSON.parse(readFileSync(jsonPath, 'utf8')) writeFileSync(outPath, render(tpl, vars)) }