import { getPluginConfigContent } from '@/scripts/shared' import { getYamlConfigContent, getPackageInNodeModulesDir } from '@ones-open/cli-utils' import { ABILITY_VERSION_JSON_NAME, DEFAULT_ABILITY_PACKAGE_NAME } from '@ones-open/cli-utils' import type { AbilityTemplateSchema, ProjectPluginConfigAbilitiesField, AbilityVersionJSON, RelateModule, } from '@ones-open/cli-utils' import { MODULE_TYPE } from '@ones-open/utils' import fse from 'fs-extra' import { join, relative } from 'path' import { cwd } from 'process' const { readJson } = fse type AbilityTypeMap = Map // generates 'type@version: ability fileName' map from local 'ability_version.json' async function generateAbilityTypeMapFromVersionJson( abilityPackagePath: string, ): Promise { const abilityJSONPath = join(abilityPackagePath, ABILITY_VERSION_JSON_NAME) const abilityList: AbilityVersionJSON = await readJson(abilityJSONPath) const map = new Map() for (const ability of abilityList) { const { ability_template_dir, ability_type, ability_version } = ability map.set(`${ability_type}@${ability_version}`, ability_template_dir) } return map } // find the ability template based on the current ability function getAbilityTemplateYamlPath( currentAbility: ProjectPluginConfigAbilitiesField, localAbilities: AbilityTypeMap, abilityDirPath: string, ) { const { version, templateName, abilityType } = currentAbility const ability_template_dir = localAbilities.get(`${abilityType}@${version}`) as string const abilityDirName = `${version}${templateName ? '-' + templateName : ''}` const abilityTemplatePath = join(abilityDirPath, ability_template_dir, abilityDirName) const abilityTemplateYamlPath = join(abilityTemplatePath, `${ability_template_dir}.yaml`) return abilityTemplateYamlPath } async function getAbilityTemplateRelateModule(abilityTemplateYamlPath: string) { const { templates } = (await getYamlConfigContent( abilityTemplateYamlPath, )) as AbilityTemplateSchema const templateAbilities = templates?.abilities const abilityConfig = templateAbilities?.[0] return abilityConfig?.relateModule || {} } function checkIsValidRelateModule(relateModule: RelateModule) { for (const key in relateModule) { const moduleType = relateModule[key] if (!(moduleType in MODULE_TYPE)) { return false } } return true } async function checkAbilityRelateModule(currentWorkingDirectory: string) { const pluginConfigContent = await getPluginConfigContent() const { abilities, modules } = pluginConfigContent const abilityPackagePath = getPackageInNodeModulesDir( currentWorkingDirectory, DEFAULT_ABILITY_PACKAGE_NAME, ) const abilityDirPath = join(abilityPackagePath, 'template') const localAbilityTypeMap = await generateAbilityTypeMapFromVersionJson(abilityPackagePath) const errorMessage: string[] = [] for (const ability of abilities as ProjectPluginConfigAbilitiesField[]) { const abilityTemplatePath = getAbilityTemplateYamlPath( ability, localAbilityTypeMap, abilityDirPath, ) const templateRelateModule = await getAbilityTemplateRelateModule(abilityTemplatePath) const { id } = ability const msg_prefix = `ability(${id})` // handle undefined/null const relateModule = ability.relateModule || {} if (!checkIsValidRelateModule(templateRelateModule)) { errorMessage.push( `${msg_prefix}: Invalid relateModule in ${relative( currentWorkingDirectory, abilityTemplatePath, )}`, ) continue } if (Object.keys(templateRelateModule).length !== Object.keys(relateModule).length) { errorMessage.push(`${msg_prefix}: the quantity of the relate module is incorrect`) continue } for (const [componentKey, moduleId] of Object.entries(relateModule)) { if (!(componentKey in templateRelateModule)) { errorMessage.push(`${msg_prefix}: configuration '${componentKey}' is a non-existent key`) continue } const module = modules?.find((module) => module.id === moduleId) if (!module) { errorMessage.push( `${msg_prefix}: configuration '${componentKey}' is binded with a non-existent module`, ) continue } if (templateRelateModule[componentKey] !== module.moduleType) { errorMessage.push( `${msg_prefix}: configuration '${componentKey}' is binded with an incorrect module type`, ) } } } if (errorMessage.length) { throw new Error('\n ' + errorMessage.join(' \n ')) } } async function checkExistAbility() { const pluginConfigContent = await getPluginConfigContent() return !!pluginConfigContent.abilities?.length } function getCheckAbilityRelateModuleTask(currentWorkingDirectory: string = cwd()) { const task = { title: 'Checking ability relate module', skip: async () => { const existAbility = await checkExistAbility() return !existAbility }, task: () => checkAbilityRelateModule(currentWorkingDirectory), } return task } export { getCheckAbilityRelateModuleTask }