Skip to content

Instantly share code, notes, and snippets.

@RaschidJFR
Last active September 20, 2020 06:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RaschidJFR/f915fdf6c1d6479e24085c92ac6b127b to your computer and use it in GitHub Desktop.
Save RaschidJFR/f915fdf6c1d6479e24085c92ac6b127b to your computer and use it in GitHub Desktop.
Print version information in different formats from `git describe`
#!/usr/bin/env node
// Use this script to parse version tags from git describe
// Use `node version -h` for help
const { spawn, exec } = require('child_process');
const program = require('commander');
program
.name('node version')
.usage('[--describe | --full | --numeric | --prerelease] [--tag <version.tag>]')
.option('-b, --build', 'Output describe info (without version): <PRE-RELEASE-PHASE(2)>.<PRE-RELEASE-VERSION(2)>.<COMMITS(2)>')
.option('-d, --describe', 'Similar format as doing `git describe --tags`: <VERSION>-<PRE-RELEASE>-<COMMITS>-<HASH>')
.option('-f, --full', 'Add prerelease info to output: <VERSION>-<PRE-RELEASE>')
.option('-n, --numeric', 'Encode version info into numeric format for Android: <VERSION(6)><PRE-RELEASE-PHASE(1)><PRE-RELEASE-VERSION(1)><COMMITS(2)>')
.option('-p, --prerelease', 'Output prerelease info instead of version')
.option('-r, --branch-release', 'Get the version string from current release/* branch. <COMMITS> will be counted from the last parent commit.')
.option('-t, --tag <string>', 'Use this version tag instead of the one from git describe');
program.parse(process.argv);
function getReleaseFromBranch() {
return new Promise((resolve) => {
const child = spawn('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
child.stdout.on('data', data => {
const branchName = data.toString();
const v = branchName.match(/(release|hotfix)\/(\d+\.\d+(\.\d+)*.*)/);
resolve(v && v[2]);
});
});
}
/**
* @param {string} prerelease In the format `phase.version`. E.g.: `beta.2`
*/
function encodePrerelease(prerelease) {
// 1 digit for prerelease version + 1 for prerelease phase
let numPrerelease = '0'; // 1 digit
let numPhase = '4';
if (prerelease) {
numPrerelease = prerelease.split('.')[1] || '0';
const prSegments = prerelease.split('.');
let phase = prSegments.shift();
if (/[a-z]+\d+/.test(phase)) {
const a = phase.match(/[a-z]+/)[0];
const n = phase.match(/[\d]+/)[0];
phase = a;
numPrerelease = n;
}
// Max number for versionCode is `2100000000` http://tiny.cc/f94uhz
numPhase = phase === 'dev' || phase === 'next' || phase === 'd' ? '0'
: phase === 'alpha' || phase === 'a' ? '1'
: phase === 'beta' || phase === 'b' ? '2'
: phase === 'rc' || phase === 'r' ? '3'
: '4';
}
return {
phase: numPhase,
version: numPrerelease
};
}
async function parseTag(tag, commits) {
const segments = tag.trim().toLowerCase().split('-');
const version = segments[0];
let prerelease = segments.length >= 2 ? segments[1] : '';
prerelease = prerelease ? prerelease.match(/[a-z]+(\.?\d+)?/) && prerelease.match(/[a-z]+(\.?\d+)?/)[0] : null;
if (!commits) {
commits = segments.length === 3 ?
Number(segments[2]) ? segments[2] : segments[1]
: segments.length === 2 ? segments[1]
: segments.length > 3 ? segments[segments.length - 2]
: null;
commits = commits ? commits.match(/^\d+$/) && commits.match(/^\d+$/)[0] : null;
}
// const hash = segments.length === 4 ? segments[3].trim() : null;
let output = version;
if (program.prerelease) {
output = prerelease;
} else if (program.full) {
output = `${version}-${prerelease}`;
} else if (program.describe) {
output = tag + (program.branchRelease ? `-${commits}` : '');
} else if (program.build) {
const { phase, version } = encodePrerelease(prerelease);
output = `${phase}.${version}.${commits || 0}`;
} else if (program.numeric) {
// 6 digits for version
let numVersion = version.split('.')
.map(n =>
n[1] ? n : '0' + n)
.join('');
for (let i = numVersion.length; i < 6; i++) {
numVersion += '0';
}
const { phase: numPhase, version: numPrerelease } = encodePrerelease(prerelease);
// 2 digits for commits
let numCommits = '00';
if (commits) {
numCommits = '';
for (let i = commits.length; i < 2; i++) {
numCommits += '0';
}
numCommits += commits;
}
numCommits = Number(numCommits) > 99 ? '99' : numCommits; // Clamp to 99
// <VERSION(6)><PRE-RELEASE-PHASE(1)><PRE-RELEASE-VERSION(1)><COMMITS(2)>
output = `${numVersion}${numPhase}${numPrerelease}${numCommits}`;
}
output = output.split('-').filter(s => !!s && s !== 'null').join('-');
console.log(output);
}
const t = (program.tag ? 1 : 0) + (program.branchRelease ? 1 : 0);
if (t > 1) {
console.error('Error: options [--tag --branch-release] cannot be combined');
process.exit(1);
}
if (program.tag) {
parseTag(program.tag);
} else {
if (program.branchRelease) {
exec('git merge-base HEAD master', (_err, strMergeBase, _stderr) => {
if (_err) throw new Error(_err);
exec(`git log ${strMergeBase.trim()}..HEAD --oneline | wc -l`, (_err, data, _stderr) => {
if (_err) throw new Error(_err);
const commitCount = data.trim();
getReleaseFromBranch().then(version => {
if (!version) {
console.error('This is not a release/ branch');
process.exit(1);
}
version = version.replace(/-[!\da-zA-Z\.]+$/, '').concat('-next');
parseTag(version, commitCount);
});
});
});
} else {
const child = spawn('git', ['describe', '--tags']);
child.stdout.on('data', data => {
const tags = data.toString();
parseTag(tags);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment