- pnpm workspaces / tsconfig 双工程(node · web)/ 路径别名(§11.1) - eslint flat(§11.3 + §12.5 可机检项)/ dependency-cruiser 基线八条 + JCAI 增补(§11.4) - tools/module-tiers.json + gen-depcruise-tiers.ts 生成 tier-order 逐层规则 - tools/api-report.mjs 契约快照只留契约面(ADR-005) - docker-compose(PG16)+ db/init 四 schema;种子数据五岗位五员工冻结为常量 - docs/debt.md · docs/adr/ADR-005 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
/**
|
|
* 从 tools/module-tiers.json(编码规范 §4 层级表的机器形式)生成 depcruise 的
|
|
* tier-order 规则,写入 tools/depcruise-tiers.generated.cjs。
|
|
*
|
|
* 规则语义(§5.2):模块只准 import §4 表中**层级更低**者的 index.ts;
|
|
* 同层互不 import(协作走 port + app 装配),更高层当然也不行。
|
|
*
|
|
* 运行:pnpm gen:tiers(gates 的第一步,产物入库,diff 即规则变更)
|
|
*/
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const tiersPath = join(here, 'module-tiers.json');
|
|
const outPath = join(here, 'depcruise-tiers.generated.cjs');
|
|
|
|
type TierTable = { tiers: Record<string, string[]> };
|
|
|
|
const raw = readFileSync(tiersPath, 'utf8');
|
|
const table = JSON.parse(raw) as TierTable;
|
|
|
|
const modulesBase = '^apps/api/src/modules';
|
|
|
|
/** 模块名 → 层级 */
|
|
const tierOf = new Map<string, number>();
|
|
for (const [tier, modules] of Object.entries(table.tiers)) {
|
|
for (const m of modules) {
|
|
if (tierOf.has(m)) throw new Error(`模块 ${m} 在层级表中重复出现`);
|
|
tierOf.set(m, Number(tier));
|
|
}
|
|
}
|
|
|
|
const allModules = [...tierOf.keys()].sort();
|
|
|
|
type Rule = {
|
|
name: string;
|
|
comment: string;
|
|
severity: 'error';
|
|
from: { path: string };
|
|
to: { path: string };
|
|
};
|
|
|
|
const rules: Rule[] = [];
|
|
|
|
for (const from of allModules) {
|
|
const fromTier = tierOf.get(from)!;
|
|
// 不得依赖的模块 = 同层其它模块 + 全部更高层模块
|
|
const forbidden = allModules.filter((to) => to !== from && tierOf.get(to)! >= fromTier);
|
|
if (forbidden.length === 0) continue;
|
|
rules.push({
|
|
name: `tier-order-${from}`,
|
|
comment:
|
|
`模块 ${from}(L${fromTier})只准依赖更低层级模块;` +
|
|
`同层互需一律 port + app 装配(编码规范 §4 / §5.2)`,
|
|
severity: 'error',
|
|
from: { path: `${modulesBase}/${from}/` },
|
|
to: { path: `${modulesBase}/(${forbidden.join('|')})/` },
|
|
});
|
|
}
|
|
|
|
// 未登记模块拦截:modules/ 下出现层级表里没有的目录即报错(§16 检查清单第一条)
|
|
const knownGroup = allModules.join('|');
|
|
|
|
const banner = `/* eslint-disable */
|
|
/**
|
|
* 本文件由 tools/gen-depcruise-tiers.ts 从 tools/module-tiers.json 生成。
|
|
* 请勿手改 —— 改层级请改 module-tiers.json 与 编码规范.md §4,然后 pnpm gen:tiers。
|
|
*/
|
|
`;
|
|
|
|
const body =
|
|
banner +
|
|
'module.exports = ' +
|
|
JSON.stringify(
|
|
[
|
|
...rules,
|
|
{
|
|
name: 'module-registered-in-tiers',
|
|
comment: 'modules/ 下的每个模块必须登记在 tools/module-tiers.json(编码规范 §16)',
|
|
severity: 'error',
|
|
from: {},
|
|
to: {
|
|
path: `${modulesBase}/([^/]+)/`,
|
|
pathNot: `${modulesBase}/(${knownGroup})/`,
|
|
},
|
|
},
|
|
],
|
|
null,
|
|
2,
|
|
) +
|
|
';\n';
|
|
|
|
writeFileSync(outPath, body, 'utf8');
|
|
process.stdout.write(
|
|
`gen-depcruise-tiers: ${rules.length + 1} 条规则 → tools/depcruise-tiers.generated.cjs\n`,
|
|
);
|