|
| 1 | +/** |
| 2 | + * Replaces version numbers in files. |
| 3 | + * |
| 4 | + * Usage: |
| 5 | + * node replace-versions.js <version> |
| 6 | + * |
| 7 | + * Parameters: |
| 8 | + * <version> The version number to replace. |
| 9 | + * --dry-run Prints the files that would be modified without actually modifying them. |
| 10 | + * --plugin Replaces the version in plugin files. |
| 11 | + * --docblock Replaces the version in docblocks. |
| 12 | + * --comment Replaces the version in comments. |
| 13 | + * --all Replaces the version in all files. |
| 14 | + */ |
| 15 | + |
| 16 | +const fs = require('fs'); |
| 17 | +const path = require('path'); |
| 18 | +const glob = require('glob'); |
| 19 | +const minimist = require('minimist'); |
| 20 | + |
| 21 | +const argv = minimist(process.argv.slice(2)); |
| 22 | + |
| 23 | +const version = argv._[0]; |
| 24 | + |
| 25 | +if (!version) { |
| 26 | + console.error('Please provide a version number.'); |
| 27 | + process.exit(1); |
| 28 | +} |
| 29 | + |
| 30 | +const dryRun = argv['dry-run']; |
| 31 | + |
| 32 | +let replaceType = 'all'; |
| 33 | + |
| 34 | +if (argv['plugin']) { |
| 35 | + replaceType = 'plugin'; |
| 36 | +} else if (argv['docblock']) { |
| 37 | + replaceType = 'docblock'; |
| 38 | +} |
| 39 | + |
| 40 | +const excludedDirs = [ |
| 41 | + 'node_modules/**', |
| 42 | + 'vendor/**', |
| 43 | + 'vendor-prefixed/**', |
| 44 | + 'bin/**', |
| 45 | +]; |
| 46 | + |
| 47 | +const versionPatterns = [ |
| 48 | + // Plugin file header. |
| 49 | + { |
| 50 | + regex: /^([\t ]*\*[\t ]*Version:[\t ]*)(.*)/gm, |
| 51 | + replacement: (newVersion) => `$1${newVersion}`, |
| 52 | + }, |
| 53 | + // Plugin main class version. |
| 54 | + { |
| 55 | + regex: /^(.*public[\t ]+static[\t ]+\$VER[\t ]*=[\t ]*['"])(.*)(['"];\s*)$/gm, |
| 56 | + replacement: (newVersion) => `$1${newVersion}$3`, |
| 57 | + }, |
| 58 | + // Plugin config array. |
| 59 | + { |
| 60 | + regex: /^([\t ]*'version'[\t ]*=>[\t ]*['"])(.*)(['"],)$/gm, |
| 61 | + replacement: (newVersion) => `$1${newVersion}$3`, |
| 62 | + }, |
| 63 | + // Plugin readme. |
| 64 | + { |
| 65 | + regex: /^(Stable tag:[\t ]*)(.*)/gm, |
| 66 | + replacement: (newVersion) => `$1${newVersion}`, |
| 67 | + }, |
| 68 | + // Plugin composer & package json. |
| 69 | + { |
| 70 | + regex: /(\s*"version":\s*")(\d+\.\d+\.\d+)(")/gm, |
| 71 | + replacement: (newVersion) => `$1${newVersion}$3`, |
| 72 | + }, |
| 73 | +]; |
| 74 | + |
| 75 | +const docblockPatterns = [ |
| 76 | + { |
| 77 | + // Only match if the version is currently X.X.X exactly (the string "@deprecated X.X.X"). |
| 78 | + regex: /((@deprecated|@since|@version)\s+)X.X.X/gm, |
| 79 | + replacement: (newVersion) => (_match, tag) => { |
| 80 | + return `${tag}${newVersion}`; |
| 81 | + }, |
| 82 | + }, |
| 83 | +]; |
| 84 | + |
| 85 | +const commentPatterns = [ |
| 86 | + { |
| 87 | + // Match // single line comments with X.X.X |
| 88 | + regex: /(\/\/.*\s+)X.X.X/gm, |
| 89 | + replacement: (newVersion) => (_match, prefix) => { |
| 90 | + return `${prefix}${newVersion}`; |
| 91 | + }, |
| 92 | + }, |
| 93 | + { |
| 94 | + // Match /* single line comments with X.X.X */ |
| 95 | + regex: /(\/\*.*\s+)X.X.X/gm, |
| 96 | + replacement: (newVersion) => (_match, prefix) => { |
| 97 | + return `${prefix}${newVersion}`; |
| 98 | + }, |
| 99 | + }, |
| 100 | + { |
| 101 | + // Match /** multi line comments (start with *\s) |
| 102 | + regex: /(\s+\*.*\s+)X.X.X/gm, |
| 103 | + replacement: (newVersion) => (_match, prefix) => { |
| 104 | + return `${prefix}${newVersion}`; |
| 105 | + }, |
| 106 | + }, |
| 107 | +]; |
| 108 | + |
| 109 | +/** |
| 110 | + * Update version in specified files with the given patterns. |
| 111 | + * |
| 112 | + * @param {string} filePath - Path to the file. |
| 113 | + * @param {string} newVersion - The new version number. |
| 114 | + * @param {boolean} dryRun - Indicate if this is a dry run. |
| 115 | + * @param {Array} patterns - Array of regex patterns to match and replace. |
| 116 | + */ |
| 117 | +function updateVersionInFile(filePath, newVersion, dryRun, patterns) { |
| 118 | + if (fs.existsSync(filePath)) { |
| 119 | + const contents = fs.readFileSync(filePath, 'utf8'); |
| 120 | + let newContents = contents; |
| 121 | + |
| 122 | + patterns.forEach((pattern) => { |
| 123 | + newContents = newContents.replace( |
| 124 | + pattern.regex, |
| 125 | + pattern.replacement(newVersion) |
| 126 | + ); |
| 127 | + }); |
| 128 | + |
| 129 | + if (newContents !== contents) { |
| 130 | + if (dryRun) { |
| 131 | + console.log(`${filePath}:`); |
| 132 | + console.log(newContents); |
| 133 | + } else { |
| 134 | + fs.writeFileSync(filePath, newContents, 'utf8'); |
| 135 | + } |
| 136 | + } |
| 137 | + } else { |
| 138 | + console.log(`No file found at ${filePath}`); |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +if (replaceType === 'all' || replaceType === 'plugin') { |
| 143 | + const pluginSlug = path.basename(process.cwd()); |
| 144 | + const pluginFile = process.cwd() + '/' + pluginSlug + '.php'; |
| 145 | + const boostrapFile = process.cwd() + '/bootstrap.php'; |
| 146 | + const readmeFile = process.cwd() + '/readme.txt'; |
| 147 | + const packageJsonFile = process.cwd() + '/' + 'package.json'; |
| 148 | + const composerJsonFile = process.cwd() + '/' + 'composer.json'; |
| 149 | + |
| 150 | + if (fs.existsSync(pluginFile)) { |
| 151 | + updateVersionInFile(pluginFile, version, dryRun, versionPatterns); |
| 152 | + } |
| 153 | + |
| 154 | + if (fs.existsSync(boostrapFile)) { |
| 155 | + updateVersionInFile(boostrapFile, version, dryRun, versionPatterns); |
| 156 | + } |
| 157 | + |
| 158 | + if (fs.existsSync(readmeFile)) { |
| 159 | + updateVersionInFile(readmeFile, version, dryRun, versionPatterns); |
| 160 | + } |
| 161 | + |
| 162 | + if (fs.existsSync(packageJsonFile)) { |
| 163 | + updateVersionInFile(packageJsonFile, version, dryRun, versionPatterns); |
| 164 | + } |
| 165 | + |
| 166 | + if (fs.existsSync(composerJsonFile)) { |
| 167 | + updateVersionInFile(composerJsonFile, version, dryRun, versionPatterns); |
| 168 | + } |
| 169 | +} |
| 170 | + |
| 171 | +if ( |
| 172 | + replaceType === 'all' || |
| 173 | + replaceType === 'docblock' || |
| 174 | + replaceType === 'comment' |
| 175 | +) { |
| 176 | + const files = glob.sync('**/*.php', { ignore: excludedDirs }); |
| 177 | + |
| 178 | + // One loop reduces the number of file system calls. |
| 179 | + files.forEach((file) => { |
| 180 | + if (replaceType === 'all' || replaceType === 'docblock') { |
| 181 | + updateVersionInFile(file, version, dryRun, docblockPatterns); |
| 182 | + } |
| 183 | + |
| 184 | + if (replaceType === 'all' || replaceType === 'comment') { |
| 185 | + updateVersionInFile(file, version, dryRun, commentPatterns); |
| 186 | + } |
| 187 | + }); |
| 188 | +} |
0 commit comments