Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eec4e68615 | ||
|
|
8371a03da0 | ||
|
|
2491032a99 | ||
|
|
02ddf34f21 | ||
|
|
685959107b | ||
|
|
613a646363 | ||
|
|
d7b323dd00 | ||
|
|
a7359bffb6 | ||
|
|
fcb832bbe1 | ||
|
|
89f5e07408 | ||
|
|
ce3841f553 | ||
|
|
40c58c9b42 | ||
|
|
0c67ba517f | ||
|
|
5bec59b26b | ||
|
|
d4c7605f68 | ||
|
|
facdadaddb |
@@ -129,7 +129,3 @@ $RECYCLE.BIN/
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Registry tests
|
||||
test/helpers/registry/couch
|
||||
test/helpers/registry/data
|
||||
|
||||
+3
-1
@@ -1,11 +1,13 @@
|
||||
language: node_js
|
||||
services:
|
||||
- couchdb
|
||||
- docker
|
||||
notifications:
|
||||
email: false
|
||||
node_js:
|
||||
- 9
|
||||
- 8
|
||||
os:
|
||||
- linux
|
||||
|
||||
# Trigger a push build on caribou and greenkeeper branches + PRs build on every branches
|
||||
# Avoid double build on PRs (See https://github.com/travis-ci/travis-ci/issues/1147)
|
||||
|
||||
@@ -167,6 +167,7 @@ semantic-release
|
||||
|
||||
These options are currently available:
|
||||
- `branch`: The branch on which releases should happen. Default: `'master'`
|
||||
- `repositoryUrl`: The git repository URL. Default: `repository` property in `package.json` or git origin url. Any valid git url format is supported (See [Git protocols](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols)). If the [Github plugin](https://github.com/semantic-release/github) is used the URL must be a valid Github URL that include the `owner`, the `repository` name and the `host`. The Github shorthand URL is not supported.
|
||||
- `dry-run`: Dry-run mode, skipping verifyConditions, publishing and release, printing next version and release notes
|
||||
- `debug`: Output debugging information
|
||||
|
||||
@@ -206,12 +207,8 @@ module.exports = function (pluginConfig, config, callback) {}
|
||||
|
||||
- `pluginConfig`: If the user of your plugin specifies additional plugin config in the `package.json` (see the `verifyConditions` example above) then it’s this object.
|
||||
- `config`: A config object containing a lot of information to act upon.
|
||||
- `env`: All environment variables
|
||||
- `options`: `semantic-release` options like `debug`, or `branch`
|
||||
- `pkg`: Parsed `package.json`
|
||||
- For certain plugins the `config` object contains even more information. See below.
|
||||
- `callback`: If an error occurs pass it as first argument. Otherwise pass your result as second argument.
|
||||
|
||||
|
||||
### `analyzeCommits`
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// Bad news: We have to write plain ES5 in this file
|
||||
// Good news: It's the only file of the entire project
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var semver = require('semver');
|
||||
|
||||
if (semver.lt(process.version, '8.0.0')) {
|
||||
@@ -22,7 +23,7 @@ npx is bundled with npm >= 5.4, or available via npm. More info: npm.im/npx`
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// node 8+ from this point on
|
||||
// Node 8+ from this point on
|
||||
require('../cli')().catch(() => {
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ module.exports = async () => {
|
||||
.name('semantic-release')
|
||||
.description('Run automated package publishing')
|
||||
.option('-b, --branch <branch>', 'Branch to release from')
|
||||
.option('-r, --repositoryUrl <repositoryUrl>', 'Git repository URL')
|
||||
.option(
|
||||
'--verify-conditions <paths>',
|
||||
'Comma separated list of paths or packages name for the verifyConditions plugin(s)',
|
||||
@@ -41,7 +42,7 @@ module.exports = async () => {
|
||||
program.outputHelp();
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
await require('./index')(program.opts());
|
||||
await require('.')(program.opts());
|
||||
}
|
||||
} catch (err) {
|
||||
// If error is a SemanticReleaseError then it's an expected exception case (no release to be done, running on a PR etc..) and the cli will return with 0
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
const marked = require('marked');
|
||||
const TerminalRenderer = require('marked-terminal');
|
||||
const SemanticReleaseError = require('@semantic-release/error');
|
||||
const {gitHead: getGitHead} = require('./lib/git');
|
||||
const getConfig = require('./lib/get-config');
|
||||
const getNextVersion = require('./lib/get-next-version');
|
||||
const getCommits = require('./lib/get-commits');
|
||||
const logger = require('./lib/logger');
|
||||
const {gitHead: getGitHead, isGitRepo} = require('./lib/git');
|
||||
|
||||
module.exports = async opts => {
|
||||
const config = await getConfig(opts, logger);
|
||||
const {plugins, env, options, pkg} = config;
|
||||
if (!await isGitRepo()) {
|
||||
throw new SemanticReleaseError('Semantic-release must run from a git repository', 'ENOGITREPO');
|
||||
}
|
||||
|
||||
logger.log('Run automated release for branch %s', options.branch);
|
||||
const config = await getConfig(opts, logger);
|
||||
const {plugins, options} = config;
|
||||
|
||||
if (!options.repositoryUrl) {
|
||||
throw new SemanticReleaseError('The repositoryUrl option is required', 'ENOREPOURL');
|
||||
}
|
||||
|
||||
logger.log('Run automated release from branch %s', options.branch);
|
||||
|
||||
if (!options.dryRun) {
|
||||
logger.log('Call plugin %s', 'verify-conditions');
|
||||
await plugins.verifyConditions({env, options, pkg, logger});
|
||||
await plugins.verifyConditions({options, logger});
|
||||
}
|
||||
|
||||
logger.log('Call plugin %s', 'get-last-release');
|
||||
const {commits, lastRelease} = await getCommits(
|
||||
await plugins.getLastRelease({env, options, pkg, logger}),
|
||||
await plugins.getLastRelease({options, logger}),
|
||||
options.branch,
|
||||
logger
|
||||
);
|
||||
|
||||
logger.log('Call plugin %s', 'analyze-commits');
|
||||
const type = await plugins.analyzeCommits({env, options, pkg, logger, lastRelease, commits});
|
||||
const type = await plugins.analyzeCommits({options, logger, lastRelease, commits});
|
||||
if (!type) {
|
||||
throw new SemanticReleaseError('There are no relevant changes, so no new version is released.', 'ENOCHANGE');
|
||||
}
|
||||
@@ -34,9 +42,9 @@ module.exports = async opts => {
|
||||
const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: `v${version}`};
|
||||
|
||||
logger.log('Call plugin %s', 'verify-release');
|
||||
await plugins.verifyRelease({env, options, pkg, logger, lastRelease, commits, nextRelease});
|
||||
await plugins.verifyRelease({options, logger, lastRelease, commits, nextRelease});
|
||||
|
||||
const generateNotesParam = {env, options, pkg, logger, lastRelease, commits, nextRelease};
|
||||
const generateNotesParam = {options, logger, lastRelease, commits, nextRelease};
|
||||
|
||||
if (options.dryRun) {
|
||||
logger.log('Call plugin %s', 'generate-notes');
|
||||
@@ -49,7 +57,7 @@ module.exports = async opts => {
|
||||
nextRelease.notes = await plugins.generateNotes(generateNotesParam);
|
||||
|
||||
logger.log('Call plugin %s', 'publish');
|
||||
await plugins.publish({options, pkg, logger, lastRelease, commits, nextRelease}, async prevInput => {
|
||||
await plugins.publish({options, logger, lastRelease, commits, nextRelease}, async prevInput => {
|
||||
const newGitHead = await getGitHead();
|
||||
// If previous publish plugin has created a commit (gitHead changed)
|
||||
if (prevInput.nextRelease.gitHead !== newGitHead) {
|
||||
@@ -59,7 +67,7 @@ module.exports = async opts => {
|
||||
nextRelease.notes = await plugins.generateNotes(generateNotesParam);
|
||||
}
|
||||
// Call the next publish plugin with the updated `nextRelease`
|
||||
return {options, pkg, logger, lastRelease, commits, nextRelease};
|
||||
return {options, logger, lastRelease, commits, nextRelease};
|
||||
});
|
||||
logger.log('Published release: %s', nextRelease.version);
|
||||
}
|
||||
|
||||
+2
-1
@@ -18,6 +18,7 @@ const getVersionHead = require('./get-version-head');
|
||||
* @typedef {Object} LastRelease
|
||||
* @property {string} version The version number of the last release.
|
||||
* @property {string} [gitHead] The commit sha used to make the last release.
|
||||
* @property {string} [gitTag] The tag used to make the last release.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -45,7 +46,7 @@ const getVersionHead = require('./get-version-head');
|
||||
* @throws {SemanticReleaseError} with code `ENOTINHISTORY` if `lastRelease.gitHead` or the commit sha derived from `config.lastRelease.version` is not in the direct history of `branch`.
|
||||
* @throws {SemanticReleaseError} with code `ENOGITHEAD` if `lastRelease.gitHead` is undefined and no commit sha can be found for the `config.lastRelease.version`.
|
||||
*/
|
||||
module.exports = async ({version, gitHead}, branch, logger) => {
|
||||
module.exports = async ({version, gitHead} = {}, branch, logger) => {
|
||||
let gitTag;
|
||||
if (gitHead || version) {
|
||||
try {
|
||||
|
||||
+14
-6
@@ -1,19 +1,27 @@
|
||||
const {readJson} = require('fs-extra');
|
||||
const readPkgUp = require('read-pkg-up');
|
||||
const {defaults} = require('lodash');
|
||||
const normalizeData = require('normalize-package-data');
|
||||
const cosmiconfig = require('cosmiconfig');
|
||||
const debug = require('debug')('semantic-release:config');
|
||||
const {repoUrl} = require('./git');
|
||||
const plugins = require('./plugins');
|
||||
|
||||
module.exports = async (opts, logger) => {
|
||||
const pkg = await readJson('./package.json');
|
||||
normalizeData(pkg);
|
||||
const options = defaults(opts, pkg.release, {branch: 'master'});
|
||||
const {config} = (await cosmiconfig('release', {rcExtensions: true}).load(process.cwd())) || {};
|
||||
const options = defaults(opts, config, {branch: 'master', repositoryUrl: (await pkgRepoUrl()) || (await repoUrl())});
|
||||
|
||||
debug('name: %O', options.name);
|
||||
debug('branch: %O', options.branch);
|
||||
debug('repositoryUrl: %O', options.repositoryUrl);
|
||||
debug('analyzeCommits: %O', options.analyzeCommits);
|
||||
debug('generateNotes: %O', options.generateNotes);
|
||||
debug('verifyConditions: %O', options.verifyConditions);
|
||||
debug('verifyRelease: %O', options.verifyRelease);
|
||||
debug('publish: %O', options.publish);
|
||||
|
||||
return {env: process.env, pkg, options, plugins: await plugins(options, logger), logger};
|
||||
return {options, plugins: await plugins(options, logger)};
|
||||
};
|
||||
|
||||
async function pkgRepoUrl() {
|
||||
const {pkg} = await readPkgUp();
|
||||
return pkg && pkg.repository ? pkg.repository.url : null;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
const semver = require('semver');
|
||||
const SemanticReleaseError = require('@semantic-release/error');
|
||||
|
||||
module.exports = (type, lastRelease, logger) => {
|
||||
let version;
|
||||
if (!lastRelease.version) {
|
||||
if (lastRelease.version) {
|
||||
version = semver.inc(lastRelease.version, type);
|
||||
logger.log('The next release version is %s', version);
|
||||
} else {
|
||||
version = '1.0.0';
|
||||
logger.log('There is no previous release, the next release version is %s', version);
|
||||
} else {
|
||||
version = semver.inc(lastRelease.version, type);
|
||||
if (!version) {
|
||||
throw new SemanticReleaseError(`Invalid release type ${type}`, 'EINVALIDTYPE');
|
||||
}
|
||||
logger.log('The next release version is %s', version);
|
||||
}
|
||||
|
||||
return version;
|
||||
|
||||
+17
-1
@@ -72,4 +72,20 @@ async function gitHead() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {gitTagHead, gitCommitTag, isCommitInHistory, unshallow, gitHead};
|
||||
/**
|
||||
* @return {string|null} The value of the remote git URL.
|
||||
*/
|
||||
async function repoUrl() {
|
||||
return (await execa.stdout('git', ['remote', 'get-url', 'origin'], {reject: false})) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Boolean} `true` if the current working directory is in a git repository, `false` otherwise.
|
||||
*/
|
||||
async function isGitRepo() {
|
||||
const shell = await execa('git', ['rev-parse', '--git-dir'], {reject: false});
|
||||
debugShell('Check if the current working directory is a git repository', shell, debug);
|
||||
return shell.code === 0;
|
||||
}
|
||||
|
||||
module.exports = {gitTagHead, gitCommitTag, isCommitInHistory, unshallow, gitHead, repoUrl, isGitRepo};
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
const {isString, isObject, isFunction, isArray} = require('lodash');
|
||||
const semver = require('semver');
|
||||
const conditionTravis = require('@semantic-release/condition-travis');
|
||||
const commitAnalyzer = require('@semantic-release/commit-analyzer');
|
||||
const releaseNotesGenerator = require('@semantic-release/release-notes-generator');
|
||||
const npm = require('@semantic-release/npm');
|
||||
const github = require('@semantic-release/github');
|
||||
|
||||
const RELEASE_TYPE = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'];
|
||||
const validatePluginConfig = conf => isString(conf) || isString(conf.path) || isFunction(conf);
|
||||
|
||||
module.exports = {
|
||||
verifyConditions: {
|
||||
default: [npm.verifyConditions, github.verifyConditions, conditionTravis],
|
||||
default: ['@semantic-release/npm', '@semantic-release/github', '@semantic-release/condition-travis'],
|
||||
config: {
|
||||
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
message:
|
||||
@@ -18,7 +14,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
getLastRelease: {
|
||||
default: npm.getLastRelease,
|
||||
default: '@semantic-release/npm',
|
||||
config: {
|
||||
validator: conf => Boolean(conf) && validatePluginConfig(conf),
|
||||
message:
|
||||
@@ -34,7 +30,7 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
analyzeCommits: {
|
||||
default: commitAnalyzer,
|
||||
default: '@semantic-release/commit-analyzer',
|
||||
config: {
|
||||
validator: conf => Boolean(conf) && validatePluginConfig(conf),
|
||||
message:
|
||||
@@ -42,7 +38,7 @@ module.exports = {
|
||||
},
|
||||
output: {
|
||||
validator: output => !output || RELEASE_TYPE.includes(output),
|
||||
message: 'The "analyzeCommits" plugin output must be either undefined or a valid semver release type.',
|
||||
message: 'The "analyzeCommits" plugin output, if defined, must be a valid semver release type.',
|
||||
},
|
||||
},
|
||||
verifyRelease: {
|
||||
@@ -54,19 +50,19 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
generateNotes: {
|
||||
default: releaseNotesGenerator,
|
||||
default: '@semantic-release/release-notes-generator',
|
||||
config: {
|
||||
validator: conf => !conf || validatePluginConfig(conf),
|
||||
message:
|
||||
'The "generateNotes" plugin, if defined, must be a single plugin definition. A plugin definition is either a string or an object with a path property.',
|
||||
},
|
||||
output: {
|
||||
validator: output => isString(output),
|
||||
message: 'The "generateNotes" plugin output must be a string.',
|
||||
validator: output => !output || isString(output),
|
||||
message: 'The "generateNotes" plugin output, if defined, must be a string.',
|
||||
},
|
||||
},
|
||||
publish: {
|
||||
default: [npm.publish, github.publish],
|
||||
default: ['@semantic-release/npm', '@semantic-release/github'],
|
||||
config: {
|
||||
validator: conf => Boolean(conf) && (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||
message:
|
||||
@@ -74,5 +70,3 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const validatePluginConfig = conf => isString(conf) || isString(conf.path) || isFunction(conf);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {isArray} = require('lodash');
|
||||
const {isArray, isObject} = require('lodash');
|
||||
const DEFINITIONS = require('./definitions');
|
||||
const pipeline = require('./pipeline');
|
||||
const normalize = require('./normalize');
|
||||
@@ -8,6 +8,10 @@ module.exports = (options, logger) =>
|
||||
const {config, output, default: def} = DEFINITIONS[pluginType];
|
||||
let pluginConfs;
|
||||
if (options[pluginType]) {
|
||||
// If an object is passed and the path is missing, set the default one for single plugins
|
||||
if (isObject(options[pluginType]) && !options[pluginType].path && !isArray(def)) {
|
||||
options[pluginType].path = def;
|
||||
}
|
||||
if (config && !config.validator(options[pluginType])) {
|
||||
throw new Error(config.message);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {promisify, inspect} = require('util');
|
||||
const {inspect} = require('util');
|
||||
const {isString, isObject, isFunction, noop, cloneDeep} = require('lodash');
|
||||
const importFrom = require('import-from');
|
||||
|
||||
@@ -14,9 +14,9 @@ module.exports = (pluginType, pluginConfig, logger, validator) => {
|
||||
|
||||
let func;
|
||||
if (isFunction(plugin)) {
|
||||
func = promisify(plugin.bind(null, cloneDeep(config)));
|
||||
func = plugin.bind(null, cloneDeep(config));
|
||||
} else if (isObject(plugin) && plugin[pluginType] && isFunction(plugin[pluginType])) {
|
||||
func = promisify(plugin[pluginType].bind(null, cloneDeep(config)));
|
||||
func = plugin[pluginType].bind(null, cloneDeep(config));
|
||||
} else {
|
||||
throw new Error(
|
||||
`The ${pluginType} plugin must be a function, or an object with a function in the property ${pluginType}.`
|
||||
@@ -27,7 +27,7 @@ module.exports = (pluginType, pluginConfig, logger, validator) => {
|
||||
const result = await func(cloneDeep(input));
|
||||
|
||||
if (validator && !validator.validator(result)) {
|
||||
throw new Error(`${validator.message}. Received: ${inspect(result)}`);
|
||||
throw new Error(`${validator.message} Received: ${inspect(result)}`);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
+28
-33
@@ -15,68 +15,53 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@semantic-release/commit-analyzer": "^4.0.0",
|
||||
"@semantic-release/condition-travis": "^6.0.0",
|
||||
"@semantic-release/commit-analyzer": "^5.0.0",
|
||||
"@semantic-release/condition-travis": "^7.0.0",
|
||||
"@semantic-release/error": "^2.1.0",
|
||||
"@semantic-release/github": "^1.0.0",
|
||||
"@semantic-release/npm": "^1.0.0",
|
||||
"@semantic-release/release-notes-generator": "^5.0.0",
|
||||
"@semantic-release/github": "^2.0.0",
|
||||
"@semantic-release/npm": "^2.0.0",
|
||||
"@semantic-release/release-notes-generator": "^6.0.0",
|
||||
"chalk": "^2.3.0",
|
||||
"commander": "^2.11.0",
|
||||
"cosmiconfig": "^3.1.0",
|
||||
"debug": "^3.1.0",
|
||||
"execa": "^0.8.0",
|
||||
"fs-extra": "^4.0.2",
|
||||
"get-stream": "^3.0.0",
|
||||
"git-log-parser": "^1.2.0",
|
||||
"import-from": "^2.1.0",
|
||||
"lodash": "^4.0.0",
|
||||
"marked": "^0.3.6",
|
||||
"marked-terminal": "^2.0.0",
|
||||
"normalize-package-data": "^2.3.4",
|
||||
"p-reduce": "^1.0.0",
|
||||
"read-pkg-up": "^3.0.0",
|
||||
"semver": "^5.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ava": "^0.23.0",
|
||||
"ava": "^0.24.0",
|
||||
"codecov": "^3.0.0",
|
||||
"commitizen": "^2.9.6",
|
||||
"cz-conventional-changelog": "^2.0.0",
|
||||
"dockerode": "^2.5.2",
|
||||
"eslint": "^4.7.0",
|
||||
"eslint-config-prettier": "^2.5.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-node": "^5.2.0",
|
||||
"eslint-plugin-prettier": "^2.3.0",
|
||||
"eslint-plugin-promise": "^3.5.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"file-url": "^2.0.2",
|
||||
"fs-extra": "^4.0.2",
|
||||
"got": "^8.0.0",
|
||||
"js-yaml": "^3.10.0",
|
||||
"mockserver-client": "^2.0.0",
|
||||
"nock": "^9.0.2",
|
||||
"npm-registry-couchapp": "^2.6.12",
|
||||
"nyc": "^11.2.1",
|
||||
"p-map-series": "^1.0.0",
|
||||
"p-retry": "^1.0.0",
|
||||
"prettier": "~1.8.0",
|
||||
"proxyquire": "^1.8.0",
|
||||
"rimraf": "^2.5.0",
|
||||
"sinon": "^4.0.0",
|
||||
"tempy": "^0.2.1"
|
||||
"tempy": "^0.2.1",
|
||||
"xo": "^0.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4",
|
||||
"npm": ">=2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"standard",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": 2
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"lib",
|
||||
@@ -127,12 +112,22 @@
|
||||
"url": "git+https://github.com/semantic-release/semantic-release.git"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf coverage && rimraf .nyc_output",
|
||||
"cm": "git-cz",
|
||||
"codecov": "codecov -f coverage/coverage-final.json",
|
||||
"lint": "eslint index.js cli.js lib test",
|
||||
"pretest": "npm run clean && npm run lint",
|
||||
"lint": "xo",
|
||||
"pretest": "npm run lint",
|
||||
"semantic-release": "./bin/semantic-release.js",
|
||||
"test": "nyc ava -v"
|
||||
},
|
||||
"xo": {
|
||||
"extends": [
|
||||
"prettier"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+6
-19
@@ -1,21 +1,8 @@
|
||||
module.exports = {
|
||||
verifyConditions(config, options, cb) {
|
||||
cb();
|
||||
},
|
||||
|
||||
getLastRelease(config, options, cb) {
|
||||
cb();
|
||||
},
|
||||
analyzeCommits(config, options, cb) {
|
||||
cb();
|
||||
},
|
||||
verifyRelease(config, options, cb) {
|
||||
cb();
|
||||
},
|
||||
generateNotes(config, options, cb) {
|
||||
cb();
|
||||
},
|
||||
publish(config, options, cb) {
|
||||
cb();
|
||||
},
|
||||
verifyConditions: () => {},
|
||||
getLastRelease: () => {},
|
||||
analyzeCommits: () => {},
|
||||
verifyRelease: () => {},
|
||||
generateNotes: () => {},
|
||||
publish: () => {},
|
||||
};
|
||||
|
||||
+2
-2
@@ -9,6 +9,6 @@ class InheritedError extends SemanticReleaseError {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(config, options, cb) {
|
||||
cb(new InheritedError('Inherited error', 'EINHERITED'));
|
||||
module.exports = () => {
|
||||
throw new InheritedError('Inherited error', 'EINHERITED');
|
||||
};
|
||||
|
||||
Vendored
+2
-2
@@ -1,5 +1,5 @@
|
||||
module.exports = function(config, options, cb) {
|
||||
module.exports = () => {
|
||||
const error = new Error('a');
|
||||
error.errorProperty = 'errorProperty';
|
||||
cb(error);
|
||||
throw error;
|
||||
};
|
||||
|
||||
Vendored
+1
-3
@@ -1,3 +1 @@
|
||||
module.exports = (config, options, cb) => {
|
||||
cb(null);
|
||||
};
|
||||
module.exports = () => {};
|
||||
|
||||
+1
-3
@@ -1,3 +1 @@
|
||||
module.exports = function(pluginConfig, options, cb) {
|
||||
cb(null, {pluginConfig, options});
|
||||
};
|
||||
module.exports = (pluginConfig, options) => ({pluginConfig, options});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import test from 'ava';
|
||||
import {stub} from 'sinon';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import getCommits from '../lib/get-commits';
|
||||
import {
|
||||
gitRepo,
|
||||
gitCommits,
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
gitLog,
|
||||
gitDetachedHead,
|
||||
} from './helpers/git-utils';
|
||||
import getCommits from '../lib/get-commits';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current working diretory
|
||||
@@ -409,7 +408,7 @@ test.serial('Throws ENOGITHEAD error if the gitHead of the last release cannot b
|
||||
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOGITHEAD');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
// Verify the log function has been called with a message explaining the error
|
||||
t.regex(
|
||||
t.context.error.firstCall.args[0],
|
||||
@@ -428,7 +427,7 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in history', async t =
|
||||
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
// Verify the log function has been called with a message mentionning the 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
|
||||
@@ -453,7 +452,7 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in branch history but
|
||||
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
// Verify the log function has been called with a message mentionning the 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
|
||||
@@ -482,7 +481,7 @@ test.serial('Throws ENOTINHISTORY error if gitHead is not in detached head but p
|
||||
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
// Verify the log function has been called with a message mentionning the 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
|
||||
@@ -508,7 +507,7 @@ test.serial('Throws ENOTINHISTORY error when a tag is not in branch history but
|
||||
const error = await t.throws(getCommits({version: '1.0.0'}, 'master', t.context.logger));
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOTINHISTORY');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
// Verify the log function has been called with a message mentionning the 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
|
||||
|
||||
+136
-46
@@ -1,9 +1,9 @@
|
||||
import test from 'ava';
|
||||
import {writeJson} from 'fs-extra';
|
||||
import {writeFile, writeJson} from 'fs-extra';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import normalizeData from 'normalize-package-data';
|
||||
import {gitRepo} from './helpers/git-utils';
|
||||
import yaml from 'js-yaml';
|
||||
import {gitRepo, gitCommits, gitShallowClone, gitAddConfig} from './helpers/git-utils';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current process.env
|
||||
@@ -21,85 +21,175 @@ test.afterEach.always(t => {
|
||||
process.env = Object.assign({}, t.context.env);
|
||||
});
|
||||
|
||||
test.serial('Default values', async t => {
|
||||
const pkg = {name: 'package_name', release: {}};
|
||||
test.serial('Default values, reading repositoryUrl from package.json', async t => {
|
||||
const pkg = {repository: 'git@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']);
|
||||
// Add remote.origin.url config
|
||||
await gitAddConfig('remote.origin.url', 'git@repo.com:owner/module.git');
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', pkg);
|
||||
|
||||
const {options} = await t.context.getConfig();
|
||||
|
||||
// Verify the default options are set
|
||||
t.is(options.branch, 'master');
|
||||
t.is(options.repositoryUrl, 'git@package.com:owner/module.git');
|
||||
});
|
||||
|
||||
test.serial('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add remote.origin.url config
|
||||
await gitAddConfig('remote.origin.url', 'git@repo.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');
|
||||
});
|
||||
|
||||
test.serial('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
|
||||
const pkg = {repository: 'git+https://hostname.com/owner/module.git'};
|
||||
// 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);
|
||||
|
||||
const result = await t.context.getConfig();
|
||||
const {options} = 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(options.branch, 'master');
|
||||
t.is(options.repositoryUrl, pkg.repository);
|
||||
});
|
||||
|
||||
test.serial('Read package.json configuration', async t => {
|
||||
test.serial('Read options from package.json', async t => {
|
||||
const release = {
|
||||
analyzeCommits: 'analyzeCommits',
|
||||
generateNotes: 'generateNotes',
|
||||
getLastRelease: {
|
||||
path: 'getLastRelease',
|
||||
param: 'getLastRelease_param',
|
||||
},
|
||||
getLastRelease: {path: 'getLastRelease', param: 'getLastRelease_param'},
|
||||
branch: 'test_branch',
|
||||
repositoryUrl: 'git+https://hostname.com/owner/module.git',
|
||||
};
|
||||
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);
|
||||
await writeJson('./package.json', {release});
|
||||
|
||||
const result = await t.context.getConfig();
|
||||
const {options} = 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);
|
||||
|
||||
t.deepEqual(options, release);
|
||||
// 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.deepEqual(t.context.plugins.firstCall.args[0], release);
|
||||
});
|
||||
|
||||
test.serial('Prioritise cli parameters over package.json configuration', async t => {
|
||||
test.serial('Read options from .releaserc.yml', async t => {
|
||||
const release = {
|
||||
analyzeCommits: 'analyzeCommits',
|
||||
generateNotes: 'generateNotes',
|
||||
getLastRelease: {
|
||||
path: 'getLastRelease',
|
||||
param: 'getLastRelease_pkg',
|
||||
},
|
||||
getLastRelease: {path: 'getLastRelease', param: 'getLastRelease_param'},
|
||||
branch: 'test_branch',
|
||||
repositoryUrl: 'git+https://hostname.com/owner/module.git',
|
||||
};
|
||||
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeFile('.releaserc.yml', yaml.safeDump(release));
|
||||
|
||||
const {options} = await t.context.getConfig();
|
||||
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(options, release);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.firstCall.args[0], release);
|
||||
});
|
||||
|
||||
test.serial('Read options from .releaserc.json', async t => {
|
||||
const release = {
|
||||
getLastRelease: {path: 'getLastRelease', param: 'getLastRelease_param'},
|
||||
branch: 'test_branch',
|
||||
repositoryUrl: 'git+https://hostname.com/owner/module.git',
|
||||
};
|
||||
|
||||
// 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('.releaserc.json', release);
|
||||
|
||||
const {options} = await t.context.getConfig();
|
||||
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(options, release);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.firstCall.args[0], release);
|
||||
});
|
||||
|
||||
test.serial('Read options from .releaserc.js', async t => {
|
||||
const release = {
|
||||
getLastRelease: {path: 'getLastRelease', param: 'getLastRelease_param'},
|
||||
branch: 'test_branch',
|
||||
repositoryUrl: 'git+https://hostname.com/owner/module.git',
|
||||
};
|
||||
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeFile('.releaserc.js', `module.exports = ${JSON.stringify(release)}`);
|
||||
|
||||
const {options} = await t.context.getConfig();
|
||||
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(options, release);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.firstCall.args[0], release);
|
||||
});
|
||||
|
||||
test.serial('Read options from release.config.js', async t => {
|
||||
const release = {
|
||||
getLastRelease: {path: 'getLastRelease', param: 'getLastRelease_param'},
|
||||
branch: 'test_branch',
|
||||
repositoryUrl: 'git+https://hostname.com/owner/module.git',
|
||||
};
|
||||
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Create package.json in repository root
|
||||
await writeFile('release.config.js', `module.exports = ${JSON.stringify(release)}`);
|
||||
|
||||
const {options} = await t.context.getConfig();
|
||||
|
||||
// Verify the options contains the plugin config from package.json
|
||||
t.deepEqual(options, release);
|
||||
// Verify the plugins module is called with the plugin options from package.json
|
||||
t.deepEqual(t.context.plugins.firstCall.args[0], release);
|
||||
});
|
||||
|
||||
test.serial('Prioritise cli parameters over file configuration and git repo', async t => {
|
||||
const release = {
|
||||
getLastRelease: {path: 'getLastRelease', param: 'getLastRelease_pkg'},
|
||||
branch: 'branch_pkg',
|
||||
};
|
||||
const options = {
|
||||
getLastRelease: {
|
||||
path: 'getLastRelease',
|
||||
param: 'getLastRelease_cli',
|
||||
},
|
||||
getLastRelease: {path: 'getLastRelease', param: 'getLastRelease_cli'},
|
||||
branch: 'branch_cli',
|
||||
repositoryUrl: 'http://cli-url.com/owner/package',
|
||||
};
|
||||
const pkg = {name: 'package_name', release};
|
||||
|
||||
const pkg = {release, repository: 'git@hostname.com:owner/module.git'};
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
const repo = await gitRepo();
|
||||
await gitCommits(['First']);
|
||||
// Create a clone
|
||||
await gitShallowClone(repo);
|
||||
// Create package.json in repository root
|
||||
await writeJson('./package.json', pkg);
|
||||
|
||||
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);
|
||||
|
||||
t.deepEqual(result.options, options);
|
||||
// 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);
|
||||
t.deepEqual(t.context.plugins.firstCall.args[0], options);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import test from 'ava';
|
||||
import {stub} from 'sinon';
|
||||
import SemanticReleaseError from '@semantic-release/error';
|
||||
import getNextVersion from '../lib/get-next-version';
|
||||
|
||||
test.beforeEach(t => {
|
||||
@@ -28,9 +27,3 @@ test('Return 1.0.0 if there is no previous release', t => {
|
||||
const version = getNextVersion('minor', {}, t.context.logger);
|
||||
t.is(version, '1.0.0');
|
||||
});
|
||||
|
||||
test('Return an error if the release type is invalid', t => {
|
||||
const error = t.throws(() => getNextVersion('invalid', {version: '1.0.0'}, t.context.logger));
|
||||
t.is(error.code, 'EINVALIDTYPE');
|
||||
t.true(error instanceof SemanticReleaseError);
|
||||
});
|
||||
|
||||
+40
-5
@@ -1,6 +1,15 @@
|
||||
import test from 'ava';
|
||||
import {gitRepo, gitCommits, gitCheckout, gitTagVersion, gitShallowClone, gitLog} from './helpers/git-utils';
|
||||
import {gitTagHead, gitCommitTag, isCommitInHistory, unshallow, gitHead} from '../lib/git';
|
||||
import fileUrl from 'file-url';
|
||||
import {gitTagHead, gitCommitTag, isCommitInHistory, unshallow, gitHead, repoUrl} from '../lib/git';
|
||||
import {
|
||||
gitRepo,
|
||||
gitCommits,
|
||||
gitCheckout,
|
||||
gitTagVersion,
|
||||
gitShallowClone,
|
||||
gitLog,
|
||||
gitAddConfig,
|
||||
} from './helpers/git-utils';
|
||||
|
||||
test.beforeEach(t => {
|
||||
// Save the current working diretory
|
||||
@@ -27,7 +36,7 @@ test.serial('Throw error if the last commit sha cannot be found', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
|
||||
await t.throws(gitHead());
|
||||
await t.throws(gitHead(), Error);
|
||||
});
|
||||
|
||||
test.serial('Unshallow repository', async t => {
|
||||
@@ -74,7 +83,7 @@ test.serial('Get the tag associated with a commit sha or "null" if the commit do
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['First']);
|
||||
const commits = await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
|
||||
@@ -86,10 +95,36 @@ test.serial('Get the commit sha for a given tag or "null" if the tag does not ex
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
// Add commits to the master branch
|
||||
let commits = await gitCommits(['First']);
|
||||
const commits = await gitCommits(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
|
||||
t.is((await gitTagHead('v1.0.0')).substring(0, 7), commits[0].hash);
|
||||
t.falsy(await gitTagHead('missing_tag'));
|
||||
});
|
||||
|
||||
test.serial('Return git remote repository url from config', async t => {
|
||||
// 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@hostname.com:owner/package.git');
|
||||
|
||||
t.is(await repoUrl(), 'git@hostname.com:owner/package.git');
|
||||
});
|
||||
|
||||
test.serial('Return git remote repository url set while cloning', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
const repo = await gitRepo();
|
||||
await gitCommits(['First']);
|
||||
// Create a clone
|
||||
await gitShallowClone(repo);
|
||||
|
||||
t.is(await repoUrl(), fileUrl(repo));
|
||||
});
|
||||
|
||||
test.serial('Return "null" if git repository url is not set', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
|
||||
t.is(await repoUrl(), null);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import tempy from 'tempy';
|
||||
import execa from 'execa';
|
||||
import pMapSeries from 'p-map-series';
|
||||
import fileUrl from 'file-url';
|
||||
import pReduce from 'p-reduce';
|
||||
|
||||
/**
|
||||
* Commit message informations.
|
||||
@@ -33,11 +34,16 @@ export async function gitRepo() {
|
||||
* @returns {Array<Commit>} The created commits, in reverse order (to match `git log` order).
|
||||
*/
|
||||
export async function gitCommits(messages) {
|
||||
return (await pMapSeries(messages, async msg => {
|
||||
const {stdout} = await execa('git', ['commit', '-m', msg, '--allow-empty', '--no-gpg-sign']);
|
||||
const [, branch, hash, message] = /^\[(\w+)\(?.*?\)?(\w+)\] (.+)$/.exec(stdout);
|
||||
return {branch, hash, message};
|
||||
})).reverse();
|
||||
return (await pReduce(
|
||||
messages,
|
||||
async (commits, msg) => {
|
||||
const {stdout} = await execa('git', ['commit', '-m', msg, '--allow-empty', '--no-gpg-sign']);
|
||||
const [, branch, hash, message] = /^\[(\w+)\(?.*?\)?(\w+)\] (.+)$/.exec(stdout);
|
||||
commits.push({branch, hash, message});
|
||||
return commits;
|
||||
},
|
||||
[]
|
||||
)).reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,14 +93,14 @@ export async function gitTagVersion(tagName, sha) {
|
||||
* @return {Array<string>} The list of tags from the current git repository.
|
||||
*/
|
||||
export async function gitTags() {
|
||||
return (await execa('git', ['tag'])).stdout.split('\n').filter(tag => !!tag);
|
||||
return (await execa('git', ['tag'])).stdout.split('\n').filter(tag => Boolean(tag));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Array<string>} The list of commit sha from the current git repository.
|
||||
*/
|
||||
export async function gitLog() {
|
||||
return (await execa('git', ['log', '--format=format:%H'])).stdout.split('\n').filter(sha => !!sha);
|
||||
return (await execa('git', ['log', '--format=format:%H'])).stdout.split('\n').filter(sha => Boolean(sha));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +115,7 @@ export async function gitShallowClone(origin, branch = 'master', depth = 1) {
|
||||
const dir = tempy.directory();
|
||||
|
||||
process.chdir(dir);
|
||||
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, `file://${origin}`, dir]);
|
||||
await execa('git', ['clone', '--no-hardlinks', '--no-tags', '-b', branch, '--depth', depth, fileUrl(origin), dir]);
|
||||
return dir;
|
||||
}
|
||||
|
||||
@@ -137,3 +143,13 @@ export async function gitDetachedHead(origin, head) {
|
||||
export async function gitPackRefs() {
|
||||
await execa('git', ['pack-refs', '--all']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Git configuration.
|
||||
*
|
||||
* @param {string} name Config name.
|
||||
* @param {string} value Config value.
|
||||
*/
|
||||
export async function gitAddConfig(name, value) {
|
||||
await execa('git', ['config', '--add', name, value]);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import nock from 'nock';
|
||||
* @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(
|
||||
export default function authenticate(
|
||||
{githubToken = 'GH_TOKEN', githubUrl = 'https://api.github.com', githubApiPathPrefix = ''} = {}
|
||||
) {
|
||||
return nock(`${githubUrl}/${githubApiPathPrefix}`, {reqheaders: {Authorization: `token ${githubToken}`}});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import Docker from 'dockerode';
|
||||
import getStream from 'get-stream';
|
||||
import got from 'got';
|
||||
import pRetry from 'p-retry';
|
||||
import {mockServerClient} from 'mockserver-client';
|
||||
|
||||
const IMAGE = 'jamesdbloom/mockserver:latest';
|
||||
const MOCK_SERVER_PORT = 1080;
|
||||
const MOCK_SERVER_HOST = 'localhost';
|
||||
const docker = new Docker();
|
||||
@@ -13,22 +16,35 @@ let container;
|
||||
* @return {Promise} Promise that resolves when the container is started.
|
||||
*/
|
||||
async function start() {
|
||||
await getStream(await docker.pull('jamesdbloom/mockserver:latest'));
|
||||
await getStream(await docker.pull(IMAGE));
|
||||
|
||||
container = await docker.createContainer({
|
||||
Image: 'jamesdbloom/mockserver',
|
||||
Tty: true,
|
||||
Image: IMAGE,
|
||||
PortBindings: {[`${MOCK_SERVER_PORT}/tcp`]: [{HostPort: `${MOCK_SERVER_PORT}`}]},
|
||||
});
|
||||
return container.start();
|
||||
await container.start();
|
||||
|
||||
try {
|
||||
// Wait for the mock server to be ready
|
||||
await pRetry(() => got.put(`http://${MOCK_SERVER_HOST}:${MOCK_SERVER_PORT}/status`, {cache: false}), {
|
||||
retries: 7,
|
||||
minTimeout: 1000,
|
||||
factor: 2,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Couldn't start mock-server after 2 min`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the `mockserver` Docker container.
|
||||
* Stop and remote the `mockserver` Docker container.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is stopped.
|
||||
*/
|
||||
async function stop() {
|
||||
return container.stop();
|
||||
await container.stop();
|
||||
await container.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import Docker from 'dockerode';
|
||||
import getStream from 'get-stream';
|
||||
import got from 'got';
|
||||
import pRetry from 'p-retry';
|
||||
|
||||
const IMAGE = 'npmjs/npm-docker-couchdb:1.6.1';
|
||||
const SERVER_PORT = 15986;
|
||||
const COUCHDB_PORT = 5984;
|
||||
const SERVER_HOST = 'localhost';
|
||||
const NPM_USERNAME = 'integration';
|
||||
const NPM_PASSWORD = 'suchsecure';
|
||||
const NPM_EMAIL = 'integration@test.com';
|
||||
const docker = new Docker();
|
||||
let container;
|
||||
|
||||
/**
|
||||
* Download the `npm-docker-couchdb` 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(IMAGE));
|
||||
|
||||
container = await docker.createContainer({
|
||||
Tty: true,
|
||||
Image: IMAGE,
|
||||
PortBindings: {[`${COUCHDB_PORT}/tcp`]: [{HostPort: `${SERVER_PORT}`}]},
|
||||
});
|
||||
|
||||
await container.start();
|
||||
|
||||
try {
|
||||
// Wait for the registry to be ready
|
||||
await pRetry(() => got(`http://${SERVER_HOST}:${SERVER_PORT}/registry/_design/app`, {cache: false}), {
|
||||
retries: 7,
|
||||
minTimeout: 1000,
|
||||
factor: 2,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Couldn't start npm-docker-couchdb after 2 min`);
|
||||
}
|
||||
|
||||
// Create user
|
||||
await got(`http://${SERVER_HOST}:${SERVER_PORT}/_users/org.couchdb.user:${NPM_USERNAME}`, {
|
||||
json: true,
|
||||
auth: 'admin:admin',
|
||||
method: 'PUT',
|
||||
body: {
|
||||
_id: `org.couchdb.user:${NPM_USERNAME}`,
|
||||
name: NPM_USERNAME,
|
||||
roles: [],
|
||||
type: 'user',
|
||||
password: NPM_PASSWORD,
|
||||
email: NPM_EMAIL,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const url = `http://${SERVER_HOST}:${SERVER_PORT}/registry/_design/app/_rewrite/`;
|
||||
|
||||
const authEnv = {
|
||||
npm_config_registry: url, // eslint-disable-line camelcase
|
||||
NPM_USERNAME,
|
||||
NPM_PASSWORD,
|
||||
NPM_EMAIL,
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop and remote the `npm-docker-couchdb` Docker container.
|
||||
*
|
||||
* @return {Promise} Promise that resolves when the container is stopped.
|
||||
*/
|
||||
async function stop() {
|
||||
await container.stop();
|
||||
await container.remove();
|
||||
}
|
||||
|
||||
export default {start, stop, authEnv, url};
|
||||
@@ -1,14 +0,0 @@
|
||||
import execa from 'execa';
|
||||
|
||||
const opts = {cwd: __dirname};
|
||||
const uri = 'http://localhost:' + (process.env.TRAVIS === 'true' ? 5984 : 15986) + '/registry/_design/app/_rewrite/';
|
||||
|
||||
function start() {
|
||||
return execa('./start.sh', opts);
|
||||
}
|
||||
|
||||
function stop() {
|
||||
return execa('./stop.sh', opts);
|
||||
}
|
||||
|
||||
export default {start, stop, uri};
|
||||
@@ -1,20 +0,0 @@
|
||||
[couchdb]
|
||||
database_dir = data
|
||||
view_index_dir = data
|
||||
delayed_commits = false
|
||||
uuid = bf4ecd84a7c89d60b5b2540fdf8c322c
|
||||
|
||||
[couch_httpd_auth]
|
||||
public_fields = appdotnet, avatar, avatarMedium, avatarLarge, date, email, fields, freenode, fullname, github, homepage, name, roles, twitter, type, _id, _rev
|
||||
users_db_public = true
|
||||
|
||||
[httpd]
|
||||
port = 15986
|
||||
bind_address = 127.0.0.1
|
||||
secure_rewrites = false
|
||||
|
||||
[log]
|
||||
file = couch/couch.log
|
||||
|
||||
[admins]
|
||||
admin = -pbkdf2-89582b49cd2e8443e29a841f5a76d367956a8e58,1afa2f1531a17ac97f2ac9e334293753,10
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# exit if an error occurs
|
||||
set -e
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
mkdir -p couch
|
||||
|
||||
if [[ $TRAVIS = true ]]
|
||||
then
|
||||
COUCH=http://admin:password@127.0.0.1:5984
|
||||
|
||||
curl -X PUT http://127.0.0.1:5984/_config/admins/admin -d '"password"'
|
||||
|
||||
curl -X PUT $COUCH/_config/couchdb/delayed_commits -d '"false"'
|
||||
curl -X PUT $COUCH/_config/couch_httpd_auth/users_db_public -d '"true"'
|
||||
curl -X PUT $COUCH/_config/couch_httpd_auth/public_fields -d '"appdotnet, avatar, avatarMedium, avatarLarge, date, email, fields, freenode, fullname, github, homepage, name, roles, twitter, type, _id, _rev"'
|
||||
curl -X PUT $COUCH/_config/httpd/secure_rewrites -d '"false"'
|
||||
|
||||
else
|
||||
COUCH=http://admin:password@127.0.0.1:15986
|
||||
couchdb -b -a local.ini -p couch/pid -o couch/stdout.log -e couch/stderr.log
|
||||
# wait for couch to start
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# create "registry" database
|
||||
curl -X PUT $COUCH/registry
|
||||
|
||||
# create sample npm user
|
||||
curl -X PUT $COUCH/_users/org.couchdb.user:integration -H Content-Type:application/json --data-binary '{"_id": "org.couchdb.user:integration", "name": "integration", "roles": [], "type": "user", "password": "suchsecure", "email": "integration@test.com"}'
|
||||
|
||||
# npm-registry-couchpp needs this variable set to run
|
||||
export DEPLOY_VERSION=nope
|
||||
|
||||
# setup npm-registry-couchapp
|
||||
npm explore npm-registry-couchapp -- npm start --npm-registry-couchapp:couch=$COUCH/registry
|
||||
npm explore npm-registry-couchapp -- npm run load --npm-registry-couchapp:couch=$COUCH/registry
|
||||
export NO_PROMPT=yes
|
||||
npm explore npm-registry-couchapp -- npm run copy --npm-registry-couchapp:couch=$COUCH/registry
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# close couchdb background process
|
||||
couchdb -d
|
||||
|
||||
# delete data and logs
|
||||
cd $(dirname $0)
|
||||
|
||||
cat couch/{couch,stdout,stderr}.log
|
||||
|
||||
cat couch/pid | xargs kill
|
||||
rm -rf couch
|
||||
rm -rf data
|
||||
+123
-43
@@ -1,9 +1,8 @@
|
||||
import {callbackify} from 'util';
|
||||
import test from 'ava';
|
||||
import {writeJson} from 'fs-extra';
|
||||
import proxyquire from 'proxyquire';
|
||||
import {stub} from 'sinon';
|
||||
import normalizeData from 'normalize-package-data';
|
||||
import tempy from 'tempy';
|
||||
import DEFINITIONS from '../lib/plugins/definitions';
|
||||
import {gitHead as getGitHead} from '../lib/git';
|
||||
import {gitRepo, gitCommits, gitTagVersion} from './helpers/git-utils';
|
||||
|
||||
@@ -42,11 +41,9 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
|
||||
const name = 'package-name';
|
||||
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
|
||||
const verifyConditions1 = stub().resolves();
|
||||
const verifyConditions2 = stub().resolves();
|
||||
const getLastRelease = stub().resolves(lastRelease);
|
||||
@@ -57,41 +54,34 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
|
||||
const options = {
|
||||
branch: 'master',
|
||||
verifyConditions: [callbackify(verifyConditions1), callbackify(verifyConditions2)],
|
||||
getLastRelease: callbackify(getLastRelease),
|
||||
analyzeCommits: callbackify(analyzeCommits),
|
||||
verifyRelease: callbackify(verifyRelease),
|
||||
generateNotes: callbackify(generateNotes),
|
||||
publish: callbackify(publish),
|
||||
repositoryUrl: 'git@hostname.com:owner/module.git',
|
||||
verifyConditions: [verifyConditions1, verifyConditions2],
|
||||
getLastRelease,
|
||||
analyzeCommits,
|
||||
verifyRelease,
|
||||
generateNotes,
|
||||
publish,
|
||||
};
|
||||
const pkg = {name, version: '0.0.0-dev'};
|
||||
normalizeData(pkg);
|
||||
|
||||
await writeJson('./package.json', pkg);
|
||||
|
||||
await t.context.semanticRelease(options);
|
||||
|
||||
t.true(verifyConditions1.calledOnce);
|
||||
t.deepEqual(verifyConditions1.firstCall.args[1], {env: process.env, options, pkg, logger: t.context.logger});
|
||||
t.deepEqual(verifyConditions1.firstCall.args[1], {options, logger: t.context.logger});
|
||||
t.true(verifyConditions2.calledOnce);
|
||||
t.deepEqual(verifyConditions2.firstCall.args[1], {env: process.env, options, pkg, logger: t.context.logger});
|
||||
t.deepEqual(verifyConditions2.firstCall.args[1], {options, logger: t.context.logger});
|
||||
|
||||
t.true(getLastRelease.calledOnce);
|
||||
t.deepEqual(getLastRelease.firstCall.args[1], {env: process.env, options, pkg, logger: t.context.logger});
|
||||
t.deepEqual(getLastRelease.firstCall.args[1], {options, logger: t.context.logger});
|
||||
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].env, process.env);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].options, options);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].pkg, pkg);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].logger, t.context.logger);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].lastRelease, lastRelease);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].commits[0].message, commits[0].message);
|
||||
|
||||
t.true(verifyRelease.calledOnce);
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].env, process.env);
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].options, options);
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].pkg, pkg);
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].logger, t.context.logger);
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].lastRelease, lastRelease);
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
@@ -99,9 +89,7 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].nextRelease, nextRelease);
|
||||
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.deepEqual(generateNotes.firstCall.args[1].env, process.env);
|
||||
t.deepEqual(generateNotes.firstCall.args[1].options, options);
|
||||
t.deepEqual(generateNotes.firstCall.args[1].pkg, pkg);
|
||||
t.deepEqual(generateNotes.firstCall.args[1].logger, t.context.logger);
|
||||
t.deepEqual(generateNotes.firstCall.args[1].lastRelease, lastRelease);
|
||||
t.deepEqual(generateNotes.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
@@ -110,7 +98,6 @@ test.serial('Plugins are called with expected values', async t => {
|
||||
|
||||
t.true(publish.calledOnce);
|
||||
t.deepEqual(publish.firstCall.args[1].options, options);
|
||||
t.deepEqual(publish.firstCall.args[1].pkg, pkg);
|
||||
t.deepEqual(publish.firstCall.args[1].logger, t.context.logger);
|
||||
t.deepEqual(publish.firstCall.args[1].lastRelease, lastRelease);
|
||||
t.deepEqual(publish.firstCall.args[1].commits[0].hash.substring(0, 7), commits[0].hash);
|
||||
@@ -140,15 +127,15 @@ test.serial('Use new gitHead, and recreate release notes if a publish plugin cre
|
||||
|
||||
const options = {
|
||||
branch: 'master',
|
||||
verifyConditions: callbackify(stub().resolves()),
|
||||
getLastRelease: callbackify(stub().resolves(lastRelease)),
|
||||
analyzeCommits: callbackify(stub().resolves(nextRelease.type)),
|
||||
verifyRelease: callbackify(stub().resolves()),
|
||||
generateNotes: callbackify(generateNotes),
|
||||
publish: [callbackify(publish1), callbackify(publish2)],
|
||||
repositoryUrl: 'git@hostname.com:owner/module.git',
|
||||
verifyConditions: stub().resolves(),
|
||||
getLastRelease: stub().resolves(lastRelease),
|
||||
analyzeCommits: stub().resolves(nextRelease.type),
|
||||
verifyRelease: stub().resolves(),
|
||||
generateNotes,
|
||||
publish: [publish1, publish2],
|
||||
};
|
||||
|
||||
await writeJson('./package.json', {});
|
||||
await t.context.semanticRelease(options);
|
||||
|
||||
t.true(generateNotes.calledTwice);
|
||||
@@ -173,7 +160,6 @@ test.serial('Dry-run skips verifyConditions and publish', async t => {
|
||||
// Add new commits to the master branch
|
||||
commits = (await gitCommits(['Second'])).concat(commits);
|
||||
|
||||
const name = 'package-name';
|
||||
const lastRelease = {version: '1.0.0', gitHead: commits[commits.length - 1].hash, gitTag: 'v1.0.0'};
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const notes = 'Release notes';
|
||||
@@ -188,17 +174,14 @@ test.serial('Dry-run skips verifyConditions and publish', async t => {
|
||||
const options = {
|
||||
dryRun: true,
|
||||
branch: 'master',
|
||||
verifyConditions: callbackify(verifyConditions),
|
||||
getLastRelease: callbackify(getLastRelease),
|
||||
analyzeCommits: callbackify(analyzeCommits),
|
||||
verifyRelease: callbackify(verifyRelease),
|
||||
generateNotes: callbackify(generateNotes),
|
||||
publish: callbackify(publish),
|
||||
repositoryUrl: 'git@hostname.com:owner/module.git',
|
||||
verifyConditions,
|
||||
getLastRelease,
|
||||
analyzeCommits,
|
||||
verifyRelease,
|
||||
generateNotes,
|
||||
publish,
|
||||
};
|
||||
const pkg = {name, version: '0.0.0-dev'};
|
||||
normalizeData(pkg);
|
||||
|
||||
await writeJson('./package.json', pkg);
|
||||
|
||||
await t.context.semanticRelease(options);
|
||||
|
||||
@@ -209,3 +192,100 @@ test.serial('Dry-run skips verifyConditions and publish', async t => {
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.true(publish.notCalled);
|
||||
});
|
||||
|
||||
test.serial('Accept "undefined" values for the "getLastRelease" and "generateNotes" plugins', 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(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
|
||||
const lastRelease = {gitHead: undefined, gitTag: undefined, version: undefined};
|
||||
const nextRelease = {type: 'major', version: '2.0.0', gitHead: await getGitHead(), gitTag: 'v2.0.0'};
|
||||
const verifyConditions = stub().resolves();
|
||||
const getLastRelease = stub().resolves();
|
||||
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||
const verifyRelease = stub().resolves();
|
||||
const generateNotes = stub().resolves();
|
||||
const publish = stub().resolves();
|
||||
|
||||
const options = {
|
||||
branch: 'master',
|
||||
repositoryUrl: 'git@hostname.com:owner/module.git',
|
||||
verifyConditions: [verifyConditions],
|
||||
getLastRelease,
|
||||
analyzeCommits,
|
||||
verifyRelease,
|
||||
generateNotes,
|
||||
publish,
|
||||
};
|
||||
|
||||
await t.context.semanticRelease(options);
|
||||
|
||||
t.true(getLastRelease.calledOnce);
|
||||
|
||||
t.true(analyzeCommits.calledOnce);
|
||||
t.deepEqual(analyzeCommits.firstCall.args[1].lastRelease, lastRelease);
|
||||
|
||||
t.true(verifyRelease.calledOnce);
|
||||
t.deepEqual(verifyRelease.firstCall.args[1].lastRelease, lastRelease);
|
||||
|
||||
t.true(generateNotes.calledOnce);
|
||||
t.deepEqual(generateNotes.firstCall.args[1].lastRelease, lastRelease);
|
||||
|
||||
t.true(publish.calledOnce);
|
||||
t.deepEqual(publish.firstCall.args[1].lastRelease, lastRelease);
|
||||
t.falsy(publish.firstCall.args[1].nextRelease.notes);
|
||||
});
|
||||
|
||||
test.serial('Throw SemanticReleaseError if not running from a git repository', async t => {
|
||||
// Set the current working directory to a temp directory
|
||||
process.chdir(tempy.directory());
|
||||
|
||||
const error = await t.throws(t.context.semanticRelease());
|
||||
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOGITREPO');
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
});
|
||||
|
||||
test.serial('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found', async t => {
|
||||
// Create a git repository, set the current working directory at the root of the repo
|
||||
await gitRepo();
|
||||
|
||||
const error = await t.throws(t.context.semanticRelease());
|
||||
|
||||
// Verify error code and type
|
||||
t.is(error.code, 'ENOREPOURL');
|
||||
t.is(error.name, 'SemanticReleaseError');
|
||||
});
|
||||
|
||||
test.serial('Throw an Error if returns an unexpected value', 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(['First']);
|
||||
// Create the tag corresponding to version 1.0.0
|
||||
await gitTagVersion('v1.0.0');
|
||||
// Add new commits to the master branch
|
||||
await gitCommits(['Second']);
|
||||
|
||||
const verifyConditions = stub().resolves();
|
||||
const getLastRelease = stub().resolves('string');
|
||||
|
||||
const options = {
|
||||
branch: 'master',
|
||||
repositoryUrl: 'git@hostname.com:owner/module.git',
|
||||
verifyConditions: [verifyConditions],
|
||||
getLastRelease,
|
||||
};
|
||||
|
||||
const error = await t.throws(t.context.semanticRelease(options), Error);
|
||||
|
||||
// Verify error message
|
||||
t.regex(error.message, new RegExp(DEFINITIONS.getLastRelease.output.message));
|
||||
t.regex(error.message, /Received: 'string'/);
|
||||
});
|
||||
|
||||
+85
-59
@@ -3,58 +3,83 @@ import {writeJson, readJson} from 'fs-extra';
|
||||
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 npmRegistry from './helpers/npm-registry';
|
||||
import semanticRelease from '..';
|
||||
|
||||
// Environment variables used with cli
|
||||
/* eslint camelcase: ["error", {properties: "never"}] */
|
||||
|
||||
// Environment variables used with semantic-release cli (similar to what a user would setup)
|
||||
const env = {
|
||||
npm_config_registry: registry.uri,
|
||||
GH_TOKEN: 'github_token',
|
||||
GITHUB_URL: mockServer.url,
|
||||
NPM_EMAIL: 'integration@test.com',
|
||||
NPM_USERNAME: 'integration',
|
||||
NPM_PASSWORD: 'suchsecure',
|
||||
};
|
||||
// Environment variables used only for the local npm command used to do verification
|
||||
const testEnv = Object.assign({}, process.env, {
|
||||
npm_config_registry: npmRegistry.url,
|
||||
NPM_EMAIL: 'integration@test.com',
|
||||
LEGACY_TOKEN: Buffer.from(`${process.env.NPM_USERNAME}:${process.env.NPM_PASSWORD}`, 'utf8').toString('base64'),
|
||||
});
|
||||
const cli = require.resolve('../bin/semantic-release');
|
||||
const pluginError = require.resolve('./fixtures/plugin-error');
|
||||
const pluginInheritedError = require.resolve('./fixtures/plugin-error-inherited');
|
||||
|
||||
test.before(async t => {
|
||||
test.before(async () => {
|
||||
// Start the local NPM registry
|
||||
await npmRegistry.start();
|
||||
// Start Mock Server
|
||||
await mockServer.start();
|
||||
// Start the local NPM registry
|
||||
await registry.start();
|
||||
});
|
||||
|
||||
test.beforeEach(async t => {
|
||||
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();
|
||||
|
||||
// Delete env paramaters that could have been set on the machine running the tests
|
||||
delete process.env.NPM_TOKEN;
|
||||
delete process.env.NPM_USERNAME;
|
||||
delete process.env.NPM_PASSWORD;
|
||||
delete process.env.NPM_EMAIL;
|
||||
delete process.env.GH_TOKEN;
|
||||
delete process.env.GITHUB_TOKEN;
|
||||
delete process.env.GH_URL;
|
||||
delete process.env.GITHUB_URL;
|
||||
delete process.env.GH_PREFIX;
|
||||
delete process.env.GITHUB_PREFIX;
|
||||
// Delete all `npm_config` environment variable set by CI as they take precedence over the `.npmrc` because the process that runs the tests is started before the `.npmrc` is created
|
||||
for (let i = 0, keys = Object.keys(process.env); i < keys.length; i++) {
|
||||
if (keys[i].startsWith('npm_config')) {
|
||||
delete process.env[keys[i]];
|
||||
}
|
||||
}
|
||||
// Disable logs during tests
|
||||
t.context.log = stub(console, 'log');
|
||||
t.context.error = stub(console, 'error');
|
||||
t.context.stdout = stub(process.stdout, 'write');
|
||||
t.context.stderr = stub(process.stderr, 'write');
|
||||
});
|
||||
|
||||
test.afterEach.always(async t => {
|
||||
test.afterEach.always(t => {
|
||||
// Restore process.env
|
||||
process.env = Object.assign({}, t.context.env);
|
||||
// Restore the current working directory
|
||||
process.chdir(t.context.cwd);
|
||||
|
||||
// Restore the logs
|
||||
t.context.log.restore();
|
||||
t.context.error.restore();
|
||||
t.context.stdout.restore();
|
||||
t.context.stderr.restore();
|
||||
});
|
||||
|
||||
test.after.always(async t => {
|
||||
await mockServer.stop();
|
||||
test.after.always(async () => {
|
||||
// Stop the local NPM registry
|
||||
await registry.stop();
|
||||
await npmRegistry.stop();
|
||||
// Stop Mock Server
|
||||
await mockServer.stop();
|
||||
});
|
||||
|
||||
test.serial('Release patch, minor and major versions', async t => {
|
||||
@@ -69,12 +94,12 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
||||
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
|
||||
publishConfig: {registry: registry.uri},
|
||||
publishConfig: {registry: npmRegistry.url},
|
||||
});
|
||||
// Create a npm-shrinkwrap.json file
|
||||
await execa('npm', ['shrinkwrap'], {env});
|
||||
await execa('npm', ['shrinkwrap'], {env: testEnv});
|
||||
|
||||
/** No release **/
|
||||
/* No release */
|
||||
|
||||
let verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -88,7 +113,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
t.regex(stdout, /There are no relevant changes, so no new version is released/);
|
||||
t.is(code, 0);
|
||||
|
||||
/** Initial release **/
|
||||
/* Initial release */
|
||||
let version = '1.0.0';
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -123,7 +148,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
|
||||
// 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
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
@@ -133,7 +158,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/** Patch release **/
|
||||
/* Patch release */
|
||||
version = '1.0.1';
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -168,7 +193,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
@@ -178,7 +203,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/** Minor release **/
|
||||
/* Minor release */
|
||||
version = '1.1.0';
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -213,7 +238,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
@@ -223,7 +248,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
await mockServer.verify(createRefMock);
|
||||
await mockServer.verify(createReleaseMock);
|
||||
|
||||
/** Major release **/
|
||||
/* Major release */
|
||||
version = '2.0.0';
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -258,7 +283,7 @@ test.serial('Release patch, minor and major versions', async t => {
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
[, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env})).stdout
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
@@ -282,10 +307,10 @@ test.serial('Release versions from a packed git repository, using tags to determ
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git@github.com:${owner}/${packageName}.git`},
|
||||
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
|
||||
publishConfig: {registry: registry.uri},
|
||||
publishConfig: {registry: npmRegistry.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
/* Initial release */
|
||||
let version = '1.0.0';
|
||||
let verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -317,7 +342,7 @@ test.serial('Release versions from a packed git repository, using tags to determ
|
||||
// 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;
|
||||
let releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env: testEnv})).stdout;
|
||||
t.is(releasedVersion, version);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
await mockServer.verify(verifyMock);
|
||||
@@ -327,7 +352,7 @@ test.serial('Release versions from a packed git repository, using tags to determ
|
||||
await gitTagVersion(`v${version}`);
|
||||
t.log(`Create git tag v${version}`);
|
||||
|
||||
/** Patch release **/
|
||||
/* Patch release */
|
||||
version = '1.0.1';
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -358,7 +383,7 @@ test.serial('Release versions from a packed git repository, using tags to determ
|
||||
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;
|
||||
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env: testEnv})).stdout;
|
||||
t.is(releasedVersion, version);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
await mockServer.verify(verifyMock);
|
||||
@@ -397,10 +422,10 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
||||
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
|
||||
publishConfig: {registry: registry.uri},
|
||||
publishConfig: {registry: npmRegistry.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
/* Initial release */
|
||||
let version = '1.0.0';
|
||||
let verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
@@ -432,7 +457,7 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
||||
|
||||
// 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
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
|
||||
);
|
||||
const head = await gitHead();
|
||||
t.is(releasedGitHead, head);
|
||||
@@ -446,12 +471,12 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
||||
await gitTagVersion(`v${version}`);
|
||||
t.log(`Create git tag v${version}`);
|
||||
|
||||
/** Rewrite sha of commit used for release **/
|
||||
/* Rewrite sha of commit used for release */
|
||||
|
||||
t.log('Amend release commit');
|
||||
const {hash} = await gitAmmendCommit('feat: Initial commit');
|
||||
|
||||
/** Patch release **/
|
||||
/* Patch release */
|
||||
verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||
@@ -473,7 +498,7 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
||||
)
|
||||
);
|
||||
|
||||
/** Create a tag to recover and redo release **/
|
||||
/* Create a tag to recover and redo release */
|
||||
|
||||
t.log(`Create git tag v${version} to recover`);
|
||||
await gitTagVersion(`v${version}`, hash);
|
||||
@@ -507,7 +532,7 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
||||
t.is((await readJson('./package.json')).version, version);
|
||||
|
||||
// Retrieve the published package from the registry and check version and gitHead
|
||||
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env})).stdout;
|
||||
releasedVersion = (await execa('npm', ['show', packageName, 'version'], {env: testEnv})).stdout;
|
||||
t.is(releasedVersion, version);
|
||||
t.log(`+ released ${releasedVersion}`);
|
||||
await mockServer.verify(verifyMock);
|
||||
@@ -526,14 +551,15 @@ test.serial('Dry-run', async t => {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
||||
publishConfig: {registry: npmRegistry.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
/* 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});
|
||||
const {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/);
|
||||
@@ -554,15 +580,15 @@ test.serial('Pass options via CLI arguments', async t => {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
||||
publishConfig: {registry: registry.uri},
|
||||
publishConfig: {registry: npmRegistry.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
let version = '1.0.0';
|
||||
/* Initial release */
|
||||
const version = '1.0.0';
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release');
|
||||
let {stdout, code} = await execa(
|
||||
const {stdout, code} = await execa(
|
||||
cli,
|
||||
['--verify-conditions', '@semantic-release/npm', '--publish', '@semantic-release/npm', '--debug'],
|
||||
{env}
|
||||
@@ -574,8 +600,8 @@ test.serial('Pass options via CLI arguments', async t => {
|
||||
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
|
||||
const [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
@@ -594,22 +620,22 @@ test.serial('Run via JS API', async t => {
|
||||
name: packageName,
|
||||
version: '0.0.0-dev',
|
||||
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
||||
publishConfig: {registry: registry.uri},
|
||||
publishConfig: {registry: npmRegistry.url},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
let version = '1.0.0';
|
||||
let verifyMock = await mockServer.mock(
|
||||
/* Initial release */
|
||||
const version = '1.0.0';
|
||||
const verifyMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}`,
|
||||
{headers: [{name: 'Authorization', values: [`token ${githubToken}`]}]},
|
||||
{body: {permissions: {push: true}}, method: 'GET'}
|
||||
);
|
||||
let createRefMock = await mockServer.mock(
|
||||
const createRefMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/git/refs`,
|
||||
{body: {ref: `refs/tags/v${version}`}, headers: [{name: 'Authorization', values: [`token ${githubToken}`]}]},
|
||||
{body: {ref: `refs/tags/${version}`}}
|
||||
);
|
||||
let createReleaseMock = await mockServer.mock(
|
||||
const createReleaseMock = await mockServer.mock(
|
||||
`/repos/${owner}/${packageName}/releases`,
|
||||
{
|
||||
body: {tag_name: `v${version}`, target_commitish: 'master', name: `v${version}`},
|
||||
@@ -636,8 +662,8 @@ test.serial('Run via JS API', async t => {
|
||||
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
|
||||
const [, releasedVersion, releasedGitHead] = /^version = '(.+)'\s+gitHead = '(.+)'$/.exec(
|
||||
(await execa('npm', ['show', packageName, 'version', 'gitHead'], {env: testEnv})).stdout
|
||||
);
|
||||
t.is(releasedVersion, version);
|
||||
t.is(releasedGitHead, await gitHead());
|
||||
@@ -662,11 +688,11 @@ test.serial('Log unexpected errors from plugins and exit with 1', async t => {
|
||||
release: {verifyConditions: pluginError},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
/* 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});
|
||||
const {stderr, code} = await execa(cli, [], {env, reject: false});
|
||||
// Verify the type and message are logged
|
||||
t.regex(stderr, /Error: a/);
|
||||
// Verify the the stacktrace is logged
|
||||
@@ -690,11 +716,11 @@ test.serial('Log errors inheriting SemanticReleaseError and exit with 0', async
|
||||
release: {verifyConditions: pluginInheritedError},
|
||||
});
|
||||
|
||||
/** Initial release **/
|
||||
/* Initial release */
|
||||
t.log('Commit a feature');
|
||||
await gitCommits(['feat: Initial commit']);
|
||||
t.log('$ semantic-release');
|
||||
let {stdout, code} = await execa(cli, [], {env, reject: false});
|
||||
const {stdout, code} = await execa(cli, [], {env, reject: false});
|
||||
// Verify the type and message are logged
|
||||
t.regex(stdout, /EINHERITED Inherited error/);
|
||||
t.is(code, 0);
|
||||
@@ -702,21 +728,21 @@ test.serial('Log errors inheriting SemanticReleaseError and exit with 0', async
|
||||
|
||||
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});
|
||||
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');
|
||||
let {stdout, code} = await execa(cli, ['--help'], {env});
|
||||
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');
|
||||
let {stderr, code} = await execa(cli, ['--unknown-option'], {env, reject: false});
|
||||
const {stderr, code} = await execa(cli, ['--unknown-option'], {env, reject: false});
|
||||
t.regex(stderr, /unknown option/);
|
||||
t.is(code, 1);
|
||||
});
|
||||
|
||||
@@ -89,12 +89,12 @@ test('The "analyzeCommits" plugin output must be either undefined or a valid sem
|
||||
t.true(definitions.analyzeCommits.output.validator('major'));
|
||||
});
|
||||
|
||||
test('The "generateNotes" plugin output must be a string', t => {
|
||||
t.false(definitions.generateNotes.output.validator());
|
||||
t.false(definitions.generateNotes.output.validator(null));
|
||||
test('The "generateNotes" plugin output, if defined, must be a string', t => {
|
||||
t.false(definitions.generateNotes.output.validator(1));
|
||||
t.false(definitions.generateNotes.output.validator({}));
|
||||
|
||||
t.true(definitions.generateNotes.output.validator());
|
||||
t.true(definitions.generateNotes.output.validator(null));
|
||||
t.true(definitions.generateNotes.output.validator(''));
|
||||
t.true(definitions.generateNotes.output.validator('string'));
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {callbackify} from 'util';
|
||||
import test from 'ava';
|
||||
import {noop} from 'lodash';
|
||||
import {stub, match} from 'sinon';
|
||||
@@ -39,21 +38,21 @@ test('Normalize and load plugin that retuns multiple functions', t => {
|
||||
|
||||
test('Wrap plugin in a function that validate the output of the plugin', async t => {
|
||||
const pluginFunction = stub().resolves(1);
|
||||
const plugin = normalize('', callbackify(pluginFunction), t.context.logger, {
|
||||
const plugin = normalize('', pluginFunction, t.context.logger, {
|
||||
validator: output => output === 1,
|
||||
message: 'The output must be 1',
|
||||
message: 'The output must be 1.',
|
||||
});
|
||||
|
||||
await t.notThrows(plugin());
|
||||
|
||||
pluginFunction.resolves(2);
|
||||
const error = await t.throws(plugin());
|
||||
const error = await t.throws(plugin(), Error);
|
||||
t.is(error.message, 'The output must be 1. Received: 2');
|
||||
});
|
||||
|
||||
test('Plugin is called with "pluginConfig" (omitting "path") and input', async t => {
|
||||
const pluginFunction = stub().resolves();
|
||||
const conf = {path: callbackify(pluginFunction), conf: 'confValue'};
|
||||
const conf = {path: pluginFunction, conf: 'confValue'};
|
||||
const plugin = normalize('', conf, t.context.logger);
|
||||
await plugin('param');
|
||||
|
||||
@@ -61,9 +60,8 @@ test('Plugin is called with "pluginConfig" (omitting "path") and input', async t
|
||||
});
|
||||
|
||||
test('Prevent plugins to modify "pluginConfig"', async t => {
|
||||
const pluginFunction = stub().callsFake((pluginConfig, options, cb) => {
|
||||
const pluginFunction = stub().callsFake(pluginConfig => {
|
||||
pluginConfig.conf.subConf = 'otherConf';
|
||||
cb();
|
||||
});
|
||||
const conf = {path: pluginFunction, conf: {subConf: 'originalConf'}};
|
||||
const plugin = normalize('', conf, t.context.logger);
|
||||
@@ -73,9 +71,8 @@ test('Prevent plugins to modify "pluginConfig"', async t => {
|
||||
});
|
||||
|
||||
test('Prevent plugins to modify its input', async t => {
|
||||
const pluginFunction = stub().callsFake((pluginConfig, options, cb) => {
|
||||
const pluginFunction = stub().callsFake((pluginConfig, options) => {
|
||||
options.param.subParam = 'otherParam';
|
||||
cb();
|
||||
});
|
||||
const input = {param: {subParam: 'originalSubParam'}};
|
||||
const plugin = normalize('', pluginFunction, t.context.logger);
|
||||
@@ -84,7 +81,7 @@ test('Prevent plugins to modify its input', async t => {
|
||||
t.is(input.param.subParam, 'originalSubParam');
|
||||
});
|
||||
|
||||
test('Return noop if the plugin is not defined', async t => {
|
||||
test('Return noop if the plugin is not defined', t => {
|
||||
const plugin = normalize();
|
||||
|
||||
t.is(plugin, noop);
|
||||
@@ -106,8 +103,8 @@ test('Always pass a defined "pluginConfig" for plugin defined with path', async
|
||||
t.deepEqual(pluginResult.pluginConfig, {});
|
||||
});
|
||||
|
||||
test('Throws an error if the plugin return an object without the expected plugin function', async t => {
|
||||
const error = t.throws(() => normalize('inexistantPlugin', './test/fixtures/multi-plugin', t.context.logger));
|
||||
test('Throws an error if the plugin return an object without the expected plugin function', t => {
|
||||
const error = t.throws(() => normalize('inexistantPlugin', './test/fixtures/multi-plugin', t.context.logger), Error);
|
||||
|
||||
t.is(
|
||||
error.message,
|
||||
|
||||
@@ -32,7 +32,7 @@ test('Stop execution and throw error is a step rejects', async t => {
|
||||
const step2 = stub().throws(new Error('test error'));
|
||||
const step3 = stub().resolves(3);
|
||||
|
||||
const error = await t.throws(pipeline([step1, step2, step3])(0));
|
||||
const error = await t.throws(pipeline([step1, step2, step3])(0), Error);
|
||||
t.is(error.message, 'test error');
|
||||
t.true(step1.calledWith(0));
|
||||
t.true(step2.calledWith(0));
|
||||
|
||||
@@ -9,8 +9,8 @@ test.beforeEach(t => {
|
||||
});
|
||||
|
||||
test('Export default plugins', t => {
|
||||
// Call the plugin module
|
||||
const plugins = getPlugins({}, t.context.logger);
|
||||
|
||||
// Verify the module returns a function for each plugin
|
||||
t.is(typeof plugins.verifyConditions, 'function');
|
||||
t.is(typeof plugins.getLastRelease, 'function');
|
||||
@@ -21,7 +21,6 @@ test('Export default plugins', t => {
|
||||
});
|
||||
|
||||
test('Export plugins based on config', t => {
|
||||
// Call the plugin module
|
||||
const plugins = getPlugins(
|
||||
{
|
||||
verifyConditions: ['./test/fixtures/plugin-noop', {path: './test/fixtures/plugin-noop'}],
|
||||
@@ -31,6 +30,7 @@ test('Export plugins based on config', t => {
|
||||
},
|
||||
t.context.logger
|
||||
);
|
||||
|
||||
// Verify the module returns a function for each plugin
|
||||
t.is(typeof plugins.verifyConditions, 'function');
|
||||
t.is(typeof plugins.getLastRelease, 'function');
|
||||
@@ -40,8 +40,29 @@ test('Export plugins based on config', t => {
|
||||
t.is(typeof plugins.publish, 'function');
|
||||
});
|
||||
|
||||
test('Throw an error if plugin configuration is invalid', t => {
|
||||
const error = t.throws(() => getPlugins({verifyConditions: {}}, t.context.logger));
|
||||
test('Use default when only options are passed for a single plugin', t => {
|
||||
const plugins = getPlugins({getLastRelease: {}, analyzeCommits: {}}, t.context.logger);
|
||||
|
||||
// Verify the module returns a function for each plugin
|
||||
t.is(typeof plugins.getLastRelease, 'function');
|
||||
t.is(typeof plugins.analyzeCommits, 'function');
|
||||
});
|
||||
|
||||
test('Throw an error if plugin configuration is missing a path for plugin pipeline', t => {
|
||||
const error = t.throws(() => getPlugins({verifyConditions: {}}, t.context.logger), Error);
|
||||
|
||||
t.is(
|
||||
error.message,
|
||||
'The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a path property.'
|
||||
);
|
||||
});
|
||||
|
||||
test('Throw an error if an array of plugin configuration is missing a path for plugin pipeline', t => {
|
||||
const error = t.throws(
|
||||
() => getPlugins({verifyConditions: [{path: '@semantic-release/npm'}, {}]}, t.context.logger),
|
||||
Error
|
||||
);
|
||||
|
||||
t.is(
|
||||
error.message,
|
||||
'The "verifyConditions" plugin, if defined, must be a single or an array of plugins definition. A plugin definition is either a string or an object with a path property.'
|
||||
|
||||
Reference in New Issue
Block a user