Compare commits
@@ -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`
|
||||
|
||||
@@ -290,6 +287,8 @@ Being able to write code for just the most recent node versions greatly simplifi
|
||||
|
||||
For a special purpose tool like `semantic-release`, that's only meant to be used in controlled CI environments, we think it's okay to have such a high version requirement. As `semantic-release` handles package publishing we expect almost every project to have at least one build job running node 8 already – and that's all it takes. Even if that's not that case `semantic-release` can still be executed with the help of [npx](https://www.npmjs.com/package/npx) (`npx -p node@8 npm run semantic-release`).
|
||||
|
||||
Please see our [Node Support Policy](#node-support-policy) for our long-term promise for supporting Node.
|
||||
|
||||
## Badge
|
||||
|
||||
Use this in one of your projects? Include one of these badges in your README.md to let people know that your package is published using `semantic-release`.
|
||||
@@ -312,6 +311,20 @@ Use this in one of your projects? Include one of these badges in your README.md
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||
```
|
||||
|
||||
## Node Support Policy
|
||||
|
||||
We only support [Long-Term Support](https://github.com/nodejs/Release) versions of Node starting with [Node 8.9.0 (LTS)](https://nodejs.org/en/blog/release/v8.9.0/).
|
||||
|
||||
We specifically limit our support to LTS versions of Node, not because this package won't work on other versions, but because we have a limited amount of time, and supporting LTS offers the greatest return on that investment.
|
||||
|
||||
It's possible this package will work correctly on newer versions of Node. It may even be possible to use this package on older versions of Node, though that's more unlikely as we'll make every effort to take advantage of features available in the oldest LTS version we support.
|
||||
|
||||
As each Node LTS version reaches its end-of-life we will remove that version from the node engines property of our package's package.json file. Removing a Node version is considered a breaking change and will entail the publishing of a new major version of this package. We will not accept any requests to support an end-of-life version of Node. Any merge requests or issues supporting an end-of-life version of Node will be closed.
|
||||
|
||||
We will accept code that allows this package to run on newer, non-LTS, versions of Node. Furthermore, we will attempt to ensure our own changes work on the latest version of Node. To help in that commitment, our continuous integration setup runs against all LTS versions of Node in addition the most recent Node release; called current.
|
||||
|
||||
JavaScript package managers should allow you to install this package with any version of Node, with, at most, a warning if your version of Node does not fall within the range specified by our node engines property. If you encounter issues installing this package, please report the issue to your package manager.
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+5
-10
@@ -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 {
|
||||
@@ -83,24 +84,18 @@ function noGitHeadMessage(branch, version) {
|
||||
This means semantic-release can not extract the commits between now and then.
|
||||
This is usually caused by releasing from outside the repository directory or with innaccessible git metadata.
|
||||
|
||||
You can recover from this error by creating a tag for the version "${
|
||||
version
|
||||
}" on the commit corresponding to this release:
|
||||
You can recover from this error by creating a tag for the version "${version}" on the commit corresponding to this release:
|
||||
$ git tag -f v${version} <commit sha1 corresponding to last release>
|
||||
$ git push -f --tags origin ${branch}
|
||||
`;
|
||||
}
|
||||
|
||||
function notInHistoryMessage(gitHead, branch, version) {
|
||||
return `The commit the last release of this package was derived from is not in the direct history of the "${
|
||||
branch
|
||||
}" branch.
|
||||
return `The commit the last release of this package was derived from is not in the direct history of the "${branch}" branch.
|
||||
This means semantic-release can not extract the commits between now and then.
|
||||
This is usually caused by force pushing, releasing from an unrelated branch, or using an already existing package name.
|
||||
|
||||
You can recover from this error by restoring the commit "${gitHead}" or by creating a tag for the version "${
|
||||
version
|
||||
}" on the commit corresponding to this release:
|
||||
You can recover from this error by restoring the commit "${gitHead}" or by creating a tag for the version "${version}" on the commit corresponding to this release:
|
||||
$ git tag -f v${version || '<version>'} <commit sha1 corresponding to last release>
|
||||
$ git push -f --tags origin ${branch}
|
||||
`;
|
||||
|
||||
+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');
|
||||
|
||||
@@ -8,15 +8,15 @@ module.exports = (pluginType, pluginConfig, logger, validator) => {
|
||||
}
|
||||
const {path, ...config} = isString(pluginConfig) || isFunction(pluginConfig) ? {path: pluginConfig} : pluginConfig;
|
||||
if (!isFunction(pluginConfig)) {
|
||||
logger.log('Load plugin %s', path);
|
||||
logger.log('Load plugin %s from %s', pluginType, path);
|
||||
}
|
||||
const plugin = isFunction(path) ? path : importFrom.silent(__dirname, path) || importFrom(process.cwd(), path);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
+30
-35
@@ -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",
|
||||
"mockserver-client": "^2.0.0",
|
||||
"file-url": "^2.0.2",
|
||||
"fs-extra": "^4.0.2",
|
||||
"got": "^8.0.0",
|
||||
"js-yaml": "^3.10.0",
|
||||
"mockserver-client": "^5.1.1",
|
||||
"nock": "^9.0.2",
|
||||
"npm-registry-couchapp": "^2.6.12",
|
||||
"nyc": "^11.2.1",
|
||||
"p-map-series": "^1.0.0",
|
||||
"prettier": "~1.8.0",
|
||||
"p-retry": "^1.0.0",
|
||||
"prettier": "~1.9.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'/);
|
||||
});
|
||||
|
||||
+86
-62
@@ -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}`]}]},
|
||||
@@ -467,13 +492,11 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
||||
t.regex(
|
||||
stderr,
|
||||
new RegExp(
|
||||
`You can recover from this error by restoring the commit "${head}" or by creating a tag for the version "${
|
||||
version
|
||||
}" on the commit corresponding to this release`
|
||||
`You can recover from this error by restoring the commit "${head}" or by creating a tag for the version "${version}" on the commit corresponding to this release`
|
||||
)
|
||||
);
|
||||
|
||||
/** Create a tag to recover and redo release **/
|
||||
/* Create a tag to recover and redo release */
|
||||
|
||||
t.log(`Create git tag v${version} to recover`);
|
||||
await gitTagVersion(`v${version}`, hash);
|
||||
@@ -507,7 +530,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 +549,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 +578,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 +598,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 +618,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 +660,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 +686,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 +714,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 +726,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';
|
||||
@@ -11,17 +10,17 @@ test.beforeEach(t => {
|
||||
});
|
||||
|
||||
test('Normalize and load plugin from string', t => {
|
||||
const plugin = normalize('', './test/fixtures/plugin-noop', t.context.logger);
|
||||
const plugin = normalize('verifyConditions', './test/fixtures/plugin-noop', t.context.logger);
|
||||
|
||||
t.is(typeof plugin, 'function');
|
||||
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-noop'));
|
||||
t.true(t.context.log.calledWith(match.string, 'verifyConditions', './test/fixtures/plugin-noop'));
|
||||
});
|
||||
|
||||
test('Normalize and load plugin from object', t => {
|
||||
const plugin = normalize('', {path: './test/fixtures/plugin-noop'}, t.context.logger);
|
||||
const plugin = normalize('publish', {path: './test/fixtures/plugin-noop'}, t.context.logger);
|
||||
|
||||
t.is(typeof plugin, 'function');
|
||||
t.true(t.context.log.calledWith(match.string, './test/fixtures/plugin-noop'));
|
||||
t.true(t.context.log.calledWith(match.string, 'publish', './test/fixtures/plugin-noop'));
|
||||
});
|
||||
|
||||
test('Normalize and load plugin from function', t => {
|
||||
@@ -34,26 +33,26 @@ test('Normalize and load plugin that retuns multiple functions', t => {
|
||||
const plugin = normalize('verifyConditions', './test/fixtures/multi-plugin', t.context.logger);
|
||||
|
||||
t.is(typeof plugin, 'function');
|
||||
t.true(t.context.log.calledWith(match.string, './test/fixtures/multi-plugin'));
|
||||
t.true(t.context.log.calledWith(match.string, 'verifyConditions', './test/fixtures/multi-plugin'));
|
||||
});
|
||||
|
||||
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