Skip to content

Commit

Permalink
feat: Do not depends on package.json and use new repositoryUrl
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Require semantic-release version that pass the `repositoryUrl` option

BREAKING CHANGE: Return async function instead of function calling a callback
  • Loading branch information
pvdlg committed Nov 25, 2017
1 parent 336ba0a commit d49ce22
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 125 deletions.
19 changes: 9 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
const {callbackify} = require('util');
const verifyGithub = require('./lib/verify');
const publishGit = require('./lib/publish');
const publishGithub = require('./lib/publish');

let verified;

async function verifyConditions(pluginConfig, {pkg, options: {publish}}) {
async function verifyConditions(pluginConfig, {options}) {
// If the Github publish plugin is used and has `assets` configured, validate it now in order to prevent any release if the configuration is wrong
if (publish) {
const publishPlugin = (Array.isArray(publish) ? publish : [publish]).find(
if (options.publish) {
const publishPlugin = (Array.isArray(options.publish) ? options.publish : [options.publish]).find(
config => config.path && config.path === '@semantic-release/github'
);
if (publishPlugin && publishPlugin.assets) {
pluginConfig.assets = publishPlugin.assets;
}
}

await verifyGithub(pluginConfig, pkg);
await verifyGithub(pluginConfig, options);
verified = true;
}

async function publish(pluginConfig, {pkg, nextRelease, options, logger}) {
async function publish(pluginConfig, {nextRelease, options, logger}) {
if (!verified) {
await verifyGithub(pluginConfig, pkg);
await verifyGithub(pluginConfig, options);
verified = true;
}
await publishGit(pluginConfig, options, pkg, nextRelease, logger);
await publishGithub(pluginConfig, options, nextRelease, logger);
}

module.exports = {verifyConditions: callbackify(verifyConditions), publish: callbackify(publish)};
module.exports = {verifyConditions, publish};
6 changes: 3 additions & 3 deletions lib/publish.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
const {basename} = require('path');
const {parse} = require('url');
const {stat} = require('fs-extra');
const gitUrlParse = require('git-url-parse');
const parseGithubUrl = require('parse-github-url');
const GitHubApi = require('github');
const pEachSeries = require('p-each-series');
const debug = require('debug')('semantic-release:publish-github');
const resolveConfig = require('./resolve-config');

module.exports = async (pluginConfig, {branch}, {repository}, {version, gitHead, gitTag, notes}, logger) => {
module.exports = async (pluginConfig, {branch, repositoryUrl}, {version, gitHead, gitTag, notes}, logger) => {
const {githubToken, githubUrl, githubApiPathPrefix, assets} = resolveConfig(pluginConfig);
const {name: repo, owner} = gitUrlParse(repository.url);
const {name: repo, owner} = parseGithubUrl(repositoryUrl);
let {port, protocol, hostname: host} = githubUrl ? parse(githubUrl) : {};
protocol = (protocol || '').split(':')[0] || null;

Expand Down
21 changes: 10 additions & 11 deletions lib/verify.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
const {parse} = require('url');
const gitUrlParse = require('git-url-parse');
const parseGithubUrl = require('parse-github-url');
const GitHubApi = require('github');
const SemanticReleaseError = require('@semantic-release/error');
const resolveConfig = require('./resolve-config');

module.exports = async (pluginConfig, {name, repository}) => {
module.exports = async (pluginConfig, {repositoryUrl}) => {
const {githubToken, githubUrl, githubApiPathPrefix, assets} = resolveConfig(pluginConfig);

if (!name) {
throw new SemanticReleaseError('No "name" found in package.json.', 'ENOPKGNAME');
}

if (!repository || !repository.url) {
throw new SemanticReleaseError('No "repository" found in package.json.', 'ENOPKGREPO');
}

if (!githubToken) {
throw new SemanticReleaseError('No github token specified.', 'ENOGHTOKEN');
}
Expand All @@ -29,7 +21,14 @@ module.exports = async (pluginConfig, {name, repository}) => {
}
}

const {name: repo, owner} = gitUrlParse(repository.url);
const {name: repo, owner} = parseGithubUrl(repositoryUrl);
if (!owner || !repo) {
throw new SemanticReleaseError(
`The git repository URL ${repositoryUrl} is not a valid Github URL.`,
'EINVALIDGITURL'
);
}

let {port, protocol, hostname: host} = githubUrl ? parse(githubUrl) : {};
protocol = (protocol || '').split(':')[0] || null;

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
"@semantic-release/error": "^2.1.0",
"debug": "^3.1.0",
"fs-extra": "^4.0.2",
"git-url-parse": "^7.0.1",
"github": "^12.0.5",
"p-each-series": "^1.0.0"
"p-each-series": "^1.0.0",
"parse-github-url": "^1.0.1"
},
"devDependencies": {
"ava": "^0.23.0",
Expand All @@ -34,7 +34,7 @@
"nock": "^9.1.0",
"nyc": "^11.2.1",
"prettier": "~1.8.2",
"semantic-release": "^9.1.1",
"semantic-release": "^10.0.0",
"sinon": "^4.0.0",
"xo": "^0.18.2"
},
Expand Down
41 changes: 22 additions & 19 deletions test/integration.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {promisify} from 'util';
import {escape} from 'querystring';
import test from 'ava';
import {stub, match} from 'sinon';
Expand Down Expand Up @@ -36,14 +35,13 @@ test.serial('Verify Github auth', async t => {
process.env.GITHUB_TOKEN = 'github_token';
const owner = 'test_user';
const repo = 'test_repo';
const options = {};
const pkg = {name: 'package-name', repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
const options = {repositoryUrl: `git+https://othertesturl.com/${owner}/${repo}.git`};

const github = authenticate({githubToken: process.env.GITHUB_TOKEN})
.get(`/repos/${owner}/${repo}`)
.reply(200, {permissions: {push: true}});

await t.notThrows(promisify(t.context.m.verifyConditions)({}, {pkg, options}));
await t.notThrows(t.context.m.verifyConditions({}, {options}));

t.true(github.isDone());
});
Expand All @@ -52,14 +50,16 @@ test.serial('Verify Github auth with publish options', async t => {
process.env.GITHUB_TOKEN = 'github_token';
const owner = 'test_user';
const repo = 'test_repo';
const options = {publish: {path: '@semantic-release/github'}};
const pkg = {name: 'package-name', repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
const options = {
publish: {path: '@semantic-release/github'},
repositoryUrl: `git+https://othertesturl.com/${owner}/${repo}.git`,
};

const github = authenticate({githubToken: process.env.GITHUB_TOKEN})
.get(`/repos/${owner}/${repo}`)
.reply(200, {permissions: {push: true}});

await t.notThrows(promisify(t.context.m.verifyConditions)({}, {pkg, options}));
await t.notThrows(t.context.m.verifyConditions({}, {options}));

t.true(github.isDone());
});
Expand All @@ -69,14 +69,16 @@ test.serial('Verify Github auth and assets config', async t => {
const owner = 'test_user';
const repo = 'test_repo';
const assets = [{path: 'lib/file.js'}, 'file.js'];
const options = {publish: [{path: '@semantic-release/npm'}, {path: '@semantic-release/github', assets}]};
const pkg = {name: 'package-name', repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
const options = {
publish: [{path: '@semantic-release/npm'}, {path: '@semantic-release/github', assets}],
repositoryUrl: `git+https://othertesturl.com/${owner}/${repo}.git`,
};

const github = authenticate({githubToken: process.env.GH_TOKEN})
.get(`/repos/${owner}/${repo}`)
.reply(200, {permissions: {push: true}});

await t.notThrows(promisify(t.context.m.verifyConditions)({}, {pkg, options}));
await t.notThrows(t.context.m.verifyConditions({}, {options}));

t.true(github.isDone());
});
Expand All @@ -86,10 +88,12 @@ test.serial('Throw SemanticReleaseError if invalid config', async t => {
const owner = 'test_user';
const repo = 'test_repo';
const assets = [{wrongProperty: 'lib/file.js'}];
const options = {publish: [{path: '@semantic-release/npm'}, {path: '@semantic-release/github', assets}]};
const pkg = {name: 'package-name', repository: {url: `git+https://othertesturl.com/${owner}/${repo}.git`}};
const options = {
publish: [{path: '@semantic-release/npm'}, {path: '@semantic-release/github', assets}],
repositoryUrl: `git+https://othertesturl.com/${owner}/${repo}.git`,
};

const error = await t.throws(promisify(t.context.m.verifyConditions)({}, {pkg, options}));
const error = await t.throws(t.context.m.verifyConditions({}, {options}));

t.true(error instanceof SemanticReleaseError);
t.is(error.code, 'EINVALIDASSETS');
Expand All @@ -104,8 +108,7 @@ test.serial('Publish a release with an array of assets', async t => {
{path: 'test/fixtures/upload_other.txt', name: 'other_file.txt', label: 'Other File'},
];
const nextRelease = {version: '1.0.0', gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'};
const options = {branch: 'master'};
const pkg = {name: 'package-name', repository: {url: `https://github.com/${owner}/${repo}.git`}};
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
const assetUrl = `https://github.com/${owner}/${repo}/releases/download/${nextRelease.version}/upload.txt`;
const otherAssetUrl = `https://github.com/${owner}/${repo}/releases/download/${nextRelease.version}/other_file.txt`;
Expand Down Expand Up @@ -134,7 +137,7 @@ test.serial('Publish a release with an array of assets', async t => {
)
.reply(200, {browser_download_url: otherAssetUrl});

await promisify(t.context.m.publish)({githubToken, assets}, {pkg, nextRelease, options, logger: t.context.logger});
await t.context.m.publish({githubToken, assets}, {nextRelease, options, logger: t.context.logger});

t.true(t.context.log.calledWith(match.string, releaseUrl));
t.true(t.context.log.calledWith(match.string, assetUrl));
Expand All @@ -151,10 +154,10 @@ test.serial('Verify Github auth and release', async t => {
'test/fixtures/upload.txt',
{path: 'test/fixtures/upload_other.txt', name: 'other_file.txt', label: 'Other File'},
];
const pkg = {name: 'package-name', repository: {url: `https://github.com/${owner}/${repo}.git`}};
const options = {
publish: [{path: '@semantic-release/npm'}, {path: '@semantic-release/github', assets}],
branch: 'master',
repositoryUrl: `https://github.com/${owner}/${repo}.git`,
};
const nextRelease = {version: '1.0.0', gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'};
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
Expand Down Expand Up @@ -185,8 +188,8 @@ test.serial('Verify Github auth and release', async t => {
)
.reply(200, {browser_download_url: otherAssetUrl});

await t.notThrows(promisify(t.context.m.verifyConditions)({}, {pkg, options}));
await promisify(t.context.m.publish)({assets}, {pkg, nextRelease, options, logger: t.context.logger});
await t.notThrows(t.context.m.verifyConditions({}, {options}));
await t.context.m.publish({assets}, {nextRelease, options, logger: t.context.logger});

t.true(t.context.log.calledWith(match.string, releaseUrl));
t.true(t.context.log.calledWith(match.string, assetUrl));
Expand Down
25 changes: 10 additions & 15 deletions test/publish.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ test.serial('Publish a release', async t => {
const githubToken = 'github_token';
const pluginConfig = {githubToken};
const nextRelease = {version: '1.0.0', gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'};
const options = {branch: 'master'};
const pkg = {repository: {url: `https://github.com/${owner}/${repo}.git`}};
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;

const github = authenticate({githubToken})
Expand All @@ -51,7 +50,7 @@ test.serial('Publish a release', async t => {
.post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${nextRelease.gitTag}`, sha: nextRelease.gitHead})
.reply({});

await publish(pluginConfig, options, pkg, nextRelease, t.context.logger);
await publish(pluginConfig, options, nextRelease, t.context.logger);

t.true(t.context.log.calledWith(match.string, releaseUrl));
t.true(github.isDone());
Expand All @@ -65,8 +64,7 @@ test.serial('Publish a release with one asset', async t => {
process.env.GH_PREFIX = 'prefix';
const pluginConfig = {assets: 'test/fixtures/upload.txt'};
const nextRelease = {version: '1.0.0', gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'};
const options = {branch: 'master'};
const pkg = {repository: {url: `https://github.com/${owner}/${repo}.git`}};
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
const assetUrl = `https://github.com/${owner}/${repo}/releases/download/${nextRelease.version}/upload.txt`;
const releaseId = 1;
Expand All @@ -90,7 +88,7 @@ test.serial('Publish a release with one asset', async t => {
.post(`/repos/${owner}/${repo}/releases/${releaseId}/assets?name=${escape('upload.txt')}`)
.reply(200, {browser_download_url: assetUrl});

await publish(pluginConfig, options, pkg, nextRelease, t.context.logger);
await publish(pluginConfig, options, nextRelease, t.context.logger);

t.true(t.context.log.calledWith(match.string, releaseUrl));
t.true(t.context.log.calledWith(match.string, assetUrl));
Expand All @@ -107,8 +105,7 @@ test.serial('Publish a release with one asset and custom github url', async t =>
const assets = 'test/fixtures/upload.txt';
const pluginConfig = {githubUrl: process.env.GITHUB_URL, githubApiPathPrefix: process.env.GITHUB_PREFIX, assets};
const nextRelease = {version: '1.0.0', gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'};
const options = {branch: 'master'};
const pkg = {repository: {url: `https://github.com/${owner}/${repo}.git`}};
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
const assetUrl = `https://github.com/${owner}/${repo}/releases/download/${nextRelease.version}/upload.txt`;
const releaseId = 1;
Expand All @@ -132,7 +129,7 @@ test.serial('Publish a release with one asset and custom github url', async t =>
.post(`/repos/${owner}/${repo}/releases/${releaseId}/assets?name=${escape('upload.txt')}`)
.reply(200, {browser_download_url: assetUrl});

await publish(pluginConfig, options, pkg, nextRelease, t.context.logger);
await publish(pluginConfig, options, nextRelease, t.context.logger);

t.true(t.context.log.calledWith(match.string, releaseUrl));
t.true(t.context.log.calledWith(match.string, assetUrl));
Expand All @@ -152,8 +149,7 @@ test.serial('Publish a release with an array of assets', async t => {
],
};
const nextRelease = {version: '1.0.0', gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'};
const options = {branch: 'master'};
const pkg = {repository: {url: `https://github.com/${owner}/${repo}.git`}};
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
const assetUrl = `https://github.com/${owner}/${repo}/releases/download/${nextRelease.version}/upload.txt`;
const otherAssetUrl = `https://github.com/${owner}/${repo}/releases/download/${nextRelease.version}/other_file.txt`;
Expand All @@ -180,7 +176,7 @@ test.serial('Publish a release with an array of assets', async t => {
)
.reply(200, {browser_download_url: otherAssetUrl});

await publish(pluginConfig, options, pkg, nextRelease, t.context.logger);
await publish(pluginConfig, options, nextRelease, t.context.logger);

t.true(t.context.log.calledWith(match.string, releaseUrl));
t.true(t.context.log.calledWith(match.string, assetUrl));
Expand All @@ -198,8 +194,7 @@ test.serial('Publish a release with an array of misconfigured assets', async t =
assets: ['test/fixtures', {path: 'test/fixtures/missing.txt', name: 'missing.txt', label: 'Missing File'}],
};
const nextRelease = {version: '1.0.0', gitHead: '123', gitTag: 'v1.0.0', notes: 'Test release note body'};
const options = {branch: 'master'};
const pkg = {repository: {url: `https://github.com/${owner}/${repo}.git`}};
const options = {branch: 'master', repositoryUrl: `https://github.com/${owner}/${repo}.git`};
const releaseUrl = `https://github.com/${owner}/${repo}/releases/${nextRelease.version}`;
const releaseId = 1;

Expand All @@ -214,7 +209,7 @@ test.serial('Publish a release with an array of misconfigured assets', async t =
.post(`/repos/${owner}/${repo}/git/refs`, {ref: `refs/tags/${nextRelease.gitTag}`, sha: nextRelease.gitHead})
.reply({});

await publish(pluginConfig, options, pkg, nextRelease, t.context.logger);
await publish(pluginConfig, options, nextRelease, t.context.logger);

t.true(t.context.log.calledWith(match.string, releaseUrl));
t.true(t.context.error.calledWith(match.string, 'test/fixtures/missing.txt'));
Expand Down

0 comments on commit d49ce22

Please sign in to comment.