#!/usr/bin/env node /** * 模块注册表前端逻辑的最小自检(仓库无单测基建,用 Node + typescript.transpileModule 跑纯函数)。 * * 覆盖 ticket #65 的可见行为: * 1. 「可新建审批链」下拉只含 enabled=1 的模块,按 sortNumber 升序,标题取注册表 title; * 2. module → detailKey 的映射收在 resolveDetailKey 一处,注册表里没有该模块时回退 module 本身, * 保证停用模块的历史审批链仍能回显标题、仍能打开详情; * 3. 编辑历史配置时把停用的模块补进下拉(带「已停用」标记),不重复添加。 * * 运行:node scripts/selfcheck-module-registry.mjs */ import { readFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const require = createRequire(import.meta.url); const ts = require('typescript'); const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const loadTs = (relative) => { const file = resolve(root, relative); const source = readFileSync(file, 'utf8'); const { outputText } = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2019 }, fileName: file }); const mod = { exports: {} }; new Function('exports', 'require', 'module', outputText)( mod.exports, require, mod ); return mod.exports; }; let failed = 0; const assert = (name, condition, detail) => { if (condition) { console.log(' ok ' + name); return; } failed += 1; console.log(' FAIL ' + name + (detail ? ' -> ' + detail : '')); }; const core = loadTs('src/utils/module-registry-core.ts'); // 与后端种子数据一致:10 个模块里只有 4 个 enabled=1 const REGISTRY = [ { module: 'gxmu_sjqn', detailKey: 'gxmu_sjqn', title: '十佳青年岗位能手', enabled: 1, sortNumber: 1 }, { module: 'gxmu_sjtbzbsj', detailKey: 'gxmu_sjtbzbsj', title: '十佳团支部书记', enabled: 0, sortNumber: 2 }, { module: 'gxmu_tzbcy_form', detailKey: 'gxmu_tzbcy_form', title: '挑战杯', enabled: 1, sortNumber: 7 }, { module: 'gxmu_qmgc_form', detailKey: 'gxmu_qmgc_form', title: '青马工程', enabled: 1, sortNumber: 8 }, { module: 'gxmu_wxxzx_project', detailKey: 'gxmu_wxxzx_project', title: '未来学术之星', enabled: 1, sortNumber: 9 }, { module: 'cms_manuscript', detailKey: 'cms_manuscript', title: '稿件', enabled: 0, sortNumber: 10 } ]; console.log('新建审批链的模块下拉(enabled=1,按 sortNumber)'); const options = core.toModuleOptions(REGISTRY); assert( '只含已启用模块', options.map((o) => o.value).join(',') === 'gxmu_sjqn,gxmu_tzbcy_form,gxmu_qmgc_form,gxmu_wxxzx_project', JSON.stringify(options) ); assert('label 取注册表标题', options[0].label === '十佳青年岗位能手'); assert('停用模块不出现', !options.some((o) => o.value === 'gxmu_sjtbzbsj')); console.log('审核列表的模块筛选项(同理,按注册表顺序)'); assert( '与新建下拉同源', core.toModuleOptions(REGISTRY).length === options.length ); console.log('module → detailKey 映射(收在一处)'); assert('走注册表 detailKey', core.resolveDetailKey(REGISTRY, 'gxmu_sjqn') === 'gxmu_sjqn'); assert( '注册表缺该模块时回退 module(停用模块的详情仍可解析)', core.resolveDetailKey(REGISTRY, 'legacy_module') === 'legacy_module' ); assert('空模块返回空', core.resolveDetailKey(REGISTRY, undefined) === undefined); console.log('标题解析(历史配置回显)'); assert('注册表命中', core.resolveModuleTitle(REGISTRY, 'gxmu_qmgc_form') === '青马工程'); assert('注册表未命中回退 module 本身', core.resolveModuleTitle(REGISTRY, 'legacy_module') === 'legacy_module'); console.log('编辑历史配置:把停用模块补进下拉'); const merged = core.ensureModuleOption(options, 'gxmu_sjtbzbsj', '十佳团支部书记'); assert('补进来的项标记已停用', merged[0]?.label === '十佳团支部书记(已停用)', JSON.stringify(merged[0])); assert('补进来的项在最前面且可回显', merged[0]?.value === 'gxmu_sjtbzbsj'); assert('原有选项保留', merged.length === options.length + 1); const duplicated = core.ensureModuleOption(options, 'gxmu_sjqn'); assert('已在选项里则不重复添加', duplicated.length === options.length); assert( '无 moduleName 时用 module 当标题', core.ensureModuleOption(options, 'legacy_module')[0]?.label === 'legacy_module(已停用)' ); if (failed) { console.error('\n自检失败:' + failed + ' 项'); process.exit(1); } console.log('\n自检通过:' + 5 + ' 组行为');