Compare commits

...
14 Commits
Author SHA1 Message Date
Pierre Vanduynslager 857d4180e9 fix: allow boolean option to be set in config file 2018-02-16 00:51:58 -05:00
Pierre Vanduynslager 4d0490122c style: lint 2018-02-15 23:19:57 -05:00
Pierre Vanduynslager aa724e80f7 chore(package): simplify xo configuration 2018-02-15 23:19:57 -05:00
greenkeeper[bot]andPierre Vanduynslager ce15b75edf chore(package): update xo to version 0.20.0 2018-02-15 23:19:57 -05:00
Pierre VanduynslagerandGregor Martynus 1966f0e3e2 fix: verify branch first 2018-02-15 18:35:37 +00:00
Pierre Vanduynslager 305f4ee8eb fix: do not transform repositoryUrl if it allow to push
Even the user set Git credentials via environment variable, use the configured URL (with authentication) if it works.
This allow users to push tags and commits via ssh while still using the GitHub/GitLab API.
2018-02-14 10:45:44 -05:00
Pierre Vanduynslager 9788fcad4e feat: use @semantic-release/github as default for success and fail hooks
BREAKING CHANGE: `success` and `fail` hooks are now enabled by default

In order to disable the `@semantic-release/github` plugin for the `success` and `fail` hook, the corresponding options have to be set to `false` in the **semantic-release** configuration:

```json
{
  "release": {
    "success": false,
    "fail": false
  }
}
```

