Browse Source

Add `now billing` and `now upgrade` (#309)

* Add the skeleton of `now cc`

* Add the `ls` command

* Add `inquirer` dependency

* Add the `set-default` command

* Fix typo

* Show the real number of cards when selecting the default one

* Add the `ls` command

* Fix: Do not throw if there's no cards in the account

* Add `blessed` dependency

* Add the first sketch of `now cc add`

* Add instructions

* Add labels

* Save every element in the `elements` array instead of variables

* Tweaks

* Fix: update the element attribute if it's not a special case

* Add the `name` input; Add moving between inputs; Make the state more reliable

* Auto "detect" if the input is losing focus

* Remove useless stuff

* Add the ability to move between the fields with tab/shift+tab

* Add CCV field

* Make the cycling between the fields "infinite"

* Add expiration date field and allow only numbers in the CCV field

* The form shouldn't have a fixed height

* Add the address box and label

* Add the address fields

* Remove blessed stuff

* Add preliminary input field

* output utils

* add prompt for booleans

* fix @matheuss linting problems

* remove example

* lint

* error and info helpers

* helper for embedded commands

* Remove useless stuff

* Add `trailing` option

* Add `resolveChars` option

* Add `validate` option

* Add `strip-ansi` dependency

* Add `credit-card` dependency

* Add credit card masking

* Add support for expiration date mask

* Make things simpler

* Add auto completion support

* Always show the `card_` id prefix

* Add `@google/maps` dependency

* Always print the initial value if it's available

* Add `stripe` dependency

* Add `add()` method

* Add billing related utils

* Add `now cc add`

* Rename `cc` to `billing`

* Fix: log only one blank line

* Refactor

* Add list input component

* This shouldn't be here

* Add `code` output util

* Add `now upgrade | downgrade`

* add build step

* make it more future-proof

* more reliable build

* remove lock for now

* Hide the CCV

* Print the new line before `Saving card`

* Use the new `success` component

* Add confirmation steps for `cc rm` and `cc set-default`

* Temporarily monket patch Inquirer

* Build before testing

* Run the tests using the built files

* Fix the `prepublish` script and run the `build` one before packaging

* Improve `now help`
master
Matheus Fernandes 8 years ago
committed by GitHub
parent
commit
47a5c69c26
  1. 1
      .gitignore
  2. 203
      bin/now-billing-add.js
  3. 315
      bin/now-billing.js
  4. 29
      bin/now-deploy.js
  5. 207
      bin/now-upgrade.js
  6. 18
      bin/now.js
  7. 10
      build.sh
  8. 70
      lib/credit-cards.js
  9. 48
      lib/plans.js
  10. 8
      lib/utils/billing/card-brands.json
  11. 251
      lib/utils/billing/country-list.json
  12. 27
      lib/utils/billing/geocode.js
  13. 86
      lib/utils/input/list.js
  14. 207
      lib/utils/input/text.js
  15. 8
      lib/utils/output/cmd.js
  16. 8
      lib/utils/output/code.js
  17. 7
      lib/utils/output/error.js
  18. 7
      lib/utils/output/info.js
  19. 8
      lib/utils/output/param.js
  20. 54
      lib/utils/output/prompt-bool.js
  21. 12
      lib/utils/output/stamp.js
  22. 6
      lib/utils/output/success.js
  23. 7
      lib/utils/output/uid.js
  24. 15
      lib/utils/output/wait.js
  25. 18
      package.json
  26. 2
      test/args-parsing.js
  27. 6
      test/index.js
  28. 2
      test/to-host.js

1
.gitignore

@ -6,3 +6,4 @@ node_modules
# logs
npm-debug.log
build

203
bin/now-billing-add.js

@ -0,0 +1,203 @@
#!/usr/bin/env node
const ansiEscapes = require('ansi-escapes')
const chalk = require('chalk')
const ccValidator = require('credit-card')
const textInput = require('../lib/utils/input/text')
const countries = require('../lib/utils/billing/country-list')
const cardBrands = require('../lib/utils/billing/card-brands')
const geocode = require('../lib/utils/billing/geocode')
const success = require('../lib/utils/output/success')
const wait = require('../lib/utils/output/wait')
function rightPad(string, n = 12) {
n -= string.length
return string + ' '.repeat(n > -1 ? n : 0)
}
function expDateMiddleware(data) {
return data
}
module.exports = function (creditCards) {
const state = {
error: undefined,
cardGroupLabel: `> ${chalk.bold('Enter your card details')}`,
name: {
label: rightPad('Name'),
placeholder: 'John Appleseed',
validateValue: data => data.trim().length > 0
},
cardNumber: {
label: rightPad('Number'),
mask: 'cc',
placeholder: '#### #### #### ####',
validateKeypress: (data, value) => (
/\d/.test(data) && value.length < 19
),
validateValue: data => {
data = data.replace(/ /g, '')
const type = ccValidator.determineCardType(data)
if (!type) {
return false
}
return ccValidator.isValidCardNumber(data, type)
}
},
ccv: {
label: rightPad('CCV'),
mask: 'ccv',
placeholder: '###',
validateValue: data => {
const brand = state.cardNumber.brand.toLowerCase()
return ccValidator.doesCvvMatchType(data, brand)
}
},
expDate: {
label: rightPad('Exp. Date'),
mask: 'expDate',
placeholder: 'mm / yyyy',
middleware: expDateMiddleware,
validateValue: data => !ccValidator.isExpired(...data.split(' / '))
},
addressGroupLabel: `\n> ${chalk.bold('Enter your billing address')}`,
country: {
label: rightPad('Country'),
async autoComplete(value) {
for (const country in countries) {
if (!Object.hasOwnProperty.call(countries, country)) {
continue
}
if (country.startsWith(value)) {
return country.substr(value.length)
}
}
return false
},
validateValue: value => countries[value] !== undefined
},
zipCode: {
label: rightPad('ZIP'),
validadeKeypress: data => data.trim().length > 0,
validateValue: data => data.trim().length > 0
},
state: {
label: rightPad('State'),
validateValue: data => data.trim().length > 0
},
city: {
label: rightPad('City'),
validateValue: data => data.trim().length > 0
},
address1: {
label: rightPad('Address'),
validateValue: data => data.trim().length > 0
}
}
async function render() {
for (const key in state) {
if (!Object.hasOwnProperty.call(state, key)) {
continue
}
const piece = state[key]
if (typeof piece === 'string') {
console.log(piece)
} else if (typeof piece === 'object') {
let result
try {
result = await textInput({
label: '- ' + piece.label,
initialValue: piece.initialValue || piece.value,
placeholder: piece.placeholder,
mask: piece.mask,
validateKeypress: piece.validateKeypress,
validateValue: piece.validateValue,
autoComplete: piece.autoComplete
})
piece.value = result
if (key === 'cardNumber') {
let brand = cardBrands[ccValidator.determineCardType(result)]
piece.brand = brand
if (brand === 'American Express') {
state.ccv.placeholder = '#'.repeat(4)
} else {
state.ccv.placeholder = '#'.repeat(3)
}
brand = chalk.cyan(`[${brand}]`)
process.stdout.write(
`${chalk.cyan('✓')} ${piece.label}${result} ${brand}\n`
)
} else if (key === 'ccv') {
process.stdout.write(
`${chalk.cyan('✓')} ${piece.label}${'*'.repeat(result.length)}\n`
)
} else if (key === 'expDate') {
let text = result.split(' / ')
text = text[0] + chalk.gray(' / ') + text[1]
process.stdout.write(`${chalk.cyan('✓')} ${piece.label}${text}\n`)
} else if (key === 'zipCode') {
const stopSpinner = wait(piece.label + result)
const addressInfo = await geocode({
country: state.country.value,
zipCode: result
})
if (addressInfo.state) {
state.state.initialValue = addressInfo.state
}
if (addressInfo.city) {
state.city.initialValue = addressInfo.city
}
stopSpinner()
process.stdout.write(`${chalk.cyan('✓')} ${piece.label}${result}\n`)
} else {
process.stdout.write(`${chalk.cyan('✓')} ${piece.label}${result}\n`)
}
} catch (err) {
if (err.message === 'USER_ABORT') {
process.exit(1)
} else {
console.error(err)
}
}
}
}
console.log('') // new line
const stopSpinner = wait('Saving card')
try {
const res = await creditCards.add({
name: state.name.value,
cardNumber: state.cardNumber.value,
ccv: state.ccv.value,
expDate: state.expDate.value,
country: state.country.value,
zipCode: state.zipCode.value,
state: state.state.value,
city: state.city.value,
address1: state.address1.value
})
stopSpinner()
success(`${state.cardNumber.brand} ending in ${res.last4} was added to your account`)
} catch (err) {
stopSpinner()
const linesToClear = state.error ? 13 : 12
process.stdout.write(ansiEscapes.eraseLines(linesToClear))
state.error = `${chalk.red('> Error!')} ${err.message} Please make sure the info is correct`
await render()
}
}
render().catch(console.error)
}

315
bin/now-billing.js

@ -0,0 +1,315 @@
#!/usr/bin/env node
// Native
const {resolve} = require('path')
// Packages
const chalk = require('chalk')
const minimist = require('minimist')
const ms = require('ms')
// Ours
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const {error} = require('../lib/error')
const NowCreditCards = require('../lib/credit-cards')
const indent = require('../lib/indent')
const listInput = require('../lib/utils/input/list')
const success = require('../lib/utils/output/success')
const promptBool = require('../lib/utils/output/prompt-bool')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
}
})
const subcommand = argv._[0]
const help = () => {
console.log(`
${chalk.bold('𝚫 now billing')} <ls | add | rm | set-default>
${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('–')} Lists all your credit cards:
${chalk.cyan('$ now billing ls')}
${chalk.gray('–')} Adds a credit card (interactively):
${chalk.cyan(`$ now billing add`)}
${chalk.gray('–')} Removes a credit card:
${chalk.cyan(`$ now billing rm <id>`)}
${chalk.gray('–')} If the id is ommitted, you can choose interactively
${chalk.gray('–')} Selects your default credit card:
${chalk.cyan(`$ now billing set-default <id>`)}
${chalk.gray('–')} If the id is ommitted, you can choose interactively
`)
}
// 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 || !subcommand) {
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)
})
}
// Builds a `choices` object that can be passesd to inquirer.prompt()
function buildInquirerChoices(cards) {
return cards.cards.map(card => {
const _default = card.id === cards.defaultCardId ? ' ' + chalk.bold('(default)') : ''
const id = `${chalk.cyan(`ID: ${card.id}`)}${_default}`
const number = `${chalk.gray('#### ').repeat(3)}${card.last4}`
const str = [
id,
indent(card.name, 2),
indent(`${card.brand} ${number}`, 2)
].join('\n')
return {
name: str, // Will be displayed by Inquirer
value: card.id, // Will be used to identify the answer
short: card.id // Will be displayed after the users answers
}
})
}
async function run(token) {
const start = new Date()
const creditCards = new NowCreditCards(apiUrl, token, {debug})
const args = argv._.slice(1)
switch (subcommand) {
case 'ls':
case 'list': {
const cards = await creditCards.ls()
const text = cards.cards.map(card => {
const _default = card.id === cards.defaultCardId ? ' ' + chalk.bold('(default)') : ''
const id = `${chalk.gray('-')} ${chalk.cyan(`ID: ${card.id}`)}${_default}`
const number = `${chalk.gray('#### ').repeat(3)}${card.last4}`
let address = card.address_line1
if (card.address_line2) {
address += `, ${card.address_line2}.`
} else {
address += '.'
}
address += `\n${card.address_city}, `
if (card.address_state) {
address += `${card.address_state}, `
}
// TODO: Stripe is returning a two digit code for the country,
// but we want the full country name
address += `${card.address_zip}. ${card.address_country}`
return [
id,
indent(card.name, 2),
indent(`${card.brand} ${number}`, 2),
indent(address, 2)
].join('\n')
}).join('\n\n')
const elapsed = ms(new Date() - start)
console.log(`> ${cards.cards.length} card${cards.cards.length === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed}]`)}`)
if (text) {
console.log(`\n${text}\n`)
}
break
}
case 'set-default': {
if (args.length > 1) {
error('Invalid number of arguments')
return exit(1)
}
const start = new Date()
const cards = await creditCards.ls()
if (cards.cards.length === 0) {
error('You have no credit cards to choose from')
return exit(0)
}
let cardId = args[0]
if (cardId === undefined) {
const elapsed = ms(new Date() - start)
const message = `Selecting a new default payment card from ${cards.cards.length} total ${chalk.gray(`[${elapsed}]`)}`
const choices = buildInquirerChoices(cards)
cardId = await listInput({
message,
choices,
separator: true,
abort: 'end'
})
}
// TODO: check if the provided cardId (in case the user
// typed `now billing set-default <some-id>`) is valid
if (cardId) {
const label = `Are you sure that you to set this card as the default?`
const confirmation = await promptBool(label)
console.log('') // new line
if (!confirmation) {
console.log('Aborted')
break
}
const start = new Date()
await creditCards.setDefault(cardId)
const card = cards.cards.find(card => card.id === cardId)
const elapsed = ms(new Date() - start)
success(`${card.brand} ending in ${card.last4} is now the default ${chalk.gray(`[${elapsed}]`)}`)
} else {
console.log('No changes made')
}
break
}
case 'rm':
case 'remove': {
if (args.length > 1) {
error('Invalid number of arguments')
return exit(1)
}
const start = new Date()
const cards = await creditCards.ls()
if (cards.cards.length === 0) {
error('You have no credit cards to choose from to delete')
return exit(0)
}
let cardId = args[0]
if (cardId === undefined) {
const elapsed = ms(new Date() - start)
const message = `Selecting a card to ${chalk.underline('remove')} from ${cards.cards.length} total ${chalk.gray(`[${elapsed}]`)}`
const choices = buildInquirerChoices(cards)
cardId = await listInput({
message,
choices,
separator: true,
abort: 'start'
})
}
// TODO: check if the provided cardId (in case the user
// typed `now billing rm <some-id>`) is valid
if (cardId) {
const label = `Are you sure that you want to remove this card?`
const confirmation = await promptBool(label)
console.log('') // new line
if (!confirmation) {
console.log('Aborted')
break
}
const start = new Date()
await creditCards.rm(cardId)
const deletedCard = cards.cards.find(card => card.id === cardId)
const remainingCards = cards.cards.filter(card => card.id !== cardId)
let text = `${deletedCard.brand} ending in ${deletedCard.last4} was deleted`
// ${chalk.gray(`[${elapsed}]`)}
if (cardId === cards.defaultCardId) {
if (remainingCards.length === 0) {
// The user deleted the last card in their account
text += `\n${chalk.yellow('Warning!')} You have no default card`
} else {
// We can't guess the current default card – let's ask the API
const cards = await creditCards.ls()
const newDefaultCard = cards.cards.find(card => card.id === cards.defaultCardId)
text += `\n${newDefaultCard.brand} ending in ${newDefaultCard.last4} in now default`
}
}
const elapsed = ms(new Date() - start)
text += ` ${chalk.gray(`[${elapsed}]`)}`
success(text)
} else {
console.log('No changes made')
}
break
}
case 'add': {
require(resolve(__dirname, 'now-billing-add.js'))(creditCards)
break
}
default:
error('Please specify a valid subcommand: ls | add | rm | set-default')
help()
exit(1)
}
creditCards.close()
}

29
bin/now-deploy.js

@ -73,17 +73,24 @@ const help = () => {
console.log(`
${chalk.bold('𝚫 now')} [options] <command | path>
${chalk.dim('Commands:')}
deploy [path] Performs a deployment ${chalk.bold('(default)')}
ls | list [app] List deployments
rm | remove [id] Remove a deployment
ln | alias [id] [url] Configures aliases for deployments
domains [name] Manages your domain names
certs [cmd] Manages your SSL certificates
secrets [name] Manages your secret environment variables
dns [name] Manages your DNS records
help [cmd] Displays complete help for [cmd]
${chalk.dim('Commands')}
${chalk.dim('Cloud')}
deploy [path] Performs a deployment ${chalk.bold('(default)')}
ls | list [app] List deployments
rm | remove [id] Remove a deployment
ln | alias [id] [url] Configures aliases for deployments
domains [name] Manages your domain names
certs [cmd] Manages your SSL certificates
secrets [name] Manages your secret environment variables
dns [name] Manages your DNS records
help [cmd] Displays complete help for [cmd]
${chalk.dim('Administrative')}
billing | cc [cmd] Manages your credit cards and billing methods
upgrade | downgrade [plan] Upgrades or downgrades your plan
${chalk.dim('Options:')}

207
bin/now-upgrade.js

@ -0,0 +1,207 @@
#!/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 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('𝚫 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)
const 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()
}

18
bin/now.js

@ -12,14 +12,6 @@ const chalk = require('chalk')
const {error} = require('../lib/error')
const pkg = require('../package')
const pathSep = process.platform === 'win32' ? '\\\\' : '/'
// Support for keywords "async" and "await"
require('async-to-gen/register')({
includes: new RegExp(`.*now(-cli)?${pathSep}(lib|bin).*`),
excludes: null,
sourceMaps: false
})
// Throw an error if node version is too low
if (nodeVersion.major < 6) {
error('Now requires at least version 6 of Node. Please upgrade!')
@ -59,7 +51,11 @@ const commands = new Set([
'cert',
'certs',
'secret',
'secrets'
'secrets',
'cc',
'billing',
'upgrade',
'downgrade'
])
const aliases = new Map([
@ -69,7 +65,9 @@ const aliases = new Map([
['aliases', 'alias'],
['domain', 'domains'],
['cert', 'certs'],
['secret', 'secrets']
['secret', 'secrets'],
['cc', 'billing'],
['downgrade', 'upgrade']
])
let cmd = defaultCommand

10
build.sh

@ -0,0 +1,10 @@
#!/usr/bin/env bash
rm -rf build
mkdir -p build/{lib,bin}
find lib/** -type d -exec mkdir -p build/{} \;
find bin/** -type d -exec mkdir -p build/{} \;
find lib/** -type f -exec node_modules/.bin/async-to-gen --out-file build/{} {} \;
find bin/** -type f -exec node_modules/.bin/async-to-gen --out-file build/{} {} \;
chmod +x build/bin/now.js
cp lib/utils/billing/*.json build/lib/utils/billing/
cp package.json build/

70
lib/credit-cards.js

@ -0,0 +1,70 @@
const stripe = require('stripe')('pk_live_alyEi3lN0kSwbdevK0nrGwTw')
const Now = require('../lib')
module.exports = class CreditCards extends Now {
async ls() {
const res = await this._fetch('/www/user/cards')
const body = await res.json()
return body
}
async setDefault(cardId) {
await this._fetch('/www/user/cards/default', {
method: 'PUT',
body: {cardId}
})
return true
}
async rm(cardId) {
await this._fetch(`/www/user/cards/${encodeURIComponent(cardId)}`, {method: 'DELEtE'})
return true
}
/* eslint-disable camelcase */
add(card) {
return new Promise(async (resolve, reject) => {
const expDateParts = card.expDate.split(' / ')
card = {
name: card.name,
number: card.cardNumber,
cvc: card.ccv,
address_country: card.country,
address_zip: card.zipCode,
address_state: card.state,
address_city: card.city,
address_line1: card.address1
}
card.exp_month = expDateParts[0]
card.exp_year = expDateParts[1]
try {
const stripeToken = (await stripe.tokens.create({card})).id
const res = await this._fetch('/www/user/cards', {
method: 'POST',
body: {stripeToken}
})
const body = await res.json()
if (body.card && body.card.id) {
resolve({
last4: body.card.last4
})
} else if (body.error && body.error.message) {
reject({message: body.error.message})
} else {
reject('Unknown error')
}
} catch (err) {
reject({
message: err.message || 'Unknown error'
})
}
})
}
}

48
lib/plans.js

@ -0,0 +1,48 @@
const ms = require('ms')
const Now = require('../lib')
async function parsePlan(res) {
let id
let until
const {subscription} = await res.json()
if (subscription) {
id = subscription.plan.id
if (subscription.cancel_at_period_end) {
until = ms(
new Date(subscription.current_period_end * 1000) - new Date(),
{long: true}
)
}
} else {
id = 'oss'
}
return {id, until}
}
module.exports = class Plans extends Now {
async getCurrent() {
const res = await this._fetch('/www/user/plan')
return await parsePlan(res)
}
async set(plan) {
const res = await this._fetch('/www/user/plan', {
method: 'PUT',
body: {plan}
})
if (res.ok) {
return await parsePlan(res)
}
const err = new Error(res.statusText)
err.res = res
throw err
}
}

8
lib/utils/billing/card-brands.json

@ -0,0 +1,8 @@
{
"VISA": "Visa",
"MASTERCARD": "MasterCard",
"AMERICANEXPRESS": "American Express",
"DINERSCLUB": "Diners Club",
"DISCOVER": "Discover",
"JCB": "JCB"
}

251
lib/utils/billing/country-list.json

@ -0,0 +1,251 @@
{
"United States": "US",
"Afghanistan": "AF",
"Åland Islands": "AX",
"Albania": "AL",
"Algeria": "DZ",
"American Samoa": "AS",
"Andorra": "AD",
"Angola": "AO",
"Anguilla": "AI",
"Antarctica": "AQ",
"Antigua and Barbuda": "AG",
"Argentina": "AR",
"Armenia": "AM",
"Aruba": "AW",
"Australia": "AU",
"Austria": "AT",
"Azerbaijan": "AZ",
"Bahamas": "BS",
"Bahrain": "BH",
"Bangladesh": "BD",
"Barbados": "BB",
"Belarus": "BY",
"Belgium": "BE",
"Belize": "BZ",
"Benin": "BJ",
"Bermuda": "BM",
"Bhutan": "BT",
"Bolivia, Plurinational State of": "BO",
"Bonaire, Sint Eustatius and Saba": "BQ",
"Bosnia and Herzegovina": "BA",
"Botswana": "BW",
"Bouvet Island": "BV",
"Brazil": "BR",
"British Indian Ocean Territory": "IO",
"Brunei Darussalam": "BN",
"Bulgaria": "BG",
"Burkina Faso": "BF",
"Burundi": "BI",
"Cambodia": "KH",
"Cameroon": "CM",
"Canada": "CA",
"Cape Verde": "CV",
"Cayman Islands": "KY",
"Central African Republic": "CF",
"Chad": "TD",
"Chile": "CL",
"China": "CN",
"Christmas Island": "CX",
"Cocos (Keeling) Islands": "CC",
"Colombia": "CO",
"Comoros": "KM",
"Congo": "CG",
"CD": "Congo, the Democratic Republic of the",
"Cook Islands": "CK",
"Costa Rica": "CR",
"Côte d'Ivoire": "CI",
"Croatia": "HR",
"Cuba": "CU",
"Curaçao": "CW",
"Cyprus": "CY",
"Czech Republic": "CZ",
"Denmark": "DK",
"Djibouti": "DJ",
"Dominica": "DM",
"Dominican Republic": "DO",
"Ecuador": "EC",
"Egypt": "EG",
"El Salvador": "SV",
"Equatorial Guinea": "GQ",
"Eritrea": "ER",
"Estonia": "EE",
"Ethiopia": "ET",
"Falkland Islands (Malvinas)": "FK",
"Faroe Islands": "FO",
"Fiji": "FJ",
"Finland": "FI",
"France": "FR",
"French Guiana": "GF",
"French Polynesia": "PF",
"French Southern Territories": "TF",
"Gabon": "GA",
"Gambia": "GM",
"Georgia": "GE",
"Germany": "DE",
"Ghana": "GH",
"Gibraltar": "GI",
"Greece": "GR",
"Greenland": "GL",
"Grenada": "GD",
"Guadeloupe": "GP",
"Guam": "GU",
"Guatemala": "GT",
"Guernsey": "GG",
"Guinea": "GN",
"Guinea-Bissau": "GW",
"Guyana": "GY",
"Haiti": "HT",
"Heard Island and McDonald Islands": "HM",
"Holy See (Vatican City State)": "VA",
"Honduras": "HN",
"Hong Kong": "HK",
"Hungary": "HU",
"Iceland": "IS",
"India": "IN",
"Indonesia": "ID",
"Iran, Islamic Republic of": "IR",
"Iraq": "IQ",
"Ireland": "IE",
"Isle of Man": "IM",
"Israel": "IL",
"Italy": "IT",
"Jamaica": "JM",
"Japan": "JP",
"Jersey": "JE",
"Jordan": "JO",
"Kazakhstan": "KZ",
"Kenya": "KE",
"Kiribati": "KI",
"KP": "Korea, Democratic People's Republic of",
"Korea, Republic of": "KR",
"Kuwait": "KW",
"Kyrgyzstan": "KG",
"Lao People's Democratic Republic": "LA",
"Latvia": "LV",
"Lebanon": "LB",
"Lesotho": "LS",
"Liberia": "LR",
"Libya": "LY",
"Liechtenstein": "LI",
"Lithuania": "LT",
"Luxembourg": "LU",
"Macao": "MO",
"MK": "Macedonia, the former Yugoslav Republic of",
"Madagascar": "MG",
"Malawi": "MW",
"Malaysia": "MY",
"Maldives": "MV",
"Mali": "ML",
"Malta": "MT",
"Marshall Islands": "MH",
"Martinique": "MQ",
"Mauritania": "MR",
"Mauritius": "MU",
"Mayotte": "YT",
"Mexico": "MX",
"Micronesia, Federated States of": "FM",
"Moldova, Republic of": "MD",
"Monaco": "MC",
"Mongolia": "MN",
"Montenegro": "ME",
"Montserrat": "MS",
"Morocco": "MA",
"Mozambique": "MZ",
"Myanmar": "MM",
"Namibia": "NA",
"Nauru": "NR",
"Nepal": "NP",
"Netherlands": "NL",
"New Caledonia": "NC",
"New Zealand": "NZ",
"Nicaragua": "NI",
"Niger": "NE",
"Nigeria": "NG",
"Niue": "NU",
"Norfolk Island": "NF",
"Northern Mariana Islands": "MP",
"Norway": "NO",
"Oman": "OM",
"Pakistan": "PK",
"Palau": "PW",
"Palestinian Territory, Occupied": "PS",
"Panama": "PA",
"Papua New Guinea": "PG",
"Paraguay": "PY",
"Peru": "PE",
"Philippines": "PH",
"Pitcairn": "PN",
"Poland": "PL",
"Portugal": "PT",
"Puerto Rico": "PR",
"Qatar": "QA",
"Réunion": "RE",
"Romania": "RO",
"Russian Federation": "RU",
"Rwanda": "RW",
"Saint Barthélemy": "BL",
"SH": "Saint Helena, Ascension and Tristan da Cunha",
"Saint Kitts and Nevis": "KN",
"Saint Lucia": "LC",
"Saint Martin (French part)": "MF",
"Saint Pierre and Miquelon": "PM",
"Saint Vincent and the Grenadines": "VC",
"Samoa": "WS",
"San Marino": "SM",
"Sao Tome and Principe": "ST",
"Saudi Arabia": "SA",
"Senegal": "SN",
"Serbia": "RS",
"Seychelles": "SC",
"Sierra Leone": "SL",
"Singapore": "SG",
"Sint Maarten (Dutch part)": "SX",
"Slovakia": "SK",
"Slovenia": "SI",
"Solomon Islands": "SB",
"Somalia": "SO",
"South Africa": "ZA",
"GS": "South Georgia and the South Sandwich Islands",
"South Sudan": "SS",
"Spain": "ES",
"Sri Lanka": "LK",
"Sudan": "SD",
"Suriname": "SR",
"Svalbard and Jan Mayen": "SJ",
"Swaziland": "SZ",
"Sweden": "SE",
"Switzerland": "CH",
"Syrian Arab Republic": "SY",
"Taiwan, Province of China": "TW",
"Tajikistan": "TJ",
"Tanzania, United Republic of": "TZ",
"Thailand": "TH",
"Timor-Leste": "TL",
"Togo": "TG",
"Tokelau": "TK",
"Tonga": "TO",
"Trinidad and Tobago": "TT",
"Tunisia": "TN",
"Turkey": "TR",
"Turkmenistan": "TM",
"Turks and Caicos Islands": "TC",
"Tuvalu": "TV",
"Uganda": "UG",
"Ukraine": "UA",
"United Arab Emirates": "AE",
"United Kingdom": "GB",
"UM": "United States Minor Outlying Islands",
"Uruguay": "UY",
"Uzbekistan": "UZ",
"Vanuatu": "VU",
"Venezuela, Bolivarian Republic of": "VE",
"Viet Nam": "VN",
"Virgin Islands, British": "VG",
"Virgin Islands, U.S.": "VI",
"Wallis and Futuna": "WF",
"Western Sahara": "EH",
"Yemen": "YE",
"Zambia": "ZM",
"Zimbabwe": "ZW"
}

27
lib/utils/billing/geocode.js

@ -0,0 +1,27 @@
// Packages
const gMaps = require('@google/maps')
const MAPS_API_KEY = 'AIzaSyALfKTQ6AiIoJ8WGDXR3E7IBOwlHoTPfYY'
// eslint-disable-next-line camelcase
module.exports = function ({country, zipCode: postal_code}) {
return new Promise(resolve => {
const maps = gMaps.createClient({key: MAPS_API_KEY})
maps.geocode({
address: `${postal_code} ${country}` // eslint-disable-line camelcase
}, (err, res) => {
if (err || res.json.results.length === 0) {
resolve()
}
const data = res.json.results[0]
const components = {}
data.address_components.forEach(c => {
components[c.types[0]] = c
})
const state = components.administrative_area_level_1
const city = components.locality
resolve({state: state && state.long_name, city: city && city.long_name})
})
})
}

86
lib/utils/input/list.js

@ -0,0 +1,86 @@
const chalk = require('chalk')
const inquirer = require('inquirer')
const stripAnsi = require('strip-ansi')
/* eslint-disable no-multiple-empty-lines, no-var, no-undef, no-eq-null, eqeqeq, semi */
inquirer.prompt.prompts.list.prototype.getQuestion = function () {
var message = chalk.bold('> ' + this.opt.message) + ' '
// Append the default if available, and if question isn't answered
if (this.opt.default != null && this.status !== 'answered') {
message += chalk.dim('(' + this.opt.default + ') ')
}
return message
};
/* eslint-enable */
function getLength(string) {
let biggestLength = 0
string.split('\n').map(str => {
str = stripAnsi(str)
if (str.length > biggestLength) {
biggestLength = str.length
}
return undefined
})
return biggestLength
}
module.exports = async function ({
message = 'the question',
choices = [{ // eslint-disable-line no-unused-vars
name: 'something\ndescription\ndetails\netc',
value: 'something unique',
short: 'generally the first line of `name`'
}],
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
separator = true, // puts a blank separator between each choice
abort = 'end' // wether the `abort` option will be at the `start` or the `end`
}) {
let biggestLength = 0
choices = choices.map(choice => {
if (choice.name) {
const length = getLength(choice.name)
if (length > biggestLength) {
biggestLength = length
}
return choice
}
throw new Error('Invalid choice')
})
if (separator === true) {
choices = choices.reduce((prev, curr) => (
prev.concat(new inquirer.Separator(' '), curr)
), [])
}
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength))
const _abort = {
name: 'Abort',
value: undefined
}
if (abort === 'start') {
const blankSep = choices.shift()
choices.unshift(abortSeparator)
choices.unshift(_abort)
choices.unshift(blankSep)
} else {
choices.push(abortSeparator)
choices.push(_abort)
}
const nonce = Date.now()
const answer = await inquirer.prompt({
name: nonce,
type: 'list',
message,
choices,
pageSize
})
return answer[nonce]
}

207
lib/utils/input/text.js

@ -0,0 +1,207 @@
const ansiEscapes = require('ansi-escapes')
const ansiRegex = require('ansi-regex')
const chalk = require('chalk')
const stripAnsi = require('strip-ansi')
const ESCAPES = {
LEFT: '\x1b[D',
RIGHT: '\x1b[C',
CTRL_C: '\x03',
BACKSPACE: '\x08',
CTRL_H: '\x7f',
CARRIAGE: '\r'
}
module.exports = function ({
label = '',
initialValue = '',
// can be:
// - `false`, which does nothing
// - `cc`, for credit cards
// - `date`, for dates in the mm / yyyy format
mask = false,
placeholder = '',
abortSequences = new Set(['\x03']),
eraseSequences = new Set([ESCAPES.BACKSPACE, ESCAPES.CTRL_H]),
resolveChars = new Set([ESCAPES.CARRIAGE]),
stdin = process.stdin,
stdout = process.stdout,
// char to print before resolving/rejecting the promise
// if `false`, nothing will be printed
trailing = ansiEscapes.eraseLines(1),
// gets called on each keypress;
// `data` contains the current keypress;
// `futureValue` contains the current value + the
// keypress in the correct place
validateKeypress = (data, futureValue) => true, // eslint-disable-line no-unused-vars
// get's called before the promise is resolved
// returning `false` here will prevent the user from submiting the value
validateValue = data => true, // eslint-disable-line no-unused-vars
// receives the value of the input and should return a string
// or false if no autocomplion is available
autoComplete = value => false, // eslint-disable-line no-unused-vars
autoCompleteChars = new Set([
'\t', // tab
'\x1b[C' // right arrow
])
} = {}) {
return new Promise((resolve, reject) => {
const isRaw = process.stdin.isRaw
let value
let caretOffset = 0
let regex
let suggestion = ''
stdout.write(label)
value = initialValue
stdout.write(initialValue)
if (mask) {
if (!value) {
value = chalk.gray(placeholder)
caretOffset = 0 - stripAnsi(value).length
stdout.write(value)
stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset)))
}
regex = placeholder.split('').reduce((prev, curr) => {
if (curr !== ' ' && !prev.includes(curr)) {
if (curr === '/') {
prev.push(' / ')
} else {
prev.push(curr)
}
}
return prev
}, []).join('|')
regex = new RegExp(`(${regex})`, 'g')
}
stdin.setRawMode(true)
stdin.resume()
function restore() {
stdin.setRawMode(isRaw)
stdin.pause()
stdin.removeListener('data', onData)
if (trailing) {
stdout.write(trailing)
}
}
async function onData(buffer) {
const data = buffer.toString()
value = stripAnsi(value)
if (abortSequences.has(data)) {
restore()
return reject(new Error('USER_ABORT'))
}
if (suggestion !== '' && !caretOffset && autoCompleteChars.has(data)) {
value += stripAnsi(suggestion)
suggestion = ''
} else if (data === ESCAPES.LEFT) {
if (value.length > Math.abs(caretOffset)) {
caretOffset--
}
} else if (data === ESCAPES.RIGHT) {
if (caretOffset < 0) {
caretOffset++
}
} else if (eraseSequences.has(data)) {
let char
if ((mask) && value.length > Math.abs(caretOffset)) {
if (value[value.length + caretOffset - 1] === ' ') {
if (value[value.length + caretOffset - 2] === '/') {
caretOffset -= 1
}
char = placeholder[value.length + caretOffset]
value = value.substr(0, value.length + caretOffset - 2) + char +
value.substr(value.length + caretOffset - 1)
caretOffset--
} else {
char = placeholder[value.length + caretOffset - 1]
value = value.substr(0, value.length + caretOffset - 1) + char +
value.substr(value.length + caretOffset)
}
caretOffset--
} else {
value = value.substr(0, value.length + caretOffset - 1) +
value.substr(value.length + caretOffset)
}
suggestion = ''
} else if (resolveChars.has(data)) {
if (validateValue(value)) {
restore()
resolve(value)
} else {
if (mask === 'cc' || mask === 'ccv') {
value = value.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim()
value = value.replace(regex, chalk.gray('$1'))
} else if (mask === 'expDate') {
value = value.replace(regex, chalk.gray('$1'))
}
const l = chalk.red(label.replace('-', '✖'))
stdout.write(ansiEscapes.eraseLines(1) + l + value + ansiEscapes.beep)
if (caretOffset) {
process.stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset)))
}
}
return
} else if (!ansiRegex().test(data)) {
let tmp = value.substr(0, value.length + caretOffset) + data +
value.substr(value.length + caretOffset)
if (mask) {
if (/\d/.test(data) && caretOffset !== 0) {
if (value[value.length + caretOffset + 1] === ' ') {
tmp = value.substr(0, value.length + caretOffset) + data +
value.substr(value.length + caretOffset + 1)
caretOffset += 2
if (value[value.length + caretOffset] === '/') {
caretOffset += 2
}
} else {
tmp = value.substr(0, value.length + caretOffset) + data +
value.substr(value.length + caretOffset + 1)
caretOffset++
}
} else if (/\s/.test(data) && caretOffset < 0) {
caretOffset++
tmp = value
} else {
return stdout.write(ansiEscapes.beep)
}
value = tmp
} else if (validateKeypress(data, value)) {
value = tmp
if (caretOffset === 0) {
const completion = await autoComplete(value)
if (completion) {
suggestion = chalk.gray(completion)
suggestion += ansiEscapes.cursorBackward(completion.length)
} else {
suggestion = ''
}
}
} else {
return stdout.write(ansiEscapes.beep)
}
}
if (mask === 'cc' || mask === 'ccv') {
value = value.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim()
value = value.replace(regex, chalk.gray('$1'))
} else if (mask === 'expDate') {
value = value.replace(regex, chalk.gray('$1'))
}
stdout.write(ansiEscapes.eraseLines(1) + label + value + suggestion)
if (caretOffset) {
process.stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset)))
}
}
stdin.on('data', onData)
})
}

