From 7903aa69a6f22e8c6077a650543d399479f91230 Mon Sep 17 00:00:00 2001 From: nkzawa Date: Sat, 16 Apr 2016 01:23:37 +0900 Subject: [PATCH] add url option for development --- bin/now-deploy | 33 +++++---------------------------- bin/now-list | 43 ++++++++++++++++++++++++++++++++++++++++++- lib/agent.js | 6 +++--- lib/error.js | 25 +++++++++++++++++++++++++ lib/index.js | 32 +++++++++++++++++++++++++++++--- lib/login.js | 20 +++++++++----------- 6 files changed, 113 insertions(+), 46 deletions(-) create mode 100644 lib/error.js diff --git a/bin/now-deploy b/bin/now-deploy index 331056e..e96743b 100755 --- a/bin/now-deploy +++ b/bin/now-deploy @@ -11,6 +11,7 @@ import chalk from 'chalk'; import minimist from 'minimist'; import Now from '../lib'; import ms from 'ms'; +import { handleError, error } from '../lib/error'; const argv = minimist(process.argv.slice(2)); const help = () => { @@ -49,6 +50,7 @@ const clipboard = !(argv.noClipboard || argv.C); const force = argv.f || argv.force; const forceSync = argv.F || argv.forceSync; const shouldLogin = argv.L || argv.login; +const apiUrl = argv.url || 'https://api.now.sh'; const config = cfg.read(); @@ -59,7 +61,7 @@ if (argv.h || argv.help) { console.log(chalk.bold('𝚫 now'), version); process.exit(0); } else if (!config.token || shouldLogin) { - login() + login(apiUrl) .then((token) => { if (shouldLogin) { console.log('> Logged in successfully. Token saved in ~/.now.json'); @@ -87,13 +89,13 @@ async function sync (token) { console.log(`> Deploying "${path}"`); - const now = new Now(token, { debug }); + const now = new Now(apiUrl, token, { debug }); try { await now.create(path, { forceNew: force, forceSync: forceSync }); } catch (err) { handleError(err); - return; + process.exit(1); } const { url } = now; @@ -170,28 +172,3 @@ function printLogs (host) { process.exit(0); }); } - -function handleError (err) { - if (403 === err.status) { - error('Authentication error. Run `now -L` or `now --login` to log-in again.'); - } else if (429 === err.status) { - if (null != err.retryAfter) { - error('Rate limit exceeded error. Try again in ' + - ms(err.retryAfter * 1000, { long: true }) + - ', or upgrade your account: https://zeit.co/now#pricing'); - } else { - error('Rate limit exceeded error. Please try later.'); - } - } else if (err.userError) { - error(err.message); - } else if (500 === err.status) { - error('Unexpected server error. Please retry.'); - } else { - error(`Unexpected error. Please try later. (${err.message})`); - } - process.exit(1); -} - -function error (err) { - console.error(`> \u001b[31mError!\u001b[39m ${err}`); -} diff --git a/bin/now-list b/bin/now-list index 0aa9f4d..25310d3 100755 --- a/bin/now-list +++ b/bin/now-list @@ -1,3 +1,44 @@ #!/usr/bin/env node -console.log('!list', process.argv); +import minimist from 'minimist'; +import Now from '../lib'; +import login from '../lib/login'; +import * as cfg from '../lib/cfg'; +import { handleError, error } from '../lib/error'; + +const argv = minimist(process.argv.slice(2)); +const app = argv._[0]; + +// options +const debug = argv.debug || argv.d; +const apiUrl = argv.url || 'https://api.now.sh'; + +const config = cfg.read(); + +Promise.resolve(config.token || login(apiUrl)) +.then(async (token) => { + try { + await list(token); + } catch (err) { + error(`Unknown error: ${err.stack}`); + process.exit(1); + } +}) +.catch((e) => { + error(`Authentication error – ${e.message}`); + process.exit(1); +}); + +async function list (token) { + const now = new Now(apiUrl, token, { debug }); + + let deployments; + try { + deployments = await now.list(app); + } catch (err) { + handleError(err); + process.exit(1); + } + + console.log(deployments); +} diff --git a/lib/agent.js b/lib/agent.js index 2b55266..84debbc 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -12,8 +12,8 @@ import fetch from 'node-fetch'; */ export default class Agent { - constructor (host, { debug } = {}) { - this._host = host; + constructor (url, { debug } = {}) { + this._url = url; this._debug = debug; this._initAgent(); } @@ -54,7 +54,7 @@ export default class Agent { opts.headers['Content-Length'] = Buffer.byteLength(opts.body); } - return fetch(`https://${this._host}${path}`, opts); + return fetch(this._url + path, opts); } close () { diff --git a/lib/error.js b/lib/error.js new file mode 100644 index 0000000..acaab62 --- /dev/null +++ b/lib/error.js @@ -0,0 +1,25 @@ +import ms from 'ms'; + +export function handleError (err) { + if (403 === err.status) { + error('Authentication error. Run `now -L` or `now --login` to log-in again.'); + } else if (429 === err.status) { + if (null != err.retryAfter) { + error('Rate limit exceeded error. Try again in ' + + ms(err.retryAfter * 1000, { long: true }) + + ', or upgrade your account: https://zeit.co/now#pricing'); + } else { + error('Rate limit exceeded error. Please try later.'); + } + } else if (err.userError) { + error(err.message); + } else if (500 === err.status) { + error('Unexpected server error. Please retry.'); + } else { + error(`Unexpected error. Please try later. (${err.message})`); + } +} + +export function error (err) { + console.error(`> \u001b[31mError!\u001b[39m ${err}`); +} diff --git a/lib/index.js b/lib/index.js index a34fbe2..773dd97 100644 --- a/lib/index.js +++ b/lib/index.js @@ -20,12 +20,12 @@ const IS_WIN = /^win/.test(process.platform); const SEP = IS_WIN ? '\\' : '/'; export default class Now extends EventEmitter { - constructor (token, { forceNew = false, debug = false }) { + constructor (url, token, { forceNew = false, debug = false }) { super(); this._token = token; this._debug = debug; this._forceNew = forceNew; - this._agent = new Agent('api.now.sh', { debug }); + this._agent = new Agent(url, { debug }); this._onRetry = this._onRetry.bind(this); } @@ -159,6 +159,32 @@ export default class Now extends EventEmitter { uploadChunk(); } + async list (app) { + const query = app ? `?app=${encodeURIComponent(app)}` : ''; + + const { deployments } = await retry(async (bail) => { + if (this._debug) console.time('> [debug] /list'); + const res = await this._fetch('/list' + query); + if (this._debug) console.timeEnd('> [debug] /list'); + + // no retry on 4xx + if (400 <= res.status && 500 > res.status) { + if (this._debug) { + console.log('> [debug] bailing on listing due to %s', res.status); + } + return bail(responseError(res)); + } + + if (200 !== res.status) { + throw new Error('Fetching deployment list failed'); + } + + return res.json(); + }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry }); + + return deployments; + } + _onRetry (err) { if (this._debug) { console.log(`> [debug] Retrying: ${err.stack}`); @@ -190,7 +216,7 @@ export default class Now extends EventEmitter { return this._syncAmount; } - async _fetch (_url, opts) { + async _fetch (_url, opts = {}) { opts.headers = opts.headers || {}; opts.headers.authorization = `Bearer ${this._token}`; return await this._agent.fetch(_url, opts); diff --git a/lib/login.js b/lib/login.js index 1024aa3..8a831d0 100644 --- a/lib/login.js +++ b/lib/login.js @@ -3,8 +3,6 @@ import fetch from 'node-fetch'; import * as cfg from './cfg'; import { stringify as stringifyQuery } from 'querystring'; -const URL = 'https://api.now.sh/registration'; - const stdin = process.openStdin(); function readEmail () { @@ -17,9 +15,9 @@ function readEmail () { }); } -async function getVerificationToken (email) { +async function getVerificationToken (url, email) { const data = JSON.stringify({ email }); - const res = await fetch(URL, { + const res = await fetch(url + '/registration', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -36,13 +34,13 @@ async function getVerificationToken (email) { return body.token; } -async function verify (email, verificationToken) { +async function verify (url, email, verificationToken) { const query = { email, token: verificationToken }; - const res = await fetch(`${URL}/verify?${stringifyQuery(query)}`); + const res = await fetch(`${url}/registration/verify?${stringifyQuery(query)}`); const body = await res.json(); return body.token; } @@ -53,9 +51,9 @@ function sleep (ms) { }); } -async function register () { +async function register (url) { const email = await readEmail(); - const verificationToken = await getVerificationToken(email); + const verificationToken = await getVerificationToken(url, email); console.log(`> Please follow the link sent to ${chalk.bold(email)} to log in.`); process.stdout.write('> Waiting for confirmation..'); @@ -64,7 +62,7 @@ async function register () { do { await sleep(2500); try { - final = await verify(email, verificationToken); + final = await verify(url, email, verificationToken); } catch (e) {} process.stdout.write('.'); } while (!final); @@ -74,8 +72,8 @@ async function register () { return { email, token: final }; } -export default async function () { - const loginData = await register(); +export default async function (url) { + const loginData = await register(url); cfg.merge(loginData); return loginData.token; }