Compare commits

...
3 Commits
8 changed files with 63 additions and 102 deletions
+1
View File
@@ -13,6 +13,7 @@
## Articles
- [“Introduction to SemVer” - Irina Gebauer](https://blog.greenkeeper.io/introduction-to-semver-d272990c44f2)
- ["Introduction to Semantic Release" - liv](https://blog.greenkeeper.io/introduction-to-semantic-release-33f73b117c8)
## Tutorials
+8 -1
View File
@@ -11,8 +11,10 @@ const getNextVersion = require('./lib/get-next-version');
const getCommits = require('./lib/get-commits');
const getLastRelease = require('./lib/get-last-release');
const {extractErrors} = require('./lib/utils');
const getGitAuthUrl = require('./lib/get-git-auth-url');
const logger = require('./lib/logger');
const {unshallow, gitHead: getGitHead, tag, push} = require('./lib/git');
const {unshallow, verifyAuth, gitHead: getGitHead, tag, push} = require('./lib/git');
const getError = require('./lib/get-error');
marked.setOptions({renderer: new TerminalRenderer()});
@@ -44,6 +46,11 @@ async function run(options, plugins) {
await verify(options);
options.repositoryUrl = await getGitAuthUrl(options);
if (!await verifyAuth(options.repositoryUrl, options.branch)) {
throw getError('EGITNOPERMISSION', {options});
}
logger.log('Run automated release from branch %s', options.branch);
logger.log('Call plugin %s', 'verify-conditions');
-3
View File
@@ -6,7 +6,6 @@ const debug = require('debug')('semantic-release:config');
const {repoUrl} = require('./git');
const PLUGINS_DEFINITIONS = require('./definitions/plugins');
const plugins = require('./plugins');
const getGitAuthUrl = require('./get-git-auth-url');
module.exports = async (opts, logger) => {
const {config} = (await cosmiconfig('release', {rcExtensions: true}).load(process.cwd())) || {};
@@ -51,8 +50,6 @@ module.exports = async (opts, logger) => {
...pickBy(options, option => !isUndefined(option) && !isNull(option)),
};
options.repositoryUrl = await getGitAuthUrl(options);
debug('options values: %O', options);
return {options, plugins: await plugins(options, pluginsPath, logger)};
+25 -27
View File
@@ -17,38 +17,36 @@ const GIT_TOKENS = ['GIT_CREDENTIALS', 'GH_TOKEN', 'GITHUB_TOKEN', 'GL_TOKEN', '
* @return {String} The formatted Git repository URL.
*/
module.exports = async ({repositoryUrl, branch}) => {
if (repositoryUrl) {
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true});
if (info && info.getDefaultRepresentation() === 'shortcut') {
// Expand shorthand URLs (such as `owner/repo` or `gitlab:owner/repo`)
repositoryUrl = info.https();
} else {
const {protocols} = gitUrlParse(repositoryUrl);
if (info && info.getDefaultRepresentation() === 'shortcut') {
// Expand shorthand URLs (such as `owner/repo` or `gitlab:owner/repo`)
repositoryUrl = info.https();
} else {
const {protocols} = gitUrlParse(repositoryUrl);
// Replace `git+https` and `git+http` with `https` or `http`
if (protocols.includes('http') || protocols.includes('https')) {
repositoryUrl = format({
...parse(repositoryUrl),
...{protocol: protocols.includes('https') ? 'https' : 'http'},
});
}
// Replace `git+https` and `git+http` with `https` or `http`
if (protocols.includes('http') || protocols.includes('https')) {
repositoryUrl = format({
...parse(repositoryUrl),
...{protocol: protocols.includes('https') ? 'https' : 'http'},
});
}
}
// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
if (!await verifyAuth(repositoryUrl, branch)) {
const envVar = GIT_TOKENS.find(envVar => !isUndefined(process.env[envVar]));
const gitCredentials = ['GL_TOKEN', 'GITLAB_TOKEN'].includes(envVar)
? `gitlab-ci-token:${process.env[envVar]}`
: process.env[envVar];
const {protocols} = gitUrlParse(repositoryUrl);
const protocol = protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : 'https';
// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
if (!await verifyAuth(repositoryUrl, branch)) {
const envVar = GIT_TOKENS.find(envVar => !isUndefined(process.env[envVar]));
const gitCredentials = ['GL_TOKEN', 'GITLAB_TOKEN'].includes(envVar)
? `gitlab-ci-token:${process.env[envVar]}`
: process.env[envVar];
const {protocols} = gitUrlParse(repositoryUrl);
const protocol = protocols.includes('https') ? 'https' : protocols.includes('http') ? 'http' : 'https';
// If credentials are set via anvironment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
return gitCredentials
? format({...parse(`${gitUrlParse(repositoryUrl).toString(protocol)}.git`), ...{auth: gitCredentials}})
: repositoryUrl;
}
// If credentials are set via anvironment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
return gitCredentials
? format({...parse(`${gitUrlParse(repositoryUrl).toString(protocol)}.git`), ...{auth: gitCredentials}})
: repositoryUrl;
}
return repositoryUrl;
};
+11 -14
View File
@@ -1,8 +1,5 @@
const execa = require('execa');
const debug = require('debug')('semantic-release:git');
const envCi = require('env-ci');
const timeout = envCi().isCi ? 10000 : 0;
/**
* Get the commit sha for a given tag.
@@ -13,7 +10,7 @@ const timeout = envCi().isCi ? 10000 : 0;
*/
async function gitTagHead(tagName) {
try {
return await execa.stdout('git', ['rev-list', '-1', tagName], {timeout});
return await execa.stdout('git', ['rev-list', '-1', tagName]);
} catch (err) {
debug(err);
}
@@ -24,7 +21,7 @@ async function gitTagHead(tagName) {
* @throws {Error} If the `git` command fails.
*/
async function gitTags() {
return (await execa.stdout('git', ['tag'], {timeout}))
return (await execa.stdout('git', ['tag']))
.split('\n')
.map(tag => tag.trim())
.filter(tag => Boolean(tag));
@@ -39,7 +36,7 @@ async function gitTags() {
*/
async function isRefInHistory(ref) {
try {
return (await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD'], {timeout})).code === 0;
return (await execa('git', ['merge-base', '--is-ancestor', ref, 'HEAD'])).code === 0;
} catch (err) {
debug(err);
}
@@ -49,14 +46,14 @@ async function isRefInHistory(ref) {
* Unshallow the git repository (retriving every commits and tags).
*/
async function unshallow() {
await execa('git', ['fetch', '--unshallow', '--tags'], {reject: false, timeout});
await execa('git', ['fetch', '--unshallow', '--tags'], {reject: false});
}
/**
* @return {string} the sha of the HEAD commit.
*/
async function gitHead() {
return execa.stdout('git', ['rev-parse', 'HEAD'], {timeout});
return execa.stdout('git', ['rev-parse', 'HEAD']);
}
/**
@@ -64,7 +61,7 @@ async function gitHead() {
*/
async function repoUrl() {
try {
return await execa.stdout('git', ['remote', 'get-url', 'origin'], {timeout});
return await execa.stdout('git', ['remote', 'get-url', 'origin']);
} catch (err) {
debug(err);
}
@@ -75,7 +72,7 @@ async function repoUrl() {
*/
async function isGitRepo() {
try {
return (await execa('git', ['rev-parse', '--git-dir'], {timeout})).code === 0;
return (await execa('git', ['rev-parse', '--git-dir'])).code === 0;
} catch (err) {
debug(err);
}
@@ -91,7 +88,7 @@ async function isGitRepo() {
*/
async function verifyAuth(origin, branch) {
try {
return (await execa('git', ['push', '--dry-run', origin, `HEAD:${branch}`], {timeout})).code === 0;
return (await execa('git', ['push', '--dry-run', origin, `HEAD:${branch}`])).code === 0;
} catch (err) {
debug(err);
}
@@ -104,7 +101,7 @@ async function verifyAuth(origin, branch) {
* @throws {Error} if the tag creation failed.
*/
async function tag(tagName) {
await execa('git', ['tag', tagName], {timeout});
await execa('git', ['tag', tagName]);
}
/**
@@ -115,7 +112,7 @@ async function tag(tagName) {
* @throws {Error} if the push failed.
*/
async function push(origin, branch) {
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`], {timeout});
await execa('git', ['push', '--tags', origin, `HEAD:${branch}`]);
}
/**
@@ -126,7 +123,7 @@ async function push(origin, branch) {
*/
async function verifyTagName(tagName) {
try {
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`], {timeout})).code === 0;
return (await execa('git', ['check-ref-format', `refs/tags/${tagName}`])).code === 0;
} catch (err) {
debug(err);
}
+1 -3
View File
@@ -1,6 +1,6 @@
const {template} = require('lodash');
const AggregateError = require('aggregate-error');
const {isGitRepo, verifyAuth, verifyTagName} = require('./git');
const {isGitRepo, verifyTagName} = require('./git');
const getError = require('./get-error');
module.exports = async options => {
@@ -10,8 +10,6 @@ module.exports = async options => {
errors.push(getError('ENOGITREPO'));
} else if (!options.repositoryUrl) {
errors.push(getError('ENOREPOURL'));
} else if (!await verifyAuth(options.repositoryUrl, options.branch)) {
errors.push(getError('EGITNOPERMISSION', {options}));
}
// Verify that compiling the `tagFormat` produce a valid Git tag
+3 -54
View File
@@ -31,7 +31,6 @@ test.afterEach.always(() => {
});
test.serial('Default values, reading repositoryUrl from package.json', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
const pkg = {repository: 'https://host.null/owner/package.git'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
@@ -45,12 +44,11 @@ test.serial('Default values, reading repositoryUrl from package.json', async t =
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'https://user:pass@host.null/owner/package.git');
t.is(options.repositoryUrl, 'https://host.null/owner/package.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Default values, reading repositoryUrl from repo if not set in package.json', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Add remote.origin.url config
@@ -60,12 +58,11 @@ test.serial('Default values, reading repositoryUrl from repo if not set in packa
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'https://user:pass@host.null/owner/module.git');
t.is(options.repositoryUrl, 'https://host.null/owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Default values, reading repositoryUrl (http url) from package.json if not set in repo', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
const pkg = {repository: 'https://host.null/owner/module.git'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
@@ -76,58 +73,10 @@ test.serial('Default values, reading repositoryUrl (http url) from package.json
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'https://user:pass@host.null/owner/module.git');
t.is(options.repositoryUrl, 'https://host.null/owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial('Default values, reading repositoryUrl (shorthand url) from package.json if not set in repo', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
const pkg = {repository: 'owner/module'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Create package.json in repository root
await outputJson('./package.json', pkg);
const {options} = await t.context.getConfig();
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'https://user:pass@github.com/owner/module.git');
t.is(options.tagFormat, `v\${version}`);
});
test.serial(
'Default values, reading repositoryUrl (gitlab shorthand url) from package.json if not set in repo',
async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
const pkg = {repository: 'gitlab:owner/module'};
// Create a git repository, set the current working directory at the root of the repo
await gitRepo();
// Create package.json in repository root
await outputJson('./package.json', pkg);
const {options} = await t.context.getConfig();
// Verify the default options are set
t.is(options.branch, 'master');
t.is(options.repositoryUrl, 'https://user:pass@gitlab.com/owner/module.git');
t.is(options.tagFormat, `v\${version}`);
}
);
test.serial('Do not add git credential to repositoryUrl if push is allowed', async t => {
process.env.GIT_CREDENTIALS = 'user:pass';
// Create a git repository, set the current working directory at the root of the repo
const repositoryUrl = await gitRepo(true);
const pkg = {repository: repositoryUrl};
// Create package.json in repository root
await outputJson('./package.json', pkg);
const {options} = await t.context.getConfig();
t.is(options.repositoryUrl, repositoryUrl);
});
test.serial('Read options from package.json', async t => {
const release = {
analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'},
+14
View File
@@ -51,6 +51,20 @@ test.serial('Handle "git" URL with group and subgroup', async t => {
);
});
test.serial('Convert shorthand URL', async t => {
t.is(
await getAuthUrl({repositoryUrl: 'semanitc-release/semanitc-release'}),
'https://github.com/semanitc-release/semanitc-release.git'
);
});
test.serial('Convert GitLab shorthand URL', async t => {
t.is(
await getAuthUrl({repositoryUrl: 'gitlab:semanitc-release/semanitc-release'}),
'https://gitlab.com/semanitc-release/semanitc-release.git'
);
});
test.serial(
'Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL',
async t => {