1import chalk from 'chalk'; 2import inquirer from 'inquirer'; 3import stripAnsi from 'strip-ansi'; 4 5import logger from '../Logger'; 6import { 7 AndroidProjectDependenciesUpdates, 8 AndroidProjectReport, 9 GradleDependency, 10 GradleDependencyUpdate, 11} from './types'; 12import { 13 addColorBasedOnSemverDiff, 14 calculateSemverDiff, 15 getChangelogLink, 16 SemverDiff, 17} from './utils'; 18 19function generateAndroidProjectsSelectionChoice({ 20 projectName, 21 gradleReport: { outdated, exceeded, unresolved }, 22}: AndroidProjectReport) { 23 const deprecationMarking = 24 outdated.length > 0 ? ` ${chalk.yellow(`(${outdated.length} ⚠️ )`)}` : ''; 25 const hasUnresolvedOrExceedeedMarking = 26 exceeded.length > 0 || unresolved.length > 0 27 ? ` ${chalk.red(`(${exceeded.length + unresolved.length} ❗️)`)}` 28 : ''; 29 const name = `${projectName}${deprecationMarking}${hasUnresolvedOrExceedeedMarking}`; 30 return { 31 name, 32 value: projectName, 33 checked: outdated.length > 0 || exceeded.length > 0 || unresolved.length > 0, 34 }; 35} 36 37export async function promptForAndroidProjectsSelection( 38 reports: AndroidProjectReport[] 39): Promise<AndroidProjectReport[]> { 40 const { selectedProjects } = await inquirer.prompt<{ selectedProjects: string[] }>([ 41 { 42 type: 'checkbox', 43 name: 'selectedProjects', 44 message: `Choose which projects need updates. ${chalk.yellow( 45 '(<number> ⚠️ )' 46 )} shows how many dependencies are outdated. ${chalk.red( 47 '(<number> ❗️)' 48 )} shows other problems with respective project's dependencies.`, 49 choices: reports.map(generateAndroidProjectsSelectionChoice), 50 pageSize: Math.min(reports.length, (process.stdout.rows || 100) - 2), 51 }, 52 ]); 53 return reports.filter(({ projectName }) => selectedProjects.includes(projectName)); 54} 55 56async function promptForDependenciesVersions( 57 dependencies: GradleDependency[] 58): Promise<GradleDependencyUpdate[]> { 59 const updates: GradleDependencyUpdate[] = []; 60 61 const sortedDependencies = dependencies.sort((a, b) => a.fullName.localeCompare(b.fullName)); 62 for (const dependency of sortedDependencies) { 63 logger.log( 64 ` ▶︎ ${chalk.blueBright(dependency.fullName)} ${getChangelogLink( 65 dependency.fullName, 66 dependency.projectUrl 67 )}` 68 ); 69 const semverDiff = calculateSemverDiff(dependency.currentVersion, dependency.availableVersion); 70 const version = await promptForDependencyVersion(dependency, semverDiff); 71 if (version !== false) { 72 updates.push({ 73 name: dependency.name, 74 group: dependency.group, 75 fullName: dependency.fullName, 76 oldVersion: dependency.currentVersion, 77 newVersion: stripAnsi(version), 78 }); 79 } 80 } 81 82 return updates; 83} 84 85async function promptForDependencyVersion(dependency: GradleDependency, semverDiff: SemverDiff) { 86 let version = ( 87 await inquirer.prompt<{ version: string | boolean }>([ 88 { 89 type: 'list', 90 name: 'version', 91 message: `Choose version to update to:`, 92 choices: [ 93 { 94 name: `Latest version – (${addColorBasedOnSemverDiff( 95 dependency.availableVersion, 96 semverDiff 97 )})`, 98 value: dependency.availableVersion, 99 }, 100 { 101 name: `Don't update – (${dependency.currentVersion})`, 102 value: false, 103 }, 104 { 105 name: `Different version – will ask in the next step`, 106 value: true, 107 }, 108 ], 109 default: 0, 110 prefix: ` ${chalk.green('?')}`, 111 }, 112 ]) 113 ).version; 114 if (version === true) { 115 version = ( 116 await inquirer.prompt<{ version: string }>([ 117 { 118 type: 'input', 119 name: 'version', 120 message: `${dependency.fullName}:${dependency.currentVersion} ➡️ `, 121 default: addColorBasedOnSemverDiff(dependency.availableVersion, semverDiff), 122 prefix: ` ${chalk.green('?')}`, 123 }, 124 ]) 125 ).version; 126 } 127 return version; 128} 129 130async function promptForDependenciesUpdatesSelection( 131 report: AndroidProjectReport 132): Promise<GradleDependencyUpdate[]> { 133 const result: GradleDependencyUpdate[] = []; 134 135 logger.log(`\n● project: ${chalk.blue(report.projectName)}`); 136 result.push(...(await promptForDependenciesVersions(report.gradleReport.outdated))); 137 138 if (report.gradleReport.exceeded.length > 0) { 139 logger.log(` these dependencies ${chalk.yellow('exceed')} available version:`); 140 result.push(...(await promptForDependenciesVersions(report.gradleReport.exceeded))); 141 } 142 if (report.gradleReport.unresolved.length > 0) { 143 logger.log(` ${chalk.red('Failed to resolve')} these dependencies:`); 144 result.push(...(await promptForDependenciesVersions(report.gradleReport.unresolved))); 145 } 146 147 return result; 148} 149 150export async function promptForNativeDependenciesUpdates( 151 reports: AndroidProjectReport[] 152): Promise<AndroidProjectDependenciesUpdates[]> { 153 const selectedDependenciesUpdates: AndroidProjectDependenciesUpdates[] = []; 154 logger.log( 155 chalk.white.bold( 156 '\nProvide new native dependencies versions for each project. Check their changes in respective CHANGELOGs. To skip dependency provide no value.' 157 ) 158 ); 159 for (const report of reports) { 160 const updates = await promptForDependenciesUpdatesSelection(report); 161 if (updates.length > 0) { 162 selectedDependenciesUpdates.push({ 163 report, 164 updates, 165 }); 166 } 167 } 168 return selectedDependenciesUpdates; 169} 170