feat: Refactor CLI to run with one command, improve logs, modularize, add tests
- Run with one command and do not rely on error exit codes to stop the process when a release is not necessary - Break `index.js` in smaller modules in order to improve testability and simplify the code - Add several missing unit and integration tests to reach 100% coverage - Integration tests now test end to end, including publishing to Github (with http://www.mock-server.com on Docker) - Use `tj/commander.js` to print an help message, verify and parse CLI arguments - Semantic-release can now be called via Javascript API: `require('semantic-release')(options)` - Remove npmlog dependency and add more log messages - Logger is now passed to plugins - Add debug logs with `visionmedia/debug`. `debug` is enabled for both semantic-release and plugins with `--debug` - Use `kevva/npm-conf` in place of the deprecated `npm/npmconf` - Pass lastRelease, nextRelease and commits to generate-notes plugin - In dry-run mode, print the release note instead of publishing it to Github as draft, and skip the CI verifications - The dry-run mode does not require npm and Github TOKEN to be set anymore and can be run locally BREAKING CHANGE: Semantic-Release must now be executed with `semantic-release` instead of `semantic-release pre && npm publish && semantic-release post`. BREAKING CHANGE: The `semantic-release` command now returns with exit code 0 on expected exception (no release has to be done, running on a PR, gitHead not found, other CI job failed etc...). It only returns with 1 when there is an unexpected error (code error in a plugin, plugin not found, git command cannot be run etc..). BREAKING CHANGE: Calling the `semantic-release` command with unexpected argument(s) now exit with 1 and print an help message. BREAKING CHANGE: Semantic-Release does not rely on `npmlog` anymore and the log level cannot be configured. Debug logs can be activated with CLI option `--debug` or with environment variable `DEBUG=semantic-release:*` BREAKING CHANGE: The CLI options `--debug` doesn't enable the dry-run mode anymore but activate the debugs. The dry run mode is now set with the CLI command `--dry-run` or `-d`.
This commit is contained in:
committed by
Gregor Martynus
parent
1bd095d26c
commit
e2a8a5cd32
+137
-103
@@ -1,4 +1,7 @@
|
||||
import test from 'ava';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import {
|
||||
gitRepo,
|
||||
gitCommits,
|
||||
@@ -9,20 +12,16 @@ import {
|
||||
gitLog,
|
||||
gitDetachedHead,
|
||||
} from './helpers/git-utils';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
|
||||
// Stub to capture the log messages
|
||||
const errorLog = stub();
|
||||
// Module to test
|
||||
const getCommits = proxyquire('../src/lib/get-commits', {npmlog: {error: errorLog}});
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current working diretory
|
||||
t.context.cwd = process.cwd();
|
||||
// Reset the stub call history
|
||||
errorLog.resetHistory();
|
||||
// Stub the logger functions
|
||||
t.context.log = stub();
|
||||
t.context.error = stub();
|
||||
t.context.getCommits = proxyquire('../src/lib/get-commits', {
|
||||
'./logger': {log: t.context.log, error: t.context.error},
|
||||
});
|
||||
});
|
||||
|
||||
test.afterEach.always(t => {
|
||||
@@ -37,14 +36,18 @@ test.serial('Get all commits when there is no last release', async t => {
|
||||
const commits = await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const result = await getCommits({lastRelease: {}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.falsy(result.lastRelease.gitHead);
|
||||
t.falsy(result.lastRelease.version);
|
||||
});
|
||||
|
||||
test.serial('Get all commits when there is no last release, including the ones not in the shallow clone', async t => {
|
||||
@@ -59,14 +62,18 @@ test.serial('Get all commits when there is no last release, including the ones n
|
||||
t.is((await gitLog()).length, 1);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const result = await getCommits({lastRelease: {}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.falsy(result.lastRelease.gitHead);
|
||||
t.falsy(result.lastRelease.version);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from lastRelease)', async t => {
|
||||
@@ -76,17 +83,18 @@ test.serial('Get all commits since gitHead (from lastRelease)', async t => {
|
||||
const commits = await gitCommits(['First', 'Second', 'Third']);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First'
|
||||
const result = await getCommits({
|
||||
lastRelease: {gitHead: commits[commits.length - 1].hash},
|
||||
options: {branch: 'master'},
|
||||
});
|
||||
const result = await t.context.getCommits({gitHead: commits[commits.length - 1].hash}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead, commits[commits.length - 1].hash);
|
||||
t.falsy(result.lastRelease.version);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from lastRelease) on a detached head repo', async t => {
|
||||
@@ -98,15 +106,16 @@ test.serial('Get all commits since gitHead (from lastRelease) on a detached head
|
||||
await gitDetachedHead(repo, commits[1].hash);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First'
|
||||
const result = await getCommits({
|
||||
lastRelease: {gitHead: commits[commits.length - 1].hash},
|
||||
options: {branch: 'master'},
|
||||
});
|
||||
const result = await t.context.getCommits({gitHead: commits[commits.length - 1].hash}, 'master');
|
||||
|
||||
// Verify the module retrieved only the commit 'feat: Second' (included in the detached and after 'fix: First')
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[0].message, commits[1].message);
|
||||
t.is(result.commits.length, 1);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[0].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead, commits[commits.length - 1].hash);
|
||||
t.falsy(result.lastRelease.version);
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from tag) ', async t => {
|
||||
@@ -120,14 +129,18 @@ test.serial('Get all commits since gitHead (from tag) ', async t => {
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
|
||||
t.is(result.lastRelease.version, '1.0.0');
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from tag) on a detached head repo', async t => {
|
||||
@@ -143,12 +156,16 @@ test.serial('Get all commits since gitHead (from tag) on a detached head repo',
|
||||
await gitDetachedHead(repo, commits[1].hash);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag 1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
|
||||
|
||||
// Verify the module retrieved only the commit 'feat: Second' (included in the detached and after 'fix: First')
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[0].message, commits[1].message);
|
||||
t.is(result.commits.length, 1);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[0].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
|
||||
t.is(result.lastRelease.version, '1.0.0');
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead (from tag formatted like v<version>) ', async t => {
|
||||
@@ -162,14 +179,18 @@ test.serial('Get all commits since gitHead (from tag formatted like v<version>)
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
|
||||
t.is(result.lastRelease.version, '1.0.0');
|
||||
});
|
||||
|
||||
test.serial('Get commits when last release gitHead is missing but a tag match the version', async t => {
|
||||
@@ -183,14 +204,18 @@ test.serial('Get commits when last release gitHead is missing but a tag match th
|
||||
commits = (await gitCommits(['Second', 'Third'])).concat(commits);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0', gitHead: 'missing'}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({version: '1.0.0', gitHead: 'missing'}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
|
||||
t.is(result.lastRelease.version, '1.0.0');
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead, when gitHead are mising from the shallow clone', async t => {
|
||||
@@ -202,17 +227,18 @@ test.serial('Get all commits since gitHead, when gitHead are mising from the sha
|
||||
await gitShallowClone(repo);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First'
|
||||
const result = await getCommits({
|
||||
lastRelease: {version: '1.0.0', gitHead: commits[commits.length - 1].hash},
|
||||
options: {branch: 'master'},
|
||||
});
|
||||
const result = await t.context.getCommits({version: '1.0.0', gitHead: commits[commits.length - 1].hash}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
|
||||
t.is(result.lastRelease.version, '1.0.0');
|
||||
});
|
||||
|
||||
test.serial('Get all commits since gitHead from tag, when tags are mising from the shallow clone', async t => {
|
||||
@@ -231,14 +257,18 @@ test.serial('Get all commits since gitHead from tag, when tags are mising from t
|
||||
t.is((await gitTags()).length, 0);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'First' (associated with tag v1.0.0)
|
||||
const result = await getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({version: '1.0.0'}, 'master');
|
||||
|
||||
// Verify the commits created and retrieved by the module are identical
|
||||
t.is(result.length, 2);
|
||||
t.is(result[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result[0].message, commits[0].message);
|
||||
t.is(result[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result[1].message, commits[1].message);
|
||||
t.is(result.commits.length, 2);
|
||||
t.is(result.commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.is(result.commits[0].message, commits[0].message);
|
||||
t.is(result.commits[1].hash.substring(0, 7), commits[1].hash);
|
||||
t.is(result.commits[1].message, commits[1].message);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead.substring(0, 7), commits[commits.length - 1].hash);
|
||||
t.is(result.lastRelease.version, '1.0.0');
|
||||
});
|
||||
|
||||
test.serial('Return empty array if lastRelease.gitHead is the last commit', async t => {
|
||||
@@ -248,10 +278,14 @@ test.serial('Return empty array if lastRelease.gitHead is the last commit', asyn
|
||||
const commits = await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'Second' (therefore none)
|
||||
const result = await getCommits({lastRelease: {gitHead: commits[0].hash}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({gitHead: commits[0].hash, version: '1.0.0'}, 'master');
|
||||
|
||||
// Verify no commit is retrieved
|
||||
t.deepEqual(result, []);
|
||||
t.deepEqual(result.commits, []);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.is(result.lastRelease.gitHead.substring(0, 7), commits[0].hash);
|
||||
t.is(result.lastRelease.version, '1.0.0');
|
||||
});
|
||||
|
||||
test.serial('Return empty array if there is no commits', async t => {
|
||||
@@ -259,10 +293,14 @@ test.serial('Return empty array if there is no commits', async t => {
|
||||
await gitRepo();
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const result = await getCommits({lastRelease: {}, options: {branch: 'master'}});
|
||||
const result = await t.context.getCommits({}, 'master');
|
||||
|
||||
// Verify no commit is retrieved
|
||||
t.deepEqual(result, []);
|
||||
t.deepEqual(result.commits, []);
|
||||
// Verify the last release is returned and updated
|
||||
t.truthy(result.lastRelease);
|
||||
t.falsy(result.lastRelease.gitHead);
|
||||
t.falsy(result.lastRelease.version);
|
||||
});
|
||||
|
||||
test.serial('Throws ENOGITHEAD error if the gitHead of the last release cannot be found', async t => {
|
||||
@@ -272,15 +310,15 @@ test.serial('Throws ENOGITHEAD error if the gitHead of the last release cannot b
|
||||
await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}}));
|
||||
const error = await t.throws(t.context.getCommits({version: '1.0.0'}, 'master'));
|
||||
|
||||
// Verify error code and message
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOGITHEAD');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
// Verify the log function has been called with a message explaining the error
|
||||
t.regex(
|
||||
errorLog.firstCall.args[1],
|
||||
/The commit the last release of this package was derived from cannot be determined from the release metadata not from the repository tags/
|
||||
t.context.error.firstCall.args[0],
|
||||
/The commit the last release of this package was derived from cannot be determined from the release metadata nor from the repository tags/
|
||||
);
|
||||
});
|
||||
|
||||
@@ -291,15 +329,15 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in history', async t =
|
||||
await gitCommits(['First', 'Second']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(getCommits({lastRelease: {gitHead: 'notinhistory'}, options: {branch: 'master'}}));
|
||||
const error = await t.throws(t.context.getCommits({gitHead: 'notinhistory'}, 'master'));
|
||||
|
||||
// Verify error code and message
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
// Verify the log function has been called with a message mentionning the branch
|
||||
t.regex(errorLog.firstCall.args[1], /history of the "master" branch/);
|
||||
t.regex(t.context.error.firstCall.args[0], /history of the "master" branch/);
|
||||
// Verify the log function has been called with a message mentionning the missing gitHead
|
||||
t.regex(errorLog.firstCall.args[1], /restoring the commit "notinhistory"/);
|
||||
t.regex(t.context.error.firstCall.args[0], /restoring the commit "notinhistory"/);
|
||||
});
|
||||
|
||||
test.serial('Throws ENOTINHISTORY error if gitHead is not in branch history but present in others', async t => {
|
||||
@@ -314,17 +352,15 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in branch history but
|
||||
await gitCheckout('master', false);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(
|
||||
getCommits({lastRelease: {version: '1.0.1', gitHead: commitsBranch[0].hash}, options: {branch: 'master'}})
|
||||
);
|
||||
const error = await t.throws(t.context.getCommits({version: '1.0.1', gitHead: commitsBranch[0].hash}, 'master'));
|
||||
|
||||
// Verify error code and message
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
// Verify the log function has been called with a message mentionning the branch
|
||||
t.regex(errorLog.firstCall.args[1], /history of the "master" branch/);
|
||||
t.regex(t.context.error.firstCall.args[0], /history of the "master" branch/);
|
||||
// Verify the log function has been called with a message mentionning the missing gitHead
|
||||
t.regex(errorLog.firstCall.args[1], new RegExp(`restoring the commit "${commitsBranch[0].hash}"`));
|
||||
t.regex(t.context.error.firstCall.args[0], new RegExp(`restoring the commit "${commitsBranch[0].hash}"`));
|
||||
});
|
||||
|
||||
test.serial('Throws ENOTINHISTORY error if gitHead is not in detached head but present in other branch', async t => {
|
||||
@@ -343,17 +379,15 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in detached head but p
|
||||
await gitDetachedHead(repo, commitsMaster[0].hash);
|
||||
|
||||
// Retrieve the commits with the commits module, since commit 'Second'
|
||||
const error = await t.throws(
|
||||
getCommits({lastRelease: {version: '1.0.1', gitHead: commitsBranch[0].hash}, options: {branch: 'master'}})
|
||||
);
|
||||
const error = await t.throws(t.context.getCommits({version: '1.0.1', gitHead: commitsBranch[0].hash}, 'master'));
|
||||
|
||||
// Verify error code and message
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
// Verify the log function has been called with a message mentionning the branch
|
||||
t.regex(errorLog.firstCall.args[1], /history of the "master" branch/);
|
||||
t.regex(t.context.error.firstCall.args[0], /history of the "master" branch/);
|
||||
// Verify the log function has been called with a message mentionning the missing gitHead
|
||||
t.regex(errorLog.firstCall.args[1], new RegExp(`restoring the commit "${commitsBranch[0].hash}"`));
|
||||
t.regex(t.context.error.firstCall.args[0], new RegExp(`restoring the commit "${commitsBranch[0].hash}"`));
|
||||
});
|
||||
|
||||
test.serial('Throws ENOTINHISTORY error when a tag is not in branch history but present in others', async t => {
|
||||
@@ -372,12 +406,12 @@ test.serial('Throws ENOTINHISTORY error when a tag is not in branch history but
|
||||
await gitCommits(['Forth']);
|
||||
|
||||
// Retrieve the commits with the commits module
|
||||
const error = await t.throws(getCommits({lastRelease: {version: '1.0.0'}, options: {branch: 'master'}}));
|
||||
// Verify error code and message
|
||||
const error = await t.throws(t.context.getCommits({version: '1.0.0'}, 'master'));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
// Verify the log function has been called with a message mentionning the branch
|
||||
t.regex(errorLog.firstCall.args[1], /history of the "master" branch/);
|
||||
t.regex(t.context.error.firstCall.args[0], /history of the "master" branch/);
|
||||
// Verify the log function has been called with a message mentionning the missing gitHead
|
||||
t.regex(errorLog.firstCall.args[1], new RegExp(`restoring the commit "${shaTag}"`));
|
||||
t.regex(t.context.error.firstCall.args[0], new RegExp(`restoring the commit "${shaTag}"`));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import url from 'url';
|
||||
import test from 'ava';
|
||||
import {writeJson, writeFile} from 'fs-extra';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import normalizeData from 'normalize-package-data';
|
||||
import {gitRepo} from './helpers/git-utils';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current process.env
|
||||
t.context.env = Object.assign({}, process.env);
|
||||
// Save the current working diretory
|
||||
t.context.cwd = process.cwd();
|
||||
t.context.plugins = stub().returns({});
|
||||
t.context.getConfig = proxyquire('../src/lib/get-config', {'./plugins': t.context.plugins});
|
||||
});
|
||||
|
||||
test.afterEach.always(t => {
|
||||
// Restore the current working directory
|
||||
process.chdir(t.context.cwd);
|
||||
// Restore process.env
|
||||
process.env = Object.assign({}, t.context.env);
|
||||
});
|
||||
|
||||
test.serial('Default values', async t => {
|
||||
const pkg = {name: 'package_name', release: {}};
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', pkg);
|
||||
process.env.GH_TOKEN = 'GH_TOKEN';
|
||||
|
||||
const result = await t.context.getConfig();
|
||||
|
||||
// Verify the normalized package is returned
|
||||
normalizeData(pkg);
|
||||
t.deepEqual(result.pkg, pkg);
|
||||
// Verify the default options are set
|
||||
t.is(result.options.branch, 'master');
|
||||
t.is(result.options.githubToken, process.env.GH_TOKEN);
|
||||
// Verify the default npm options are set
|
||||
t.is(result.npm.tag, 'latest');
|
||||
|
||||
// Use the environment variable npm_config_registry as the default so the test works on both npm and yarn
|
||||
t.is(result.npm.registry, url.format(url.parse(process.env.npm_config_registry || 'https://registry.npmjs.org/')));
|
||||
});
|
||||
|
||||
test.serial('Read package.json configuration', async t => {
|
||||
const release = {
|
||||
analyzeCommits: 'analyzeCommits',
|
||||
generateNotes: 'generateNotes',
|
||||
getLastRelease: {
|
||||
path: 'getLastRelease',
|
||||
param: 'getLastRelease_param',
|
||||
},
|
||||
branch: 'test_branch',
|
||||
};
|
||||
const pkg = {name: 'package_name', release};
|
||||
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', pkg);
|
||||
delete process.env.GH_TOKEN;
|
||||
process.env.GITHUB_TOKEN = 'GITHUB_TOKEN';
|
||||
|
||||
const result = await t.context.getConfig();
|
||||
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.is(result.options.analyzeCommits, release.analyzeCommits);
|
||||
t.is(result.options.generateNotes, release.generateNotes);
|
||||
t.deepEqual(result.options.getLastRelease, release.getLastRelease);
|
||||
t.is(result.options.branch, release.branch);
|
||||
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.is(t.context.plugins.firstCall.args[0].analyzeCommits, release.analyzeCommits);
|
||||
t.is(t.context.plugins.firstCall.args[0].generateNotes, release.generateNotes);
|
||||
t.deepEqual(t.context.plugins.firstCall.args[0].getLastRelease, release.getLastRelease);
|
||||
t.is(t.context.plugins.firstCall.args[0].branch, release.branch);
|
||||
|
||||
t.is(result.options.githubToken, process.env.GITHUB_TOKEN);
|
||||
});
|
||||
|
||||
test.serial('Priority cli parameters over package.json configuration', async t => {
|
||||
const release = {
|
||||
analyzeCommits: 'analyzeCommits',
|
||||
generateNotes: 'generateNotes',
|
||||
getLastRelease: {
|
||||
path: 'getLastRelease',
|
||||
param: 'getLastRelease_pkg',
|
||||
},
|
||||
branch: 'branch_pkg',
|
||||
};
|
||||
const options = {
|
||||
getLastRelease: {
|
||||
path: 'getLastRelease',
|
||||
param: 'getLastRelease_cli',
|
||||
},
|
||||
branch: 'branch_cli',
|
||||
};
|
||||
const pkg = {name: 'package_name', release};
|
||||
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', pkg);
|
||||
delete process.env.GH_TOKEN;
|
||||
process.env.GITHUB_TOKEN = 'GITHUB_TOKEN';
|
||||
|
||||
const result = await t.context.getConfig(options);
|
||||
|
||||
// Verify the options contains the plugin config from cli
|
||||
t.deepEqual(result.options.getLastRelease, options.getLastRelease);
|
||||
t.is(result.options.branch, options.branch);
|
||||
|
||||
// Verify the plugins module is called with the plugin options from cli
|
||||
t.deepEqual(t.context.plugins.firstCall.args[0].getLastRelease, options.getLastRelease);
|
||||
t.is(t.context.plugins.firstCall.args[0].branch, options.branch);
|
||||
});
|
||||
|
||||
test.serial('Get tag from .npmrc', async t => {
|
||||
const pkg = {name: 'package_name'};
|
||||
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', pkg);
|
||||
// Create local .npmrc
|
||||
await writeFile('.npmrc', 'tag=npmrc_tag');
|
||||
|
||||
// Make sure to not use the environment variable set by npm when running tests with npm run test
|
||||
delete process.env.npm_config_tag;
|
||||
const result = await t.context.getConfig();
|
||||
|
||||
// Verify the tag used in the one in .npmrc
|
||||
t.is(result.npm.tag, 'npmrc_tag');
|
||||
});
|
||||
|
||||
test.serial('Get tag from package.json, even if defined in .npmrc', async t => {
|
||||
const publishConfig = {tag: 'pkg_tag'};
|
||||
const pkg = {name: 'package_name', publishConfig};
|
||||
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', pkg);
|
||||
// Create local .npmrc
|
||||
await writeFile('.npmrc', 'tag=npmrc_tag');
|
||||
|
||||
const result = await t.context.getConfig();
|
||||
|
||||
// Verify the tag used in the one in package.json
|
||||
t.is(result.npm.tag, 'pkg_tag');
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import test from 'ava';
|
||||
import {stub} from 'sinon';
|
||||
import proxyquire from 'proxyquire';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Stub the logger functions
|
||||
t.context.log = stub();
|
||||
t.context.getNextVersion = proxyquire('../src/lib/get-next-version', {'./logger': {log: t.context.log}});
|
||||
});
|
||||
|
||||
test('Increase version for patch release', t => {
|
||||
const version = t.context.getNextVersion('patch', {version: '1.0.0'});
|
||||
t.is(version, '1.0.1');
|
||||
});
|
||||
|
||||
test('Increase version for minor release', t => {
|
||||
const version = t.context.getNextVersion('minor', {version: '1.0.0'});
|
||||
t.is(version, '1.1.0');
|
||||
});
|
||||
|
||||
test('Increase version for major release', t => {
|
||||
const version = t.context.getNextVersion('major', {version: '1.0.0'});
|
||||
t.is(version, '2.0.0');
|
||||
});
|
||||
|
||||
test('Return 1.0.0 if there is no previous release', t => {
|
||||
const version = t.context.getNextVersion('minor', {});
|
||||
t.is(version, '1.0.0');
|
||||
});
|
||||
|
||||
test('Return an error if the release type is invalid', t => {
|
||||
const error = t.throws(() => t.context.getNextVersion('invalid', {version: '1.0.0'}));
|
||||
t.is(error.code, 'EINVALIDTYPE');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
import {callbackify} from 'util';
|
||||
import test from 'ava';
|
||||
import {stub} from 'sinon';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import getReleaseType from '../src/lib/get-release-type';
|
||||
|
||||
test('Get commit types from commits', async t => {
|
||||
// Stub the commitAnalyzer plugin, returns 'major' release type
|
||||
const analyzeCommits = stub().resolves('major');
|
||||
const commits = [{hash: '0', message: 'a'}];
|
||||
|
||||
// Call the get-release-type module
|
||||
const releaseType = await getReleaseType({
|
||||
commits,
|
||||
lastRelease: {version: '1.0.0'},
|
||||
plugins: {analyzeCommits: callbackify(analyzeCommits)},
|
||||
});
|
||||
|
||||
// Verify the module return the release type obtain from the commitAnalyzer plugin
|
||||
t.is(releaseType, 'major');
|
||||
|
||||
// Verify the commitAnalyzer plugin was called with the commits
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits);
|
||||
});
|
||||
|
||||
test('Throws error when no changes', async t => {
|
||||
// Stub the commitAnalyzer plugin, returns 'null' release type
|
||||
const analyzeCommits = stub().resolves(null);
|
||||
const commits = [{hash: '0', message: 'a'}];
|
||||
|
||||
// Call the get-release-type module and verify it returns an error
|
||||
const error = await t.throws(
|
||||
getReleaseType({
|
||||
commits,
|
||||
lastRelease: {version: '1.0.0'},
|
||||
plugins: {analyzeCommits: callbackify(analyzeCommits)},
|
||||
})
|
||||
);
|
||||
|
||||
// Verify the error code adn type
|
||||
t.is(error.code, 'ENOCHANGE');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
|
||||
// Verify the commitAnalyzer plugin was called with the commits
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits);
|
||||
});
|
||||
|
||||
test('Return initial if there is no lastRelease', async t => {
|
||||
// Stub the commitAnalyzer plugin, returns 'major' release type
|
||||
const analyzeCommits = stub().resolves('major');
|
||||
const commits = [{hash: '0', message: 'a'}];
|
||||
|
||||
// Call the get-release-type module
|
||||
const releaseType = await getReleaseType({
|
||||
commits,
|
||||
lastRelease: {},
|
||||
plugins: {analyzeCommits: callbackify(analyzeCommits)},
|
||||
});
|
||||
|
||||
// Verify the module return an initial release type
|
||||
t.is(releaseType, 'initial');
|
||||
|
||||
// Verify the commitAnalyzer plugin was called with the commits
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits);
|
||||
});
|
||||
|
||||
test('Throws error when no changes even if there is no lastRelease', async t => {
|
||||
// Stub the commitAnalyzer plugin, returns 'null' release type
|
||||
const analyzeCommits = stub().resolves(null);
|
||||
const commits = [{hash: '0', message: 'a'}];
|
||||
|
||||
// Call the get-release-type module and verify it returns an error
|
||||
const error = await t.throws(
|
||||
getReleaseType({commits, lastRelease: {}, plugins: {analyzeCommits: callbackify(analyzeCommits)}})
|
||||
);
|
||||
|
||||
// Verify the error code adn type
|
||||
t.is(error.code, 'ENOCHANGE');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
|
||||
// Verify the commitAnalyzer plugin was called with the commits
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[0].commits, commits);
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import test from 'ava';
|
||||
import {gitRepo, gitCommits, gitHead} from './helpers/git-utils';
|
||||
import nock from 'nock';
|
||||
import {authenticate} from './helpers/mock-github';
|
||||
import githubRelease from '../src/lib/github-release';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current working diretory
|
||||
t.context.cwd = process.cwd();
|
||||
});
|
||||
|
||||
test.afterEach.always(t => {
|
||||
// Restore the current working directory
|
||||
process.chdir(t.context.cwd);
|
||||
// Reset nock
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
test.serial('Github release with default url', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
|
||||
const sha = await gitHead();
|
||||
const owner = 'test_user';
|
||||
const repo = 'test_repo';
|
||||
const githubToken = 'github_token';
|
||||
const notes = 'Test release note body';
|
||||
const version = '1.0.0';
|
||||
const branch = 'master';
|
||||
const tagName = `v${version}`;
|
||||
const options = {branch, githubToken};
|
||||
const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
|
||||
const releaseUrl = `https://othertesturl.com/${owner}/${repo}/releases/${version}`;
|
||||
|
||||
// Mock github API for releases and git/refs endpoints
|
||||
const github = authenticate({githubToken})
|
||||
.post(`/repos/${owner}/${repo}/releases`, {
|
||||
tag_name: tagName,
|
||||
target_commitish: branch,
|
||||
name: tagName,
|
||||
body: notes,
|
||||
})
|
||||
.reply(200, {html_url: releaseUrl})
|
||||
.post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${tagName}`, sha})
|
||||
.reply({});
|
||||
|
||||
// Call the post module
|
||||
t.is(releaseUrl, await githubRelease(pkg, notes, version, options));
|
||||
// Verify the releases and git/refs endpoint have been call with expected requests
|
||||
t.true(github.isDone());
|
||||
});
|
||||
|
||||
test.serial('Github release with custom url', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
|
||||
const sha = await gitHead();
|
||||
const owner = 'test_user';
|
||||
const repo = 'test_repo';
|
||||
const githubUrl = 'https://testurl.com:443';
|
||||
const githubToken = 'github_token';
|
||||
const githubApiPathPrefix = 'prefix';
|
||||
const notes = 'Test release note body';
|
||||
const version = '1.0.0';
|
||||
const branch = 'master';
|
||||
const tagName = `v${version}`;
|
||||
const options = {branch, githubUrl, githubToken, githubApiPathPrefix};
|
||||
const pkg = {version, repository: {url: `git@othertesturl.com:${owner}/${repo}.git`}};
|
||||
const releaseUrl = `https://othertesturl.com/${owner}/${repo}/releases/${version}`;
|
||||
|
||||
// Mock github API for releases and git/refs endpoints
|
||||
const github = authenticate({githubUrl, githubToken, githubApiPathPrefix})
|
||||
.post(`/repos/${owner}/${repo}/releases`, {
|
||||
tag_name: tagName,
|
||||
target_commitish: branch,
|
||||
name: tagName,
|
||||
body: notes,
|
||||
})
|
||||
.reply(200, {html_url: releaseUrl})
|
||||
.post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${tagName}`, sha})
|
||||
.reply({});
|
||||
|
||||
// Call the post module
|
||||
t.is(releaseUrl, await githubRelease(pkg, notes, version, options));
|
||||
// Verify the releases and git/refs endpoint have been call with expected requests
|
||||
t.true(github.isDone());
|
||||
});
|
||||
@@ -1,5 +1,12 @@
|
||||
import nock from 'nock';
|
||||
|
||||
/**
|
||||
* Retun a `nock` object setup to respond to a github authentication request. Other expectation and responses can be chained.
|
||||
*
|
||||
* @param {String} [githubToken='GH_TOKEN'] The github token to return in the authentication response.
|
||||
* @param {String} [githubUrl='https://api.github.com'] The url on which to intercept http requests.
|
||||
* @return {Object} A `nock` object ready to respond to a github authentication request.
|
||||
*/
|
||||
export function authenticate(
|
||||
{githubToken = 'GH_TOKEN', githubUrl = 'https://api.github.com', githubApiPathPrefix = ''} = {}
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import Docker from 'dockerode';
|
||||
import getStream from 'get-stream';
|
||||
import {mockServerClient} from 'mockserver-client';
|
||||
|
||||
const MOCK_SERVER_PORT = 1080;
|
||||
const MOCK_SERVER_HOST = 'localhost';
|
||||
const docker = new Docker();
|
||||
let container;
|
||||
|
||||
/**
|
||||
* Download the `mockserver` Docker image, create a new container and start it.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is started.
|
||||
*/
|
||||
async function start() {
|
||||
await getStream(await docker.pull('jamesdbloom/mockserver:latest'));
|
||||
|
||||
container = await docker.createContainer({
|
||||
Image: 'jamesdbloom/mockserver',
|
||||
PortBindings: {[`${MOCK_SERVER_PORT}/tcp`]: [{HostPort: `${MOCK_SERVER_PORT}`}]},
|
||||
});
|
||||
return container.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the `mockserver` Docker container.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is stopped.
|
||||
*/
|
||||
async function stop() {
|
||||
return container.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Object} A `mockserver` client configured to connect to the current instance.
|
||||
*/
|
||||
const client = mockServerClient(MOCK_SERVER_HOST, MOCK_SERVER_PORT);
|
||||
/**
|
||||
* @type {string} the url of the `mockserver` instance
|
||||
*/
|
||||
const url = `http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}`;
|
||||
|
||||
/**
|
||||
* Set up the `mockserver` instance response for a specific request.
|
||||
*
|
||||
* @param {string} path URI for which to respond.
|
||||
* @param {Object} request Request expectation. The http request made on `path` has to match those criteria in order to be valid.
|
||||
* @param {Object} request.body The JSON body the expected request must match.
|
||||
* @param {Object} request.headers The headers the expected request must match.
|
||||
* @param {Object} response The http response to return when receiving a request on `path`.
|
||||
* @param {String} [response.method='POST'] The http method for which to respond.
|
||||
* @param {number} [response.statusCode=200] The status code to respond.
|
||||
* @param {Object} response.body The JSON object to respond in the response body.
|
||||
* @return {Object} An object representation the expectation. Pass to the `verify` function to validate the `mockserver` has been called with a `request` matching the expectations.
|
||||
*/
|
||||
function mock(
|
||||
path,
|
||||
{body: requestBody, headers: requestHeaders},
|
||||
{method = 'POST', statusCode = 200, body: responseBody}
|
||||
) {
|
||||
client.mockAnyResponse({
|
||||
httpRequest: {path},
|
||||
httpResponse: {
|
||||
statusCode,
|
||||
headers: [{name: 'Content-Type', values: ['application/json; charset=utf-8']}],
|
||||
body: JSON.stringify(responseBody),
|
||||
},
|
||||
times: {remainingTimes: 1, unlimited: false},
|
||||
});
|
||||
|
||||
return {
|
||||
method,
|
||||
path,
|
||||
headers: requestHeaders,
|
||||
body: {type: 'JSON', json: JSON.stringify(requestBody), matchType: 'ONLY_MATCHING_FIELDS'},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the `mockserver` has been called with a requestion matching expectations. The `expectation` is created with the `mock` function.
|
||||
*
|
||||
* @param {Object} expectation The expectation created with `mock` function.
|
||||
* @return {Promise} A Promise that resolves if the expectation is met or reject otherwise.
|
||||
*/
|
||||
async function verify(expectation) {
|
||||
return client.verify(expectation);
|
||||
}
|
||||
|
||||
export default {start, stop, mock, verify, url};
|
||||
@@ -1,14 +1,14 @@
|
||||
import execa from 'execa';
|
||||
|
||||
const opts = {cwd: __dirname};
|
||||
const uri = 'http://localhost:' + (process.env.TRAVIS === 'true' ? 5984 : 15986) + '/registry/_design/app/_rewrite/';
|
||||
|
||||
export const uri =
|
||||
'http://localhost:' + (process.env.TRAVIS === 'true' ? 5984 : 15986) + '/registry/_design/app/_rewrite/';
|
||||
|
||||
export function start() {
|
||||
function start() {
|
||||
return execa('./start.sh', opts);
|
||||
}
|
||||
|
||||
export function stop() {
|
||||
function stop() {
|
||||
return execa('./stop.sh', opts);
|
||||
}
|
||||
|
||||
export default {start, stop, uri};
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
import test from 'ava';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
|
||||
const consoleLog = stub(console, 'log');
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current process.env
|
||||
t.context.env = Object.assign({}, process.env);
|
||||
// Save the current working diretory
|
||||
t.context.cwd = process.cwd();
|
||||
});
|
||||
|
||||
test.afterEach.always(t => {
|
||||
// Restore the current working directory
|
||||
process.chdir(t.context.cwd);
|
||||
// Restore process.env
|
||||
process.env = Object.assign({}, t.context.env);
|
||||
});
|
||||
|
||||
test.after.always(t => {
|
||||
consoleLog.restore();
|
||||
});
|
||||
|
||||
test('Plugins are called with expected values', async t => {
|
||||
const env = {NPM_TOKEN: 'NPM_TOKEN'};
|
||||
const pkgOptions = {branch: 'master'};
|
||||
const cliOptions = {githubToken: 'GH_TOKEN'};
|
||||
const options = Object.assign({}, pkgOptions, cliOptions);
|
||||
const pkg = {name: 'available', release: options, repository: {url: 'http://github.com/whats/up.git'}};
|
||||
const npm = {registry: 'http://test.registry.com'};
|
||||
const lastRelease = {version: '1.0.0', gitHead: 'test_commit_head'};
|
||||
const commitsLastRelease = {version: '1.0.0', gitHead: 'tag_head'};
|
||||
const commits = [{hash: '1', message: 'fix: First fix'}, {hash: '2', message: 'feat: First feature'}];
|
||||
const nextRelease = {type: 'major', version: '2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
|
||||
// Stub modules
|
||||
const log = stub();
|
||||
const error = stub();
|
||||
const logger = {log, error};
|
||||
const verifyAuth = stub().returns();
|
||||
const publishNpm = stub().resolves();
|
||||
const githubRelease = stub().resolves();
|
||||
const getCommits = stub().resolves({commits, lastRelease: commitsLastRelease});
|
||||
const getNextVersion = stub().returns(nextRelease.version);
|
||||
// Stub plugins
|
||||
const verifyConditions = stub().resolves();
|
||||
const getLastRelease = stub().resolves(lastRelease);
|
||||
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const verifyRelease = stub().resolves();
|
||||
const generateNotes = stub().resolves(notes);
|
||||
const getConfig = stub().resolves({
|
||||
plugins: {getLastRelease, analyzeCommits, verifyRelease, verifyConditions, generateNotes},
|
||||
env,
|
||||
options,
|
||||
pkg,
|
||||
npm,
|
||||
});
|
||||
|
||||
const semanticRelease = proxyquire('../src/index', {
|
||||
'./lib/logger': logger,
|
||||
'./lib/verify-auth': verifyAuth,
|
||||
'./lib/get-config': getConfig,
|
||||
'./lib/get-commits': getCommits,
|
||||
'./lib/publish-npm': publishNpm,
|
||||
'./lib/github-release': githubRelease,
|
||||
'./lib/get-next-version': getNextVersion,
|
||||
});
|
||||
|
||||
// Call the index module
|
||||
await semanticRelease(cliOptions);
|
||||
|
||||
// Verify the sub-modules have been called with expected parameters
|
||||
t.true(getConfig.calledOnce);
|
||||
t.true(getConfig.calledWithExactly(cliOptions));
|
||||
t.true(verifyAuth.calledOnce);
|
||||
t.true(verifyAuth.calledWithExactly(options, env));
|
||||
t.true(publishNpm.calledOnce);
|
||||
t.true(publishNpm.calledWithExactly(pkg, npm, nextRelease));
|
||||
t.true(githubRelease.calledOnce);
|
||||
t.true(githubRelease.calledWithExactly(pkg, notes, nextRelease.version, options));
|
||||
// Verify plugins have been called with expected parameters
|
||||
t.true(verifyConditions.calledOnce);
|
||||
t.true(verifyConditions.calledWithExactly({env, options, pkg, npm, logger}));
|
||||
t.true(getLastRelease.calledOnce);
|
||||
t.true(getLastRelease.calledWithExactly({env, options, pkg, npm, logger}));
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.true(analyzeCommits.calledWithExactly({env, options, pkg, npm, logger, lastRelease: commitsLastRelease, commits}));
|
||||
t.true(verifyRelease.calledOnce);
|
||||
t.true(
|
||||
verifyRelease.calledWithExactly({
|
||||
env,
|
||||
options,
|
||||
pkg,
|
||||
npm,
|
||||
logger,
|
||||
lastRelease: commitsLastRelease,
|
||||
commits,
|
||||
nextRelease,
|
||||
})
|
||||
);
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.true(
|
||||
generateNotes.calledWithExactly({
|
||||
env,
|
||||
options,
|
||||
pkg,
|
||||
npm,
|
||||
logger,
|
||||
lastRelease: commitsLastRelease,
|
||||
commits,
|
||||
nextRelease,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('Dry-run skips verifyAuth, verifyConditions, publishNpm and githubRelease', async t => {
|
||||
const env = {NPM_TOKEN: 'NPM_TOKEN'};
|
||||
const pkgOptions = {branch: 'master'};
|
||||
const cliOptions = {githubToken: 'GH_TOKEN', dryRun: true};
|
||||
const options = Object.assign({}, pkgOptions, cliOptions);
|
||||
const pkg = {name: 'available', release: options, repository: {url: 'http://github.com/whats/up.git'}};
|
||||
const npm = {registry: 'http://test.registry.com'};
|
||||
const lastRelease = {version: '1.0.0', gitHead: 'test_commit_head'};
|
||||
const commitsLastRelease = {version: '1.0.0', gitHead: 'tag_head'};
|
||||
const commits = [{hash: '1', message: 'fix: First fix'}, {hash: '2', message: 'feat: First feature'}];
|
||||
const nextRelease = {type: 'major', version: '2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
|
||||
// Stub modules
|
||||
const log = stub();
|
||||
const error = stub();
|
||||
const logger = {log, error};
|
||||
const verifyAuth = stub().returns();
|
||||
const publishNpm = stub().resolves();
|
||||
const githubRelease = stub().resolves();
|
||||
const getCommits = stub().resolves({commits, lastRelease: commitsLastRelease});
|
||||
const getNextVersion = stub().returns(nextRelease.version);
|
||||
// Stub plugins
|
||||
const verifyConditions = stub().resolves();
|
||||
const getLastRelease = stub().resolves(lastRelease);
|
||||
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const verifyRelease = stub().resolves();
|
||||
const generateNotes = stub().resolves(notes);
|
||||
const getConfig = stub().resolves({
|
||||
plugins: {getLastRelease, analyzeCommits, verifyRelease, verifyConditions, generateNotes},
|
||||
env,
|
||||
options,
|
||||
pkg,
|
||||
npm,
|
||||
});
|
||||
|
||||
const semanticRelease = proxyquire('../src/index', {
|
||||
'./lib/logger': logger,
|
||||
'./lib/verify-auth': verifyAuth,
|
||||
'./lib/get-config': getConfig,
|
||||
'./lib/get-commits': getCommits,
|
||||
'./lib/publish-npm': publishNpm,
|
||||
'./lib/github-release': githubRelease,
|
||||
'./lib/get-next-version': getNextVersion,
|
||||
});
|
||||
|
||||
// Call the index module
|
||||
await semanticRelease(cliOptions);
|
||||
|
||||
// Verify that publishNpm, githubRelease, verifyAuth, verifyConditions have not been called in a dry run
|
||||
t.true(publishNpm.notCalled);
|
||||
t.true(githubRelease.notCalled);
|
||||
t.true(verifyAuth.notCalled);
|
||||
t.true(verifyConditions.notCalled);
|
||||
// Verify the release notes are logged
|
||||
t.true(consoleLog.calledWithMatch(notes));
|
||||
// Verify the sub-modules have been called with expected parameters
|
||||
t.true(getConfig.calledOnce);
|
||||
t.true(getConfig.calledWithExactly(cliOptions));
|
||||
// Verify plugins have been called with expected parameters
|
||||
t.true(getLastRelease.calledOnce);
|
||||
t.true(getLastRelease.calledWithExactly({env, options, pkg, npm, logger}));
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.true(analyzeCommits.calledWithExactly({env, options, pkg, npm, logger, lastRelease: commitsLastRelease, commits}));
|
||||
t.true(verifyRelease.calledOnce);
|
||||
t.true(
|
||||
verifyRelease.calledWithExactly({
|
||||
env,
|
||||
options,
|
||||
pkg,
|
||||
npm,
|
||||
logger,
|
||||
lastRelease: commitsLastRelease,
|
||||
commits,
|
||||
nextRelease,
|
||||
})
|
||||
);
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.true(
|
||||
generateNotes.calledWithExactly({
|
||||
env,
|
||||
options,
|
||||
pkg,
|
||||
npm,
|
||||
logger,
|
||||
lastRelease: commitsLastRelease,
|
||||
commits,
|
||||
nextRelease,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('Throw SemanticReleaseError if there is no release to be done', async t => {
|
||||
const env = {NPM_TOKEN: 'NPM_TOKEN'};
|
||||
const pkgOptions = {branch: 'master'};
|
||||
const cliOptions = {githubToken: 'GH_TOKEN'};
|
||||
const options = Object.assign({}, pkgOptions, cliOptions);
|
||||
const pkg = {name: 'available', release: options, repository: {url: 'http://github.com/whats/up.git'}};
|
||||
const npm = {registry: 'http://test.registry.com'};
|
||||
const lastRelease = {version: '1.0.0', gitHead: 'test_commit_head'};
|
||||
const commitsLastRelease = {version: '1.0.0', gitHead: 'tag_head'};
|
||||
const commits = [{hash: '1', message: 'fix: First fix'}, {hash: '2', message: 'feat: First feature'}];
|
||||
const nextRelease = {type: undefined};
|
||||
|
||||
// Stub modules
|
||||
const log = stub();
|
||||
const error = stub();
|
||||
const logger = {log, error};
|
||||
const verifyAuth = stub().returns();
|
||||
const publishNpm = stub().resolves();
|
||||
const githubRelease = stub().resolves();
|
||||
const getCommits = stub().resolves({commits, lastRelease: commitsLastRelease});
|
||||
const getNextVersion = stub().returns(null);
|
||||
// Stub plugins
|
||||
const verifyConditions = stub().resolves();
|
||||
const getLastRelease = stub().resolves(lastRelease);
|
||||
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const verifyRelease = stub().resolves();
|
||||
const generateNotes = stub().resolves();
|
||||
const getConfig = stub().resolves({
|
||||
plugins: {getLastRelease, analyzeCommits, verifyRelease, verifyConditions, generateNotes},
|
||||
env,
|
||||
options,
|
||||
pkg,
|
||||
npm,
|
||||
});
|
||||
|
||||
const semanticRelease = proxyquire('../src/index', {
|
||||
'./lib/logger': logger,
|
||||
'./lib/verify-auth': verifyAuth,
|
||||
'./lib/get-config': getConfig,
|
||||
'./lib/get-commits': getCommits,
|
||||
'./lib/publish-npm': publishNpm,
|
||||
'./lib/github-release': githubRelease,
|
||||
'./lib/get-next-version': getNextVersion,
|
||||
});
|
||||
|
||||
// Call the index module
|
||||
const err = await t.throws(semanticRelease(cliOptions));
|
||||
// Verify error code and type
|
||||
t.is(err.code, 'ENOCHANGE');
|
||||
t.true(err instanceof SemanticReleaseError);
|
||||
// Verify the sub-modules have been called with expected parameters
|
||||
t.true(getConfig.calledOnce);
|
||||
t.true(getConfig.calledWithExactly(cliOptions));
|
||||
t.true(verifyAuth.calledOnce);
|
||||
t.true(verifyAuth.calledWithExactly(options, env));
|
||||
// Verify plugins have been called with expected parameters
|
||||
t.true(verifyConditions.calledOnce);
|
||||
t.true(verifyConditions.calledWithExactly({env, options, pkg, npm, logger}));
|
||||
t.true(getLastRelease.calledOnce);
|
||||
t.true(getLastRelease.calledWithExactly({env, options, pkg, npm, logger}));
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.true(analyzeCommits.calledWithExactly({env, options, pkg, npm, logger, lastRelease: commitsLastRelease, commits}));
|
||||
// Verify that verifyRelease, publishNpm, generateNotes, githubRelease have not been called when no release is done
|
||||
t.true(verifyRelease.notCalled);
|
||||
t.true(generateNotes.notCalled);
|
||||
t.true(publishNpm.notCalled);
|
||||
t.true(githubRelease.notCalled);
|
||||
});
|
||||
+508
-151
@@ -1,49 +1,68 @@
|
||||
import test from 'ava';
|
||||
import {writeJson, readJson} from 'fs-extra';
|
||||
import {start, stop, uri} from './helpers/registry';
|
||||
import {gitRepo, gitCommits, gitHead, gitTagVersion, gitPackRefs, gitAmmendCommit} from './helpers/git-utils';
|
||||
import {stub} from 'sinon';
|
||||
import execa from 'execa';
|
||||
import {gitRepo, gitCommits, gitHead, gitTagVersion, gitPackRefs, gitAmmendCommit} from './helpers/git-utils';
|
||||
import registry from './helpers/registry';
|
||||
import mockServer from './helpers/mockserver';
|
||||
import semanticRelease from '../src';
|
||||
|
||||
// Environment variables used with cli
|
||||
const env = {
|
||||
CI: true,
|
||||
npm_config_registry: uri,
|
||||
npm_config_registry: registry.uri,
|
||||
GH_TOKEN: 'github_token',
|
||||
NPM_OLD_TOKEN: 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==',
|
||||
NPM_EMAIL: 'integration@test.com',
|
||||
};
|
||||
const cli = require.resolve('../bin/semantic-release');
|
||||
const noop = require.resolve('../src/lib/plugin-noop');
|
||||
const pluginError = require.resolve('./fixtures/plugin-error-a');
|
||||
|
||||
test.before(async t => {
|
||||
await mockServer.start();
|
||||
// Start the local NPM registry
|
||||
await start();
|
||||
await registry.start();
|
||||
});
|
||||
|
||||
test.beforeEach(async t => {
|
||||
// Save the current process.env
|
||||
t.context.env = Object.assign({}, process.env);
|
||||
// Save the current working diretory
|
||||
t.context.cwd = process.cwd();
|
||||
|
||||
t.context.log = stub(console, 'log');
|
||||
t.context.error = stub(console, 'error');
|
||||
});
|
||||
|
||||
test.afterEach.always(async t => {
|
||||
console.log();
|
||||
// Restore process.env
|
||||
process.env = Object.assign({}, t.context.env);
|
||||
// Restore the current working directory
|
||||
process.chdir(t.context.cwd);
|
||||
|
||||
t.context.log.restore();
|
||||
t.context.error.restore();
|
||||
});
|
||||
|
||||
test.after.always(async t => {
|
||||
await mockServer.stop();
|
||||
// Stop the local NPM registry
|
||||
await stop();
|
||||
await registry.stop();
|
||||
});
|
||||
|
||||
test.serial('Release patch, minor and major versions', async t => {
|
||||
const packageName = 'test-module';
|
||||
const repo = 'test-repo';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository');
|
||||
t.log('Create git repository and package.json');
|
||||
await gitRepo();
|
||||
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: 'test-module',
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: 'git+https://github.com/semantic-release/test-module'},
|
||||
release: {verifyConditions: require.resolve('../src/lib/plugin-noop')},
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {verifyConditions: noop, githubUrl: mockServer.url},
|
||||
});
|
||||
// Create a npm-shrinkwrap.json file
|
||||
await execa('npm', ['shrinkwrap'], {env});
|
||||
@@ -52,215 +71,325 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
|
||||
t.log('Commit a chore');
|
||||
await gitCommits(['chore: Init repository']);
|
||||
t.log('$ semantic-release pre');
|
||||
let {stdout, stderr, code} = await t.throws(execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
t.regex(stderr, /ENOCHANGE There are no relevant changes, so no new version is released/);
|
||||
t.is(code, 1);
|
||||
t.log('$ semantic-release');
|
||||
let {stdout, code} = await execa(cli, [], {env});
|
||||
t.regex(stdout, /There are no relevant changes, so no new version is released/);
|
||||
t.is(code, 0);
|
||||
|
||||
/** Minor release **/
|
||||
/** Initial release **/
|
||||
let version = '1.0.0';
|
||||
let createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
let createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release pre');
|
||||
({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.0');
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, '1.0.0');
|
||||
t.log('$ npm publish');
|
||||
({stdout, stderr, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module@1.0.0/);
|
||||
t.log('$ semantic-release');
|
||||
({stdout, code} = await execa(cli, [], {env}));
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
let [, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout
|
||||
let [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
t.is(version, '1.0.0');
|
||||
t.is(releaseGitHead, await gitHead());
|
||||
t.log(`+ released ${version} with gitHead ${releaseGitHead}`);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
|
||||
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/** Patch release **/
|
||||
version = '1.0.1';
|
||||
createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
t.log('Commit a fix');
|
||||
await gitCommits(['fix: bar']);
|
||||
t.log('$ semantic-release pre');
|
||||
({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.1');
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, '1.0.1');
|
||||
t.log('$ npm publish');
|
||||
({stdout, stderr, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module@1.0.1/);
|
||||
t.log('$ semantic-release');
|
||||
({stdout, code} = await execa(cli, [], {env}));
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
t.is(version, '1.0.1');
|
||||
t.is(releaseGitHead, await gitHead());
|
||||
t.log(`+ released ${version} with gitHead ${releaseGitHead}`);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
|
||||
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/** Minor release **/
|
||||
version = '1.1.0';
|
||||
createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: baz']);
|
||||
t.log('$ semantic-release pre');
|
||||
({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, '1.1.0');
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, '1.1.0');
|
||||
t.log('$ npm publish');
|
||||
({stdout, stderr, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module@1.1.0/);
|
||||
t.log('$ semantic-release');
|
||||
({stdout, code} = await execa(cli, [], {env}));
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
t.is(version, '1.1.0');
|
||||
t.is(releaseGitHead, await gitHead());
|
||||
t.log(`+ released ${version} with gitHead ${releaseGitHead}`);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
|
||||
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/** Major release **/
|
||||
version = '2.0.0';
|
||||
createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
t.log('Commit a breaking change');
|
||||
await gitCommits(['feat: foo\n\n BREAKING CHANGE: bar']);
|
||||
t.log('$ semantic-release pre');
|
||||
({stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, '2.0.0');
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, '2.0.0');
|
||||
t.log('$ npm publish');
|
||||
({stdout, stderr, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module@2.0.0/);
|
||||
t.log('$ semantic-release');
|
||||
({stdout, code} = await execa(cli, [], {env}));
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
|
||||
// Verify package.json and npm-shrinkwrap.json have been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
t.is((await readJson('./npm-shrinkwrap.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', 'test-module', 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
t.is(version, '2.0.0');
|
||||
t.is(releaseGitHead, await gitHead());
|
||||
t.log(`+ released ${version} with gitHead ${releaseGitHead}`);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
|
||||
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
});
|
||||
|
||||
test.serial('Release versions from a packed git repository, using tags to determine last release gitHead', async t => {
|
||||
const packageName = 'test-module-2';
|
||||
const repo = 'test-repo';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository');
|
||||
await gitRepo();
|
||||
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: 'test-module-2',
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: 'git+https://github.com/semantic-release/test-module-2'},
|
||||
release: {verifyConditions: require.resolve('../src/lib/plugin-noop')},
|
||||
repository: {url: `git@github.com:${repo}/${packageName}.git`},
|
||||
release: {verifyConditions: noop, githubUrl: mockServer.url},
|
||||
});
|
||||
|
||||
/** Minor release **/
|
||||
|
||||
/** Initial release **/
|
||||
let version = '1.0.0';
|
||||
let createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
let createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ git pack-refs --all');
|
||||
await gitPackRefs();
|
||||
t.log('$ semantic-release pre');
|
||||
let {stdout, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env});
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.0');
|
||||
t.log('$ npm publish');
|
||||
({stdout, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module-2@1.0.0/);
|
||||
t.log('$ semantic-release');
|
||||
let {stdout, code} = await execa(cli, [], {env});
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
let version = (await execa('npm', ['show', 'test-module-2', 'version'], {env})).stdout;
|
||||
t.is(version, '1.0.0');
|
||||
t.log(`+ released ${version}`);
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
// Retrieve the published package from the registry and check version
|
||||
let releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env})).stdout;
|
||||
t.is(releasedVersion, version);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
// Create a tag version so the tag can be used later to determine the commit associated with the version
|
||||
await gitTagVersion('v1.0.0');
|
||||
t.log('Create git tag v1.0.0');
|
||||
await gitTagVersion(`v${version}`);
|
||||
t.log(`Create git tag v${version}`);
|
||||
|
||||
/** Patch release **/
|
||||
|
||||
version = '1.0.1';
|
||||
createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
t.log('Commit a fix');
|
||||
await gitCommits(['fix: bar']);
|
||||
t.log('$ semantic-release pre');
|
||||
({stdout, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.1');
|
||||
t.log('$ npm publish');
|
||||
({stdout, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module-2@1.0.1/);
|
||||
t.log('$ semantic-release');
|
||||
({stdout, code} = await execa(cli, [], {env}));
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
version = (await execa('npm', ['show', 'test-module-2', 'version'], {env})).stdout;
|
||||
t.is(version, '1.0.1');
|
||||
t.log(`+ released ${version}`);
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version
|
||||
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env})).stdout;
|
||||
t.is(releasedVersion, version);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
});
|
||||
|
||||
test.serial('Exit with 1 in a plugin is not found', async t => {
|
||||
// Environment variables used with cli
|
||||
const env = {
|
||||
CI: true,
|
||||
npm_config_registry: uri,
|
||||
GH_TOKEN: 'github_token',
|
||||
NPM_OLD_TOKEN: 'aW50ZWdyYXRpb246c3VjaHNlY3VyZQ==',
|
||||
NPM_EMAIL: 'integration@test.com',
|
||||
};
|
||||
test.serial('Exit with 1 if a plugin is not found', async t => {
|
||||
const packageName = 'test-module-3';
|
||||
const repo = 'test-repo';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository');
|
||||
await gitRepo();
|
||||
await writeJson('./package.json', {
|
||||
name: 'test-module-3',
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: 'git+https://github.com/semantic-release/test-module-4'},
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {analyzeCommits: 'non-existing-path'},
|
||||
});
|
||||
|
||||
const {code} = await t.throws(execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
const {code, stderr} = await t.throws(execa(cli, [], {env}));
|
||||
t.is(code, 1);
|
||||
t.regex(stderr, /Cannot find module/);
|
||||
});
|
||||
|
||||
test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', async t => {
|
||||
const packageName = 'test-module-4';
|
||||
const repo = 'test-repo';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository');
|
||||
await gitRepo();
|
||||
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: 'test-module-4',
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: 'git+https://github.com/semantic-release/test-module-2'},
|
||||
release: {verifyConditions: require.resolve('../src/lib/plugin-noop')},
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {verifyConditions: noop, githubUrl: mockServer.url},
|
||||
});
|
||||
|
||||
/** Minor release **/
|
||||
|
||||
/** Initial release **/
|
||||
let version = '1.0.0';
|
||||
let createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
let createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release pre');
|
||||
let {stdout, stderr, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env});
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.0');
|
||||
t.log('$ npm publish');
|
||||
({stdout, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module-4@1.0.0/);
|
||||
t.log('$ semantic-release');
|
||||
let {stderr, stdout, code} = await execa(cli, [], {env});
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
let [, version, releaseGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', 'test-module-4', 'version', 'gitHead'], {env})).stdout
|
||||
let [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
const head = await gitHead();
|
||||
t.is(releaseGitHead, head);
|
||||
t.log(`+ released ${version}`);
|
||||
t.is(version, '1.0.0');
|
||||
t.is(releasedGitHead, head);
|
||||
t.is(releasedVersion, version);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
// Create a tag version so the tag can be used later to determine the commit associated with the version
|
||||
await gitTagVersion('v1.0.0');
|
||||
t.log('Create git tag v1.0.0');
|
||||
await gitTagVersion(`v${version}`);
|
||||
t.log(`Create git tag v${version}`);
|
||||
|
||||
/** Rewrite sha of commit used for release **/
|
||||
|
||||
@@ -271,34 +400,262 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
||||
|
||||
t.log('Commit a fix');
|
||||
await gitCommits(['fix: bar']);
|
||||
t.log('$ semantic-release pre');
|
||||
({stderr, stdout, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env, reject: false}));
|
||||
t.log('$ semantic-release');
|
||||
({stderr, stdout, code} = await execa(cli, [], {env, reject: false}));
|
||||
|
||||
t.log('Fail with "ENOTINHISTORY" error');
|
||||
t.is(code, 1);
|
||||
t.log('Log "ENOTINHISTORY" message');
|
||||
t.is(code, 0);
|
||||
t.regex(
|
||||
stderr,
|
||||
new RegExp(
|
||||
`You can recover from this error by restoring the commit "${head}" or by creating a tag for the version "1.0.0" on the commit corresponding to this release`
|
||||
`You can recover from this error by restoring the commit "${head}" or by creating a tag for the version "${version}" on the commit corresponding to this release`
|
||||
)
|
||||
);
|
||||
|
||||
/** Create a tag to recover and redo release **/
|
||||
|
||||
t.log('Create git tag v1.0.0 to recover');
|
||||
await gitTagVersion('v1.0.0', hash);
|
||||
t.log(`Create git tag v${version} to recover`);
|
||||
await gitTagVersion(`v${version}`, hash);
|
||||
|
||||
t.log('$ semantic-release pre');
|
||||
({stderr, stdout, code} = await execa(require.resolve('../bin/semantic-release'), ['pre'], {env}));
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, '1.0.1');
|
||||
t.log('$ npm publish');
|
||||
({stdout, code} = await execa('npm', ['publish'], {env}));
|
||||
// Verify output of npm publish
|
||||
t.regex(stdout, /test-module-4@1.0.1/);
|
||||
version = '1.0.1';
|
||||
createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
t.log('$ semantic-release');
|
||||
({stderr, stdout, code} = await execa(cli, [], {env}));
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
// Verify package.json has been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
version = (await execa('npm', ['show', 'test-module-4', 'version'], {env})).stdout;
|
||||
t.is(version, '1.0.1');
|
||||
t.log(`+ released ${version}`);
|
||||
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env})).stdout;
|
||||
t.is(releasedVersion, version);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
});
|
||||
|
||||
test.serial('Dry-run', async t => {
|
||||
const packageName = 'test-module-5';
|
||||
const repo = 'test-repo';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository and package.json');
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {githubUrl: mockServer.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
const version = '1.0.0';
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release -d');
|
||||
let {stdout, code} = await execa(cli, ['-d'], {env});
|
||||
t.regex(stdout, new RegExp(`There is no previous release, the next release version is ${version}`));
|
||||
t.regex(stdout, new RegExp(`Release note for version ${version}`));
|
||||
t.regex(stdout, /Initial commit/);
|
||||
t.is(code, 0);
|
||||
|
||||
// Verify package.json and has not been modified
|
||||
t.is((await readJson('./package.json')).version, '0.0.0-dev');
|
||||
});
|
||||
|
||||
test.serial('Pass options via CLI arguments', async t => {
|
||||
const packageName = 'test-module-6';
|
||||
const repo = 'test-repo';
|
||||
const githubToken = 'OTHER_TOKEN';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository and package.json');
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {githubUrl: mockServer.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
let version = '1.0.0';
|
||||
let createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${githubToken}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
let createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${githubToken}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release');
|
||||
let {stdout, code} = await execa(
|
||||
cli,
|
||||
['--github-token', githubToken, '--verify-conditions', `${noop}, ${noop}`, '--debug'],
|
||||
{env}
|
||||
);
|
||||
t.regex(stdout, new RegExp(`Published Github release: release-url/${version}`));
|
||||
t.regex(stdout, new RegExp(`Publishing version ${version} to npm registry ${registry.uri}`));
|
||||
t.is(code, 0);
|
||||
|
||||
// Verify package.json and has been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
let [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
|
||||
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
});
|
||||
|
||||
test.serial('Run via JS API', async t => {
|
||||
const packageName = 'test-module-7';
|
||||
const repo = 'test-repo';
|
||||
const githubToken = 'OTHER_TOKEN';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository and package.json');
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {githubUrl: mockServer.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
let version = '1.0.0';
|
||||
let createRefMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${githubToken}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
let createReleaseMock = mockServer.mock(
|
||||
`/repos/${repo}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
headers: [{name: 'Authorization', values: [`token ${githubToken}`]}],
|
||||
},
|
||||
{body: {html_url: `release-url/${version}`}}
|
||||
);
|
||||
|
||||
process.env = Object.assign(process.env, env);
|
||||
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ Call semantic-release via API');
|
||||
await semanticRelease({githubToken, verifyConditions: [noop, noop], debug: true});
|
||||
|
||||
t.true(t.context.log.calledWithMatch(/Published Github release: /, new RegExp(`release-url/${version}`)));
|
||||
t.true(t.context.log.calledWithMatch(/Publishing version %s to npm registry %s/, version, registry.uri));
|
||||
|
||||
// Verify package.json and has been updated
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
let [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
t.log(`+ released ${releasedVersion} with gitHead ${releasedGitHead}`);
|
||||
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
});
|
||||
|
||||
test.serial('Returns and error code if NPM token is invalid', async t => {
|
||||
const env = {npm_config_registry: registry.uri, GH_TOKEN: 'github_token', NPM_TOKEN: 'wrong_token'};
|
||||
const packageName = 'test-module-8';
|
||||
const repo = 'test-repo';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository and package.json');
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {verifyConditions: noop, githubUrl: mockServer.url},
|
||||
});
|
||||
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release');
|
||||
let {stderr, code} = await execa(cli, [], {env, reject: false});
|
||||
|
||||
t.regex(stderr, /forbidden Please log in before writing to the db/);
|
||||
t.is(code, 1);
|
||||
});
|
||||
|
||||
test.serial('Log unexpected errors from plugins', async t => {
|
||||
const packageName = 'test-module-9';
|
||||
const repo = 'test-repo';
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
t.log('Create git repository and package.json');
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${repo}/${packageName}`},
|
||||
release: {githubUrl: mockServer.url, verifyConditions: pluginError},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release');
|
||||
let {stderr, code} = await execa(cli, [], {env, reject: false});
|
||||
t.regex(stderr, /Error: a/);
|
||||
t.regex(stderr, new RegExp(pluginError));
|
||||
t.is(code, 1);
|
||||
});
|
||||
|
||||
test.serial('CLI returns error code and prints help if called with a command', async t => {
|
||||
t.log('$ semantic-release pre');
|
||||
let {stdout, code} = await execa(cli, ['pre'], {env, reject: false});
|
||||
t.regex(stdout, /Usage: semantic-release/);
|
||||
t.is(code, 1);
|
||||
});
|
||||
|
||||
test.serial('CLI prints help if called with --help', async t => {
|
||||
t.log('$ semantic-release --help');
|
||||
let {stdout, code} = await execa(cli, ['--help'], {env});
|
||||
t.regex(stdout, /Usage: semantic-release/);
|
||||
t.is(code, 0);
|
||||
});
|
||||
|
||||
test.serial('CLI returns error code with invalid option', async t => {
|
||||
t.log('$ semantic-release --unknown-option');
|
||||
let {stderr, code} = await execa(cli, ['--unknown-option'], {env, reject: false});
|
||||
t.regex(stderr, /unknown option/);
|
||||
t.is(code, 1);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import test from 'ava';
|
||||
import {stub} from 'sinon';
|
||||
import logger from '../src/lib/logger';
|
||||
|
||||
test.beforeEach(t => {
|
||||
t.context.log = stub(console, 'log');
|
||||
t.context.error = stub(console, 'error');
|
||||
});
|
||||
|
||||
test.afterEach.always(t => {
|
||||
t.context.log.restore();
|
||||
t.context.error.restore();
|
||||
});
|
||||
|
||||
test.serial('Basic log', t => {
|
||||
logger.log('test log');
|
||||
logger.error('test error');
|
||||
|
||||
t.true(t.context.log.calledWithMatch(/.*test log/));
|
||||
t.true(t.context.error.calledWithMatch(/.*test error/));
|
||||
});
|
||||
|
||||
test.serial('Log with string formatting', t => {
|
||||
logger.log('test log %s', 'log value');
|
||||
logger.error('test error %s', 'error value');
|
||||
|
||||
t.true(t.context.log.calledWithMatch(/.*test log %s/, 'log value'));
|
||||
t.true(t.context.error.calledWithMatch(/.*test error %s/, 'error value'));
|
||||
});
|
||||
|
||||
test.serial('Log with error stacktrace', t => {
|
||||
logger.error(new Error('error message'));
|
||||
logger.error('test error %s', new Error('other error message'));
|
||||
|
||||
t.true(t.context.error.calledWithMatch(/.*test error %s/, /Error: other error message(\s|.)*?logger\.test\.js/));
|
||||
t.true(t.context.error.calledWithMatch(/Error: error message(\s|.)*?logger\.test\.js/));
|
||||
});
|
||||
+31
-11
@@ -1,10 +1,17 @@
|
||||
import {promisify} from 'util';
|
||||
import test from 'ava';
|
||||
import plugins from '../src/lib/plugins';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub, match} from 'sinon';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Stub the logger functions
|
||||
t.context.log = stub();
|
||||
t.context.plugins = proxyquire('../src/lib/plugins', {'./logger': {log: t.context.log}});
|
||||
});
|
||||
|
||||
test('Export plugins', t => {
|
||||
// Call the plugin module
|
||||
const defaultPlugins = plugins({});
|
||||
const defaultPlugins = t.context.plugins({});
|
||||
|
||||
// Verify the module returns a function for each plugin
|
||||
t.is(typeof defaultPlugins.analyzeCommits, 'function');
|
||||
@@ -16,7 +23,7 @@ test('Export plugins', t => {
|
||||
|
||||
test('Pipeline - Get all results', async t => {
|
||||
// Call the plugin module with a verifyRelease plugin pipeline
|
||||
const pipelinePlugins = plugins({
|
||||
const pipelinePlugins = t.context.plugins({
|
||||
verifyRelease: ['./src/lib/plugin-noop', './test/fixtures/plugin-result-a', './test/fixtures/plugin-result-b'],
|
||||
});
|
||||
|
||||
@@ -25,6 +32,10 @@ test('Pipeline - Get all results', async t => {
|
||||
|
||||
// Verify the pipeline return the expected result for each plugin, in order
|
||||
t.deepEqual(results, [undefined, 'a', 'b']);
|
||||
// Verify the logger has been called with the plugins path
|
||||
t.true(t.context.log.calledWith(match.string, './src/lib/plugin-noop'));
|
||||
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-result-a'));
|
||||
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-result-b'));
|
||||
});
|
||||
|
||||
test('Pipeline - Pass pluginConfig and options to each plugins', async t => {
|
||||
@@ -33,7 +44,7 @@ test('Pipeline - Pass pluginConfig and options to each plugins', async t => {
|
||||
// Semantic-release global options
|
||||
const options = {semanticReleaseParam: 'param2'};
|
||||
// Call the plugin module with a verifyRelease plugin pipeline
|
||||
const pipelinePlugins = plugins({
|
||||
const pipelinePlugins = t.context.plugins({
|
||||
verifyRelease: [pluginConfig, './test/fixtures/plugin-result-config'],
|
||||
});
|
||||
|
||||
@@ -42,37 +53,46 @@ test('Pipeline - Pass pluginConfig and options to each plugins', async t => {
|
||||
|
||||
// Verify the pipeline first result is the pluginConfig and options parameters (to verify the plugin was called with the defined pluginConfig and options parameters)
|
||||
t.deepEqual(results, [{pluginConfig, options}, {pluginConfig: {}, options}]);
|
||||
// Verify the logger has been called with the plugins path
|
||||
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-result-config'));
|
||||
});
|
||||
|
||||
test('Pipeline - Get first error', async t => {
|
||||
// Call the plugin module with a verifyRelease plugin pipeline
|
||||
const pipelinePlugins = plugins({
|
||||
const pipelinePlugins = t.context.plugins({
|
||||
verifyRelease: ['./src/lib/plugin-noop', './test/fixtures/plugin-error-a', './test/fixtures/plugin-error-b'],
|
||||
});
|
||||
|
||||
// Call the verifyRelease pipeline and verify it returns the error thrown by './test/fixtures/plugin-error-a'
|
||||
await t.throws(pipelinePlugins.verifyRelease({}), 'a');
|
||||
// Verify the logger has been called with the plugins path
|
||||
t.true(t.context.log.calledWith(match.string, './src/lib/plugin-noop'));
|
||||
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-error-a'));
|
||||
});
|
||||
|
||||
test('Normalize and load plugin from string', t => {
|
||||
// Call the normalize function with a path
|
||||
const plugin = plugins.normalize('./src/lib/plugin-noop');
|
||||
const plugin = t.context.plugins.normalize('./src/lib/plugin-noop');
|
||||
|
||||
// Verify the plugin is loaded
|
||||
t.is(typeof plugin, 'function');
|
||||
// Verify the logger has been called with the plugins path
|
||||
t.true(t.context.log.calledWith(match.string, './src/lib/plugin-noop'));
|
||||
});
|
||||
|
||||
test('Normalize and load plugin from object', t => {
|
||||
// Call the normalize function with an object (with path property)
|
||||
const plugin = plugins.normalize({path: './src/lib/plugin-noop'});
|
||||
const plugin = t.context.plugins.normalize({path: './src/lib/plugin-noop'});
|
||||
|
||||
// Verify the plugin is loaded
|
||||
t.is(typeof plugin, 'function');
|
||||
// Verify the logger has been called with the plugins path
|
||||
t.true(t.context.log.calledWith(match.string, './src/lib/plugin-noop'));
|
||||
});
|
||||
|
||||
test('Load from fallback', t => {
|
||||
// Call the normalize function with a fallback
|
||||
const plugin = plugins.normalize(null, '../lib/plugin-noop');
|
||||
const plugin = t.context.plugins.normalize(null, '../lib/plugin-noop');
|
||||
|
||||
// Verify the fallback plugin is loaded
|
||||
t.is(typeof plugin, 'function');
|
||||
@@ -80,7 +100,7 @@ test('Load from fallback', t => {
|
||||
|
||||
test('Always pass a defined "pluginConfig" for plugin defined with string', async t => {
|
||||
// Call the normalize function with the path of a plugin that returns its config
|
||||
const plugin = plugins.normalize('./test/fixtures/plugin-result-config');
|
||||
const plugin = t.context.plugins.normalize('./test/fixtures/plugin-result-config');
|
||||
const pluginResult = await promisify(plugin)({});
|
||||
|
||||
t.deepEqual(pluginResult.pluginConfig, {});
|
||||
@@ -88,7 +108,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with string', asyn
|
||||
|
||||
test('Always pass a defined "pluginConfig" for plugin defined with path', async t => {
|
||||
// Call the normalize function with the path of a plugin that returns its config
|
||||
const plugin = plugins.normalize({path: './test/fixtures/plugin-result-config'});
|
||||
const plugin = t.context.plugins.normalize({path: './test/fixtures/plugin-result-config'});
|
||||
const pluginResult = await promisify(plugin)({});
|
||||
|
||||
t.deepEqual(pluginResult.pluginConfig, {path: './test/fixtures/plugin-result-config'});
|
||||
@@ -96,7 +116,7 @@ test('Always pass a defined "pluginConfig" for plugin defined with path', async
|
||||
|
||||
test('Always pass a defined "pluginConfig" for fallback plugin', async t => {
|
||||
// Call the normalize function with the path of a plugin that returns its config
|
||||
const plugin = plugins.normalize(null, '../../test/fixtures/plugin-result-config');
|
||||
const plugin = t.context.plugins.normalize(null, '../../test/fixtures/plugin-result-config');
|
||||
const pluginResult = await promisify(plugin)({});
|
||||
|
||||
t.deepEqual(pluginResult.pluginConfig, {});
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
import {callbackify} from 'util';
|
||||
import test from 'ava';
|
||||
import {gitRepo, gitCommits, gitHead} from './helpers/git-utils';
|
||||
import {stub} from 'sinon';
|
||||
import nock from 'nock';
|
||||
import {authenticate} from './helpers/mock-github';
|
||||
import post from '../src/post';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current working diretory
|
||||
t.context.cwd = process.cwd();
|
||||
});
|
||||
|
||||
test.afterEach.always(t => {
|
||||
// Restore the current working directory
|
||||
process.chdir(t.context.cwd);
|
||||
// Reset nock
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
test.serial('Post run with github token', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
|
||||
const sha = await gitHead();
|
||||
const owner = 'test_user';
|
||||
const repo = 'test_repo';
|
||||
const githubUrl = 'https://testurl.com:443';
|
||||
const githubToken = 'github_token';
|
||||
const githubApiPathPrefix = 'prefix';
|
||||
const releaseLog = 'Test release note body';
|
||||
// Stub the generateNotes plugin
|
||||
const generateNotes = stub().resolves(releaseLog);
|
||||
const version = '1.0.0';
|
||||
const branch = 'master';
|
||||
const debug = false;
|
||||
const tagName = `v${version}`;
|
||||
const options = {branch, debug, githubUrl, githubToken, githubApiPathPrefix};
|
||||
const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
|
||||
|
||||
// Mock github API for releases and git/refs endpoints
|
||||
const github = authenticate({githubUrl, githubToken, githubApiPathPrefix})
|
||||
.post(`/repos/${owner}/${repo}/releases`, {
|
||||
tag_name: tagName,
|
||||
target_commitish: branch,
|
||||
name: tagName,
|
||||
body: releaseLog,
|
||||
draft: debug,
|
||||
})
|
||||
.reply({})
|
||||
.post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${tagName}`, sha})
|
||||
.reply({});
|
||||
|
||||
// Call the post module
|
||||
const result = await post({pkg, options, plugins: {generateNotes: callbackify(generateNotes)}});
|
||||
|
||||
// Verify the getLastRelease plugin has been called with 'options' and 'pkg'
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.deepEqual(generateNotes.firstCall.args[0].options, options);
|
||||
t.deepEqual(generateNotes.firstCall.args[0].pkg, pkg);
|
||||
|
||||
// Verify the published release note
|
||||
t.deepEqual(result, {
|
||||
published: true,
|
||||
release: {owner, repo, tag_name: tagName, name: tagName, target_commitish: branch, draft: debug, body: releaseLog},
|
||||
});
|
||||
// Verify the releases and git/refs endpoint have been call with expected requests
|
||||
t.true(github.isDone());
|
||||
});
|
||||
|
||||
test.serial('Post dry run with github token', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
|
||||
const owner = 'test_user';
|
||||
const repo = 'test_repo';
|
||||
const githubToken = 'github_token';
|
||||
const releaseLog = 'Test release note body';
|
||||
// Stub the generateNotes plugin
|
||||
const generateNotes = stub().resolves(releaseLog);
|
||||
const version = '1.0.0';
|
||||
const branch = 'master';
|
||||
const debug = true;
|
||||
const tagName = `v${version}`;
|
||||
const options = {branch, debug, githubToken};
|
||||
const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
|
||||
|
||||
// Mock github API for releases endpoint
|
||||
const github = authenticate({githubToken})
|
||||
.post(`/repos/${owner}/${repo}/releases`, {
|
||||
tag_name: tagName,
|
||||
target_commitish: branch,
|
||||
name: tagName,
|
||||
body: releaseLog,
|
||||
draft: debug,
|
||||
})
|
||||
.reply({});
|
||||
|
||||
// Call the post module
|
||||
const result = await post({pkg, options, plugins: {generateNotes: callbackify(generateNotes)}});
|
||||
|
||||
// Verify the getLastRelease plugin has been called with 'options' and 'pkg'
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.deepEqual(generateNotes.firstCall.args[0].options, options);
|
||||
t.deepEqual(generateNotes.firstCall.args[0].pkg, pkg);
|
||||
|
||||
// Verify the published release note
|
||||
t.deepEqual(result, {
|
||||
published: true,
|
||||
release: {owner, repo, tag_name: tagName, name: tagName, target_commitish: branch, draft: debug, body: releaseLog},
|
||||
});
|
||||
// Verify the releases and git/refs endpoint have been call with expected requests
|
||||
t.true(github.isDone());
|
||||
});
|
||||
|
||||
test.serial('Post dry run without github token', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
|
||||
const owner = 'test_user';
|
||||
const repo = 'test_repo';
|
||||
const releaseLog = 'Test release note body';
|
||||
// Stub the generateNotes plugin
|
||||
const generateNotes = stub().resolves(releaseLog);
|
||||
const version = '1.0.0';
|
||||
const branch = 'master';
|
||||
const debug = true;
|
||||
const tagName = `v${version}`;
|
||||
const options = {branch, debug};
|
||||
const pkg = {version, repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
|
||||
|
||||
// Call the post module
|
||||
const result = await post({pkg, options, plugins: {generateNotes: callbackify(generateNotes)}});
|
||||
|
||||
// Verify the getLastRelease plugin has been called with 'options' and 'pkg'
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.deepEqual(generateNotes.firstCall.args[0].options, options);
|
||||
t.deepEqual(generateNotes.firstCall.args[0].pkg, pkg);
|
||||
|
||||
// Verify the release note
|
||||
t.deepEqual(result, {
|
||||
published: false,
|
||||
release: {owner, repo, tag_name: tagName, name: tagName, target_commitish: branch, draft: debug, body: releaseLog},
|
||||
});
|
||||
});
|
||||
@@ -1,163 +0,0 @@
|
||||
import {callbackify} from 'util';
|
||||
import test from 'ava';
|
||||
import {gitRepo, gitCommits} from './helpers/git-utils';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
|
||||
// Stub to capture the log messages
|
||||
const errorLog = stub();
|
||||
// Module to test
|
||||
const pre = proxyquire('../src/pre', {
|
||||
'./lib/get-commits': proxyquire('../src/lib/get-commits', {npmlog: {error: errorLog}}),
|
||||
});
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current working diretory
|
||||
t.context.cwd = process.cwd();
|
||||
// Reset the stub call history
|
||||
errorLog.resetHistory();
|
||||
});
|
||||
|
||||
test.afterEach.always(t => {
|
||||
// Restore the current working directory
|
||||
process.chdir(t.context.cwd);
|
||||
});
|
||||
|
||||
test.serial('Increase version', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const cmts = await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
|
||||
const options = {branch: 'master'};
|
||||
const pkg = {name: 'available'};
|
||||
const lastRelease = {version: '1.0.0', gitHead: cmts[cmts.length - 1].hash};
|
||||
// Stub the getLastRelease, analyzeCommits and verifyRelease plugins
|
||||
const getLastRelease = stub().resolves(lastRelease);
|
||||
const analyzeCommits = stub().resolves('major');
|
||||
const verifyRelease = stub().resolves();
|
||||
|
||||
// Call the pre module
|
||||
const nextRelease = await pre({
|
||||
options,
|
||||
pkg,
|
||||
plugins: {
|
||||
getLastRelease: callbackify(getLastRelease),
|
||||
analyzeCommits: callbackify(analyzeCommits),
|
||||
verifyRelease: verifyRelease,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify the pre module returns the 'type' returned by analyzeCommits and the 'version' returned by getLastRelease increamented with the 'type' (current version 1.0.0 => major release = version 2.0.0)
|
||||
t.deepEqual(nextRelease, {type: 'major', version: '2.0.0'});
|
||||
|
||||
// Verify the getLastRelease plugin has been called with 'options' and 'pkg'
|
||||
t.true(getLastRelease.calledOnce);
|
||||
t.deepEqual(getLastRelease.firstCall.args[0].options, options);
|
||||
t.deepEqual(getLastRelease.firstCall.args[0].pkg, pkg);
|
||||
|
||||
// Verify the analyzeCommits plugin has been called with the repo 'commits' since lastVersion githead
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits.length, 1);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[0].hash.substring(0, 7), cmts[0].hash);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[0].message, cmts[0].message);
|
||||
|
||||
// Verify the verifyRelease plugin has been called with 'lastRelease' and 'nextRelease'
|
||||
t.true(verifyRelease.calledOnce);
|
||||
t.deepEqual(verifyRelease.firstCall.args[0].lastRelease, lastRelease);
|
||||
t.deepEqual(verifyRelease.firstCall.args[0].nextRelease, nextRelease);
|
||||
});
|
||||
|
||||
test.serial('Initial version', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const cmts = await gitCommits(['fix(scope1): First fix', 'feat(scope2): Second feature']);
|
||||
|
||||
const options = {branch: 'master'};
|
||||
const pkg = {name: 'available'};
|
||||
const lastRelease = {version: null, gitHead: undefined};
|
||||
// Stub the getLastRelease, analyzeCommits and verifyRelease plugins
|
||||
const getLastRelease = stub().resolves({version: null, gitHead: undefined});
|
||||
const analyzeCommits = stub().resolves('major');
|
||||
const verifyRelease = stub().resolves();
|
||||
|
||||
// Call the pre module
|
||||
const nextRelease = await pre({
|
||||
options,
|
||||
pkg,
|
||||
plugins: {
|
||||
getLastRelease: callbackify(getLastRelease),
|
||||
analyzeCommits: callbackify(analyzeCommits),
|
||||
verifyRelease: verifyRelease,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify the pre module returns the 'type' returned by analyzeCommits and the 'version' returned by getLastRelease increamented with the 'type' (no current version => initial release = version 1.0.0)
|
||||
t.deepEqual(nextRelease, {type: 'initial', version: '1.0.0'});
|
||||
|
||||
// Verify the getLastRelease plugin has been called with 'options' and 'pkg'
|
||||
t.true(getLastRelease.calledOnce);
|
||||
t.deepEqual(getLastRelease.firstCall.args[0].options, options);
|
||||
t.deepEqual(getLastRelease.firstCall.args[0].pkg, pkg);
|
||||
|
||||
// Verify the analyzeCommits plugin has been called with all the repo 'commits'
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits.length, 2);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[0].hash.substring(0, 7), cmts[0].hash);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[0].message, cmts[0].message);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[1].hash.substring(0, 7), cmts[1].hash);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[1].message, cmts[1].message);
|
||||
|
||||
// Verify the verifyRelease plugin has been called with 'lastRelease' and 'nextRelease'
|
||||
t.true(verifyRelease.calledOnce);
|
||||
t.deepEqual(verifyRelease.firstCall.args[0].lastRelease, lastRelease);
|
||||
t.deepEqual(verifyRelease.firstCall.args[0].nextRelease, nextRelease);
|
||||
});
|
||||
|
||||
test.serial('Throws error if verifyRelease fails', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
const cmts = await gitCommits(['fix: First fix', 'feat: Second feature']);
|
||||
|
||||
const options = {branch: 'master'};
|
||||
const pkg = {name: 'available'};
|
||||
const lastRelease = {version: '1.0.0', gitHead: cmts[cmts.length - 1].hash};
|
||||
// Stub the getLastRelease, analyzeCommits and verifyRelease plugins
|
||||
const getLastRelease = stub().resolves(lastRelease);
|
||||
const analyzeCommits = stub().resolves('major');
|
||||
const verifyRelease = stub().rejects(new Error('verifyRelease failed'));
|
||||
|
||||
// Call the pre module and verify it returns the Error returned by verifyRelease
|
||||
const error = await t.throws(
|
||||
pre({
|
||||
options,
|
||||
pkg,
|
||||
plugins: {
|
||||
getLastRelease: callbackify(getLastRelease),
|
||||
analyzeCommits: callbackify(analyzeCommits),
|
||||
verifyRelease: verifyRelease,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Verify the error message is the one returned by verifyRelease
|
||||
t.is(error.message, 'verifyRelease failed');
|
||||
|
||||
// Verify the getLastRelease plugin has been called with 'options' and 'pkg'
|
||||
t.true(getLastRelease.calledOnce);
|
||||
t.deepEqual(getLastRelease.firstCall.args[0].options, options);
|
||||
t.deepEqual(getLastRelease.firstCall.args[0].pkg, pkg);
|
||||
|
||||
// Verify the analyzeCommits plugin has been called with all the repo 'commits'
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits.length, 1);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[0].hash.substring(0, 7), cmts[0].hash);
|
||||
t.is(analyzeCommits.firstCall.args[0].commits[0].message, cmts[0].message);
|
||||
|
||||
// Verify the verifyRelease plugin has been called with 'lastRelease' and 'nextRelease'
|
||||
t.true(verifyRelease.calledOnce);
|
||||
t.deepEqual(verifyRelease.firstCall.args[0].lastRelease, lastRelease);
|
||||
t.deepEqual(verifyRelease.firstCall.args[0].nextRelease, {type: 'major', version: '2.0.0'});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import test from 'ava';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import verify from '../src/lib/verify-auth';
|
||||
|
||||
test('Verify npm and github auth', t => {
|
||||
// Call the verify module with options and env
|
||||
t.notThrows(() => verify({githubToken: 'sup'}, {NPM_TOKEN: 'yo'}));
|
||||
});
|
||||
|
||||
test('Verify npm (old token and mail) and github auth', t => {
|
||||
// Call the verify module with options and env
|
||||
t.notThrows(() => verify({githubToken: 'sup'}, {NPM_OLD_TOKEN: 'yo', NPM_EMAIL: 'test@email.com'}));
|
||||
});
|
||||
|
||||
test('Return error for missing github token', t => {
|
||||
// Call the verify module with options and env
|
||||
const error = t.throws(() => verify({}, {NPM_TOKEN: 'yo'}));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOGHTOKEN');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
|
||||
test('Return error for missing npm token', t => {
|
||||
// Call the verify module with options and env
|
||||
const error = t.throws(() => verify({githubToken: 'sup'}, {}));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENONPMTOKEN');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
|
||||
test('Return error for missing old npm token', t => {
|
||||
// Call the verify module with options and env
|
||||
const error = t.throws(() => verify({githubToken: 'sup'}, {NPM_EMAIL: 'test@email.com'}));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENONPMTOKEN');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
|
||||
test('Return error for missing npm email', t => {
|
||||
// Call the verify module with options and env
|
||||
const error = t.throws(() => verify({githubToken: 'sup'}, {NPM_OLD_TOKEN: 'yo'}));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENONPMTOKEN');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import test from 'ava';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import verify from '../src/lib/verify-pkg';
|
||||
|
||||
test.only('Verify name and repository', t => {
|
||||
// Call the verify module with package
|
||||
t.notThrows(() => verify({name: 'package', repository: {url: 'http://github.com/whats/up.git'}}));
|
||||
});
|
||||
|
||||
test.only('Return error for missing package name', t => {
|
||||
// Call the verify module with package
|
||||
const error = t.throws(() => verify({repository: {url: 'http://github.com/whats/up.git'}}));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOPKGNAME');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
|
||||
test.only('Return error for missing repository', t => {
|
||||
// Call the verify module with package
|
||||
const error = t.throws(() => verify({name: 'package'}));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOPKGREPO');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
|
||||
test.only('Return error for missing repository url', t => {
|
||||
// Call the verify module with package
|
||||
const error = t.throws(() => verify({name: 'package', repository: {}}));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOPKGREPO');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
import test from 'ava';
|
||||
import verify from '../src/lib/verify';
|
||||
|
||||
test('Dry run - Verify pkg, options and env', t => {
|
||||
// Call the verify module with debug (Dry run), package name and repo URL
|
||||
const errors = verify({
|
||||
options: {debug: true},
|
||||
pkg: {name: 'package', repository: {url: 'http://github.com/whats/up.git'}},
|
||||
});
|
||||
|
||||
// Verify no error has been returned
|
||||
t.is(errors.length, 0);
|
||||
});
|
||||
|
||||
test('Dry run - Returns errors for missing package name and repo', t => {
|
||||
// Call the verify module with debug (Dry run), no package name and no repo URL
|
||||
const errors = verify({options: {debug: true}, pkg: {}});
|
||||
|
||||
// Verify the module return an error for each missing configuration
|
||||
t.is(errors.length, 2);
|
||||
t.is(errors[0].code, 'ENOPKGNAME');
|
||||
t.is(errors[1].code, 'ENOPKGREPO');
|
||||
});
|
||||
|
||||
test('Dry run - Verify pkg, options and env for gitlab repo', t => {
|
||||
// Call the verify module with debug (Dry run), no package name and no repo URL
|
||||
const errors = verify({
|
||||
options: {debug: true},
|
||||
pkg: {name: 'package', repository: {url: 'http://gitlab.corp.com/whats/up.git'}},
|
||||
});
|
||||
|
||||
// Verify no error has been returned
|
||||
t.is(errors.length, 0);
|
||||
});
|
||||
|
||||
test('Publish - Verify pkg, options and env', t => {
|
||||
// Call the verify module with package name, repo URL, npm token and github token
|
||||
const errors = verify({
|
||||
env: {NPM_TOKEN: 'yo'},
|
||||
options: {githubToken: 'sup'},
|
||||
pkg: {name: 'package', repository: {url: 'http://github.com/whats/up.git'}},
|
||||
});
|
||||
|
||||
// Verify no error has been returned
|
||||
t.is(errors.length, 0);
|
||||
});
|
||||
|
||||
test('Publish - Returns errors for missing package name, repo github token and npm token', t => {
|
||||
// Call the verify module with no package name, no repo URL, no NPM token and no github token
|
||||
const errors = verify({env: {}, options: {}, pkg: {}});
|
||||
|
||||
// Verify the module return an error for each missing configuration
|
||||
t.is(errors.length, 4);
|
||||
t.is(errors[0].code, 'ENOPKGNAME');
|
||||
t.is(errors[1].code, 'ENOPKGREPO');
|
||||
t.is(errors[2].code, 'ENOGHTOKEN');
|
||||
t.is(errors[3].code, 'ENONPMTOKEN');
|
||||
});
|
||||
|
||||
test('Publish - Returns errors for missing email when using legacy npm token', t => {
|
||||
// Call the verify module with package name, repo URL, old NPM token and github token and no npm email
|
||||
const errors = verify({
|
||||
env: {NPM_OLD_TOKEN: 'yo'},
|
||||
options: {githubToken: 'sup'},
|
||||
pkg: {name: 'package', repository: {url: 'http://github.com/whats/up.git'}},
|
||||
});
|
||||
|
||||
// Verify the module return an error for each missing configuration
|
||||
t.is(errors.length, 1);
|
||||
t.is(errors[0].code, 'ENONPMTOKEN');
|
||||
});
|
||||
Reference in New Issue
Block a user