Initial commit
This commit is contained in:
113
api/infra/moduleValidator.ts
Normal file
113
api/infra/moduleValidator.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { readdirSync, statSync, existsSync } from 'fs'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import type { ApiModule } from './types.js'
|
||||
import type { ModuleDefinition } from '../../shared/modules/types.js'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
function getSharedModulesPath(): string | null {
|
||||
const possiblePaths = [
|
||||
join(__dirname, '../../shared/modules'),
|
||||
join(__dirname, '../../../shared/modules'),
|
||||
join((process as unknown as { resourcesPath?: string })?.resourcesPath || '', 'shared/modules'),
|
||||
]
|
||||
|
||||
for (const p of possiblePaths) {
|
||||
if (existsSync(p)) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export interface SharedModuleDefinition {
|
||||
id: string
|
||||
name: string
|
||||
backend?: {
|
||||
enabled?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
function needsBackendImplementation(moduleDef: SharedModuleDefinition): boolean {
|
||||
return moduleDef.backend?.enabled !== false
|
||||
}
|
||||
|
||||
async function loadModuleDefinitions(): Promise<SharedModuleDefinition[]> {
|
||||
const modules: SharedModuleDefinition[] = []
|
||||
const sharedModulesPath = getSharedModulesPath()
|
||||
|
||||
if (!sharedModulesPath) {
|
||||
return modules
|
||||
}
|
||||
|
||||
const entries = readdirSync(sharedModulesPath)
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = join(sharedModulesPath, entry)
|
||||
const stat = statSync(entryPath)
|
||||
|
||||
if (!stat.isDirectory()) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
const moduleExports = await import(`../../shared/modules/${entry}/index.js`)
|
||||
|
||||
for (const key of Object.keys(moduleExports)) {
|
||||
if (key.endsWith('_MODULE')) {
|
||||
const moduleDef = moduleExports[key] as ModuleDefinition
|
||||
if (moduleDef && moduleDef.id) {
|
||||
modules.push({
|
||||
id: moduleDef.id,
|
||||
name: moduleDef.name,
|
||||
backend: moduleDef.backend,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// 模块加载失败,跳过
|
||||
}
|
||||
}
|
||||
|
||||
return modules
|
||||
}
|
||||
|
||||
export async function validateModuleConsistency(apiModules: ApiModule[]): Promise<void> {
|
||||
const sharedModules = await loadModuleDefinitions()
|
||||
|
||||
if (sharedModules.length === 0) {
|
||||
console.log('[ModuleValidator] Skipping validation (shared modules not found, likely packaged mode)')
|
||||
return
|
||||
}
|
||||
|
||||
const apiModuleIds = new Set(apiModules.map((m) => m.metadata.id))
|
||||
const errors: string[] = []
|
||||
|
||||
for (const sharedModule of sharedModules) {
|
||||
const needsBackend = needsBackendImplementation(sharedModule)
|
||||
const hasApiModule = apiModuleIds.has(sharedModule.id)
|
||||
|
||||
if (needsBackend && !hasApiModule) {
|
||||
errors.push(
|
||||
`Module '${sharedModule.id}' is defined in shared but not registered in API modules`
|
||||
)
|
||||
}
|
||||
|
||||
if (!needsBackend && hasApiModule) {
|
||||
errors.push(
|
||||
`Module '${sharedModule.id}' has backend disabled but is registered in API modules`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Module consistency validation failed:\n - ${errors.join('\n - ')}`)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[ModuleValidator] ✓ Module consistency validated: ${sharedModules.length} shared, ${apiModules.length} API`
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user