Compare commits

...
7 Commits
8 changed files with 52 additions and 34 deletions
+3
View File
@@ -59,3 +59,6 @@
- `publish`: Submit the generated archives to the webstore for review, and publish the item including release notes
- [semantic-release-gerrit](https://github.com/pascalMN/semantic-release-gerrit)
- `generateNotes`: Generate release notes with Gerrit reviews URL
- [semantic-release-expo](https://github.com/bycedric/semantic-release-expo)
- `verifyConditions`: Verify Expo manifest(s) are readable and valid.
- `prepare`: Update version, ios build number and android version code in the Expo manifest(s).
+4 -6
View File
@@ -62,14 +62,12 @@ async function run(context, plugins) {
try {
await verifyAuth(options.repositoryUrl, options.branch, {cwd, env});
} catch (error) {
console.error('call isBranchUpToDate');
if (!(await isBranchUpToDate(options.branch, {cwd, env}))) {
logger.log(
`The local branch ${options.branch} is behind the remote one, therefore a new version won't be published.`
);
return false;
}
console.error('call throw error');
throw error;
}
} catch (error) {
@@ -98,14 +96,14 @@ async function run(context, plugins) {
await plugins.verifyRelease(context);
nextRelease.notes = await plugins.generateNotes(context);
if (options.dryRun) {
const notes = await plugins.generateNotes(context);
logger.log(`Release note for version ${nextRelease.version}:`);
if (notes) {
context.stdout.write(marked(notes));
if (nextRelease.notes) {
context.stdout.write(marked(nextRelease.notes));
}
} else {
nextRelease.notes = await plugins.generateNotes(context);
await plugins.prepare(context);
// Create the tag before calling the publish plugins as some require the tag to exists
+4 -1
View File
@@ -20,7 +20,10 @@ const CONFIG_FILES = [
module.exports = async (context, opts) => {
const {cwd, env} = context;
const {config} = (await cosmiconfig(CONFIG_NAME, {searchPlaces: CONFIG_FILES}).search(cwd)) || {};
const {config, filepath} = (await cosmiconfig(CONFIG_NAME, {searchPlaces: CONFIG_FILES}).search(cwd)) || {};
debug('load config from: %s', filepath);
// Merge config file options and CLI/API options
let options = {...config, ...opts};
const pluginsPath = {};
+12 -11
View File
@@ -1,6 +1,5 @@
const {parse, format} = require('url');
const {isNil} = require('lodash');
const gitUrlParse = require('git-url-parse');
const hostedGitInfo = require('hosted-git-info');
const {verifyAuth} = require('./git');
@@ -27,17 +26,14 @@ const GIT_TOKENS = {
*/
module.exports = async ({cwd, env, options: {repositoryUrl, branch}}) => {
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 {
const {protocols} = gitUrlParse(repositoryUrl);
} else if (protocol && protocol.includes('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'});
}
repositoryUrl = format({...parsed, protocol: protocol.includes('https') ? 'https' : 'http', href: null});
}
// Test if push is allowed without transforming the URL (e.g. is ssh keys are set up)
@@ -46,11 +42,16 @@ module.exports = async ({cwd, env, options: {repositoryUrl, branch}}) => {
} catch (error) {
const envVar = Object.keys(GIT_TOKENS).find(envVar => !isNil(env[envVar]));
const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar] || ''}`;
const {protocols, ...parsed} = 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 ? {...parsed, protocols: [protocol], user: gitCredentials}.toString(protocol) : repositoryUrl;
if (gitCredentials) {
// If credentials are set via anvironment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
const [match, auth, host, path] = /^(?!.+:\/\/)(?:(.*)@)?(.*?):(.*)$/.exec(repositoryUrl) || [];
return format({
...parse(match ? `ssh://${auth ? `${auth}@` : ''}${host}/${path}` : repositoryUrl),
auth: gitCredentials,
protocol: protocol && /http[^s]/.test(protocol) ? 'http' : 'https',
});
}
}
return repositoryUrl;
+3
View File
@@ -1,4 +1,5 @@
const {isPlainObject, isFunction, noop, cloneDeep, omit} = require('lodash');
const debug = require('debug')('semantic-release:plugins');
const getError = require('../get-error');
const {extractErrors} = require('../utils');
const PLUGINS_DEFINITIONS = require('../definitions/plugins');
@@ -14,6 +15,8 @@ module.exports = (context, type, pluginOpt, pluginsPath) => {
const pluginName = name.pluginName ? name.pluginName : isFunction(name) ? `[Function: ${name.name}]` : name;
const plugin = loadPlugin(context, name, pluginsPath);
debug(`options for ${pluginName}/${type}: %O`, config);
let func;
if (isFunction(plugin)) {
func = plugin.bind(null, cloneDeep({...options, ...config}));
+1 -2
View File
@@ -23,7 +23,7 @@
"@semantic-release/error": "^2.2.0",
"@semantic-release/github": "^5.1.0",
"@semantic-release/npm": "^5.0.5",
"@semantic-release/release-notes-generator": "^7.1.0",
"@semantic-release/release-notes-generator": "^7.1.2",
"aggregate-error": "^1.0.0",
"cosmiconfig": "^5.0.1",
"debug": "^4.0.0",
@@ -33,7 +33,6 @@
"find-versions": "^2.0.0",
"get-stream": "^4.0.0",
"git-log-parser": "^1.2.0",
"git-url-parse": "^10.0.1",
"hook-std": "^1.1.0",
"hosted-git-info": "^2.7.1",
"lodash": "^4.17.4",
+13
View File
@@ -101,6 +101,19 @@ test('Return the "https" formatted URL if "gitCredentials" is defined and reposi
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "git" URL without user', async t => {
const {cwd} = await gitRepo();
t.is(
await getAuthUrl({
cwd,
env: {...env, GIT_CREDENTIALS: 'user:pass'},
options: {branch: 'master', repositoryUrl: 'host.null:owner/repo.git'},
}),
'https://user:pass@host.null/owner/repo.git'
);
});
test('Return the "https" formatted URL if "gitCredentials" is defined and repositoryUrl is a "https" URL', async t => {
const {cwd} = await gitRepo();
+12 -14
View File
@@ -1,5 +1,5 @@
import test from 'ava';
import {escapeRegExp, isString} from 'lodash';
import {escapeRegExp, isString, sortBy} from 'lodash';
import proxyquire from 'proxyquire';
import {spy, stub} from 'sinon';
import {WritableStreamBuffer} from 'stream-buffers';
@@ -375,13 +375,10 @@ test('Log all "verifyConditions" errors', async t => {
)),
];
t.deepEqual(errors, [error1, error2, error3]);
t.deepEqual(t.context.error.args[t.context.error.args.length - 2], ['ERR2 error 2']);
t.deepEqual(t.context.error.args[t.context.error.args.length - 1], ['ERR3 error 3']);
t.deepEqual(t.context.error.args[t.context.error.args.length - 3], [
'An error occurred while running semantic-release: %O',
error1,
]);
t.deepEqual(sortBy(errors, ['message']), sortBy([error1, error2, error3], ['message']));
t.true(t.context.error.calledWith('An error occurred while running semantic-release: %O', error1));
t.true(t.context.error.calledWith('ERR2 error 2'));
t.true(t.context.error.calledWith('ERR3 error 3'));
t.true(t.context.error.calledAfter(t.context.log));
t.is(fail.callCount, 1);
t.deepEqual(fail.args[0][0], config);
@@ -423,9 +420,9 @@ test('Log all "verifyRelease" errors', async t => {
)),
];
t.deepEqual(errors, [error1, error2]);
t.deepEqual(t.context.error.args[t.context.error.args.length - 2], ['ERR1 error 1']);
t.deepEqual(t.context.error.args[t.context.error.args.length - 1], ['ERR2 error 2']);
t.deepEqual(sortBy(errors, ['message']), sortBy([error1, error2], ['message']));
t.true(t.context.error.calledWith('ERR1 error 1'));
t.true(t.context.error.calledWith('ERR2 error 2'));
t.is(fail.callCount, 1);
t.deepEqual(fail.args[0][0], config);
t.deepEqual(fail.args[0][1].errors, [error1, error2]);
@@ -520,9 +517,9 @@ test('Dry-run skips fail', async t => {
)),
];
t.deepEqual(errors, [error1, error2]);
t.deepEqual(t.context.error.args[t.context.error.args.length - 2], ['ERR1 error 1']);
t.deepEqual(t.context.error.args[t.context.error.args.length - 1], ['ERR2 error 2']);
t.deepEqual(sortBy(errors, ['message']), sortBy([error1, error2], ['message']));
t.true(t.context.error.calledWith('ERR1 error 1'));
t.true(t.context.error.calledWith('ERR2 error 2'));
t.is(fail.callCount, 0);
});
@@ -1089,6 +1086,7 @@ test('Hide sensitive information passed to "success" plugin', async t => {
verifyConditions: false,
verifyRelease: false,
prepare: false,
generateNotes: stub().resolves(`Exposing token ${env.MY_TOKEN}`),
publish: stub().resolves({
name: `Name: Exposing token ${env.MY_TOKEN}`,
url: `URL: Exposing token ${env.MY_TOKEN}`,