diff --git a/lib/verify.js b/lib/verify.js index 532bdbf9..5ee7cd4b 100644 --- a/lib/verify.js +++ b/lib/verify.js @@ -69,6 +69,14 @@ module.exports = async (pluginConfig, context) => { }, } = await github.repos.get({repo, owner}); if (!push) { + // If authenticated as GitHub App installation, `push` will always be false. + // We send another request to check if current authentication is an installation. + // Note: we cannot check if the installation has all required permissions, it's + // up to the user to make sure it has + if (await github.request('HEAD /installation/repositories', {per_page: 1}).catch(() => false)) { + return; + } + errors.push(getError('EGHNOPERMISSION', {owner, repo})); } } catch (error) { diff --git a/test/verify.test.js b/test/verify.test.js index 8ee70efb..a01a2186 100644 --- a/test/verify.test.js +++ b/test/verify.test.js @@ -401,23 +401,50 @@ test('Throw SemanticReleaseError for invalid repositoryUrl', async (t) => { t.is(error.code, 'EINVALIDGITHUBURL'); }); -test.serial("Throw SemanticReleaseError if token doesn't have the push permission on the repository", async (t) => { - const owner = 'test_user'; - const repo = 'test_repo'; - const env = {GH_TOKEN: 'github_token'}; - const github = authenticate(env) - .get(`/repos/${owner}/${repo}`) - .reply(200, {permissions: {push: false}}); +test.serial( + "Throw SemanticReleaseError if token doesn't have the push permission on the repository and it's not a Github installation token", + async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: false}}) + .head('/installation/repositories') + .query({per_page: 1}) + .reply(403); - const [error, ...errors] = await t.throwsAsync( - verify({}, {env, options: {repositoryUrl: `https://github.com/${owner}/${repo}.git`}, logger: t.context.logger}) - ); + const [error, ...errors] = await t.throwsAsync( + verify({}, {env, options: {repositoryUrl: `https://github.com/${owner}/${repo}.git`}, logger: t.context.logger}) + ); - t.is(errors.length, 0); - t.is(error.name, 'SemanticReleaseError'); - t.is(error.code, 'EGHNOPERMISSION'); - t.true(github.isDone()); -}); + t.is(errors.length, 0); + t.is(error.name, 'SemanticReleaseError'); + t.is(error.code, 'EGHNOPERMISSION'); + t.true(github.isDone()); + } +); + +test.serial( + "Do not throw SemanticReleaseError if token doesn't have the push permission but it is a Github installation token", + async (t) => { + const owner = 'test_user'; + const repo = 'test_repo'; + const env = {GH_TOKEN: 'github_token'}; + const github = authenticate(env) + .get(`/repos/${owner}/${repo}`) + .reply(200, {permissions: {push: false}}) + .head('/installation/repositories') + .query({per_page: 1}) + .reply(200); + + await t.notThrowsAsync( + verify({}, {env, options: {repositoryUrl: `https://github.com/${owner}/${repo}.git`}, logger: t.context.logger}) + ); + + t.true(github.isDone()); + } +); test.serial("Throw SemanticReleaseError if the repository doesn't exist", async (t) => { const owner = 'test_user';