1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
| #!/usr/bin/env node
const path = require('path');
const yargs = require('yargs');
const execa = require('execa');
const jsonfile = require('jsonfile');
const noop = () => {};
async function lernaCommand(command, options) {
const { devBranch } = options;
const branch = await getCurrentBranch();
if (branch !== devBranch) {
return Promise.reject(
`You should be in"${devBranch}" branch to detect changes but current branch is"${branch}".`
);
}
const latestVersion = await getLatestVersion();
const bumpVersion = async bump => {
await lernaVersion(latestVersion, bump);
const version = await getLernaVersion();
const packageJsonPath = path.resolve(__dirname, 'package.json');
const packageJson = await jsonfile.readFile(packageJsonPath);
packageJson.version = version;
await jsonfile.writeFile(packageJsonPath, packageJson, { spaces: 2 });
await exec('git', ['add', '-A']);
await exec('git', ['commit', '-m', 'Version bump.']);
return version;
};
const reject = e => {
if (typeof e === 'string') {
return Promise.reject(e);
}
return Promise.reject('Unable to detect any changes in packages, probably nothing has changed.');
};
switch (command) {
case 'publish': {
const { bump, skipVersion, releaseBranch } = options;
if (releaseBranch === devBranch) {
return Promise.reject('Release and development branches can\'t be the same.');
}
try {
const version = skipVersion ? await getLernaVersion() : await bumpVersion(bump);
await lernaPublish(latestVersion, version);
await exec('git', ['checkout', releaseBranch]);
await exec('git', ['merge', '--no-ff', devBranch, '-m', `Version ${version}.`]);
await exec('git', ['tag', '-a', version, '-m', `Version ${version}.`]);
await exec('git', ['checkout', devBranch]);
}
catch (e) {
return reject(e);
}
break;
}
case 'version': {
const { bump } = options;
try {
await bumpVersion(bump);
}
catch (e) {
return reject(e);
}
break;
}
case 'changed': {
try {
await lernaChanged(latestVersion);
}
catch (e) {
return reject(e);
}
break;
}
}
}
async function lernaPublish(since, version) {
if (since === version) {
return Promise.reject(`Unable to publish packages with same version ${version}.`);
}
return exec('lerna', ['publish', '--since', since, version, '--no-push', '--no-git-tag-version', '--yes']);
}
async function lernaVersion(since, bump) {
return exec('lerna', ['version', '--since', since, bump, '--no-push', '--no-git-tag-version', '--yes']);
}
async function lernaChanged(since) {
return exec('lerna', ['changed', '--since', since]);
}
async function patch() {
try {
await exec('git', ['apply', '-p3', '--directory', 'node_modules/@lerna/version', 'lerna-version-since.patch']);
}
catch (e) {
return Promise.reject('Lerna Gitflow patch is not applied (probably, it\'s already applied before).');
}
}
async function getCurrentBranch() {
const { stdout } = await exec('git', ['branch']);
const match = stdout.match(/\\* ([\\S]+)/);
if (match === null) {
return Promise.reject('Unable to detect current git branch.');
}
return match[1];
}
async function getLatestTaggedCommit() {
const { stdout } = await exec('git', ['rev-list', '--tags', '--max-count', 1]);
if (!stdout) {
return Promise.reject('Unable to find any tagged commit.');
}
return stdout;
}
async function getLatestVersion() {
const commit = await getLatestTaggedCommit();
const { stdout } = await exec('git', ['describe', '--tags', commit]);
return stdout;
}
async function getLernaVersion() {
const lernaJson = await jsonfile.readFile(path.resolve(__dirname, 'lerna.json'));
return lernaJson.version;
}
function exec(cmd, args, opts) {
console.log(`$ ${cmd} ${args.join(' ')}`);
const promise = execa(cmd, args, opts);
promise.stdout.pipe(process.stdout);
promise.stderr.pipe(process.stderr);
return promise;
}
yargs
.wrap(null)
.strict(true)
.help(true, 'Show help')
.version(false)
.fail((msg, error) => {
console.error(error);
if (msg) {
console.error(msg);
}
})
.demandCommand()
.command(
'publish <bump>',
'Bump and commit packages\' in development branch, then publish, merge into and tag in release branch',
yargs => yargs
.positional('bump', {
describe: 'Type of version update',
type: 'string'
})
.option('skip-version', {
describe: 'Skip version bumping and commiting in development branch',
type: 'boolean',
default: false
}),
opts => lernaCommand('publish', opts)
)
.command(
'version <bump>',
'Bump and commit packages\' version in development branch',
yargs => yargs
.positional('bump', {
describe: 'Type of version update',
type: 'string'
}),
opts => lernaCommand('version', opts)
)
.command(
'changes',
'Detect packages changes since latest release',
noop,
opts => lernaCommand('changed', opts)
)
.command('patch', 'Patch Lerna to use with Gitflow', noop, () => patch())
.options({
'dev-branch': {
describe: 'Name of git development branch',
type: 'string',
demandOption: true,
default: 'develop'
},
'release-branch': {
describe: 'Name of git release branch',
type: 'string',
demandOption: true,
default: 'master'
}
})
.parse(); |