f6ce5267d5
- .segswitch 段控原子入 atoms.css + 登记 index.html - purchase/ui-mobile/ui-desktop 三屏:购买页支付渠道 switch(USDT→crypto 默认 / 人民币→alipay),套餐价随渠道分币种显示,删两步弹层 + nezha - 订单列表/详情集成进移动+桌面主框架 - icons.js 58 图标 sprite 单源 + gen_flutter_icons codegen + check-l1-sync 同源闸(图标同集 + l10n 完整) - pay-v2 开发决策日志 docs/pay-v2-dev-log.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
63 lines
3.1 KiB
JavaScript
63 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
||
// check-l1-sync.mjs —— L1 跨端同源闸(ds-flow)。零依赖。
|
||
// ① 图标集合:Flutter 图标集 == 原型 sprite 图标集(camel 化后逐名相等)。
|
||
// 真源 design/prototype/icons.js;镜像 client/lib/widgets/pangolin_icons.dart。
|
||
// ② l10n 完整性:app_text.dart 声明的每个 getter,6 个语言文件必须全部实现
|
||
// (谁少 key 就红 —— 防「某语言漏字串」漂移,如支付字串只加 zh/en)。
|
||
// 任一不通过 → 列出并 exit 1。
|
||
import { readFileSync, existsSync } from 'node:fs';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { dirname, resolve } from 'node:path';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const root = resolve(__dirname, '..');
|
||
let failed = 0;
|
||
|
||
/* ① 图标集合 */
|
||
const spriteKeys = new Set(
|
||
[...readFileSync(resolve(root, 'design/prototype/icons.js'), 'utf8')
|
||
.matchAll(/'([a-z0-9-]+)'\s*:\s*'</g)].map((m) => m[1].replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase()))
|
||
);
|
||
const dartKeys = new Set(
|
||
[...readFileSync(resolve(root, 'client/lib/widgets/pangolin_icons.dart'), 'utf8')
|
||
.matchAll(/LucideIcons\.([A-Za-z0-9]+)/g)].map((m) => m[1])
|
||
);
|
||
const iconMissing = [...spriteKeys].filter((k) => !dartKeys.has(k)).sort();
|
||
const iconExtra = [...dartKeys].filter((k) => !spriteKeys.has(k)).sort();
|
||
if (iconMissing.length === 0 && iconExtra.length === 0) {
|
||
console.log(`✓ 图标同集 OK —— ${spriteKeys.size} 个 sprite ↔ Flutter 逐名相等`);
|
||
} else {
|
||
failed++;
|
||
console.error('✗ 图标同集 MISMATCH:');
|
||
if (iconMissing.length) console.error(' sprite 有 / Flutter 缺:', iconMissing.join(', '));
|
||
if (iconExtra.length) console.error(' Flutter 多 / sprite 无:', iconExtra.join(', '));
|
||
console.error(' 修复:node design/codegen/gen_flutter_icons.mjs');
|
||
}
|
||
|
||
/* ② Flutter l10n 完整性 */
|
||
const L10N = resolve(root, 'client/lib/l10n');
|
||
const BASE = resolve(L10N, 'app_text.dart');
|
||
const LANGS = ['zh', 'en', 'ja', 'ko', 'ru', 'es'];
|
||
const getterSet = (src) => new Set([...src.matchAll(/\bString\s+get\s+(\w+)\b/g)].map((m) => m[1]));
|
||
if (!existsSync(BASE)) {
|
||
console.log('· l10n 完整性:跳过(无 app_text.dart)');
|
||
} else {
|
||
// canonical = 仅抽象 getter(`String get X;` 分号结尾);块体默认 `String get X {…}` 是继承默认,不要求 langs 实现
|
||
const canonical = new Set([...readFileSync(BASE, 'utf8').matchAll(/\bString\s+get\s+(\w+)\s*;/g)].map((m) => m[1]));
|
||
const gaps = [];
|
||
for (const lang of LANGS) {
|
||
const f = resolve(L10N, `strings_${lang}.dart`);
|
||
if (!existsSync(f)) { gaps.push(` ${lang}: 文件缺失`); continue; }
|
||
const miss = [...canonical].filter((k) => !getterSet(readFileSync(f, 'utf8')).has(k)).sort();
|
||
if (miss.length) gaps.push(` ${lang}: 缺 ${miss.length} 个 → ${miss.slice(0, 12).join(', ')}${miss.length > 12 ? ' …' : ''}`);
|
||
}
|
||
if (gaps.length === 0) {
|
||
console.log(`✓ l10n 完整 OK —— ${canonical.size} 个 getter × ${LANGS.length} 语言全实现`);
|
||
} else {
|
||
failed++;
|
||
console.error('✗ l10n 不完整(某语言漏 key):');
|
||
gaps.forEach((g) => console.error(g));
|
||
}
|
||
}
|
||
process.exit(failed ? 1 : 0);
|