1import chalk from 'chalk'; 2import { readFile, writeFile } from 'fs-extra'; 3import * as path from 'path'; 4import terminalLink from 'terminal-link'; 5 6import { EXPO_DIR } from '../Constants'; 7import logger from '../Logger'; 8import { AndroidProjectDependenciesUpdates } from './types'; 9import { addColorBasedOnSemverDiff, calculateSemverDiff } from './utils'; 10 11function replaceVersionInGradleFile( 12 body: string, 13 { 14 oldVersion, 15 newVersion, 16 fullName, 17 group, 18 name, 19 }: { 20 oldVersion: string; 21 newVersion: string; 22 fullName: string; 23 name: string; 24 group: string; 25 } 26): string { 27 let modifiedBody = body; 28 29 const regexVersionVariable = new RegExp(`${fullName}:\\\${?(\\w+)}?`, 'ig'); 30 31 // 'de.kevcodez:pubg-api-wrapper:$myVar' 32 // 'de.kevcodez:pubg-api-wrapper:${myVar}' 33 const versionWithVariableMatches = regexVersionVariable.exec(modifiedBody); 34 if (versionWithVariableMatches?.length === 2) { 35 const variableName = versionWithVariableMatches[1]; 36 37 const regexVariableDefinition = new RegExp( 38 `(${variableName}(\\s+)?=(\\s+)?('|")${oldVersion}('|"))`, 39 'ig' 40 ); 41 42 regexVariableDefinition 43 .exec(modifiedBody) 44 ?.filter((it) => it.includes(oldVersion)) 45 .forEach((match) => { 46 modifiedBody = modifiedBody.replace(match, match.replace(oldVersion, newVersion)); 47 }); 48 49 // val PUBG_API_WRAPPER by extra("0.8.1") 50 // eslint-disable-next-line no-useless-escape 51 const regexKotlinValExtra = new RegExp(`${variableName}.+\(("|')${oldVersion}("|')\)`); 52 regexKotlinValExtra 53 .exec(modifiedBody) 54 ?.filter((it) => it.includes(oldVersion)) 55 .forEach((match) => { 56 modifiedBody = modifiedBody.replace(match, match.replace(oldVersion, newVersion)); 57 }); 58 } 59 60 // compile 'de.kevcodez:pubg-api-wrapper:1.0.0' 61 const regexVersionInline = new RegExp(`${fullName}:${oldVersion}`, 'g'); 62 regexVersionInline 63 .exec(modifiedBody) 64 ?.filter((it) => it.includes(`${fullName}:${oldVersion}`)) 65 .forEach((match) => { 66 modifiedBody = modifiedBody.replace(match, `${fullName}:${newVersion}`); 67 }); 68 69 // id 'com.github.ben-manes.versions' version "0.21.0" 70 // id("com.github.ben-manes.versions") version "0.22.0" 71 const regexPluginVersionWithPrefix = new RegExp( 72 `${group}("|')\\)?(\\s+)?version(\\s+)?("|')${oldVersion}("|')` 73 ); 74 regexPluginVersionWithPrefix 75 .exec(modifiedBody) 76 ?.filter((it) => it.includes(oldVersion)) // filter out all groups not containing version 77 .forEach((match) => { 78 modifiedBody = modifiedBody.replace(match, match.replace(oldVersion, newVersion)); 79 }); 80 81 // compile group: 'de.kevcodez.pubg', name: 'pubg-api-wrapper', version: '0.8.1' 82 const regexDependencyWithVersionPrefix = new RegExp( 83 `${name}('|"),(\\s+)?version:(\\s+)('|")${oldVersion}('|")` 84 ); 85 regexDependencyWithVersionPrefix 86 .exec(modifiedBody) 87 ?.filter((it) => it.includes(oldVersion)) 88 .forEach((match) => { 89 modifiedBody = modifiedBody.replace(match, match.replace(oldVersion, newVersion)); 90 }); 91 92 return modifiedBody; 93} 94 95async function readGradleFiles( 96 updates: AndroidProjectDependenciesUpdates[] 97): Promise<Record<string, string>> { 98 const buildFiles = ( 99 await Promise.all( 100 updates.map(async ({ report: { gradleFilePath } }) => [ 101 gradleFilePath, 102 await readFile(gradleFilePath, 'utf-8'), 103 ]) 104 ) 105 ).reduce<Record<string, string>>((acc, [filePath, content]) => { 106 acc[filePath] = content; 107 return acc; 108 }, {}); 109 110 return buildFiles; 111} 112 113async function writeGradleFiles(gradleFiles: Record<string, string>) { 114 await Promise.all( 115 Object.entries(gradleFiles).map( 116 async ([filePath, content]) => await writeFile(filePath, content) 117 ) 118 ); 119} 120 121export async function updateGradleDependencies( 122 updatesList: AndroidProjectDependenciesUpdates[] 123): Promise<void> { 124 logger.log(chalk.white.bold(`\nUpdating gradle files.`)); 125 const buildFiles = await readGradleFiles(updatesList); 126 for (const updates of updatesList) { 127 if (updates.updates.length === 0) { 128 continue; 129 } 130 131 logger.log( 132 `\n Updating %s native dependencies in file: %s`, 133 chalk.blue(updates.report.projectName), 134 terminalLink( 135 chalk.italic.grey(path.relative(EXPO_DIR, updates.report.gradleFilePath)), 136 updates.report.gradleFilePath 137 ) 138 ); 139 140 let buildFile = buildFiles[updates.report.gradleFilePath]; 141 for (const singleUpdate of updates.updates) { 142 logger.log( 143 ` ▶︎ ${chalk.blueBright(singleUpdate.fullName)}:${ 144 singleUpdate.oldVersion 145 } ➡️ ${addColorBasedOnSemverDiff( 146 singleUpdate.newVersion, 147 calculateSemverDiff(singleUpdate.oldVersion, singleUpdate.newVersion) 148 )}` 149 ); 150 buildFile = replaceVersionInGradleFile(buildFile, singleUpdate); 151 } 152 buildFiles[updates.report.gradleFilePath] = buildFile; 153 } 154 155 await writeGradleFiles(buildFiles); 156} 157