import output from '@/output' import { convertUsernameField, fetchUserProfile, getLocalConfigContent } from '@/scripts/login' import { getPluginConfigContent } from '@/scripts/shared' import { AbilityDAO, getPackageInNodeModulesDir, validateAbilityConstraints, } from '@ones-open/cli-utils' import { ABILITY_VERSION_JSON_NAME, DEFAULT_ABILITY_PACKAGE_NAME } from '@ones-open/cli-utils' import type { LoginRequestParamsSchema, AbilityList, AbilityListRequestParams, } from '@ones-open/cli-utils' import type { ProjectLocalConfig, AbilityVersionJSON } from '@ones-open/cli-utils' import { cloneDeep } from '@senojs/lodash' import chalk from 'chalk' import enquirer from 'enquirer' import fse from 'fs-extra' import { join } from 'path' const { readJson } = fse export interface AbilityItemInfo { name: string rawName: string type: string version: string dirPath: string disabled?: string non_standard_ability?: boolean } type AxiosCallbackError = Error & { response?: { data: unknown } } export type AbilityInfoMap = Map async function parseLocalAbilities(abilityPackagePath: string): Promise { const abilityDirPath = join(abilityPackagePath, 'template') const abilityJSONPath = join(abilityPackagePath, ABILITY_VERSION_JSON_NAME) const abilityList: AbilityVersionJSON = await readJson(abilityJSONPath) const map = new Map() abilityList.forEach( ({ ability_version, ability_template_dir, ability_type, non_standard_ability }) => { const name = `${ability_template_dir}@${ability_version}` as string const dirPath = join(abilityDirPath, ability_template_dir) const abilityInfo: AbilityItemInfo = { name, rawName: ability_template_dir, type: ability_type, version: ability_version, dirPath, non_standard_ability, } return map.set(name, abilityInfo) }, ) return map } async function checkLogin(localConfig: ProjectLocalConfig) { const { platform: { baseURL, username, password }, } = localConfig const loginParams = { baseURL, password, ...convertUsernameField(username as string), } as LoginRequestParamsSchema try { const userProfile = await fetchUserProfile(loginParams) return userProfile } catch (e) { return undefined } } async function getUsableAbilityList(localConfig: ProjectLocalConfig, token: string) { const { platform: { baseURL }, local: { user_uuid, team_uuid }, } = localConfig const headers = { 'ones-plugin-id': 'built_in_apis', 'ones-User-Id': user_uuid, 'ones-Check-Id': team_uuid, 'Authorization': `Bearer ${token}`, } const requestParams = { baseURL, headers, } as AbilityListRequestParams try { const { data } = await AbilityDAO.getAbilityList(requestParams) if (data.code === 200) { return data.data as AbilityList } output.warn(`Unexpected response from server: \n${JSON.stringify(data, null, 2)}`) } catch (error) { if (error instanceof Error) { const responseError = (error as AxiosCallbackError).response?.data const errorMessage = responseError ? JSON.stringify(responseError, null, 2) : error.message output.warn(`The request for a list of available abilities failed\n${errorMessage}`) } } return false } async function signDisabledAbilities( abilityInfoMap: AbilityInfoMap, abilityList: AbilityList | false, ) { const set = new Set() const pluginConfig = await getPluginConfigContent() if (abilityList) { for (const ability of abilityList) { const { ability_type, ability_version } = ability set.add(`${ability_type}@${ability_version}`) } } for (const ability of abilityInfoMap.values()) { const { type, version, non_standard_ability } = ability const name = `${type}@${version}` if (abilityList && !set.has(name) && !non_standard_ability) { ability.disabled = `${chalk.red('(Ability do not match the current system version)')}` } else { try { await validateAbilityConstraints(name, { mode: 'add', pluginConfig, }) } catch (error) { if (typeof error === 'string') { ability.disabled = `${chalk.red(`(${error.replace(`[${name}] `, '')})`)}` } else { throw error } } } } } async function getAbilitiesInfo(currentWorkingDirectory: string) { const abilityPackagePath = getPackageInNodeModulesDir( currentWorkingDirectory, DEFAULT_ABILITY_PACKAGE_NAME, ) const localConfig = await getLocalConfigContent(currentWorkingDirectory) // assemble local data const localAbilities = await parseLocalAbilities(abilityPackagePath) const loginSuccess = await checkLogin(localConfig) const availableList = !!loginSuccess && (await getUsableAbilityList(localConfig, loginSuccess.user.token)) await signDisabledAbilities(localAbilities, availableList) return localAbilities } async function getTargetAbilityInfo(abilityInfoMap: AbilityInfoMap) { const abilities = Array.from(abilityInfoMap.values()).sort( ({ disabled: a }, { disabled: b }) => +Boolean(a) - +Boolean(b), ) if (abilities.every((item) => item.disabled)) { throw new Error('No available abilities') } const { targetAbilityName } = await enquirer.prompt<{ targetAbilityName: string }>({ type: 'autocomplete', name: 'targetAbilityName', message: 'Please select the ability you want to add:', // Prevents the object from being modified choices: cloneDeep(abilities), validate(value: string) { const abilityInfo = abilityInfoMap.get(value) if (abilityInfo?.disabled) { return 'Disabled abilities can not be selected' } return true }, }) const abilityInfo = abilityInfoMap.get(targetAbilityName) if (!abilityInfo) throw new Error(`Ability ${targetAbilityName} not found`) return abilityInfo } export { getAbilitiesInfo, getTargetAbilityInfo }