Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bin/good-first-issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const opn = require('open')
const gfi = require('libgfi')

const packageJSON = require('../package.json')
const formatError = require('../lib/format-error')
const log = require('../lib/log')
const prompt = require('../lib/prompt')
const projects = require('../data/projects.json')
Expand Down Expand Up @@ -54,7 +55,7 @@ cli
process.exitCode = 0
}
} catch (err) {
console.error(err)
console.error(`\n${formatError(err, input)}\n`)
process.exitCode = 1
}
})
Expand Down
63 changes: 63 additions & 0 deletions lib/format-error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
function formatError (err, input) {
const target = input ? `"${input}"` : 'the requested project'
const prefix = `Error: Unable to fetch issues for ${target}.`

if (isRateLimitError(err)) {
return `${prefix}\nGitHub API rate limit reached. Retry later or pass --auth <token> for higher limits.`
}

if (isInvalidProjectError(err)) {
return `${prefix}\nPlease check the project name or repository path and try again.`
}

if (isNetworkError(err)) {
return `${prefix}\nPlease check your network connection and try again.`
}

return `${prefix}\n${getErrorDetail(err)}`
}

function getErrorDetail (err) {
if (err && typeof err.message === 'string' && err.message.trim()) {
return err.message.trim()
}

return 'An unexpected error occurred while querying the GitHub API.'
}

function isInvalidProjectError (err) {
if (!err || err.status !== 422) {
return false
}

const errors = err.response && err.response.data && err.response.data.errors
return Array.isArray(errors) && errors.some(error => error && error.code === 'invalid')
}

function isRateLimitError (err) {
if (!err) {
return false
}

const headers = getResponseHeaders(err)
const remaining = headers && headers['x-ratelimit-remaining']
return err.status === 403 && remaining === '0'
}

function isNetworkError (err) {
if (!err || err.status) {
return false
}

return Boolean(err.code) || /network|timed? out|fetch failed|socket/i.test(getErrorDetail(err))
}

function getResponseHeaders (err) {
if (err.response && err.response.headers) {
return err.response.headers
}

return err.headers || null
}

module.exports = formatError
50 changes: 50 additions & 0 deletions tests/format-error.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const formatError = require('../lib/format-error')

test('formats invalid project errors clearly', () => {
const actual = formatError({
status: 422,
response: {
data: {
errors: [{ code: 'invalid' }]
}
}
}, 'thisisntarealprojectorgithuborg')

expect(actual).toBe(
'Error: Unable to fetch issues for "thisisntarealprojectorgithuborg".\nPlease check the project name or repository path and try again.'
)
})

test('formats rate limit errors clearly', () => {
const actual = formatError({
status: 403,
response: {
headers: {
'x-ratelimit-remaining': '0'
}
}
}, 'facebook/react')

expect(actual).toBe(
'Error: Unable to fetch issues for "facebook/react".\nGitHub API rate limit reached. Retry later or pass --auth <token> for higher limits.'
)
})

test('formats network errors clearly', () => {
const actual = formatError({
code: 'ENOTFOUND',
message: 'getaddrinfo ENOTFOUND api.github.com'
}, 'nodejs/node')

expect(actual).toBe(
'Error: Unable to fetch issues for "nodejs/node".\nPlease check your network connection and try again.'
)
})

test('falls back to the original error message for unknown failures', () => {
const actual = formatError(new Error('Something odd happened'), 'vercel/next.js')

expect(actual).toBe(
'Error: Unable to fetch issues for "vercel/next.js".\nSomething odd happened'
)
})