8
lib/utils/output/cmd.js

@ -0,0 +1,8 @@
const chalk = require('chalk')
// the equivalent of <code>, for embedding a cmd
// eg: Please run ${cmd(woot)}
module.exports = cmd => (
`${chalk.gray('`')}${chalk.cyan(cmd)}${chalk.gray('`')}`
)

8
lib/utils/output/code.js

@ -0,0 +1,8 @@
const chalk = require('chalk')
// the equivalent of <code>, for embedding anything
// you may want to take a look at ./cmd.js
module.exports = cmd => (
`${chalk.gray('`')}${chalk.bold(cmd)}${chalk.gray('`')}`
)

7
lib/utils/output/error.js

@ -0,0 +1,7 @@
const chalk = require('chalk')
// prints an error message
module.exports = msg => {
console.error(`${chalk.red('> Error!')} ${msg}`)
}

7
lib/utils/output/info.js

@ -0,0 +1,7 @@
const chalk = require('chalk')
// prints an informational message
module.exports = msg => {
console.log(`${chalk.gray('>')} ${msg}`)
}

8
lib/utils/output/param.js

@ -0,0 +1,8 @@
const chalk = require('chalk')
// returns a user param in a nice formatting
// e.g.: google.com -> "google.com" (in bold)
module.exports = param => (
chalk.bold(`${chalk.gray('"')}${chalk.bold(param)}${chalk.gray('"')}`)
)

