114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
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`
|
|
)
|
|
}
|