Users who do not use the `@semantic-release/github` plugin, should disable it in the `success` and `fail` by setting the corresponding options to `false` or to alternative plugin providing `success` and `fail` hooks.
2018-02-13 16:33:37 -05:00
Pierre Vanduynslager 04f3061bed fix: remove the github plugin from default success and fail hooks 2018-02-13 16:06:30 -05:00
Pierre Vanduynslager 1b3c51d624 docs: add missing success and fail options 2018-02-13 16:06:30 -05:00
Pierre Vanduynslager 8a2ef48308 test: revert to mockserver:latest docker container 2018-02-12 21:40:47 -05:00
Pierre Vanduynslager ce1e74f611 fix: set repository authentication when repositoryUrl is set as an option 2018-02-12 16:17:09 -05:00
Pierre Vanduynslager b6837a20a8 test: use older version of mockserver
Due to https://github.com/jamesdbloom/mockserver/issues/435
2018-02-12 16:17:09 -05:00
Pierre VanduynslagerandGregor Martynus 97cb354fea feat: improve CLI
- Replace `commander.js` with `yargs`
- Add CLI unit tests
- Add a `--version` option
- Improve `--help` output
- Remove `commander.js` related workaround
- Allow to set list option with arg repetition or space separated list
- Maintain the list options defined as comma separated list
2018-02-12 11:01:09 -08:00
Pierre VanduynslagerandGregor Martynus f92677b092 fix: log current version of semantic-release 2018-02-12 11:01:09 -08:00
13 changed files with 386 additions and 157 deletions
+54 -53
View File
@@ -1,64 +1,65 @@
const program = require('commander');
const {pickBy, isUndefined} = require('lodash');
function list(values) {
return values
.split(',')
.map(value => value.trim())
.filter(value => value && value !== 'false');
}
const stringList = {
type: 'string',
array: true,
coerce: values =>
values.length === 1 && values[0].trim() === 'false'
? []
: values.reduce((values, value) => values.concat(value.split(',').map(value => value.trim())), []),
};
module.exports = async () => {
program
.name('semantic-release')
.description('Run automated package publishing')
.option('-b, --branch <branch>', 'Branch to release from')
.option('-r, --repository-url <repositoryUrl>', 'Git repository URL')
.option('-t, --tag-format <tagFormat>', `Git tag format`)
.option('-e, --extends <paths>', 'Comma separated list of shareable config paths or packages name', list)
.option(
'--verify-conditions <paths>',
'Comma separated list of paths or packages name for the verifyConditions plugin(s)',
list
)
.option('--analyze-commits <path>', 'Path or package name for the analyzeCommits plugin')
.option(
'--verify-release <paths>',
'Comma separated list of paths or packages name for the verifyRelease plugin(s)',
list
)
.option('--generate-notes <path>', 'Path or package name for the generateNotes plugin')
.option('--publish <paths>', 'Comma separated list of paths or packages name for the publish plugin(s)', list)
.option('--success <paths>', 'Comma separated list of paths or packages name for the success plugin(s)', list)
.option('--fail <paths>', 'Comma separated list of paths or packages name for the fail plugin(s)', list)
.option(
'--no-ci',
'Skip Continuous Integration environment verifications, allowing to make releases from a local machine'
)
.option('--debug', 'Output debugging information')
.option(
'-d, --dry-run',
'Dry-run mode, skipping verifyConditions, publishing and release, printing next version and release notes'
)
.parse(process.argv);
if (program.debug) {
// Debug must be enabled before other requires in order to work
require('debug').enable('semantic-release:*');
}
const cli = require('yargs')
.command('$0', 'Run automated package publishing', yargs => {
yargs.demandCommand(0, 0).usage(`Run automated package publishing
Usage:
semantic-release [options] [plugins]`);
})
.option('b', {alias: 'branch', describe: 'Git branch to release from', type: 'string', group: 'Options'})
.option('r', {alias: 'repository-url', describe: 'Git repository URL', type: 'string', group: 'Options'})
.option('t', {alias: 'tag-format', describe: 'Git tag format', type: 'string', group: 'Options'})
.option('e', {alias: 'extends', describe: 'Shareable configurations', ...stringList, group: 'Options'})
.option('ci', {describe: 'Toggle CI verifications', default: undefined, type: 'boolean', group: 'Options'})
.option('verify-conditions', {...stringList, group: 'Plugins'})
.option('analyze-commits', {type: 'string', group: 'Plugins'})
.option('verify-release', {...stringList, group: 'Plugins'})
.option('generate-notes', {type: 'string', group: 'Plugins'})
.option('publish', {...stringList, group: 'Plugins'})
.option('success', {...stringList, group: 'Plugins'})
.option('fail', {...stringList, group: 'Plugins'})
.option('debug', {describe: 'Output debugging information', default: undefined, type: 'boolean', group: 'Options'})
.option('d', {alias: 'dry-run', describe: 'Skip publishing', default: undefined, type: 'boolean', group: 'Options'})
.option('h', {alias: 'help', default: undefined, group: 'Options'})
.option('v', {alias: 'version', default: undefined, group: 'Options'})
.strict(false)
.exitProcess(false);
try {
if (program.args.length > 0) {
program.outputHelp();
process.exitCode = 1;
} else {
const opts = program.opts();
// Set the `noCi` options as commander.js sets the `ci` options instead (because args starts with `--no`)
opts.noCi = opts.ci === false ? true : undefined;
// Remove option with undefined values, as commander.js sets non defined options as `undefined`
await require('.')(pickBy(opts, value => !isUndefined(value)));
// Remove option with undefined values, as yargs sets non defined options as `undefined`
const {help, version, ...opts} = pickBy(cli.argv, value => !isUndefined(value));
if (Boolean(help) || Boolean(version)) {
process.exitCode = 0;
return;
}
// Set the `noCi` options as yargs sets the `ci` options instead (because arg starts with `--no`)
if (opts.ci === false) {
opts.noCi = true;
}
if (opts.debug) {
// Debug must be enabled before other requires in order to work
require('debug').enable('semantic-release:*');
}
await require('.')(opts);
process.exitCode = 0;
} catch (err) {
if (err.name !== 'YError') {
console.error(err);
}
process.exitCode = 1;
}
};
+24
View File
@@ -166,3 +166,27 @@ CLI argument: `--publish`
Define the list of [publish plugins](plugins.md#publish-plugin). Plugins will run in series, in the order defined in the `Array`.
See [Plugins configuration](plugins.md#configuration) for more details.
### success
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/github']`
CLI argument: `--success`
Define the list of [success plugins](plugins.md#success-plugin). Plugins will run in series, in the order defined in the `Array`.
See [Plugins configuration](plugins.md#configuration) for more details.
### fail
Type: `Array`, `String`, `Object`
Default: `['@semantic-release/github']`
CLI argument: `--fail`
Define the list of [fail plugins](plugins.md#fail-plugin). Plugins will run in series, in the order defined in the `Array`.
See [Plugins configuration](plugins.md#configuration) for more details.
+11 -2
View File
@@ -3,6 +3,7 @@ const marked = require('marked');
const TerminalRenderer = require('marked-terminal');
const envCi = require('env-ci');
const hookStd = require('hook-std');
const pkg = require('./package.json');
const hideSensitive = require('./lib/hide-sensitive');
const getConfig = require('./lib/get-config');
const verify = require('./lib/verify');
@@ -28,10 +29,17 @@ async function run(options, plugins) {
return;
}
if (!await verify(options, branch, logger)) {
return;
if (branch !== options.branch) {
logger.log(
`This test run was triggered on the branch ${branch}, while semantic-release is configured to only publish from ${
options.branch
}, therefore a new version won’t be published.`
);
return false;
}
await verify(options);
logger.log('Run automated release from branch %s', options.branch);
logger.log('Call plugin %s', 'verify-conditions');
@@ -140,6 +148,7 @@ async function callFail(plugins, options, error) {
}
module.exports = async opts => {
logger.log(`Running %s version %s`, pkg.name, pkg.version);
const unhook = hookStd({silent: false}, hideSensitive);
try {
const config = await getConfig(opts, logger);
+6 -4
View File
@@ -3,7 +3,7 @@ const readPkgUp = require('read-pkg-up');
const cosmiconfig = require('cosmiconfig');
const resolveFrom = require('resolve-from');
const debug = require('debug')('semantic-release:config');
const {repoUrl} = require('./git');
const {repoUrl, verifyAuth} = require('./git');
const PLUGINS_DEFINITIONS = require('./definitions/plugins');
const plugins = require('./plugins');
const getGitAuthUrl = require('./get-git-auth-url');
@@ -42,17 +42,19 @@ module.exports = async (opts, logger) => {
};
}
const repositoryUrl = (await pkgRepoUrl()) || (await repoUrl());
// Set default options values if not defined yet
options = {
branch: 'master',
repositoryUrl: repositoryUrl ? getGitAuthUrl(repositoryUrl) : repositoryUrl,
repositoryUrl: (await pkgRepoUrl()) || (await repoUrl()),
tagFormat: `v\${version}`,
// Remove `null` and `undefined` options so they can be replaced with default ones
...pickBy(options, option => !isUndefined(option) && !isNull(option)),
};
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
options.repositoryUrl = options.repositoryUrl ? getGitAuthUrl(options.repositoryUrl) : options.repositoryUrl;
}
debug('options values: %O', options);
return {options, plugins: await plugins(options, pluginsPath, logger)};
+1 -12
View File
@@ -3,7 +3,7 @@ const AggregateError = require('aggregate-error');
const {isGitRepo, verifyAuth, verifyTagName} = require('./git');
const getError = require('./get-error');
module.exports = async (options, branch, logger) => {
module.exports = async options => {
const errors = [];
if (!await isGitRepo()) {
@@ -29,15 +29,4 @@ module.exports = async (options, branch, logger) => {
if (errors.length > 0) {
throw new AggregateError(errors);
}
if (branch !== options.branch) {
logger.log(
`This test run was triggered on the branch ${branch}, while semantic-release is configured to only publish from ${
options.branch
}, therefore a new version won’t be published.`
);
return false;
}
return true;
};
+6 -18
View File
@@ -42,7 +42,8 @@
"p-reduce": "^1.0.0",
"read-pkg-up": "^3.0.0",
"resolve-from": "^4.0.0",
"semver": "^5.4.1"
"semver": "^5.4.1",
"yargs": "^11.0.0"
},
"devDependencies": {
"ava": "^0.25.0",
@@ -52,8 +53,6 @@
"cz-conventional-changelog": "^2.0.0",
"delay": "^2.0.0",
"dockerode": "^2.5.2",
"eslint-config-prettier": "^2.5.0",
"eslint-plugin-prettier": "^2.3.0",
"file-url": "^2.0.2",
"fs-extra": "^5.0.0",
"got": "^8.0.0",
@@ -62,11 +61,10 @@
"nock": "^9.0.2",
"nyc": "^11.2.1",
"p-retry": "^1.0.0",
"prettier": "~1.10.0",
"proxyquire": "^1.8.0",
"sinon": "^4.0.0",
"tempy": "^0.2.1",
"xo": "^0.18.2"
"xo": "^0.20.0"
},
"engines": {
"node": ">=8.3"
@@ -106,10 +104,7 @@
"all": true
},
"prettier": {
"printWidth": 120,
"singleQuote": true,
"bracketSpacing": false,
"trailingComma": "es5"
"printWidth": 120
},
"publishConfig": {
"tag": "next"
@@ -130,14 +125,7 @@
"test": "nyc ava -v"
},
"xo": {
"extends": [
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": 2
}
"prettier": true,
"space": true
}
}
+233
View File
@@ -0,0 +1,233 @@
import test from 'ava';
import proxyquire from 'proxyquire';
import clearModule from 'clear-module';
import {stub} from 'sinon';
// Save the current process.env and process.argv
const envBackup = Object.assign({}, process.env);
const argvBackup = Object.assign({}, process.argv);
test.beforeEach(t => {
clearModule('yargs');
t.context.logs = '';
t.context.errors = '';
t.context.stdout = stub(process.stdout, 'write').callsFake(val => {
t.context.logs += val.toString();
});
t.context.stderr = stub(process.stderr, 'write').callsFake(val => {
t.context.errors += val.toString();
});
});
test.afterEach.always(t => {
process.env = envBackup;
process.argv = argvBackup;
t.context.stdout.restore();
t.context.stderr.restore();
delete process.exitCode;
});
test.serial('Pass options to semantic-release API', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = [
'',
'',
'-b',
'master',
'-r',
'https://github/com/owner/repo.git',
'-t',
`v\${version}`,
'-e',
'config1',
'config2',
'--verify-conditions',
'condition1',
'condition2',
'--analyze-commits',
'analyze',
'--verify-release',
'verify1',
'verify2',
'--generate-notes',
'notes',
'--publish',
'publish1',
'publish2',
'--success',
'success1',
'success2',
'--fail',
'fail1',
'fail2',
'--debug',
'-d',
];
await cli();
t.is(run.args[0][0].branch, 'master');
t.is(run.args[0][0].repositoryUrl, 'https://github/com/owner/repo.git');
t.is(run.args[0][0].tagFormat, `v\${version}`);
t.deepEqual(run.args[0][0].extends, ['config1', 'config2']);
t.deepEqual(run.args[0][0].verifyConditions, ['condition1', 'condition2']);
t.is(run.args[0][0].analyzeCommits, 'analyze');
t.deepEqual(run.args[0][0].verifyRelease, ['verify1', 'verify2']);
t.is(run.args[0][0].generateNotes, 'notes');
t.deepEqual(run.args[0][0].publish, ['publish1', 'publish2']);
t.deepEqual(run.args[0][0].success, ['success1', 'success2']);
t.deepEqual(run.args[0][0].fail, ['fail1', 'fail2']);
t.is(run.args[0][0].debug, true);
t.is(run.args[0][0].dryRun, true);
t.is(process.exitCode, 0);
});
test.serial('Pass options to semantic-release API with alias arguments', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = [
'',
'',
'--branch',
'master',
'--repository-url',
'https://github/com/owner/repo.git',
'--tag-format',
`v\${version}`,
'--extends',
'config1',
'config2',
'--dry-run',
];
await cli();
t.is(run.args[0][0].branch, 'master');
t.is(run.args[0][0].repositoryUrl, 'https://github/com/owner/repo.git');
t.is(run.args[0][0].tagFormat, `v\${version}`);
t.deepEqual(run.args[0][0].extends, ['config1', 'config2']);
t.is(run.args[0][0].dryRun, true);
t.is(process.exitCode, 0);
});
test.serial('Pass unknown options to semantic-release API', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = [
'',
'',
'--bool',
'--first-option',
'value1',
'--second-option',
'value2',
'--second-option',
'value3',
];
await cli();
t.is(run.args[0][0].bool, true);
t.is(run.args[0][0].firstOption, 'value1');
t.deepEqual(run.args[0][0].secondOption, ['value2', 'value3']);
t.is(process.exitCode, 0);
});
test.serial('Pass empty Array to semantic-release API for list option set to "false"', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = ['', '', '--publish', 'false'];
await cli();
t.deepEqual(run.args[0][0].publish, []);
t.is(process.exitCode, 0);
});
test.serial('Do not set properties in option for which arg is not in command line', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = ['', '', '-b', 'master'];
await cli();
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'ci'));
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'd'));
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'dry-run'));
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'debug'));
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 'r'));
t.false(Object.prototype.hasOwnProperty.call(run.args[0][0], 't'));
});
test.serial('Set "noCi" options to "true" with "--no-ci"', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = ['', '', '--no-ci'];
await cli();
t.is(run.args[0][0].noCi, true);
t.is(process.exitCode, 0);
});
test.serial('Display help', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = ['', '', '--help'];
await cli();
t.regex(t.context.logs, /Run automated package publishing/);
t.is(process.exitCode, 0);
});
test.serial('Returns error code and prints help if called with a command', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = ['', '', 'pre'];
await cli();
t.regex(t.context.errors, /Run automated package publishing/);
t.regex(t.context.errors, /Too many non-option arguments/);
t.is(process.exitCode, 1);
});
test.serial('Return error code if multiple plugin are set for single plugin', async t => {
const run = stub().resolves(true);
const cli = proxyquire('../cli', {'.': run});
process.argv = ['', '', '--analyze-commits', 'analyze1', 'analyze2'];
await cli();
t.regex(t.context.errors, /Run automated package publishing/);
t.regex(t.context.errors, /Too many non-option arguments/);
t.is(process.exitCode, 1);
});
test.serial('Return error code if semantic-release throw error', async t => {
const run = stub().rejects(new Error('semantic-release error'));
const cli = proxyquire('../cli', {'.': run});
process.argv = ['', ''];
await cli();
t.regex(t.context.errors, /semantic-release error/);
t.is(process.exitCode, 1);
});
+21 -5
View File
@@ -31,7 +31,8 @@ test.afterEach.always(() => {
});
test.serial('Default values, reading repositoryUrl from package.json', async t => {
const pkg = {repository: 'git@package.com:owner/module.git'};
process.env.GIT_CREDENTIALS = 'user:pass';
const pkg = {repository: 'https://package.com/owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
await gitCommits(['First']);
@@ -44,25 +45,27 @@ test.serial('Default values, reading repositoryUrl from package.json', async t =
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'git@package.com:owner/module.git');
t.is(options.repositoryUrl, 'https://user:pass@package.com/owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add remote.origin.url config
await gitAddConfig('remote.origin.url', 'git@repo.com:owner/module.git');
await gitAddConfig('remote.origin.url', 'https://hostname.com/owner/module.git');
const {options} = await t.context.getConfig();
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'git@repo.com:owner/module.git');
t.is(options.repositoryUrl, 'https://user:pass@hostname.com/owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
const pkg = {repository: 'https://hostname.com/owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
@@ -73,10 +76,23 @@ test.serial('Default values, reading repositoryUrl (http url) from package.json
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, pkg.repository);
t.is(options.repositoryUrl, 'https://user:pass@hostname.com/owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Do not add git credential to repositoryUrl if push is allowed', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
// Create a git repository, set the current working directory at the root of the repo
const repositoryUrl = await gitRepo(true);
const pkg = {repository: repositoryUrl};
// Create package.json in repository root
await outputJson('./package.json', pkg);
const {options} = await t.context.getConfig();
t.is(options.repositoryUrl, repositoryUrl);
});
test.serial('Read options from package.json', async t => {
const release = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
+13 -13
View File
@@ -302,9 +302,9 @@ test.serial('Log all "verifyConditions" errors', async t => {
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
const errors = await t.throws(semanticRelease(options));
const errors = [...(await t.throws(semanticRelease(options)))];
t.deepEqual(Array.from(errors), [error1, error2, error3]);
t.deepEqual(errors, [error1, error2, error3]);
t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 2', 'ERR2']);
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s error 3', 'ERR3']);
t.deepEqual(t.context.error.args[t.context.error.args.length - 1], [
@@ -345,9 +345,9 @@ test.serial('Log all "verifyRelease" errors', async t => {
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
const errors = await t.throws(semanticRelease(options));
const errors = [...(await t.throws(semanticRelease(options)))];
t.deepEqual(Array.from(errors), [error1, error2]);
t.deepEqual(errors, [error1, error2]);
t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 1', 'ERR1']);
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s error 2', 'ERR2']);
t.is(fail.callCount, 1);
@@ -428,9 +428,9 @@ test.serial('Dry-run skips fail', async t => {
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
const errors = await t.throws(semanticRelease(options));
const errors = [...(await t.throws(semanticRelease(options)))];
t.deepEqual(Array.from(errors), [error1, error2]);
t.deepEqual(errors, [error1, error2]);
t.deepEqual(t.context.log.args[t.context.log.args.length - 2], ['%s error 1', 'ERR1']);
t.deepEqual(t.context.log.args[t.context.log.args.length - 1], ['%s error 2', 'ERR2']);
t.is(fail.callCount, 0);
@@ -475,7 +475,7 @@ test.serial('Force a dry-run if not on a CI and "noCi" is not explicitly set', a
});
t.truthy(await semanticRelease(options));
t.is(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
t.is(t.context.log.args[1][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
t.is(verifyConditions.callCount, 1);
t.is(analyzeCommits.callCount, 1);
t.is(verifyRelease.callCount, 1);
@@ -596,7 +596,7 @@ test.serial('Returns falsy value if triggered by a PR', async t => {
t.falsy(await semanticRelease({repositoryUrl}));
t.is(
t.context.log.args[8][0],
t.context.log.args[t.context.log.args.length - 1][0],
"This run was triggered by a pull request and therefore a new version won't be published."
);
});
@@ -623,7 +623,7 @@ test.serial('Returns falsy value if not running from the configured branch', asy
t.falsy(await semanticRelease(options));
t.is(
t.context.log.args[0][0],
t.context.log.args[1][0],
'This test run was triggered on the branch other-branch, while semantic-release is configured to only publish from master, therefore a new version won’t be published.'
);
});
@@ -661,7 +661,7 @@ test.serial('Returns falsy value if there is no relevant changes', async t => {
t.is(verifyRelease.callCount, 0);
t.is(generateNotes.callCount, 0);
t.is(publish.callCount, 0);
t.is(t.context.log.args[6][0], 'There are no relevant changes, so no new version is released.');
t.is(t.context.log.args[7][0], 'There are no relevant changes, so no new version is released.');
});
test.serial('Exclude commits with [skip release] or [release skip] from analysis', async t => {
@@ -723,8 +723,8 @@ test.serial('Hide sensitive environment variable values from the logs', async t
await t.throws(semanticRelease(options));
t.regex(t.context.stdout.args[8][0], /Console: The token \[secure\] is invalid/);
t.regex(t.context.stdout.args[9][0], /Log: The token \[secure\] is invalid/);
t.regex(t.context.stdout.args[t.context.stdout.args.length - 2][0], /Console: The token \[secure\] is invalid/);
t.regex(t.context.stdout.args[t.context.stdout.args.length - 1][0], /Log: The token \[secure\] is invalid/);
t.regex(t.context.stderr.args[0][0], /Error: The token \[secure\] is invalid/);
t.regex(t.context.stderr.args[1][0], /Invalid token \[secure\]/);
});
@@ -785,7 +785,7 @@ test.serial('Throw SemanticReleaseError if repositoryUrl is not set and cannot b
'./lib/logger': t.context.logger,
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
});
const errors = Array.from(await t.throws(semanticRelease()));
const errors = [...(await t.throws(semanticRelease()))];
// Verify error code and type
t.is(errors[0].code, 'ENOREPOURL');
+2 -23
View File
@@ -603,11 +603,11 @@ test.serial('Exit with 1 if missing permission to push to the remote repository'
// Create a git repository, set the current working directory at the root of the repo
t.log('Create git repository');
const {repositoryUrl} = await gitbox.createRepo(packageName);
await gitbox.createRepo(packageName);
await writeJson('./package.json', {
name: packageName,
version: '0.0.0-dev',
repository: {url: repositoryUrl},
repository: {url: 'http://user:wrong_pass@localhost:2080/git/unauthorized.git'},
});
/* Initial release */
@@ -619,24 +619,3 @@ test.serial('Exit with 1 if missing permission to push to the remote repository'
t.regex(stdout, /EGITNOPERMISSION/);
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');
const {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');
const {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');
const {stderr, code} = await execa(cli, ['--unknown-option'], {env, reject: false});
t.regex(stderr, /unknown option/);
t.is(code, 1);
});
+4 -4
View File
@@ -118,7 +118,7 @@ test('Throw all errors from the first step throwing an AggregateError', async t
const errors = await t.throws(pipeline([step1, step2, step3])(0));
t.deepEqual(Array.from(errors), [error1, error2]);
t.deepEqual([...errors], [error1, error2]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
t.true(step3.notCalled);
@@ -133,7 +133,7 @@ test('Execute all even if a Promise rejects', async t => {
const errors = await t.throws(pipeline([step1, step2, step3])(0, {settleAll: true}));
t.deepEqual(Array.from(errors), [error1, error2]);
t.deepEqual([...errors], [error1, error2]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
t.true(step3.calledWith(0));
@@ -149,7 +149,7 @@ test('Throw all errors from all steps throwing an AggregateError', async t => {
const errors = await t.throws(pipeline([step1, step2])(0, {settleAll: true}));
t.deepEqual(Array.from(errors), [error1, error2, error3, error4]);
t.deepEqual([...errors], [error1, error2, error3, error4]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(0));
});
@@ -165,7 +165,7 @@ test('Execute each function in series passing a transformed input even if a step
const errors = await t.throws(pipeline([step1, step2, step3, step4])(0, {settleAll: true, getNextInput}));
t.deepEqual(Array.from(errors), [error2, error3]);
t.deepEqual([...errors], [error2, error3]);
t.true(step1.calledWith(0));
t.true(step2.calledWith(0 + 1));
t.true(step3.calledWith(0 + 1 + error2));
+4 -4
View File
@@ -136,16 +136,16 @@ test('Merge global options with plugin options', async t => {
});
test('Throw an error if plugins configuration are missing a path for plugin pipeline', t => {
const errors = Array.from(t.throws(() => getPlugins({verifyConditions: {}}, {}, t.context.logger)));
const errors = [...t.throws(() => getPlugins({verifyConditions: {}}, {}, t.context.logger))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
});
test('Throw an error if an array of plugin configuration is missing a path for plugin pipeline', t => {
const errors = Array.from(
t.throws(() => getPlugins({verifyConditions: [{path: '@semantic-release/npm'}, {}]}, {}, t.context.logger))
);
const errors = [
...t.throws(() => getPlugins({verifyConditions: [{path: '@semantic-release/npm'}, {}]}, {}, t.context.logger)),
];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EPLUGINCONF');
+7 -19
View File
@@ -1,5 +1,4 @@
import test from 'ava';
import {stub} from 'sinon';
import tempy from 'tempy';
import verify from '../lib/verify';
import {gitRepo} from './helpers/git-utils';
@@ -9,17 +8,13 @@ const envBackup = Object.assign({}, process.env);
// Save the current working diretory
const cwd = process.cwd();
test.beforeEach(t => {
test.beforeEach(() => {
// Delete environment variables that could have been set on the machine running the tests
delete process.env.GIT_CREDENTIALS;
delete process.env.GH_TOKEN;
delete process.env.GITHUB_TOKEN;
delete process.env.GL_TOKEN;
delete process.env.GITLAB_TOKEN;
// Stub the logger functions
t.context.log = stub();
t.context.error = stub();
t.context.logger = {log: t.context.log, error: t.context.error};
});
test.afterEach.always(() => {
@@ -32,7 +27,7 @@ test.afterEach.always(() => {
test.serial('Throw a AggregateError', async t => {
await gitRepo();
const errors = Array.from(await t.throws(verify({}, 'master', t.context.logger)));
const errors = [...(await t.throws(verify({})))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'ENOREPOURL');
@@ -46,7 +41,7 @@ test.serial('Throw a SemanticReleaseError if does not run on a git repository',
const dir = tempy.directory();
process.chdir(dir);
const errors = Array.from(await t.throws(verify({}, 'master', t.context.logger)));
const errors = [...(await t.throws(verify({})))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'ENOGITREPO');
@@ -56,7 +51,7 @@ test.serial('Throw a SemanticReleaseError if the "tagFormat" is not valid', asyn
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `?\${version}`};
const errors = Array.from(await t.throws(verify(options, 'master', t.context.logger)));
const errors = [...(await t.throws(verify(options, 'master', t.context.logger)))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'EINVALIDTAGFORMAT');
@@ -66,7 +61,7 @@ test.serial('Throw a SemanticReleaseError if the "tagFormat" does not contains t
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: 'test'};
const errors = Array.from(await t.throws(verify(options, 'master', t.context.logger)));
const errors = [...(await t.throws(verify(options, 'master', t.context.logger)))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'ETAGNOVERSION');
@@ -76,22 +71,15 @@ test.serial('Throw a SemanticReleaseError if the "tagFormat" contains multiple "
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `\${version}v\${version}`};
const errors = Array.from(await t.throws(verify(options, 'master', t.context.logger)));
const errors = [...(await t.throws(verify(options)))];
t.is(errors[0].name, 'SemanticReleaseError');
t.is(errors[0].code, 'ETAGNOVERSION');
});
test.serial('Return "false" if the current branch is not the once configured', async t => {
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `v\${version}`, branch: 'master'};
t.false(await verify(options, 'other', t.context.logger));
});
test.serial('Return "true" if all verification pass', async t => {
const repositoryUrl = await gitRepo(true);
const options = {repositoryUrl, tagFormat: `v\${version}`, branch: 'master'};
t.true(await verify(options, 'master', t.context.logger));
await t.notThrows(verify(options));
});