diff --git a/bin/now-alias.js b/bin/now-alias.js index 90e6c0e..531ea75 100755 --- a/bin/now-alias.js +++ b/bin/now-alias.js @@ -15,7 +15,7 @@ const NowAlias = require('../lib/alias') const NowDomains = require('../lib/domains') const login = require('../lib/login') const cfg = require('../lib/cfg') -const { error } = require('../lib/error') +const { handleError, error } = require('../lib/error') const toHost = require('../lib/to-host') const { reAlias } = require('../lib/re-alias') const exit = require('../lib/utils/exit') @@ -136,28 +136,33 @@ if (argv.help) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) - try { - await run({ token, config }) - } catch (err) { - if (err.userError) { - error(err.message) - } else { - error(`Unknown error: ${err}\n${err.stack}`) + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) } - exit(1) - } - }) + + try { + await run({ token, config }) + } catch (err) { + if (err.userError) { + error(err.message) + } else { + error(`Unknown error: ${err}\n${err.stack}`) + } + exit(1) + } + }) + .catch(err => { + handleError(err) + process.exit(1) + }) } async function run({ token, config: { currentTeam, user } }) { diff --git a/bin/now-billing.js b/bin/now-billing.js index 3aab0da..54e53cc 100644 --- a/bin/now-billing.js +++ b/bin/now-billing.js @@ -11,7 +11,7 @@ const ms = require('ms') // Ours const login = require('../lib/login') const cfg = require('../lib/cfg') -const { error } = require('../lib/error') +const { handleError, error } = require('../lib/error') const NowCreditCards = require('../lib/credit-cards') const indent = require('../lib/indent') const listInput = require('../lib/utils/input/list') @@ -92,27 +92,32 @@ if (argv.help || !subcommand) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } - try { - await run({ token, config }) - } catch (err) { - if (err.userError) { - error(err.message) - } else { - error(`Unknown error: ${err.stack}`) + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) } - exit(1) - } - }) + try { + await run({ token, config }) + } catch (err) { + if (err.userError) { + error(err.message) + } else { + error(`Unknown error: ${err.stack}`) + } + exit(1) + } + }) + .catch(err => { + handleError(err) + process.exit(1) + }) } // Builds a `choices` object that can be passesd to inquirer.prompt() diff --git a/bin/now-certs.js b/bin/now-certs.js index 2c03644..343bdce 100755 --- a/bin/now-certs.js +++ b/bin/now-certs.js @@ -92,24 +92,29 @@ if (argv.help || !subcommand) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) + } - try { - await run({ token, config }) - } catch (err) { + try { + await run({ token, config }) + } catch (err) { + handleError(err) + exit(1) + } + }) + .catch(err => { handleError(err) - exit(1) - } - }) + process.exit(1) + }) } function formatExpirationDate(date) { diff --git a/bin/now-deploy.js b/bin/now-deploy.js index 82d63c1..04f1b21 100755 --- a/bin/now-deploy.js +++ b/bin/now-deploy.js @@ -263,9 +263,6 @@ if (deploymentName || wantsPublic) { let alwaysForwardNpm async function main() { - let config = await cfg.read({ token: argv.token }) - alwaysForwardNpm = config.forwardNpm - if (argv.h || argv.help) { help() return exit(0) @@ -274,6 +271,21 @@ async function main() { return exit(0) } + let config + + try { + config = await cfg.read({ token: argv.token }) + } catch (err) { + if (shouldLogin && err.userError) { + // We ignore user errors here, which means for example + // the token is mis-configured or revoked, if the user + // is attempting to log in again + config = {} + } else { + throw err + } + } + let token = argv.token || config.token if (!token || shouldLogin) { try { @@ -294,6 +306,8 @@ async function main() { return exit(0) } + alwaysForwardNpm = config.forwardNpm + // If we got to here then `token` should be set try { await sync({ token, config }) @@ -868,4 +882,7 @@ function printLogs(host, token, currentTeam, user) { }) } -main() +main().catch(err => { + handleError(err, { debug }) + process.exit(1) +}) diff --git a/bin/now-dns.js b/bin/now-dns.js index 252a69a..1fd542b 100755 --- a/bin/now-dns.js +++ b/bin/now-dns.js @@ -86,24 +86,29 @@ if (argv.help || !subcommand) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) + } - try { - await run({ token, config }) - } catch (err) { + try { + await run({ token, config }) + } catch (err) { + handleError(err) + exit(1) + } + }) + .catch(err => { handleError(err) - exit(1) - } - }) + process.exit(1) + }) } async function run({ token, config: { currentTeam, user } }) { diff --git a/bin/now-domains.js b/bin/now-domains.js index 80a25fa..f76c0a9 100755 --- a/bin/now-domains.js +++ b/bin/now-domains.js @@ -19,7 +19,7 @@ const logo = require('../lib/utils/output/logo') const promptBool = require('../lib/utils/input/prompt-bool') const strlen = require('../lib/strlen') const toHost = require('../lib/to-host') -const { error } = require('../lib/error') +const { handleError, error } = require('../lib/error') const argv = minimist(process.argv.slice(2), { string: ['config', 'token'], @@ -150,27 +150,32 @@ if (argv.help || !subcommand) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } - try { - await run({ token, config }) - } catch (err) { - if (err.userError) { - error(err.message) - } else { - error(`Unknown error: ${err}\n${err.stack}`) + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) } - exit(1) - } - }) + try { + await run({ token, config }) + } catch (err) { + if (err.userError) { + error(err.message) + } else { + error(`Unknown error: ${err}\n${err.stack}`) + } + exit(1) + } + }) + .catch(err => { + handleError(err) + process.exit(1) + }) } async function run({ token, config: { currentTeam, user } }) { diff --git a/bin/now-list.js b/bin/now-list.js index 41bd93a..0b6439a 100755 --- a/bin/now-list.js +++ b/bin/now-list.js @@ -71,24 +71,29 @@ if (argv.config) { cfg.setConfigFile(argv.config) } -Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - process.exit(1) - } +Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + process.exit(1) + } - try { - await list({ token, config }) - } catch (err) { - error(`Unknown error: ${err}\n${err.stack}`) + try { + await list({ token, config }) + } catch (err) { + error(`Unknown error: ${err}\n${err.stack}`) + process.exit(1) + } + }) + .catch(err => { + handleError(err) process.exit(1) - } -}) + }) async function list({ token, config: { currentTeam, user } }) { const now = new Now({ apiUrl, token, debug, currentTeam }) diff --git a/bin/now-logout.js b/bin/now-logout.js index abc598e..98bc572 100644 --- a/bin/now-logout.js +++ b/bin/now-logout.js @@ -9,6 +9,7 @@ const ora = require('ora') // Utilities const cfg = require('../lib/cfg') const logo = require('../lib/utils/output/logo') +const { handleError } = require('../lib/error') const argv = minimist(process.argv.slice(2), { string: ['config'], @@ -123,4 +124,7 @@ const logout = async () => { spinner.succeed('Logged out!') } -logout() +logout().catch(err => { + handleError(err) + process.exit(1) +}) diff --git a/bin/now-logs.js b/bin/now-logs.js index 74139b8..c375221 100644 --- a/bin/now-logs.js +++ b/bin/now-logs.js @@ -124,7 +124,7 @@ Promise.resolve() await printLogs({ token, config }) }) .catch(err => { - error(`Unknown error: ${err.stack}`) + handleError(err) process.exit(1) }) diff --git a/bin/now-remove.js b/bin/now-remove.js index 4b4e84d..2736bc2 100755 --- a/bin/now-remove.js +++ b/bin/now-remove.js @@ -85,24 +85,29 @@ if (argv.config) { cfg.setConfigFile(argv.config) } -Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || login(apiUrl) - } catch (err) { - error(`Authentication error – ${err.message}`) - process.exit(1) - } +Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || login(apiUrl) + } catch (err) { + error(`Authentication error – ${err.message}`) + process.exit(1) + } - try { - await remove({ token, config }) - } catch (err) { - error(`Unknown error: ${err}\n${err.stack}`) + try { + await remove({ token, config }) + } catch (err) { + error(`Unknown error: ${err}\n${err.stack}`) + process.exit(1) + } + }) + .catch(err => { + handleError(err) process.exit(1) - } -}) + }) function readConfirmation(matches) { return new Promise(resolve => { diff --git a/bin/now-scale.js b/bin/now-scale.js index 7ee062a..d807cbb 100755 --- a/bin/now-scale.js +++ b/bin/now-scale.js @@ -87,28 +87,33 @@ if (argv.help) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) + } - try { - await run({ token, config }) - } catch (err) { - if (err.userError) { - error(err.message) - } else { - error(`Unknown error: ${err}\n${err.stack}`) + try { + await run({ token, config }) + } catch (err) { + if (err.userError) { + error(err.message) + } else { + error(`Unknown error: ${err}\n${err.stack}`) + } + exit(1) } - exit(1) - } - }) + }) + .catch(err => { + handleError(err) + process.exit(1) + }) } function guessParams() { diff --git a/bin/now-secrets.js b/bin/now-secrets.js index 2457704..17e93a9 100755 --- a/bin/now-secrets.js +++ b/bin/now-secrets.js @@ -94,24 +94,29 @@ if (argv.help || !subcommand) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) + } - try { - await run({ token, config }) - } catch (err) { + try { + await run({ token, config }) + } catch (err) { + handleError(err) + exit(1) + } + }) + .catch(err => { handleError(err) - exit(1) - } - }) + process.exit(1) + }) } async function run({ token, config: { currentTeam, user } }) { diff --git a/bin/now-teams.js b/bin/now-teams.js index 072a67b..eaef587 100644 --- a/bin/now-teams.js +++ b/bin/now-teams.js @@ -14,6 +14,7 @@ const error = require('../lib/utils/output/error') const NowTeams = require('../lib/teams') const logo = require('../lib/utils/output/logo') const exit = require('../lib/utils/exit') +const { handleError } = require('../lib/error') const argv = minimist(process.argv.slice(2), { string: ['config', 'token'], @@ -91,28 +92,33 @@ if (argv.help || !subcommand) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) + } - try { - await run({ token, config }) - } catch (err) { - if (err.userError) { - error(err.message) - } else { - error(`Unknown error: ${err.stack}`) + try { + await run({ token, config }) + } catch (err) { + if (err.userError) { + error(err.message) + } else { + error(`Unknown error: ${err.stack}`) + } + exit(1) } - exit(1) - } - }) + }) + .catch(err => { + handleError(err) + process.exit(1) + }) } async function run({ token, config: { currentTeam } }) { diff --git a/bin/now-upgrade.js b/bin/now-upgrade.js index 0ad7d45..55581c9 100644 --- a/bin/now-upgrade.js +++ b/bin/now-upgrade.js @@ -16,6 +16,7 @@ const error = require('../lib/utils/output/error') const success = require('../lib/utils/output/success') const cmd = require('../lib/utils/output/cmd') const logo = require('../lib/utils/output/logo') +const { handleError } = require('../lib/error') const { bold } = chalk @@ -83,28 +84,33 @@ if (argv.help) { help() exit(0) } else { - Promise.resolve().then(async () => { - const config = await cfg.read({ token: argv.token }) - - let token - try { - token = config.token || (await login(apiUrl)) - } catch (err) { - error(`Authentication error – ${err.message}`) - exit(1) - } + Promise.resolve() + .then(async () => { + const config = await cfg.read({ token: argv.token }) + + let token + try { + token = config.token || (await login(apiUrl)) + } catch (err) { + error(`Authentication error – ${err.message}`) + exit(1) + } - try { - await run({ token, config }) - } catch (err) { - if (err.userError) { - error(err.message) - } else { - error(`Unknown error: ${err.stack}`) + try { + await run({ token, config }) + } catch (err) { + if (err.userError) { + error(err.message) + } else { + error(`Unknown error: ${err.stack}`) + } + exit(1) } - exit(1) - } - }) + }) + .catch(err => { + handleError(err) + process.exit(1) + }) } function buildInquirerChoices(current, until) { diff --git a/bin/now-whoami.js b/bin/now-whoami.js index 3a6286d..d94d4c9 100644 --- a/bin/now-whoami.js +++ b/bin/now-whoami.js @@ -9,6 +9,7 @@ const cfg = require('../lib/cfg') const exit = require('../lib/utils/exit') const cmd = require('../lib/utils/output/cmd') const logo = require('../lib/utils/output/logo') +const { handleError } = require('../lib/error') const argv = minimist(process.argv.slice(2), { string: ['config', 'token'], @@ -71,4 +72,7 @@ async function whoami() { console.log(name) } -whoami() +whoami().catch(err => { + handleError(err) + process.exit(1) +}) diff --git a/lib/error.js b/lib/error.js index d43a9bf..c94c589 100644 --- a/lib/error.js +++ b/lib/error.js @@ -5,12 +5,16 @@ const chalk = require('chalk') const error = require('./utils/output/error') const info = require('./utils/output/info') -function handleError(err) { +function handleError(err, { debug = false } = {}) { // Coerce Strings to Error instances if (typeof err === 'string') { err = new Error(err) } + if (debug) { + console.log(`> [debug] handling error: ${err.stack}`) + } + if (err.status === 403) { error( 'Authentication error. Run `now -L` or `now --login` to log-in again.' @@ -24,7 +28,7 @@ function handleError(err) { error( 'Rate limit exceeded error. Try again in ' + ms(err.retryAfter * 1000, { long: true }) + - ', or upgrade your account by runnung ' + + ', or upgrade your account by running ' + `${chalk.gray('`')}${chalk.cyan('now upgrade')}${chalk.gray('`')}` ) } @@ -35,11 +39,47 @@ function handleError(err) { } else if (err.code === 'USER_ABORT') { info('Aborted') } else { - error(`Unexpected error. Please try later. (${err.message})`) + error(`Unexpected error. Please try again later. (${err.message})`) + } +} + +async function responseError(res) { + let message + let userError + + if (res.status >= 400 && res.status < 500) { + let body + + try { + body = await res.json() + } catch (err) { + body = {} + } + + // Some APIs wrongly return `err` instead of `error` + message = (body.error || body.err || {}).message + userError = true + } else { + userError = false + } + + const err = new Error(message || 'Response error') + err.status = res.status + err.userError = userError + + if (res.status === 429) { + const retryAfter = res.headers.get('Retry-After') + + if (retryAfter) { + err.retryAfter = parseInt(retryAfter, 10) + } } + + return err } module.exports = { handleError, + responseError, error } diff --git a/lib/index.js b/lib/index.js index 3f9e314..a5361c4 100644 --- a/lib/index.js +++ b/lib/index.js @@ -14,6 +14,7 @@ const retry = require('async-retry') const splitArray = require('split-array') const { parse: parseIni } = require('ini') const { readFile, stat, lstat } = require('fs-extra') +const { responseError } = require('./error') // Ours const { @@ -526,12 +527,24 @@ module.exports = class Now extends EventEmitter { } async listAliases(deploymentId) { - return this.retry(async () => { + return this.retry(async bail => { const res = await this._fetch( deploymentId ? `/now/deployments/${deploymentId}/aliases` : '/now/aliases' ) + + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on get domain due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('API error getting aliases') + } + const body = await res.json() return body.aliases }) @@ -567,6 +580,17 @@ module.exports = class Now extends EventEmitter { console.timeEnd(`> [debug] #${attempt} GET /domains`) } + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on get domain due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('API error getting domains') + } + const body = await res.json() return body.domains }) @@ -580,6 +604,17 @@ module.exports = class Now extends EventEmitter { const res = await this._fetch(`/domains/${domain}`) + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on get domain due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('API error getting domain name') + } + if (this._debug) { console.timeEnd(`> [debug] #${attempt} GET /domains/${domain}`) } @@ -953,41 +988,6 @@ function toRelative(path, base) { return relative.replace(/\\/g, '/') } -async function responseError(res) { - let message - let userError - - if (res.status >= 400 && res.status < 500) { - let body - - try { - body = await res.json() - } catch (err) { - body = {} - } - - // Some APIs wrongly return `err` instead of `error` - message = (body.error || body.err || {}).message - userError = true - } else { - userError = false - } - - const err = new Error(message || 'Response error') - err.status = res.status - err.userError = userError - - if (res.status === 429) { - const retryAfter = res.headers.get('Retry-After') - - if (retryAfter) { - err.retryAfter = parseInt(retryAfter, 10) - } - } - - return err -} - function hasNpmStart(pkg) { return pkg.scripts && (pkg.scripts.start || pkg.scripts['now-start']) } diff --git a/lib/user.js b/lib/user.js index a3fb6de..4c648b1 100644 --- a/lib/user.js +++ b/lib/user.js @@ -1,4 +1,5 @@ const _fetch = require('node-fetch') +const { responseError } = require('./error') function _filter(data) { data = data.user @@ -34,19 +35,32 @@ async function get( fetch = _fetch } - try { - const res = await fetch(url, { headers }) + const res = await fetch(url, { headers }) - const json = await res.json() + if (res.status === 403) { + const err = Error( + 'Your authentication token is invalid. Try running `now login` to log in again.' + ) + err.userError = true + throw err + } - if (filter) { - return _filter(json) - } - return json - } catch (err) { - console.error(err.stack) - return null + if (res.status >= 400 && res.status < 500) { + const err = await responseError(res) + throw err + } + + if (res.status !== 200) { + throw new Error('API Error getting user data') } + + const json = await res.json() + + if (filter) { + return _filter(json) + } + + return json } module.exports = {