Tony Kovanen
9 years ago
8 changed files with 331 additions and 204 deletions
@ -0,0 +1,174 @@ |
|||
#!/usr/bin/env node |
|||
import Progress from 'progress'; |
|||
import copy from '../lib/copy'; |
|||
import { resolve } from 'path'; |
|||
import login from '../lib/login'; |
|||
import * as cfg from '../lib/cfg'; |
|||
import { version } from '../../package'; |
|||
import Logger from '../lib/build-logger'; |
|||
import bytes from 'bytes'; |
|||
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 = () => { |
|||
console.log(` |
|||
𝚫 now [options] <command|path> |
|||
|
|||
Commands: |
|||
|
|||
list output list of instances |
|||
ls alias of list |
|||
|
|||
Options: |
|||
|
|||
-h, --help output usage information |
|||
-v, --version output the version number |
|||
-d, --debug Debug mode [off] |
|||
-f, --force Force a new deployment even if nothing has changed |
|||
-L, --login Configure login |
|||
-C, --no-clipboard Do not attempt to copy URL to clipboard |
|||
`); |
|||
}; |
|||
|
|||
let path = argv._[0]; |
|||
|
|||
if (path) { |
|||
if ('/' !== path[0]) { |
|||
path = resolve(process.cwd(), path); |
|||
} |
|||
} else { |
|||
path = process.cwd(); |
|||
} |
|||
|
|||
// options |
|||
const debug = argv.debug || argv.d; |
|||
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(); |
|||
|
|||
if (argv.h || argv.help) { |
|||
help(); |
|||
process.exit(0); |
|||
} else if (argv.v || argv.version) { |
|||
console.log(chalk.bold('𝚫 now'), version); |
|||
process.exit(0); |
|||
} else if (!config.token || shouldLogin) { |
|||
login(apiUrl) |
|||
.then((token) => { |
|||
if (shouldLogin) { |
|||
console.log('> Logged in successfully. Token saved in ~/.now.json'); |
|||
process.exit(0); |
|||
} else { |
|||
sync(token).catch((err) => { |
|||
error(`Unknown error: ${err.stack}`); |
|||
process.exit(1); |
|||
}); |
|||
} |
|||
}) |
|||
.catch((e) => { |
|||
error(`Authentication error – ${e.message}`); |
|||
process.exit(1); |
|||
}); |
|||
} else { |
|||
sync(config.token).catch((err) => { |
|||
error(`Unknown error: ${err.stack}`); |
|||
process.exit(1); |
|||
}); |
|||
} |
|||
|
|||
async function sync (token) { |
|||
const start = Date.now(); |
|||
|
|||
console.log(`> Deploying "${path}"`); |
|||
|
|||
const now = new Now(apiUrl, token, { debug }); |
|||
|
|||
try { |
|||
await now.create(path, { forceNew: force, forceSync: forceSync }); |
|||
} catch (err) { |
|||
handleError(err); |
|||
process.exit(1); |
|||
} |
|||
|
|||
const { url } = now; |
|||
const elapsed = ms(new Date() - start); |
|||
|
|||
if (clipboard) { |
|||
try { |
|||
await copy(url); |
|||
console.log(`${chalk.cyan('> Ready!')} ${chalk.bold(url)} (copied to clipboard) [${elapsed}]`); |
|||
} catch (err) { |
|||
console.log(`${chalk.cyan('> Ready!')} ${chalk.bold(url)} [${elapsed}]`); |
|||
} |
|||
} else { |
|||
console.log(`> ${url} [${elapsed}]`); |
|||
} |
|||
|
|||
const start_u = new Date(); |
|||
const complete = () => { |
|||
const elapsed_u = ms(new Date() - start_u); |
|||
console.log(`> Sync complete (${bytes(now.syncAmount)}) [${elapsed_u}] `); |
|||
|
|||
// close http2 agent |
|||
now.close(); |
|||
|
|||
// show build logs |
|||
printLogs(now.host); |
|||
}; |
|||
|
|||
if (now.syncAmount) { |
|||
const bar = new Progress('> Upload [:bar] :percent :etas', { |
|||
width: 20, |
|||
complete: '=', |
|||
incomplete: '', |
|||
total: now.syncAmount |
|||
}); |
|||
|
|||
now.upload(); |
|||
|
|||
now.on('upload', ({ name, data }) => { |
|||
const amount = data.length; |
|||
if (debug) { |
|||
console.log(`> [debug] Uploaded: ${name} (${bytes(data.length)})`); |
|||
} |
|||
bar.tick(amount); |
|||
}); |
|||
|
|||
now.on('complete', complete); |
|||
|
|||
now.on('error', (err) => { |
|||
error('Upload failed'); |
|||
handleError(err); |
|||
process.exit(1); |
|||
}); |
|||
} else { |
|||
console.log('> Sync complete (cached)'); |
|||
|
|||
// close http2 agent |
|||
now.close(); |
|||
|
|||
// show build logs |
|||
printLogs(now.host); |
|||
} |
|||
} |
|||
|
|||
function printLogs (host) { |
|||
// log build |
|||
const logger = new Logger(host); |
|||
logger.on('error', () => { |
|||
console.log('> Connection error.'); |
|||
process.exit(1); |
|||
}); |
|||
logger.on('close', () => { |
|||
console.log(`${chalk.cyan('> Deployment complete!')}`); |
|||
process.exit(0); |
|||
}); |
|||
} |
@ -0,0 +1,68 @@ |
|||
#!/usr/bin/env node |
|||
|
|||
import minimist from 'minimist'; |
|||
import chalk from 'chalk'; |
|||
import table from 'text-table'; |
|||
import ms from 'ms'; |
|||
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); |
|||
} |
|||
|
|||
now.close(); |
|||
|
|||
const apps = new Map(); |
|||
for (const dep of deployments) { |
|||
const deps = apps.get(dep.name) || []; |
|||
apps.set(dep.name, deps.concat(dep)); |
|||
} |
|||
|
|||
const current = Date.now(); |
|||
const text = [...apps].map(([name, deps]) => { |
|||
const t = table(deps.map(({ uid, url, created }) => { |
|||
const time = ms(current - created, { long: true }) + ' ago'; |
|||
return [ uid, time, `https://${url}` ]; |
|||
}), { align: ['l', 'r', 'l'] }); |
|||
return chalk.bold(name) + '\n\n' + indent(t, 2).split('\n').join('\n\n'); |
|||
}).join('\n\n'); |
|||
|
|||
if (text) console.log('\n' + text + '\n'); |
|||
} |
|||
|
|||
function indent (text, n) { |
|||
return text.split('\n').map((l) => ' '.repeat(n) + l).join('\n'); |
|||
} |
@ -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}`); |
|||
} |
Loading…
Reference in new issue