Skip to content

Commit

Permalink
feat: add details to error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
pvdlg committed Feb 12, 2018
1 parent 0e5b372 commit 06979d8
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 27 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Add a comment to each GitHub issue or pull request resolved by the release.
The GitHub authentication configuration is **required** and can be set via
[environment variables](#environment-variables).

Follow the [Creating a personal access token for the command line](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) documentation to obtain an authentication token. The token has to be made available in your CI environment via the `GH_TOKEN` environment variable.
Follow the [Creating a personal access token for the command line](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) documentation to obtain an authentication token. The token has to be made available in your CI environment via the `GH_TOKEN` environment variable. The user associated with the token must have push permission to the repository.

### Environment variables

Expand Down
67 changes: 67 additions & 0 deletions lib/definitions/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const url = require('url');
const {inspect} = require('util');
const {isString} = require('lodash');
const pkg = require('../../package.json');

const homepage = url.format({...url.parse(pkg.homepage), ...{hash: null}});
const stringify = obj => (isString(obj) ? obj : inspect(obj, {breakLength: Infinity, depth: 2, maxArrayLength: 5}));
const linkify = file => `${homepage}/blob/master/${file}`;

module.exports = {
EINVALIDASSETS: ({assets}) => ({
message: 'Invalid `assets` option.',
details: `The [assets option](${linkify(
'README.md#assets'
)}) option must be an \`Array\` of \`Strings\` or \`Objects\` with a \`path\` property.
Your configuration for the \`assets\` option is \`${stringify(assets)}\`.`,
}),
EINVALIDSUCCESSCOMMENT: ({successComment}) => ({
message: 'Invalid `successComment` option.',
details: `The [successComment option](${linkify(
'README.md#successcomment'
)}) option, if defined, must be a non empty \`String\`.
Your configuration for the \`successComment\` option is \`${stringify(successComment)}\`.`,
}),
EINVALIDGITHUBURL: () => ({
message: 'The git repository URL is not a valid GitHub URL.',
details: `The **semantic-release** \`repositoryUrl\` option must a valid GitHub URL with the format \`<GitHub_or_GHE_URL>/<owner>/<repo>.git\`.
By default the \`repositoryUrl\` option is retrieved from the \`repository\` property of your \`package.json\` or the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) of the repository cloned by your CI environment.`,
}),
EMISSINGREPO: ({owner, repo}) => ({
message: `The repository ${owner}/${repo} doesn't exist.`,
details: `The **semantic-release** \`repositoryUrl\` option must refer to your GitHub repository. The repository must be accessible with the [GitHub API](https://developer.github.com/v3).
By default the \`repositoryUrl\` option is retrieved from the \`repository\` property of your \`package.json\` or the [git origin url](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes) of the repository cloned by your CI environment.
If you are using [GitHub Enterprise](https://enterprise.github.com) please make sure to configure the \`githubUrl\` and \`githubApiPathPrefix\` [options](${linkify(
'README.md#options'
)}).`,
}),
EGHNOPERMISSION: ({owner, repo}) => ({
message: `The GitHub token doesn't allow to push on the repository ${owner}/${repo}.`,
details: `The user associated with the [GitHub token](${linkify(
'README.md#github-authentication'
)}) configured in the \`GH_TOKEN\` or \`GITHUB_TOKEN\` environment variable must allows to push to the repository ${owner}/${repo}.
Please make sure the GitHub user associated with the token is an [owner](https://help.github.com/articles/permission-levels-for-a-user-account-repository/#owner-access-on-a-repository-owned-by-a-user-account) or a [collaborator](https://help.github.com/articles/permission-levels-for-a-user-account-repository/#collaborator-access-on-a-repository-owned-by-a-user-account) if the reposotory belong to a user account or has [write permissions](https://help.github.com/articles/managing-team-access-to-an-organization-repository) if the repository [belongs to an organization](https://help.github.com/articles/repository-permission-levels-for-an-organization).`,
}),
EINVALIDGHTOKEN: ({owner, repo}) => ({
message: 'Invalid GitHub token.',
details: `The [GitHub token](${linkify(
'README.md#github-authentication'
)}) configured in the \`GH_TOKEN\` or \`GITHUB_TOKEN\` environment variable must be a valid [personnal token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) allowing to push to the repository ${owner}/${repo}.
Please make sure to set the \`GH_TOKEN\` or \`GITHUB_TOKEN\` environment variable in your CI with the exact value of the GitHub personnal token.`,
}),
ENOGHTOKEN: ({owner, repo}) => ({
message: 'No GitHub token specified.',
details: `A [GitHub personnal token](${linkify(
'README.md#github-authentication'
)}) must be created and set in the \`GH_TOKEN\` or \`GITHUB_TOKEN\` environment variable on your CI environment.
Please make sure to create a [GitHub personnal token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line) and to set it in the \`GH_TOKEN\` or \`GITHUB_TOKEN\` environment variable on your CI environment. The token must allow to push to the repository ${owner}/${repo}.`,
}),
};
7 changes: 7 additions & 0 deletions lib/get-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const SemanticReleaseError = require('@semantic-release/error');
const ERROR_DEFINITIONS = require('./definitions/errors');

module.exports = (code, ctx = {}) => {
const {message, details} = ERROR_DEFINITIONS[code](ctx);
return new SemanticReleaseError(message, code, details);
};
31 changes: 8 additions & 23 deletions lib/verify.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ const {isString, isPlainObject, isUndefined, isArray} = require('lodash');
const parseGithubUrl = require('parse-github-url');
const urlJoin = require('url-join');
const AggregateError = require('aggregate-error');
const SemanticReleaseError = require('@semantic-release/error');
const resolveConfig = require('./resolve-config');
const getClient = require('./get-client');
const getError = require('./get-error');

const isNonEmptyString = value => isString(value) && value.trim();
const isStringOrStringArray = value => isNonEmptyString(value) || (isArray(value) && value.every(isNonEmptyString));
Expand All @@ -21,21 +21,11 @@ module.exports = async (pluginConfig, {options: {repositoryUrl}, logger}) => {
assets.every(asset => isStringOrStringArray(asset) || (isPlainObject(asset) && isStringOrStringArray(asset.path)))
)
) {
errors.push(
new SemanticReleaseError(
'The "assets" options must be an Array of Strings or Objects with a path property.',
'EINVALIDASSETS'
)
);
errors.push(getError('EINVALIDASSETS', {assets}));
}

