Files
XCDesktop/api/infra/moduleValidator.ts

114 lines
3.1 KiB
TypeScript
Raw Permalink Normal View History

2026-03-08 01:34:54 +08:00
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`
)
}