/* eslint-disable no-console */ import output from '@/output' import { hasWorkspacesInPackageJson } from '@/scripts' import { getNpm, getPackageInNodeModulesDir, semverMaxSatisfying, semverMajor, DEFAULT_ABILITY_PACKAGE_NAME, DEFAULT_OP_UTILS_PACKAGE_NAME, } from '@ones-open/cli-utils' import { execa } from 'execa' import fse from 'fs-extra' import Listr from 'listr' import type { ListrTask } from 'listr' import { join } from 'path' import { cwd, exit } from 'process' const { readJSON } = fse const npmCommand = getNpm() const ALLOW_TARGET_TYPES = new Set(['template']) async function validateUpdateCommandTargetingType(target: string) { if (!ALLOW_TARGET_TYPES.has(target)) throw new Error(`Invalid target type: ${target}`) return true } async function runPreRequireTask(rawTargetStr: string) { const preRequireTaskList: ListrTask[] = [ { title: 'Validating the input params', task: () => validateUpdateCommandTargetingType(rawTargetStr), }, { title: 'Validating the project template', task: async () => { if (!(await hasWorkspacesInPackageJson())) { throw new Error('current project template not support update template') } }, }, ] const preRequireTasks = new Listr(preRequireTaskList) return preRequireTasks.run() } async function updatePackage(pck: string, version: string, currentWorkingDirectory: string) { const updateArgs = ['install', `${pck}@${version}`] await execa(npmCommand, updateArgs, { cwd: currentWorkingDirectory }) } async function removePackage(pck: string, currentWorkingDirectory: string) { const args = ['uninstall', pck] await execa(npmCommand, args, { cwd: currentWorkingDirectory }) } async function getInnerPackageJSONContent(packageName: string, currentWorkingDirectory: string) { const packagePath = await getPackageInNodeModulesDir(currentWorkingDirectory, packageName) const packageJsonPath = join(packagePath, 'package.json') const packageJSONContent = await readJSON(packageJsonPath) return packageJSONContent } async function getMinorLastestVersion( packageName: string, currentVersion: string, currentWorkingDirectory: string, ) { const versionList = await getPacakgeVersions(packageName, currentWorkingDirectory) const major = semverMajor(currentVersion) const range = major != 0 ? `^${currentVersion}` : '~0' const version = semverMaxSatisfying(versionList, range) as string return version } async function getPacakgeVersions(packageName: string, currentWorkingDirectory: string) { const viewArgs = ['view', packageName, 'versions', '--json'] const { stdout } = await execa(npmCommand, viewArgs, { cwd: currentWorkingDirectory }) const versionList = JSON.parse(stdout) // filter informal version return versionList.filter((version: string) => version.indexOf('-') === -1) } // update template task function getUpdateTemplateTasks(currentWorkingDirectory: string) { const updatePackages = [DEFAULT_ABILITY_PACKAGE_NAME, DEFAULT_OP_UTILS_PACKAGE_NAME] const tasks: ListrTask[] = updatePackages.map((packageName) => ({ title: `Upgrading ${packageName}`, task: async (_, task: Listr.ListrTaskWrapper) => { let currentVersion try { const packageJSON = await getInnerPackageJSONContent(packageName, currentWorkingDirectory) currentVersion = packageJSON.version } catch (e) { throw new Error(`${packageName} does not exist`) } const minorLatestVersion = await getMinorLastestVersion( packageName, currentVersion, currentWorkingDirectory, ) if (currentVersion === minorLatestVersion) { task.skip(`The version of the ${packageName} is already the latest version`) return } if (!minorLatestVersion) { task.skip(`Could not find a newer official version of ${packageName}`) return } task.title = `Upgrade ${packageName} to '${minorLatestVersion}'` await updatePackage(packageName, minorLatestVersion, currentWorkingDirectory) if (packageName === DEFAULT_OP_UTILS_PACKAGE_NAME) { // remove @ones-open/utils from project dep await removePackage(packageName, currentWorkingDirectory) } }, })) return tasks } async function getTargetTasks(target: string, currentWorkingDirectory = cwd()) { return getUpdateTemplateTasks(currentWorkingDirectory) } async function executeTargetTask(targetTasks: ListrTask[]) { const tasks = new Listr(targetTasks) await tasks.run() } async function update(rawTargetStr: string) { let targetTasks try { await runPreRequireTask(rawTargetStr) output.completed('Run Pre-require tasks') } catch (error) { output.error(error, 'Command execution failed') exit(1) } try { targetTasks = await getTargetTasks(rawTargetStr) } catch (error) { output.error(error) } try { if (!targetTasks) throw new Error('Target tasks is not defined') await executeTargetTask(targetTasks) output.newline() output.success(`Update ${rawTargetStr}`) } catch (error) { output.error(error, `Execute update ${rawTargetStr} action`) } } export { update }