Compare commits

..
Author SHA1 Message Date
Pierre Vanduynslager b2c1b2c670 feat: use Git notes to store the channels on which a version has been released
BREAKING CHANGE: this feature change the way semantic-release keep track of the channels on which a version has been released.
It now use a JSON object stored in a [Git note](https://git-scm.com/docs/git-notes) instead of Git tags formatted as v{version}@{channel}.

The tags formatted as v{version}@{channel} will now be ignored. If you have made releases with v16.0.0 on branches other than the default one you will have to update your repository.

The changes to make consist in:
- Finding all the versions that have been released on a branch other than the default one by searching for all tags formatted v{version}@{channel}
- For each of those version:
  - Create a tag without the {@channel} if none doesn't already exists
  - Add a Git note to the tag without the {@channel} containing the channels on which the version was released formatted as `{"channels":["channel1","channel2"]}` and using `null` for the default channel (for example.`{"channels":[null,"channel1","channel2"]}`)
  - Push the tags and notes
  - Update the GitHub releases that refer to a tag formatted as v{version}@{channel} to use the tag without it
  - Delete the tags formatted as v{version}@{channel}
2019-12-02 23:38:40 -05:00
Pierre Vanduynslager 2caafbaa2b fix: add a flag indicate which branch is the main one 2019-11-28 13:59:06 -05:00
Pierre Vanduynslager e722354f4f style: merge both filter functions in get-last-release 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 97748c5e25 fix: do not create tags in dry-run mode for released to add to a channel 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager aec96c791f fix: correctly determine release to add to a channel
- Add only the most recent release to a channel (rather than adding all the one not added yet)
- Avoid attempting to ad the version twice in case that version is already present in multiple upper branches
2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 5744c5ecd2 fix: add channel to publish success log 2019-11-27 15:18:23 -05:00
Pierre Vanduynslager 1275b8c005 revert: fix: update commit-analyzer dep version off errant publish
This reverts commit 8d8910577f.
2019-11-23 11:06:31 -05:00
Eddie LefflerandPierre Vanduynslager 8d8910577f fix: update commit-analyzer dep version off errant publish 2019-11-20 23:11:33 -05:00
EddieandPierre Vanduynslager 9890584007 fix: update plugins dependencies 2019-11-20 22:29:59 -05:00
Pierre Vanduynslager 63f51ae6dd fix: allow multiple branches with same channel 2019-11-20 16:17:24 -05:00
Pierre Vanduynslager 20e7a38cdb style: prettier formatting 2019-11-20 14:16:13 -05:00
Pierre Vanduynslager e06eb82cff chore: add Mockserver generated file to gitignore 2019-11-20 14:16:13 -05:00
Pierre Vanduynslager 751a5f1349 fix: handle branch properties set to false 2019-11-20 14:16:13 -05:00
29 changed files with 1057 additions and 675 deletions
+3
View File
@@ -131,3 +131,6 @@ yarn.lock
# Gitbook
_book
# Mockserver
CertificateAuthorityCertificate.pem
+5 -3
View File
@@ -48,8 +48,10 @@ For example the configuration `['+([0-9])?(.{+([0-9]),x}).x', 'master', 'next']`
### channel
The `channel` can be defined for any branch type. If it's not defined, releases will be done on the default distribution channel (for example the `@latest` [dist-tag](https://docs.npmjs.com/cli/dist-tag) for npm).
The value of `channel`, if defined, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
The `channel` can be defined for any branch type. By default releases will be done on the default distribution channel (for example the `@latest` [dist-tag](https://docs.npmjs.com/cli/dist-tag) for npm) for the first [release branch](#release-branches) and on a distribution channel named based on the branch `name` for any other branch.
If the `channel` property is set to `false` the default channel will be used.
The value of `channel`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
For example the configuration `['master', {name: 'next', channel: 'channel-${name}'}]` will be expanded as:
```js
@@ -78,7 +80,7 @@ For example the configuration `['1.1.x', '1.2.x', 'master']` will be expanded as
### prerelease
A `prerelease` property applies only to pre-release branches, is required and The `prerelease` value must be valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). It will determine the name of versions (for example if `prerelease` is set to `beta` the version be formatted like `2.0.0-beta.1`, `2.0.0-beta.2` etc...).
A `prerelease` property applies only to pre-release branches and the `prerelease` value must be valid per the [Semantic Versioning Specification](https://semver.org/#spec-item-9). It will determine the name of versions (for example if `prerelease` is set to `beta` the version be formatted like `2.0.0-beta.1`, `2.0.0-beta.2` etc...).
If the `prerelease` property is set to `true` the `name` value will be used.
The value of `prerelease`, if defined as a string, is generated with [Lodash template](https://lodash.com/docs#template) with the variable `name` available.
+39 -29
View File
@@ -3,7 +3,6 @@ const marked = require('marked');
const TerminalRenderer = require('marked-terminal');
const envCi = require('env-ci');
const hookStd = require('hook-std');
const pEachSeries = require('p-each-series');
const semver = require('semver');
const AggregateError = require('aggregate-error');
const pkg = require('./package.json');
@@ -13,18 +12,18 @@ const verify = require('./lib/verify');
const getNextVersion = require('./lib/get-next-version');
const getCommits = require('./lib/get-commits');
const getLastRelease = require('./lib/get-last-release');
const getReleasesToAdd = require('./lib/get-releases-to-add');
const getReleaseToAdd = require('./lib/get-release-to-add');
const {extractErrors, makeTag} = require('./lib/utils');
const getGitAuthUrl = require('./lib/get-git-auth-url');
const getBranches = require('./lib/branches');
const getLogger = require('./lib/get-logger');
const {verifyAuth, isBranchUpToDate, getGitHead, tag, push, getTagHead} = require('./lib/git');
const {verifyAuth, isBranchUpToDate, getGitHead, tag, push, pushNotes, getTagHead, addNote} = require('./lib/git');
const getError = require('./lib/get-error');
const {COMMIT_NAME, COMMIT_EMAIL} = require('./lib/definitions/constants');
marked.setOptions({renderer: new TerminalRenderer()});
/* eslint complexity: ["warn", 25] */
/* eslint complexity: off */
async function run(context, plugins) {
const {cwd, env, options, logger} = context;
const {isCi, branch: ciBranch, isPr} = envCi({env, cwd});
@@ -92,35 +91,42 @@ async function run(context, plugins) {
await plugins.verifyConditions(context);
const releasesToAdd = getReleasesToAdd(context);
const errors = [];
context.releases = [];
const releaseToAdd = getReleaseToAdd(context);
if (releaseToAdd) {
const {lastRelease, currentRelease, nextRelease} = releaseToAdd;
await pEachSeries(releasesToAdd, async ({lastRelease, currentRelease, nextRelease}) => {
nextRelease.gitHead = await getTagHead(nextRelease.gitHead, {cwd, env});
currentRelease.gitHead = await getTagHead(currentRelease.gitHead, {cwd, env});
if (context.branch.mergeRange && !semver.satisfies(nextRelease.version, context.branch.mergeRange)) {
errors.push(getError('EINVALIDMAINTENANCEMERGE', {...context, nextRelease}));
return;
} else {
const commits = await getCommits({...context, lastRelease, nextRelease});
nextRelease.notes = await plugins.generateNotes({...context, commits, lastRelease, nextRelease});
if (options.dryRun) {
logger.warn(`Skip ${nextRelease.gitTag} tag creation in dry-run mode`);
} else {
await addNote({channels: [...currentRelease.channels, nextRelease.channel]}, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
await pushNotes(options.repositoryUrl, {cwd, env});
logger.success(`Add channel ${nextRelease.channel} to tag ${nextRelease.gitTag}`);
}
context.branch.tags.push({
version: nextRelease.version,
channel: nextRelease.channel,
gitTag: nextRelease.gitTag,
gitHead: nextRelease.gitHead,
});
const releases = await plugins.addChannel({...context, commits, lastRelease, currentRelease, nextRelease});
context.releases.push(...releases);
await plugins.success({...context, lastRelease, commits, nextRelease, releases});
}
const commits = await getCommits({...context, lastRelease, nextRelease});
nextRelease.notes = await plugins.generateNotes({...context, commits, lastRelease, nextRelease});
logger.log('Create tag %s', nextRelease.gitTag);
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
context.branch.tags.push({
version: nextRelease.version,
channel: nextRelease.channel,
gitTag: nextRelease.gitTag,
gitHead: nextRelease.gitHead,
});
const releases = await plugins.addChannel({...context, commits, lastRelease, currentRelease, nextRelease});
context.releases.push(...releases);
await plugins.success({...context, lastRelease, commits, nextRelease, releases});
});
}
if (errors.length > 0) {
throw new AggregateError(errors);
@@ -143,7 +149,7 @@ async function run(context, plugins) {
const nextRelease = {
type: await plugins.analyzeCommits(context),
channel: context.branch.channel,
channel: context.branch.channel || null,
gitHead: await getGitHead({cwd, env}),
};
if (!nextRelease.type) {
@@ -153,8 +159,8 @@ async function run(context, plugins) {
context.nextRelease = nextRelease;
nextRelease.version = getNextVersion(context);
nextRelease.gitTag = makeTag(options.tagFormat, nextRelease.version, nextRelease.channel);
nextRelease.name = makeTag(options.tagFormat, nextRelease.version);
nextRelease.gitTag = makeTag(options.tagFormat, nextRelease.version);
nextRelease.name = nextRelease.gitTag;
if (context.branch.type !== 'prerelease' && !semver.satisfies(nextRelease.version, context.branch.range)) {
throw getError('EINVALIDNEXTVERSION', {
@@ -176,7 +182,9 @@ async function run(context, plugins) {
} else {
// Create the tag before calling the publish plugins as some require the tag to exists
await tag(nextRelease.gitTag, nextRelease.gitHead, {cwd, env});
await addNote({channels: [nextRelease.channel]}, nextRelease.gitHead, {cwd, env});
await push(options.repositoryUrl, {cwd, env});
await pushNotes(options.repositoryUrl, {cwd, env});
logger.success(`Created tag ${nextRelease.gitTag}`);
}
@@ -185,7 +193,9 @@ async function run(context, plugins) {
await plugins.success({...context, releases});
logger.success(`Published release ${nextRelease.version}`);
logger.success(
`Published release ${nextRelease.version} on ${nextRelease.channel ? nextRelease.channel : 'default'} channel`
);
if (options.dryRun) {
logger.log(`Release note for version ${nextRelease.version}:`);
+12 -6
View File
@@ -2,22 +2,28 @@ const {template, escapeRegExp} = require('lodash');
const semver = require('semver');
const pReduce = require('p-reduce');
const debug = require('debug')('semantic-release:get-tags');
const {getTags} = require('../../lib/git');
const {getTags, getNote} = require('../../lib/git');
module.exports = async ({cwd, env, options: {tagFormat}}, branches) => {
// Generate a regex to parse tags formatted with `tagFormat`
// by replacing the `version` variable in the template by `(.+)`.
// The `tagFormat` is compiled with space as the `version` as it's an invalid tag character,
// so it's guaranteed to no be present in the `tagFormat`.
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.[^@]+)@?(.+)?')}`;
const tagRegexp = `^${escapeRegExp(template(tagFormat)({version: ' '})).replace(' ', '(.+)')}`;
return pReduce(
branches,
async (branches, branch) => {
const branchTags = (await getTags(branch.name, {cwd, env})).reduce((tags, tag) => {
const [, version, channel] = tag.match(tagRegexp) || [];
return version && semver.valid(semver.clean(version)) ? [...tags, {gitTag: tag, version, channel}] : tags;
}, []);
const branchTags = await pReduce(
await getTags(branch.name, {cwd, env}),
async (branchTags, tag) => {
const [, version] = tag.match(tagRegexp) || [];
return version && semver.valid(semver.clean(version))
? [...branchTags, {gitTag: tag, version, channels: (await getNote(tag, {cwd, env})).channels || [null]}]
: branchTags;
},
[]
);
debug('found tags for branch %s: %o', branch.name, branchTags);
return [...branches, {...branch, tags: branchTags}];
+3 -1
View File
@@ -3,7 +3,7 @@ const AggregateError = require('aggregate-error');
const pEachSeries = require('p-each-series');
const DEFINITIONS = require('../definitions/branches');
const getError = require('../get-error');
const {fetch, verifyBranchName} = require('../git');
const {fetch, fetchNotes, verifyBranchName} = require('../git');
const expand = require('./expand');
const getTags = require('./get-tags');
const normalize = require('./normalize');
@@ -21,6 +21,8 @@ module.exports = async (repositoryUrl, context) => {
await fetch(repositoryUrl, name, {cwd, env});
});
await fetchNotes(repositoryUrl, {cwd, env});
const branches = await getTags(context, remoteBranches);
const errors = [];
+1
View File
@@ -91,6 +91,7 @@ function release({release}) {
name,
range: getRange(min, bound),
accept: bound ? RELEASE_TYPE.slice(0, RELEASE_TYPE.indexOf(diff)) : RELEASE_TYPE,
main: idx === 0,
};
});
}
+4 -4
View File
@@ -1,15 +1,15 @@
const {isUndefined, uniqBy} = require('lodash');
const {isNil, uniqBy} = require('lodash');
const semver = require('semver');
const {isMaintenanceRange} = require('../utils');
const maintenance = {
filter: ({name, range}) => !isUndefined(range) || isMaintenanceRange(name),
branchValidator: ({range}) => (isUndefined(range) ? true : isMaintenanceRange(range)),
filter: ({name, range}) => (!isNil(range) && range !== false) || isMaintenanceRange(name),
branchValidator: ({range}) => (isNil(range) ? true : isMaintenanceRange(range)),
branchesValidator: branches => uniqBy(branches, ({range}) => semver.validRange(range)).length === branches.length,
};
const prerelease = {
filter: ({prerelease}) => !isUndefined(prerelease),
filter: ({prerelease}) => !isNil(prerelease) && prerelease !== false,
branchValidator: ({name, prerelease}) =>
Boolean(prerelease) && Boolean(semver.valid(`1.0.0-${prerelease === true ? name : prerelease}.1`)),
branchesValidator: branches => uniqBy(branches, 'prerelease').length === branches.length,
+3
View File
@@ -14,6 +14,8 @@ const SECRET_REPLACEMENT = '[secure]';
const SECRET_MIN_SIZE = 5;
const GIT_NOTE_REF = 'semantic-release';
module.exports = {
RELEASE_TYPE,
FIRST_RELEASE,
@@ -23,4 +25,5 @@ module.exports = {
RELEASE_NOTES_SEPARATOR,
SECRET_REPLACEMENT,
SECRET_MIN_SIZE,
GIT_NOTE_REF,
};
+9 -5
View File
@@ -1,6 +1,6 @@
const {isUndefined} = require('lodash');
const semver = require('semver');
const {makeTag} = require('./utils');
const {makeTag, isSameChannel} = require('./utils');
/**
* Last release.
@@ -27,13 +27,17 @@ const {makeTag} = require('./utils');
* @return {LastRelease} The last tagged release or empty object if none is found.
*/
module.exports = ({branch, options: {tagFormat}}, {before} = {}) => {
const [{version, gitTag, channel} = {}] = branch.tags
.filter(tag => (branch.type === 'prerelease' && branch.channel === tag.channel) || !semver.prerelease(tag.version))
.filter(tag => isUndefined(before) || semver.lt(tag.version, before))
const [{version, gitTag, channels} = {}] = branch.tags
.filter(
tag =>
((branch.type === 'prerelease' && tag.channels.some(channel => isSameChannel(branch.channel, channel))) ||
!semver.prerelease(tag.version)) &&
(isUndefined(before) || semver.lt(tag.version, before))
)
.sort((a, b) => semver.rcompare(a.version, b.version));
if (gitTag) {
return {version, gitTag, channel, gitHead: gitTag, name: makeTag(tagFormat, version)};
return {version, gitTag, channels, gitHead: gitTag, name: makeTag(tagFormat, version)};
}
return {};
+3 -1
View File
@@ -1,5 +1,6 @@
const semver = require('semver');
const {FIRST_RELEASE, FIRSTPRERELEASE} = require('./definitions/constants');
const {isSameChannel} = require('./utils');
module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) => {
let version;
@@ -7,7 +8,8 @@ module.exports = ({branch, nextRelease: {type, channel}, lastRelease, logger}) =
const {major, minor, patch} = semver.parse(lastRelease.version);
version =
branch.type === 'prerelease'
? semver.prerelease(lastRelease.version) && lastRelease.channel === channel
? semver.prerelease(lastRelease.version) &&
lastRelease.channels.some(lastReleaseChannel => isSameChannel(lastReleaseChannel, channel))
? semver.inc(lastRelease.version, 'prerelease')
: `${semver.inc(`${major}.${minor}.${patch}`, type)}-${branch.prerelease}.${FIRSTPRERELEASE}`
: semver.inc(lastRelease.version, type);
+60
View File
@@ -0,0 +1,60 @@
const {uniqBy, intersection} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
const higherChannels = branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
.map(({channel}) => channel || null);
const versiontoAdd = uniqBy(
branch.tags.filter(
({channels, version}) =>
!channels.includes(branch.channel || null) &&
intersection(channels, higherChannels).length > 0 &&
(branch.type !== 'maintenance' || semver.gte(version, getLowerBound(branch.mergeRange)))
),
'version'
).sort((a, b) => semver.compare(b.version, a.version))[0];
if (versiontoAdd) {
const {version, gitTag, channels} = versiontoAdd;
const lastRelease = getLastRelease(context, {before: version});
if (semver.gt(getLastRelease(context).version, version)) {
return;
}
const type = lastRelease.version ? semverDiff(lastRelease.version, version) : 'major';
const name = makeTag(tagFormat, version);
return {
lastRelease,
currentRelease: {type, version, channels, gitTag, name, gitHead: gitTag},
nextRelease: {
type,
version,
channel: branch.channel || null,
gitTag: makeTag(tagFormat, version),
name,
gitHead: gitTag,
},
};
}
};
-71
View File
@@ -1,71 +0,0 @@
const {uniq} = require('lodash');
const semver = require('semver');
const semverDiff = require('semver-diff');
const getLastRelease = require('./get-last-release');
const {makeTag, getLowerBound} = require('./utils');
/**
* Find releases that have been merged from from a higher branch but not added on the channel of the current branch.
*
* @param {Object} context semantic-release context.
*
* @return {Array<Object>} Last release and next release to be added on the channel of the current branch.
*/
module.exports = context => {
const {
branch,
branches,
options: {tagFormat},
} = context;
return (
branches
// Consider only releases of higher branches
.slice(branches.findIndex(({name}) => name === branch.name) + 1)
// Exclude prerelease branches
.filter(({type}) => type !== 'prerelease')
// Find higher branch releases merged to building branch but not released on associated channel
.reduce(
(releases, higherBranch) => [
...releases,
// For all unique release version of the higher branch merged on current branch, excluding lower than start range version for maintenance branches
...uniq(
branch.tags.filter(
({channel, version}) =>
channel === higherBranch.channel &&
channel !== branch.channel &&
(branch.type !== 'maintenance' || semver.gte(version, getLowerBound(branch.mergeRange)))
)
)
// Find ones that are not released on the building branch channel
.filter(tag =>
branch.tags.every(
({version, channel}) =>
version !== tag.version || channel === higherBranch.channel || channel !== branch.channel
)
)
// Sort in ascending order to add the most recent release last
.sort((a, b) => semver.compare(a.version, b.version))
// Construct the last and next release to add to the building branch channel
.map(({version, gitTag}) => {
const lastRelease = getLastRelease(context, {before: version});
const type = lastRelease.version ? semverDiff(lastRelease.version, version) : 'major';
const name = makeTag(tagFormat, version);
return {
lastRelease,
currentRelease: {type, version, channel: higherBranch.channel, gitTag, name, gitHead: gitTag},
nextRelease: {
type,
version,
channel: branch.channel,
gitTag: makeTag(tagFormat, version, branch.channel),
name,
gitHead: gitTag,
},
};
}),
],
[]
)
);
};
+77 -5
View File
@@ -2,6 +2,7 @@ const gitLogParser = require('git-log-parser');
const getStream = require('get-stream');
const execa = require('execa');
const debug = require('debug')('semantic-release:git');
const {GIT_NOTE_REF} = require('./definitions/constants');
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
@@ -42,12 +43,14 @@ async function getTags(branch, execaOpts) {
* @return {Promise<Array<Object>>} The list of commits between `from` and `to`.
*/
async function getCommits(from, to, execaOpts) {
return (await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
return (
await getStream.array(
gitLogParser.parse(
{_: `${from ? from + '..' : ''}${to}`},
{cwd: execaOpts.cwd, env: {...process.env, ...execaOpts.env}}
)
)
)).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
).map(({message, gitTags, ...commit}) => ({...commit, message: message.trim(), gitTags: gitTags.trim()}));
}
/**
@@ -144,6 +147,27 @@ async function fetch(repositoryUrl, branch, execaOpts) {
}
}
/**
* Unshallow the git repository if necessary and fetch all the notes.
*
* @param {String} repositoryUrl The remote repository URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function fetchNotes(repositoryUrl, execaOpts) {
try {
await execa(
'git',
['fetch', '--unshallow', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`],
execaOpts
);
} catch (_) {
await execa('git', ['fetch', repositoryUrl, `+refs/notes/${GIT_NOTE_REF}:refs/notes/${GIT_NOTE_REF}`], {
...execaOpts,
reject: false,
});
}
}
/**
* Get the HEAD sha.
*
@@ -228,6 +252,18 @@ async function push(repositoryUrl, execaOpts) {
await execa('git', ['push', '--tags', repositoryUrl], execaOpts);
}
/**
* Push notes to the remote repository.
*
* @param {String} repositoryUrl The remote repository URL.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @throws {Error} if the push failed.
*/
async function pushNotes(repositoryUrl, execaOpts) {
await execa('git', ['push', repositoryUrl, `refs/notes/${GIT_NOTE_REF}`], execaOpts);
}
/**
* Verify a tag name is a valid Git reference.
*
@@ -278,6 +314,38 @@ async function isBranchUpToDate(repositoryUrl, branch, execaOpts) {
}
}
/**
* Get and parse the JSON note of a given reference.
*
* @param {String} ref The Git reference for which to retrieve the note.
* @param {Object} [execaOpts] Options to pass to `execa`.
*
* @return {Object} the parsed JSON note if there is one, an empty object otherwise.
*/
async function getNote(ref, execaOpts) {
try {
return JSON.parse((await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout);
} catch (error) {
if (error.exitCode === 1) {
return {};
}
debug(error);
throw error;
}
}
/**
* Get and parse the JSON note of a given reference.
*
* @param {Object} note The object to save in the reference note.
* @param {String} ref The Git reference to add the note to.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
async function addNote(note, ref, execaOpts) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-f', '-m', JSON.stringify(note), ref], execaOpts);
}
module.exports = {
getTagHead,
getTags,
@@ -286,13 +354,17 @@ module.exports = {
isRefInHistory,
isRefExists,
fetch,
fetchNotes,
getGitHead,
repoUrl,
isGitRepo,
verifyAuth,
tag,
push,
pushNotes,
verifyTagName,
isBranchUpToDate,
verifyBranchName,
getNote,
addNote,
};
+7 -2
View File
@@ -67,8 +67,12 @@ function getRange(min, max) {
return `>=${min}${max ? ` <${max}` : ''}`;
}
function makeTag(tagFormat, version, channel) {
return template(tagFormat)({version: `${version}${channel ? `@${channel}` : ''}`});
function makeTag(tagFormat, version) {
return template(tagFormat)({version});
}
function isSameChannel(channel, otherChannel) {
return channel === otherChannel || (!channel && !otherChannel);
}
module.exports = {
@@ -86,4 +90,5 @@ module.exports = {
getFirstVersion,
getRange,
makeTag,
isSameChannel,
};
+3 -3
View File
@@ -22,10 +22,10 @@
"Pierre Vanduynslager (https://twitter.com/@pvdlg_)"
],
"dependencies": {
"@semantic-release/commit-analyzer": "7.0.0-beta.4",
"@semantic-release/commit-analyzer": "^7.0.0-beta",
"@semantic-release/error": "^2.2.0",
"@semantic-release/github": "5.6.0-beta.2",
"@semantic-release/npm": "6.0.0-beta.3",
"@semantic-release/github": "^5.6.0-beta",
"@semantic-release/npm": "^6.0.0-beta",
"@semantic-release/release-notes-generator": "^7.1.2",
"aggregate-error": "^3.0.0",
"cosmiconfig": "^5.0.1",
+8 -2
View File
@@ -171,7 +171,10 @@ test('Throw SemanticReleaseError for invalid configurations', async t => {
});
test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
const branches = [{name: 'master', tags: []}, {name: 'master', tags: []}];
const branches = [
{name: 'master', tags: []},
{name: 'master', tags: []},
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', {options: {branches}})))];
@@ -183,7 +186,10 @@ test('Throw a SemanticReleaseError if there is duplicate branches', async t => {
});
test('Throw a SemanticReleaseError for each invalid branch name', async t => {
const branches = [{name: '~master', tags: []}, {name: '^master', tags: []}];
const branches = [
{name: '~master', tags: []},
{name: '^master', tags: []},
];
const getBranches = proxyquire('../../lib/branches', {'./get-tags': () => branches, './expand': () => []});
const errors = [...(await t.throwsAsync(getBranches('repositoryUrl', {options: {branches}})))];
+32 -56
View File
@@ -1,6 +1,6 @@
import test from 'ava';
import getTags from '../../lib/branches/get-tags';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout} from '../helpers/git-utils';
import {gitRepo, gitCommits, gitTagVersion, gitCheckout, gitAddNote} from '../helpers/git-utils';
test('Get the valid tags', async t => {
const {cwd} = await gitRepo();
@@ -20,9 +20,9 @@ test('Get the valid tags', async t => {
{
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined},
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channel: undefined},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v3.0.0-beta.1', version: '3.0.0-beta.1', channels: [null]},
],
},
]);
@@ -30,20 +30,21 @@ test('Get the valid tags', async t => {
test('Get the valid tags from multiple branches', async t => {
const {cwd} = await gitRepo();
const commits = await gitCommits(['First'], {cwd});
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 gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitTagVersion('v1.1.0@1.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.1.0', {cwd});
await gitCheckout('1.x', true, {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['Third'], {cwd})));
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitTagVersion('v2.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['Fourth'], {cwd})));
await gitTagVersion('v3.0.0@next', undefined, {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v3.0.0', {cwd});
const result = await getTags({cwd, options: {tagFormat: `v\${version}`}}, [
{name: '1.x'},
@@ -55,47 +56,17 @@ test('Get the valid tags from multiple branches', async t => {
{
name: '1.x',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channel: undefined},
{gitTag: 'v1.0.0@1.x', version: '1.0.0', channel: '1.x'},
{gitTag: 'v1.1.0', version: '1.1.0', channel: undefined},
{gitTag: 'v1.1.0@1.x', version: '1.1.0', channel: '1.x'},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, '1.x']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null, '1.x']},
],
},
{
name: 'master',
tags: [
...result[0].tags,
{gitTag: 'v2.0.0', version: '2.0.0', channel: undefined},
{gitTag: 'v2.0.0@next', version: '2.0.0', channel: 'next'},
],
tags: [...result[0].tags, {gitTag: 'v2.0.0', version: '2.0.0', channels: [null, 'next']}],
},
{
name: 'next',
tags: [...result[1].tags, {gitTag: 'v3.0.0@next', version: '3.0.0', channel: 'next'}],
},
]);
});
test('Match the tag name from the begining of the string and the channel from the last "@"', async t => {
const {cwd} = await gitRepo();
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},
{gitTag: 'prefix@v1.0.0@next', version: '1.0.0', channel: 'next'},
{gitTag: 'prefix@v2.0.0', version: '2.0.0', channel: undefined},
{gitTag: 'prefix@v2.0.0@next', version: '2.0.0', channel: 'next'},
],
tags: [...result[1].tags, {gitTag: 'v3.0.0', version: '3.0.0', channels: ['next']}],
},
]);
});
@@ -120,16 +91,21 @@ test('Return branches with and empty tags array if no valid tag is found in hist
await gitCheckout('next', true, {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Third'], {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitTagVersion('v2.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v2.0.0', {cwd});
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v3.0.0', undefined, {cwd});
await gitTagVersion('v3.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v3.0.0', {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: []}]);
t.deepEqual(result, [
{name: 'master', tags: []},
{name: 'next', tags: []},
]);
});
test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
@@ -138,19 +114,19 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
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}]},
{name: 'master', tags: [{gitTag: '1.0.0', version: '1.0.0', channels: [null]}]},
]);
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}]},
{name: 'master', tags: [{gitTag: 'foo-1.0.0-bar', version: '1.0.0', channels: [null]}]},
]);
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}],
tags: [{gitTag: 'foo-v1.0.0-bar', version: '1.0.0', channels: [null]}],
},
]);
@@ -158,7 +134,7 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
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}],
tags: [{gitTag: '(.+)/1.0.0/(a-z)', version: '1.0.0', channels: [null]}],
},
]);
@@ -166,12 +142,12 @@ test('Get the highest valid tag corresponding to the "tagFormat"', async t => {
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}],
tags: [{gitTag: '2.0.0-1.0.0-bar.1', version: '1.0.0', channels: [null]}],
},
]);
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}]},
{name: 'master', tags: [{gitTag: '3.0.0-bar.2', version: '3.0.0', channels: [null]}]},
]);
});
+127 -37
View File
@@ -4,7 +4,11 @@ import normalize from '../../lib/branches/normalize';
const toTags = versions => versions.map(version => ({version}));
test('Maintenance branches - initial state', t => {
const maintenance = [{name: '1.x', channel: '1.x', tags: []}, {name: '1.1.x', tags: []}, {name: '1.2.x', tags: []}];
const maintenance = [
{name: '1.x', channel: '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, mergeRange}) => ({
@@ -151,16 +155,26 @@ test('Release branches - initial state', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{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: 'master', range: '>=1.0.0 <1.1.0', accept: ['patch'], channel: undefined, main: true},
{
type: 'release',
name: 'next',
range: '>=1.1.0 <2.0.0',
accept: ['patch', 'minor'],
channel: 'next',
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -174,16 +188,26 @@ test('Release branches - 3 release branches', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{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: 'master', range: '>=1.0.2 <1.1.0', accept: ['patch'], channel: undefined, main: true},
{
type: 'release',
name: 'next',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
channel: 'next',
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -196,10 +220,26 @@ test('Release branches - 2 release branches', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{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'},
{
type: 'release',
name: 'master',
range: '>=1.2.0 <2.0.0',
accept: ['patch', 'minor'],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=2.1.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
main: false,
},
]
);
});
@@ -221,16 +261,19 @@ test('Release branches - cap ranges to first release only present on following b
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{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: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined, main: true},
{type: 'release', name: 'next', range: '>=2.1.0 <2.2.0', accept: ['patch'], channel: 'next', main: false},
{
type: 'release',
name: 'next-major',
range: '>=2.2.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -243,10 +286,26 @@ test('Release branches - Handle missing previous tags in branch history', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{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'},
{
type: 'release',
name: 'master',
range: '>=2.0.0 <3.0.0',
accept: ['patch', 'minor'],
channel: undefined,
main: true,
},
{
type: 'release',
name: 'next',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next',
main: false,
},
]
);
});
@@ -259,16 +318,26 @@ test('Release branches - enforce release gaps after downstream merge', t => {
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{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: 'master', range: '>=2.0.0 <2.1.0', accept: ['patch'], channel: undefined, main: true},
{
type: 'release',
name: 'next',
range: '>=2.1.0 <3.0.0',
accept: ['patch', 'minor'],
channel: 'next',
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=3.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -282,16 +351,26 @@ test('Release branches - limit releases on 2nd and 3rd branche based on 1st bran
];
t.deepEqual(
normalize.release({release}).map(({type, name, range, accept, channel}) => ({type, name, range, accept, channel})),
normalize
.release({release})
.map(({type, name, range, accept, channel, main}) => ({type, name, range, accept, channel, main})),
[
{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: 'master', range: '>=3.0.0 <3.1.0', accept: ['patch'], channel: undefined, main: true},
{
type: 'release',
name: 'next',
range: '>=3.1.0 <4.0.0',
accept: ['patch', 'minor'],
channel: 'next',
main: false,
},
{
type: 'release',
name: 'next-major',
range: '>=4.0.0',
accept: ['patch', 'minor', 'major'],
channel: 'next-major',
main: false,
},
]
);
@@ -303,24 +382,35 @@ test('Prerelease branches', t => {
{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'},
]);
t.deepEqual(
normalize.prerelease({prerelease}).map(({type, name, channel}) => ({type, name, channel})),
[
{type: 'prerelease', name: 'beta', channel: 'beta'},
{type: 'prerelease', name: 'alpha', channel: 'alpha'},
]
);
});
test('Allow to set channel to "false" to prevent default', t => {
const maintenance = [{name: '1.x', channel: false, tags: []}];
const release = [{name: 'master', channel: false, tags: []}, {name: 'next', channel: false, tags: []}];
const release = [
{name: 'master', channel: false, tags: []},
{name: 'next', channel: false, tags: []},
];
const prerelease = [{name: 'beta', channel: false, prerelease: true, tags: []}];
t.deepEqual(normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})), [
{name: '1.x', channel: false},
]);
t.deepEqual(normalize.release({release}).map(({name, channel}) => ({name, channel})), [
{name: 'master', channel: false},
{name: 'next', channel: false},
]);
t.deepEqual(normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})), [
{name: 'beta', channel: false},
]);
t.deepEqual(
normalize.maintenance({maintenance, release}).map(({name, channel}) => ({name, channel})),
[{name: '1.x', channel: false}]
);
t.deepEqual(
normalize.release({release}).map(({name, channel}) => ({name, channel})),
[
{name: 'master', channel: false},
{name: 'next', channel: false},
]
);
t.deepEqual(
normalize.prerelease({prerelease}).map(({name, channel}) => ({name, channel})),
[{name: 'beta', channel: false}]
);
});
+7 -7
View File
@@ -8,9 +8,10 @@ test('A "maintenance" branch is identified by having a "range" property or a "na
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.true(maintenance.filter({name: 'some-name', range: true}));
t.false(maintenance.filter({name: 'some-name', range: null}));
t.false(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'}));
@@ -24,9 +25,8 @@ test('A "maintenance" branches must have a "range" property formatted like "N.x"
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: true}));
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 => {
@@ -36,13 +36,13 @@ test('The "maintenance" branches must have unique ranges', t => {
t.false(maintenance.branchesValidator([{range: '1.x.x'}, {range: '1.x'}]));
});
test('A "prerelease" branch is identified by having a range "prerelease" property', t => {
test('A "prerelease" branch is identified by having a thruthy "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', prerelease: null}));
t.false(prerelease.filter({name: 'some-name', prerelease: false}));
t.false(prerelease.filter({name: 'some-name'}));
});
+11 -11
View File
@@ -8,14 +8,14 @@ test('Get the highest non-prerelease valid tag', t => {
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1@beta', gitHead: 'v3.0.0-beta.1@beta'},
{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1', gitHead: 'v3.0.0-beta.1'},
],
type: 'release',
},
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channel: undefined});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channels: undefined});
});
test('Get the highest prerelease valid tag, ignoring other tags from other prerelease channels', t => {
@@ -25,9 +25,9 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
prerelease: 'beta',
channel: 'beta',
tags: [
{version: '1.0.0-beta.1', gitTag: 'v1.0.0-beta.1@beta', gitHead: 'v1.0.0-beta.1@beta', channel: 'beta'},
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2@beta', gitHead: 'v1.0.0-beta.2@beta', channel: 'beta'},
{version: '1.0.0-alpha.1', gitTag: 'v1.0.0-alpha.1@alpha', gitHead: 'v1.0.0-alpha.1@alpha', channel: 'alpha'},
{version: '1.0.0-beta.1', gitTag: 'v1.0.0-beta.1', gitHead: 'v1.0.0-beta.1', channels: ['beta']},
{version: '1.0.0-beta.2', gitTag: 'v1.0.0-beta.2', gitHead: 'v1.0.0-beta.2', channels: ['beta']},
{version: '1.0.0-alpha.1', gitTag: 'v1.0.0-alpha.1', gitHead: 'v1.0.0-alpha.1', channels: ['alpha']},
],
type: 'prerelease',
},
@@ -36,10 +36,10 @@ test('Get the highest prerelease valid tag, ignoring other tags from other prere
t.deepEqual(result, {
version: '1.0.0-beta.2',
gitTag: 'v1.0.0-beta.2@beta',
gitTag: 'v1.0.0-beta.2',
name: 'v1.0.0-beta.2',
gitHead: 'v1.0.0-beta.2@beta',
channel: 'beta',
gitHead: 'v1.0.0-beta.2',
channels: ['beta'],
});
});
@@ -47,7 +47,7 @@ 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: 'v3.0.0-beta.1@beta'}],
tags: [{version: '3.0.0-beta.1', gitTag: 'v3.0.0-beta.1', gitHead: 'v3.0.0-beta.1'}],
type: 'release',
},
options: {tagFormat: `v\${version}`},
@@ -65,7 +65,7 @@ test('Get the highest non-prerelease valid tag before a certain version', t => {
tags: [
{version: '2.0.0', gitTag: 'v2.0.0', gitHead: 'v2.0.0'},
{version: '1.0.0', gitTag: 'v1.0.0', gitHead: 'v1.0.0'},
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1@beta', gitHead: 'v2.0.0-beta.1@beta'},
{version: '2.0.0-beta.1', gitTag: 'v2.0.0-beta.1', gitHead: 'v2.0.0-beta.1'},
{version: '2.1.0', gitTag: 'v2.1.0', gitHead: 'v2.1.0'},
{version: '2.1.1', gitTag: 'v2.1.1', gitHead: 'v2.1.1'},
],
@@ -76,5 +76,5 @@ test('Get the highest non-prerelease valid tag before a certain version', t => {
{before: '2.1.0'}
);
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channel: undefined});
t.deepEqual(result, {version: '2.0.0', gitTag: 'v2.0.0', name: 'v2.0.0', gitHead: 'v2.0.0', channels: undefined});
});
+12 -12
View File
@@ -13,7 +13,7 @@ test('Increase version for patch release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.1'
@@ -25,7 +25,7 @@ test('Increase version for minor release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.1.0'
@@ -37,7 +37,7 @@ test('Increase version for major release', t => {
getNextVersion({
branch: {name: 'master', type: 'release'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'2.0.0'
@@ -61,7 +61,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.1-beta.1'
@@ -71,7 +71,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'patch'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -81,7 +81,7 @@ test('Increase version for patch release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'patch', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'1.0.1-alpha.1'
@@ -93,7 +93,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'1.1.0-beta.1'
@@ -103,7 +103,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'minor'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -113,7 +113,7 @@ test('Increase version for minor release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'minor', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'1.1.0-alpha.1'
@@ -125,7 +125,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0'},
lastRelease: {version: '1.0.0', channels: [undefined]},
logger: t.context.logger,
}),
'2.0.0-beta.1'
@@ -135,7 +135,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'beta', type: 'prerelease', prerelease: 'beta'},
nextRelease: {type: 'major'},
lastRelease: {version: '1.0.0-beta.1'},
lastRelease: {version: '1.0.0-beta.1', channels: [undefined]},
logger: t.context.logger,
}),
'1.0.0-beta.2'
@@ -145,7 +145,7 @@ test('Increase version for major release on prerelease branch', t => {
getNextVersion({
branch: {name: 'alpha', type: 'prerelease', prerelease: 'alpha'},
nextRelease: {type: 'major', channel: 'alpha'},
lastRelease: {version: '1.0.0-beta.1', channel: 'beta'},
lastRelease: {version: '1.0.0-beta.1', channels: ['beta']},
logger: t.context.logger,
}),
'2.0.0-alpha.1'
+282
View File
@@ -0,0 +1,282 @@
import test from 'ava';
import getReleaseToAdd from '../lib/get-release-to-add';
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['2.x']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null]},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '2.1.0', channels: [null], gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: 'v2.1.0'},
currentRelease: {
type: 'patch',
version: '2.1.1',
channels: [null],
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
nextRelease: {
type: 'patch',
version: '2.1.1',
channel: '2.x',
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
});
});
test('Return versions merged between release branches', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, 'next']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['next-major']},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {
version: '1.1.0',
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0',
channels: ['next'],
},
currentRelease: {
type: 'major',
version: '2.0.0',
channels: ['next-major'],
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: null,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
});
});
test('Return releases sorted by ascending order', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['next-major']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, 'next']},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0', name: 'v1.1.0', gitHead: 'v1.1.0', channels: ['next']},
currentRelease: {
type: 'major',
version: '2.0.0',
channels: ['next-major'],
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: null,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0',
},
});
});
test('No lastRelease', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [{gitTag: 'v1.0.0', version: '1.0.0', channels: ['next']}],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {},
currentRelease: {
type: 'major',
version: '1.0.0',
channels: ['next'],
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: 'v1.0.0',
},
nextRelease: {
type: 'major',
version: '1.0.0',
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: 'v1.0.0',
},
});
});
test('Ignore pre-release versions', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null, 'next']},
{gitTag: 'v1.1.0', version: '1.1.0', channels: ['next']},
{gitTag: 'v2.0.0-alpha.1', version: '2.0.0-alpha.1', channels: ['alpha']},
],
},
branches: [
{name: 'master'},
{name: 'next', channel: 'next'},
{name: 'alpha', type: 'prerelease', channel: 'alpha'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, {
lastRelease: {version: '1.0.0', channels: [null, 'next'], gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channels: ['next'],
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: null,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0',
},
});
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: 'latest',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null]},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
],
},
branches: [
{name: '2.x', channel: 'latest'},
{name: 'master', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
channel: 'latest',
tags: [
{gitTag: 'v1.0.0', channels: ['latest'], version: '1.0.0'},
{gitTag: 'v1.1.0', channels: ['latest'], version: '1.1.0'},
{gitTag: 'v2.0.0', channels: ['latest'], version: '2.0.0'},
],
},
branches: [
{name: 'master', channel: 'latest'},
{name: 'next', channel: 'latest'},
{name: 'next-major', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
const result = getReleaseToAdd({
branch: {
name: 'master',
channel: false,
tags: [
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
],
},
branches: [
{name: 'master', channel: false},
{name: 'next', channel: false},
{name: 'next-major', channel: false},
],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
test('Exclude versions number less than the latest version already released on that branch', t => {
const result = getReleaseToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0', channels: ['2.x']},
{gitTag: 'v2.0.0', version: '2.0.0', channels: [null]},
{gitTag: 'v2.1.0', version: '2.1.0', channels: [null]},
{gitTag: 'v2.1.1', version: '2.1.1', channels: [null, '2.x']},
{gitTag: 'v1.0.0', version: '1.0.0', channels: [null]},
{gitTag: 'v1.1.0', version: '1.1.0', channels: [null]},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.is(result, undefined);
});
-316
View File
@@ -1,316 +0,0 @@
import test from 'ava';
import getReleasesToAdd from '../lib/get-releases-to-add';
test('Return versions merged from release to maintenance branch, excluding lower than branch start range', t => {
const result = getReleasesToAdd({
branch: {
name: '2.x',
channel: '2.x',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0@2.x', version: '2.0.0', channel: '2.x'},
{gitTag: 'v2.0.0', version: '2.0.0'},
{gitTag: 'v2.1.0', version: '2.1.0'},
{gitTag: 'v2.1.1', version: '2.1.1'},
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.1.0', version: '1.1.0'},
],
},
branches: [{name: '2.x', channel: '2.x'}, {name: 'master'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '2.0.0', channel: '2.x', gitTag: 'v2.0.0@2.x', name: 'v2.0.0', gitHead: 'v2.0.0@2.x'},
currentRelease: {
type: 'minor',
version: '2.1.0',
channel: undefined,
gitTag: 'v2.1.0',
name: 'v2.1.0',
gitHead: 'v2.1.0',
},
nextRelease: {
type: 'minor',
version: '2.1.0',
channel: '2.x',
gitTag: 'v2.1.0@2.x',
name: 'v2.1.0',
gitHead: 'v2.1.0',
},
},
{
lastRelease: {version: '2.1.0', channel: undefined, gitTag: 'v2.1.0', name: 'v2.1.0', gitHead: 'v2.1.0'},
currentRelease: {
type: 'patch',
version: '2.1.1',
channel: undefined,
gitTag: 'v2.1.1',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
nextRelease: {
type: 'patch',
version: '2.1.1',
channel: '2.x',
gitTag: 'v2.1.1@2.x',
name: 'v2.1.1',
gitHead: 'v2.1.1',
},
},
]);
});
test('Return versions merged between release branches', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next'},
{gitTag: 'v2.0.0@next-major', version: '2.0.0', channel: 'next-major'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: 'v1.1.0@next', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
},
]);
});
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'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next'},
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'},
],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}, {name: 'next-major', channel: 'next-major'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
},
{
lastRelease: {version: '1.1.0', gitTag: 'v1.1.0@next', name: 'v1.1.0', gitHead: 'v1.1.0@next', channel: 'next'},
currentRelease: {
type: 'major',
version: '2.0.0',
channel: 'next-major',
gitTag: 'v2.0.0@next-major',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
nextRelease: {
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
name: 'v2.0.0',
gitHead: 'v2.0.0@next-major',
},
},
]);
});
test('No lastRelease', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'}],
},
branches: [{name: 'master'}, {name: 'next', channel: 'next'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {},
currentRelease: {
type: 'major',
version: '1.0.0',
channel: 'next',
gitTag: 'v1.0.0@next',
name: 'v1.0.0',
gitHead: 'v1.0.0@next',
},
nextRelease: {
type: 'major',
version: '1.0.0',
channel: undefined,
gitTag: 'v1.0.0',
name: 'v1.0.0',
gitHead: 'v1.0.0@next',
},
},
]);
});
test('Ignore pre-release versions', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
tags: [
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.0.0@next', version: '1.0.0', channel: 'next'},
{gitTag: 'v1.1.0@next', version: '1.1.0', channel: 'next'},
{gitTag: 'v2.0.0-alpha.1@alpha', version: '2.0.0', channel: 'alpha'},
],
},
branches: [
{name: 'master'},
{name: 'next', channel: 'next'},
{name: 'alpha', type: 'prerelease', channel: 'alpha'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, [
{
lastRelease: {version: '1.0.0', channel: undefined, gitTag: 'v1.0.0', name: 'v1.0.0', gitHead: 'v1.0.0'},
currentRelease: {
type: 'minor',
version: '1.1.0',
channel: 'next',
gitTag: 'v1.1.0@next',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
nextRelease: {
type: 'minor',
version: '1.1.0',
channel: undefined,
gitTag: 'v1.1.0',
name: 'v1.1.0',
gitHead: 'v1.1.0@next',
},
},
]);
});
test('Exclude versions merged from release to maintenance branch if they have the same "channel"', t => {
const result = getReleasesToAdd({
branch: {
name: '2.x',
channel: 'latest',
type: 'maintenance',
mergeRange: '>=2.0.0 <3.0.0',
tags: [
{gitTag: 'v2.0.0', version: '2.0.0'},
{gitTag: 'v2.0.0', version: '2.0.0'},
{gitTag: 'v2.1.0', version: '2.1.0'},
{gitTag: 'v2.1.1', version: '2.1.1'},
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.1.0', version: '1.1.0'},
],
},
branches: [{name: '2.x', channel: 'latest'}, {name: 'master', channel: 'latest'}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
test('Exclude versions merged between release branches if they have the same "channel"', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
channel: 'latest',
tags: [
{gitTag: 'v1.0.0', channel: 'latest', version: '1.0.0'},
{gitTag: 'v1.1.0', channel: 'latest', version: '1.1.0'},
{gitTag: 'v2.0.0', channel: 'latest', version: '2.0.0'},
],
},
branches: [
{name: 'master', channel: 'latest'},
{name: 'next', channel: 'latest'},
{name: 'next-major', channel: 'latest'},
],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
test('Exclude versions merged between release branches if they all have "channel" set to "false"', t => {
const result = getReleasesToAdd({
branch: {
name: 'master',
channel: false,
tags: [
{gitTag: 'v1.0.0', version: '1.0.0'},
{gitTag: 'v1.1.0', version: '1.1.0'},
{gitTag: 'v2.0.0', version: '2.0.0'},
],
},
branches: [{name: 'master', channel: false}, {name: 'next', channel: false}, {name: 'next-major', channel: false}],
options: {tagFormat: `v\${version}`},
});
t.deepEqual(result, []);
});
+93
View File
@@ -14,6 +14,9 @@ import {
isGitRepo,
verifyTagName,
isBranchUpToDate,
getNote,
addNote,
fetchNotes,
} from '../lib/git';
import {
gitRepo,
@@ -27,6 +30,8 @@ import {
gitRemoteTagHead,
gitPush,
gitDetachedHead,
gitAddNote,
gitGetNote,
} from './helpers/git-utils';
test('Get the last commit sha', async t => {
@@ -276,3 +281,91 @@ test('Return "true" if local repository is ahead', async t => {
t.true(await isBranchUpToDate(repositoryUrl, 'master', {cwd}));
});
test('Get a commit note', 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'], {cwd});
await gitAddNote(JSON.stringify({note: 'note'}), commits[0].hash, {cwd});
t.deepEqual(await getNote(commits[0].hash, {cwd}), {note: 'note'});
});
test('Return empty object if there is no commit note', 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'], {cwd});
t.deepEqual(await getNote(commits[0].hash, {cwd}), {});
});
test('Throw error if a commit note in invalid', 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'], {cwd});
await gitAddNote('non-json note', commits[0].hash, {cwd});
await t.throwsAsync(getNote(commits[0].hash, {cwd}));
});
test('Add a commit note', 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'], {cwd});
await addNote({note: 'note'}, commits[0].hash, {cwd});
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Overwrite a commit note', 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'], {cwd});
await addNote({note: 'note'}, commits[0].hash, {cwd});
await addNote({note: 'note2'}, commits[0].hash, {cwd});
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note2"}');
});
test('Unshallow and fetch repository with notes', async t => {
// Create a git repository, set the current working directory at the root of the repo
let {cwd, repositoryUrl} = await gitRepo();
// Add commits to the master branch
const commits = await gitCommits(['First', 'Second'], {cwd});
await gitAddNote(JSON.stringify({note: 'note'}), commits[0].hash, {cwd});
// Create a shallow clone with only 1 commit
cwd = await gitShallowClone(repositoryUrl);
// Verify the shallow clone doesn't contains the note
await t.throwsAsync(gitGetNote(commits[0].hash, {cwd}));
await fetch(repositoryUrl, 'master', {cwd});
await fetchNotes(repositoryUrl, {cwd});
// Verify the shallow clone contains the note
t.is(await gitGetNote(commits[0].hash, {cwd}), '{"note":"note"}');
});
test('Fetch all notes on a detached head repository', async t => {
let {cwd, repositoryUrl} = await gitRepo();
await gitCommits(['First'], {cwd});
const [commit] = await gitCommits(['Second'], {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitAddNote(JSON.stringify({note: 'note'}), commit.hash, {cwd});
cwd = await gitDetachedHead(repositoryUrl, commit.hash);
await fetch(repositoryUrl, 'master', {cwd});
await fetchNotes(repositoryUrl, {cwd});
t.is(await gitGetNote(commit.hash, {cwd}), '{"note":"note"}');
});
+27 -3
View File
@@ -4,6 +4,7 @@ import fileUrl from 'file-url';
import pEachSeries from 'p-each-series';
import gitLogParser from 'git-log-parser';
import getStream from 'get-stream';
import {GIT_NOTE_REF} from '../../lib/definitions/constants';
/**
* Commit message informations.
@@ -86,9 +87,11 @@ export async function gitCommits(messages, execaOpts) {
*/
export async function gitGetCommits(from, execaOpts) {
Object.assign(gitLogParser.fields, {hash: 'H', message: 'B', gitTags: 'd', committerDate: {key: 'ci', type: Date}});
return (await getStream.array(
gitLogParser.parse({_: `${from ? from + '..' : ''}HEAD`}, {...execaOpts, env: {...process.env, ...execaOpts.env}})
)).map(commit => {
return (
await getStream.array(
gitLogParser.parse({_: `${from ? from + '..' : ''}HEAD`}, {...execaOpts, env: {...process.env, ...execaOpts.env}})
)
).map(commit => {
commit.message = commit.message.trim();
commit.gitTags = commit.gitTags.trim();
return commit;
@@ -256,3 +259,24 @@ export async function mergeFf(ref, execaOpts) {
export async function rebase(ref, execaOpts) {
await execa('git', ['rebase', ref], execaOpts);
}
/**
* Add a note to a Git reference.
*
* @param {String} note The note to add.
* @param {String} ref The ref to add the note to.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitAddNote(note, ref, execaOpts) {
await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'add', '-m', note, ref], execaOpts);
}
/**
* Get the note associated with a Git reference.
*
* @param {String} ref The ref to get the note from.
* @param {Object} [execaOpts] Options to pass to `execa`.
*/
export async function gitGetNote(ref, execaOpts) {
return (await execa('git', ['notes', '--ref', GIT_NOTE_REF, 'show', ref], execaOpts)).stdout;
}
+191 -84
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import {escapeRegExp, isString, sortBy} from 'lodash';
import {escapeRegExp, isString, sortBy, omit} from 'lodash';
import proxyquire from 'proxyquire';
import {spy, stub} from 'sinon';
import {WritableStreamBuffer} from 'stream-buffers';
@@ -19,6 +19,8 @@ import {
merge,
mergeFf,
rebase,
gitAddNote,
gitGetNote,
} from './helpers/git-utils';
const requireNoCache = proxyquire.noPreserveCache();
@@ -45,7 +47,8 @@ test('Plugins are called with expected values', async t => {
// Add commits to the master branch
let commits = await gitCommits(['First'], {cwd});
// Create the tag corresponding to version 1.0.0
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.0.0', {cwd});
commits = (await gitCommits(['Second'], {cwd})).concat(commits);
await gitCheckout('next', true, {cwd});
await gitPush(repositoryUrl, 'next', {cwd});
@@ -55,9 +58,9 @@ test('Plugins are called with expected values', async t => {
const lastRelease = {
version: '1.0.0',
gitHead: commits[commits.length - 1].hash,
gitTag: 'v1.0.0@next',
gitTag: 'v1.0.0',
name: 'v1.0.0',
channel: 'next',
channels: ['next'],
};
const nextRelease = {
name: 'v1.1.0',
@@ -65,7 +68,7 @@ test('Plugins are called with expected values', async t => {
version: '1.1.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v1.1.0',
channel: undefined,
channel: null,
};
const notes1 = 'Release notes 1';
const notes2 = 'Release notes 2';
@@ -96,16 +99,18 @@ test('Plugins are called with expected values', async t => {
name: 'master',
range: '>=1.0.0 <2.0.0',
accept: ['patch', 'minor'],
tags: [{channel: 'next', gitTag: 'v1.0.0@next', version: '1.0.0'}],
tags: [{channels: ['next'], gitTag: 'v1.0.0', version: '1.0.0'}],
type: 'release',
main: true,
},
{
channel: 'next',
name: 'next',
range: '>=2.0.0',
accept: ['patch', 'minor', 'major'],
tags: [{channel: 'next', gitTag: 'v1.0.0@next', version: '1.0.0'}],
tags: [{channels: ['next'], gitTag: 'v1.0.0', version: '1.0.0'}],
type: 'release',
main: false,
},
];
const branch = branches[0];
@@ -124,11 +129,11 @@ test('Plugins are called with expected values', async t => {
const releases = [
{
...lastRelease,
...omit(lastRelease, 'channels'),
...release1,
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
pluginName: '[Function: functionStub]',
@@ -176,10 +181,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes1.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes1.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes1.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
});
@@ -193,10 +198,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes2.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes2.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes2.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: notes1,
@@ -211,10 +216,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(generateNotes3.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(generateNotes3.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(generateNotes3.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: `${notes1}\n\n${notes2}`,
@@ -222,7 +227,7 @@ test('Plugins are called with expected values', async t => {
branch.tags.push({
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
gitHead: commits[commits.length - 1].hash,
});
@@ -236,10 +241,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(addChannel.args[0][1].lastRelease, {});
t.deepEqual(addChannel.args[0][1].currentRelease, {...lastRelease, type: 'major'});
t.deepEqual(addChannel.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
@@ -330,10 +335,10 @@ test('Plugins are called with expected values', async t => {
t.deepEqual(success.args[0][1].commits[0].hash, commits[1].hash);
t.deepEqual(success.args[0][1].commits[0].message, commits[1].message);
t.deepEqual(success.args[0][1].nextRelease, {
...lastRelease,
...omit(lastRelease, 'channels'),
type: 'major',
version: '1.0.0',
channel: undefined,
channel: null,
gitTag: 'v1.0.0',
name: 'v1.0.0',
notes: `${notes1}\n\n${notes2}\n\n${notes3}`,
@@ -433,7 +438,7 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
version: '2.0.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v2.0.0',
channel: undefined,
channel: null,
};
const notes = 'Release notes';
@@ -492,15 +497,16 @@ test('Use new gitHead, and recreate release notes if a prepare plugin create a c
test('Make a new release when a commit is forward-ported to an upper branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial release'], {cwd});
await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@1.0.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.0.x']}), 'v1.0.0', {cwd});
await gitCheckout('1.0.x', true, {cwd});
commits.push(...(await gitCommits(['fix: fix on maintenance version 1.0.x'], {cwd})));
await gitTagVersion('v1.0.1@1.0.x', undefined, {cwd});
await gitCommits(['fix: fix on maintenance version 1.0.x'], {cwd});
await gitTagVersion('v1.0.1', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['1.0.x']}), 'v1.0.1', {cwd});
await gitPush('origin', '1.0.x', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['feat: new feature on master'], {cwd})));
await gitCommits(['feat: new feature on master'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await merge('1.0.x', {cwd});
await gitPush('origin', 'master', {cwd});
@@ -566,7 +572,8 @@ test('Publish a pre-release version', async t => {
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.1');
t.is(releases[0].gitTag, 'v1.1.0-beta.1@beta');
t.is(releases[0].gitTag, 'v1.1.0-beta.1');
t.is(await gitGetNote('v1.1.0-beta.1', {cwd}), '{"channels":["beta"]}');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
@@ -578,19 +585,133 @@ test('Publish a pre-release version', async t => {
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.2');
t.is(releases[0].gitTag, 'v1.1.0-beta.2@beta');
t.is(releases[0].gitTag, 'v1.1.0-beta.2');
t.is(await gitGetNote('v1.1.0-beta.2', {cwd}), '{"channels":["beta"]}');
});
test('Publish releases from different branch on the same channel', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('next-major', true, {cwd});
await gitPush(repositoryUrl, 'next-major', {cwd});
await gitCheckout('next', true, {cwd});
await gitCommits(['feat: a feature'], {cwd});
await gitPush(repositoryUrl, 'next', {cwd});
const config = {
branches: ['master', {name: 'next', channel: false}, {name: 'next-major', channel: false}],
repositoryUrl,
};
const addChannel = stub().resolves({});
const options = {
...config,
verifyConditions: stub().resolves(),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(''),
addChannel,
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
};
let semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'next', isPr: false}),
});
let {releases} = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0');
t.is(releases[0].gitTag, 'v1.1.0');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
cwd,
env: {},
stdout: {write: () => {}},
stderr: {write: () => {}},
}));
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.1');
t.is(releases[0].gitTag, 'v1.1.1');
await gitCheckout('master', false, {cwd});
await merge('next', {cwd});
await gitPush('origin', 'master', {cwd});
semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
t.falsy(await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}}));
t.is(addChannel.callCount, 0);
});
test('Publish pre-releases the same channel as regular releases', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('beta', true, {cwd});
await gitCommits(['feat: a feature'], {cwd});
await gitPush(repositoryUrl, 'beta', {cwd});
const config = {
branches: ['master', {name: 'beta', channel: false, prerelease: true}],
repositoryUrl,
};
const options = {
...config,
verifyConditions: stub().resolves(),
verifyRelease: stub().resolves(),
generateNotes: stub().resolves(''),
addChannel: false,
prepare: stub().resolves(),
publish: stub().resolves(),
success: stub().resolves(),
fail: stub().resolves(),
};
const semanticRelease = requireNoCache('..', {
'./lib/get-logger': () => t.context.logger,
'env-ci': () => ({isCi: true, branch: 'beta', isPr: false}),
});
let {releases} = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.1');
t.is(releases[0].gitTag, 'v1.1.0-beta.1');
await gitCommits(['fix: a fix'], {cwd});
({releases} = await semanticRelease(options, {
cwd,
env: {},
stdout: {write: () => {}},
stderr: {write: () => {}},
}));
t.is(releases.length, 1);
t.is(releases[0].version, '1.1.0-beta.2');
t.is(releases[0].gitTag, 'v1.1.0-beta.2');
});
test('Do not add pre-releases to a different channel', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial release'], {cwd});
await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@beta', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'beta']}), 'v1.0.0', {cwd});
await gitCheckout('beta', true, {cwd});
commits.push(...(await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd})));
await gitTagVersion('v2.0.0-beta.1@beta', undefined, {cwd});
commits.push(...(await gitCommits(['fix: a fix'], {cwd})));
await gitTagVersion('v2.0.0-beta.2@beta', undefined, {cwd});
await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd});
await gitTagVersion('v2.0.0-beta.1', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['beta']}), 'v2.0.0-beta.1', {cwd});
await gitCommits(['fix: a fix'], {cwd});
await gitTagVersion('v2.0.0-beta.2', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['beta']}), 'v2.0.0-beta.2', {cwd});
await gitPush('origin', 'beta', {cwd});
await gitCheckout('master', false, {cwd});
await merge('beta', {cwd});
@@ -635,14 +756,18 @@ async function addChannelMacro(t, mergeFunction) {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial release'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['feat: breaking change/n/nBREAKING CHANGE: break something'], {cwd})));
await gitTagVersion('v2.0.0@next', undefined, {cwd});
await gitTagVersion('v2.0.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.0.0', {cwd});
commits.push(...(await gitCommits(['fix: a fix'], {cwd})));
await gitTagVersion('v2.0.1@next', undefined, {cwd});
await gitTagVersion('v2.0.1', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.0.1', {cwd});
commits.push(...(await gitCommits(['feat: a feature'], {cwd})));
await gitTagVersion('v2.1.0@next', undefined, {cwd});
await gitTagVersion('v2.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v2.1.0', {cwd});
await gitPush('origin', 'next', {cwd});
await gitCheckout('master', false, {cwd});
// Merge all commits but last one from next to master
@@ -671,19 +796,11 @@ async function addChannelMacro(t, mergeFunction) {
publish,
success,
};
const nextRelease1 = {
name: 'v2.0.0',
type: 'major',
version: '2.0.0',
channel: undefined,
gitTag: 'v2.0.0',
gitHead: commits[1].hash,
};
const nextRelease2 = {
name: 'v2.0.1',
type: 'patch',
version: '2.0.1',
channel: undefined,
channel: null,
gitTag: 'v2.0.1',
gitHead: commits[2].hash,
};
@@ -695,15 +812,11 @@ async function addChannelMacro(t, mergeFunction) {
const result = await semanticRelease(options, {cwd, env: {}, stdout: {write: () => {}}, stderr: {write: () => {}}});
t.deepEqual(result.releases, [
{...nextRelease1, ...release1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease2, ...release1, notes, pluginName: '[Function: functionStub]'},
{...nextRelease2, notes, pluginName: '[Function: functionStub]'},
]);
// Verify the tag has been created on the local and remote repo and reference
t.is(await gitTagHead(nextRelease1.gitTag, {cwd}), nextRelease1.gitHead);
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease1.gitTag, {cwd}), nextRelease1.gitHead);
t.is(await gitTagHead(nextRelease2.gitTag, {cwd}), nextRelease2.gitHead);
t.is(await gitRemoteTagHead(repositoryUrl, nextRelease2.gitTag, {cwd}), nextRelease2.gitHead);
}
@@ -731,7 +844,7 @@ test('Call all "success" plugins even if one errors out', async t => {
version: '2.0.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v2.0.0',
channel: undefined,
channel: null,
};
const notes = 'Release notes';
const verifyConditions1 = stub().resolves();
@@ -866,9 +979,11 @@ test('Dry-run skips addChannel, prepare, publish and success', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitTagVersion('v1.1.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('next', true, {cwd});
await gitPush('origin', 'next', {cwd});
@@ -1148,8 +1263,10 @@ test('Accept "undefined" value returned by "generateNotes" and "false" by "publi
const {cwd, repositoryUrl} = await gitRepo(true);
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@next', undefined, {cwd});
await gitTagVersion('v1.1.0@next', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, 'next']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
await gitPush(repositoryUrl, 'master', {cwd});
await gitCheckout('next', true, {cwd});
await gitPush('origin', 'next', {cwd});
@@ -1161,7 +1278,7 @@ test('Accept "undefined" value returned by "generateNotes" and "false" by "publi
version: '1.2.0',
gitHead: await getGitHead({cwd}),
gitTag: 'v1.2.0',
channel: undefined,
channel: null,
};
const analyzeCommits = stub().resolves(nextRelease.type);
const verifyRelease = stub().resolves();
@@ -1234,16 +1351,16 @@ test('Returns false if triggered by a PR', async t => {
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current maintenance branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial commit'], {cwd});
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@1.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.x']}), 'v1.0.0', {cwd});
await gitCheckout('1.x', true, {cwd});
await gitPush('origin', '1.x', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['feat: new feature on master'], {cwd})));
await gitCommits(['feat: new feature on master'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitCheckout('1.x', false, {cwd});
commits.push(...(await gitCommits(['feat: feature on maintenance version 1.x'], {cwd})));
await gitCommits(['feat: feature on maintenance version 1.x'], {cwd});
await gitPush('origin', 'master', {cwd});
const verifyConditions = stub().resolves();
@@ -1285,16 +1402,17 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current release branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['feat: initial commit'], {cwd});
await gitCommits(['feat: initial commit'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitCheckout('next', true, {cwd});
commits.push(...(await gitCommits(['feat: new feature on next'], {cwd})));
await gitTagVersion('v1.1.0@next', undefined, {cwd});
await gitCommits(['feat: new feature on next'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: ['next']}), 'v1.1.0', {cwd});
await gitPush('origin', 'next', {cwd});
await gitCheckout('next-major', true, {cwd});
await gitPush('origin', 'next-major', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['feat: new feature on master', 'fix: new fix on master'], {cwd})));
await gitCommits(['feat: new feature on master', 'fix: new fix on master'], {cwd});
await gitPush('origin', 'master', {cwd});
const verifyConditions = stub().resolves();
@@ -1336,18 +1454,18 @@ test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the curren
test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a maintenance branch', async t => {
const {cwd, repositoryUrl} = await gitRepo(true);
const commits = await gitCommits(['First'], {cwd});
await gitCommits(['First'], {cwd});
await gitTagVersion('v1.0.0', undefined, {cwd});
await gitTagVersion('v1.0.0@1.1.x', undefined, {cwd});
commits.push(...(await gitCommits(['Second'], {cwd})));
await gitAddNote(JSON.stringify({channels: [null, '1.1.x']}), 'v1.0.0', {cwd});
await gitCommits(['Second'], {cwd});
await gitTagVersion('v1.1.0', undefined, {cwd});
await gitTagVersion('v1.1.0@1.1.x', undefined, {cwd});
await gitAddNote(JSON.stringify({channels: [null, '1.1.x']}), 'v1.1.0', {cwd});
await gitCheckout('1.1.x', 'master', {cwd});
await gitPush('origin', '1.1.x', {cwd});
await gitCheckout('master', false, {cwd});
commits.push(...(await gitCommits(['Third'], {cwd})));
await gitCommits(['Third'], {cwd});
await gitTagVersion('v1.1.1', undefined, {cwd});
commits.push(...(await gitCommits(['Fourth'], {cwd})));
await gitCommits(['Fourth'], {cwd});
await gitTagVersion('v1.2.0', undefined, {cwd});
await gitPush('origin', 'master', {cwd});
await gitCheckout('1.1.x', false, {cwd});
@@ -1379,15 +1497,6 @@ test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a ma
fail,
};
const nextRelease = {
type: 'patch',
version: '1.1.1',
channel: '1.1.x',
gitTag: 'v1.1.1@1.1.x',
name: 'v1.1.1',
gitHead: commits[2].hash,
};
const semanticRelease = proxyquire('..', {
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: '1.1.x', isPr: false}),
@@ -1398,13 +1507,11 @@ test('Throws "EINVALIDMAINTENANCEMERGE" if merge an out of range release in a ma
)),
];
t.is(addChannel.callCount, 1);
t.deepEqual(addChannel.args[0][1].nextRelease, {...nextRelease, notes});
t.is(addChannel.callCount, 0);
t.is(publish.callCount, 0);
t.is(success.callCount, 1);
t.deepEqual(success.args[0][1].releases, [{...nextRelease, notes, pluginName: '[Function: functionStub]'}]);
t.is(success.callCount, 0);
t.is(fail.callCount, 1);
t.deepEqual(fail.args[0][1].errors, errors);
+10 -10
View File
@@ -16,6 +16,7 @@ import {
gitPush,
gitCheckout,
merge,
gitGetNote,
} from './helpers/git-utils';
import {npmView} from './helpers/npm-utils';
import gitbox from './helpers/gitbox';
@@ -220,7 +221,7 @@ test('Release patch, minor and major versions', async t => {
createReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases`,
{
body: {tag_name: `v${version}@next`, 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}`}}
@@ -246,8 +247,9 @@ test('Release patch, minor and major versions', async t => {
} = await npmView(packageName, testEnv));
head = await gitHead({cwd});
t.is(releasedVersion, version);
t.is(await gitTagHead(`v${version}@next`, {cwd}), head);
t.is(await gitRemoteTagHead(authUrl, `v${version}@next`, {cwd}), head);
t.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next"]}');
t.is(await gitTagHead(`v${version}`, {cwd}), head);
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), head);
t.log(`+ released ${releasedVersion} on @next`);
await mockServer.verify(verifyMock);
@@ -262,14 +264,14 @@ test('Release patch, minor and major versions', async t => {
{body: {permissions: {push: true}}, method: 'GET'}
);
const getReleaseMock = await mockServer.mock(
`/repos/${owner}/${packageName}/releases/tags/v2.0.0@next`,
`/repos/${owner}/${packageName}/releases/tags/v2.0.0`,
{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},
body: {name: `v${version}`, prerelease: false},
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
},
{body: {html_url: `release-url/${version}`}, method: 'PATCH'}
@@ -292,11 +294,9 @@ test('Release patch, minor and major versions', async t => {
'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.is(await gitGetNote(`v${version}`, {cwd}), '{"channels":["next",null]}');
t.is(await gitTagHead(`v${version}`, {cwd}), await gitTagHead(`v${version}`, {cwd}));
t.is(await gitRemoteTagHead(authUrl, `v${version}`, {cwd}), await gitRemoteTagHead(authUrl, `v${version}`, {cwd}));
t.log(`+ added ${releasedVersion}`);
await mockServer.verify(verifyMock);
+18 -3
View File
@@ -47,7 +47,12 @@ test('Execute each function in series passing the "lastResult" and "result" to "
const result = await pipeline([step1, step2, step3, step4], {settleAll: false, getNextInput})(5);
t.deepEqual(result, [1, 2, 3, 4]);
t.deepEqual(getNextInput.args, [[5, 1], [5, 2], [5, 3], [5, 4]]);
t.deepEqual(getNextInput.args, [
[5, 1],
[5, 2],
[5, 3],
[5, 4],
]);
});
test('Execute each function in series calling "transform" to modify the results', async t => {
@@ -61,7 +66,12 @@ test('Execute each function in series calling "transform" to modify the results'
const result = await pipeline([step1, step2, step3, step4], {getNextInput, transform})(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
t.deepEqual(getNextInput.args, [
[5, 1 + 1],
[5, 2 + 1],
[5, 3 + 1],
[5, 4 + 1],
]);
});
test('Execute each function in series calling "transform" to modify the results with "settleAll"', async t => {
@@ -75,7 +85,12 @@ test('Execute each function in series calling "transform" to modify the results
const result = await pipeline([step1, step2, step3, step4], {settleAll: true, getNextInput, transform})(5);
t.deepEqual(result, [1 + 1, 2 + 1, 3 + 1, 4 + 1]);
t.deepEqual(getNextInput.args, [[5, 1 + 1], [5, 2 + 1], [5, 3 + 1], [5, 4 + 1]]);
t.deepEqual(getNextInput.args, [
[5, 1 + 1],
[5, 2 + 1],
[5, 3 + 1],
[5, 4 + 1],
]);
});
test('Stop execution and throw error if a step rejects', async t => {
+10 -4
View File
@@ -14,6 +14,7 @@ import {
getFirstVersion,
getRange,
makeTag,
isSameChannel,
} from '../lib/utils';
test('extractErrors', t => {
@@ -173,8 +174,13 @@ test('getRange', t => {
test('makeTag', t => {
t.is(makeTag(`v\${version}`, '1.0.0'), 'v1.0.0');
t.is(makeTag(`v\${version}`, '1.0.0', false), 'v1.0.0');
t.is(makeTag(`v\${version}`, '1.0.0', null), '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');
});
test('isSameChannel', t => {
t.true(isSameChannel('next', 'next'));
t.true(isSameChannel(null, undefined));
t.true(isSameChannel(false, undefined));
t.true(isSameChannel('', false));
t.false(isSameChannel('next', false));
});