feat: support multiple branches and distribution channels
- Allow to configure multiple branches to release from - Allow to define a distribution channel associated with each branch - Manage the availability on distribution channels based on git merges - Support regular releases, maintenance releases and pre-releases - Add the `addChannel` plugin step to make an existing release available on a different distribution channel BREAKING CHANGE: the `branch` option has been removed in favor of `branches` The new `branches` option expect either an Array or a single branch definition. To migrate your configuration: - If you want to publish package from multiple branches, please the configuration documentation - If you use the default configuration and want to publish only from `master`: nothing to change - If you use the `branch` configuration and want to publish only from one branch: replace `branch` by `branches` (`"branch": "my-release-branch"` => `"branches": "my-release-branch"`)
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
import test from 'ava';
|
||||
import {union} from 'lodash';
|
||||
import semver from 'semver';
|
||||
import proxyquire from 'proxyquire';
|
||||
|
||||
const getBranch = (branches, branch) => branches.find(({name}) => name === branch);
|
||||
const release = (branches, name, version) => getBranch(branches, name).tags.push({version});
|
||||
const merge = (branches, source, target, tag) => {
|
||||
getBranch(branches, target).tags = union(
|
||||
getBranch(branches, source).tags.filter(({version}) => !tag || semver.cmp(version, '<=', tag)),
|
||||
getBranch(branches, target).tags
|
||||
);
|
||||
};
|
||||
|
||||
test('Enforce ranges with branching release workflow', async t => {
|
||||
const branches = [
|
||||
{name: '1.x', tags: []},
|
||||
{name: '1.0.x', tags: []},
|
||||
{name: 'master', tags: []},
|
||||
{name: 'next', tags: []},
|
||||
{name: 'next-major', tags: []},
|
||||
{name: 'beta', prerelease: true, tags: []},
|
||||
{name: 'alpha', prerelease: true, tags: []},
|
||||
];
|
||||
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches});
|
||||
|
||||
let result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.0 <1.1.0', 'Can release only patch on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.0 <2.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.0', 'Can release only major on next-major');
|
||||
|
||||
release(branches, 'master', '1.0.0');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.0 <1.0.0', 'Cannot release on 1.0.x before a releasing on master');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.0', 'Cannot release on 1.x before a releasing on master');
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.0 <1.1.0', 'Can release only patch on master');
|
||||
|
||||
release(branches, 'master', '1.0.1');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
|
||||
merge(branches, 'master', 'next');
|
||||
merge(branches, 'master', 'next-major');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.0 <2.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.0', 'Can release only major on next-major');
|
||||
|
||||
release(branches, 'next', '1.1.0');
|
||||
release(branches, 'next', '1.1.1');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'next').range, '>=1.1.1 <2.0.0', 'Can release only patch or minor, > than 1.1.0 on next');
|
||||
|
||||
release(branches, 'next-major', '2.0.0');
|
||||
release(branches, 'next-major', '2.0.1');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
|
||||
|
||||
merge(branches, 'next-major', 'beta');
|
||||
release(branches, 'beta', '3.0.0-beta.1');
|
||||
merge(branches, 'beta', 'alpha');
|
||||
release(branches, 'alpha', '4.0.0-alpha.1');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
|
||||
|
||||
merge(branches, 'master', '1.0.x');
|
||||
merge(branches, 'master', '1.x');
|
||||
release(branches, 'master', '1.0.1');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.1 <1.1.0', 'Can release only patch, > than 1.0.1 on master');
|
||||
t.is(
|
||||
getBranch(result, '1.0.x').range,
|
||||
'>=1.0.1 <1.0.1',
|
||||
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
|
||||
);
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.1', 'Cannot release on 1.x before >= 1.2.0 is released on master');
|
||||
|
||||
release(branches, 'master', '1.0.2');
|
||||
release(branches, 'master', '1.0.3');
|
||||
release(branches, 'master', '1.0.4');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.0.4 <1.1.0', 'Can release only patch, > than 1.0.4 on master');
|
||||
t.is(
|
||||
getBranch(result, '1.0.x').range,
|
||||
'>=1.0.1 <1.0.2',
|
||||
'Cannot release on 1.0.x before >= 1.1.0 is released on master'
|
||||
);
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 1.2.0 is released on master');
|
||||
|
||||
merge(branches, 'next', 'master');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=1.2.0 <2.0.0', 'Can release only patch or minor, > than 1.2.0 on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=2.0.1', 'Can release any version, > than 2.0.1 on next-major');
|
||||
t.is(
|
||||
getBranch(result, '1.0.x').range,
|
||||
'>=1.0.1 <1.0.2',
|
||||
'Cannot release on 1.0.x before 1.0.x version from master are merged'
|
||||
);
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.0.2', 'Cannot release on 1.x before >= 2.0.0 is released on master');
|
||||
|
||||
merge(branches, 'master', '1.0.x', '1.0.4');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.0 <1.1.0', 'Cannot release on 1.x before >= 2.0.0 is released on master');
|
||||
|
||||
merge(branches, 'master', '1.x');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=1.1.1 <1.2.0', 'Can release only patch, > than 1.1.1 on master');
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.1 <1.1.1', 'Cannot release on 1.x before >= 2.0.0 is released on master');
|
||||
|
||||
merge(branches, 'next-major', 'next');
|
||||
merge(branches, 'next', 'master');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=2.0.1 <2.1.0', 'Can release only patch, > than 2.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=2.1.0 <3.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only major on next-major');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.1.1 <2.0.0', 'Can release on 1.x only within range');
|
||||
|
||||
merge(branches, 'beta', 'master');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, 'master').range, '>=2.0.1 <2.1.0', 'Can release only patch, > than 2.0.1 on master');
|
||||
t.is(getBranch(result, 'next').range, '>=2.1.0 <3.0.0', 'Can release only minor on next');
|
||||
t.is(getBranch(result, 'next-major').range, '>=3.0.0', 'Can release only major on next-major');
|
||||
|
||||
branches.push({name: '1.1.x', tags: []});
|
||||
merge(branches, '1.x', '1.1.x');
|
||||
result = (await getBranches({options: {branches}})).map(({name, range}) => ({name, range}));
|
||||
t.is(getBranch(result, '1.0.x').range, '>=1.0.4 <1.1.0', 'Can release on 1.0.x only within range');
|
||||
t.is(getBranch(result, '1.1.x').range, '>=1.1.1 <1.2.0', 'Can release on 1.1.x only within range');
|
||||
t.is(getBranch(result, '1.x').range, '>=1.2.0 <2.0.0', 'Can release on 1.x only within range');
|
||||
});
|
||||
|
||||
test('Throw SemanticReleaseError for invalid configurations', async t => {
|
||||
const branches = [
|
||||
{name: '123', range: '123', tags: []},
|
||||
{name: '1.x', tags: []},
|
||||
{name: 'maintenance-1', range: '1.x', tags: []},
|
||||
{name: '1.x.x', tags: []},
|
||||
{name: 'beta', prerelease: '', tags: []},
|
||||
{name: 'alpha', prerelease: 'alpha', tags: []},
|
||||
{name: 'preview', prerelease: 'alpha', tags: []},
|
||||
];
|
||||
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches});
|
||||
const errors = [...(await t.throws(getBranches({options: {branches}})))];
|
||||
|
||||
t.is(errors[0].name, 'SemanticReleaseError');
|
||||
t.is(errors[0].code, 'EMAINTENANCEBRANCH');
|
||||
t.truthy(errors[0].message);
|
||||
t.truthy(errors[0].details);
|
||||
t.is(errors[1].name, 'SemanticReleaseError');
|
||||
t.is(errors[1].code, 'EMAINTENANCEBRANCHES');
|
||||
t.truthy(errors[1].message);
|
||||
t.truthy(errors[1].details);
|
||||
t.is(errors[2].name, 'SemanticReleaseError');
|
||||
t.is(errors[2].code, 'EPRERELEASEBRANCH');
|
||||
t.truthy(errors[2].message);
|
||||
t.truthy(errors[2].details);
|
||||
t.is(errors[3].name, 'SemanticReleaseError');
|
||||
t.is(errors[3].code, 'EPRERELEASEBRANCHES');
|
||||
t.truthy(errors[3].message);
|
||||
t.truthy(errors[3].details);
|
||||
t.is(errors[4].name, 'SemanticReleaseError');
|
||||
t.is(errors[4].code, 'ERELEASEBRANCHES');
|
||||
t.truthy(errors[4].message);
|
||||
t.truthy(errors[4].details);
|
||||
});
|
||||
|
||||
test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
|
||||
const branches = [{name: 'master', tags: []}, {name: 'master', tags: []}];
|
||||
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches});
|
||||
|
||||
const errors = [...(await t.throws(getBranches({options: {branches}})))];
|
||||
|
||||
t.is(errors[0].name, 'SemanticReleaseError');
|
||||
t.is(errors[0].code, 'EDUPLICATEBRANCHES');
|
||||
t.truthy(errors[0].message);
|
||||
t.truthy(errors[0].details);
|
||||
});
|
||||
|
||||
test('Throw a SemanticReleaseError for each invalid branch name', async t => {
|
||||
const branches = [{name: '~master', tags: []}, {name: '^master', tags: []}];
|
||||
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches});
|
||||
|
||||
const errors = [...(await t.throws(getBranches({options: {branches}})))];
|
||||
|
||||
t.is(errors[0].name, 'SemanticReleaseError');
|
||||
t.is(errors[0].code, 'EINVALIDBRANCHNAME');
|
||||
t.truthy(errors[0].message);
|
||||
t.truthy(errors[0].details);
|
||||
t.is(errors[1].name, 'SemanticReleaseError');
|
||||
t.is(errors[1].code, 'EINVALIDBRANCHNAME');
|
||||
t.truthy(errors[1].message);
|
||||
t.truthy(errors[1].details);
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import test from 'ava';
|
||||
import expand from '../../lib/branches/expand';
|
||||
import {gitRepo, gitCommits, gitCheckout} from '../helpers/git-utils';
|
||||
|
||||
test('Expand branches defined with globs', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitCheckout('1.1.x', true, {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitCheckout('1.x.x', true, {cwd});
|
||||
await gitCommits(['Third'], {cwd});
|
||||
await gitCheckout('2.x', true, {cwd});
|
||||
await gitCommits(['Fourth'], {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
await gitCommits(['Fifth'], {cwd});
|
||||
await gitCheckout('pre/foo', true, {cwd});
|
||||
await gitCommits(['Sixth'], {cwd});
|
||||
await gitCheckout('pre/bar', true, {cwd});
|
||||
await gitCommits(['Seventh'], {cwd});
|
||||
await gitCheckout('beta', true, {cwd});
|
||||
await gitCommits(['Eighth'], {cwd});
|
||||
|
||||
const branches = [
|
||||
// Should match all maintenance type branches
|
||||
{name: '+([1-9])?(.{+([1-9]),x}).x'},
|
||||
{name: 'master', channel: 'latest'},
|
||||
{name: 'next'},
|
||||
{name: 'pre/{foo,bar}', channel: `\${name.replace(/^pre\\//g, '')}`, prerelease: true},
|
||||
// Should be ignored as there is no matching branches in the repo
|
||||
{name: 'missing'},
|
||||
// Should be ignored as the matching branch in the repo is already matched by `/^pre\\/(\\w+)$/gi`
|
||||
{name: '*/foo', channel: 'foo', prerelease: 'foo'},
|
||||
{name: 'beta', channel: `channel-\${name}`, prerelease: true},
|
||||
];
|
||||
|
||||
t.deepEqual(await expand({cwd}, branches), [
|
||||
{name: '1.1.x'},
|
||||
{name: '1.x.x'},
|
||||
{name: '2.x'},
|
||||
{name: 'master', channel: 'latest'},
|
||||
{name: 'next'},
|
||||
{name: 'pre/bar', channel: 'bar', prerelease: true},
|
||||
{name: 'pre/foo', channel: 'foo', prerelease: true},
|
||||
{name: 'beta', channel: 'channel-beta', prerelease: true},
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,202 @@
|
||||
import test from 'ava';
|
||||
import getTags from '../../lib/branches/get-tags';
|
||||
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, merge, changeAuthor} from '../helpers/git-utils';
|
||||
|
||||
test('Get the valid tags', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('foo', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['Second'], {cwd})));
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['Third'], {cwd})));
|
||||
await gitTagVersion('v3.0', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['Fourth'], {cwd})));
|
||||
await gitTagVersion('v3.0.0-beta.1', undefined, {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [{name: 'master'}]);
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
name: 'master',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[1].hash},
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[0].hash},
|
||||
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channel: undefined, gitHead: commits[3].hash},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Get the valid tags from multiple branches', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@1.x', undefined, {cwd});
|
||||
commits.push(...(await gitCommits(['Second'], {cwd})));
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.1.0@1.x', undefined, {cwd});
|
||||
await gitCheckout('1.x', true, {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
commits.push(...(await gitCommits(['Third'], {cwd})));
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.0@next', undefined, {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
commits.push(...(await gitCommits(['Fourth'], {cwd})));
|
||||
await gitTagVersion('v3.0.0@next', undefined, {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [
|
||||
{name: '1.x'},
|
||||
{name: 'master'},
|
||||
{name: 'next'},
|
||||
]);
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
name: '1.x',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash},
|
||||
{gitTag: 'v1.0.0@1.x', version: '1.0.0', channel: '1.x', gitHead: commits[0].hash},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', channel: undefined, gitHead: commits[1].hash},
|
||||
{gitTag: 'v1.1.0@1.x', version: '1.1.0', channel: '1.x', gitHead: commits[1].hash},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'master',
|
||||
tags: [
|
||||
...result[0].tags,
|
||||
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[2].hash},
|
||||
{gitTag: 'v2.0.0@next', version: '2.0.0', channel: 'next', gitHead: commits[2].hash},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'next',
|
||||
tags: [...result[1].tags, {gitTag: 'v3.0.0@next', version: '3.0.0', channel: 'next', gitHead: commits[3].hash}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Match the tag name from the begining of the string and the channel from the last "@"', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('prefix@v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('prefix@v1.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('prefix@v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('prefix@v2.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('other-prefix@v3.0.0', undefined, {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}]);
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
name: 'master',
|
||||
tags: [
|
||||
{gitTag: 'prefix@v1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash},
|
||||
{gitTag: 'prefix@v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[0].hash},
|
||||
{gitTag: 'prefix@v2.0.0', version: '2.0.0', channel: undefined, gitHead: commits[0].hash},
|
||||
{gitTag: 'prefix@v2.0.0@next', version: '2.0.0', channel: 'next', gitHead: commits[0].hash},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Return branches with and empty tags array if no valid tag is found', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('foo', undefined, {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v2.0.x', undefined, {cwd});
|
||||
await gitCommits(['Third'], {cwd});
|
||||
await gitTagVersion('v3.0', undefined, {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
|
||||
|
||||
t.deepEqual(result, [{name: 'master', tags: []}, {name: 'next', tags: []}]);
|
||||
});
|
||||
|
||||
test('Return branches with and empty tags array if no valid tag is found in history of configured branches', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitCheckout('other-branch', true, {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v2.0.0@next', undefined, {cwd});
|
||||
await gitTagVersion('v3.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v3.0.0@next', undefined, {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `prefix@v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
|
||||
|
||||
t.deepEqual(result, [{name: 'master', tags: []}, {name: 'next', tags: []}]);
|
||||
});
|
||||
|
||||
test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
|
||||
await gitTagVersion('1.0.0', undefined, {cwd});
|
||||
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}`}}, [{name: 'master'}]), [
|
||||
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}]},
|
||||
]);
|
||||
|
||||
await gitTagVersion('foo-1.0.0-bar', undefined, {cwd});
|
||||
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-\${version}-bar`}}, [{name: 'master'}]), [
|
||||
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}]},
|
||||
]);
|
||||
|
||||
await gitTagVersion('foo-v1.0.0-bar', undefined, {cwd});
|
||||
t.deepEqual(await getTags({cwd, options: {tagFormat: `foo-v\${version}-bar`}}, [{name: 'master'}]), [
|
||||
{
|
||||
name: 'master',
|
||||
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
|
||||
},
|
||||
]);
|
||||
|
||||
await gitTagVersion('(.+)/1.0.0/(a-z)', undefined, {cwd});
|
||||
t.deepEqual(await getTags({cwd, options: {tagFormat: `(.+)/\${version}/(a-z)`}}, [{name: 'master'}]), [
|
||||
{
|
||||
name: 'master',
|
||||
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
|
||||
},
|
||||
]);
|
||||
|
||||
await gitTagVersion('2.0.0-1.0.0-bar.1', undefined, {cwd});
|
||||
t.deepEqual(await getTags({cwd, options: {tagFormat: `2.0.0-\${version}-bar.1`}}, [{name: 'master'}]), [
|
||||
{
|
||||
name: 'master',
|
||||
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channel: undefined, gitHead: commits[0].hash}],
|
||||
},
|
||||
]);
|
||||
|
||||
await gitTagVersion('3.0.0-bar.2', undefined, {cwd});
|
||||
t.deepEqual(await getTags({cwd, options: {tagFormat: `\${version}-bar.2`}}, [{name: 'master'}]), [
|
||||
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channel: undefined, gitHead: commits[0].hash}]},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Get the tag on branch where commits have been rebased', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitCheckout('next', true, {cwd});
|
||||
commits.push(...(await gitCommits(['Second/n/n/commit body'], {cwd})));
|
||||
await gitTagVersion('v1.0.0@next', undefined, {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
await merge('next', {cwd});
|
||||
// Simulate GitHub "Rebase and Merge" by changing the committer info, which will result in a new commit sha and losing the tag
|
||||
await changeAuthor(commits[1].hash, {cwd});
|
||||
|
||||
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [{name: 'master'}, {name: 'next'}]);
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
name: 'master',
|
||||
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[1].hash}],
|
||||
},
|
||||
{
|
||||
name: 'next',
|
||||
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: commits[1].hash}],
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,307 @@
|
||||
import test from 'ava';
|
||||
import normalize from '../../lib/branches/normalize';
|
||||
|
||||
const toTags = versions => versions.map(version => ({version}));
|
||||
|
||||
test('Maintenance branches - initial state', t => {
|
||||
const maintenance = [{name: '1.x', tags: []}, {name: '1.1.x', tags: []}, {name: '1.2.x', tags: []}];
|
||||
const release = [{name: 'master', tags: []}];
|
||||
t.deepEqual(
|
||||
normalize
|
||||
.maintenance({maintenance, release})
|
||||
.map(({type, name, range, accept, channel, 'merge-range': maintenanceRange}) => ({
|
||||
type,
|
||||
name,
|
||||
range,
|
||||
accept,
|
||||
channel,
|
||||
'merge-range': maintenanceRange,
|
||||
})),
|
||||
[
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '1.1.x',
|
||||
range: '>=1.1.0 <1.0.0',
|
||||
accept: [],
|
||||
channel: '1.1.x',
|
||||
'merge-range': '>=1.1.0 <1.2.0',
|
||||
},
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '1.2.x',
|
||||
range: '>=1.2.0 <1.0.0',
|
||||
accept: [],
|
||||
channel: '1.2.x',
|
||||
'merge-range': '>=1.2.0 <1.3.0',
|
||||
},
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '1.x',
|
||||
range: '>=1.3.0 <1.0.0',
|
||||
accept: [],
|
||||
channel: '1.x',
|
||||
'merge-range': '>=1.3.0 <2.0.0',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Maintenance branches - cap range to first release present on default branch and not in any Maintenance one', t => {
|
||||
const maintenance = [
|
||||
{name: '1.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0'])},
|
||||
{name: 'name', range: '1.1.x', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1'])},
|
||||
{name: '1.2.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1'])},
|
||||
{name: '2.x.x', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.5.0'])},
|
||||
];
|
||||
const release = [
|
||||
{
|
||||
name: 'master',
|
||||
tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '1.2.1', '1.3.0', '1.4.0', '1.5.0', '1.6.0', '2.0.0']),
|
||||
},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize
|
||||
.maintenance({maintenance, release})
|
||||
.map(({type, name, range, accept, channel, 'merge-range': maintenanceRange}) => ({
|
||||
type,
|
||||
name,
|
||||
range,
|
||||
accept,
|
||||
channel,
|
||||
'merge-range': maintenanceRange,
|
||||
})),
|
||||
[
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: 'name',
|
||||
range: '>=1.1.1 <1.2.0',
|
||||
accept: ['patch'],
|
||||
channel: 'name',
|
||||
'merge-range': '>=1.1.0 <1.2.0',
|
||||
},
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '1.2.x',
|
||||
range: '>=1.2.1 <1.3.0',
|
||||
accept: ['patch'],
|
||||
channel: '1.2.x',
|
||||
'merge-range': '>=1.2.0 <1.3.0',
|
||||
},
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '1.x',
|
||||
range: '>=1.5.0 <1.6.0',
|
||||
accept: ['patch'],
|
||||
channel: '1.x',
|
||||
'merge-range': '>=1.3.0 <2.0.0',
|
||||
},
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '2.x.x',
|
||||
range: '>=2.0.0 <1.6.0',
|
||||
accept: [],
|
||||
channel: '2.x.x',
|
||||
'merge-range': '>=2.0.0 <3.0.0',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Maintenance branches - cap range to default branch last release if all release are also present on maintenance branch', t => {
|
||||
const maintenance = [
|
||||
{name: '1.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0'])},
|
||||
{name: '2.x.x', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])},
|
||||
];
|
||||
const release = [{name: 'master', tags: toTags(['1.0.0', '1.2.0', '1.3.0', '2.0.0'])}];
|
||||
|
||||
t.deepEqual(
|
||||
normalize
|
||||
.maintenance({maintenance, release})
|
||||
.map(({type, name, range, accept, channel, 'merge-range': maintenanceRange}) => ({
|
||||
type,
|
||||
name,
|
||||
range,
|
||||
accept,
|
||||
channel,
|
||||
'merge-range': maintenanceRange,
|
||||
})),
|
||||
[
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '1.x',
|
||||
range: '>=1.3.0 <2.0.0',
|
||||
accept: ['patch', 'minor'],
|
||||
channel: '1.x',
|
||||
'merge-range': '>=1.0.0 <2.0.0',
|
||||
},
|
||||
{
|
||||
type: 'maintenance',
|
||||
name: '2.x.x',
|
||||
range: '>=2.0.0 <2.0.0',
|
||||
accept: [],
|
||||
channel: '2.x.x',
|
||||
'merge-range': '>=2.0.0 <3.0.0',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - initial state', t => {
|
||||
const release = [{name: 'master', tags: []}, {name: 'next', tags: []}, {name: 'next-major', tags: []}];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=1.0.0 <1.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=1.1.0 <2.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=2.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - 3 release branches', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.0.2'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0'])},
|
||||
{name: 'next-major', tags: toTags(['1.0.0', '1.0.1', '1.0.2', '1.1.0', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=1.2.0 <2.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=2.1.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - 2 release branches', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.0.1', '1.1.0', '1.1.1', '1.2.0', '2.0.0', '2.0.1', '2.1.0'])},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=1.2.0 <2.0.0', accept: ['patch', 'minor'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=2.1.0', accept: ['patch', 'minor', 'major'], channel: 'next'},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - 1 release branches', t => {
|
||||
const release = [{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0'])}];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[{type: 'release', name: 'master', range: '>=1.2.0', accept: ['patch', 'minor', 'major'], channel: undefined}]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - cap ranges to first release only present on following branch', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0'])},
|
||||
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '1.2.0', '2.0.0', '2.1.0', '2.2.0'])},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=2.2.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - Handle missing previous tags in branch history', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '2.0.0'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '1.1.1', '1.2.0', '2.0.0'])},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=2.0.0 <3.0.0', accept: ['patch', 'minor'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=3.0.0', accept: ['patch', 'minor', 'major'], channel: 'next'},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - enforce release gaps after downstream merge', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
|
||||
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=2.1.0 <3.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=3.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Release branches - limit releases on 2nd and 3rd branche based on 1st branch last release', t => {
|
||||
const release = [
|
||||
{name: 'master', tags: toTags(['1.0.0', '1.1.0', '2.0.0', '3.0.0'])},
|
||||
{name: 'next', tags: toTags(['1.0.0', '1.1.0'])},
|
||||
{name: 'next-major', tags: toTags(['1.0.0', '1.1.0', '2.0.0'])},
|
||||
];
|
||||
|
||||
t.deepEqual(
|
||||
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
|
||||
[
|
||||
{type: 'release', name: 'master', range: '>=3.0.0 <3.1.0', accept: ['patch'], channel: undefined},
|
||||
{type: 'release', name: 'next', range: '>=3.1.0 <4.0.0', accept: ['patch', 'minor'], channel: 'next'},
|
||||
{
|
||||
type: 'release',
|
||||
name: 'next-major',
|
||||
range: '>=4.0.0',
|
||||
accept: ['patch', 'minor', 'major'],
|
||||
channel: 'next-major',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test('Prerelease branches', t => {
|
||||
const prerelease = [{name: 'beta', prerelease: true, tags: []}, {name: 'alpha', prerelease: 'preview', tags: []}];
|
||||
|
||||
t.deepEqual(normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})), [
|
||||
{type: 'prerelease', name: 'beta', channel: 'beta'},
|
||||
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user