if (!isUndefined(successComment) && successComment !== false && !isNonEmptyString(successComment)) {
errors.push(
new SemanticReleaseError(
'The "successComment" options, if defined, must be a non empty String.',
'EINVALIDSUCCESSCOMMENT'
)
);
errors.push(getError('EINVALIDSUCCESSCOMMENT', {successComment}));
}

if (githubUrl) {
Expand All @@ -46,7 +36,7 @@ module.exports = async (pluginConfig, {options: {repositoryUrl}, logger}) => {

const {name: repo, owner} = parseGithubUrl(repositoryUrl);
if (!owner || !repo) {
errors.push(new SemanticReleaseError('The git repository URL is not a valid GitHub URL.', 'EINVALIDGITURL'));
errors.push(getError('EINVALIDGITHUBURL'));
}

if (githubToken) {
Expand All @@ -55,24 +45,19 @@ module.exports = async (pluginConfig, {options: {repositoryUrl}, logger}) => {
try {
const {data: {permissions: {push}}} = await github.repos.get({repo, owner});
if (!push) {
errors.push(
new SemanticReleaseError(
`The github token doesn't allow to push on the repository ${owner}/${repo}.`,
'EGHNOPERMISSION'
)
);
errors.push(getError('EGHNOPERMISSION', {owner, repo}));
}
} catch (err) {
if (err.code === 401) {
errors.push(new SemanticReleaseError('Invalid GitHub token.', 'EINVALIDGHTOKEN'));
errors.push(getError('EINVALIDGHTOKEN', {owner, repo}));
} else if (err.code === 404) {
errors.push(new SemanticReleaseError(`The repository ${owner}/${repo} doesn't exist.`, 'EMISSINGREPO'));
errors.push(getError('EMISSINGREPO', {owner, repo}));
} else {
throw err;
}
}
} else {
errors.push(new SemanticReleaseError('No github token specified.', 'ENOGHTOKEN'));
errors.push(getError('ENOGHTOKEN', {owner, repo}));
}
if (errors.length > 0) {
throw new AggregateError(errors);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
],
"dependencies": {
"@octokit/rest": "^14.0.9",
"@semantic-release/error": "^2.1.0",
"@semantic-release/error": "^2.2.0",
"aggregate-error": "^1.0.0",
"debug": "^3.1.0",
"fs-extra": "^5.0.0",
Expand Down
2 changes: 1 addition & 1 deletion test/integration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ test.serial('Throw SemanticReleaseError if invalid config', async t => {
t.is(errors[1].name, 'SemanticReleaseError');
t.is(errors[1].code, 'EINVALIDSUCCESSCOMMENT');
t.is(errors[2].name, 'SemanticReleaseError');
t.is(errors[2].code, 'EINVALIDGITURL');
t.is(errors[2].code, 'EINVALIDGITHUBURL');
t.is(errors[3].name, 'SemanticReleaseError');
t.is(errors[3].code, 'ENOGHTOKEN');
});
Expand Down
2 changes: 1 addition & 1 deletion test/verify.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ test.serial('Throw SemanticReleaseError for invalid repositoryUrl', async t => {
const [error] = await t.throws(verify({}, {options: {repositoryUrl: 'invalid_url'}, logger: t.context.logger}));

t.is(error.name, 'SemanticReleaseError');
t.is(error.code, 'EINVALIDGITURL');
t.is(error.code, 'EINVALIDGITHUBURL');
});

test.serial("Throw SemanticReleaseError if token doesn't have the push permission on the repository", async t => {
Expand Down

0 comments on commit 06979d8

Please sign in to comment.