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'},
|
||||
]);
|
||||
});
|
||||
+4
-3
@@ -29,6 +29,7 @@ test.serial('Pass options to semantic-release API', async t => {
|
||||
'',
|
||||
'-b',
|
||||
'master',
|
||||
'next',
|
||||
'-r',
|
||||
'https://github/com/owner/repo.git',
|
||||
'-t',
|
||||
@@ -68,7 +69,7 @@ test.serial('Pass options to semantic-release API', async t => {
|
||||
|
||||
const exitCode = await cli();
|
||||
|
||||
t.is(run.args[0][0].branch, 'master');
|
||||
t.deepEqual(run.args[0][0].branches, ['master', 'next']);
|
||||
t.is(run.args[0][0].repositoryUrl, 'https://github/com/owner/repo.git');
|
||||
t.is(run.args[0][0].tagFormat, `v\${version}`);
|
||||
t.deepEqual(run.args[0][0].plugins, ['plugin1', 'plugin2']);
|
||||
@@ -92,7 +93,7 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
|
||||
const argv = [
|
||||
'',
|
||||
'',
|
||||
'--branch',
|
||||
'--branches',
|
||||
'master',
|
||||
'--repository-url',
|
||||
'https://github/com/owner/repo.git',
|
||||
@@ -110,7 +111,7 @@ test.serial('Pass options to semantic-release API with alias arguments', async t
|
||||
|
||||
const exitCode = await cli();
|
||||
|
||||
t.is(run.args[0][0].branch, 'master');
|
||||
t.deepEqual(run.args[0][0].branches, ['master']);
|
||||
t.is(run.args[0][0].repositoryUrl, 'https://github/com/owner/repo.git');
|
||||
t.is(run.args[0][0].tagFormat, `v\${version}`);
|
||||
t.deepEqual(run.args[0][0].plugins, ['plugin1', 'plugin2']);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import test from 'ava';
|
||||
import {maintenance, prerelease, release} from '../../lib/definitions/branches';
|
||||
|
||||
test('A "maintenance" branch is identified by having a "range" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', t => {
|
||||
t.true(maintenance.filter({name: '1.x.x'}));
|
||||
t.true(maintenance.filter({name: '1.0.x'}));
|
||||
t.true(maintenance.filter({name: '1.x'}));
|
||||
t.true(maintenance.filter({name: 'some-name', range: '1.x.x'}));
|
||||
t.true(maintenance.filter({name: 'some-name', range: '1.1.x'}));
|
||||
t.true(maintenance.filter({name: 'some-name', range: ''}));
|
||||
t.true(maintenance.filter({name: 'some-name', range: null}));
|
||||
t.true(maintenance.filter({name: 'some-name', range: false}));
|
||||
|
||||
t.false(maintenance.filter({name: 'some-name'}));
|
||||
t.false(maintenance.filter({name: '1.0.0'}));
|
||||
t.false(maintenance.filter({name: 'x.x.x'}));
|
||||
});
|
||||
|
||||
test('A "maintenance" branches must have a "range" property formatted like "N.x", "N.x.x" or "N.N.x"', t => {
|
||||
t.true(maintenance.branchValidator({name: 'some-name', range: '1.x.x'}));
|
||||
t.true(maintenance.branchValidator({name: 'some-name', range: '1.1.x'}));
|
||||
|
||||
t.false(maintenance.branchValidator({name: 'some-name', range: '^1.0.0'}));
|
||||
t.false(maintenance.branchValidator({name: 'some-name', range: '>=1.0.0 <2.0.0'}));
|
||||
t.false(maintenance.branchValidator({name: 'some-name', range: '1.0.0'}));
|
||||
t.false(maintenance.branchValidator({name: 'some-name', range: 'wrong-range'}));
|
||||
t.false(maintenance.branchValidator({name: 'some-name', range: ''}));
|
||||
t.false(maintenance.branchValidator({name: 'some-name', range: null}));
|
||||
t.false(maintenance.branchValidator({name: 'some-name', range: false}));
|
||||
});
|
||||
|
||||
test('The "maintenance" branches must have unique ranges', t => {
|
||||
t.true(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.0.x'}]));
|
||||
|
||||
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x.x'}]));
|
||||
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
|
||||
});
|
||||
|
||||
test('A "prerelease" branch is identified by having a range "prerelease" property', t => {
|
||||
t.true(prerelease.filter({name: 'some-name', prerelease: true}));
|
||||
t.true(prerelease.filter({name: 'some-name', prerelease: 'beta'}));
|
||||
t.true(prerelease.filter({name: 'some-name', prerelease: ''}));
|
||||
t.true(prerelease.filter({name: 'some-name', prerelease: null}));
|
||||
t.true(prerelease.filter({name: 'some-name', prerelease: false}));
|
||||
|
||||
t.false(prerelease.filter({name: 'some-name'}));
|
||||
});
|
||||
|
||||
test('A "prerelease" branch must have a valid prerelease detonation in "prerelease" property or in "name" if "prerelease" is "true"', t => {
|
||||
t.true(prerelease.branchValidator({name: 'beta', prerelease: true}));
|
||||
t.true(prerelease.branchValidator({name: 'some-name', prerelease: 'beta'}));
|
||||
|
||||
t.false(prerelease.branchValidator({name: 'some-name', prerelease: ''}));
|
||||
t.false(prerelease.branchValidator({name: 'some-name', prerelease: null}));
|
||||
t.false(prerelease.branchValidator({name: 'some-name', prerelease: false}));
|
||||
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '000'}));
|
||||
t.false(prerelease.branchValidator({name: 'some-name', prerelease: '#beta'}));
|
||||
t.false(prerelease.branchValidator({name: '000', prerelease: true}));
|
||||
t.false(prerelease.branchValidator({name: '#beta', prerelease: true}));
|
||||
});
|
||||
|
||||
test('The "prerelease" branches must have unique "prerelease" property', t => {
|
||||
t.true(prerelease.branchesValidator([{prerelease: 'beta'}, {prerelease: 'alpha'}]));
|
||||
|
||||
t.false(prerelease.branchesValidator([{range: 'beta'}, {range: 'beta'}, {range: 'alpha'}]));
|
||||
});
|
||||
|
||||
test('A "release" branch is identified by not havin a "range" or "prerelease" property or a "name" formatted like "N.x", "N.x.x" or "N.N.x"', t => {
|
||||
t.true(release.filter({name: 'some-name'}));
|
||||
|
||||
t.false(release.filter({name: '1.x.x'}));
|
||||
t.false(release.filter({name: '1.0.x'}));
|
||||
t.false(release.filter({name: 'some-name', range: '1.x.x'}));
|
||||
t.false(release.filter({name: 'some-name', range: '1.1.x'}));
|
||||
t.false(release.filter({name: 'some-name', prerelease: true}));
|
||||
t.false(release.filter({name: 'some-name', prerelease: 'beta'}));
|
||||
});
|
||||
|
||||
test('There must be between 1 and 3 release branches', t => {
|
||||
t.true(release.branchesValidator([{name: 'branch1'}]));
|
||||
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}]));
|
||||
t.true(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}]));
|
||||
|
||||
t.false(release.branchesValidator([]));
|
||||
t.false(release.branchesValidator([{name: 'branch1'}, {name: 'branch2'}, {name: 'branch3'}, {name: 'branch4'}]));
|
||||
});
|
||||
@@ -32,6 +32,16 @@ test('The "publish" plugin output, if defined, must be an object', t => {
|
||||
t.true(plugins.publish.outputValidator(''));
|
||||
});
|
||||
|
||||
test('The "addChannel" plugin output, if defined, must be an object', t => {
|
||||
t.false(plugins.addChannel.outputValidator(1));
|
||||
t.false(plugins.addChannel.outputValidator('string'));
|
||||
|
||||
t.true(plugins.addChannel.outputValidator({}));
|
||||
t.true(plugins.addChannel.outputValidator());
|
||||
t.true(plugins.addChannel.outputValidator(null));
|
||||
t.true(plugins.addChannel.outputValidator(''));
|
||||
});
|
||||
|
||||
test('The "generateNotes" plugins output are concatenated with separator and sensitive data is hidden', t => {
|
||||
const env = {MY_TOKEN: 'secret token'};
|
||||
t.is(plugins.generateNotes.postprocess(['note 1', 'note 2'], {env}), `note 1${RELEASE_NOTES_SEPARATOR}note 2`);
|
||||
|
||||
@@ -66,6 +66,25 @@ test('Get all commits since gitHead (from lastRelease) on a detached head repo',
|
||||
t.truthy(result[0].committer.name);
|
||||
});
|
||||
|
||||
test('Get all commits between lastRelease.gitHead and a shas', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const commits = await gitCommits(['First', 'Second', 'Third'], {cwd});
|
||||
|
||||
// Retrieve the commits with the commits module, between commit 'First' and 'Third'
|
||||
const result = await getCommits({
|
||||
cwd,
|
||||
lastRelease: {gitHead: commits[commits.length - 1].hash},
|
||||
nextRelease: {gitHead: commits[1].hash},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 1);
|
||||
t.deepEqual(result, commits.slice(1, commits.length - 1));
|
||||
});
|
||||
|
||||
test('Return empty array if lastRelease.gitHead is the last commit', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
|
||||
+94
-63
@@ -6,7 +6,7 @@ import {omit} from 'lodash';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import yaml from 'js-yaml';
|
||||
import {gitRepo, gitCommits, gitShallowClone, gitAddConfig} from './helpers/git-utils';
|
||||
import {gitRepo, gitTagVersion, gitCommits, gitShallowClone, gitAddConfig} from './helpers/git-utils';
|
||||
|
||||
const DEFAULT_PLUGINS = [
|
||||
'@semantic-release/commit-analyzer',
|
||||
@@ -23,8 +23,10 @@ test.beforeEach(t => {
|
||||
test('Default values, reading repositoryUrl from package.json', async t => {
|
||||
const pkg = {repository: 'https://host.null/owner/package.git'};
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
const {cwd} = await gitRepo(true);
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v1.1.0', undefined, {cwd});
|
||||
// Add remote.origin.url config
|
||||
await gitAddConfig('remote.origin.url', 'git@host.null:owner/repo.git', {cwd});
|
||||
// Create package.json in repository root
|
||||
@@ -33,21 +35,35 @@ test('Default values, reading repositoryUrl from package.json', async t => {
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
// Verify the default options are set
|
||||
t.is(result.branch, 'master');
|
||||
t.deepEqual(result.branches, [
|
||||
'+([1-9])?(.{+([1-9]),x}).x',
|
||||
'master',
|
||||
'next',
|
||||
'next-major',
|
||||
{name: 'beta', prerelease: true},
|
||||
{name: 'alpha', prerelease: true},
|
||||
]);
|
||||
t.is(result.repositoryUrl, 'https://host.null/owner/package.git');
|
||||
t.is(result.tagFormat, `v\${version}`);
|
||||
});
|
||||
|
||||
test('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
const {cwd} = await gitRepo(true);
|
||||
// Add remote.origin.url config
|
||||
await gitAddConfig('remote.origin.url', 'https://host.null/owner/module.git', {cwd});
|
||||
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
// Verify the default options are set
|
||||
t.is(result.branch, 'master');
|
||||
t.deepEqual(result.branches, [
|
||||
'+([1-9])?(.{+([1-9]),x}).x',
|
||||
'master',
|
||||
'next',
|
||||
'next-major',
|
||||
{name: 'beta', prerelease: true},
|
||||
{name: 'alpha', prerelease: true},
|
||||
]);
|
||||
t.is(result.repositoryUrl, 'https://host.null/owner/module.git');
|
||||
t.is(result.tagFormat, `v\${version}`);
|
||||
});
|
||||
@@ -62,7 +78,14 @@ test('Default values, reading repositoryUrl (http url) from package.json if not
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
// Verify the default options are set
|
||||
t.is(result.branch, 'master');
|
||||
t.deepEqual(result.branches, [
|
||||
'+([1-9])?(.{+([1-9]),x}).x',
|
||||
'master',
|
||||
'next',
|
||||
'next-major',
|
||||
{name: 'beta', prerelease: true},
|
||||
{name: 'alpha', prerelease: true},
|
||||
]);
|
||||
t.is(result.repositoryUrl, 'https://host.null/owner/module.git');
|
||||
t.is(result.tagFormat, `v\${version}`);
|
||||
});
|
||||
@@ -73,7 +96,7 @@ test('Read options from package.json', async t => {
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
|
||||
generateNotes: 'generateNotes',
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
@@ -83,10 +106,11 @@ test('Read options from package.json', async t => {
|
||||
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(result, options);
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
});
|
||||
|
||||
test('Read options from .releaserc.yml', async t => {
|
||||
@@ -94,7 +118,7 @@ test('Read options from .releaserc.yml', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
@@ -104,10 +128,11 @@ test('Read options from .releaserc.yml', async t => {
|
||||
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(result, options);
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
});
|
||||
|
||||
test('Read options from .releaserc.json', async t => {
|
||||
@@ -115,7 +140,7 @@ test('Read options from .releaserc.json', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
@@ -125,10 +150,11 @@ test('Read options from .releaserc.json', async t => {
|
||||
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(result, options);
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
});
|
||||
|
||||
test('Read options from .releaserc.js', async t => {
|
||||
@@ -136,7 +162,7 @@ test('Read options from .releaserc.js', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
@@ -146,10 +172,11 @@ test('Read options from .releaserc.js', async t => {
|
||||
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(result, options);
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
});
|
||||
|
||||
test('Read options from release.config.js', async t => {
|
||||
@@ -157,7 +184,7 @@ test('Read options from release.config.js', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
@@ -167,10 +194,11 @@ test('Read options from release.config.js', async t => {
|
||||
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(result, options);
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
});
|
||||
|
||||
test('Prioritise CLI/API parameters over file configuration and git repo', async t => {
|
||||
@@ -181,11 +209,11 @@ test('Prioritise CLI/API parameters over file configuration and git repo', async
|
||||
cwd = await gitShallowClone(repositoryUrl);
|
||||
const pkgOptions = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_pkg'},
|
||||
branch: 'branch_pkg',
|
||||
branches: ['branch_pkg'],
|
||||
};
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_cli'},
|
||||
branch: 'branch_cli',
|
||||
branches: ['branch_cli'],
|
||||
repositoryUrl: 'http://cli-url.com/owner/package',
|
||||
tagFormat: `cli\${version}`,
|
||||
plugins: false,
|
||||
@@ -196,10 +224,11 @@ test('Prioritise CLI/API parameters over file configuration and git repo', async
|
||||
|
||||
const result = await t.context.getConfig({cwd}, options);
|
||||
|
||||
const expected = {...options, branches: ['branch_cli']};
|
||||
// Verify the options contains the plugin config from CLI/API
|
||||
t.deepEqual(result.options, options);
|
||||
t.deepEqual(result.options, expected);
|
||||
// Verify the plugins module is called with the plugin options from CLI/API
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
});
|
||||
|
||||
test('Read configuration from file path in "extends"', async t => {
|
||||
@@ -209,7 +238,7 @@ test('Read configuration from file path in "extends"', async t => {
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
|
||||
generateNotes: 'generateNotes',
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: ['plugin-1', ['plugin-2', {plugin2Opt: 'value'}]],
|
||||
@@ -220,10 +249,11 @@ test('Read configuration from file path in "extends"', async t => {
|
||||
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from shareable.json
|
||||
t.deepEqual(result, options);
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from shareable.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
t.deepEqual(t.context.plugins.args[0][1], {
|
||||
analyzeCommits: './shareable.json',
|
||||
generateNotes: './shareable.json',
|
||||
@@ -239,7 +269,7 @@ test('Read configuration from module path in "extends"', async t => {
|
||||
const options = {
|
||||
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
|
||||
generateNotes: 'generateNotes',
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
@@ -248,12 +278,13 @@ test('Read configuration from module path in "extends"', async t => {
|
||||
await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
|
||||
await outputJson(path.resolve(cwd, 'node_modules/shareable/index.json'), options);
|
||||
|
||||
const {options: results} = await t.context.getConfig({cwd});
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from shareable.json
|
||||
t.deepEqual(results, options);
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from shareable.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
t.deepEqual(t.context.plugins.args[0][1], {
|
||||
analyzeCommits: 'shareable',
|
||||
generateNotes: 'shareable',
|
||||
@@ -267,14 +298,14 @@ test('Read configuration from an array of paths in "extends"', async t => {
|
||||
const options1 = {
|
||||
verifyRelease: 'verifyRelease1',
|
||||
analyzeCommits: {path: 'analyzeCommits1', param: 'analyzeCommits_param1'},
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
};
|
||||
const options2 = {
|
||||
verifyRelease: 'verifyRelease2',
|
||||
generateNotes: 'generateNotes2',
|
||||
analyzeCommits: {path: 'analyzeCommits2', param: 'analyzeCommits_param2'},
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
};
|
||||
@@ -283,12 +314,13 @@ test('Read configuration from an array of paths in "extends"', async t => {
|
||||
await outputJson(path.resolve(cwd, 'shareable1.json'), options1);
|
||||
await outputJson(path.resolve(cwd, 'shareable2.json'), options2);
|
||||
|
||||
const {options: results} = await t.context.getConfig({cwd});
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {...options1, ...options2, branches: ['test_branch']};
|
||||
// Verify the options contains the plugin config from shareable1.json and shareable2.json
|
||||
t.deepEqual(results, {...options1, ...options2});
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from shareable1.json and shareable2.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options: {...options1, ...options2}});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
t.deepEqual(t.context.plugins.args[0][1], {
|
||||
verifyRelease1: './shareable1.json',
|
||||
verifyRelease2: './shareable2.json',
|
||||
@@ -303,7 +335,7 @@ test('Prioritize configuration from config file over "extends"', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const pkgOptions = {
|
||||
extends: './shareable.json',
|
||||
branch: 'test_pkg',
|
||||
branches: ['test_pkg'],
|
||||
generateNotes: 'generateNotes',
|
||||
publish: [{path: 'publishPkg', param: 'publishPkg_param'}],
|
||||
};
|
||||
@@ -311,7 +343,7 @@ test('Prioritize configuration from config file over "extends"', async t => {
|
||||
analyzeCommits: 'analyzeCommits',
|
||||
generateNotes: 'generateNotesShareable',
|
||||
publish: [{path: 'publishShareable', param: 'publishShareable_param'}],
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
@@ -320,12 +352,13 @@ test('Prioritize configuration from config file over "extends"', async t => {
|
||||
await outputJson(path.resolve(cwd, 'package.json'), {release: pkgOptions});
|
||||
await outputJson(path.resolve(cwd, 'shareable.json'), options1);
|
||||
|
||||
const {options} = await t.context.getConfig({cwd});
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = omit({...options1, ...pkgOptions, branches: ['test_pkg']}, 'extends');
|
||||
// Verify the options contains the plugin config from package.json and shareable.json
|
||||
t.deepEqual(options, omit({...options1, ...pkgOptions}, 'extends'));
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from package.json and shareable.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {cwd, options: omit({...options, ...pkgOptions}, 'extends')});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
t.deepEqual(t.context.plugins.args[0][1], {
|
||||
analyzeCommits: './shareable.json',
|
||||
generateNotesShareable: './shareable.json',
|
||||
@@ -338,13 +371,13 @@ test('Prioritize configuration from cli/API options over "extends"', async t =>
|
||||
const {cwd} = await gitRepo();
|
||||
const cliOptions = {
|
||||
extends: './shareable2.json',
|
||||
branch: 'branch_opts',
|
||||
branches: ['branch_opts'],
|
||||
publish: [{path: 'publishOpts', param: 'publishOpts_param'}],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
};
|
||||
const pkgOptions = {
|
||||
extends: './shareable1.json',
|
||||
branch: 'branch_pkg',
|
||||
branches: ['branch_pkg'],
|
||||
generateNotes: 'generateNotes',
|
||||
publish: [{path: 'publishPkg', param: 'publishPkg_param'}],
|
||||
};
|
||||
@@ -352,13 +385,13 @@ test('Prioritize configuration from cli/API options over "extends"', async t =>
|
||||
analyzeCommits: 'analyzeCommits1',
|
||||
generateNotes: 'generateNotesShareable1',
|
||||
publish: [{path: 'publishShareable', param: 'publishShareable_param1'}],
|
||||
branch: 'test_branch1',
|
||||
branches: ['test_branch1'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
};
|
||||
const options2 = {
|
||||
analyzeCommits: 'analyzeCommits2',
|
||||
publish: [{path: 'publishShareable', param: 'publishShareable_param2'}],
|
||||
branch: 'test_branch2',
|
||||
branches: ['test_branch2'],
|
||||
tagFormat: `v\${version}`,
|
||||
plugins: false,
|
||||
};
|
||||
@@ -367,15 +400,13 @@ test('Prioritize configuration from cli/API options over "extends"', async t =>
|
||||
await outputJson(path.resolve(cwd, 'shareable1.json'), options1);
|
||||
await outputJson(path.resolve(cwd, 'shareable2.json'), options2);
|
||||
|
||||
const {options} = await t.context.getConfig({cwd}, cliOptions);
|
||||
const {options: result} = await t.context.getConfig({cwd}, cliOptions);
|
||||
|
||||
const expected = omit({...options2, ...pkgOptions, ...cliOptions, branches: ['branch_opts']}, 'extends');
|
||||
// Verify the options contains the plugin config from package.json and shareable2.json
|
||||
t.deepEqual(options, omit({...options2, ...pkgOptions, ...cliOptions}, 'extends'));
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from package.json and shareable2.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {
|
||||
cwd,
|
||||
options: omit({...options2, ...pkgOptions, ...cliOptions}, 'extends'),
|
||||
});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
});
|
||||
|
||||
test('Allow to unset properties defined in shareable config with "null"', async t => {
|
||||
@@ -384,7 +415,7 @@ test('Allow to unset properties defined in shareable config with "null"', async
|
||||
const pkgOptions = {
|
||||
extends: './shareable.json',
|
||||
analyzeCommits: null,
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
plugins: null,
|
||||
};
|
||||
@@ -415,6 +446,7 @@ test('Allow to unset properties defined in shareable config with "null"', async
|
||||
},
|
||||
cwd,
|
||||
});
|
||||
|
||||
t.deepEqual(t.context.plugins.args[0][1], {
|
||||
generateNotes: './shareable.json',
|
||||
analyzeCommits: './shareable.json',
|
||||
@@ -428,7 +460,7 @@ test('Allow to unset properties defined in shareable config with "undefined"', a
|
||||
const pkgOptions = {
|
||||
extends: './shareable.json',
|
||||
analyzeCommits: undefined,
|
||||
branch: 'test_branch',
|
||||
branches: ['test_branch'],
|
||||
repositoryUrl: 'https://host.null/owner/module.git',
|
||||
};
|
||||
const options1 = {
|
||||
@@ -441,18 +473,17 @@ test('Allow to unset properties defined in shareable config with "undefined"', a
|
||||
await writeFile(path.resolve(cwd, 'release.config.js'), `module.exports = ${format(pkgOptions)}`);
|
||||
await outputJson(path.resolve(cwd, 'shareable.json'), options1);
|
||||
|
||||
const {options} = await t.context.getConfig({cwd});
|
||||
const {options: result} = await t.context.getConfig({cwd});
|
||||
|
||||
const expected = {
|
||||
...omit(options1, 'analyzeCommits'),
|
||||
...omit(pkgOptions, ['extends', 'analyzeCommits']),
|
||||
branches: ['test_branch'],
|
||||
};
|
||||
// Verify the options contains the plugin config from shareable.json
|
||||
t.deepEqual(options, {...omit(options1, 'analyzeCommits'), ...omit(pkgOptions, ['extends', 'analyzeCommits'])});
|
||||
t.deepEqual(result, expected);
|
||||
// Verify the plugins module is called with the plugin options from shareable.json
|
||||
t.deepEqual(t.context.plugins.args[0][0], {
|
||||
options: {
|
||||
...omit(options1, 'analyzeCommits'),
|
||||
...omit(pkgOptions, ['extends', 'analyzeCommits']),
|
||||
},
|
||||
cwd,
|
||||
});
|
||||
t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd});
|
||||
t.deepEqual(t.context.plugins.args[0][1], {
|
||||
generateNotes: './shareable.json',
|
||||
analyzeCommits: './shareable.json',
|
||||
|
||||
@@ -8,7 +8,7 @@ test('Return the same "git" formatted URL if "gitCredentials" is not defined', a
|
||||
const {cwd} = await gitRepo();
|
||||
|
||||
t.is(
|
||||
await getAuthUrl({cwd, env, options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'}}),
|
||||
await getAuthUrl({cwd, env, branch: {name: 'master'}, options: {repositoryUrl: 'git@host.null:owner/repo.git'}}),
|
||||
'git@host.null:owner/repo.git'
|
||||
);
|
||||
});
|
||||
@@ -17,7 +17,12 @@ test('Return the same "https" formatted URL if "gitCredentials" is not defined',
|
||||
const {cwd} = await gitRepo();
|
||||
|
||||
t.is(
|
||||
await getAuthUrl({cwd, env, options: {branch: 'master', repositoryUrl: 'https://host.null/owner/repo.git'}}),
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env,
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'https://host.null/owner/repo.git'},
|
||||
}),
|
||||
'https://host.null/owner/repo.git'
|
||||
);
|
||||
});
|
||||
@@ -26,7 +31,12 @@ test('Return the "https" formatted URL if "gitCredentials" is not defined and re
|
||||
const {cwd} = await gitRepo();
|
||||
|
||||
t.is(
|
||||
await getAuthUrl({cwd, env, options: {branch: 'master', repositoryUrl: 'git+https://host.null/owner/repo.git'}}),
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env,
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git+https://host.null/owner/repo.git'},
|
||||
}),
|
||||
'https://host.null/owner/repo.git'
|
||||
);
|
||||
});
|
||||
@@ -35,7 +45,7 @@ test('Do not add trailing ".git" if not present in the origian URL', async t =>
|
||||
const {cwd} = await gitRepo();
|
||||
|
||||
t.is(
|
||||
await getAuthUrl({cwd, env, options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo'}}),
|
||||
await getAuthUrl({cwd, env, vranch: {name: 'master'}, options: {repositoryUrl: 'git@host.null:owner/repo'}}),
|
||||
'git@host.null:owner/repo'
|
||||
);
|
||||
});
|
||||
@@ -47,7 +57,8 @@ test('Handle "https" URL with group and subgroup', async t => {
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env,
|
||||
options: {branch: 'master', repositoryUrl: 'https://host.null/group/subgroup/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'https://host.null/group/subgroup/owner/repo.git'},
|
||||
}),
|
||||
'https://host.null/group/subgroup/owner/repo.git'
|
||||
);
|
||||
@@ -60,7 +71,8 @@ test('Handle "git" URL with group and subgroup', async t => {
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env,
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:group/subgroup/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:group/subgroup/owner/repo.git'},
|
||||
}),
|
||||
'git@host.null:group/subgroup/owner/repo.git'
|
||||
);
|
||||
@@ -70,7 +82,12 @@ test('Convert shorthand URL', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
|
||||
t.is(
|
||||
await getAuthUrl({cwd, env, options: {repositoryUrl: 'semanitc-release/semanitc-release'}}),
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env,
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'semanitc-release/semanitc-release'},
|
||||
}),
|
||||
'https://github.com/semanitc-release/semanitc-release.git'
|
||||
);
|
||||
});
|
||||
@@ -82,7 +99,8 @@ test('Convert GitLab shorthand URL', async t => {
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env,
|
||||
options: {branch: 'master', repositoryUrl: 'gitlab:semanitc-release/semanitc-release'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'gitlab:semanitc-release/semanitc-release'},
|
||||
}),
|
||||
'https://gitlab.com/semanitc-release/semanitc-release.git'
|
||||
);
|
||||
@@ -95,7 +113,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
}),
|
||||
'https://user:pass@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -121,7 +140,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
options: {branch: 'master', repositoryUrl: 'https://host.null/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'https://host.null/owner/repo.git'},
|
||||
}),
|
||||
'https://user:pass@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -134,7 +154,8 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
options: {branch: 'master', repositoryUrl: 'http://host.null/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'http://host.null/owner/repo.git'},
|
||||
}),
|
||||
'http://user:pass@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -147,7 +168,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
options: {branch: 'master', repositoryUrl: 'git+https://host.null/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git+https://host.null/owner/repo.git'},
|
||||
}),
|
||||
'https://user:pass@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -160,7 +182,8 @@ test('Return the "http" formatted URL if "gitCredentials" is defined and reposit
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
options: {branch: 'master', repositoryUrl: 'git+http://host.null/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git+http://host.null/owner/repo.git'},
|
||||
}),
|
||||
'http://user:pass@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -173,7 +196,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GH_T
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GH_TOKEN: 'token'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
}),
|
||||
'https://token@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -186,7 +210,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GITH
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GITHUB_TOKEN: 'token'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
}),
|
||||
'https://token@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -199,7 +224,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GL_T
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GL_TOKEN: 'token'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
}),
|
||||
'https://gitlab-ci-token:token@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -212,7 +238,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "GITL
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GITLAB_TOKEN: 'token'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
}),
|
||||
'https://gitlab-ci-token:token@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -225,7 +252,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "BB_T
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, BB_TOKEN: 'token'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
}),
|
||||
'https://x-token-auth:token@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -238,7 +266,8 @@ test('Return the "https" formatted URL if "gitCredentials" is defined with "BITB
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, BITBUCKET_TOKEN: 'token'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:owner/repo.git'},
|
||||
}),
|
||||
'https://x-token-auth:token@host.null/owner/repo.git'
|
||||
);
|
||||
@@ -251,7 +280,8 @@ test('Handle "https" URL with group and subgroup, with "GIT_CREDENTIALS"', async
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
options: {branch: 'master', repositoryUrl: 'https://host.null/group/subgroup/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'https://host.null/group/subgroup/owner/repo.git'},
|
||||
}),
|
||||
'https://user:pass@host.null/group/subgroup/owner/repo.git'
|
||||
);
|
||||
@@ -264,7 +294,8 @@ test('Handle "git" URL with group and subgroup, with "GIT_CREDENTIALS', async t
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
options: {branch: 'master', repositoryUrl: 'git@host.null:group/subgroup/owner/repo.git'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl: 'git@host.null:group/subgroup/owner/repo.git'},
|
||||
}),
|
||||
'https://user:pass@host.null/group/subgroup/owner/repo.git'
|
||||
);
|
||||
@@ -274,7 +305,12 @@ test('Do not add git credential to repositoryUrl if push is allowed', async t =>
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
|
||||
t.is(
|
||||
await getAuthUrl({cwd, env: {...env, GIT_CREDENTIALS: 'user:pass'}, options: {branch: 'master', repositoryUrl}}),
|
||||
await getAuthUrl({
|
||||
cwd,
|
||||
env: {...env, GIT_CREDENTIALS: 'user:pass'},
|
||||
branch: {name: 'master'},
|
||||
options: {repositoryUrl},
|
||||
}),
|
||||
repositoryUrl
|
||||
);
|
||||
});
|
||||
|
||||
+49
-132
@@ -1,7 +1,6 @@
|
||||
import test from 'ava';
|
||||
import {stub} from 'sinon';
|
||||
import getLastRelease from '../lib/get-last-release';
|
||||
import {gitRepo, gitCommits, gitTagVersion, gitCheckout} from './helpers/git-utils';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Stub the logger functions
|
||||
@@ -9,143 +8,61 @@ test.beforeEach(t => {
|
||||
t.context.logger = {log: t.context.log};
|
||||
});
|
||||
|
||||
test('Get the highest non-prerelease valid tag', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Create some commits and tags
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('foo', undefined, {cwd});
|
||||
const commits = await gitCommits(['Second'], {cwd});
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
await gitCommits(['Third'], {cwd});
|
||||
await gitTagVersion('v1.0.0', undefined, {cwd});
|
||||
await gitCommits(['Fourth'], {cwd});
|
||||
await gitTagVersion('v3.0', undefined, {cwd});
|
||||
await gitCommits(['Fifth'], {cwd});
|
||||
await gitTagVersion('v3.0.0-beta.1', undefined, {cwd});
|
||||
test('Get the highest non-prerelease valid tag', t => {
|
||||
const result = getLastRelease({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [
|
||||
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: '222'},
|
||||
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: '111'},
|
||||
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: '333'},
|
||||
],
|
||||
type: 'release',
|
||||
},
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
const result = await getLastRelease({cwd, options: {tagFormat: `v\${version}`}, logger: t.context.logger});
|
||||
|
||||
t.deepEqual(result, {gitHead: commits[0].hash, gitTag: 'v2.0.0', version: '2.0.0'});
|
||||
t.deepEqual(t.context.log.args[0], ['Found git tag v2.0.0 associated with version 2.0.0']);
|
||||
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: '222', channel: undefined});
|
||||
t.deepEqual(t.context.log.args[0][0], 'Found git tag v2.0.0 associated with version 2.0.0 on branch master');
|
||||
});
|
||||
|
||||
test('Get the highest tag in the history of the current branch', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commit to the master branch
|
||||
await gitCommits(['First'], {cwd});
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
// Create the new branch 'other-branch' from master
|
||||
await gitCheckout('other-branch', true, {cwd});
|
||||
// Add commit to the 'other-branch' branch
|
||||
await gitCommits(['Second'], {cwd});
|
||||
// Create the tag corresponding to version 3.0.0
|
||||
await gitTagVersion('v3.0.0', undefined, {cwd});
|
||||
// Checkout master
|
||||
await gitCheckout('master', false, {cwd});
|
||||
// Add another commit to the master branch
|
||||
const commits = await gitCommits(['Third'], {cwd});
|
||||
// Create the tag corresponding to version 2.0.0
|
||||
await gitTagVersion('v2.0.0', undefined, {cwd});
|
||||
|
||||
const result = await getLastRelease({cwd, options: {tagFormat: `v\${version}`}, logger: t.context.logger});
|
||||
|
||||
t.deepEqual(result, {gitHead: commits[0].hash, gitTag: 'v2.0.0', version: '2.0.0'});
|
||||
});
|
||||
|
||||
test('Match the tag name from the begining of the string', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitTagVersion('prefix/v1.0.0', undefined, {cwd});
|
||||
await gitTagVersion('prefix/v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('other-prefix/v3.0.0', undefined, {cwd});
|
||||
|
||||
const result = await getLastRelease({cwd, options: {tagFormat: `prefix/v\${version}`}, logger: t.context.logger});
|
||||
|
||||
t.deepEqual(result, {gitHead: commits[0].hash, gitTag: 'prefix/v2.0.0', version: '2.0.0'});
|
||||
});
|
||||
|
||||
test('Return empty object if no valid tag is found', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Create some commits and tags
|
||||
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 getLastRelease({cwd, options: {tagFormat: `v\${version}`}, logger: t.context.logger});
|
||||
test('Return empty object if no valid tag is found', t => {
|
||||
const result = getLastRelease({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: '111'}],
|
||||
type: 'release',
|
||||
},
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
t.deepEqual(result, {});
|
||||
t.is(t.context.log.args[0][0], 'No git tag version found');
|
||||
t.deepEqual(t.context.log.args[0][0], 'No git tag version found on branch master');
|
||||
});
|
||||
|
||||
test('Return empty object if no valid tag is found in history', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
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('v2.0.0', undefined, {cwd});
|
||||
await gitTagVersion('v3.0.0', undefined, {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
test('Get the highest non-prerelease valid tag before a certain version', t => {
|
||||
const result = getLastRelease(
|
||||
{
|
||||
branch: {
|
||||
name: 'master',
|
||||
channel: undefined,
|
||||
tags: [
|
||||
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: '333'},
|
||||
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: '111'},
|
||||
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1@beta', gitHead: '222'},
|
||||
{version: '2.1.0', gitTag: 'v2.1.0', gitHead: '444'},
|
||||
{version: '2.1.1', gitTag: 'v2.1.1', gitHead: '555'},
|
||||
],
|
||||
type: 'release',
|
||||
},
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
},
|
||||
{before: '2.1.0'}
|
||||
);
|
||||
|
||||
const result = await getLastRelease({cwd, options: {tagFormat: `v\${version}`}, logger: t.context.logger});
|
||||
|
||||
t.deepEqual(result, {});
|
||||
t.is(t.context.log.args[0][0], 'No git tag version found');
|
||||
});
|
||||
|
||||
test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Create some commits and tags
|
||||
const [{hash: gitHead}] = await gitCommits(['First'], {cwd});
|
||||
|
||||
await gitTagVersion('1.0.0', undefined, {cwd});
|
||||
t.deepEqual(await getLastRelease({cwd, options: {tagFormat: `\${version}`}, logger: t.context.logger}), {
|
||||
gitHead,
|
||||
gitTag: '1.0.0',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
await gitTagVersion('foo-1.0.0-bar', undefined, {cwd});
|
||||
t.deepEqual(await getLastRelease({cwd, options: {tagFormat: `foo-\${version}-bar`}, logger: t.context.logger}), {
|
||||
gitHead,
|
||||
gitTag: 'foo-1.0.0-bar',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
await gitTagVersion('foo-v1.0.0-bar', undefined, {cwd});
|
||||
t.deepEqual(await getLastRelease({cwd, options: {tagFormat: `foo-v\${version}-bar`}, logger: t.context.logger}), {
|
||||
gitHead,
|
||||
gitTag: 'foo-v1.0.0-bar',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
await gitTagVersion('(.+)/1.0.0/(a-z)', undefined, {cwd});
|
||||
t.deepEqual(await getLastRelease({cwd, options: {tagFormat: `(.+)/\${version}/(a-z)`}, logger: t.context.logger}), {
|
||||
gitHead,
|
||||
gitTag: '(.+)/1.0.0/(a-z)',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
await gitTagVersion('2.0.0-1.0.0-bar.1', undefined, {cwd});
|
||||
t.deepEqual(await getLastRelease({cwd, options: {tagFormat: `2.0.0-\${version}-bar.1`}, logger: t.context.logger}), {
|
||||
gitHead,
|
||||
gitTag: '2.0.0-1.0.0-bar.1',
|
||||
version: '1.0.0',
|
||||
});
|
||||
|
||||
await gitTagVersion('3.0.0-bar.1', undefined, {cwd});
|
||||
t.deepEqual(await getLastRelease({cwd, options: {tagFormat: `\${version}-bar.1`}, logger: t.context.logger}), {
|
||||
gitHead,
|
||||
gitTag: '3.0.0-bar.1',
|
||||
version: '3.0.0',
|
||||
});
|
||||
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: '333', channel: undefined});
|
||||
t.deepEqual(t.context.log.args[0][0], 'Found git tag v2.0.0 associated with version 2.0.0 on branch master');
|
||||
});
|
||||
|
||||
+114
-20
@@ -9,33 +9,127 @@ test.beforeEach(t => {
|
||||
});
|
||||
|
||||
test('Increase version for patch release', t => {
|
||||
const version = getNextVersion({
|
||||
nextRelease: {type: 'patch'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
t.is(version, '1.0.1');
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
nextRelease: {type: 'patch'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.1'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for minor release', t => {
|
||||
const version = getNextVersion({
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
t.is(version, '1.1.0');
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.1.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for major release', t => {
|
||||
const version = getNextVersion({
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
t.is(version, '2.0.0');
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'2.0.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('Return 1.0.0 if there is no previous release', t => {
|
||||
const version = getNextVersion({nextRelease: {type: 'minor'}, lastRelease: {}, logger: t.context.logger});
|
||||
t.is(version, '1.0.0');
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'master', type: 'release'},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for patch release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'patch'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.1-beta.1'
|
||||
);
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'patch'},
|
||||
lastRelease: {version: '1.0.0-beta.1'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.2'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for minor release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.1.0-beta.1'
|
||||
);
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {version: '1.0.0-beta.1'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.2'
|
||||
);
|
||||
});
|
||||
|
||||
test('Increase version for major release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: '1.0.0'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'2.0.0-beta.1'
|
||||
);
|
||||
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'major'},
|
||||
lastRelease: {version: '1.0.0-beta.1'},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.2'
|
||||
);
|
||||
});
|
||||
|
||||
test('Return 1.0.0 if there is no previous release on prerelease branch', t => {
|
||||
t.is(
|
||||
getNextVersion({
|
||||
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
|
||||
nextRelease: {type: 'minor'},
|
||||
lastRelease: {},
|
||||
logger: t.context.logger,
|
||||
}),
|
||||
'1.0.0-beta.1'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
import test from 'ava';
|
||||
import {stub} from 'sinon';
|
||||
import getReleasesToAdd from '../lib/get-releases-to-add';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Stub the logger functions
|
||||
t.context.log = stub();
|
||||
t.context.logger = {log: t.context.log};
|
||||
});
|
||||
|
||||
test('Return versions merged from release to maintenance branch', t => {
|
||||
const result = getReleasesToAdd({
|
||||
branch: {
|
||||
name: '1.x',
|
||||
channel: '1.x',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0@1.x', version: '1.0.0', channel: '1.x', gitHead: '111'},
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
|
||||
{gitTag: 'v1.1.0', version: '1.1.0', gitHead: '222'},
|
||||
{gitTag: 'v1.1.1', version: '1.1.1', gitHead: '333'},
|
||||
],
|
||||
},
|
||||
branches: [{name: '1.x', channel: '1.x'}, {name: 'master'}],
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
lastRelease: {version: '1.0.0', channel: '1.x', gitTag: 'v1.0.0@1.x', name: 'v1.0.0', gitHead: '111'},
|
||||
currentRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: undefined,
|
||||
gitTag: 'v1.1.0',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: '1.x',
|
||||
gitTag: 'v1.1.0@1.x',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
},
|
||||
{
|
||||
lastRelease: {version: '1.1.0', channel: undefined, gitTag: 'v1.1.0', name: 'v1.1.0', gitHead: '222'},
|
||||
currentRelease: {
|
||||
type: 'patch',
|
||||
version: '1.1.1',
|
||||
channel: undefined,
|
||||
gitTag: 'v1.1.1',
|
||||
name: 'v1.1.1',
|
||||
gitHead: '333',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'patch',
|
||||
version: '1.1.1',
|
||||
channel: '1.x',
|
||||
gitTag: 'v1.1.1@1.x',
|
||||
name: 'v1.1.1',
|
||||
gitHead: '333',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Return versions merged from future branch to release branch', t => {
|
||||
const result = getReleasesToAdd({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
|
||||
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
|
||||
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
|
||||
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major', gitHead: '333'},
|
||||
],
|
||||
},
|
||||
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
|
||||
currentRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: 'next',
|
||||
gitTag: 'v1.1.0@next',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: undefined,
|
||||
gitTag: 'v1.1.0',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
},
|
||||
{
|
||||
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: '222', channel: 'next'},
|
||||
currentRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channel: 'next-major',
|
||||
gitTag: 'v2.0.0@next-major',
|
||||
name: 'v2.0.0',
|
||||
gitHead: '333',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channel: undefined,
|
||||
gitTag: 'v2.0.0',
|
||||
name: 'v2.0.0',
|
||||
gitHead: '333',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Return releases sorted by ascending order', t => {
|
||||
const result = getReleasesToAdd({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [
|
||||
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major', gitHead: '333'},
|
||||
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
|
||||
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
|
||||
],
|
||||
},
|
||||
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
|
||||
currentRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: 'next',
|
||||
gitTag: 'v1.1.0@next',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: undefined,
|
||||
gitTag: 'v1.1.0',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
},
|
||||
{
|
||||
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: '222', channel: 'next'},
|
||||
currentRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channel: 'next-major',
|
||||
gitTag: 'v2.0.0@next-major',
|
||||
name: 'v2.0.0',
|
||||
gitHead: '333',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'major',
|
||||
version: '2.0.0',
|
||||
channel: undefined,
|
||||
gitTag: 'v2.0.0',
|
||||
name: 'v2.0.0',
|
||||
gitHead: '333',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('no lastRelease', t => {
|
||||
const result = getReleasesToAdd({
|
||||
branch: {name: 'master', tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'}]},
|
||||
branches: [{name: 'master'}, {name: 'next', channel: 'next'}],
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
lastRelease: {},
|
||||
currentRelease: {
|
||||
type: 'major',
|
||||
version: '1.0.0',
|
||||
channel: 'next',
|
||||
gitTag: 'v1.0.0@next',
|
||||
name: 'v1.0.0',
|
||||
gitHead: '111',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'major',
|
||||
version: '1.0.0',
|
||||
channel: undefined,
|
||||
gitTag: 'v1.0.0',
|
||||
name: 'v1.0.0',
|
||||
gitHead: '111',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Ignore pre-release versions', t => {
|
||||
const result = getReleasesToAdd({
|
||||
branch: {
|
||||
name: 'master',
|
||||
tags: [
|
||||
{gitTag: 'v1.0.0', version: '1.0.0', gitHead: '111'},
|
||||
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next', gitHead: '111'},
|
||||
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next', gitHead: '222'},
|
||||
{gitTag: 'v2.0.0-alpha.1@alpha', version: '2.0.0', channel: 'alpha', gitHead: '333'},
|
||||
],
|
||||
},
|
||||
branches: [
|
||||
{name: 'master'},
|
||||
{name: 'next', channel: 'next'},
|
||||
{name: 'alpha', type: 'prerelease', channel: 'alpha'},
|
||||
],
|
||||
options: {tagFormat: `v\${version}`},
|
||||
logger: t.context.logger,
|
||||
});
|
||||
|
||||
t.deepEqual(result, [
|
||||
{
|
||||
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: '111'},
|
||||
currentRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: 'next',
|
||||
gitTag: 'v1.1.0@next',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
nextRelease: {
|
||||
type: 'minor',
|
||||
version: '1.1.0',
|
||||
channel: undefined,
|
||||
gitTag: 'v1.1.0',
|
||||
name: 'v1.1.0',
|
||||
gitHead: '222',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
+40
-11
@@ -3,12 +3,14 @@ import tempy from 'tempy';
|
||||
import {
|
||||
getTagHead,
|
||||
isRefInHistory,
|
||||
isRefExists,
|
||||
fetch,
|
||||
getGitHead,
|
||||
repoUrl,
|
||||
tag,
|
||||
push,
|
||||
getTags,
|
||||
getBranches,
|
||||
isGitRepo,
|
||||
verifyTagName,
|
||||
isBranchUpToDate,
|
||||
@@ -56,7 +58,7 @@ test('Unshallow and fetch repository', async t => {
|
||||
// Verify the shallow clone contains only one commit
|
||||
t.is((await gitGetCommits(undefined, {cwd})).length, 1);
|
||||
|
||||
await fetch(repositoryUrl, {cwd});
|
||||
await fetch({cwd});
|
||||
|
||||
// Verify the shallow clone contains all the commits
|
||||
t.is((await gitGetCommits(undefined, {cwd})).length, 2);
|
||||
@@ -64,10 +66,10 @@ test('Unshallow and fetch repository', async t => {
|
||||
|
||||
test('Do not throw error when unshallow a complete repository', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd, repositoryUrl} = await gitRepo();
|
||||
const {cwd} = await gitRepo(true);
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First'], {cwd});
|
||||
await t.notThrows(fetch(repositoryUrl, {cwd}));
|
||||
await t.notThrows(fetch({cwd}));
|
||||
});
|
||||
|
||||
test('Fetch all tags on a detached head repository', async t => {
|
||||
@@ -82,7 +84,7 @@ test('Fetch all tags on a detached head repository', async t => {
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
cwd = await gitDetachedHead(repositoryUrl, commit.hash);
|
||||
|
||||
await fetch(repositoryUrl, {cwd});
|
||||
await fetch({cwd});
|
||||
|
||||
t.deepEqual((await getTags({cwd})).sort(), ['v1.0.0', 'v1.0.1', 'v1.1.0'].sort());
|
||||
});
|
||||
@@ -98,9 +100,36 @@ test('Verify if the commit `sha` is in the direct history of the current branch'
|
||||
const otherCommits = await gitCommits(['Second'], {cwd});
|
||||
await gitCheckout('master', false, {cwd});
|
||||
|
||||
t.true(await isRefInHistory(commits[0].hash, {cwd}));
|
||||
t.falsy(await isRefInHistory(otherCommits[0].hash, {cwd}));
|
||||
await t.throws(isRefInHistory('non-existant-sha', {cwd}));
|
||||
t.true(await isRefInHistory(commits[0].hash, 'master', false, {cwd}));
|
||||
t.falsy(await isRefInHistory(otherCommits[0].hash, 'master', false, {cwd}));
|
||||
t.falsy(await isRefInHistory(otherCommits[0].hash, 'missing-branch', false, {cwd}));
|
||||
await t.throws(isRefInHistory('non-existant-sha', 'master', false, {cwd}));
|
||||
});
|
||||
|
||||
test('Verify if a branch exists', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const {cwd} = await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['First'], {cwd});
|
||||
// Create the new branch 'other-branch' from master
|
||||
await gitCheckout('other-branch', true, {cwd});
|
||||
// Add commits to the 'other-branch' branch
|
||||
await gitCommits(['Second'], {cwd});
|
||||
|
||||
t.true(await isRefExists('master', {cwd}));
|
||||
t.true(await isRefExists('other-branch', {cwd}));
|
||||
t.falsy(await isRefExists('next', {cwd}));
|
||||
});
|
||||
|
||||
test('Get all branches', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
await gitCommits(['First'], {cwd});
|
||||
await gitCheckout('second-branch', true, {cwd});
|
||||
await gitCommits(['Second'], {cwd});
|
||||
await gitCheckout('third-branch', true, {cwd});
|
||||
await gitCommits(['Third'], {cwd});
|
||||
|
||||
t.deepEqual((await getBranches({cwd})).sort(), ['master', 'second-branch', 'third-branch'].sort());
|
||||
});
|
||||
|
||||
test('Get the commit sha for a given tag or falsy if the tag does not exists', async t => {
|
||||
@@ -146,7 +175,7 @@ test('Add tag on head commit', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const commits = await gitCommits(['Test commit'], {cwd});
|
||||
|
||||
await tag('tag_name', {cwd});
|
||||
await tag('tag_name', 'HEAD', {cwd});
|
||||
|
||||
await t.is(await gitCommitTag(commits[0].hash, {cwd}), 'tag_name');
|
||||
});
|
||||
@@ -156,13 +185,13 @@ test('Push tag to remote repository', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const commits = await gitCommits(['Test commit'], {cwd});
|
||||
|
||||
await tag('tag_name', {cwd});
|
||||
await tag('tag_name', 'HEAD', {cwd});
|
||||
await push(repositoryUrl, 'master', {cwd});
|
||||
|
||||
t.is(await gitRemoteTagHead(repositoryUrl, 'tag_name', {cwd}), commits[0].hash);
|
||||
});
|
||||
|
||||
test('Push tag to remote repository with remote branch ahaed', async t => {
|
||||
test('Push tag to remote repository with remote branch ahead', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const commits = await gitCommits(['First'], {cwd});
|
||||
await gitPush(repositoryUrl, 'master', {cwd});
|
||||
@@ -170,7 +199,7 @@ test('Push tag to remote repository with remote branch ahaed', async t => {
|
||||
await gitCommits(['Second'], {cwd: tmpRepo});
|
||||
await gitPush('origin', 'master', {cwd: tmpRepo});
|
||||
|
||||
await tag('tag_name', {cwd});
|
||||
await tag('tag_name', 'HEAD', {cwd});
|
||||
await push(repositoryUrl, 'master', {cwd});
|
||||
|
||||
t.is(await gitRemoteTagHead(repositoryUrl, 'tag_name', {cwd}), commits[0].hash);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import tempy from 'tempy';
|
||||
import execa from 'execa';
|
||||
import fileUrl from 'file-url';
|
||||
import pReduce from 'p-reduce';
|
||||
import pEachSeries from 'p-each-series';
|
||||
import gitLogParser from 'git-log-parser';
|
||||
import getStream from 'get-stream';
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function initBareRepo(repositoryUrl, branch = 'master') {
|
||||
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
|
||||
*/
|
||||
export async function gitCommits(messages, execaOpts) {
|
||||
await pReduce(messages, (_, message) =>
|
||||
await pEachSeries(messages, message =>
|
||||
execa.stdout('git', ['commit', '-m', message, '--allow-empty', '--no-gpg-sign'], execaOpts)
|
||||
);
|
||||
return (await gitGetCommits(undefined, execaOpts)).slice(0, messages.length);
|
||||
@@ -225,3 +225,46 @@ export function gitCommitTag(gitHead, execaOpts) {
|
||||
export async function gitPush(repositoryUrl = 'origin', branch = 'master', execaOpts) {
|
||||
await execa('git', ['push', '--tags', repositoryUrl, `HEAD:${branch}`], execaOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a branch into the current one with `git merge`.
|
||||
*
|
||||
* @param {String} ref The ref to merge.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
export async function merge(ref, execaOpts) {
|
||||
await execa('git', ['merge', '--no-ff', ref], execaOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a branch into the current one with `git merge --ff`.
|
||||
*
|
||||
* @param {String} ref The ref to merge.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
export async function mergeFf(ref, execaOpts) {
|
||||
await execa('git', ['merge', '--ff', ref], execaOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge a branch into the current one with `git rebase`.
|
||||
*
|
||||
* @param {String} ref The ref to merge.
|
||||
* @param {Object} [execaOpts] Options to pass to `execa`.
|
||||
*/
|
||||
export async function rebase(ref, execaOpts) {
|
||||
await execa('git', ['rebase', ref], execaOpts);
|
||||
}
|
||||
|
||||
export async function changeAuthor(sha, execaOpts) {
|
||||
await execa(
|
||||
'git',
|
||||
[
|
||||
'filter-branch',
|
||||
'-f',
|
||||
'--env-filter',
|
||||
`if [[ "$GIT_COMMIT" = "${sha}" ]]; then export GIT_COMMITTER_NAME="New Author" GIT_COMMITTER_EMAIL="author@test.com"; fi`,
|
||||
],
|
||||
execaOpts
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import execa from 'execa';
|
||||
|
||||
export async function npmView(packageName, env) {
|
||||
return JSON.parse(await execa.stdout('npm', ['view', packageName, '--json'], {env}));
|
||||
}
|
||||
+747
-93
File diff suppressed because it is too large
Load Diff
+95
-42
@@ -5,8 +5,19 @@ import {escapeRegExp} from 'lodash';
|
||||
import {writeJson, readJson} from 'fs-extra';
|
||||
import execa from 'execa';
|
||||
import {WritableStreamBuffer} from 'stream-buffers';
|
||||
import delay from 'delay';
|
||||
import {SECRET_REPLACEMENT} from '../lib/definitions/constants';
|
||||
import {gitHead, gitTagHead, gitRepo, gitCommits, gitRemoteTagHead, gitPush} from './helpers/git-utils';
|
||||
import {
|
||||
gitHead,
|
||||
gitTagHead,
|
||||
gitRepo,
|
||||
gitCommits,
|
||||
gitRemoteTagHead,
|
||||
gitPush,
|
||||
gitCheckout,
|
||||
merge,
|
||||
} from './helpers/git-utils';
|
||||
import {npmView} from './helpers/npm-utils';
|
||||
import gitbox from './helpers/gitbox';
|
||||
import mockServer from './helpers/mockserver';
|
||||
import npmRegistry from './helpers/npm-registry';
|
||||
@@ -58,7 +69,7 @@ test('Release patch, minor and major versions', async t => {
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: repositoryUrl},
|
||||
publishConfig: {registry: npmRegistry.url},
|
||||
release: {success: false, fail: false},
|
||||
release: {branches: ['master', 'next'], success: false, fail: false},
|
||||
});
|
||||
// Create a npm-shrinkwrap.json file
|
||||
await execa('npm', ['shrinkwrap'], {env: testEnv, cwd});
|
||||
@@ -86,7 +97,7 @@ test('Release patch, minor and major versions', async t => {
|
||||
let createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
body: {tag_name: `v${version}`, name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
@@ -105,15 +116,14 @@ test('Release patch, minor and major versions', async t => {
|
||||
t.is((await readJson(path.resolve(cwd, 'npm-shrinkwrap.json'))).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
let [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv, cwd})).stdout
|
||||
);
|
||||
let {
|
||||
'dist-tags': {latest: releasedVersion},
|
||||
} = await npmView(packageName, testEnv);
|
||||
let head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, head);
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), head);
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), head);
|
||||
t.log(`+ released ${releasedVersion} with head ${releasedGitHead}`);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
|
||||
await mockServer.verify(verifyMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
@@ -128,7 +138,7 @@ test('Release patch, minor and major versions', async t => {
|
||||
createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
body: {tag_name: `v${version}`, name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
@@ -147,15 +157,14 @@ test('Release patch, minor and major versions', async t => {
|
||||
t.is((await readJson(path.resolve(cwd, 'npm-shrinkwrap.json'))).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv, cwd})).stdout
|
||||
);
|
||||
({
|
||||
'dist-tags': {latest: releasedVersion},
|
||||
} = await npmView(packageName, testEnv));
|
||||
head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, head);
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), head);
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), head);
|
||||
t.log(`+ released ${releasedVersion} with head ${releasedGitHead}`);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
|
||||
await mockServer.verify(verifyMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
@@ -170,7 +179,7 @@ test('Release patch, minor and major versions', async t => {
|
||||
createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
body: {tag_name: `v${version}`, name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
@@ -189,20 +198,19 @@ test('Release patch, minor and major versions', async t => {
|
||||
t.is((await readJson(path.resolve(cwd, 'npm-shrinkwrap.json'))).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv, cwd})).stdout
|
||||
);
|
||||
({
|
||||
'dist-tags': {latest: releasedVersion},
|
||||
} = await npmView(packageName, testEnv));
|
||||
head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, head);
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), head);
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), head);
|
||||
t.log(`+ released ${releasedVersion} with head ${releasedGitHead}`);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
|
||||
await mockServer.verify(verifyMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/* Major release */
|
||||
/* Major release on next */
|
||||
version = '2.0.0';
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -212,16 +220,18 @@ test('Release patch, minor and major versions', async t => {
|
||||
createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
body: {tag_name: `v${version}@next`, name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
t.log('Commit a breaking change');
|
||||
t.log('Commit a breaking change on next');
|
||||
await gitCheckout('next', true, {cwd});
|
||||
await gitPush('origin', 'next', {cwd});
|
||||
await gitCommits(['feat: foo\n\n BREAKING CHANGE: bar'], {cwd});
|
||||
t.log('$ semantic-release');
|
||||
({stdout, code} = await execa(cli, [], {env, cwd}));
|
||||
({stdout, code} = await execa(cli, [], {env: {...env, TRAVIS_BRANCH: 'next'}, cwd}));
|
||||
t.regex(stdout, new RegExp(`Published GitHub release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry`));
|
||||
t.is(code, 0);
|
||||
@@ -231,18 +241,67 @@ test('Release patch, minor and major versions', async t => {
|
||||
t.is((await readJson(path.resolve(cwd, 'npm-shrinkwrap.json'))).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv, cwd})).stdout
|
||||
);
|
||||
({
|
||||
'dist-tags': {next: releasedVersion},
|
||||
} = await npmView(packageName, testEnv));
|
||||
head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, head);
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), head);
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), head);
|
||||
t.log(`+ released ${releasedVersion} with head ${releasedGitHead}`);
|
||||
t.is(await gitTagHead(`v${version}@next`, {cwd}), head);
|
||||
t.is(await gitRemoteTagHead(authUrl, `v${version}@next`, {cwd}), head);
|
||||
t.log(`+ released ${releasedVersion} on @next`);
|
||||
|
||||
await mockServer.verify(verifyMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/* Merge next into master */
|
||||
version = '2.0.0';
|
||||
const releaseId = 1;
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {permissions: {push: true}}, method: 'GET'}
|
||||
);
|
||||
const getReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases/tags/v2.0.0@next`,
|
||||
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {id: releaseId}, method: 'GET'}
|
||||
);
|
||||
const updateReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases/${releaseId}`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, name: `v${version}`, prerelease: false},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}, method: 'PATCH'}
|
||||
);
|
||||
|
||||
t.log('Merge next into master');
|
||||
await gitCheckout('master', false, {cwd});
|
||||
await merge('next', {cwd});
|
||||
await gitPush('origin', 'master', {cwd});
|
||||
t.log('$ semantic-release');
|
||||
({stdout, code} = await execa(cli, [], {env, cwd}));
|
||||
t.regex(stdout, new RegExp(`Updated GitHub release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Adding version ${version} to npm registry on dist-tag latest`));
|
||||
t.is(code, 0);
|
||||
|
||||
// Wait for 3s as the change of dist-tag takes time to be reflected in the registry
|
||||
await delay(3000);
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
({
|
||||
'dist-tags': {latest: releasedVersion},
|
||||
} = await npmView(packageName, testEnv));
|
||||
t.is(releasedVersion, version);
|
||||
t.is(await gitTagHead(`v${version}`, {cwd}), await gitTagHead(`v${version}@next`, {cwd}));
|
||||
t.is(
|
||||
await gitRemoteTagHead(authUrl, `v${version}`, {cwd}),
|
||||
await gitRemoteTagHead(authUrl, `v${version}@next`, {cwd})
|
||||
);
|
||||
t.log(`+ added ${releasedVersion}`);
|
||||
|
||||
await mockServer.verify(verifyMock);
|
||||
await mockServer.verify(getReleaseMock);
|
||||
await mockServer.verify(updateReleaseMock);
|
||||
});
|
||||
|
||||
test('Exit with 1 if a plugin is not found', async t => {
|
||||
@@ -366,7 +425,7 @@ test('Allow local releases with "noCi" option', async t => {
|
||||
const createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
body: {tag_name: `v${version}`, name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
@@ -384,9 +443,7 @@ test('Allow local releases with "noCi" option', async t => {
|
||||
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
const [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv, cwd})).stdout
|
||||
);
|
||||
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, testEnv);
|
||||
|
||||
const head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
@@ -439,9 +496,7 @@ test('Pass options via CLI arguments', async t => {
|
||||
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
const [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv, cwd})).stdout
|
||||
);
|
||||
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, testEnv);
|
||||
const head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, head);
|
||||
@@ -482,7 +537,7 @@ test('Run via JS API', async t => {
|
||||
const createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
body: {tag_name: `v${version}`, name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
@@ -497,9 +552,7 @@ test('Run via JS API', async t => {
|
||||
t.is((await readJson(path.resolve(cwd, 'package.json'))).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
const [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv, cwd})).stdout
|
||||
);
|
||||
const {version: releasedVersion, gitHead: releasedGitHead} = await npmView(packageName, testEnv);
|
||||
const head = await gitHead({cwd});
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, head);
|
||||
|
||||
@@ -152,6 +152,24 @@ test('Wrap "publish" plugin in a function that validate the output of the plugin
|
||||
t.regex(error.details, /2/);
|
||||
});
|
||||
|
||||
test('Wrap "addChannel" plugin in a function that validate the output of the plugin', async t => {
|
||||
const addChannel = stub().resolves(2);
|
||||
const plugin = normalize(
|
||||
{cwd, options: {}, stderr: t.context.stderr, logger: t.context.logger},
|
||||
'addChannel',
|
||||
addChannel,
|
||||
{}
|
||||
);
|
||||
|
||||
const error = await t.throws(plugin({options: {}}));
|
||||
|
||||
t.is(error.code, 'EADDCHANNELOUTPUT');
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
t.truthy(error.message);
|
||||
t.truthy(error.details);
|
||||
t.regex(error.details, /2/);
|
||||
});
|
||||
|
||||
test('Plugin is called with "pluginConfig" (with object definition) and input', async t => {
|
||||
const pluginFunction = stub().resolves();
|
||||
const pluginConf = {path: pluginFunction, conf: 'confValue'};
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
import test from 'ava';
|
||||
import AggregateError from 'aggregate-error';
|
||||
import {
|
||||
extractErrors,
|
||||
tagsToVersions,
|
||||
isMajorRange,
|
||||
isMaintenanceRange,
|
||||
getUpperBound,
|
||||
getLowerBound,
|
||||
highest,
|
||||
lowest,
|
||||
getLatestVersion,
|
||||
getEarliestVersion,
|
||||
getFirstVersion,
|
||||
getRange,
|
||||
makeTag,
|
||||
} from '../lib/utils';
|
||||
|
||||
test('extractErrors', t => {
|
||||
const errors = [new Error('Error 1'), new Error('Error 2')];
|
||||
|
||||
t.deepEqual(extractErrors(new AggregateError(errors)), errors);
|
||||
t.deepEqual(extractErrors(errors[0]), [errors[0]]);
|
||||
});
|
||||
|
||||
test('tagsToVersions', t => {
|
||||
t.deepEqual(tagsToVersions([{version: '1.0.0'}, {version: '1.1.0'}, {version: '1.2.0'}]), [
|
||||
'1.0.0',
|
||||
'1.1.0',
|
||||
'1.2.0',
|
||||
]);
|
||||
});
|
||||
|
||||
test('isMajorRange', t => {
|
||||
t.false(isMajorRange('1.1.x'));
|
||||
t.false(isMajorRange('1.1.X'));
|
||||
t.false(isMajorRange('1.1.0'));
|
||||
|
||||
t.true(isMajorRange('1.x.x'));
|
||||
t.true(isMajorRange('1.X.X'));
|
||||
t.true(isMajorRange('1.x'));
|
||||
t.true(isMajorRange('1.X'));
|
||||
});
|
||||
|
||||
test('isMaintenanceRange', t => {
|
||||
t.true(isMaintenanceRange('1.1.x'));
|
||||
t.true(isMaintenanceRange('1.x.x'));
|
||||
t.true(isMaintenanceRange('1.x'));
|
||||
t.true(isMaintenanceRange('1.1.X'));
|
||||
t.true(isMaintenanceRange('1.X.X'));
|
||||
t.true(isMaintenanceRange('1.X'));
|
||||
|
||||
t.false(isMaintenanceRange('1.1.0'));
|
||||
t.false(isMaintenanceRange('~1.0.0'));
|
||||
t.false(isMaintenanceRange('^1.0.0'));
|
||||
});
|
||||
|
||||
test('getUpperBound', t => {
|
||||
t.is(getUpperBound('1.x.x'), '2.0.0');
|
||||
t.is(getUpperBound('1.x'), '2.0.0');
|
||||
t.is(getUpperBound('1.0.x'), '1.1.0');
|
||||
t.is(getUpperBound('1.0.0'), '1.0.0');
|
||||
|
||||
t.is(getUpperBound('foo'), undefined);
|
||||
});
|
||||
|
||||
test('getLowerBound', t => {
|
||||
t.is(getLowerBound('1.x.x'), '1.0.0');
|
||||
t.is(getLowerBound('1.x'), '1.0.0');
|
||||
t.is(getLowerBound('1.0.x'), '1.0.0');
|
||||
t.is(getLowerBound('1.0.0'), '1.0.0');
|
||||
|
||||
t.is(getLowerBound('foo'), undefined);
|
||||
});
|
||||
|
||||
test('highest', t => {
|
||||
t.is(highest('1.0.0', '2.0.0'), '2.0.0');
|
||||
t.is(highest('1.1.1', '1.1.0'), '1.1.1');
|
||||
t.is(highest(null, '1.0.0'), '1.0.0');
|
||||
t.is(highest('1.0.0'), '1.0.0');
|
||||
t.is(highest(), undefined);
|
||||
});
|
||||
|
||||
test('lowest', t => {
|
||||
t.is(lowest('1.0.0', '2.0.0'), '1.0.0');
|
||||
t.is(lowest('1.1.1', '1.1.0'), '1.1.0');
|
||||
t.is(lowest(null, '1.0.0'), '1.0.0');
|
||||
t.is(lowest(), undefined);
|
||||
});
|
||||
|
||||
test.serial('getLatestVersion', t => {
|
||||
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.1', '1.0.0-alpha.1']), '1.2.0');
|
||||
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2']), undefined);
|
||||
|
||||
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.1', '1.0.0-alpha.1']), '1.2.0');
|
||||
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2']), undefined);
|
||||
|
||||
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.1', '1.0.0-alpha.1'], {withPrerelease: true}), '1.2.3-alpha.3');
|
||||
t.is(getLatestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2'], {withPrerelease: true}), '1.2.3-alpha.3');
|
||||
|
||||
t.is(getLatestVersion([]), undefined);
|
||||
});
|
||||
|
||||
test.serial('getEarliestVersion', t => {
|
||||
t.is(getEarliestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.0', '1.0.1-alpha.1']), '1.0.0');
|
||||
t.is(getEarliestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2']), undefined);
|
||||
|
||||
t.is(getEarliestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.0', '1.0.1-alpha.1']), '1.0.0');
|
||||
t.is(getEarliestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2']), undefined);
|
||||
|
||||
t.is(
|
||||
getEarliestVersion(['1.2.3-alpha.3', '1.2.0', '1.0.1', '1.0.0-alpha.1'], {withPrerelease: true}),
|
||||
'1.0.0-alpha.1'
|
||||
);
|
||||
t.is(getEarliestVersion(['1.2.3-alpha.3', '1.2.3-alpha.2'], {withPrerelease: true}), '1.2.3-alpha.2');
|
||||
|
||||
t.is(getEarliestVersion([]), undefined);
|
||||
});
|
||||
|
||||
test('getFirstVersion', t => {
|
||||
t.is(getFirstVersion(['1.2.0', '1.0.0', '1.3.0', '1.1.0', '1.4.0'], []), '1.0.0');
|
||||
t.is(
|
||||
getFirstVersion(
|
||||
['1.2.0', '1.0.0', '1.3.0', '1.1.0', '1.4.0'],
|
||||
[
|
||||
{name: 'master', tags: [{version: '1.0.0'}, {version: '1.1.0'}]},
|
||||
{name: 'next', tags: [{version: '1.0.0'}, {version: '1.1.0'}, {version: '1.2.0'}]},
|
||||
]
|
||||
),
|
||||
'1.3.0'
|
||||
);
|
||||
t.is(
|
||||
getFirstVersion(
|
||||
['1.2.0', '1.0.0', '1.1.0'],
|
||||
[
|
||||
{name: 'master', tags: [{version: '1.0.0'}, {version: '1.1.0'}]},
|
||||
{name: 'next', tags: [{version: '1.0.0'}, {version: '1.1.0'}, {version: '1.2.0'}]},
|
||||
]
|
||||
),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
test('getRange', t => {
|
||||
t.is(getRange('1.0.0', '1.1.0'), '>=1.0.0 <1.1.0');
|
||||
t.is(getRange('1.0.0'), '>=1.0.0');
|
||||
});
|
||||
|
||||
test('makeTag', t => {
|
||||
t.is(makeTag(`v\${version}`, '1.0.0'), 'v1.0.0');
|
||||
t.is(makeTag(`v\${version}`, '1.0.0', 'next'), 'v1.0.0@next');
|
||||
t.is(makeTag(`v\${version}@test`, '1.0.0', 'next'), 'v1.0.0@next@test');
|
||||
});
|
||||
+44
-6
@@ -5,7 +5,7 @@ import {gitRepo} from './helpers/git-utils';
|
||||
|
||||
test('Throw a AggregateError', async t => {
|
||||
const {cwd} = await gitRepo();
|
||||
const options = {};
|
||||
const options = {branches: [{name: 'master'}, {name: ''}]};
|
||||
|
||||
const errors = [...(await t.throws(verify({cwd, options})))];
|
||||
|
||||
@@ -21,11 +21,15 @@ test('Throw a AggregateError', async t => {
|
||||
t.is(errors[2].code, 'ETAGNOVERSION');
|
||||
t.truthy(errors[2].message);
|
||||
t.truthy(errors[2].details);
|
||||
t.is(errors[3].name, 'SemanticReleaseError');
|
||||
t.is(errors[3].code, 'EINVALIDBRANCH');
|
||||
t.truthy(errors[3].message);
|
||||
t.truthy(errors[3].details);
|
||||
});
|
||||
|
||||
test('Throw a SemanticReleaseError if does not run on a git repository', async t => {
|
||||
const cwd = tempy.directory();
|
||||
const options = {};
|
||||
const options = {branches: []};
|
||||
|
||||
const errors = [...(await t.throws(verify({cwd, options})))];
|
||||
|
||||
@@ -37,7 +41,7 @@ test('Throw a SemanticReleaseError if does not run on a git repository', async t
|
||||
|
||||
test('Throw a SemanticReleaseError if the "tagFormat" is not valid', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const options = {repositoryUrl, tagFormat: `?\${version}`};
|
||||
const options = {repositoryUrl, tagFormat: `?\${version}`, branches: []};
|
||||
|
||||
const errors = [...(await t.throws(verify({cwd, options})))];
|
||||
|
||||
@@ -49,7 +53,7 @@ test('Throw a SemanticReleaseError if the "tagFormat" is not valid', async t =>
|
||||
|
||||
test('Throw a SemanticReleaseError if the "tagFormat" does not contains the "version" variable', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const options = {repositoryUrl, tagFormat: 'test'};
|
||||
const options = {repositoryUrl, tagFormat: 'test', branches: []};
|
||||
|
||||
const errors = [...(await t.throws(verify({cwd, options})))];
|
||||
|
||||
@@ -61,7 +65,7 @@ test('Throw a SemanticReleaseError if the "tagFormat" does not contains the "ver
|
||||
|
||||
test('Throw a SemanticReleaseError if the "tagFormat" contains multiple "version" variables', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const options = {repositoryUrl, tagFormat: `\${version}v\${version}`};
|
||||
const options = {repositoryUrl, tagFormat: `\${version}v\${version}`, branches: []};
|
||||
|
||||
const errors = [...(await t.throws(verify({cwd, options})))];
|
||||
|
||||
@@ -71,9 +75,43 @@ test('Throw a SemanticReleaseError if the "tagFormat" contains multiple "version
|
||||
t.truthy(errors[0].details);
|
||||
});
|
||||
|
||||
test('Throw a SemanticReleaseError for each invalid branch', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const options = {
|
||||
repositoryUrl,
|
||||
tagFormat: `v\${version}`,
|
||||
branches: [{name: ''}, {name: ' '}, {name: 1}, {}, {name: ''}, 1, 'master'],
|
||||
};
|
||||
|
||||
const errors = [...(await t.throws(verify({cwd, options})))];
|
||||
|
||||
t.is(errors[0].name, 'SemanticReleaseError');
|
||||
t.is(errors[0].code, 'EINVALIDBRANCH');
|
||||
t.truthy(errors[0].message);
|
||||
t.truthy(errors[0].details);
|
||||
t.is(errors[1].name, 'SemanticReleaseError');
|
||||
t.is(errors[1].code, 'EINVALIDBRANCH');
|
||||
t.truthy(errors[1].message);
|
||||
t.truthy(errors[1].details);
|
||||
t.is(errors[2].name, 'SemanticReleaseError');
|
||||
t.is(errors[2].code, 'EINVALIDBRANCH');
|
||||
t.truthy(errors[2].message);
|
||||
t.truthy(errors[2].details);
|
||||
t.is(errors[3].name, 'SemanticReleaseError');
|
||||
t.is(errors[3].code, 'EINVALIDBRANCH');
|
||||
t.truthy(errors[3].message);
|
||||
t.truthy(errors[3].details);
|
||||
t.is(errors[4].code, 'EINVALIDBRANCH');
|
||||
t.truthy(errors[4].message);
|
||||
t.truthy(errors[4].details);
|
||||
t.is(errors[5].code, 'EINVALIDBRANCH');
|
||||
t.truthy(errors[5].message);
|
||||
t.truthy(errors[5].details);
|
||||
});
|
||||
|
||||
test('Return "true" if all verification pass', async t => {
|
||||
const {cwd, repositoryUrl} = await gitRepo(true);
|
||||
const options = {repositoryUrl, tagFormat: `v\${version}`};
|
||||
const options = {repositoryUrl, tagFormat: `v\${version}`, branches: [{name: 'master'}]};
|
||||
|
||||
await t.notThrows(verify({cwd, options}));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user