mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-11-04 11:17:31 +00:00
56 lines
5.6 KiB
JavaScript
56 lines
5.6 KiB
JavaScript
import {readFile,writeFile} from 'node:fs/promises';
|
|
import {configurationField} from '../site/device/src/config-schema.js';
|
|
import {configurationPresentation} from '../site/device/src/config-field-presentation.js';
|
|
const sources=['modules/core_sys/src/app/tms_config_codec.cpp','platform/esp/arduino_common/src/app_config_tms_settings_extension.cpp','platform/esp/arduino_common/src/app_config_tms_ble_extension.cpp','modules/core_sys/include/app/tms_c6_config_extension.h'];
|
|
const contents=await Promise.all(sources.map(file=>readFile(new URL('../'+file,import.meta.url),'utf8')));
|
|
const fields=new Map();
|
|
const add=(key,source)=>{if(!fields.has(key))fields.set(key,new Set());fields.get(key).add(source);};
|
|
const table=(name,prefix)=>{
|
|
const matched=contents[0].match(new RegExp('constexpr const char\\* '+name+'\\[\\] = \\{([\\s\\S]*?)\\};'));
|
|
if(!matched)throw Error('Missing authoritative schema table: '+name);
|
|
for(const value of matched[1].matchAll(/"([a-z0-9_.]+)"/g))add(prefix+value[1],sources[0]);
|
|
};
|
|
table('kGeneralFields','');
|
|
const meshPrefixes=new Set([...contents[0].matchAll(/\bwriteMeshBase\(\s*encoder,\s*"([a-z0-9_.]+)"/g)].map(match=>match[1]));
|
|
const mqttPrefixes=new Set([...contents[0].matchAll(/\bwriteMqtt\(\s*encoder,\s*"([a-z0-9_.]+)"/g)].map(match=>match[1]));
|
|
if(!meshPrefixes.size||!mqttPrefixes.size)throw Error('Missing production mesh/MQTT prefix calls');
|
|
for(const prefix of meshPrefixes)table('kMeshBaseFields',prefix);
|
|
for(const prefix of mqttPrefixes)table('kMqttFields',prefix);
|
|
table('kMeshCoreFields','mc.');table('kMeshCoreChannelFields','mc.channel.{slot}.');table('kReticulumFields','rt.');table('kReticulumGroupFields','rt.group.{slot}.');
|
|
// C6 keys are generated by stringifying macro arguments, not literal writers.
|
|
// Cross-check write and consume declarations so an omission fails this audit.
|
|
const c6Writes=[...contents[3].matchAll(/C6_WRITE\(\s*([a-z_]+)\s*,\s*(?:boolean|u8|u16|text)\s*\)/g)].map(match=>match[1]);
|
|
const c6Consumes=[...contents[3].matchAll(/C6_(?:FIELD|TEXT)\(\s*([a-z_]+)\s*,(?:\s*[a-z0-9_]+\s*,)?\s*(\d+)\s*\)/g)];
|
|
const complete=Number(contents[3].match(/kComplete\s*=\s*\(1U\s*<<\s*(\d+)U\)/)?.[1]);
|
|
if(!complete||new Set(c6Writes).size!==complete||c6Consumes.length!==complete||new Set(c6Consumes.map(match=>match[1])).size!==complete||new Set(c6Consumes.map(match=>Number(match[2]))).size!==complete||
|
|
c6Consumes.some(match=>Number(match[2])>=complete||!c6Writes.includes(match[1])))throw Error('C6 write/consume inventory mismatch');
|
|
for(const field of c6Writes)add('c6_companion.'+field,sources[3]);
|
|
for(let index=0;index<sources.length;index++){
|
|
const source=contents[index];
|
|
for(const match of source.matchAll(/(?:encoder|writer)\.(?:boolean|u8|i8|u16|u32|i32|f32|text|blob|enumeration)\(\s*"([a-z0-9_.]+)"/g))add(match[1],sources[index]);
|
|
for(const match of source.matchAll(/write_(?:text_setting|blob_setting|string_as_blob|i32_setting|u32_setting|bool_setting)\(\s*writer,\s*"([a-z0-9_.]+)"/g))add(match[1],sources[index]);
|
|
for(const match of source.matchAll(/std::snprintf\(key,\s*sizeof\(key\),\s*"([a-z0-9_.]*%u[a-z0-9_.]*)"/g))add(match[1].replace('%u','{slot}'),sources[index]);
|
|
}
|
|
const rows=[...fields].sort(([a],[b])=>a.localeCompare(b)).map(([key,source])=>{
|
|
const actual=key.replace('{slot}','0'),schema=configurationField({key:actual,type:'u8'},configurationPresentation(actual,'en'));
|
|
return {key,label:schema.label,localized:!/[\u3400-\u9fff]/.test(schema.label),source:[...source]};
|
|
});
|
|
const missing=rows.filter(row=>!row.localized);
|
|
const escape=value=>value.replaceAll('|','\\|').replaceAll('\n',' ');
|
|
const markdown=['# 当前 TMS 字段展示覆盖核对','',
|
|
'此表由 scripts/audit_web_config_fields.mjs 从当前核心与配置扩展的字段表、字面量 writer 调用、带索引的键模板和 C6 宏生成字段提取,按字段族去重。C6 写入/消费名称及 presence 索引交叉检查;其字段只在相应板型能力下导出。槽位用 {slot} 表示,不表示只支持一个槽位。表中不读取设备或配置文件中的实际值。','',
|
|
'范围限制:这是一份已知 TMS 字段族和英文展示缺口清单,不证明所有 NVS/SD 独立设置都已纳入 TMS,不证明条件编译分支或每个字段的录入约束、硬件能力和读写行为已经验收。未检测到中文仅是必要条件,不能代替其他十种语言的完整性检查或术语质量审阅。字段类型与结构化控件仍需要逐项对照 writer 和设备验证器;尤其不能将 protobuf 模块 chunk 的原始编辑视为完整模块交互。','',
|
|
`当前提取 **${rows.length} 个字段族**;其中 **${missing.length} 个**英文展示仍包含中文回退,必须补齐。`,'',
|
|
'## 已确认的展示缺口','',...missing.map(row=>`- ${row.key}`),'',
|
|
'## 字段族清单','', '| 字段族 | 当前英文展示 | 中文回退 | 源码 |','|---|---|---|---|',
|
|
...rows.map(row=>`| ${row.key} | ${escape(row.label)} | ${row.localized?'无':'有'} | ${row.source.map(file=>`[${file}](../../../../${file})`).join(', ')} |`),''].join('\n');
|
|
const output=new URL('../docs/locales/zh-Hans/web-outdoor-management/15-配置字段覆盖核对.md',import.meta.url);
|
|
if(process.argv.includes('--check')){
|
|
const current=await readFile(output,'utf8');
|
|
if(current.replaceAll('\r\n','\n')!==markdown)throw Error('Configuration field inventory is stale; run node scripts/audit_web_config_fields.mjs and review the changes.');
|
|
console.log(`Known TMS families: ${rows.length}; English labels with Chinese fallback: ${missing.length}. Source inventory verified.`);
|
|
}else{
|
|
await writeFile(output,markdown);
|
|
console.log(`Known TMS families: ${rows.length}; English labels with Chinese fallback: ${missing.length}. Report updated.`);
|
|
}
|