54
lib/utils/output/prompt-bool.js

@ -0,0 +1,54 @@
const chalk = require('chalk')
module.exports = (label, {
defaultValue = false,
abortSequences = new Set(['\x03']),
resolveChars = new Set(['\r']),
yesChar = 'y',
noChar = 'n',
stdin = process.stdin,
stdout = process.stdout
} = {}) => {
return new Promise((resolve, reject) => {
const isRaw = process.stdin.isRaw
stdin.setRawMode(true)
stdin.resume()
function restore() {
stdin.setRawMode(isRaw)
stdin.pause()
stdin.removeListener('data', onData)
}
function onData(buffer) {
const data = buffer.toString()
if (abortSequences.has(data)) {
restore()
return reject(new Error('USER_ABORT'))
}
if (resolveChars.has(data[0])) {
restore()
resolve(defaultValue)
} else if (data[0].toLowerCase() === yesChar) {
restore()
resolve(true)
} else if (data[0].toLowerCase() === noChar) {
restore()
resolve(false)
} else {
// ignore extraneous input
}
}
const defaultText = defaultValue === null ?
`[${yesChar}|${noChar}]` :
defaultValue ?
`[${chalk.bold(yesChar.toUpperCase())}|${noChar}]` :
`[${yesChar}|${chalk.bold(noChar.toUpperCase())}]`
stdout.write(`${chalk.gray('-')} ${label} ${chalk.gray(defaultText)} `)
stdin.on('data', onData)
})
}

