Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix error handling and debug information with configuration files that are empty, or not present. #2155

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
8 changes: 8 additions & 0 deletions lib/definitions/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ const wordsList = (words) =>
`${words.slice(0, -1).join(', ')}${words.length > 1 ? ` or ${words[words.length - 1]}` : trim(words[0])}`;

module.exports = {
ECONFIGNOTFOUND: () => ({
message: 'Your release configuration file was NOT found',
details: `We could not found a config file, proceeding with only the defaults`,
}),
EEMPTYCONFIG: ({filepath}) => ({
message: 'Your release configuration was found but has no contents',
details: `Please verify your config file ${filepath} is present with content`,
}),
ENOGITREPO: ({cwd}) => ({
message: 'Not running from a git repository.',
details: `The \`semantic-release\` command must be executed from a Git repository.
Expand Down
115 changes: 89 additions & 26 deletions lib/get-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,85 @@ const {repoUrl} = require('./git');
const PLUGINS_DEFINITIONS = require('./definitions/plugins');
const plugins = require('./plugins');
const {validatePlugin, parseConfig} = require('./plugins/utils');
const getError = require('./get-error');

const CONFIG_NAME = 'release';

async function pkgRepoUrl(options) {
const {packageJson} = (await readPkgUp(options)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
}

async function ciInferFromEnv(env) {
// currently, used in GitLab
if ( env["CI_REPOSITORY_URL"] ) {
let url = new URL(env["CI_REPOSITORY_URL"]);

// By default the password is set to the GITLAB_CI_TOKEN This will allow the
// publication of releases, it will not allow pushing tags to Git which is
// how the releases are currently tracked by default. In order to just use
// the GITLAB_CI_TOKEN, versions will have to be infered from GitLab
// releases. Moreover, even then you couldn't push changelogs and bump
// versions in manifests
// Solution: If we have a GitLab token use that instead.
if ( env["GITLAB_TOKEN"] !== undefined ) {
url.username = 'oauth2';
url.password = env["GITLAB_TOKEN"];
}

return url.toString();
}
return null;
}

async function repoUrlResolve(options) {
let {cwd, env, ci} = options;
let ret;

if (ci) {
ret ||= await ciInferFromEnv(env);
}

// get from package.json
ret ||= await pkgRepoUrl({normalize: false, cwd});

// get from git
ret ||= await repoUrl({cwd, env});

debug("Resolved repoUrl to %s", ret);

return ret;
}

const DEFAULTS = {
branches: [
'+([0-9])?(.{+([0-9]),x}).x',
'master',
'next',
'next-major',
{name: 'beta', prerelease: true},
{name: 'alpha', prerelease: true},
],
tagFormat: `v\${version}`,
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/npm',
'@semantic-release/github',
],
};

module.exports = async (context, cliOptions) => {
const {cwd, env} = context;
const {config, filepath} = (await cosmiconfig(CONFIG_NAME).search(cwd)) || {};

debug('load config from: %s', filepath);

if ( filepath === undefined ) {
debug( getError('ECONFIGNOTFOUND') );
}
// Will trigger if the file is .js but doesn't set modules.exports
else if ( Object.prototype.constructor.keys(config||{}).length == 0 ) {
throw getError('EEMPTYCONFIG', {filepath});
}

// Merge config file options and CLI/API options
let options = {...config, ...cliOptions};
Expand Down Expand Up @@ -54,37 +125,29 @@ module.exports = async (context, cliOptions) => {

// Set default options values if not defined yet
options = {
branches: [
'+([0-9])?(.{+([0-9]),x}).x',
'master',
'next',
'next-major',
{name: 'beta', prerelease: true},
{name: 'alpha', prerelease: true},
],
repositoryUrl: (await pkgRepoUrl({normalize: false, cwd})) || (await repoUrl({cwd, env})),
tagFormat: `v\${version}`,
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
'@semantic-release/npm',
'@semantic-release/github',
],
...DEFAULTS,
// Remove `null` and `undefined` options so they can be replaced with default ones
...pickBy(options, (option) => !isNil(option)),
...(options.branches ? {branches: castArray(options.branches)} : {}),
};

if (options.ci === false) {
options.noCi = true;

// Not sure why we have noCi, for more information see GH #1696
if (options.ci === true || options.ci === false) {
options.noCi = !options.ci;
}
else if (options.noCi === true || options.noCi === false) {
options.ci = !options.noCi;
}
else if (env["CI"] !== undefined) {
let ci = env["CI"] ? true : false;
options.ci = ci;
options.noCi = !ci;
}

debug('options values: %O', options);
options["repositoryUrl"] = await repoUrlResolve({cwd, env, ci: options.ci});

debug(`Load config %O`,{filepath, 'unprocessed_config': config, 'processed_options': options});

return {options, plugins: await plugins({...context, options}, pluginsPath)};
};

async function pkgRepoUrl(options) {
const {packageJson} = (await readPkgUp(options)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
}
9 changes: 5 additions & 4 deletions lib/get-git-auth-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,16 @@ module.exports = async (context) => {

let {repositoryUrl} = context.options;
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
const {protocol, ...parsed} = parse(repositoryUrl);

if (info && info.getDefaultRepresentation() === 'shortcut') {
// Expand shorthand URLs (such as `owner/repo` or `gitlab:owner/repo`)
repositoryUrl = info.https();
} else if (protocol && protocol.includes('http')) {
// Replace `git+https` and `git+http` with `https` or `http`
repositoryUrl = format({...parsed, protocol: protocol.includes('https') ? 'https' : 'http', href: null});
}

let url = parse(repositoryUrl);
// Replace `git+https` and `git+http` with `https` or `http`
url.href = url.href.replace( /^git\+/, '' );
const {protocol, ...parsed} = url;

// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
try {
Expand Down
29 changes: 16 additions & 13 deletions lib/hide-sensitive.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ const {escapeRegExp, size, isString} = require('lodash');
const {SECRET_REPLACEMENT, SECRET_MIN_SIZE} = require('./definitions/constants');

module.exports = (env) => {
const toReplace = Object.keys(env).filter((envVar) => {
// https://github.com/semantic-release/semantic-release/issues/1558
if (envVar === 'GOPRIVATE') {
return false;
}
if ( env["SCRUB"] ) {
const toReplace = Object.keys(env).filter((envVar) => {
// https://github.com/semantic-release/semantic-release/issues/1558
if (envVar === 'GOPRIVATE') {
return false;
}

return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
});
return /token|password|credential|secret|private/i.test(envVar) && size(env[envVar].trim()) >= SECRET_MIN_SIZE;
});

const regexp = new RegExp(
toReplace.map((envVar) => `${escapeRegExp(env[envVar])}|${escapeRegExp(encodeURI(env[envVar]))}`).join('|'),
'g'
);
return (output) =>
output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
const regexp = new RegExp(
toReplace.map((envVar) => `${escapeRegExp(env[envVar])}|${escapeRegExp(encodeURI(env[envVar]))}`).join('|'),
'g'
);
return (output) =>
output && isString(output) && toReplace.length > 0 ? output.toString().replace(regexp, SECRET_REPLACEMENT) : output;
}
return (output) => output;
};