diff --git a/docs/usage/workflow-configuration.md b/docs/usage/workflow-configuration.md index aee88c61..c2d27265 100644 --- a/docs/usage/workflow-configuration.md +++ b/docs/usage/workflow-configuration.md @@ -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. diff --git a/lib/get-last-release.js b/lib/get-last-release.js index 58d2451e..6c4bf50c 100644 --- a/lib/get-last-release.js +++ b/lib/get-last-release.js @@ -1,6 +1,6 @@ const {isUndefined} = require('lodash'); const semver = require('semver'); -const {makeTag} = require('./utils'); +const {makeTag, isSameChannel} = require('./utils'); /** * Last release. @@ -28,7 +28,10 @@ const {makeTag} = require('./utils'); */ 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 => + (branch.type === 'prerelease' && isSameChannel(branch.channel, tag.channel)) || !semver.prerelease(tag.version) + ) .filter(tag => isUndefined(before) || semver.lt(tag.version, before)) .sort((a, b) => semver.rcompare(a.version, b.version)); diff --git a/lib/get-next-version.js b/lib/get-next-version.js index 19b9e299..825f77d6 100644 --- a/lib/get-next-version.js +++ b/lib/get-next-version.js @@ -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,7 @@ 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) && isSameChannel(lastRelease.channel, channel) ? semver.inc(lastRelease.version, 'prerelease') : `${semver.inc(`${major}.${minor}.${patch}`, type)}-${branch.prerelease}.${FIRSTPRERELEASE}` : semver.inc(lastRelease.version, type); diff --git a/lib/utils.js b/lib/utils.js index 9497026b..ef8463f7 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -71,6 +71,10 @@ function makeTag(tagFormat, version, channel) { return template(tagFormat)({version: `${version}${channel ? `@${channel}` : ''}`}); } +function isSameChannel(channel, otherChannel) { + return channel === otherChannel || (!channel && !otherChannel); +} + module.exports = { extractErrors, hideSensitiveValues, @@ -86,4 +90,5 @@ module.exports = { getFirstVersion, getRange, makeTag, + isSameChannel, }; diff --git a/test/index.test.js b/test/index.test.js index 7fd441fb..d7f0b54a 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -581,6 +581,117 @@ test('Publish a pre-release version', async t => { t.is(releases[0].gitTag, 'v1.1.0-beta.2@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}); diff --git a/test/utils.test.js b/test/utils.test.js index 08b4ebd9..21308838 100644 --- a/test/utils.test.js +++ b/test/utils.test.js @@ -14,6 +14,7 @@ import { getFirstVersion, getRange, makeTag, + isSameChannel, } from '../lib/utils'; test('extractErrors', t => { @@ -178,3 +179,12 @@ test('makeTag', t => { 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)); +});