12
lib/utils/output/stamp.js

@ -0,0 +1,12 @@
const ms = require('ms')
const chalk = require('chalk')
// returns a time delta with the right color
// example: `[103ms]`
module.exports = () => {
const start = new Date()
return () => (
chalk.gray(`[${ms(new Date() - start)}]`)
)
}

6
lib/utils/output/success.js

@ -0,0 +1,6 @@
const chalk = require('chalk')
// prints a success message
module.exports = msg => {
console.log(`${chalk.cyan('> Success!')} ${msg}`)
}

7
lib/utils/output/uid.js

@ -0,0 +1,7 @@
const chalk = require('chalk')
// used for including uids in the output
// example: `(dom_ji13dj2fih4fi2hf)`
module.exports = id => (
chalk.gray(`(${id})`)
)

15
lib/utils/output/wait.js

@ -0,0 +1,15 @@
const ora = require('ora')
const chalk = require('chalk')
const {eraseLine} = require('ansi-escapes')
// prints a spinner followed by the given text
module.exports = msg => {
const spinner = ora(chalk.gray(msg))
spinner.color = 'gray'
spinner.start()
return () => {
spinner.stop()
process.stdout.write(eraseLine)
}
}

18
package.json

@ -11,17 +11,19 @@
"scripts": {
"precommit": "npm run lint",
"lint": "xo",
"test": "npm run lint && ava",
"pack": "pkg . --out-dir packed -t node7-alpine-x64,node7-linux-x64,node7-macos-x64,node7-win-x64"
"test": "npm run build && npm run lint && ava",
"prepublish": "npm run build",
"build": "./build.sh",
"pack": "npm run build && pkg . --out-dir packed -t node7-alpine-x64,node7-linux-x64,node7-macos-x64,node7-win-x64"
},
"pkg": {
"scripts": [
"./bin/*",
"./lib/**/*"
"./build/bin/*",
"./build/lib/**/*"
]
},
"bin": {
"now": "./bin/now.js"
"now": "./build/bin/now.js"
},
"ava": {
"failFast": true,
@ -55,7 +57,9 @@
"node": ">=6.9.0"
},
"dependencies": {
"@google/maps": "0.3.1",
"ansi-escapes": "1.4.0",
"ansi-regex": "^2.1.1",
"arr-flatten": "1.0.1",
"array-unique": "0.3.2",
"async-retry": "0.2.1",
@ -63,6 +67,7 @@
"bytes": "2.4.0",
"chalk": "1.1.3",
"copy-paste": "1.3.0",
"credit-card": "^3.0.1",
"cross-spawn": "5.0.1",
"docker-file-parser": "0.1.0",
"dotenv": "4.0.0",
@ -73,6 +78,7 @@
"glob": "7.1.1",
"ignore": "3.2.2",
"ini": "1.3.4",
"inquirer": "^2.0.0",
"is-url": "1.2.2",
"minimist": "1.2.0",
"ms": "0.7.2",
@ -84,6 +90,8 @@
"resumer": "0.0.0",
"socket.io-client": "1.7.2",
"split-array": "1.0.1",
"strip-ansi": "3.0.1",
"stripe": "4.15.0",
"text-table": "0.2.0",
"tmp-promise": "1.0.3",
"update-notifier": "2.0.0"

2
test/args-parsing.js

@ -66,7 +66,7 @@ test('"now alias --help" is the same as "now --help alias"', async t => {
*/
function now(...args) {
return new Promise((resolve, reject) => {
const command = path.resolve(__dirname, '../bin/now.js')
const command = path.resolve(__dirname, '../build/bin/now.js')
const now = spawn(command, args)
let stdout = ''

6
test/index.js

@ -6,9 +6,9 @@ const test = require('ava')
const {asc: alpha} = require('alpha-sort')
// Ours
const hash = require('../lib/hash')
const readMetadata = require('../lib/read-metadata')
const {npm: getNpmFiles_, docker: getDockerFiles} = require('../lib/get-files')
const hash = require('../build/lib/hash')
const readMetadata = require('../build/lib/read-metadata')
const {npm: getNpmFiles_, docker: getDockerFiles} = require('../build/lib/get-files')
const prefix = join(__dirname, '_fixtures') + '/'
const base = path => path.replace(prefix, '')

2
test/to-host.js

@ -1,5 +1,5 @@
const test = require('ava')
const toHost = require('../lib/to-host')
const toHost = require('../build/lib/to-host')
test('simple', async t => {
t.is(toHost('zeit.co'), 'zeit.co')

Loading…
Cancel
Save