feat: make semantic-release CI agnostic
- Remove `@semantic-release/condition-travis` from the default plugins - Verify the current branch in the core - Verify the build is not triggered by a PR in the core - Run in dry-run mode if not triggered on CI - Dry-run mode runs the `verifyConditions` plugins, allowing to detect configuration error locally - Return without error when no version has to be released due to no changes - Return without error if the build is triggered from a PR - Return without error if the current branch is not the configured branch - CLI return with exit code 1 if there is a `semanticReleaseError`, allowing to fail builds in case of config error, missing token etc... BREAKING CHANGE: `semantic-release` doesn't make sure it runs only on one Travis job anymore. The CI configuration has to be done such that `semantic-release` - runs only once per build - runs only after all tests are successful on every jobs of the build - runs on Node >=8 This can easily be done with [travis-deploy-once](https://github.com/semantic-release/travis-deploy-once). Migration Guide Modify your `.travis.yml` to use `travis-deploy-once`. Replace: ```yaml after_success: - npm run semantic-release ``` by: Replace ```yaml after_success: - npm install -g travis-deploy-once@4 - travis-deploy-once "npm run semantic-release" ```
This commit is contained in:
parent
996305d69c
commit
8d575654c2
@ -25,4 +25,5 @@ script:
|
|||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- npm run codecov
|
- npm run codecov
|
||||||
- npm run semantic-release
|
- npm install -g semantic-release/travis-deploy-once@4
|
||||||
|
- travis-deploy-once "npm run semantic-release"
|
||||||
|
@ -170,7 +170,7 @@ semantic-release
|
|||||||
These options are currently available:
|
These options are currently available:
|
||||||
- `branch`: The branch on which releases should happen. Default: `'master'`
|
- `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.
|
- `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
|
- `dry-run`: Dry-run mode, skip publishing, print next version and release notes
|
||||||
- `extends`: Array of module or files path containing a shareable configuration. Options defined via CLI or in the `release` property will take precedence over the one defined in a shareable configuration.
|
- `extends`: Array of module or files path containing a shareable configuration. Options defined via CLI or in the `release` property will take precedence over the one defined in a shareable configuration.
|
||||||
- `debug`: Output debugging information
|
- `debug`: Output debugging information
|
||||||
|
|
||||||
|
4
cli.js
4
cli.js
@ -48,12 +48,10 @@ module.exports = async () => {
|
|||||||
await require('.')(pickBy(program.opts(), value => !isUndefined(value)));
|
await require('.')(pickBy(program.opts(), value => !isUndefined(value)));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} 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
|
process.exitCode = 1;
|
||||||
// Otherwise it's an unexpected error (configuration issue, code issue, plugin issue etc...) and the cli will return 1
|
|
||||||
if (err.semanticRelease) {
|
if (err.semanticRelease) {
|
||||||
logger.log(`%s ${err.message}`, err.code);
|
logger.log(`%s ${err.message}`, err.code);
|
||||||
} else {
|
} else {
|
||||||
process.exitCode = 1;
|
|
||||||
logger.error('An error occurred while running semantic-release: %O', err);
|
logger.error('An error occurred while running semantic-release: %O', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
index.js
36
index.js
@ -1,6 +1,6 @@
|
|||||||
const marked = require('marked');
|
const marked = require('marked');
|
||||||
const TerminalRenderer = require('marked-terminal');
|
const TerminalRenderer = require('marked-terminal');
|
||||||
const SemanticReleaseError = require('@semantic-release/error');
|
const envCi = require('env-ci');
|
||||||
const getConfig = require('./lib/get-config');
|
const getConfig = require('./lib/get-config');
|
||||||
const getNextVersion = require('./lib/get-next-version');
|
const getNextVersion = require('./lib/get-next-version');
|
||||||
const getCommits = require('./lib/get-commits');
|
const getCommits = require('./lib/get-commits');
|
||||||
@ -8,19 +8,39 @@ const logger = require('./lib/logger');
|
|||||||
const {gitHead: getGitHead, isGitRepo} = require('./lib/git');
|
const {gitHead: getGitHead, isGitRepo} = require('./lib/git');
|
||||||
|
|
||||||
module.exports = async opts => {
|
module.exports = async opts => {
|
||||||
|
const {isCi, branch, isPr} = envCi();
|
||||||
|
|
||||||
|
if (!isCi && !opts.dryRun) {
|
||||||
|
logger.log('This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||||
|
opts.dryRun = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCi && isPr) {
|
||||||
|
logger.log('This run was triggered by a pull request and therefore a new version won’t be published.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!await isGitRepo()) {
|
if (!await isGitRepo()) {
|
||||||
throw new SemanticReleaseError('Semantic-release must run from a git repository', 'ENOGITREPO');
|
logger.error('Semantic-release must run from a git repository.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await getConfig(opts, logger);
|
const config = await getConfig(opts, logger);
|
||||||
const {plugins, options} = config;
|
const {plugins, options} = config;
|
||||||
|
|
||||||
|
if (branch !== options.branch) {
|
||||||
|
logger.log(
|
||||||
|
`This test run was triggered on the branch ${branch}, while semantic-release is configured to only publish from ${
|
||||||
|
options.branch
|
||||||
|
}, therefore a new version won’t be published.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
logger.log('Run automated release from branch %s', options.branch);
|
logger.log('Run automated release from branch %s', options.branch);
|
||||||
|
|
||||||
if (!options.dryRun) {
|
logger.log('Call plugin %s', 'verify-conditions');
|
||||||
logger.log('Call plugin %s', 'verify-conditions');
|
await plugins.verifyConditions({options, logger});
|
||||||
await plugins.verifyConditions({options, logger});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.log('Call plugin %s', 'get-last-release');
|
logger.log('Call plugin %s', 'get-last-release');
|
||||||
const {commits, lastRelease} = await getCommits(
|
const {commits, lastRelease} = await getCommits(
|
||||||
@ -32,7 +52,8 @@ module.exports = async opts => {
|
|||||||
logger.log('Call plugin %s', 'analyze-commits');
|
logger.log('Call plugin %s', 'analyze-commits');
|
||||||
const type = await plugins.analyzeCommits({options, logger, lastRelease, commits});
|
const type = await plugins.analyzeCommits({options, logger, lastRelease, commits});
|
||||||
if (!type) {
|
if (!type) {
|
||||||
throw new SemanticReleaseError('There are no relevant changes, so no new version is released.', 'ENOCHANGE');
|
logger.log('There are no relevant changes, so no new version is released.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const version = getNextVersion(type, lastRelease, logger);
|
const version = getNextVersion(type, lastRelease, logger);
|
||||||
const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: `v${version}`};
|
const nextRelease = {type, version, gitHead: await getGitHead(), gitTag: `v${version}`};
|
||||||
@ -67,4 +88,5 @@ module.exports = async opts => {
|
|||||||
});
|
});
|
||||||
logger.log('Published release: %s', nextRelease.version);
|
logger.log('Published release: %s', nextRelease.version);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@ const validatePluginConfig = conf => isString(conf) || isString(conf.path) || is
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
verifyConditions: {
|
verifyConditions: {
|
||||||
default: ['@semantic-release/npm', '@semantic-release/github', '@semantic-release/condition-travis'],
|
default: ['@semantic-release/npm', '@semantic-release/github'],
|
||||||
config: {
|
config: {
|
||||||
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
validator: conf => !conf || (isArray(conf) ? conf : [conf]).every(conf => validatePluginConfig(conf)),
|
||||||
message:
|
message:
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@semantic-release/commit-analyzer": "^5.0.0",
|
"@semantic-release/commit-analyzer": "^5.0.0",
|
||||||
"@semantic-release/condition-travis": "^7.0.0",
|
|
||||||
"@semantic-release/error": "^2.1.0",
|
"@semantic-release/error": "^2.1.0",
|
||||||
"@semantic-release/github": "^3.0.0",
|
"@semantic-release/github": "^3.0.0",
|
||||||
"@semantic-release/npm": "^2.0.0",
|
"@semantic-release/npm": "^2.0.0",
|
||||||
@ -25,6 +24,7 @@
|
|||||||
"commander": "^2.11.0",
|
"commander": "^2.11.0",
|
||||||
"cosmiconfig": "^3.1.0",
|
"cosmiconfig": "^3.1.0",
|
||||||
"debug": "^3.1.0",
|
"debug": "^3.1.0",
|
||||||
|
"env-ci": "^1.0.0",
|
||||||
"execa": "^0.8.0",
|
"execa": "^0.8.0",
|
||||||
"get-stream": "^3.0.0",
|
"get-stream": "^3.0.0",
|
||||||
"git-log-parser": "^1.2.0",
|
"git-log-parser": "^1.2.0",
|
||||||
|
@ -19,7 +19,6 @@ test.beforeEach(t => {
|
|||||||
t.context.log = stub();
|
t.context.log = stub();
|
||||||
t.context.error = stub();
|
t.context.error = stub();
|
||||||
t.context.logger = {log: t.context.log, error: t.context.error};
|
t.context.logger = {log: t.context.log, error: t.context.error};
|
||||||
t.context.semanticRelease = proxyquire('../index', {'./lib/logger': t.context.logger});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.afterEach.always(() => {
|
test.afterEach.always(() => {
|
||||||
@ -61,7 +60,11 @@ test.serial('Plugins are called with expected values', async t => {
|
|||||||
publish,
|
publish,
|
||||||
};
|
};
|
||||||
|
|
||||||
await t.context.semanticRelease(options);
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
t.truthy(await semanticRelease(options));
|
||||||
|
|
||||||
t.is(verifyConditions1.callCount, 1);
|
t.is(verifyConditions1.callCount, 1);
|
||||||
t.deepEqual(verifyConditions1.args[0][0], config);
|
t.deepEqual(verifyConditions1.args[0][0], config);
|
||||||
@ -140,7 +143,11 @@ test.serial('Use new gitHead, and recreate release notes if a publish plugin cre
|
|||||||
publish: [publish1, publish2],
|
publish: [publish1, publish2],
|
||||||
};
|
};
|
||||||
|
|
||||||
await t.context.semanticRelease(options);
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
t.truthy(await semanticRelease(options));
|
||||||
|
|
||||||
t.is(generateNotes.callCount, 2);
|
t.is(generateNotes.callCount, 2);
|
||||||
t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease);
|
t.deepEqual(generateNotes.args[0][1].nextRelease, nextRelease);
|
||||||
@ -154,7 +161,7 @@ test.serial('Use new gitHead, and recreate release notes if a publish plugin cre
|
|||||||
t.deepEqual(publish2.args[0][1].nextRelease, Object.assign({}, nextRelease, {notes}));
|
t.deepEqual(publish2.args[0][1].nextRelease, Object.assign({}, nextRelease, {notes}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Dry-run skips verifyConditions and publish', async t => {
|
test.serial('Dry-run skips publish', async t => {
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
await gitRepo();
|
await gitRepo();
|
||||||
// Add commits to the master branch
|
// Add commits to the master branch
|
||||||
@ -187,9 +194,62 @@ test.serial('Dry-run skips verifyConditions and publish', async t => {
|
|||||||
publish,
|
publish,
|
||||||
};
|
};
|
||||||
|
|
||||||
await t.context.semanticRelease(options);
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
t.truthy(await semanticRelease(options));
|
||||||
|
|
||||||
t.is(verifyConditions.callCount, 0);
|
t.not(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||||
|
t.is(verifyConditions.callCount, 1);
|
||||||
|
t.is(getLastRelease.callCount, 1);
|
||||||
|
t.is(analyzeCommits.callCount, 1);
|
||||||
|
t.is(verifyRelease.callCount, 1);
|
||||||
|
t.is(generateNotes.callCount, 1);
|
||||||
|
t.is(publish.callCount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial('Force a dry-run if not on a CI', 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
|
||||||
|
let commits = await gitCommits(['First']);
|
||||||
|
// Create the tag corresponding to version 1.0.0
|
||||||
|
await gitTagVersion('v1.0.0');
|
||||||
|
// Add new commits to the master branch
|
||||||
|
commits = (await gitCommits(['Second'])).concat(commits);
|
||||||
|
|
||||||
|
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 verifyConditions = stub().resolves();
|
||||||
|
const getLastRelease = stub().resolves(lastRelease);
|
||||||
|
const analyzeCommits = stub().resolves(nextRelease.type);
|
||||||
|
const verifyRelease = stub().resolves();
|
||||||
|
const generateNotes = stub().resolves(notes);
|
||||||
|
const publish = stub().resolves();
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
dryRun: false,
|
||||||
|
branch: 'master',
|
||||||
|
repositoryUrl: 'git@hostname.com:owner/module.git',
|
||||||
|
verifyConditions,
|
||||||
|
getLastRelease,
|
||||||
|
analyzeCommits,
|
||||||
|
verifyRelease,
|
||||||
|
generateNotes,
|
||||||
|
publish,
|
||||||
|
};
|
||||||
|
|
||||||
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: false, branch: 'master'}),
|
||||||
|
});
|
||||||
|
t.truthy(await semanticRelease(options));
|
||||||
|
|
||||||
|
t.is(t.context.log.args[0][0], 'This run was not triggered in a known CI environment, running in dry-run mode.');
|
||||||
|
t.is(verifyConditions.callCount, 1);
|
||||||
t.is(getLastRelease.callCount, 1);
|
t.is(getLastRelease.callCount, 1);
|
||||||
t.is(analyzeCommits.callCount, 1);
|
t.is(analyzeCommits.callCount, 1);
|
||||||
t.is(verifyRelease.callCount, 1);
|
t.is(verifyRelease.callCount, 1);
|
||||||
@ -227,7 +287,11 @@ test.serial('Accept "undefined" values for the "getLastRelease" and "generateNot
|
|||||||
publish,
|
publish,
|
||||||
};
|
};
|
||||||
|
|
||||||
await t.context.semanticRelease(options);
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
t.truthy(await semanticRelease(options));
|
||||||
|
|
||||||
t.is(getLastRelease.callCount, 1);
|
t.is(getLastRelease.callCount, 1);
|
||||||
|
|
||||||
@ -245,29 +309,121 @@ test.serial('Accept "undefined" values for the "getLastRelease" and "generateNot
|
|||||||
t.falsy(publish.args[0][1].nextRelease.notes);
|
t.falsy(publish.args[0][1].nextRelease.notes);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Throw SemanticReleaseError if not running from a git repository', async t => {
|
test.serial('Returns falsy value if not running from a git repository', async t => {
|
||||||
// Set the current working directory to a temp directory
|
// Set the current working directory to a temp directory
|
||||||
process.chdir(tempy.directory());
|
process.chdir(tempy.directory());
|
||||||
|
|
||||||
const error = await t.throws(t.context.semanticRelease());
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
// Verify error code and type
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
t.is(error.code, 'ENOGITREPO');
|
});
|
||||||
t.is(error.name, 'SemanticReleaseError');
|
t.falsy(await semanticRelease());
|
||||||
|
t.is(t.context.error.args[0][0], 'Semantic-release must run from a git repository.');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found', async t => {
|
test.serial('Returns falsy value if triggered by a PR', async t => {
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
await gitRepo();
|
await gitRepo();
|
||||||
|
|
||||||
const error = await t.throws(t.context.semanticRelease());
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: true}),
|
||||||
|
});
|
||||||
|
|
||||||
|
t.falsy(await semanticRelease({repositoryUrl: 'git@hostname.com:owner/module.git'}));
|
||||||
|
t.is(
|
||||||
|
t.context.log.args[0][0],
|
||||||
|
'This run was triggered by a pull request and therefore a new version won’t be published.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial('Returns falsy value if not running from the configured branch', async t => {
|
||||||
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
|
await gitRepo();
|
||||||
|
|
||||||
|
const verifyConditions = stub().resolves();
|
||||||
|
const getLastRelease = stub().resolves();
|
||||||
|
const analyzeCommits = stub().resolves();
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'other-branch', isPr: false}),
|
||||||
|
});
|
||||||
|
|
||||||
|
t.falsy(await semanticRelease(options));
|
||||||
|
t.is(
|
||||||
|
t.context.log.args[0][0],
|
||||||
|
'This test run was triggered on the branch other-branch, while semantic-release is configured to only publish from master, therefore a new version won’t be published.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial('Returns falsy value if there is no relevant changes', 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']);
|
||||||
|
|
||||||
|
const verifyConditions = stub().resolves();
|
||||||
|
const getLastRelease = stub().resolves();
|
||||||
|
const analyzeCommits = stub().resolves();
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
|
||||||
|
t.falsy(await semanticRelease(options));
|
||||||
|
t.is(analyzeCommits.callCount, 1);
|
||||||
|
t.is(verifyRelease.callCount, 0);
|
||||||
|
t.is(generateNotes.callCount, 0);
|
||||||
|
t.is(publish.callCount, 0);
|
||||||
|
t.is(t.context.log.args[6][0], 'There are no relevant changes, so no new version is released.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test.serial('Throw SemanticReleaseError if repositoryUrl is not set and cannot be found from repo config', async t => {
|
||||||
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
|
await gitRepo();
|
||||||
|
|
||||||
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
const error = await t.throws(semanticRelease());
|
||||||
|
|
||||||
// Verify error code and type
|
// Verify error code and type
|
||||||
t.is(error.code, 'ENOREPOURL');
|
t.is(error.code, 'ENOREPOURL');
|
||||||
t.is(error.name, 'SemanticReleaseError');
|
t.is(error.name, 'SemanticReleaseError');
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Throw an Error if returns an unexpected value', async t => {
|
test.serial('Throw an Error if plugin returns an unexpected value', async t => {
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
await gitRepo();
|
await gitRepo();
|
||||||
// Add commits to the master branch
|
// Add commits to the master branch
|
||||||
@ -287,7 +443,11 @@ test.serial('Throw an Error if returns an unexpected value', async t => {
|
|||||||
getLastRelease,
|
getLastRelease,
|
||||||
};
|
};
|
||||||
|
|
||||||
const error = await t.throws(t.context.semanticRelease(options), Error);
|
const semanticRelease = proxyquire('..', {
|
||||||
|
'./lib/logger': t.context.logger,
|
||||||
|
'env-ci': () => ({isCi: true, branch: 'master', isPr: false}),
|
||||||
|
});
|
||||||
|
const error = await t.throws(semanticRelease(options), Error);
|
||||||
|
|
||||||
// Verify error message
|
// Verify error message
|
||||||
t.regex(error.message, new RegExp(DEFINITIONS.getLastRelease.output.message));
|
t.regex(error.message, new RegExp(DEFINITIONS.getLastRelease.output.message));
|
||||||
|
@ -53,6 +53,12 @@ test.beforeEach(() => {
|
|||||||
delete process.env.GITHUB_URL;
|
delete process.env.GITHUB_URL;
|
||||||
delete process.env.GH_PREFIX;
|
delete process.env.GH_PREFIX;
|
||||||
delete process.env.GITHUB_PREFIX;
|
delete process.env.GITHUB_PREFIX;
|
||||||
|
|
||||||
|
process.env.TRAVIS = 'true';
|
||||||
|
process.env.CI = 'true';
|
||||||
|
process.env.TRAVIS_BRANCH = 'master';
|
||||||
|
process.env.TRAVIS_PULL_REQUEST = 'false';
|
||||||
|
|
||||||
// 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
|
// 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++) {
|
for (let i = 0, keys = Object.keys(process.env); i < keys.length; i++) {
|
||||||
if (keys[i].startsWith('npm_config')) {
|
if (keys[i].startsWith('npm_config')) {
|
||||||
@ -86,7 +92,6 @@ test.serial('Release patch, minor and major versions', async t => {
|
|||||||
name: packageName,
|
name: packageName,
|
||||||
version: '0.0.0-dev',
|
version: '0.0.0-dev',
|
||||||
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
||||||
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
|
|
||||||
publishConfig: {registry: npmRegistry.url},
|
publishConfig: {registry: npmRegistry.url},
|
||||||
});
|
});
|
||||||
// Create a npm-shrinkwrap.json file
|
// Create a npm-shrinkwrap.json file
|
||||||
@ -299,7 +304,6 @@ test.serial('Release versions from a packed git repository, using tags to determ
|
|||||||
name: packageName,
|
name: packageName,
|
||||||
version: '0.0.0-dev',
|
version: '0.0.0-dev',
|
||||||
repository: {url: `git@github.com:${owner}/${packageName}.git`},
|
repository: {url: `git@github.com:${owner}/${packageName}.git`},
|
||||||
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
|
|
||||||
publishConfig: {registry: npmRegistry.url},
|
publishConfig: {registry: npmRegistry.url},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -453,7 +457,6 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
|||||||
name: packageName,
|
name: packageName,
|
||||||
version: '0.0.0-dev',
|
version: '0.0.0-dev',
|
||||||
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
repository: {url: `git+https://github.com/${owner}/${packageName}`},
|
||||||
release: {verifyConditions: ['@semantic-release/github', '@semantic-release/npm']},
|
|
||||||
publishConfig: {registry: npmRegistry.url},
|
publishConfig: {registry: npmRegistry.url},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -520,7 +523,7 @@ test.serial('Create a tag as a recovery solution for "ENOTINHISTORY" error', asy
|
|||||||
({stderr, stdout, code} = await execa(cli, [], {env, reject: false}));
|
({stderr, stdout, code} = await execa(cli, [], {env, reject: false}));
|
||||||
|
|
||||||
t.log('Log "ENOTINHISTORY" message');
|
t.log('Log "ENOTINHISTORY" message');
|
||||||
t.is(code, 0);
|
t.is(code, 1);
|
||||||
t.regex(
|
t.regex(
|
||||||
stderr,
|
stderr,
|
||||||
new RegExp(
|
new RegExp(
|
||||||
@ -585,6 +588,11 @@ test.serial('Dry-run', async t => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* Initial release */
|
/* Initial release */
|
||||||
|
const verifyMock = await mockServer.mock(
|
||||||
|
`/repos/${owner}/${packageName}`,
|
||||||
|
{headers: [{name: 'Authorization', values: [`token ${env.GH_TOKEN}`]}]},
|
||||||
|
{body: {permissions: {push: true}}, method: 'GET'}
|
||||||
|
);
|
||||||
const version = '1.0.0';
|
const version = '1.0.0';
|
||||||
t.log('Commit a feature');
|
t.log('Commit a feature');
|
||||||
await gitCommits(['feat: Initial commit']);
|
await gitCommits(['feat: Initial commit']);
|
||||||
@ -597,6 +605,7 @@ test.serial('Dry-run', async t => {
|
|||||||
|
|
||||||
// Verify package.json and has not been modified
|
// Verify package.json and has not been modified
|
||||||
t.is((await readJson('./package.json')).version, '0.0.0-dev');
|
t.is((await readJson('./package.json')).version, '0.0.0-dev');
|
||||||
|
await mockServer.verify(verifyMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Pass options via CLI arguments', async t => {
|
test.serial('Pass options via CLI arguments', async t => {
|
||||||
@ -681,11 +690,7 @@ test.serial('Run via JS API', async t => {
|
|||||||
t.log('Commit a feature');
|
t.log('Commit a feature');
|
||||||
await gitCommits(['feat: Initial commit']);
|
await gitCommits(['feat: Initial commit']);
|
||||||
t.log('$ Call semantic-release via API');
|
t.log('$ Call semantic-release via API');
|
||||||
await semanticRelease({
|
await semanticRelease();
|
||||||
verifyConditions: [{path: '@semantic-release/github'}, '@semantic-release/npm'],
|
|
||||||
publish: [{path: '@semantic-release/github'}, '@semantic-release/npm'],
|
|
||||||
debug: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verify package.json and has been updated
|
// Verify package.json and has been updated
|
||||||
t.is((await readJson('./package.json')).version, version);
|
t.is((await readJson('./package.json')).version, version);
|
||||||
@ -731,7 +736,7 @@ test.serial('Log unexpected errors from plugins and exit with 1', async t => {
|
|||||||
t.is(code, 1);
|
t.is(code, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('Log errors inheriting SemanticReleaseError and exit with 0', async t => {
|
test.serial('Log errors inheriting SemanticReleaseError and exit with 1', async t => {
|
||||||
const packageName = 'test-inherited-error';
|
const packageName = 'test-inherited-error';
|
||||||
const owner = 'test-repo';
|
const owner = 'test-repo';
|
||||||
// Create a git repository, set the current working directory at the root of the repo
|
// Create a git repository, set the current working directory at the root of the repo
|
||||||
@ -752,7 +757,7 @@ test.serial('Log errors inheriting SemanticReleaseError and exit with 0', async
|
|||||||
const {stdout, code} = await execa(cli, [], {env, reject: false});
|
const {stdout, code} = await execa(cli, [], {env, reject: false});
|
||||||
// Verify the type and message are logged
|
// Verify the type and message are logged
|
||||||
t.regex(stdout, /EINHERITED Inherited error/);
|
t.regex(stdout, /EINHERITED Inherited error/);
|
||||||
t.is(code, 0);
|
t.is(code, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test.serial('CLI returns error code and prints help if called with a command', async t => {
|
test.serial('CLI returns error code and prints help if called with a command', async t => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user