完善 server-rs DDD 重构计划与骨架
This commit is contained in:
155
scripts/check-server-rs-ddd-boundaries.mjs
Normal file
155
scripts/check-server-rs-ddd-boundaries.mjs
Normal file
@@ -0,0 +1,155 @@
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { basename, join, relative } from 'node:path';
|
||||
|
||||
const repoRoot = process.cwd();
|
||||
const cratesDir = join(repoRoot, 'server-rs', 'crates');
|
||||
const requiredModuleFiles = [
|
||||
'domain.rs',
|
||||
'commands.rs',
|
||||
'application.rs',
|
||||
'events.rs',
|
||||
'errors.rs',
|
||||
];
|
||||
const requiredLibModules = ['domain', 'commands', 'application', 'events', 'errors'];
|
||||
const forbiddenModuleWidePatterns = [
|
||||
{
|
||||
pattern: /\baxum::/u,
|
||||
message: 'module-* 不允许直接依赖 Axum',
|
||||
},
|
||||
{
|
||||
pattern: /\bspacetimedb::(?:table|reducer|procedure|ReducerContext|ProcedureContext|Table)\b/u,
|
||||
message: 'module-* 不允许声明 SpacetimeDB table/reducer/procedure 或直接操作表',
|
||||
},
|
||||
];
|
||||
const forbiddenCorePatterns = [
|
||||
{
|
||||
pattern: /\breqwest::/u,
|
||||
message: 'DDD 核心文件不允许直接依赖 reqwest',
|
||||
},
|
||||
{
|
||||
pattern: /\bplatform_oss::/u,
|
||||
message: 'DDD 核心文件不允许直接依赖 OSS adapter',
|
||||
},
|
||||
{
|
||||
pattern: /\bplatform_llm::/u,
|
||||
message: 'DDD 核心文件不允许直接依赖 LLM adapter',
|
||||
},
|
||||
{
|
||||
pattern: /\bspacetime_client::/u,
|
||||
message: 'DDD 核心文件不允许直接依赖 SpacetimeDB client adapter',
|
||||
},
|
||||
{
|
||||
pattern: /\bstd::fs\b/u,
|
||||
message: 'DDD 核心文件不允许直接访问文件系统',
|
||||
},
|
||||
{
|
||||
pattern: /\btokio::/u,
|
||||
message: 'DDD 核心文件不允许绑定异步运行时',
|
||||
},
|
||||
];
|
||||
|
||||
function normalizePath(path) {
|
||||
return path.replace(/\\/gu, '/');
|
||||
}
|
||||
|
||||
function readText(path) {
|
||||
return readFileSync(path, 'utf8');
|
||||
}
|
||||
|
||||
function listRustFiles(dir) {
|
||||
const files = [];
|
||||
|
||||
function walk(currentDir) {
|
||||
for (const name of readdirSync(currentDir)) {
|
||||
const fullPath = join(currentDir, name);
|
||||
const stat = statSync(fullPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walk(fullPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.endsWith('.rs')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(dir);
|
||||
return files;
|
||||
}
|
||||
|
||||
function collectModuleCrates() {
|
||||
return readdirSync(cratesDir)
|
||||
.filter((name) => name.startsWith('module-'))
|
||||
.filter((name) => existsSync(join(cratesDir, name, 'Cargo.toml')))
|
||||
.sort();
|
||||
}
|
||||
|
||||
const failures = [];
|
||||
const moduleCrates = collectModuleCrates();
|
||||
|
||||
for (const crateName of moduleCrates) {
|
||||
const crateDir = join(cratesDir, crateName);
|
||||
const srcDir = join(crateDir, 'src');
|
||||
const libPath = join(srcDir, 'lib.rs');
|
||||
|
||||
for (const fileName of requiredModuleFiles) {
|
||||
const filePath = join(srcDir, fileName);
|
||||
if (!existsSync(filePath)) {
|
||||
failures.push(`${crateName} 缺少 DDD 落位文件 src/${fileName}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (existsSync(libPath)) {
|
||||
const libText = readText(libPath);
|
||||
for (const moduleName of requiredLibModules) {
|
||||
const moduleDeclaration = new RegExp(
|
||||
`(?:^|\\n)\\s*(?:pub(?:\\([^)]*\\))?\\s+)?mod\\s+${moduleName}\\s*;`,
|
||||
'u',
|
||||
);
|
||||
if (!moduleDeclaration.test(libText)) {
|
||||
failures.push(`${crateName} 的 lib.rs 缺少模块声明 mod ${moduleName};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const rustFile of listRustFiles(srcDir)) {
|
||||
const relativePath = normalizePath(relative(repoRoot, rustFile));
|
||||
const fileName = basename(rustFile);
|
||||
const text = readText(rustFile);
|
||||
|
||||
if (fileName === 'mapper.rs') {
|
||||
failures.push(`${relativePath} 不能位于 module-*,mapper 只能放在 adapter crate`);
|
||||
}
|
||||
|
||||
for (const rule of forbiddenModuleWidePatterns) {
|
||||
if (rule.pattern.test(text)) {
|
||||
failures.push(`${relativePath}: ${rule.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const isDddCoreFile = requiredModuleFiles.some((name) =>
|
||||
relativePath.endsWith(`/src/${name}`),
|
||||
);
|
||||
if (!isDddCoreFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const rule of forbiddenCorePatterns) {
|
||||
if (rule.pattern.test(text)) {
|
||||
failures.push(`${relativePath}: ${rule.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('server-rs DDD boundary check failed:');
|
||||
for (const failure of failures) {
|
||||
console.error(`- ${failure}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`server-rs DDD boundary check passed for ${moduleCrates.length} module crate(s).`);
|
||||
Reference in New Issue
Block a user