#!/usr/bin/env node // Packages const chalk = require('chalk') const minimist = require('minimist') const ms = require('ms') const stripAnsi = require('strip-ansi') // Ours const login = require('../lib/login') const cfg = require('../lib/cfg') const NowPlans = require('../lib/plans') const indent = require('../lib/indent') const listInput = require('../lib/utils/input/list') const code = require('../lib/utils/output/code') 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 argv = minimist(process.argv.slice(2), { string: ['config', 'token'], boolean: ['help', 'debug'], alias: { help: 'h', config: 'c', debug: 'd', token: 't' } }) const help = () => { console.log(` ${chalk.bold(`${logo} now upgrade`)} [plan] ${chalk.dim('Options:')} -h, --help Output usage information -c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file -d, --debug Debug mode [off] -t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token ${chalk.dim('Examples:')} ${chalk.gray('–')} List available plans and pick one interactively ${chalk.cyan('$ now upgrade')} ${chalk.yellow('NOTE:')} ${chalk.gray('Make sure you have a payment method, or add one:')} ${chalk.cyan(`$ now billing add`)} ${chalk.gray('–')} Pick a specific plan (premium): ${chalk.cyan(`$ now upgrade premium`)} `) } // options const debug = argv.debug const apiUrl = argv.url || 'https://api.zeit.co' if (argv.config) { cfg.setConfigFile(argv.config) } const exit = code => { // we give stdout some time to flush out // because there's a node bug where // stdout writes are asynchronous // https://github.com/nodejs/node/issues/6456 setTimeout(() => process.exit(code || 0), 100) } if (argv.help) { help() exit(0) } else { const config = cfg.read() Promise.resolve(argv.token || config.token || login(apiUrl)) .then(async token => { try { await run(token) } catch (err) { if (err.userError) { error(err.message) } else { error(`Unknown error: ${err.stack}`) } exit(1) } }) .catch(e => { error(`Authentication error – ${e.message}`) exit(1) }) } function buildInquirerChoices(current, until) { if (until) { until = until.split(' ') until = ' for ' + chalk.bold(until[0]) + ' more ' + until[1] } else { until = '' } const ossTitle = current === 'oss' ? `oss FREE ${' '.repeat(28)} (current)` : 'oss FREE' const premiumTitle = current === 'premium' ? `premium $15/mo ${' '.repeat(24 - stripAnsi(until).length)} (current${until})` : 'premium $15/mo' return [ { name: [ ossTitle, indent('✓ All code is public and open-source', 2), indent('✓ 20 deploys per month | 1GB monthly bandwidth', 2), indent('✓ 1GB FREE storage | 1MB size limit per file', 2) ].join('\n'), value: 'oss', short: 'oss FREE' }, { name: [ premiumTitle, indent('✓ All code is private and secure', 2), indent('✓ 1000 deploys per month | 50GB monthly bandwidth', 2), indent('✓ 100GB storage | No filesize limit', 2) ].join('\n'), value: 'premium', short: 'premium $15/mo' } ] } async function run(token) { const args = argv._ if (args.length > 1) { error('Invalid number of arguments') return exit(1) } const start = new Date() const plans = new NowPlans(apiUrl, token, {debug}) let planId = args[0] if (![undefined, 'oss', 'premium'].includes(planId)) { error(`Invalid plan name – should be ${code('oss')} or ${code('premium')}`) return exit(1) } const currentPlan = await plans.getCurrent() if (planId === undefined) { const elapsed = ms(new Date() - start) let message = `To manage this from the web UI, head to https://zeit.co/account\n` message += `> Selecting a plan for your account ${chalk.gray(`[${elapsed}]`)}` const choices = buildInquirerChoices(currentPlan.id, currentPlan.until) planId = await listInput({ message, choices, separator: true, abort: 'end' }) } if (planId === undefined || (planId === currentPlan.id && currentPlan.until === undefined)) { return console.log('No changes made') } let newPlan try { newPlan = await plans.set(planId) } catch (err) { let errorBody if (err.res && err.res.status === 400) { errorBody = err.res.json() } else { const message = 'A network error has occurred. Please retry.' errorBody = {message} } const _err = (await errorBody).error const {code, message} = _err if (code === 'customer_not_found' || code === 'source_not_found') { error(`You have no payment methods available. Run ${cmd('now billing add')} to add one`) } else { error(`An unknow error occured. Please try again later ${message}`) } plans.close() return } if (currentPlan.until && newPlan.id === 'premium') { success(`The cancelation has been undone. You're back on the ${chalk.bold('Premium plan')}`) } else if (newPlan.until) { success(`Your plan will be switched to OSS in ${chalk.bold(newPlan.until)}. Your card will not be charged again`) } else { success(`You're now on the ${chalk.bold('Premium plan')}`) } plans.close() }