Last active
February 8, 2021 07:21
-
-
Save maruf89/419b4a929f9d133d3425ebfd482a79e9 to your computer and use it in GitHub Desktop.
Node update version in file git precommit hook – checks if certain staged files changed, and updates the version number of a correllating file, then git adds that file to be committed as well
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
// This precommit checks if certain staged files changed, and updates the version number of a correllating file, then `git add`s that file to be committed as well | |
// Useful for breaking caches like in WordPress which will serve the same CSS or JS if you forget to increment the version | |
// | |
// Example test usage after you added your program and regex match at the end of the `defaultArgs` (line 26) | |
// `git add assets/dist/community-directory.js` // A file correlating to `programs` (line 46) | |
// `node .git/hooks/precommit -s=true` // Will simulate a change | |
// `node .git/hooks/precommit -t=major` // Updates the version, increments the major value '1.5.5' => '1.6.0' | |
const fs = require('fs'); | |
const readline = require('readline'); | |
const myArgs = process.argv.slice(2); | |
var path = require('path'); | |
const childProcess = require('child_process'); | |
const exec = childProcess.exec; | |
const execSync = childProcess.execSync; | |
// Get's a list of staged files | |
const commitedFilesCommand = 'git diff --name-only --cached'; | |
// Assuming this file is located in rootDirectory/.git/hooks | |
var appDir = path.resolve(path.dirname(require.main.filename), '../..'); | |
const defaultArgs = { | |
type: 'minor', // Options are `year`, `major`, and `minor` | |
t: 'type', | |
delimiter: '.', | |
d: 'delimiter', | |
simulate: false, // call true to simulate what the change would look like | |
s: 'simulate', | |
// This is the program - To set your own replace 'plugin' or add new ones like 'cssPath + cssPattern' | |
pluginPath: 'community-directory.php',// The file which has the version we want to update | |
// a regex that matches the line with the version, the current version as a whole, and each individual version number, assuming there's three numbers | |
// Example string: " define( 'COMMUNITY_DIRECTORY_VERSION', '0.6.3' );" regex match => {"COMMUNITY_DIRECTORY_VERSION', '0.6.3", "0.6.3", "0", "6", "3"} | |
pluginPattern: /COMMUNITY_DIRECTORY_VERSION.+?(?:'|")((\d{1,4})\.(\d{1,2})\.(\d{1,2}))/, | |
}; | |
const parsedArgs = myArgs.reduce((acc, arg) => { | |
let [key, val] = arg.split('='); | |
if (/--/.test(key)) acc[key.substr(2)] = val; | |
else if (/-/.test(key)) acc[acc[key.substr(1)]] = val; | |
return acc; | |
}, { ...defaultArgs }); | |
// If any of these files change, run the program defined as the value | |
const programs = { | |
'assets/dist/community-directory.js': 'plugin', | |
'assets/dist/community-directory.css': 'plugin', | |
} | |
// Command to add the newly version-updated file | |
const gitAdd = file => execSync(`git add ${file}`); | |
exec(commitedFilesCommand, (err, stdout) => { | |
const files = stdout.split(/\n/g); | |
// Filter the programs so they're distinct | |
const promises = files.reduce((acc, watchFile) => { | |
const program = programs[watchFile]; | |
if (program && !acc.includes(program)) acc.push(program); | |
return acc; | |
}, []) | |
// Then map them to promises | |
.map(program => { | |
const file = path.resolve(appDir, parsedArgs[`${program}Path`]); | |
const pattern = parsedArgs[`${program}Pattern`]; | |
console.log(`CSS or JS file has changed, updating ${program} version in ${file}`); | |
if (!file) return Promise.reject(`Invalid program: ${program} and path ${program}Path`); | |
return getNewVersion(file, pattern).then(replaceLine.bind(null, file)).then((...args) => { | |
console.log(args); | |
gitAdd(file); | |
console.log(`ran 'git add ${file}'`); | |
}).catch(err => { | |
console.log('Error occurred'); | |
console.error(err); | |
}); | |
}); | |
Promise.all(promises).then(() => { | |
process.exit(0); | |
}) | |
}); | |
function readInterface(path) { | |
return readline.createInterface({ | |
input: fs.createReadStream(path), | |
crlfDelay: Infinity, | |
}); | |
} | |
function getNewVersion(file, pattern) { | |
return new Promise((resolve, reject) => { | |
const interface = readInterface(file); | |
let lineNum = 0; | |
interface.on('line', line => { | |
++lineNum; | |
if (pattern.test(line)) { | |
const method = methods[`on_${parsedArgs.type}`]; | |
if (!method) { | |
console.error('Invalid type called, must be one of: `year`, `major`, or `minor`'); | |
reject(); | |
} | |
// Slice off the first part that matched everything, which isn't needed | |
const parts = line.match(pattern).slice(1); | |
// Get the old version '0.6.3' | |
const oldV = parts.shift(); | |
// Build the new version => '0.6.4' | |
let newV = method(...parts).join(parsedArgs.delimiter); | |
// Replace the version | |
const newVersion = line.replace(oldV, newV); | |
// Stop reading the file | |
interface.close(); | |
resolve([newVersion, line]); | |
} | |
}); | |
}); | |
} | |
function replaceLine(file, [newLine, oldLine]) { | |
return new Promise((resolve, reject) => { | |
fs.readFile(file, 'utf8', (err, data) => { | |
if (err) return reject(err); | |
var result = data.replace(oldLine, newLine); | |
if (!parsedArgs.simulate) { | |
fs.writeFile(file, result, 'utf8', function (err) { | |
if (err) return reject(err) | |
resolve(`Changed from: ${oldLine} => ${newLine}`); | |
}); | |
} else | |
resolve(`Simulating change from: ${oldLine} => ${newLine}`); | |
}); | |
}); | |
} | |
const methods = { | |
pad: (num, size) => { | |
if (String(num).length > size) size = String(num).length; | |
var s = "000000000" + num; | |
return s.substr(s.length-size); | |
}, | |
increment: stringNum => methods.pad(+stringNum + 1, stringNum.length), | |
on_year: (year) => [methods.increment(year), '0', '0'], | |
on_major: (year, major) => [year, methods.increment(major), '0'] , | |
on_minor: (year, major, minor) => [year, major, methods.increment(minor)], | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment