Browse Source

Removed everything

master
Leo Lamprecht 8 years ago
parent
commit
6f4f252875
  1. 9
      .gitignore
  2. 1
      .npmrc
  3. 119
      bin/domains/buy.js
  4. 529
      bin/now-alias.js
  5. 255
      bin/now-billing-add.js
  6. 381
      bin/now-billing.js
  7. 384
      bin/now-certs.js
  8. 877
      bin/now-deploy.js
  9. 343
      bin/now-dns.js
  10. 423
      bin/now-domains.js
  11. 247
      bin/now-list.js
  12. 130
      bin/now-logout.js
  13. 290
      bin/now-logs.js
  14. 218
      bin/now-remove.js
  15. 384
      bin/now-scale.js
  16. 297
      bin/now-secrets.js
  17. 171
      bin/now-teams.js
  18. 257
      bin/now-upgrade.js
  19. 78
      bin/now-whoami.js
  20. 142
      bin/now.js
  21. 135
      bin/teams/add.js
  22. 160
      bin/teams/invite.js
  23. 62
      bin/teams/list.js
  24. 124
      bin/teams/switch.js
  25. 29
      circle.yml
  26. 16
      download/install.js
  27. 10
      download/src/chmod.js
  28. 193
      download/src/index.js
  29. 43
      download/src/log.js
  30. 34
      download/webpack.js
  31. 83
      lib/agent.js
  32. 881
      lib/alias.js
  33. 138
      lib/build-logger.js
  34. 101
      lib/certs.js
  35. 112
      lib/cfg.js
  36. 73
      lib/credit-cards.js
  37. 15
      lib/dns.js
  38. 148
      lib/domain-records.js
  39. 264
      lib/domains.js
  40. 85
      lib/error.js
  41. 30
      lib/errors.js
  42. 385
      lib/get-files.js
  43. 221
      lib/git.js
  44. 44
      lib/hash.js
  45. 17
      lib/ignored.js
  46. 5
      lib/indent.js
  47. 1029
      lib/index.js
  48. 32
      lib/is-zeit-world.js
  49. 141
      lib/login.js
  50. 14
      lib/logs.js
  51. 13
      lib/pkg.js
  52. 57
      lib/plans.js
  53. 134
      lib/re-alias.js
  54. 195
      lib/read-metadata.js
  55. 78
      lib/scale-info.js
  56. 40
      lib/scale.js
  57. 119
      lib/secrets.js
  58. 30
      lib/sort-deployments.js
  59. 5
      lib/strlen.js
  60. 139
      lib/teams.js
  61. 22
      lib/test.js
  62. 20
      lib/to-host.js
  63. 7
      lib/ua.js
  64. 69
      lib/user.js
  65. 8
      lib/utils/billing/card-brands.json
  66. 251
      lib/utils/billing/country-list.json
  67. 33
      lib/utils/billing/geocode.js
  68. 49
      lib/utils/check-path.js
  69. 29
      lib/utils/domains/treat-buy-error.js
  70. 8
      lib/utils/exit.js
  71. 7
      lib/utils/fatal-error.js
  72. 79
      lib/utils/input/list.js
  73. 20
      lib/utils/input/patch-inquirer.js
  74. 58
      lib/utils/input/prompt-bool.js
  75. 39
      lib/utils/input/prompt-options.js
  76. 3
      lib/utils/input/regexes.js
  77. 262
      lib/utils/input/text.js
  78. 3
      lib/utils/output/chars.js
  79. 6
      lib/utils/output/cmd.js
  80. 6
      lib/utils/output/code.js
  81. 3
      lib/utils/output/erase-lines.js
  82. 9
      lib/utils/output/error.js
  83. 6
      lib/utils/output/info.js
  84. 1
      lib/utils/output/logo.js
  85. 6
      lib/utils/output/note.js
  86. 7
      lib/utils/output/param.js
  87. 4
      lib/utils/output/right-pad.js
  88. 10
      lib/utils/output/stamp.js
  89. 6
      lib/utils/output/success.js
  90. 27
      lib/utils/output/table.js
  91. 5
      lib/utils/output/uid.js
  92. 15
      lib/utils/output/wait.js
  93. 18
      lib/utils/to-human-path.js
  94. 28
      lib/utils/url.js
  95. 21
      license.md
  96. 11
      link/link.js
  97. 4
      link/package-lock.json
  98. 9
      link/package.json
  99. 9560
      package-lock.json
  100. 124
      package.json

9
.gitignore

@ -1,9 +0,0 @@
# build output
packed
download/dist
# dependencies
node_modules
# logs
npm-debug.log

1
.npmrc

@ -1 +0,0 @@
save-exact = true

119
bin/domains/buy.js

@ -1,119 +0,0 @@
const { italic, bold } = require('chalk')
const error = require('../../lib/utils/output/error')
const wait = require('../../lib/utils/output/wait')
const cmd = require('../../lib/utils/output/cmd')
const param = require('../../lib/utils/output/param')
const info = require('../../lib/utils/output/info')
const success = require('../../lib/utils/output/success')
const stamp = require('../../lib/utils/output/stamp')
const promptBool = require('../../lib/utils/input/prompt-bool')
const eraseLines = require('../../lib/utils/output/erase-lines')
const treatBuyError = require('../../lib/utils/domains/treat-buy-error')
const NowCreditCards = require('../../lib/credit-cards')
module.exports = async function({ domains, args, currentTeam, user, coupon }) {
const name = args[0]
let elapsed
if (!name) {
return error(`Missing domain name. Run ${cmd('now domains help')}`)
}
const nameParam = param(name)
let stopSpinner
let price
let period
let validCoupon
try {
if (coupon) {
stopSpinner = wait(`Validating coupon ${param(coupon)}`)
const creditCards = new NowCreditCards({
apiUrl: domains._agent._url,
token: domains._token,
debug: domains._debug,
currentTeam
})
const [couponInfo, { cards }] = await Promise.all([
domains.coupon(coupon),
creditCards.ls()
])
stopSpinner()
if (!couponInfo.isValid) {
return error(`The coupon ${param(coupon)} is invalid`)
}
if (!couponInfo.canBeUsed) {
return error(`The coupon ${param(coupon)} has already been used`)
}
validCoupon = true
if (cards.length === 0) {
info(
'You have no credit cards on file. Please add one in order to claim your free domain'
)
info(`Your card will ${bold('not')} be charged`)
await require('../now-billing-add')({
creditCards,
currentTeam,
user,
clear: true
})
}
}
elapsed = stamp()
stopSpinner = wait(`Checking availability for ${nameParam}`)
const json = await domains.price(name)
price = validCoupon ? 0 : json.price
period = json.period
} catch (err) {
stopSpinner()
return error(err.message)
}
const available = await domains.status(name)
stopSpinner()
if (!available) {
return error(
`The domain ${nameParam} is ${italic('unavailable')}! ${elapsed()}`
)
}
const periodMsg = `${period}yr${period > 1 ? 's' : ''}`
info(
`The domain ${nameParam} is ${italic('available')} to buy under ${bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}! ${elapsed()}`
)
const confirmation = await promptBool(
`Buy now for ${bold(`$${price}`)} (${periodMsg})?`
)
eraseLines(1)
if (!confirmation) {
return info('Aborted')
}
stopSpinner = wait('Purchasing')
elapsed = stamp()
try {
await domains.buy({ name, coupon })
} catch (err) {
stopSpinner()
return treatBuyError(err)
}
stopSpinner()
success(`Domain ${nameParam} purchased ${elapsed()}`)
info(
`You may now use your domain as an alias to your deployments. Run ${cmd(
'now alias --help'
)}`
)
}

529
bin/now-alias.js

@ -1,529 +0,0 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk')
const minimist = require('minimist')
const table = require('text-table')
const ms = require('ms')
const printf = require('printf')
require('epipebomb')()
const supportsColor = require('supports-color')
// Ours
const strlen = require('../lib/strlen')
const NowAlias = require('../lib/alias')
const NowDomains = require('../lib/domains')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const { handleError, error } = require('../lib/error')
const toHost = require('../lib/to-host')
const { reAlias } = require('../lib/re-alias')
const exit = require('../lib/utils/exit')
const info = require('../lib/utils/output/info')
const logo = require('../lib/utils/output/logo')
const promptBool = require('../lib/utils/input/prompt-bool')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token', 'rules'],
boolean: ['help', 'debug'],
alias: {
help: 'h',
config: 'c',
rules: 'r',
debug: 'd',
token: 't'
}
})
const subcommand = argv._[0]
const grayWidth = 10
const underlineWidth = 11
// Options
const help = () => {
console.log(`
${chalk.bold(`${logo} now alias`)} <ls | set | rm> <deployment> <alias>
${chalk.dim('Options:')}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline(
'FILE'
)} Config file
-r ${chalk.bold.underline('RULES_FILE')}, --rules=${chalk.bold.underline(
'RULES_FILE'
)} Rules 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 aliases:
${chalk.cyan('$ now alias ls')}
${chalk.gray('–')} Adds a new alias to ${chalk.underline('my-api.now.sh')}:
${chalk.cyan(
`$ now alias set ${chalk.underline(
'api-ownv3nc9f8.now.sh'
)} ${chalk.underline('my-api.now.sh')}`
)}
The ${chalk.dim('`.now.sh`')} suffix can be ommited:
${chalk.cyan('$ now alias set api-ownv3nc9f8 my-api')}
The deployment id can be used as the source:
${chalk.cyan('$ now alias set deploymentId my-alias')}
Custom domains work as alias targets:
${chalk.cyan(
`$ now alias set ${chalk.underline(
'api-ownv3nc9f8.now.sh'
)} ${chalk.underline('my-api.com')}`
)}
${chalk.dim('–')} The subcommand ${chalk.dim(
'`set`'
)} is the default and can be skipped.
${chalk.dim('–')} ${chalk.dim(
'`http(s)://`'
)} in the URLs is unneeded / ignored.
${chalk.gray('–')} Add and modify path based aliases for ${chalk.underline(
'zeit.ninja'
)}:
${chalk.cyan(
`$ now alias ${chalk.underline('zeit.ninja')} -r ${chalk.underline(
'rules.json'
)}`
)}
Export effective routing rules:
${chalk.cyan(
`$ now alias ls aliasId --json > ${chalk.underline('rules.json')}`
)}
${chalk.cyan(`$ now alias ls zeit.ninja`)}
${chalk.gray('–')} Removing an alias:
${chalk.cyan('$ now alias rm aliasId')}
To get the list of alias ids, use ${chalk.dim('`now alias ls`')}.
${chalk.dim('Alias:')} ln
`)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
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)
}
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 } }) {
const alias = new NowAlias({ apiUrl, token, debug, currentTeam })
const domains = new NowDomains({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
switch (subcommand) {
case 'ls':
case 'list': {
if (args.length === 1) {
const list = await alias.listAliases()
const item = list.find(
e => e.uid === argv._[1] || e.alias === argv._[1]
)
if (!item || !item.rules) {
error(`Could not match path alias for: ${argv._[1]}`)
return exit(1)
}
if (argv.json) {
console.log(JSON.stringify({ rules: item.rules }, null, 2))
} else {
const header = [
['', 'pathname', 'method', 'dest'].map(s => chalk.dim(s))
]
const text =
list.length === 0
? null
: table(
header.concat(
item.rules.map(rule => {
return [
'',
rule.pathname ? rule.pathname : '',
rule.method ? rule.method : '*',
rule.dest
]
})
),
{
align: ['l', 'l', 'l', 'l'],
hsep: ' '.repeat(2),
stringLength: strlen
}
)
console.log(text)
}
break
} else if (args.length !== 0) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan('`now alias ls`')}`
)
return exit(1)
}
const start_ = new Date()
const aliases = await alias.ls()
aliases.sort((a, b) => new Date(b.created) - new Date(a.created))
const current = new Date()
const sourceUrlLength =
aliases.reduce((acc, i) => {
return Math.max(acc, (i.deployment && i.deployment.url.length) || 0)
}, 0) + 9
const aliasLength =
aliases.reduce((acc, i) => {
return Math.max(acc, (i.alias && i.alias.length) || 0)
}, 0) + 8
const elapsed_ = ms(new Date() - start_)
console.log(
`> ${aliases.length} alias${aliases.length === 1
? ''
: 'es'} found ${chalk.gray(`[${elapsed_}]`)} under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
console.log()
if (supportsColor) {
const urlSpecHeader = `%-${sourceUrlLength + 1}s`
const aliasSpecHeader = `%-${aliasLength + 1}s`
console.log(
printf(
` ${chalk.gray(urlSpecHeader + ' ' + aliasSpecHeader + ' %5s')}`,
'source',
'url',
'age'
)
)
} else {
const urlSpecHeader = `%-${sourceUrlLength}s`
const aliasSpecHeader = `%-${aliasLength}s`
console.log(
printf(
` ${urlSpecHeader} ${aliasSpecHeader} %5s`,
'source',
'url',
'age'
)
)
}
let text = ''
aliases.forEach(_alias => {
let urlSpec = sourceUrlLength
let aliasSpec = aliasLength
let ageSpec = 5
const _url = chalk.underline(_alias.alias)
let _sourceUrl
if (supportsColor) {
aliasSpec += underlineWidth
ageSpec += grayWidth
}
if (_alias.deployment) {
_sourceUrl = chalk.underline(_alias.deployment.url)
if (supportsColor) {
urlSpec += grayWidth
}
} else if (_alias.rules) {
_sourceUrl = chalk.gray(
`[${_alias.rules.length} custom rule${_alias.rules.length > 1
? 's'
: ''}]`
)
if (supportsColor) {
urlSpec += underlineWidth
}
} else {
_sourceUrl = chalk.gray('<null>')
}
const time = chalk.gray(ms(current - new Date(_alias.created)))
text += printf(
` %-${urlSpec}s %-${aliasSpec}s %${ageSpec}s\n`,
_sourceUrl,
_url,
time
)
})
console.log(text)
break
}
case 'rm':
case 'remove': {
const _target = String(args[0])
if (!_target) {
const err = new Error('No alias id specified')
err.userError = true
throw err
}
if (args.length !== 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now alias rm <id>`'
)}`
)
return exit(1)
}
const _aliases = await alias.ls()
const _alias = findAlias(_target, _aliases)
if (!_alias) {
const err = new Error(
`Alias not found by "${_target}" under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}. Run ${chalk.dim('`now alias ls`')} to see your aliases.`
)
err.userError = true
throw err
}
try {
const confirmation = await confirmDeploymentRemoval(alias, _alias)
if (!confirmation) {
info('Aborted')
return process.exit(0)
}
const start = new Date()
await alias.rm(_alias)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Alias ${chalk.bold(
_alias.uid
)} removed [${elapsed}]`
)
} catch (err) {
error(err)
exit(1)
}
break
}
case 'add':
case 'set': {
if (argv.rules) {
await updatePathAlias(alias, argv._[0], argv.rules, domains)
break
}
if (args.length !== 2) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now alias set <id> <domain>`'
)}`
)
return exit(1)
}
await alias.set(
String(args[0]),
String(args[1]),
domains,
currentTeam,
user
)
break
}
default: {
if (argv._.length === 0) {
await reAlias(
token,
null,
null,
help,
exit,
apiUrl,
debug,
alias,
currentTeam,
user
)
break
}
if (argv.rules) {
await updatePathAlias(alias, argv._[0], argv.rules, domains)
break
}
if (argv._.length === 1) {
await reAlias(
token,
null,
String(argv._[0]),
help,
exit,
apiUrl,
debug,
alias,
currentTeam,
user
)
break
} else if (argv._.length === 2) {
await alias.set(
String(argv._[0]),
String(argv._[1]),
domains,
currentTeam,
user
)
} else if (argv._.length >= 3) {
error('Invalid number of arguments')
help()
exit(1)
} else {
error('Please specify a valid subcommand: ls | set | rm')
help()
exit(1)
}
}
}
domains.close()
alias.close()
}
async function confirmDeploymentRemoval(alias, _alias) {
const time = chalk.gray(ms(new Date() - new Date(_alias.created)) + ' ago')
const _sourceUrl = _alias.deployment
? chalk.underline(_alias.deployment.url)
: null
const tbl = table(
[
[
_alias.uid,
...(_sourceUrl ? [_sourceUrl] : []),
chalk.underline(_alias.alias),
time
]
],
{ hsep: ' '.repeat(6) }
)
const msg =
'> The following alias will be removed permanently\n' +
` ${tbl} \nAre you sure?`
return promptBool(msg, {
trailing: '\n'
})
}
function findAlias(alias, list) {
let key
let val
if (/\./.test(alias)) {
val = toHost(alias)
key = 'alias'
} else {
val = alias
key = 'uid'
}
const _alias = list.find(d => {
if (d[key] === val) {
if (debug) {
console.log(`> [debug] matched alias ${d.uid} by ${key} ${val}`)
}
return true
}
// Match prefix
if (`${val}.now.sh` === d.alias) {
if (debug) {
console.log(`> [debug] matched alias ${d.uid} by url ${d.host}`)
}
return true
}
return false
})
return _alias
}
async function updatePathAlias(alias, aliasName, rules, domains) {
const start = new Date()
const res = await alias.updatePathBasedroutes(
String(aliasName),
rules,
domains
)
const elapsed = ms(new Date() - start)
if (res.error) {
const err = new Error(res.error.message)
err.userError = true
throw err
} else {
console.log(
`${chalk.cyan(
'> Success!'
)} ${res.ruleCount} rules configured for ${chalk.underline(
res.alias
)} [${elapsed}]`
)
}
}

255
bin/now-billing-add.js

@ -1,255 +0,0 @@
#!/usr/bin/env node
// Packages
const ansiEscapes = require('ansi-escapes')
const chalk = require('chalk')
const ccValidator = require('credit-card')
// Ours
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')
const { tick } = require('../lib/utils/output/chars')
const rightPad = require('../lib/utils/output/right-pad')
function expDateMiddleware(data) {
return data
}
module.exports = async function({
creditCards,
currentTeam,
user,
clear = false
}) {
const state = {
error: undefined,
cardGroupLabel: `> ${chalk.bold(
`Enter your card details for ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)}`,
name: {
label: rightPad('Full Name', 12),
placeholder: 'John Appleseed',
validateValue: data => data.trim().length > 0
},
cardNumber: {
label: rightPad('Number', 12),
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', 12),
mask: 'ccv',
placeholder: '###',
validateValue: data => {
const brand = state.cardNumber.brand.toLowerCase()
return ccValidator.doesCvvMatchType(data, brand)
}
},
expDate: {
label: rightPad('Exp. Date', 12),
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', 12),
async autoComplete(value) {
for (const country in countries) {
if (!Object.hasOwnProperty.call(countries, country)) {
continue
}
if (country.startsWith(value)) {
return country.substr(value.length)
}
const lowercaseCountry = country.toLowerCase()
const lowercaseValue = value.toLowerCase()
if (lowercaseCountry.startsWith(lowercaseValue)) {
return lowercaseCountry.substr(value.length)
}
}
return false
},
validateValue: value => {
for (const country in countries) {
if (!Object.hasOwnProperty.call(countries, country)) {
continue
}
if (country.toLowerCase() === value.toLowerCase()) {
return true
}
}
return false
}
},
zipCode: {
label: rightPad('ZIP', 12),
validadeKeypress: data => data.trim().length > 0,
validateValue: data => data.trim().length > 0
},
state: {
label: rightPad('State', 12),
validateValue: data => data.trim().length > 0
},
city: {
label: rightPad('City', 12),
validateValue: data => data.trim().length > 0
},
address1: {
label: rightPad('Address', 12),
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 {
/* eslint-disable no-await-in-loop */
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}]`)
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[3]
process.stdout.write(
`${chalk.cyan(tick)} ${piece.label}${masked} ${brand}\n`
)
} else if (key === 'ccv') {
process.stdout.write(
`${chalk.cyan(tick)} ${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(tick)} ${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(tick)} ${piece.label}${result}\n`
)
} else {
process.stdout.write(
`${chalk.cyan(tick)} ${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()
if (clear) {
const linesToClear = state.error ? 15 : 14
process.stdout.write(ansiEscapes.eraseLines(linesToClear))
}
success(
`${state.cardNumber
.brand} ending in ${res.last4} was added to ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
} catch (err) {
stopSpinner()
const linesToClear = state.error ? 15 : 14
process.stdout.write(ansiEscapes.eraseLines(linesToClear))
state.error = `${chalk.red(
'> Error!'
)} ${err.message} Please make sure the info is correct`
await render()
}
}
try {
await render()
} catch (err) {
console.erorr(err)
}
}

381
bin/now-billing.js

@ -1,381 +0,0 @@
#!/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 { handleError, 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/input/prompt-bool')
const info = require('../lib/utils/output/info')
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 subcommand = argv._[0]
const help = () => {
console.log(`
${chalk.bold(`${logo} 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 omitted, you can choose interactively
${chalk.gray('–')} Selects your default credit card:
${chalk.cyan(`$ now billing set-default <id>`)}
${chalk.gray('–')} If the id is omitted, 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 {
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}`)
}
exit(1)
}
})
.catch(err => {
handleError(err)
process.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, config: { currentTeam, user } }) {
const start = new Date()
const creditCards = new NowCreditCards({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
switch (subcommand) {
case 'ls':
case 'list': {
let cards
try {
cards = await creditCards.ls()
} catch (err) {
error(err.message)
return
}
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}, `
}
// 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 under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)} ${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()
let cards
try {
cards = await creditCards.ls()
} catch (err) {
error(err.message)
return
}
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 for ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)} ${chalk.gray(`[${elapsed}]`)}`
const choices = buildInquirerChoices(cards)
cardId = await listInput({
message,
choices,
separator: true,
abort: 'end'
})
}
// 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, {
trailing: '\n'
})
if (!confirmation) {
info('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()
let cards
try {
cards = await creditCards.ls()
} catch (err) {
error(err.message)
return
}
if (cards.cards.length === 0) {
error(
`You have no credit cards to choose from to delete under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
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'
)} under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)} ${chalk.gray(`[${elapsed}]`)}`
const choices = buildInquirerChoices(cards)
cardId = await listInput({
message,
choices,
separator: true,
abort: 'start'
})
}
// Shoud 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)
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 for ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
}
}
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,
currentTeam,
user
})
break
}
default:
error('Please specify a valid subcommand: ls | add | rm | set-default')
help()
exit(1)
}
creditCards.close()
}

384
bin/now-certs.js

@ -1,384 +0,0 @@
#!/usr/bin/env node
// Native
const path = require('path')
// Packages
const chalk = require('chalk')
const table = require('text-table')
const minimist = require('minimist')
const fs = require('fs-extra')
const ms = require('ms')
const printf = require('printf')
require('epipebomb')()
const supportsColor = require('supports-color')
// Ours
const cfg = require('../lib/cfg')
const { handleError, error } = require('../lib/error')
const NowCerts = require('../lib/certs')
const login = require('../lib/login')
const exit = require('../lib/utils/exit')
const logo = require('../lib/utils/output/logo')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token', 'crt', 'key', 'ca'],
boolean: ['help', 'debug'],
alias: { help: 'h', config: 'c', debug: 'd', token: 't' }
})
const subcommand = argv._[0]
// Options
const help = () => {
console.log(`
${chalk.bold(`${logo} now certs`)} <ls | create | renew | replace | rm> <cn>
${chalk.dim('Note:')}
This command is intended for advanced use only, normally ${chalk.bold(
'now'
)} manages your certificates automatically.
${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
--crt ${chalk.bold.underline('FILE')} Certificate file
--key ${chalk.bold.underline('FILE')} Certificate key file
--ca ${chalk.bold.underline('FILE')} CA certificate chain file
${chalk.dim('Examples:')}
${chalk.gray('–')} Listing all your certificates:
${chalk.cyan('$ now certs ls')}
${chalk.gray('–')} Creating a new certificate:
${chalk.cyan('$ now certs create domain.com')}
${chalk.gray('–')} Renewing an existing certificate issued with ${chalk.bold(
'now'
)}:
${chalk.cyan('$ now certs renew domain.com')}
${chalk.gray(
'–'
)} Replacing an existing certificate with a user-supplied certificate:
${chalk.cyan(
'$ now certs replace --crt domain.crt --key domain.key --ca ca_chain.crt domain.com'
)}
`)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
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) {
handleError(err)
exit(1)
}
})
.catch(err => {
handleError(err)
process.exit(1)
})
}
function formatExpirationDate(date) {
const diff = date - Date.now()
return diff < 0
? chalk.gray(ms(-diff) + ' ago')
: chalk.gray('in ' + ms(diff))
}
async function run({ token, config: { currentTeam, user } }) {
const certs = new NowCerts({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
const start = Date.now()
if (subcommand === 'ls' || subcommand === 'list') {
if (args.length !== 0) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan('`now certs ls`')}`
)
return exit(1)
}
const list = await certs.ls()
const elapsed = ms(new Date() - start)
console.log(
`> ${list.length} certificate${list.length === 1
? ''
: 's'} found ${chalk.gray(`[${elapsed}]`)} under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
if (list.length > 0) {
const cur = Date.now()
list.sort((a, b) => {
return a.cn.localeCompare(b.cn)
})
const maxCnLength =
list.reduce((acc, i) => {
return Math.max(acc, (i.cn && i.cn.length) || 0)
}, 0) + 1
console.log(
chalk.dim(
printf(
` %-${maxCnLength}s %-8s %-10s %-10s`,
'cn',
'created',
'expiration',
'auto-renew'
)
)
)
list.forEach(cert => {
const cn = chalk.bold(cert.cn)
const time = chalk.gray(ms(cur - new Date(cert.created)) + ' ago')
const expiration = formatExpirationDate(new Date(cert.expiration))
const autoRenew = cert.autoRenew ? 'yes' : 'no'
let spec
if (supportsColor) {
spec = ` %-${maxCnLength + 9}s %-18s %-20s %-20s\n`
} else {
spec = ` %-${maxCnLength}s %-8s %-10s %-10s\n`
}
process.stdout.write(printf(spec, cn, time, expiration, autoRenew))
})
}
} else if (subcommand === 'create') {
if (args.length !== 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now certs create <cn>`'
)}`
)
return exit(1)
}
const cn = args[0]
let cert
if (argv.crt || argv.key || argv.ca) {
// Issue a custom certificate
if (!argv.crt || !argv.key) {
error(
`Missing required arguments for a custom certificate entry. Usage: ${chalk.cyan(
'`now certs create --crt DOMAIN.CRT --key DOMAIN.KEY [--ca CA.CRT] <id | cn>`'
)}`
)
return exit(1)
}
const crt = readX509File(argv.crt)
const key = readX509File(argv.key)
const ca = argv.ca ? readX509File(argv.ca) : ''
cert = await certs.put(cn, crt, key, ca)
} else {
// Issue a standard certificate
cert = await certs.create(cn)
}
if (!cert) {
// Cert is undefined and "Cert is already issued" has been printed to stdout
return exit(1)
}
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Certificate entry ${chalk.bold(
cn
)} ${chalk.gray(`(${cert.uid})`)} created ${chalk.gray(`[${elapsed}]`)}`
)
} else if (subcommand === 'renew') {
if (args.length !== 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now certs renew <id | cn>`'
)}`
)
return exit(1)
}
const cert = await getCertIdCn(certs, args[0], currentTeam, user)
if (!cert) {
return exit(1)
}
const yes = await readConfirmation(
cert,
'The following certificate will be renewed\n'
)
if (!yes) {
error('User abort')
return exit(0)
}
await certs.renew(cert.cn)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Certificate ${chalk.bold(
cert.cn
)} ${chalk.gray(`(${cert.uid})`)} renewed ${chalk.gray(`[${elapsed}]`)}`
)
} else if (subcommand === 'replace') {
if (!argv.crt || !argv.key) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now certs replace --crt DOMAIN.CRT --key DOMAIN.KEY [--ca CA.CRT] <id | cn>`'
)}`
)
return exit(1)
}
const crt = readX509File(argv.crt)
const key = readX509File(argv.key)
const ca = argv.ca ? readX509File(argv.ca) : ''
const cert = await getCertIdCn(certs, args[0], currentTeam, user)
if (!cert) {
return exit(1)
}
const yes = await readConfirmation(
cert,
'The following certificate will be replaced permanently\n'
)
if (!yes) {
error('User abort')
return exit(0)
}
await certs.put(cert.cn, crt, key, ca)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Certificate ${chalk.bold(
cert.cn
)} ${chalk.gray(`(${cert.uid})`)} replaced ${chalk.gray(`[${elapsed}]`)}`
)
} else if (subcommand === 'rm' || subcommand === 'remove') {
if (args.length !== 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now certs rm <id | cn>`'
)}`
)
return exit(1)
}
const cert = await getCertIdCn(certs, args[0], currentTeam, user)
if (!cert) {
return exit(1)
}
const yes = await readConfirmation(
cert,
'The following certificate will be removed permanently\n'
)
if (!yes) {
error('User abort')
return exit(0)
}
await certs.delete(cert.cn)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Certificate ${chalk.bold(
cert.cn
)} ${chalk.gray(`(${cert.uid})`)} removed ${chalk.gray(`[${elapsed}]`)}`
)
} else {
error(
'Please specify a valid subcommand: ls | create | renew | replace | rm'
)
help()
exit(1)
}
return certs.close()
}
process.on('uncaughtException', err => {
handleError(err)
exit(1)
})
function readConfirmation(cert, msg) {
return new Promise(resolve => {
const time = chalk.gray(ms(new Date() - new Date(cert.created)) + ' ago')
const tbl = table([[cert.uid, chalk.bold(cert.cn), time]], {
align: ['l', 'r', 'l'],
hsep: ' '.repeat(6)
})
process.stdout.write(`> ${msg}`)
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
)
process.stdin
.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
})
.resume()
})
}
function readX509File(file) {
return fs.readFileSync(path.resolve(file), 'utf8')
}
async function getCertIdCn(certs, idOrCn, currentTeam, user) {
const list = await certs.ls()
const thecert = list.filter(cert => {
return cert.uid === idOrCn || cert.cn === idOrCn
})[0]
if (!thecert) {
error(
`No certificate found by id or cn "${idOrCn}" under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
return null
}
return thecert
}

877
bin/now-deploy.js

@ -1,877 +0,0 @@
#!/usr/bin/env node
// Native
const { resolve, basename } = require('path')
// Packages
const Progress = require('progress')
const fs = require('fs-extra')
const bytes = require('bytes')
const chalk = require('chalk')
const minimist = require('minimist')
const ms = require('ms')
const flatten = require('arr-flatten')
const dotenv = require('dotenv')
const { eraseLines } = require('ansi-escapes')
const { write: copy } = require('clipboardy')
const inquirer = require('inquirer')
// Ours
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const { version } = require('../lib/pkg')
const Logger = require('../lib/build-logger')
const Now = require('../lib')
const toHumanPath = require('../lib/utils/to-human-path')
const { handleError, error } = require('../lib/error')
const { fromGit, isRepoPath, gitPathParts } = require('../lib/git')
const readMetaData = require('../lib/read-metadata')
const checkPath = require('../lib/utils/check-path')
const { reAlias, assignAlias } = require('../lib/re-alias')
const exit = require('../lib/utils/exit')
const logo = require('../lib/utils/output/logo')
const cmd = require('../lib/utils/output/cmd')
const info = require('../lib/utils/output/info')
const wait = require('../lib/utils/output/wait')
const NowPlans = require('../lib/plans')
const promptBool = require('../lib/utils/input/prompt-bool')
const promptOptions = require('../lib/utils/input/prompt-options')
const note = require('../lib/utils/output/note')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token', 'name', 'alias', 'session-affinity'],
boolean: [
'help',
'version',
'debug',
'force',
'links',
'login',
'no-clipboard',
'forward-npm',
'docker',
'npm',
'static'
],
alias: {
env: 'e',
dotenv: 'E',
help: 'h',
config: 'c',
debug: 'd',
version: 'v',
force: 'f',
token: 't',
forceSync: 'F',
links: 'l',
login: 'L',
public: 'p',
'no-clipboard': 'C',
'forward-npm': 'N',
'session-affinity': 'S',
name: 'n',
alias: 'a'
}
})
const help = () => {
console.log(`
${chalk.bold(`${logo} now`)} [options] <command | path>
${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
logs [url] Displays the logs for a deployment
scale [args] Scales the instance count of a deployment
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
teams [team] Manages your teams
switch Switches between teams and your account
login Login into your account or creates a new one
logout Logout from your account
${chalk.dim('Options:')}
-h, --help Output usage information
-v, --version Output the version number
-n, --name Set the name of the deployment
-c ${chalk.underline('FILE')}, --config=${chalk.underline(
'FILE'
)} Config file
-d, --debug Debug mode [off]
-f, --force Force a new deployment even if nothing has changed
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline(
'TOKEN'
)} Login token
-L, --login Configure login
-l, --links Copy symlinks without resolving their target
-p, --public Deployment is public (${chalk.dim(
'`/_src`'
)} is exposed) [on for oss, off for premium]
-e, --env Include an env var (e.g.: ${chalk.dim(
'`-e KEY=value`'
)}). Can appear many times.
-E ${chalk.underline('FILE')}, --dotenv=${chalk.underline(
'FILE'
)} Include env vars from .env file. Defaults to '.env'
-C, --no-clipboard Do not attempt to copy URL to clipboard
-N, --forward-npm Forward login information to install private npm modules
--session-affinity Session affinity, \`ip\` (default) or \`random\` to control session affinity.
${chalk.dim(
'Enforcable Types (when both package.json and Dockerfile exist):'
)}
--npm Node.js application
--docker Docker container
--static Static file hosting
${chalk.dim('Examples:')}
${chalk.gray('–')} Deploys the current directory
${chalk.cyan('$ now')}
${chalk.gray('–')} Deploys a custom path ${chalk.dim('`/usr/src/project`')}
${chalk.cyan('$ now /usr/src/project')}
${chalk.gray('–')} Deploys a GitHub repository
${chalk.cyan('$ now user/repo#ref')}
${chalk.gray('–')} Deploys a GitHub, GitLab or Bitbucket repo using its URL
${chalk.cyan('$ now https://gitlab.com/user/repo')}
${chalk.gray('–')} Deploys with ENV vars
${chalk.cyan(
'$ now -e NODE_ENV=production -e MYSQL_PASSWORD=@mysql-password'
)}
${chalk.gray('–')} Displays comprehensive help for the subcommand ${chalk.dim(
'`list`'
)}
${chalk.cyan('$ now help list')}
`)
}
let path = argv._[0]
if (path) {
// If path is relative: resolve
// if path is absolute: clear up strange `/` etc
path = resolve(process.cwd(), path)
} else {
path = process.cwd()
}
// If the current deployment is a repo
const gitRepo = {}
// Options
const forceNew = argv.force
let deploymentName = argv.name
let sessionAffinity = argv['session-affinity']
const debug = argv.debug
const clipboard = !argv['no-clipboard']
const forwardNpm = argv['forward-npm']
const forceSync = argv.forceSync
const shouldLogin = argv.login
const followSymlinks = !argv.links
const wantsPublic = argv.public
const apiUrl = argv.url || 'https://api.zeit.co'
const isTTY = process.stdout.isTTY
const quiet = !isTTY
const autoAliases =
typeof argv.alias === 'undefined' ? false : flatten([argv.alias])
if (argv.config) {
cfg.setConfigFile(argv.config)
}
if (Array.isArray(autoAliases)) {
console.log(
`${chalk.red('Deprecated!')} The option ${chalk.grey(
'--alias'
)} will be removed soon.`
)
console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n')
}
const stopDeployment = msg => {
handleError(msg)
process.exit(1)
}
const envFields = async list => {
const questions = []
for (const field of list) {
questions.push({
name: field,
message: field
})
}
// eslint-disable-next-line import/no-unassigned-import
require('../lib/utils/input/patch-inquirer')
info('Please enter the values for the following environment variables:')
const answers = await inquirer.prompt(questions)
for (const answer in answers) {
if (!{}.hasOwnProperty.call(answers, answer)) {
continue
}
const content = answers[answer]
if (content === '') {
stopDeployment(`Enter a value for ${answer}`)
}
}
return answers
}
let alwaysForwardNpm
async function main() {
if (argv.h || argv.help) {
help()
return exit(0)
} else if (argv.v || argv.version) {
console.log(version)
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 {
token = await login(apiUrl)
config = await cfg.read()
} catch (err) {
return stopDeployment(`Authentication error – ${err.message}`)
}
console.log(
`> Logged in successfully. Token saved to ${chalk.bold('~/.now.json')}.`
)
console.log(
`> Run ${cmd('now')} to deploy the current directory, or ${cmd(
'now --help'
)} for usage info.\n`
)
return exit(0)
}
alwaysForwardNpm = config.forwardNpm
// If we got to here then `token` should be set
try {
await sync({ token, config })
} catch (err) {
return stopDeployment(err)
}
}
async function sync({ token, config: { currentTeam, user } }) {
const start = Date.now()
const rawPath = argv._[0]
const planPromise = new NowPlans({
apiUrl,
token,
debug,
currentTeam
}).getCurrent()
try {
await fs.stat(path)
} catch (err) {
let repo
let isValidRepo = false
try {
isValidRepo = isRepoPath(rawPath)
} catch (err) {
if (err.code === 'INVALID_URL') {
stopDeployment(err)
} else {
throw err
}
}
if (isValidRepo) {
const gitParts = gitPathParts(rawPath)
Object.assign(gitRepo, gitParts)
const searchMessage = setTimeout(() => {
console.log(`> Didn't find directory. Searching on ${gitRepo.type}...`)
}, 500)
try {
repo = await fromGit(rawPath, debug)
} catch (err) {}
clearTimeout(searchMessage)
}
if (repo) {
// Tell now which directory to deploy
path = repo.path
// Set global variable for deleting tmp dir later
// once the deployment has finished
Object.assign(gitRepo, repo)
} else if (isValidRepo) {
const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : ''
stopDeployment(
`There's no repository named "${chalk.bold(
gitRepo.main
)}" ${gitRef}on ${gitRepo.type}`
)
} else {
error(`The specified directory "${basename(path)}" doesn't exist.`)
process.exit(1)
}
}
// Make sure that directory is deployable
try {
await checkPath(path)
} catch (err) {
error(err)
return
}
if (!quiet) {
if (gitRepo.main) {
const gitRef = gitRepo.ref ? ` at "${chalk.bold(gitRepo.ref)}" ` : ''
console.log(
`> Deploying ${gitRepo.type} repository "${chalk.bold(
gitRepo.main
)}" ${gitRef} under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
} else {
console.log(
`> Deploying ${chalk.bold(toHumanPath(path))} under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
}
}
let deploymentType
// CLI deployment type explicit overrides
if (argv.docker) {
if (debug) {
console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``)
}
deploymentType = 'docker'
} else if (argv.npm) {
if (debug) {
console.log(`> [debug] Forcing \`deploymentType\` = \`npm\``)
}
deploymentType = 'npm'
} else if (argv.static) {
if (debug) {
console.log(`> [debug] Forcing \`deploymentType\` = \`static\``)
}
deploymentType = 'static'
}
let meta
;({ meta, deploymentName, deploymentType, sessionAffinity } = await readMeta(
path,
deploymentName,
deploymentType,
sessionAffinity
))
const nowConfig = meta.nowConfig
const now = new Now({ apiUrl, token, debug, currentTeam })
let dotenvConfig
let dotenvOption
if (argv.dotenv) {
dotenvOption = argv.dotenv
} else if (nowConfig && nowConfig.dotenv) {
dotenvOption = nowConfig.dotenv
}
if (dotenvOption) {
const dotenvFileName =
typeof dotenvOption === 'string' ? dotenvOption : '.env'
if (!fs.existsSync(dotenvFileName)) {
error(`--dotenv flag is set but ${dotenvFileName} file is missing`)
return process.exit(1)
}
const dotenvFile = await fs.readFile(dotenvFileName)
dotenvConfig = dotenv.parse(dotenvFile)
}
let pkgEnv = nowConfig && nowConfig.env
const argEnv = [].concat(argv.env || [])
if (pkgEnv && Array.isArray(nowConfig.env)) {
const defined = argEnv.join()
const askFor = nowConfig.env.filter(item => !defined.includes(`${item}=`))
pkgEnv = await envFields(askFor)
}
// Merge `now.env` from package.json with `-e` arguments
const envs = [
...Object.keys(dotenvConfig || {}).map(k => `${k}=${dotenvConfig[k]}`),
...Object.keys(pkgEnv || {}).map(k => `${k}=${pkgEnv[k]}`),
...argEnv
]
let secrets
const findSecret = async uidOrName => {
if (!secrets) {
secrets = await now.listSecrets()
}
return secrets.filter(secret => {
return secret.name === uidOrName || secret.uid === uidOrName
})
}
const env_ = await Promise.all(
envs.map(async kv => {
if (typeof kv !== 'string') {
error('Env key and value missing')
return process.exit(1)
}
const [key, ...rest] = kv.split('=')
let val
if (rest.length > 0) {
val = rest.join('=')
}
if (/[^A-z0-9_]/i.test(key)) {
error(
`Invalid ${chalk.dim('-e')} key ${chalk.bold(
`"${chalk.bold(key)}"`
)}. Only letters, digits and underscores are allowed.`
)
return process.exit(1)
}
if (!key) {
error(`Invalid env option ${chalk.bold(`"${kv}"`)}`)
return process.exit(1)
}
if (val === undefined) {
if (key in process.env) {
console.log(
`> Reading ${chalk.bold(
`"${chalk.bold(key)}"`
)} from your env (as no value was specified)`
)
// Escape value if it begins with @
val = process.env[key].replace(/^@/, '\\@')
} else {
error(
`No value specified for env ${chalk.bold(
`"${chalk.bold(key)}"`
)} and it was not found in your env.`
)
return process.exit(1)
}
}
if (val[0] === '@') {
const uidOrName = val.substr(1)
const secrets = await findSecret(uidOrName)
if (secrets.length === 0) {
if (uidOrName === '') {
error(
`Empty reference provided for env key ${chalk.bold(
`"${chalk.bold(key)}"`
)}`
)
} else {
error(
`No secret found by uid or name ${chalk.bold(`"${uidOrName}"`)}`
)
}
return process.exit(1)
} else if (secrets.length > 1) {
error(
`Ambiguous secret ${chalk.bold(
`"${uidOrName}"`
)} (matches ${chalk.bold(secrets.length)} secrets)`
)
return process.exit(1)
}
val = { uid: secrets[0].uid }
}
return [key, typeof val === 'string' ? val.replace(/^\\@/, '@') : val]
})
)
const env = {}
env_.filter(v => Boolean(v)).forEach(([key, val]) => {
if (key in env) {
note(`Overriding duplicate env key ${chalk.bold(`"${key}"`)}`)
}
env[key] = val
})
try {
await now.create(
path,
Object.assign(
{
env,
followSymlinks,
forceNew,
forceSync,
forwardNpm: alwaysForwardNpm || forwardNpm,
quiet,
wantsPublic,
sessionAffinity
},
meta
)
)
} catch (err) {
if (debug) {
console.log(`> [debug] error: ${err}\n${err.stack}`)
}
return stopDeployment(err)
}
const { url } = now
const elapsed = ms(new Date() - start)
if (isTTY) {
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}]`)
}
} else {
process.stdout.write(url)
}
const startU = new Date()
const complete = ({ syncCount }) => {
if (!quiet) {
const elapsedU = ms(new Date() - startU)
console.log(
`> Synced ${syncCount} (${bytes(now.syncAmount)}) [${elapsedU}] `
)
console.log('> Initializing…')
}
// Close http2 agent
now.close()
// Show build logs
if (!quiet) {
if (deploymentType === 'static') {
console.log(`${chalk.cyan('> Deployment complete!')}`)
} else {
printLogs(now.host, token, currentTeam, user)
}
}
}
const plan = await planPromise
if (plan.id === 'oss' && !wantsPublic) {
if (isTTY) {
info(
`${chalk.bold(
(currentTeam && `${currentTeam.slug} is`) ||
`You (${user.username || user.email}) are`
)} on the OSS plan. Your code and logs will be made ${chalk.bold(
'public'
)}.`
)
const proceed = await promptBool(
'Are you sure you want to proceed with the deployment?',
{ trailing: eraseLines(1) }
)
if (proceed) {
note(`You can use ${cmd('now --public')} to skip this prompt`)
} else {
const stopSpinner = wait('Canceling deployment')
await now.remove(now.id, { hard: true })
stopSpinner()
info('Deployment aborted. No files were synced.')
info(`You can upgrade by running ${cmd('now upgrade')}.`)
return exit()
}
} else if (!wantsPublic) {
const msg =
'\nYou are on the OSS plan. Your code and logs will be made public.' +
' If you agree with that, please run again with --public.'
return stopDeployment(msg)
}
}
if (now.syncAmount) {
if (debug && now.syncFileCount !== now.fileCount) {
console.log(
`> [debug] total files ${now.fileCount}, ${now.syncFileCount} changed. `
)
}
const size = bytes(now.syncAmount)
const syncCount = `${now.syncFileCount} file${now.syncFileCount > 1
? 's'
: ''}`
const bar = new Progress(
`> Upload [:bar] :percent :etas (${size}) [${syncCount}]`,
{
width: 20,
complete: '=',
incomplete: '',
total: now.syncAmount,
clear: true
}
)
now.upload()
now.on('upload', ({ names, data }) => {
const amount = data.length
if (debug) {
console.log(
`> [debug] Uploaded: ${names.join(' ')} (${bytes(data.length)})`
)
}
bar.tick(amount)
})
now.on('complete', () => complete({ syncCount }))
now.on('error', err => {
error('Upload failed')
return stopDeployment(err)
})
} else {
if (!quiet) {
console.log(`> Initializing…`)
}
// Close http2 agent
now.close()
// Show build logs
if (deploymentType === 'static' && !quiet) {
console.log(`${chalk.cyan('> Deployment complete!')}`)
} else {
printLogs(now.host, token, currentTeam, user)
}
}
}
async function readMeta(path, deploymentName, deploymentType, sessionAffinity) {
try {
const meta = await readMetaData(path, {
deploymentType,
deploymentName,
quiet: true,
sessionAffinity
})
if (!deploymentType) {
deploymentType = meta.type
if (debug) {
console.log(
`> [debug] Detected \`deploymentType\` = \`${deploymentType}\``
)
}
}
if (!deploymentName) {
deploymentName = meta.name
if (debug) {
console.log(
`> [debug] Detected \`deploymentName\` = "${deploymentName}"`
)
}
}
return {
meta,
deploymentName,
deploymentType,
sessionAffinity
}
} catch (err) {
if (isTTY && err.code === 'MULTIPLE_MANIFESTS') {
if (debug) {
console.log('> [debug] Multiple manifests found, disambiguating')
}
console.log(
`> Two manifests found. Press [${chalk.bold(
'n'
)}] to deploy or re-run with --flag`
)
deploymentType = await promptOptions([
['npm', `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} `],
['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `]
])
if (debug) {
console.log(
`> [debug] Selected \`deploymentType\` = "${deploymentType}"`
)
}
return readMeta(path, deploymentName, deploymentType)
}
throw err
}
}
function printLogs(host, token, currentTeam, user) {
// Log build
const logger = new Logger(host, token, { debug, quiet })
logger.on('error', async err => {
if (!quiet) {
if (err && err.type === 'BUILD_ERROR') {
error(
`The build step of your project failed. To retry, run ${cmd(
'now --force'
)}.`
)
} else {
error('Deployment failed')
}
}
if (gitRepo && gitRepo.cleanup) {
// Delete temporary directory that contains repository
gitRepo.cleanup()
if (debug) {
console.log(`> [debug] Removed temporary repo directory`)
}
}
process.exit(1)
})
logger.on('close', async () => {
if (Array.isArray(autoAliases)) {
const aliasList = autoAliases.filter(item => item !== '')
if (aliasList.length > 0) {
const assignments = []
for (const alias of aliasList) {
assignments.push(
assignAlias(alias, token, host, apiUrl, debug, currentTeam, user)
)
}
await Promise.all(assignments)
} else {
await reAlias(
token,
host,
null,
help,
exit,
apiUrl,
debug,
currentTeam,
user
)
}
}
if (!quiet) {
console.log(`${chalk.cyan('> Deployment complete!')}`)
}
if (gitRepo && gitRepo.cleanup) {
// Delete temporary directory that contains repository
gitRepo.cleanup()
if (debug) {
console.log(`> [debug] Removed temporary repo directory`)
}
}
process.exit(0)
})
}
main().catch(err => {
handleError(err, { debug })
process.exit(1)
})

343
bin/now-dns.js

@ -1,343 +0,0 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk')
const minimist = require('minimist')
const ms = require('ms')
const table = require('text-table')
// Ours
const cfg = require('../lib/cfg')
const DomainRecords = require('../lib/domain-records')
const indent = require('../lib/indent')
const login = require('../lib/login')
const strlen = require('../lib/strlen')
const { handleError, error } = require('../lib/error')
const exit = require('../lib/utils/exit')
const logo = require('../lib/utils/output/logo')
const argv = minimist(process.argv.slice(2), {
string: ['config'],
boolean: ['help', 'debug'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
}
})
const subcommand = argv._[0]
// Options
const help = () => {
console.log(`
${chalk.bold(`${logo} now dns ls`)} [domain]
${chalk.bold(
`${logo} now dns add`
)} <domain> <name> <A | AAAA | ALIAS | CNAME | TXT> <value>
${chalk.bold(`${logo} now dns add`)} <domain> <name> MX <value> <mx_priority>
${chalk.bold(
`${logo} now dns add`
)} <domain> <name> SRV <priority> <weight> <port> <target>
${chalk.bold(`${logo} now dns rm`)} <id>
${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 all your DNS records
${chalk.cyan('$ now dns ls')}
${chalk.gray('–')} Add an A record for a subdomain
${chalk.cyan(
'$ now dns add <YOUR DOMAIN> <SUBDOMAIN NAME> A <RECORD VALUE>'
)}
${chalk.cyan('$ now dns add zeit.rocks api A 198.51.100.100')}
${chalk.gray('–')} Add an MX record (@ as a name refers to the domain)
${chalk.cyan(
'$ now dns add <YOUR DOMAIN> @ MX <RECORD VALUE> <PRIORITY>'
)}
${chalk.cyan('$ now dns add zeit.rocks @ MX mail.zeit.rocks 10')}
`)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
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) {
handleError(err)
exit(1)
}
})
.catch(err => {
handleError(err)
process.exit(1)
})
}
async function run({ token, config: { currentTeam, user } }) {
const domainRecords = new DomainRecords({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
const start = Date.now()
if (subcommand === 'ls' || subcommand === 'list') {
if (args.length > 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now dns ls [domain]`'
)}`
)
return exit(1)
}
const elapsed = ms(new Date() - start)
const res = await domainRecords.ls(args[0])
const text = []
let count = 0
res.forEach((records, domain) => {
count += records.length
if (records.length > 0) {
const cur = Date.now()
const header = [
['', 'id', 'name', 'type', 'value', 'aux', 'created'].map(s =>
chalk.dim(s)
)
]
const out = table(
header.concat(
records.map(record => {
const time = chalk.gray(
ms(cur - new Date(Number(record.created))) + ' ago'
)
const aux = (() => {
if (record.mxPriority !== undefined) return record.mxPriority
if (record.priority !== undefined) return record.priority
return ''
})()
return [
'',
record.id,
record.name,
record.type,
record.value,
aux,
time
]
})
),
{
align: ['l', 'r', 'l', 'l', 'l', 'l'],
hsep: ' '.repeat(2),
stringLength: strlen
}
)
text.push(`\n\n${chalk.bold(domain)}\n${indent(out, 2)}`)
}
})
console.log(
`> ${count} record${count === 1 ? '' : 's'} found ${chalk.gray(
`[${elapsed}]`
)} under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}`
)
console.log(text.join(''))
} else if (subcommand === 'add') {
const param = parseAddArgs(args)
if (!param) {
error(
`Invalid number of arguments. See: ${chalk.cyan(
'`now dns --help`'
)} for usage.`
)
return exit(1)
}
const record = await domainRecords.create(param.domain, param.data)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} A new DNS record for domain ${chalk.bold(
param.domain
)} ${chalk.gray(`(${record.uid})`)} created ${chalk.gray(
`[${elapsed}]`
)} (${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)})`
)
} else if (subcommand === 'rm' || subcommand === 'remove') {
if (args.length !== 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan('`now dns rm <id>`')}`
)
return exit(1)
}
const record = await domainRecords.getRecord(args[0])
if (!record) {
error('DNS record not found')
return exit(1)
}
const yes = await readConfirmation(
record,
'The following record will be removed permanently \n'
)
if (!yes) {
error('User abort')
return exit(0)
}
await domainRecords.delete(record.domain, record.id)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Record ${chalk.gray(
`${record.id}`
)} removed ${chalk.gray(`[${elapsed}]`)}`
)
} else {
error('Please specify a valid subcommand: ls | add | rm')
help()
exit(1)
}
return domainRecords.close()
}
process.on('uncaughtException', err => {
handleError(err)
exit(1)
})
function parseAddArgs(args) {
if (!args || args.length < 4) {
return null
}
const domain = args[0]
const name = args[1] === '@' ? '' : args[1].toString()
const type = args[2]
const value = args[3]
if (!(domain && typeof name === 'string' && type)) {
return null
}
if (type === 'MX') {
if (args.length !== 5) {
return null
}
return {
domain,
data: {
name,
type,
value,
mxPriority: args[4]
}
}
} else if (type === 'SRV') {
if (args.length !== 7) {
return null
}
return {
domain,
data: {
name,
type,
srv: {
priority: value,
weight: args[4],
port: args[5],
target: args[6]
}
}
}
}
if (args.length !== 4) {
return null
}
return {
domain,
data: {
name,
type,
value
}
}
}
function readConfirmation(record, msg) {
return new Promise(resolve => {
const time = chalk.gray(
ms(new Date() - new Date(Number(record.created))) + ' ago'
)
const tbl = table(
[
[
record.id,
chalk.bold(
`${record.name.length > 0
? record.name + '.'
: ''}${record.domain} ${record.type} ${record.value} ${record.mxPriority
? record.mxPriority
: ''}`
),
time
]
],
{ align: ['l', 'r', 'l'], hsep: ' '.repeat(6) }
)
process.stdout.write(`> ${msg}`)
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
)
process.stdin
.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
})
.resume()
})
}

423
bin/now-domains.js

@ -1,423 +0,0 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path')
// Packages
const chalk = require('chalk')
const minimist = require('minimist')
const ms = require('ms')
const psl = require('psl')
const table = require('text-table')
// Ours
const NowDomains = require('../lib/domains')
const cfg = require('../lib/cfg')
const exit = require('../lib/utils/exit')
const login = require('../lib/login')
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 { handleError, error } = require('../lib/error')
const argv = minimist(process.argv.slice(2), {
string: ['coupon', 'token'],
boolean: ['help', 'debug', 'external', 'force'],
alias: {
help: 'h',
coupon: 'c',
debug: 'd',
external: 'e',
force: 'f',
token: 't'
}
})
const subcommand = argv._[0]
// Options
const help = () => {
console.log(`
${chalk.bold(`${logo} now domains`)} <ls | add | rm | buy> <domain>
${chalk.dim('Options:')}
-h, --help Output usage information
-d, --debug Debug mode [off]
-e, --external Use external DNS server
-f, --force Skip DNS verification
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
${chalk.dim('Examples:')}
${chalk.gray('–')} Lists all your domains:
${chalk.cyan('$ now domains ls')}
${chalk.gray('–')} Buy a new domain:
${chalk.cyan(`$ now domains buy ${chalk.underline('domain-name.com')}`)}
${chalk.gray('–')} Adds a domain name:
${chalk.cyan(`$ now domains add ${chalk.underline('domain-name.com')}`)}
Make sure the domain's DNS nameservers are at least 2 of these:
${chalk.gray('–')} ${chalk.underline(
'california.zeit.world'
)} ${chalk.dim('173.255.215.107')}
${chalk.gray('–')} ${chalk.underline(
'london.zeit.world'
)} ${chalk.dim('178.62.47.76')}
${chalk.gray('–')} ${chalk.underline(
'newark.zeit.world'
)} ${chalk.dim('173.255.231.87')}
${chalk.gray('–')} ${chalk.underline(
'amsterdam.zeit.world'
)} ${chalk.dim('188.226.197.55')}
${chalk.gray('–')} ${chalk.underline(
'dallas.zeit.world'
)} ${chalk.dim('173.192.101.194')}
${chalk.gray('–')} ${chalk.underline(
'paris.zeit.world'
)} ${chalk.dim('37.123.115.172')}
${chalk.gray('–')} ${chalk.underline(
'singapore.zeit.world'
)} ${chalk.dim('119.81.97.170')}
${chalk.gray('–')} ${chalk.underline(
'sydney.zeit.world'
)} ${chalk.dim('52.64.171.200')}
${chalk.gray('–')} ${chalk.underline(
'frankfurt.zeit.world'
)} ${chalk.dim('91.109.245.139')}
${chalk.gray('–')} ${chalk.underline(
'iowa.zeit.world'
)} ${chalk.dim('23.236.59.22')}
${chalk.yellow('NOTE:')} running ${chalk.dim(
'`now alias`'
)} will automatically register your domain
if it's configured with these nameservers (no need to ${chalk.dim(
'`domain add`'
)}).
For more details head to ${chalk.underline('https://zeit.world')}.
${chalk.gray('–')} Removing a domain:
${chalk.cyan('$ now domain rm my-app.com')}
or
${chalk.cyan('$ now domain rm domainId')}
To get the list of domain ids, use ${chalk.dim('`now domains ls`')}.
${chalk.gray(
'–'
)} Adding and verifying a domain name using zeit.world nameservers:
${chalk.cyan('$ now domain add my-app.com')}
The command will tell you if the domain was verified succesfully. In case the domain was not verified succesfully you should retry adding the domain after some time.
${chalk.gray(
'–'
)} Adding and verifying a domain name using an external nameserver:
${chalk.cyan('$ now domain add -e my-app.com')}
and follow the verification instructions if requested. Finally, rerun the same command after completing the verification step.
`)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
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}`)
}
exit(1)
}
})
.catch(err => {
handleError(err)
process.exit(1)
})
}
async function run({ token, config: { currentTeam, user } }) {
const domain = new NowDomains({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
switch (subcommand) {
case 'ls':
case 'list': {
if (args.length !== 0) {
error('Invalid number of arguments')
return exit(1)
}
const start_ = new Date()
const domains = await domain.ls()
domains.sort((a, b) => new Date(b.created) - new Date(a.created))
const current = new Date()
const header = [
['', 'domain', 'dns', 'verified', 'created'].map(s => chalk.dim(s))
]
const out =
domains.length === 0
? null
: table(
header.concat(
domains.map(domain => {
const ns = domain.isExternal ? 'external' : 'zeit.world'
const url = chalk.bold(domain.name)
const time = chalk.gray(
ms(current - new Date(domain.created)) + ' ago'
)
return ['', url, ns, domain.verified, time]
})
),
{
align: ['l', 'l', 'l', 'l', 'l'],
hsep: ' '.repeat(2),
stringLength: strlen
}
)
const elapsed_ = ms(new Date() - start_)
console.log(
`> ${domains.length} domain${domains.length === 1
? ''
: 's'} found under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)} ${chalk.gray(`[${elapsed_}]`)}`
)
if (out) {
console.log('\n' + out + '\n')
}
break
}
case 'rm':
case 'remove': {
if (args.length !== 1) {
error('Invalid number of arguments')
return exit(1)
}
const _target = String(args[0])
if (!_target) {
const err = new Error('No domain specified')
err.userError = true
throw err
}
const _domains = await domain.ls()
const _domain = findDomain(_target, _domains)
if (!_domain) {
const err = new Error(
`Domain not found by "${_target}". Run ${chalk.dim(
'`now domains ls`'
)} to see your domains.`
)
err.userError = true
throw err
}
try {
const confirmation = (await readConfirmation(
domain,
_domain
)).toLowerCase()
if (confirmation !== 'y' && confirmation !== 'yes') {
console.log('\n> Aborted')
process.exit(0)
}
const start = new Date()
await domain.rm(_domain)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
_domain.uid
)} removed [${elapsed}]`
)
} catch (err) {
error(err)
exit(1)
}
break
}
case 'add':
case 'set': {
if (args.length !== 1) {
error('Invalid number of arguments')
return exit(1)
}
const name = String(args[0])
const parsedDomain = psl.parse(name)
if (parsedDomain.subdomain) {
const msg =
`You are adding "${name}" as a domain name which seems to contain a subdomain part "${parsedDomain.subdomain}".\n` +
' This is probably wrong unless you really know what you are doing.\n' +
` To add the root domain instead please run: ${chalk.cyan(
'now domain add ' +
(argv.external ? '-e ' : '') +
parsedDomain.domain
)}\n` +
` Continue adding "${name}" as a domain name?`
if (!await promptBool(msg)) {
return exit(1)
}
}
const start = new Date()
const { uid, code, created, verified } = await domain.add(
name,
argv.force,
argv.external
)
const elapsed = ms(new Date() - start)
if (created) {
console.log(
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
chalk.underline(name)
)} ${chalk.dim(`(${uid})`)} added [${elapsed}]`
)
} else if (verified) {
console.log(
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
chalk.underline(name)
)} ${chalk.dim(`(${uid})`)} verified [${elapsed}]`
)
} else if (code === 'not_modified') {
console.log(
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
chalk.underline(name)
)} ${chalk.dim(`(${uid})`)} already exists [${elapsed}]`
)
} else {
console.log(
'> Verification required: Please rerun this command after some time'
)
}
break
}
case 'buy': {
await require(resolve(__dirname, 'domains', 'buy.js'))({
domains: domain,
args,
currentTeam,
user,
coupon: argv.coupon
})
break
}
default:
error('Please specify a valid subcommand: ls | add | rm')
help()
exit(1)
}
domain.close()
}
async function readConfirmation(domain, _domain) {
return new Promise(resolve => {
const time = chalk.gray(ms(new Date() - new Date(_domain.created)) + ' ago')
const tbl = table([[chalk.underline(`https://${_domain.name}`), time]], {
align: ['r', 'l'],
hsep: ' '.repeat(6)
})
process.stdout.write('> The following domain will be removed permanently\n')
process.stdout.write(' ' + tbl + '\n')
if (_domain.aliases.length > 0) {
process.stdout.write(
`> ${chalk.yellow('Warning!')} This domain's ` +
`${chalk.bold(
_domain.aliases.length +
' alias' +
(_domain.aliases.length === 1 ? '' : 'es')
)} ` +
`will be removed. Run ${chalk.dim('`now alias ls`')} to list them.\n`
)
}
if (_domain.certs.length > 0) {
process.stdout.write(
`> ${chalk.yellow('Warning!')} This domain's ` +
`${chalk.bold(
_domain.certs.length +
' certificate' +
(_domain.certs.length === 1 ? '' : 's')
)} ` +
`will be removed. Run ${chalk.dim('`now cert ls`')} to list them.\n`
)
}
process.stdout.write(
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
)
process.stdin
.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim())
})
.resume()
})
}
function findDomain(val, list) {
return list.find(d => {
if (d.uid === val) {
if (debug) {
console.log(`> [debug] matched domain ${d.uid} by uid`)
}
return true
}
// Match prefix
if (d.name === toHost(val)) {
if (debug) {
console.log(`> [debug] matched domain ${d.uid} by name ${d.name}`)
}
return true
}
return false
})
}

247
bin/now-list.js

@ -1,247 +0,0 @@
#!/usr/bin/env node
// Packages
const minimist = require('minimist')
const chalk = require('chalk')
const ms = require('ms')
const printf = require('printf')
require('epipebomb')()
const supportsColor = require('supports-color')
// Ours
const Now = require('../lib')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const { handleError, error } = require('../lib/error')
const logo = require('../lib/utils/output/logo')
const sort = require('../lib/sort-deployments')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug', 'all'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
}
})
const help = () => {
console.log(`
${chalk.bold(`${logo} now list`)} [app]
${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 all deployments
${chalk.cyan('$ now ls')}
${chalk.gray('–')} List all deployments for the app ${chalk.dim('`my-app`')}
${chalk.cyan('$ now ls my-app')}
${chalk.dim('Alias:')} ls
`)
}
if (argv.help) {
help()
process.exit(0)
}
const app = argv._[0]
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
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)
}
if (!config.token) {
console.log(
`> Logged in successfully. Token saved to ${chalk.bold('~/.now.json')}.`
)
process.exit(0)
}
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 })
const start = new Date()
if (argv.all && !app) {
console.log('> You must define an app when using `--all`')
process.exit(1)
}
let deployments
try {
deployments = await now.list(app)
} catch (err) {
handleError(err)
process.exit(1)
}
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) {
const match = await now.findDeployment(app)
if (match !== null && typeof match !== 'undefined') {
deployments = Array.of(match)
}
}
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) {
const aliases = await now.listAliases()
const item = aliases.find(e => e.uid === app || e.alias === app)
if (item) {
const match = await now.findDeployment(item.deploymentId)
if (match !== null && typeof match !== 'undefined') {
deployments = Array.of(match)
}
}
}
now.close()
const apps = new Map()
if (argv.all) {
await Promise.all(
deployments.map(async ({ uid }, i) => {
deployments[i].instances = await now.listInstances(uid)
})
)
}
for (const dep of deployments) {
const deps = apps.get(dep.name) || []
apps.set(dep.name, deps.concat(dep))
}
const sorted = await sort([...apps])
const urlLength =
deployments.reduce((acc, i) => {
return Math.max(acc, (i.url && i.url.length) || 0)
}, 0) + 5
const timeNow = new Date()
console.log(
`> ${deployments.length} deployment${deployments.length === 1
? ''
: 's'} found under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)} ${chalk.grey('[' + ms(timeNow - start) + ']')}`
)
let shouldShowAllInfo = false
for (const app of apps) {
shouldShowAllInfo =
app[1].length > 5 ||
app.find(depl => {
return depl.scale && depl.scale.current > 1
})
if (shouldShowAllInfo) {
break
}
}
if (!argv.all && shouldShowAllInfo) {
console.log(
`> To expand the list and see instances run ${chalk.cyan(
'`now ls --all [app]`'
)}`
)
}
console.log()
sorted.forEach(([name, deps]) => {
const listedDeployments = argv.all ? deps : deps.slice(0, 5)
console.log(
`${chalk.bold(name)} ${chalk.gray(
'(' + listedDeployments.length + ' of ' + deps.length + ' total)'
)}`
)
const urlSpec = `%-${urlLength}s`
console.log(
printf(
` ${chalk.grey(urlSpec + ' %8s %-16s %8s')}`,
'url',
'inst #',
'state',
'age'
)
)
listedDeployments.forEach(dep => {
let state = dep.state
let extraSpaceForState = 0
if (state === null || typeof state === 'undefined') {
state = 'DEPLOYMENT_ERROR'
}
if (/ERROR/.test(state)) {
state = chalk.red(state)
extraSpaceForState = 10
} else if (state === 'FROZEN') {
state = chalk.grey(state)
extraSpaceForState = 10
}
let spec
if (supportsColor) {
spec = ` %-${urlLength + 10}s %8s %-${extraSpaceForState + 16}s %8s`
} else {
spec = ` %-${urlLength + 1}s %8s %-${16}s %8s`
}
console.log(
printf(
spec,
chalk.underline(dep.url),
dep.scale ? dep.scale.current : '✖',
state,
ms(timeNow - dep.created)
)
)
if (Array.isArray(dep.instances) && dep.instances.length > 0) {
dep.instances.forEach(i => {
console.log(
printf(` %-${urlLength + 10}s`, ` - ${chalk.underline(i.url)}`)
)
})
console.log()
}
})
console.log()
})
}

130
bin/now-logout.js

@ -1,130 +0,0 @@
#!/usr/bin/env node
// Packages
const minimist = require('minimist')
const chalk = require('chalk')
const fetch = require('node-fetch')
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'],
boolean: ['help'],
alias: {
help: 'h',
config: 'c'
}
})
const help = () => {
console.log(`
${chalk.bold(`${logo} now logout`)}
${chalk.dim('Options:')}
-h, --help output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline(
'FILE'
)} config file
${chalk.dim('Examples:')}
${chalk.gray('–')} Logout from the CLI:
${chalk.cyan('$ now logout')}
`)
}
if (argv.help) {
help()
process.exit(0)
}
const apiUrl = argv.url || 'https://api.zeit.co'
const endpoint = apiUrl + '/www/user/tokens/'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
const requestHeaders = token => ({
headers: {
Authorization: `bearer ${token}`
}
})
const getTokenId = async token => {
const result = await fetch(endpoint, requestHeaders(token))
const tokenList = await result.json()
if (!tokenList.tokens) {
return
}
const tokenInfo = tokenList.tokens.find(t => token === t.token)
if (!tokenInfo) {
return
}
return tokenInfo.id
}
const revokeToken = async (token, tokenId) => {
const details = {
method: 'DELETE'
}
Object.assign(details, requestHeaders(token))
const result = await fetch(endpoint + encodeURIComponent(tokenId), details)
if (!result.ok) {
console.error('Not able to log out')
}
}
const logout = async () => {
const spinner = ora({
text: 'Logging out...'
}).start()
const config = await cfg.read()
try {
await cfg.removeFile()
} catch (err) {
spinner.fail(`Couldn't remove config while logging out`)
process.exit(1)
}
let tokenId
try {
tokenId = await getTokenId(argv.token || config.token)
} catch (err) {
spinner.fail('Not able to get token id on logout')
process.exit(1)
}
if (!tokenId) {
return
}
try {
await revokeToken(argv.token || config.token, tokenId)
} catch (err) {
spinner.fail('Could not revoke token on logout')
process.exit(1)
}
spinner.succeed('Logged out!')
}
logout().catch(err => {
handleError(err)
process.exit(1)
})

290
bin/now-logs.js

@ -1,290 +0,0 @@
#!/usr/bin/env node
const qs = require('querystring')
const minimist = require('minimist')
const chalk = require('chalk')
const dateformat = require('dateformat')
const io = require('socket.io-client')
const Now = require('../lib')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const { handleError, error } = require('../lib/error')
const logo = require('../lib/utils/output/logo')
const { compare, deserialize } = require('../lib/logs')
const { maybeURL, normalizeURL, parseInstanceURL } = require('../lib/utils/url')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'query', 'since', 'token', 'until'],
boolean: ['help', 'all', 'debug', 'f'],
alias: {
help: 'h',
all: 'a',
config: 'c',
debug: 'd',
token: 't',
query: 'q'
}
})
let deploymentIdOrURL = argv._[0]
const help = () => {
console.log(`
${chalk.bold(`${logo} now logs`)} <deploymentId|url>
${chalk.dim('Options:')}
-h, --help output usage information
-a, --all include access logs
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline(
'FILE'
)} config file
-d, --debug debug mode [off]
-f wait for additional data [off]
-n ${chalk.bold.underline('NUMBER')} number of logs [1000]
-q ${chalk.bold.underline('QUERY')}, --query=${chalk.bold.underline(
'QUERY'
)} search query
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} login token
--since=${chalk.bold.underline(
'SINCE'
)} only return logs after date (ISO 8601)
--until=${chalk.bold.underline(
'UNTIL'
)} only return logs before date (ISO 8601), ignored if the f option is enbled.
${chalk.dim('Examples:')}
${chalk.gray('–')} Print logs for the deployment ${chalk.dim(
'`deploymentId`'
)}
${chalk.cyan('$ now logs deploymentId')}
`)
}
if (argv.help || !deploymentIdOrURL) {
help()
process.exit(0)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
const limit = typeof argv.n === 'number' ? argv.n : 1000
const query = argv.query || ''
const follow = argv.f
const types = argv.all ? [] : ['command', 'stdout', 'stderr', 'exit']
let since
try {
since = argv.since ? toSerial(argv.since) : null
} catch (err) {
error(`Invalid date string: ${argv.since}`)
process.exit(1)
}
let until
try {
until = argv.until ? toSerial(argv.until) : null
} catch (err) {
error(`Invalid date string: ${argv.until}`)
process.exit(1)
}
let instanceId
if (maybeURL(deploymentIdOrURL)) {
const normalizedURL = normalizeURL(deploymentIdOrURL)
if (normalizedURL.includes('/')) {
error(`Invalid deployment url: can't include path (${deploymentIdOrURL})`)
process.exit(1)
}
;[deploymentIdOrURL, instanceId] = parseInstanceURL(normalizedURL)
}
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)
}
await printLogs({ token, config })
})
.catch(err => {
handleError(err)
process.exit(1)
})
async function printLogs({ token, config: { currentTeam } }) {
let buf = []
let init = false
let lastLog
if (!follow) {
onLogs(await fetchLogs({ token, currentTeam, since, until }))
return
}
const isURL = deploymentIdOrURL.includes('.')
const q = qs.stringify({
deploymentId: isURL ? '' : deploymentIdOrURL,
host: isURL ? deploymentIdOrURL : '',
instanceId,
types: types.join(','),
query
})
const socket = io(`https://log-io.zeit.co?${q}`)
socket.on('connect', () => {
if (debug) {
console.log('> [debug] Socket connected')
}
})
socket.on('auth', callback => {
if (debug) {
console.log('> [debug] Socket authenticate')
}
callback(token)
})
socket.on('ready', () => {
if (debug) {
console.log('> [debug] Socket ready')
}
// For the case socket reconnected
const _since = lastLog ? lastLog.serial : since
fetchLogs({ token, currentTeam, since: _since }).then(logs => {
init = true
const m = {}
logs.concat(buf.map(b => b.log)).forEach(l => {
m[l.id] = l
})
buf = []
onLogs(Object.values(m))
})
})
socket.on('logs', l => {
const log = deserialize(l)
let timer
if (init) {
// Wait for other logs for a while
// and sort them in the correct order
timer = setTimeout(() => {
buf.sort((a, b) => compare(a.log, b.log))
const idx = buf.findIndex(b => b.log.id === log.id)
buf.slice(0, idx + 1).forEach(b => {
clearTimeout(b.timer)
onLog(b.log)
})
buf = buf.slice(idx + 1)
}, 300)
}
buf.push({ log, timer })
})
socket.on('disconnect', () => {
if (debug) {
console.log('> [debug] Socket disconnect')
}
init = false
})
socket.on('error', err => {
if (debug) {
console.log('> [debug] Socket error', err.stack)
}
})
function onLogs(logs) {
logs.sort(compare).forEach(onLog)
}
function onLog(log) {
lastLog = log
printLog(log)
}
}
function printLog(log) {
let data
const obj = log.object
if (log.type === 'request') {
data =
`REQ "${obj.method} ${obj.uri} ${obj.protocol}"` +
` ${obj.remoteAddr} - ${obj.remoteUser || ''}` +
` "${obj.referer || ''}" "${obj.userAgent}"`
} else if (log.type === 'response') {
data =
`RES "${obj.method} ${obj.uri} ${obj.protocol}"` +
` ${obj.status} ${obj.bodyBytesSent}`
} else {
data = obj
? JSON.stringify(obj, null, 2)
: (log.text || '').replace(/\n$/, '')
}
const date = dateformat(log.date, 'mm/dd hh:MM TT')
data.split('\n').forEach((line, i) => {
if (i === 0) {
console.log(`${chalk.dim(date)} ${line}`)
} else {
console.log(`${repeat(' ', date.length)} ${line}`)
}
})
}
async function fetchLogs({ token, currentTeam, since, until } = {}) {
const now = new Now({ apiUrl, token, debug, currentTeam })
let logs
try {
logs = await now.logs(deploymentIdOrURL, {
instanceId,
types,
limit,
query,
since,
until
})
} catch (err) {
handleError(err)
process.exit(1)
} finally {
now.close()
}
return logs.map(deserialize)
}
function repeat(s, n) {
return new Array(n + 1).join(s)
}
function toSerial(datestr) {
const t = Date.parse(datestr)
if (isNaN(t)) {
throw new TypeError('Invalid date string')
}
const pidLen = 19
const seqLen = 19
return t + repeat('0', pidLen + seqLen)
}

218
bin/now-remove.js

@ -1,218 +0,0 @@
#!/usr/bin/env node
// Packages
const minimist = require('minimist')
const chalk = require('chalk')
const ms = require('ms')
const table = require('text-table')
// Ours
const Now = require('../lib')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const { handleError, error } = require('../lib/error')
const logo = require('../lib/utils/output/logo')
const { normalizeURL } = require('../lib/utils/url')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug', 'hard', 'yes', 'safe'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't',
yes: 'y'
}
})
const ids = argv._
// Options
const help = () => {
console.log(`
${chalk.bold(
`${logo} now remove`
)} deploymentId|deploymentName [...deploymentId|deploymentName]
${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
-y, --yes Skip confirmation
--safe Skip deployments with an active alias
${chalk.dim('Examples:')}
${chalk.gray('–')} Remove a deployment identified by ${chalk.dim(
'`deploymentId`'
)}:
${chalk.cyan('$ now rm deploymentId')}
${chalk.gray('–')} Remove all deployments with name ${chalk.dim('`my-app`')}:
${chalk.cyan('$ now rm my-app')}
${chalk.gray('–')} Remove two deployments with IDs ${chalk.dim(
'`eyWt6zuSdeus`'
)} and ${chalk.dim('`uWHoA9RQ1d1o`')}:
${chalk.cyan('$ now rm eyWt6zuSdeus uWHoA9RQ1d1o')}
${chalk.dim('Alias:')} rm
`)
}
if (argv.help || ids.length === 0) {
help()
process.exit(0)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
const hard = argv.hard || false
const skipConfirmation = argv.yes || false
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)
}
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 => {
process.stdout.write(
`> The following deployment${matches.length === 1
? ''
: 's'} will be removed permanently:\n`
)
const tbl = table(
matches.map(depl => {
const time = chalk.gray(ms(new Date() - depl.created) + ' ago')
const url = depl.url ? chalk.underline(`https://${depl.url}`) : ''
return [depl.uid, url, time]
}),
{ align: ['l', 'r', 'l'], hsep: ' '.repeat(6) }
)
process.stdout.write(tbl + '\n')
for (const depl of matches) {
for (const alias of depl.aliases) {
process.stdout.write(
`> ${chalk.yellow('Warning!')} Deployment ${chalk.bold(depl.uid)} ` +
`is an alias for ${chalk.underline(
`https://${alias.alias}`
)} and will be removed.\n`
)
}
}
process.stdout.write(
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
)
process.stdin
.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim())
})
.resume()
})
}
async function remove({ token, config: { currentTeam } }) {
const now = new Now({ apiUrl, token, debug, currentTeam })
const deployments = await now.list()
let matches = deployments.filter(d => {
return ids.some(id => {
return d.uid === id || d.name === id || d.url === normalizeURL(id)
})
})
const aliases = await Promise.all(
matches.map(depl => now.listAliases(depl.uid))
)
matches = matches.filter((match, i) => {
if (argv.safe && aliases[i].length > 0) {
return false
}
match.aliases = aliases[i]
return true
})
if (matches.length === 0) {
error(
`Could not find ${argv.safe
? 'unaliased'
: 'any'} deployments matching ${ids
.map(id => chalk.bold(`"${id}"`))
.join(', ')}. Run ${chalk.dim(`\`now ls\``)} to list.`
)
return process.exit(1)
}
try {
if (!skipConfirmation) {
const confirmation = (await readConfirmation(matches)).toLowerCase()
if (confirmation !== 'y' && confirmation !== 'yes') {
console.log('\n> Aborted')
process.exit(0)
}
}
const start = new Date()
await Promise.all(matches.map(depl => now.remove(depl.uid, { hard })))
const elapsed = ms(new Date() - start)
console.log(`${chalk.cyan('> Success!')} [${elapsed}]`)
console.log(
table(
matches.map(depl => {
return [`Deployment ${chalk.bold(depl.uid)} removed`]
})
)
)
} catch (err) {
handleError(err)
process.exit(1)
}
now.close()
}

384
bin/now-scale.js

@ -1,384 +0,0 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk')
const isURL = require('is-url')
const minimist = require('minimist')
const ms = require('ms')
const printf = require('printf')
require('epipebomb')()
const supportsColor = require('supports-color')
// Ours
const cfg = require('../lib/cfg')
const { handleError, error } = require('../lib/error')
const NowScale = require('../lib/scale')
const login = require('../lib/login')
const exit = require('../lib/utils/exit')
const logo = require('../lib/utils/output/logo')
const info = require('../lib/scale-info')
const sort = require('../lib/sort-deployments')
const success = require('../lib/utils/output/success')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug'],
alias: { help: 'h', config: 'c', debug: 'd', token: 't' }
})
let id = argv._[0]
const scaleArg = argv._[1]
const optionalScaleArg = argv._[2]
// Options
const help = () => {
console.log(`
${chalk.bold(`${logo} now scale`)} ls
${chalk.bold(`${logo} now scale`)} <url>
${chalk.bold(`${logo} now scale`)} <url> <min> [max]
${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]
${chalk.dim('Examples:')}
${chalk.gray('–')} Create a deployment with 3 instances, never sleeps:
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 3')}
${chalk.gray('–')} Create an automatically scaling deployment:
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 1 5')}
${chalk.gray(
'–'
)} Create an automatically scaling deployment without specifying max:
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 1 auto')}
${chalk.gray(
'–'
)} Create an automatically scaling deployment without specifying min or max:
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh auto')}
${chalk.gray(
'–'
)} Create a deployment that is always active and never "sleeps":
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 1')}
`)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
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)
}
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)
})
}
function guessParams() {
if (Number.isInteger(scaleArg) && !optionalScaleArg) {
return { min: scaleArg, max: scaleArg }
} else if (Number.isInteger(scaleArg) && Number.isInteger(optionalScaleArg)) {
return { min: scaleArg, max: optionalScaleArg }
} else if (Number.isInteger(scaleArg) && optionalScaleArg === 'auto') {
return { min: scaleArg, max: 'auto' }
} else if (
(!scaleArg && !optionalScaleArg) ||
(scaleArg === 'auto' && !optionalScaleArg)
) {
return { min: 1, max: 'auto' }
}
help()
process.exit(1)
}
function isHostNameOrId(str) {
return (
/(https?:\/\/)?((?:(?=[a-z0-9-]{1,63}\.)(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.)+[a-z]{2,63})/.test(
str
) || str.length === 28
)
}
async function run({ token, config: { currentTeam } }) {
const scale = new NowScale({ apiUrl, token, debug, currentTeam })
const start = Date.now()
if (id === 'ls') {
await list(scale)
process.exit(0)
} else if (id === 'info') {
await info(scale)
process.exit(0)
} else if (id && isHostNameOrId(id)) {
// Normalize URL by removing slash from the end
if (isURL(id)) {
id = id.replace(/^https:\/\//i, '')
if (id.slice(-1) === '/') {
id = id.slice(0, -1)
}
}
} else {
error('Please specify a deployment: now scale <id|url>')
help()
exit(1)
}
const deployments = await scale.list()
let match = deployments.find(d => {
// `url` should match the hostname of the deployment
let u = id.replace(/^https:\/\//i, '')
if (u.indexOf('.') === -1) {
// `.now.sh` domain is implied if just the subdomain is given
u += '.now.sh'
}
return d.uid === id || d.name === id || d.url === u
})
if (!match) {
// Maybe it's an alias
const aliasDeployment = (await scale.listAliases()).find(
e => e.alias === id
)
if (!aliasDeployment) {
error(`Could not find any deployments matching ${id}`)
return process.exit(1)
}
match = deployments.find(d => {
return d.uid === aliasDeployment.deploymentId
})
}
const { min, max } = guessParams()
if (
!(Number.isInteger(min) || min === 'auto') &&
!(Number.isInteger(max) || max === 'auto')
) {
help()
return exit(1)
}
if (match.type === 'STATIC') {
if (min === 0 && max === 0) {
error("Static deployments can't be FROZEN. Use `now rm` to remove")
return process.exit(1)
}
console.log('> Static deployments are automatically scaled!')
return process.exit(0)
}
const {
max: currentMax,
min: currentMin,
current: currentCurrent
} = match.scale
if (
max === currentMax &&
min === currentMin &&
Number.isInteger(min) &&
currentCurrent >= min &&
Number.isInteger(max) &&
currentCurrent <= max
) {
// Nothing to do, let's print the rules
printScaleingRules(match.url, currentCurrent, min, max)
return
}
if ((match.state === 'FROZEN' || match.scale.current === 0) && min > 0) {
console.log(
`> Deployment is currently in 0 replicas, preparing deployment for scaling...`
)
if (match.scale.max < 1) {
await scale.setScale(match.uid, { min: 0, max: 1 })
}
await scale.unfreeze(match)
}
const { min: newMin, max: newMax } = await scale.setScale(match.uid, {
min,
max
})
const elapsed = ms(new Date() - start)
const currentReplicas = match.scale.current
printScaleingRules(match.url, currentReplicas, newMin, newMax, elapsed)
await info(scale, match.url)
scale.close()
}
function printScaleingRules(url, currentReplicas, min, max, elapsed) {
const log = console.log
success(
`Configured scaling rules ${chalk.gray(elapsed ? '[' + elapsed + ']' : '')}`
)
log()
log(
`${chalk.bold(url)} (${chalk.gray(currentReplicas)} ${chalk.gray(
'current'
)})`
)
log(printf('%6s %s', 'min', chalk.bold(min)))
log(printf('%6s %s', 'max', chalk.bold(max)))
log(printf('%6s %s', 'auto', chalk.bold(min === max ? '✖' : '✔')))
log()
}
async function list(scale) {
let deployments
try {
const app = argv._[1]
deployments = await scale.list(app)
} catch (err) {
handleError(err)
process.exit(1)
}
scale.close()
const apps = new Map()
for (const dep of deployments) {
const deps = apps.get(dep.name) || []
apps.set(dep.name, deps.concat(dep))
}
const sorted = await sort([...apps])
const timeNow = new Date()
const urlLength =
deployments.reduce((acc, i) => {
return Math.max(acc, (i.url && i.url.length) || 0)
}, 0) + 5
for (const app of sorted) {
const depls = argv.all ? app[1] : app[1].slice(0, 5)
console.log(
`${chalk.bold(app[0])} ${chalk.gray(
'(' + depls.length + ' of ' + app[1].length + ' total)'
)}`
)
console.log()
const urlSpec = `%-${urlLength}s`
console.log(
printf(
` ${chalk.grey(urlSpec + ' %8s %8s %8s %8s %8s')}`,
'url',
'cur',
'min',
'max',
'auto',
'age'
)
)
for (const instance of depls) {
if (!instance.scale) {
let spec
if (supportsColor) {
spec = ` %-${urlLength + 10}s %8s %8s %8s %8s %8s`
} else {
spec = ` %-${urlLength + 1}s %8s %8s %8s %8s %8s`
}
const infinite = '∞'
console.log(
printf(
spec,
chalk.underline(instance.url),
infinite,
1,
infinite,
'✔',
ms(timeNow - instance.created)
)
)
} else if (instance.scale.current > 0) {
let spec
if (supportsColor) {
spec = ` %-${urlLength + 10}s %8s %8s %8s %8s %8s`
} else {
spec = ` %-${urlLength + 1}s %8s %8s %8s %8s %8s`
}
console.log(
printf(
spec,
chalk.underline(instance.url),
instance.scale.current,
instance.scale.min,
instance.scale.max,
instance.scale.max === instance.scale.min ? '✖' : '✔',
ms(timeNow - instance.created)
)
)
} else {
let spec
if (supportsColor) {
spec = ` %-${urlLength + 10}s ${chalk.gray('%8s %8s %8s %8s %8s')}`
} else {
spec = ` %-${urlLength + 1}s ${chalk.gray('%8s %8s %8s %8s %8s')}`
}
console.log(
printf(
spec,
chalk.underline(instance.url),
instance.scale.current,
instance.scale.min,
instance.scale.max,
instance.scale.max === instance.scale.min ? '✖' : '✔',
ms(timeNow - instance.created)
)
)
}
}
console.log()
}
}
process.on('uncaughtException', err => {
handleError(err)
exit(1)
})

297
bin/now-secrets.js

@ -1,297 +0,0 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk')
const table = require('text-table')
const minimist = require('minimist')
const ms = require('ms')
// Ours
const strlen = require('../lib/strlen')
const cfg = require('../lib/cfg')
const { handleError, error } = require('../lib/error')
const NowSecrets = require('../lib/secrets')
const login = require('../lib/login')
const exit = require('../lib/utils/exit')
const logo = require('../lib/utils/output/logo')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug', 'base64'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
base64: 'b',
token: 't'
}
})
const subcommand = argv._[0]
// Options
const help = () => {
console.log(`
${chalk.bold(`${logo} now secrets`)} <ls | add | rename | rm> <secret>
${chalk.dim('Options:')}
-h, --help Output usage information
-b, --base64 Treat value as base64-encoded
-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 secrets:
${chalk.cyan('$ now secrets ls')}
${chalk.gray('–')} Adds a new secret:
${chalk.cyan('$ now secrets add my-secret "my value"')}
${chalk.gray(
'–'
)} Once added, a secret's value can't be retrieved in plaintext anymore
${chalk.gray(
'–'
)} If the secret's value is more than one word, wrap it in quotes
${chalk.gray('–')} Actually, when in doubt, wrap your value in quotes
${chalk.gray('–')} Exposes a secret as an env variable:
${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold('@my-secret')}`)}
Notice the ${chalk.cyan.bold(
'`@`'
)} symbol which makes the value a secret reference.
${chalk.gray('–')} Renames a secret:
${chalk.cyan(`$ now secrets rename my-secret my-renamed-secret`)}
${chalk.gray('–')} Removes a secret:
${chalk.cyan(`$ now secrets rm my-secret`)}
`)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
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) {
handleError(err)
exit(1)
}
})
.catch(err => {
handleError(err)
process.exit(1)
})
}
async function run({ token, config: { currentTeam, user } }) {
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
const start = Date.now()
if (subcommand === 'ls' || subcommand === 'list') {
if (args.length !== 0) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan('`now secret ls`')}`
)
return exit(1)
}
const list = await secrets.ls()
const elapsed = ms(new Date() - start)
console.log(
`> ${list.length} secret${list.length === 1
? ''
: 's'} found under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)} ${chalk.gray(`[${elapsed}]`)}`
)
if (list.length > 0) {
const cur = Date.now()
const header = [['', 'name', 'created'].map(s => chalk.dim(s))]
const out = table(
header.concat(
list.map(secret => {
return [
'',
chalk.bold(secret.name),
chalk.gray(ms(cur - new Date(secret.created)) + ' ago')
]
})
),
{
align: ['l', 'l', 'l'],
hsep: ' '.repeat(2),
stringLength: strlen
}
)
if (out) {
console.log('\n' + out + '\n')
}
}
return secrets.close()
}
if (subcommand === 'rm' || subcommand === 'remove') {
if (args.length !== 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now secret rm <name>`'
)}`
)
return exit(1)
}
const list = await secrets.ls()
const theSecret = list.find(secret => secret.name === args[0])
if (theSecret) {
const yes = await readConfirmation(theSecret)
if (!yes) {
error('User abort')
return exit(0)
}
} else {
error(`No secret found by name "${args[0]}"`)
return exit(1)
}
const secret = await secrets.rm(args[0])
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
secret.name
)} removed ${chalk.gray(`[${elapsed}]`)}`
)
return secrets.close()
}
if (subcommand === 'rename') {
if (args.length !== 2) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now secret rename <old-name> <new-name>`'
)}`
)
return exit(1)
}
const secret = await secrets.rename(args[0], args[1])
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
secret.oldName
)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}`
)
return secrets.close()
}
if (subcommand === 'add' || subcommand === 'set') {
if (args.length !== 2) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan(
'`now secret add <name> <value>`'
)}`
)
if (args.length > 2) {
const example = chalk.cyan(`$ now secret add ${args[0]}`)
console.log(
`> If your secret has spaces, make sure to wrap it in quotes. Example: \n ${example} `
)
}
return exit(1)
}
const [name, value_] = args
let value
if (argv.base64) {
value = { base64: value_ }
} else {
value = value_
}
await secrets.add(name, value)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(
name.toLowerCase()
)} added (${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}) ${chalk.gray(`[${elapsed}]`)}`
)
return secrets.close()
}
error('Please specify a valid subcommand: ls | add | rename | rm')
help()
exit(1)
}
process.on('uncaughtException', err => {
handleError(err)
exit(1)
})
function readConfirmation(secret) {
return new Promise(resolve => {
const time = chalk.gray(ms(new Date() - new Date(secret.created)) + ' ago')
const tbl = table([[chalk.bold(secret.name), time]], {
align: ['r', 'l'],
hsep: ' '.repeat(6)
})
process.stdout.write('> The following secret will be removed permanently\n')
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`
)
process.stdin
.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
})
.resume()
})
}

171
bin/now-teams.js

@ -1,171 +0,0 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path')
// Packages
const chalk = require('chalk')
const minimist = require('minimist')
// Ours
const login = require('../lib/login')
const cfg = require('../lib/cfg')
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'],
boolean: ['help', 'debug'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't',
switch: 'change'
}
})
const subcommand = argv._[0]
const help = () => {
console.log(`
${chalk.bold(`${logo} now teams`)} <add | ls | rm | invite>
${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('–')} Add a new team:
${chalk.cyan('$ now teams add')}
${chalk.gray('–')} Switch to a team:
${chalk.cyan(`$ now switch <slug>`)}
${chalk.gray(
'–'
)} If your team's url is 'zeit.co/teams/name', 'name' is the slug
${chalk.gray('–')} If the slug is omitted, you can choose interactively
${chalk.yellow(
'NOTE:'
)} When you switch, everything you add, list or remove will be scoped that team!
${chalk.gray('–')} Invite new members (interactively):
${chalk.cyan(`$ now teams invite`)}
${chalk.gray('–')} Invite a specific email:
${chalk.cyan(`$ now teams invite geist@zeit.co`)}
${chalk.gray('–')} Remove a team:
${chalk.cyan(`$ now teams rm <id>`)}
${chalk.gray('–')} If the id is omitted, you can choose interactively
`)
}
// Options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config)
}
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}`)
}
exit(1)
}
})
.catch(err => {
handleError(err)
process.exit(1)
})
}
async function run({ token, config: { currentTeam } }) {
const teams = new NowTeams({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
switch (subcommand) {
case 'list':
case 'ls': {
await require(resolve(__dirname, 'teams', 'list.js'))({
teams,
token
})
break
}
case 'switch':
case 'change': {
await require(resolve(__dirname, 'teams', 'switch.js'))({
teams,
args,
token
})
break
}
case 'add':
case 'create': {
await require(resolve(__dirname, 'teams', 'add.js'))({ teams, token })
break
}
case 'invite': {
await require(resolve(__dirname, 'teams', 'invite.js'))({
teams,
args,
token
})
break
}
default: {
let code = 0
if (subcommand !== 'help') {
error('Please specify a valid subcommand: ls | add | rm | set-default')
code = 1
}
help()
exit(code)
}
}
}

257
bin/now-upgrade.js

@ -1,257 +0,0 @@
#!/usr/bin/env node
// 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 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 { handleError } = require('../lib/error')
const { bold } = chalk
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 {
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}`)
}
exit(1)
}
})
.catch(err => {
handleError(err)
process.exit(1)
})
}
function buildInquirerChoices(current, until) {
if (until) {
until = until.split(' ')
until = ' for ' + chalk.bold(until[0]) + ' more ' + until[1]
} else {
until = ''
}
const currentText = bold('(current)')
let ossName = `OSS ${bold('FREE')}`
let premiumName = `Premium ${bold('$15')}`
let proName = `Pro ${bold('$50')}`
let advancedName = `Advanced ${bold('$200')}`
switch (current) {
case 'oss': {
ossName += indent(currentText, 6)
break
}
case 'premium': {
premiumName += indent(currentText, 3)
break
}
case 'pro': {
proName += indent(currentText, 7)
break
}
case 'advanced': {
advancedName += indent(currentText, 1)
break
}
default: {
ossName += indent(currentText, 6)
}
}
return [
{
name: ossName,
value: 'oss',
short: `OSS ${bold('FREE')}`
},
{
name: premiumName,
value: 'premium',
short: `Premium ${bold('$15')}`
},
{
name: proName,
value: 'pro',
short: `Pro ${bold('$50')}`
},
{
name: advancedName,
value: 'advanced',
short: `Advanced ${bold('$200')}`
}
]
}
async function run({ token, config: { currentTeam, user } }) {
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, currentTeam })
let planId = args[0]
if (![undefined, 'oss', 'premium', 'pro', 'advanced'].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 = `For more info, please head to https://zeit.co`
message = currentTeam
? `${message}/${currentTeam.slug}/settings/plan`
: `${message}/account/plan`
message += `\n> Select a plan for ${bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)} ${chalk.gray(`[${elapsed}]`)}`
const choices = buildInquirerChoices(currentPlan.id, currentPlan.until)
planId = await listInput({
message,
choices,
separator: false,
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) {
if (err.code === 'customer_not_found' || err.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 ${err.message}`)
}
plans.close()
return
}
if (currentPlan.until && newPlan.id !== 'oss') {
success(
`The cancelation has been undone. You're back on the ${chalk.bold(
`${newPlan.name} plan`
)}`
)
} else if (newPlan.until) {
success(
`Your plan will be switched to ${chalk.bold(
newPlan.name
)} in ${chalk.bold(newPlan.until)}. Your card will not be charged again`
)
} else {
success(`You're now on the ${chalk.bold(`${newPlan.name} plan`)}`)
}
plans.close()
}

78
bin/now-whoami.js

@ -1,78 +0,0 @@
#!/usr/bin/env node
// Packages
const minimist = require('minimist')
const chalk = require('chalk')
// Ours
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'],
boolean: ['help', 'debug', 'all'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
}
})
const help = () => {
console.log(`
${chalk.bold(`${logo} now whoami`)}
${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('–')} Show the current team context
${chalk.cyan('$ now whoami')}
`)
}
if (argv.help) {
help()
process.exit(0)
}
if (argv.config) {
cfg.setConfigFile(argv.config)
}
async function whoami() {
const config = await cfg.read({ token: argv.token })
if (!config || !config.token) {
console.log(
`> Not currently logged in! Please run ${cmd('now --login')}.\n`
)
return exit(1)
}
if (process.stdout.isTTY) {
process.stdout.write('> ')
}
const { user } = config
const name = user.username || user.email
console.log(name)
}
whoami().catch(err => {
handleError(err)
process.exit(1)
})

142
bin/now.js

@ -1,142 +0,0 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path')
// Packages
const updateNotifier = require('update-notifier')
const chalk = require('chalk')
// Check if the current path exists and throw and error
// if the user is trying to deploy a non-existing path!
// This needs to be done exactly in this place, because
// the utility imports are taking advantage of it
try {
process.cwd()
} catch (err) {
if (err.code === 'ENOENT' && err.syscall === 'uv_cwd') {
console.log(`Current path doesn't exist!`)
} else {
console.log(err)
}
process.exit(1)
}
// Utilities
const pkg = require('../lib/pkg')
if (process.pkg) {
const notifier = updateNotifier({ pkg })
const update = notifier.update
if (update) {
let message = `Update available! ${chalk.red(
update.current
)} ${chalk.green(update.latest)} \n`
message += `${chalk.magenta(
'Changelog:'
)} https://github.com/zeit/now-cli/releases/tag/${update.latest}\n`
if (pkg._npmPkg) {
message += `Run ${chalk.magenta('npm i -g now')} to update!`
} else {
message += `Please download binaries from https://zeit.co/download`
}
notifier.notify({ message })
}
}
// This command will be run if no other sub command is specified
const defaultCommand = 'deploy'
const commands = new Set([
defaultCommand,
'help',
'list',
'ls',
'rm',
'remove',
'alias',
'aliases',
'ln',
'domain',
'domains',
'dns',
'cert',
'certs',
'secret',
'secrets',
'cc',
'billing',
'upgrade',
'downgrade',
'team',
'teams',
'switch',
'log',
'logs',
'scale',
'logout',
'whoami'
])
const aliases = new Map([
['ls', 'list'],
['rm', 'remove'],
['ln', 'alias'],
['aliases', 'alias'],
['domain', 'domains'],
['cert', 'certs'],
['secret', 'secrets'],
['cc', 'billing'],
['downgrade', 'upgrade'],
['team', 'teams'],
['switch', 'teams switch'],
['log', 'logs']
])
let cmd = defaultCommand
let args = process.argv.slice(2)
const index = args.findIndex(a => commands.has(a))
if (index > -1) {
cmd = args[index]
args.splice(index, 1)
if (cmd === 'help') {
if (index < args.length && commands.has(args[index])) {
cmd = args[index]
args.splice(index, 1)
} else {
cmd = defaultCommand
}
args.unshift('--help')
}
cmd = aliases.get(cmd) || cmd
if (cmd.includes(' ')) {
const parts = cmd.split(' ')
cmd = parts.shift()
args = [].concat(parts, args)
}
}
// Don't throw a useless error message when running `now help help`
// rather show the general help and be useful
if (cmd === 'help') {
cmd = 'deploy'
} else if (cmd === defaultCommand && args[0] === 'login') {
args[0] = '--login'
}
const bin = resolve(__dirname, 'now-' + cmd + '.js')
// Prepare process.argv for subcommand
process.argv = process.argv.slice(0, 2).concat(args)
// Load sub command
// With custom parameter to make "pkg" happy
require(bin, 'may-exclude')

135
bin/teams/add.js

@ -1,135 +0,0 @@
// Packages
const chalk = require('chalk')
// Ours
const stamp = require('../../lib/utils/output/stamp')
const info = require('../../lib/utils/output/info')
const error = require('../../lib/utils/output/error')
const wait = require('../../lib/utils/output/wait')
const rightPad = require('../../lib/utils/output/right-pad')
const eraseLines = require('../../lib/utils/output/erase-lines')
const { tick } = require('../../lib/utils/output/chars')
const success = require('../../lib/utils/output/success')
const cmd = require('../../lib/utils/output/cmd')
const note = require('../../lib/utils/output/note')
const uid = require('../../lib/utils/output/uid')
const textInput = require('../../lib/utils/input/text')
const exit = require('../../lib/utils/exit')
const cfg = require('../../lib/cfg')
function validateSlugKeypress(data, value) {
// TODO: the `value` here should contain the current value + the keypress
// should be fixed on utils/input/text.js
return /^[a-zA-Z]+[a-zA-Z0-9_-]*$/.test(value + data)
}
function gracefulExit() {
console.log() // Blank line
note(
`Your team is now active for all ${cmd('now')} commands!\n Run ${cmd(
'now switch'
)} to change it in the future.`
)
return exit()
}
const teamUrlPrefix = rightPad('Team URL', 14) + chalk.gray('zeit.co/')
const teamNamePrefix = rightPad('Team Name', 14)
module.exports = async function({ teams, token }) {
let slug
let team
let elapsed
let stopSpinner
info(
`Pick a team identifier for its url (e.g.: ${chalk.cyan('`zeit.co/acme`')})`
)
do {
try {
// eslint-disable-next-line no-await-in-loop
slug = await textInput({
label: `- ${teamUrlPrefix}`,
validateKeypress: validateSlugKeypress,
initialValue: slug,
valid: team,
forceLowerCase: true
})
} catch (err) {
if (err.message === 'USER_ABORT') {
info('Aborted')
return exit()
}
throw err
}
elapsed = stamp()
stopSpinner = wait(teamUrlPrefix + slug)
let res
try {
// eslint-disable-next-line no-await-in-loop
res = await teams.create({ slug })
stopSpinner()
team = res
} catch (err) {
stopSpinner()
eraseLines(2)
error(err.message)
}
} while (!team)
eraseLines(2)
success(`Team created ${uid(team.id)} ${elapsed()}`)
console.log(chalk.cyan(`${tick} `) + teamUrlPrefix + slug + '\n')
info('Pick a display name for your team')
let name
try {
name = await textInput({
label: `- ${teamNamePrefix}`,
validateValue: value => value.trim().length > 0
})
} catch (err) {
if (err.message === 'USER_ABORT') {
info('No name specified')
gracefulExit()
} else {
throw err
}
}
elapsed = stamp()
stopSpinner = wait(teamNamePrefix + name)
const res = await teams.edit({ id: team.id, name })
stopSpinner()
eraseLines(2)
if (res.error) {
error(res.error.message)
console.log(`${chalk.red(`${teamNamePrefix}`)}${name}`)
exit(1)
// TODO: maybe we want to ask the user to retry? not sure if
// there's a scenario where that would be wanted
}
team = Object.assign(team, res)
success(`Team name saved ${elapsed()}`)
console.log(chalk.cyan(`${tick} `) + teamNamePrefix + team.name + '\n')
stopSpinner = wait('Saving')
await cfg.merge({ currentTeam: team })
stopSpinner()
await require('./invite')({
teams,
args: [],
token,
introMsg:
'Invite your team mates! When done, press enter on an empty field',
noopMsg: `You can invite team mates later by running ${cmd(
'now teams invite'
)}`
})
gracefulExit()
}

160
bin/teams/invite.js

@ -1,160 +0,0 @@
// Packages
const chalk = require('chalk')
// Ours
const regexes = require('../../lib/utils/input/regexes')
const wait = require('../../lib/utils/output/wait')
const cfg = require('../../lib/cfg')
const fatalError = require('../../lib/utils/fatal-error')
const cmd = require('../../lib/utils/output/cmd')
const info = require('../../lib/utils/output/info')
const stamp = require('../../lib/utils/output/stamp')
const param = require('../../lib/utils/output/param')
const { tick } = require('../../lib/utils/output/chars')
const rightPad = require('../../lib/utils/output/right-pad')
const textInput = require('../../lib/utils/input/text')
const eraseLines = require('../../lib/utils/output/erase-lines')
const success = require('../../lib/utils/output/success')
const error = require('../../lib/utils/output/error')
function validateEmail(data) {
return regexes.email.test(data.trim()) || data.length === 0
}
const domains = Array.from(
new Set([
'aol.com',
'gmail.com',
'google.com',
'yahoo.com',
'ymail.com',
'hotmail.com',
'live.com',
'outlook.com',
'inbox.com',
'mail.com',
'gmx.com',
'icloud.com'
])
)
function emailAutoComplete(value, teamSlug) {
const parts = value.split('@')
if (parts.length === 2 && parts[1].length > 0) {
const [, host] = parts
let suggestion = false
domains.unshift(teamSlug)
for (const domain of domains) {
if (domain.startsWith(host)) {
suggestion = domain.substr(host.length)
break
}
}
domains.shift()
return suggestion
}
return false
}
module.exports = async function(
{ teams, args, token, introMsg, noopMsg = 'No changes made' } = {}
) {
const { user, currentTeam } = await cfg.read({ token })
domains.push(user.email.split('@')[1])
if (!currentTeam) {
let err = `You can't run this command under ${param(
user.username || user.email
)}.\n`
err += `${chalk.gray('>')} Run ${cmd('now switch')} to choose to a team.`
return fatalError(err)
}
info(introMsg || `Inviting team members to ${chalk.bold(currentTeam.name)}`)
if (args.length > 0) {
for (const email of args) {
if (regexes.email.test(email)) {
const stopSpinner = wait(email)
const elapsed = stamp()
// eslint-disable-next-line no-await-in-loop
await teams.inviteUser({ teamId: currentTeam.id, email })
stopSpinner()
console.log(`${chalk.cyan(tick)} ${email} ${elapsed()}`)
} else {
console.log(`${chalk.red(`${email}`)} ${chalk.gray('[invalid]')}`)
}
}
return
}
const inviteUserPrefix = rightPad('Invite User', 14)
const emails = []
let hasError = false
let email
do {
email = ''
try {
// eslint-disable-next-line no-await-in-loop
email = await textInput({
label: `- ${inviteUserPrefix}`,
validateValue: validateEmail,
autoComplete: value => emailAutoComplete(value, currentTeam.slug)
})
} catch (err) {
if (err.message !== 'USER_ABORT') {
throw err
}
}
let elapsed
let stopSpinner
if (email) {
elapsed = stamp()
stopSpinner = wait(inviteUserPrefix + email)
try {
// eslint-disable-next-line no-await-in-loop
await teams.inviteUser({ teamId: currentTeam.id, email })
stopSpinner()
email = `${email} ${elapsed()}`
emails.push(email)
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`)
if (hasError) {
hasError = false
eraseLines(emails.length + 2)
info(
introMsg ||
`Inviting team members to ${chalk.bold(currentTeam.name)}`
)
for (const email of emails) {
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`)
}
}
} catch (err) {
stopSpinner()
eraseLines(emails.length + 2)
error(err.message)
hasError = true
for (const email of emails) {
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`)
}
}
}
} while (email !== '')
eraseLines(emails.length + 2)
const n = emails.length
if (emails.length === 0) {
info(noopMsg)
} else {
success(`Invited ${n} team mate${n > 1 ? 's' : ''}`)
for (const email of emails) {
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`)
}
}
}

62
bin/teams/list.js

@ -1,62 +0,0 @@
const chalk = require('chalk')
const wait = require('../../lib/utils/output/wait')
const cfg = require('../../lib/cfg')
const info = require('../../lib/utils/output/info')
const error = require('../../lib/utils/output/error')
const { tick: tickChar } = require('../../lib/utils/output/chars')
const table = require('../../lib/utils/output/table')
module.exports = async function({ teams, token }) {
const stopSpinner = wait('Fetching teams')
const list = (await teams.ls()).teams
let { user, currentTeam } = await cfg.read({ token })
const accountIsCurrent = !currentTeam
stopSpinner()
if (accountIsCurrent) {
currentTeam = {
slug: user.username || user.email
}
}
const teamList = list.map(({ slug, name }) => {
return {
name,
value: slug,
current: slug === currentTeam.slug ? tickChar : ''
}
})
teamList.unshift({
name: user.email,
value: user.username || user.email,
current: (accountIsCurrent && tickChar) || ''
})
// Let's bring the current team to the beginning of the list
if (!accountIsCurrent) {
const index = teamList.findIndex(
choice => choice.value === currentTeam.slug
)
const choice = teamList.splice(index, 1)[0]
teamList.unshift(choice)
}
// Printing
const count = teamList.length
if (!count) {
// Maybe should not happen
error(`No team found`)
return
}
info(`${chalk.bold(count)} team${count > 1 ? 's' : ''} found`)
console.log()
table(
['', 'id', 'email / name'],
teamList.map(team => [team.current, team.value, team.name]),
[1, 5]
)
}

124
bin/teams/switch.js

@ -1,124 +0,0 @@
const chalk = require('chalk')
const wait = require('../../lib/utils/output/wait')
const listInput = require('../../lib/utils/input/list')
const cfg = require('../../lib/cfg')
const exit = require('../../lib/utils/exit')
const success = require('../../lib/utils/output/success')
const info = require('../../lib/utils/output/info')
const error = require('../../lib/utils/output/error')
const param = require('../../lib/utils/output/param')
async function updateCurrentTeam({ cfg, newTeam } = {}) {
delete newTeam.created
delete newTeam.creator_id
await cfg.merge({ currentTeam: newTeam })
}
module.exports = async function({ teams, args, token }) {
let stopSpinner = wait('Fetching teams')
const list = (await teams.ls()).teams
let { user, currentTeam } = await cfg.read({ token })
const accountIsCurrent = !currentTeam
stopSpinner()
if (accountIsCurrent) {
currentTeam = {
slug: user.username || user.email
}
}
if (args.length !== 0) {
const desiredSlug = args[0]
const newTeam = list.find(team => team.slug === desiredSlug)
if (newTeam) {
await updateCurrentTeam({ cfg, newTeam })
success(`The team ${chalk.bold(newTeam.name)} is now active!`)
return exit()
}
if (desiredSlug === user.username) {
stopSpinner = wait('Saving')
await cfg.remove('currentTeam')
stopSpinner()
return success(`Your account (${chalk.bold(desiredSlug)}) is now active!`)
}
error(`Could not find membership for team ${param(desiredSlug)}`)
return exit(1)
}
const choices = list.map(({ slug, name }) => {
name = `${slug} (${name})`
if (slug === currentTeam.slug) {
name += ` ${chalk.bold('(current)')}`
}
return {
name,
value: slug,
short: slug
}
})
const suffix = accountIsCurrent ? ` ${chalk.bold('(current)')}` : ''
const userEntryName = user.username
? `${user.username} (${user.email})${suffix}`
: user.email
choices.unshift({
name: userEntryName,
value: user.email,
short: user.username
})
// Let's bring the current team to the beginning of the list
if (!accountIsCurrent) {
const index = choices.findIndex(choice => choice.value === currentTeam.slug)
const choice = choices.splice(index, 1)[0]
choices.unshift(choice)
}
let message
if (currentTeam) {
message = `Switch to:`
}
const choice = await listInput({
message,
choices,
separator: false
})
// Abort
if (!choice) {
info('No changes made')
return exit()
}
const newTeam = list.find(item => item.slug === choice)
// Switch to account
if (!newTeam) {
if (currentTeam.slug === user.username || currentTeam.slug === user.email) {
info('No changes made')
return exit()
}
stopSpinner = wait('Saving')
await cfg.remove('currentTeam')
stopSpinner()
return success(`Your account (${chalk.bold(choice)}) is now active!`)
}
if (newTeam.slug === currentTeam.slug) {
info('No changes made')
return exit()
}
stopSpinner = wait('Saving')
await updateCurrentTeam({ cfg, newTeam })
stopSpinner()
success(`The team ${chalk.bold(newTeam.name)} is now active!`)
}

29
circle.yml

@ -1,29 +0,0 @@
machine:
node:
version: node
compile:
override:
- npm run pack
post:
- cp packed/* $CIRCLE_ARTIFACTS
deployment:
default:
branch: /.*/
owner: zeit
commands:
- node ./scripts/slack.js
release:
tag: /.*/
owner: zeit
commands:
- ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME -replace $CIRCLE_TAG packed
- npm publish
dependencies:
pre:
- echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
post:
- go get github.com/tcnksm/ghr
- npm install -g slackup

16
download/install.js

@ -1,16 +0,0 @@
/* eslint-disable no-var */
// Native
var path = require('path')
var fs = require('fs')
var dist = path.join(__dirname, 'dist')
var src = path.join(__dirname, 'src')
// Don't install when developing locally
if (fs.existsSync(src)) {
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0)
}
require(path.join(dist, 'download.js'))

10
download/src/chmod.js

@ -1,10 +0,0 @@
// Native
import fs from 'fs'
export default function (file) {
const s = fs.statSync(file)
const newMode = s.mode | 64 | 8 | 1
if (s.mode === newMode) return
const base8 = newMode.toString(8).slice(-3)
fs.chmodSync(file, base8)
}

193
download/src/index.js

@ -1,193 +0,0 @@
/* eslint-disable unicorn/no-process-exit */
// Native
import fs from 'fs'
import path from 'path'
import { spawnSync } from 'child_process'
import zlib from 'zlib'
// Packages
import onDeath from 'death'
import fetch from 'node-fetch'
import retry from 'async-retry'
import which from 'which'
// Utilities
import plusxSync from './chmod'
import {
disableProgress,
enableProgress,
info,
showProgress,
warn
} from './log'
fetch.Promise = Promise
global.Promise = Promise
let { platform } = process
if (detectAlpine()) platform = 'alpine'
const packagePath = path.join(__dirname, '../../package.json')
const packageJSON = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
const now = path.join(__dirname, 'now')
const targetWin32 = path.join(__dirname, 'now.exe')
const target = platform === 'win32' ? targetWin32 : now
const partial = target + '.partial'
const backup = target + '.' + packageJSON.version + '.backup'
function whichPromise (name) {
return new Promise((resolve, reject) => {
which(name, (error, result) => {
if (error) return reject(error)
resolve(result)
})
})
}
const platformToName = {
alpine: 'now-alpine',
darwin: 'now-macos',
linux: 'now-linux',
win32: 'now-win.exe'
}
function detectAlpine () {
if (platform !== 'linux') return false
// https://github.com/sass/node-sass/issues/1589#issuecomment-265292579
const ldd = spawnSync('ldd', [ process.execPath ]).stdout.toString()
return /\bmusl\b/.test(ldd)
}
async function download() {
try {
fs.writeFileSync(
now,
'#!/usr/bin/env node\n' +
'console.log("Please wait until the \'now\' installation completes!")\n'
)
} catch (err) {
if (err.code === 'EACCES') {
warn(
'Please try installing now CLI again with the `--unsafe-perm` option.'
)
info('Example: `npm i -g --unsafe-perm now`')
process.exit()
}
throw err
}
onDeath(() => {
fs.writeFileSync(
now,
'#!/usr/bin/env node\n' +
'console.log("The \'now\' installation did not complete successfully.")\n' +
'console.log("Please run \'npm i -g now\' to reinstall!")\n'
)
process.exit()
})
info('For the source code, check out: https://github.com/zeit/now-cli')
// Print an empty line
console.log('')
await retry(async () => {
enableProgress('Downloading now CLI ' + packageJSON.version)
showProgress(0)
try {
const name = platformToName[platform]
const url = `https://cdn.zeit.co/releases/now-cli/${packageJSON.version}/${name}`
const resp = await fetch(url, { compress: false })
if (resp.status !== 200) {
throw new Error(resp.statusText + ' ' + url)
}
const size = resp.headers.get('content-length')
if (!size) {
throw new Error('Not found (content-length is absent)')
}
const ws = fs.createWriteStream(partial)
await new Promise((resolve, reject) => {
let bytesRead = 0
resp.body
.on('error', reject)
.on('data', chunk => {
bytesRead += chunk.length
showProgress(100 * bytesRead / size)
})
const gunzip = zlib.createGunzip()
gunzip
.on('error', reject)
resp.body.pipe(gunzip).pipe(ws)
ws
.on('error', reject)
.on('close', () => {
showProgress(100)
resolve()
})
})
} finally {
disableProgress()
}
}, {
retries: 500,
onRetry: (err) => console.error(err)
})
fs.renameSync(partial, target)
fs.writeFileSync(backup, fs.readFileSync(target))
}
async function main() {
if (fs.existsSync(backup)) {
fs.writeFileSync(target, fs.readFileSync(backup))
} else {
await download()
}
if (platform === 'win32') {
// Now.exe is executed only
fs.unlinkSync(now)
try {
// Workaround for https://github.com/npm/cmd-shim/pull/25
const globalPath = path.dirname(await whichPromise('npm'))
const gitBashFile = path.join(globalPath, 'now')
fs.writeFileSync(
gitBashFile,
'#!/bin/sh\n' +
'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")\n' +
'\n' +
'case `uname` in\n' +
' *CYGWIN*) basedir=`cygpath -w "$basedir"`;;\n' +
'esac\n' +
'\n' +
fs.readFileSync(gitBashFile, 'utf8')
)
} catch (err) {
if (err.code !== 'ENOENT') {
// Not a problem. only git cmd will not work
console.error(err)
}
}
} else {
plusxSync(now)
}
}
main().catch(err => {
console.error(err)
process.exit(2)
})

43
download/src/log.js

@ -1,43 +0,0 @@
// Packages
import assert from 'assert'
import chalk from 'chalk'
import Progress from 'progress'
let bar
export function enableProgress(text) {
assert(!bar)
bar = new Progress(`> ${text} [:bar] :percent`, {
stream: process.stdout,
width: 20,
complete: '=',
incomplete: ' ',
total: 100
})
}
export function info(text) {
console.log(`> ${text}`)
}
export function warn(text) {
console.log(chalk.red('> Warning!'), text)
}
export function showProgress(percentage) {
assert(bar)
bar.update(percentage / 100)
}
export function disableProgress() {
assert(bar)
// It is auto-completed once it updates to 100
// otherwise it outputs a blank line
if (!bar.complete) {
bar.terminate()
}
bar = undefined
}

34
download/webpack.js

@ -1,34 +0,0 @@
// Native
const path = require('path')
module.exports = {
target: 'node',
node: {
__dirname: false,
__filename: false,
process: false
},
entry: [
'./src/index.js'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'download.js'
},
module: {
loaders: [ {
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
plugins: [
'transform-async-to-generator',
'transform-runtime'
],
presets: [
'es2015'
]
}
} ]
}
}

83
lib/agent.js

@ -1,83 +0,0 @@
// Native
const { parse } = require('url')
const http = require('http')
const https = require('https')
// Packages
const fetch = require('node-fetch')
/**
* Returns a `fetch` version with a similar
* API to the browser's configured with a
* HTTP2 agent.
*
* It encodes `body` automatically as JSON.
*
* @param {String} host
* @return {Function} fetch
*/
module.exports = class Agent {
constructor(url, { tls = true, debug } = {}) {
this._url = url
const parsed = parse(url)
this._protocol = parsed.protocol
this._debug = debug
if (tls) {
this._initAgent()
}
}
_initAgent() {
const module = this._protocol === 'https:' ? https : http
this._agent = new module.Agent({
keepAlive: true,
keepAliveMsecs: 10000,
maxSockets: 8
}).on('error', err => this._onError(err, this._agent))
}
_onError(err, agent) {
if (this._debug) {
console.log(`> [debug] agent connection error ${err}\n${err.stack}`)
}
if (this._agent === agent) {
this._agent = null
}
}
fetch(path, opts = {}) {
if (!this._agent) {
if (this._debug) {
console.log('> [debug] re-initializing agent')
}
this._initAgent()
}
const { body } = opts
if (this._agent) {
opts.agent = this._agent
}
if (body && typeof body === 'object' && typeof body.pipe !== 'function') {
opts.headers['Content-Type'] = 'application/json'
opts.body = JSON.stringify(body)
}
if (opts.body && typeof body.pipe !== 'function') {
opts.headers['Content-Length'] = Buffer.byteLength(opts.body)
}
return fetch(this._url + path, opts)
}
close() {
if (this._debug) {
console.log('> [debug] closing agent')
}
if (this._agent) {
this._agent.destroy()
}
}
}

881
lib/alias.js

@ -1,881 +0,0 @@
// Packages
const { readFileSync } = require('fs')
const publicSuffixList = require('psl')
const minimist = require('minimist')
const ms = require('ms')
const chalk = require('chalk')
const { write: copy } = require('clipboardy')
// Ours
const promptBool = require('../lib/utils/input/prompt-bool')
const info = require('../lib/utils/output/info')
const param = require('../lib/utils/output/param')
const wait = require('../lib/utils/output/wait')
const success = require('../lib/utils/output/success')
const uid = require('../lib/utils/output/uid')
const eraseLines = require('../lib/utils/output/erase-lines')
const stamp = require('../lib/utils/output/stamp')
const error = require('../lib/utils/output/error')
const treatBuyError = require('../lib/utils/domains/treat-buy-error')
const scaleInfo = require('./scale-info')
const { DOMAIN_VERIFICATION_ERROR } = require('./errors')
const isZeitWorld = require('./is-zeit-world')
const resolve4 = require('./dns')
const toHost = require('./to-host')
const exit = require('./utils/exit')
const Now = require('./')
const argv = minimist(process.argv.slice(2), {
boolean: ['no-clipboard'],
alias: { 'no-clipboard': 'C' }
})
const isTTY = process.stdout.isTTY
const clipboard = !argv['no-clipboard']
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/
module.exports = class Alias extends Now {
async ls(deployment) {
if (deployment) {
const target = await this.findDeployment(deployment)
if (!target) {
const err = new Error(
`Aliases not found by "${deployment}". Run ${chalk.dim(
'`now alias ls`'
)} to see your aliases.`
)
err.userError = true
throw err
}
return this.listAliases(target.uid)
}
return this.listAliases()
}
async rm(_alias) {
return this.retry(async bail => {
const res = await this._fetch(`/now/aliases/${_alias.uid}`, {
method: 'DELETE'
})
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
if (res.status !== 200) {
const err = new Error('Deletion failed. Try again later.')
throw err
}
})
}
async findDeployment(deployment) {
const list = await this.list()
let key
let val
if (/\./.test(deployment)) {
val = toHost(deployment)
key = 'url'
} else {
val = deployment
key = 'uid'
}
const depl = list.find(d => {
if (d[key] === val) {
if (this._debug) {
console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`)
}
return true
}
// Match prefix
if (`${val}.now.sh` === d.url) {
if (this._debug) {
console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`)
}
return true
}
return false
})
return depl
}
async updatePathBasedroutes(alias, rules, domains) {
alias = await this.maybeSetUpDomain(alias, domains)
return this.upsertPathAlias(alias, rules)
}
async upsertPathAlias(alias, rules) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] /now/aliases #${attempt}`)
}
const rulesData = this.readRulesFile(rules)
const ruleCount = rulesData.rules.length
const res = await this._fetch(`/now/aliases`, {
method: 'POST',
body: { alias, rules: rulesData.rules }
})
const body = await res.json()
body.ruleCount = ruleCount
if (this._debug) {
console.timeEnd(`> [debug] /now/aliases #${attempt}`)
}
// 409 conflict is returned if it already exists
if (res.status === 409) {
return { uid: body.error.uid }
}
if (res.status === 422) {
return body
}
// No retry on authorization problems
if (res.status === 403) {
const code = body.error.code
if (code === 'custom_domain_needs_upgrade') {
const err = new Error(
`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray(
'`'
)}${chalk.cyan('now upgrade')}${chalk.gray('`')}.`
)
err.userError = true
return bail(err)
}
if (code === 'alias_in_use') {
const err = new Error(
`The alias you are trying to configure (${chalk.underline(
chalk.bold(alias)
)}) is already in use by a different account.`
)
err.userError = true
return bail(err)
}
if (code === 'forbidden') {
const err = new Error(
'The domain you are trying to use as an alias is already in use by a different account.'
)
err.userError = true
return bail(err)
}
return bail(new Error('Authorization error'))
}
// All other errors
if (body.error) {
const code = body.error.code
if (code === 'cert_missing') {
console.log(
`> Provisioning certificate for ${chalk.underline(
chalk.bold(alias)
)}`
)
try {
await this.createCert(alias)
} catch (err) {
// We bail to avoid retrying the whole process
// of aliasing which would involve too many
// retries on certificate provisioning
return bail(err)
}
// Try again, but now having provisioned the certificate
return this.upsertPathAlias(alias, rules)
}
if (code === 'cert_expired') {
console.log(
`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`
)
try {
await this.createCert(alias, { renew: true })
} catch (err) {
return bail(err)
}
}
return bail(new Error(body.error.message))
}
// The two expected succesful cods are 200 and 304
if (res.status !== 200 && res.status !== 304) {
throw new Error('Unhandled error')
}
return body
})
}
readRulesFile(rules) {
try {
const rulesJson = readFileSync(rules, 'utf8')
return JSON.parse(rulesJson)
} catch (err) {
console.error(`Reading rules file ${rules} failed: ${err}`)
}
}
async set(deployment, alias, domains, currentTeam, user) {
alias = alias.replace(/^https:\/\//i, '')
if (alias.indexOf('.') === -1) {
// `.now.sh` domain is implied if just the subdomain is given
alias += '.now.sh'
}
const depl = await this.findDeployment(deployment)
if (!depl) {
const err = new Error(
`Deployment not found by "${deployment}". Run ${chalk.dim(
'`now ls`'
)} to see your deployments.`
)
err.userError = true
throw err
}
const aliasDepl = (await this.listAliases()).find(e => e.alias === alias)
if (aliasDepl && aliasDepl.rules) {
if (isTTY) {
try {
const msg =
`> Path alias exists with ${aliasDepl.rules.length} rule${aliasDepl
.rules.length > 1
? 's'
: ''}.\n` +
`> Are you sure you want to update ${alias} to be a normal alias?\n`
const confirmation = await promptBool(msg, {
trailing: '\n'
})
if (!confirmation) {
info('Aborted')
return exit(1)
}
} catch (err) {
console.log(err)
}
} else {
console.log(
`Overwriting path alias with ${aliasDepl.rules.length} rule${aliasDepl
.rules.length > 1
? 's'
: ''} to be a normal alias.`
)
}
}
let aliasedDeployment = null
let shouldScaleDown = false
if (aliasDepl && depl.scale) {
aliasedDeployment = await this.findDeployment(aliasDepl.deploymentId)
if (
aliasedDeployment &&
aliasedDeployment.scale &&
aliasedDeployment.scale.current >= depl.scale.current &&
(aliasedDeployment.scale.min > depl.scale.min ||
aliasedDeployment.scale.max > depl.scale.max)
) {
shouldScaleDown = true
console.log(
`> Alias ${alias} points to ${chalk.bold(
aliasedDeployment.url
)} (${chalk.bold(aliasedDeployment.scale.current + ' instances')})`
)
// Test if we need to change the scale or just update the rules
console.log(
`> Scaling ${depl.url} to ${chalk.bold(
aliasedDeployment.scale.current + ' instances'
)} atomically` // Not a typo
)
if (depl.scale.current !== aliasedDeployment.scale.current) {
if (depl.scale.max < 1) {
if (this._debug) {
console.log(
'Updating max scale to 1 so that deployment may be unfrozen.'
)
}
await this.setScale(depl.uid, {
min: depl.scale.min,
max: Math.max(aliasedDeployment.scale.max, 1)
})
}
if (depl.scale.current < 1) {
if (this._debug) {
console.log(`> Deployment ${depl.url} is frozen, unfreezing...`)
}
await this.unfreeze(depl)
if (this._debug) {
console.log(
`> Deployment is now unfrozen, scaling it to match current instance count`
)
}
}
// Scale it to current limit
if (depl.scale.current !== aliasedDeployment.scale.current) {
if (this._debug) {
console.log(`> Scaling deployment to match current scale.`)
}
await this.setScale(depl.uid, {
min: aliasedDeployment.scale.current,
max: aliasedDeployment.scale.current
})
}
await scaleInfo(this, depl.url)
if (this._debug) {
console.log(`> Updating scaling rules for deployment.`)
}
}
await this.setScale(depl.uid, {
min: Math.max(aliasedDeployment.scale.min, depl.scale.min),
max: Math.max(aliasedDeployment.scale.max, depl.scale.max)
})
}
}
alias = await this.maybeSetUpDomain(alias, domains, currentTeam, user)
const aliasTime = Date.now()
const newAlias = await this.createAlias(depl, alias)
if (!newAlias) {
throw new Error(
`Unexpected error occurred while setting up alias: ${JSON.stringify(
newAlias
)}`
)
}
const { created, uid } = newAlias
if (created) {
const output = `${chalk.cyan(
'> Success!'
)} ${alias} now points to ${chalk.bold(depl.url)}! ${chalk.grey(
'[' + ms(Date.now() - aliasTime) + ']'
)}`
if (isTTY && clipboard) {
try {
await copy(depl.url)
} catch (err) {
} finally {
console.log(output)
}
} else {
console.log(output)
}
} else {
console.log(
`${chalk.cyan('> Success!')} Alias already exists ${chalk.dim(
`(${uid})`
)}.`
)
}
if (aliasedDeployment && shouldScaleDown) {
const scaleDown = Date.now()
await this.setScale(aliasedDeployment.uid, { min: 0, max: 1 })
console.log(
`> Scaled ${chalk.gray(
aliasedDeployment.url
)} down to 1 instance ${chalk.gray(
'[' + ms(Date.now() - scaleDown) + ']'
)}`
)
}
}
createAlias(depl, alias) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(
`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`
)
}
const res = await this._fetch(`/now/deployments/${depl.uid}/aliases`, {
method: 'POST',
body: { alias }
})
const body = await res.json()
if (this._debug) {
console.timeEnd(
`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`
)
}
// 409 conflict is returned if it already exists
if (res.status === 409) {
return { uid: body.error.uid }
}
// No retry on authorization problems
if (res.status === 403) {
const code = body.error.code
if (code === 'custom_domain_needs_upgrade') {
const err = new Error(
`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray(
'`'
)}${chalk.cyan('now upgrade')}${chalk.gray('`')}.`
)
err.userError = true
return bail(err)
}
if (code === 'alias_in_use') {
const err = new Error(
`The alias you are trying to configure (${chalk.underline(
chalk.bold(alias)
)}) is already in use by a different account.`
)
err.userError = true
return bail(err)
}
if (code === 'forbidden') {
const err = new Error(
'The domain you are trying to use as an alias is already in use by a different account.'
)
err.userError = true
return bail(err)
}
return bail(new Error('Authorization error'))
}
// All other errors
if (body.error) {
const code = body.error.code
if (code === 'deployment_not_found') {
return bail(new Error('Deployment not found'))
}
if (code === 'cert_missing') {
console.log(
`> Provisioning certificate for ${chalk.underline(
chalk.bold(alias)
)}`
)
try {
await this.createCert(alias)
} catch (err) {
// We bail to avoid retrying the whole process
// of aliasing which would involve too many
// retries on certificate provisioning
return bail(err)
}
// Try again, but now having provisioned the certificate
return this.createAlias(depl, alias)
}
if (code === 'cert_expired') {
console.log(
`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`
)
try {
await this.createCert(alias, { renew: true })
} catch (err) {
return bail(err)
}
}
return bail(new Error(body.error.message))
}
// The two expected succesful cods are 200 and 304
if (res.status !== 200 && res.status !== 304) {
throw new Error('Unhandled error')
}
return body
})
}
async setupRecord(domain, name) {
await this.setupDomain(domain)
if (this._debug) {
console.log(`> [debug] Setting up record "${name}" for "${domain}"`)
}
const type = name === '' ? 'ALIAS' : 'CNAME'
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] /domains/${domain}/records #${attempt}`)
}
const res = await this._fetch(`/domains/${domain}/records`, {
method: 'POST',
body: { type, name: name === '' ? name : '*', value: 'alias.zeit.co' }
})
if (this._debug) {
console.timeEnd(`> [debug] /domains/${domain}/records #${attempt}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status !== 200) {
throw new Error(body.error.message)
}
return body
})
}
async maybeSetUpDomain(alias, domains, currentTeam, user) {
const gracefulExit = () => {
this.close()
domains.close()
// eslint-disable-next-line unicorn/no-process-exit
process.exit()
}
// Make alias lowercase
alias = alias.toLowerCase()
// Trim leading and trailing dots
// for example: `google.com.` => `google.com`
alias = alias.replace(/^\.+/, '').replace(/\.+$/, '')
// Evaluate the alias
if (/\./.test(alias)) {
alias = toHost(alias)
} else {
if (this._debug) {
console.log(`> [debug] suffixing \`.now.sh\` to alias ${alias}`)
}
alias = `${alias}.now.sh`
}
if (!domainRegex.test(alias)) {
const err = new Error(`Invalid alias "${alias}"`)
err.userError = true
throw err
}
if (!/\.now\.sh$/.test(alias)) {
console.log(`> ${chalk.bold(chalk.underline(alias))} is a custom domain.`)
let stopSpinner = wait('Fetching domain info')
let elapsed = stamp()
const parsed = publicSuffixList.parse(alias)
const pricePromise = domains.price(parsed.domain).catch(() => {
// Can be safely ignored
})
const canBePurchased = await domains.status(parsed.domain)
const aliasParam = param(parsed.domain)
let price
let period
stopSpinner()
if (canBePurchased) {
try {
const json = await pricePromise
price = json.price
period = json.period
} catch (err) {
// Can be safely ignored
}
}
if (canBePurchased && price && period) {
const periodMsg = `${period}yr${period > 1 ? 's' : ''}`
info(
`The domain ${aliasParam} is ${chalk.italic(
'available'
)} to buy under ${chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)}! ${elapsed()}`
)
const confirmation = await promptBool(
`Buy now for ${chalk.bold(`$${price}`)} (${periodMsg})?`
)
eraseLines(1)
if (!confirmation) {
info('Aborted')
gracefulExit()
}
elapsed = stamp()
stopSpinner = wait('Purchasing')
let domain
try {
domain = await domains.buy(parsed.domain)
} catch (err) {
stopSpinner()
treatBuyError(err)
gracefulExit()
}
stopSpinner()
success(`Domain purchased and created ${uid(domain.uid)} ${elapsed()}`)
stopSpinner = wait('Verifying nameservers')
let domainInfo
try {
domainInfo = await this.setupDomain(parsed.domain)
} catch (err) {
if (this._debug) {
console.log('> [debug] Error while trying to setup the domain', err)
}
}
stopSpinner()
if (!domainInfo.verified) {
const tld = param(`.${parsed.tld}`)
error(
'The nameservers are pending propagation. Please try again shortly'
)
info(
`The ${tld} servers might take some extra time to reflect changes`
)
gracefulExit()
}
}
console.log(
`> Verifying the DNS settings for ${chalk.bold(
chalk.underline(alias)
)} (see ${chalk.underline('https://zeit.world')} for help)`
)
const _domain = publicSuffixList.parse(alias).domain
let _domainInfo
try {
_domainInfo = await this.getDomain(_domain)
} catch (err) {
if (err.status === 404) {
// It's ok if the domain was not found – we'll add it when creating
// the alias
} else {
throw err
}
}
const domainInfo =
_domainInfo && !_domainInfo.error ? _domainInfo : undefined
const { domain, nameservers } = domainInfo
? { domain: _domain }
: await this.getNameservers(alias)
const usingZeitWorld = domainInfo
? !domainInfo.isExternal
: isZeitWorld(nameservers)
let skipDNSVerification = false
if (this._debug) {
if (domainInfo) {
console.log(
`> [debug] Found domain ${domain} with verified:${domainInfo.verified}`
)
} else {
console.log(
`> [debug] Found domain ${domain} and nameservers ${nameservers}`
)
}
}
if (!usingZeitWorld && domainInfo) {
if (domainInfo.verified) {
skipDNSVerification = true
} else if (domainInfo.uid) {
const { verified, created } = await this.setupDomain(domain, {
isExternal: true
})
if (!(created && verified)) {
const e = new Error(
`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.`
)
e.userError = true
throw e
}
console.log(
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
chalk.underline(domain)
)} verified`
)
}
}
try {
if (!skipDNSVerification) {
await this.verifyOwnership(alias)
}
} catch (err) {
if (err.userError) {
// A user error would imply that verification failed
// in which case we attempt to correct the dns
// configuration (if we can!)
try {
if (usingZeitWorld) {
console.log(
`> Detected ${chalk.bold(
chalk.underline('zeit.world')
)} nameservers! Configuring records.`
)
const record = alias.substr(0, alias.length - domain.length)
// Lean up trailing and leading dots
const _record = record.replace(/^\./, '').replace(/\.$/, '')
const _domain = domain.replace(/^\./, '').replace(/\.$/, '')
if (_record === '') {
await this.setupRecord(_domain, '*')
}
await this.setupRecord(_domain, _record)
this.recordSetup = true
console.log('> DNS Configured! Verifying propagation…')
try {
await this.retry(() => this.verifyOwnership(alias), {
retries: 10,
maxTimeout: 8000
})
} catch (err2) {
const e = new Error(
'> We configured the DNS settings for your alias, but we were unable to ' +
"verify that they've propagated. Please try the alias again later."
)
e.userError = true
throw e
}
} else {
console.log(
`> Resolved IP: ${err.ip
? `${chalk.underline(err.ip)} (unknown)`
: chalk.dim('none')}`
)
console.log(
`> Nameservers: ${nameservers && nameservers.length
? nameservers.map(ns => chalk.underline(ns)).join(', ')
: chalk.dim('none')}`
)
throw err
}
} catch (e) {
if (e.userError) {
throw e
}
throw err
}
} else {
throw err
}
}
if (!usingZeitWorld && !skipDNSVerification) {
if (this._debug) {
console.log(
`> [debug] Trying to register a non-ZeitWorld domain ${domain} for the current user`
)
}
const { uid, verified, created } = await this.setupDomain(domain, {
isExternal: true
})
if (!(created && verified)) {
const e = new Error(
`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.`
)
e.userError = true
throw e
}
console.log(
`${chalk.cyan('> Success!')} Domain ${chalk.bold(
chalk.underline(domain)
)} ${chalk.dim(`(${uid})`)} added`
)
}
console.log(`> Verification ${chalk.bold('OK')}!`)
}
return alias
}
verifyOwnership(domain) {
return this.retry(
async bail => {
const targets = await resolve4('alias.zeit.co')
if (targets.length <= 0) {
return bail(new Error('Unable to resolve alias.zeit.co'))
}
let ips = []
try {
ips = await resolve4(domain)
} catch (err) {
if (
err.code === 'ENODATA' ||
err.code === 'ESERVFAIL' ||
err.code === 'ENOTFOUND'
) {
// Not errors per se, just absence of records
if (this._debug) {
console.log(`> [debug] No records found for "${domain}"`)
}
const err = new Error(DOMAIN_VERIFICATION_ERROR)
err.userError = true
return bail(err)
}
throw err
}
if (ips.length <= 0) {
const err = new Error(DOMAIN_VERIFICATION_ERROR)
err.userError = true
return bail(err)
}
for (const ip of ips) {
if (targets.indexOf(ip) === -1) {
const err = new Error(
`The domain ${domain} has an A record ${chalk.bold(
ip
)} that doesn't resolve to ${chalk.bold(
chalk.underline('alias.zeit.co')
)}.\n> ` + DOMAIN_VERIFICATION_ERROR
)
err.ip = ip
err.userError = true
return bail(err)
}
}
},
{ retries: 5 }
)
}
}

138
lib/build-logger.js

@ -1,138 +0,0 @@
// Native
const EventEmitter = require('events')
// Packages
const io = require('socket.io-client')
const chalk = require('chalk')
const { compare, deserialize } = require('./logs')
module.exports = class Logger extends EventEmitter {
constructor(host, token, { debug = false, quiet = false } = {}) {
super()
this.host = host
this.token = token
this.debug = debug
this.quiet = quiet
// ReadyState
this.building = false
this.socket = io(`https://io.now.sh/states?host=${host}&v=2`)
this.socket.once('error', this.onSocketError.bind(this))
this.socket.on('auth', this.onAuth.bind(this))
this.socket.on('state', this.onState.bind(this))
this.socket.on('logs', this.onLog.bind(this))
this.socket.on('backend', this.onComplete.bind(this))
// Log buffer
this.buf = []
this.printed = new Set()
}
onAuth(callback) {
if (this.debug) {
console.log('> [debug] authenticate')
}
callback(this.token)
}
onState(state) {
// Console.log(state)
if (!state.id) {
console.error('> Deployment not found')
this.emit('error')
return
}
if (state.error) {
this.emit('error', state)
return
}
if (state.backend) {
this.onComplete()
return
}
if (state.logs) {
state.logs.forEach(this.onLog, this)
}
}
onLog(log) {
if (!this.building) {
if (!this.quiet) {
console.log('> Building')
}
this.building = true
}
if (this.quiet) {
return
}
log = deserialize(log)
const timer = setTimeout(() => {
this.buf.sort((a, b) => compare(a.log, b.log))
const idx = this.buf.findIndex(b => b.log.id === log.id) + 1
for (const b of this.buf.slice(0, idx)) {
clearTimeout(b.timer)
this.printLog(b.log)
}
this.buf = this.buf.slice(idx)
}, 500)
this.buf.push({ log, timer })
}
onComplete() {
this.socket.disconnect()
if (this.building) {
this.building = false
}
this.buf.sort((a, b) => compare(a.log, b.log))
// Flush all buffer
for (const b of this.buf) {
clearTimeout(b.timer)
this.printLog(b.log)
}
this.buf = []
this.emit('close')
}
onSocketError(err) {
if (this.debug) {
console.log(`> [debug] Socket error ${err}\n${err.stack}`)
}
}
printLog(log) {
if (this.printed.has(log.id)) return
this.printed.add(log.id)
const data = log.object ? JSON.stringify(log.object) : log.text
if (log.type === 'command') {
console.log(`${chalk.gray('>')}${data}`)
} else if (log.type === 'stderr') {
data.split('\n').forEach(v => {
if (v.length > 0) {
console.error(chalk.gray(`> ${v}`))
}
})
} else if (log.type === 'stdout') {
data.split('\n').forEach(v => {
if (v.length > 0) {
console.log(`${chalk.gray('>')} ${v}`)
}
})
}
}
}

101
lib/certs.js

@ -1,101 +0,0 @@
// Ours
const Now = require('../lib')
module.exports = class Certs extends Now {
ls() {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET now/certs`)
}
const res = await this._fetch('/now/certs')
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET now/certs`)
}
const body = await res.json()
return body.certs
})
}
create(cn) {
return this.createCert(cn)
}
renew(cn) {
return this.createCert(cn, { renew: true })
}
put(cn, crt, key, ca) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} PUT now/certs`)
}
const res = await this._fetch('/now/certs', {
method: 'PUT',
body: {
domains: [cn],
ca,
cert: crt,
key
}
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PUT now/certs`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status !== 200) {
if (res.status === 404 || res.status === 400) {
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message)
}
return body
})
}
delete(cn) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} DELETE now/certs/${cn}`)
}
const res = await this._fetch(`/now/certs/${cn}`, { method: 'DELETE' })
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} DELETE now/certs/${cn}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status !== 200) {
if (res.status === 404 || res.status === 400) {
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message)
}
return body
})
}
}

112
lib/cfg.js

@ -1,112 +0,0 @@
// Native
const { homedir } = require('os')
const path = require('path')
// Packages
const fs = require('fs-extra')
const ms = require('ms')
// Ours
const { get: getUser } = require('./user')
// `8h` is arbitrarily used based on the average sleep time
const TTL = ms('8h')
let file = process.env.NOW_JSON
? path.resolve(process.env.NOW_JSON)
: path.resolve(homedir(), '.now.json')
function setConfigFile(nowjson) {
file = path.resolve(nowjson)
}
function save(data) {
fs.writeFileSync(file, JSON.stringify(data, null, 2))
}
/**
* Reads the config file
*
* Optionally, always queries the API to get the user info even if the
* config file is not present
*
* @param {Boolean} force [false] Queries the API even if the config
* file is not present. If `true`, `token`
* *must* be specified
* @param {String} token Will be used to autenticate in the API
if needed
* @param {String} apiUrl URL of the API to be used
* @return {Object}
*/
async function read({ force = false, token, apiUrl } = {}) {
let existing = {}
try {
existing = fs.readFileSync(file, 'utf8')
existing = JSON.parse(existing)
} catch (err) {}
// Will happen if `force`d or if `--token` is used and it's different from
// The one that's stored (which can be `undefined`)
if ((force && token) || (token && token !== existing.token)) {
const user = await getUser({ token, apiUrl })
if (user) {
return {
token,
user: {
uid: user.uid,
username: user.username,
email: user.email
},
currentTeam: existing.currentTeam
}
}
return {}
}
if (!existing.token) {
return {}
}
if (!existing.lastUpdate || Date.now() - existing.lastUpdate > TTL) {
// TODO: update `teams` info
const token = existing.token
const user = await getUser({ token })
if (user) {
existing.user = user
existing.lastUpdate = Date.now()
save(existing)
}
}
return existing
}
/**
* Merges the `data` object onto the
* JSON config stored in `.now.json`.
*
* (atomic)
* @param {Object} data
*/
async function merge(data) {
const cfg = Object.assign({}, await read(), data)
save(cfg)
}
// Removes a key from the config and store the result
async function remove(key) {
const cfg = await read()
delete cfg[key]
fs.writeFileSync(file, JSON.stringify(cfg, null, 2))
}
// We need to remove the config file when running `now logout`
const removeFile = async () => fs.remove(file)
module.exports = {
setConfigFile,
read,
merge,
remove,
removeFile
}

73
lib/credit-cards.js

@ -1,73 +0,0 @@
const stripe = require('stripe')('pk_live_alyEi3lN0kSwbdevK0nrGwTw')
const Now = require('../lib')
module.exports = class CreditCards extends Now {
async ls() {
const res = await this._fetch('/cards')
const body = await res.json()
if (res.status !== 200) {
const e = new Error(body.error.message)
e.code = body.error.code
throw e
}
return body
}
async setDefault(cardId) {
await this._fetch('/cards/default', {
method: 'PUT',
body: { cardId }
})
return true
}
async rm(cardId) {
await this._fetch(`/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('/cards', {
method: 'POST',
body: { stripeToken }
})
const body = await res.json()
if (body && body.id) {
resolve({
last4: body.last4
})
} else if (body.error && body.error.message) {
reject(new Error(body.error.message))
} else {
reject(new Error('Unknown error'))
}
} catch (err) {
reject(new Error(err.message || 'Unknown error'))
}
})
}
}

15
lib/dns.js

@ -1,15 +0,0 @@
// Packages
const dns = require('dns')
function resolve4(host) {
return new Promise((resolve, reject) => {
return dns.resolve4(host, (err, answer) => {
if (err) {
return reject(err)
}
resolve(answer)
})
})
}
module.exports = resolve4

148
lib/domain-records.js

@ -1,148 +0,0 @@
// Ours
const Now = require('../lib')
module.exports = class DomainRecords extends Now {
async getRecord(id) {
const all = (await this.ls()).entries()
for (const [domain, records] of all) {
for (const record of records) {
if (record.id === id) {
record.domain = domain
return record
}
}
}
return null
}
async ls(dom) {
let domains
if (dom) {
domains = [dom]
} else {
const ret = await this.listDomains()
domains = ret
.filter(x => !x.isExternal)
.map(x => x.name)
.sort((a, b) => a.localeCompare(b))
}
const records = new Map()
const bodies = []
for (const domain of domains) {
bodies.push(
this.retry(async (bail, attempt) => {
const url = `/domains/${domain}/records`
if (this._debug) {
console.time(`> [debug] #${attempt} GET ${url}`)
}
const res = await this._fetch(url)
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET ${url}`)
}
const body = await res.json()
if (res.status === 404 && body.code === 'not_found') {
return bail(new Error(body.message))
} else if (res.status !== 200) {
throw new Error(`Failed to get DNS records for domain "${domain}"`)
}
return body
})
)
}
const domainList = await Promise.all(bodies)
for (const body of domainList) {
const index = domainList.indexOf(body)
records.set(
domains[index],
body.records.sort((a, b) => a.slug.localeCompare(b.slug))
)
}
return records
}
create(domain, data) {
const url = `/domains/${domain}/records`
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST ${url}`)
}
const res = await this._fetch(url, {
method: 'POST',
body: data
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST ${url}`)
}
const body = await res.json()
if (res.status === 400) {
return bail(
new Error(body.error ? body.error.message : 'Unknown error')
)
} else if (res.status === 403) {
const err = new Error(`Not authorized to access the domain "${domain}"`)
err.userError = true
return bail(err)
} else if (res.status === 404) {
let err
if (body.error.code === 'not_found') {
err = new Error(`The domain "${domain}" was not found`)
err.userError = true
return bail(err)
}
}
if (res.status !== 200) {
throw new Error(body.error ? body.error.message : 'Unknown error')
}
return body
})
}
delete(domain, recordId) {
const url = `/domains/${domain}/records/${recordId}`
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} DELETE ${url}`)
}
const res = await this._fetch(url, { method: 'DELETE' })
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} DELETE ${url}`)
}
const body = await res.json()
if (res.status === 403) {
const err = new Error(`Not authorized to access domain ${domain}`)
err.userError = true
return bail(err)
} else if (res.status === 404) {
let err
if (body.error.code === 'not_found') {
err = new Error(body.error.message)
err.userError = true
return bail(err)
}
}
if (res.status !== 200) {
throw new Error(body.error ? body.error.message : 'Unkown error')
}
return body
})
}
}

264
lib/domains.js

@ -1,264 +0,0 @@
// Native
const { encode: encodeQuery } = require('querystring')
// Packages
const chalk = require('chalk')
// Ours
const Now = require('../lib')
const isZeitWorld = require('./is-zeit-world')
const { DNS_VERIFICATION_ERROR } = require('./errors')
const cmd = require('./utils/output/param')
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/
module.exports = class Domains extends Now {
async ls() {
return this.listDomains()
}
async rm(domain) {
// Remove aliases
for (const aliasId of domain.aliases) {
this.retry(async (bail, attempt) => {
const label = `> [debug] #${attempt} DELETE now/aliases/${aliasId}`
if (this._debug) {
console.time(label)
}
const res = await this._fetch(`/now/aliases/${aliasId}`, {
method: 'DELETE'
})
if (this._debug) {
console.timeEnd(label)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
if (res.status !== 200) {
const body = await res.json()
throw new Error(body.error.message)
}
})
}
// Remove certs
for (const cn of domain.certs) {
this.retry(async (bail, attempt) => {
const label = `> [debug] #${attempt} DELETE now/certs/${cn}`
if (this._debug) {
console.time(label)
}
const res = await this._fetch(`/now/certs/${cn}`, { method: 'DELETE' })
if (this._debug) {
console.timeEnd(label)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
if (res.status !== 200) {
const body = await res.json()
throw new Error(body.error.message)
}
})
}
// Remove the domain
const name = domain.name
return this.retry(async (bail, attempt) => {
const label = `> [debug] #${attempt} DELETE /domains/${name}`
if (this._debug) {
console.time(label)
}
const res = await this._fetch(`/domains/${name}`, { method: 'DELETE' })
if (this._debug) {
console.timeEnd(label)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
if (res.status === 409) {
const body = await res.json()
return bail(new Error(body.error.message))
}
if (res.status !== 200) {
const body = await res.json()
throw new Error(body.error.message)
}
})
}
async add(domain, skipVerification, isExternal) {
if (!domainRegex.test(domain)) {
const err = new Error(
`The supplied value ${chalk.bold(`"${domain}"`)} is not a valid domain.`
)
err.userError = true
throw err
}
if (skipVerification || isExternal) {
return this.setupDomain(domain, { isExternal })
}
let ns
try {
console.log('> Verifying nameservers…')
const res = await this.getNameservers(domain)
ns = res.nameservers
} catch (err) {
const err2 = new Error(
`Unable to fetch nameservers for ${chalk.underline(
chalk.bold(domain)
)}.`
)
err2.userError = true
throw err2
}
if (isZeitWorld(ns)) {
console.log(`> Verification ${chalk.bold('OK')}!`)
return this.setupDomain(domain)
}
if (this._debug) {
console.log(
`> [debug] Supplied domain "${domain}" has non-zeit nameservers`
)
}
const err3 = new Error(DNS_VERIFICATION_ERROR)
err3.userError = true
throw err3
}
async status(name) {
if (!name) {
throw new Error('`domain` is not defined')
}
const query = encodeQuery({ name })
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/status?${query}`)
}
const res = await this._fetch(`/domains/status?${query}`)
const json = await res.json()
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/status?${query}`)
}
return json.available
})
}
async price(name) {
if (!name) {
throw new Error('`domain` is not defined')
}
const query = encodeQuery({ name })
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/price?${query}`)
}
const res = await this._fetch(`/domains/price?${query}`)
const json = await res.json()
if (res.status === 400) {
const e = new Error(json.error.message)
e.code = json.error.code
return bail(e)
}
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/price?${query}`)
}
return json
})
}
async buy({ name, coupon }) {
if (!name) {
throw new Error('`name` is not defined')
}
const body = JSON.stringify({ name, coupon })
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/buy`)
}
const res = await this._fetch(`/domains/buy`, {
method: 'POST',
body
})
const json = await res.json()
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/buy`)
}
if ([400, 403, 500, 503].includes(res.status)) {
const e = new Error()
e.code = json.error.code
if (json.error.code === 'source_not_found') {
e.message = `No credit cards found – please run ${cmd('now cc add')}`
} else {
e.message = json.error.message
}
return bail(e)
}
return json
})
}
async coupon(coupon) {
if (!coupon) {
throw new Error('`coupon` is not defined')
}
const query = encodeQuery({ coupon })
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/buy?${query}`)
}
const res = await this._fetch(`/domains/buy?${query}`)
const json = await res.json()
if (res.status !== 200) {
const e = new Error(json.error.message)
e.code = json.error.code
return bail(e)
}
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/buy?${query}`)
}
return json
})
}
}

85
lib/error.js

@ -1,85 +0,0 @@
// Packages
const ms = require('ms')
const chalk = require('chalk')
const error = require('./utils/output/error')
const info = require('./utils/output/info')
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.'
)
} else if (err.status === 429) {
if (err.retryAfter === 'never') {
error(err.message)
} else if (err.retryAfter === null) {
error('Rate limit exceeded error. Please try later.')
} else {
error(
'Rate limit exceeded error. Try again in ' +
ms(err.retryAfter * 1000, { long: true }) +
', or upgrade your account by running ' +
`${chalk.gray('`')}${chalk.cyan('now upgrade')}${chalk.gray('`')}`
)
}
} else if (err.userError) {
error(err.message)
} else if (err.status === 500) {
error('Unexpected server error. Please retry.')
} else if (err.code === 'USER_ABORT') {
info('Aborted')
} else {
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
}

30
lib/errors.js

@ -1,30 +0,0 @@
// Packages
const chalk = require('chalk')
const DNS_VERIFICATION_ERROR = `Please make sure that your nameservers point to ${chalk.underline(
'zeit.world'
)}.
> Examples: (full list at ${chalk.underline('https://zeit.world')})
> ${chalk.gray('-')} ${chalk.underline('california.zeit.world')} ${chalk.dim(
'173.255.215.107'
)}
> ${chalk.gray('-')} ${chalk.underline('newark.zeit.world')} ${chalk.dim(
'173.255.231.87'
)}
> ${chalk.gray('-')} ${chalk.underline('london.zeit.world')} ${chalk.dim(
'178.62.47.76'
)}
> ${chalk.gray('-')} ${chalk.underline('singapore.zeit.world')} ${chalk.dim(
'119.81.97.170'
)}`
const DOMAIN_VERIFICATION_ERROR =
DNS_VERIFICATION_ERROR +
`\n> Alternatively, ensure it resolves to ${chalk.underline(
'alias.zeit.co'
)} via ${chalk.dim('CNAME')} / ${chalk.dim('ALIAS')}.`
module.exports = {
DNS_VERIFICATION_ERROR,
DOMAIN_VERIFICATION_ERROR
}

385
lib/get-files.js

@ -1,385 +0,0 @@
// Native
const { resolve } = require('path')
// Packages
const flatten = require('arr-flatten')
const unique = require('array-unique')
const ignore = require('ignore')
const _glob = require('glob')
const { stat, readdir, readFile } = require('fs-extra')
// Ours
const IGNORED = require('./ignored')
const glob = async function(pattern, options) {
return new Promise((resolve, reject) => {
_glob(pattern, options, (error, files) => {
if (error) {
reject(error)
} else {
resolve(files)
}
})
})
}
/**
* Remove leading `./` from the beginning of ignores
* because our parser doesn't like them :|
*/
const clearRelative = function(str) {
return str.replace(/(\n|^)\.\//g, '$1')
}
/**
* Returns the contents of a file if it exists.
*
* @return {String} results or `''`
*/
const maybeRead = async function(path, default_ = '') {
try {
return await readFile(path, 'utf8')
} catch (err) {
return default_
}
}
/**
* Transform relative paths into absolutes,
* and maintains absolutes as such.
*
* @param {String} maybe relative path
* @param {String} parent full path
*/
const asAbsolute = function(path, parent) {
if (path[0] === '/') {
return path
}
return resolve(parent, path)
}
/**
* Returns a list of files in the given
* directory that are subject to be
* synchronized for static deployments.
*
* @param {String} full path to directory
* @param {Object} options:
* - `limit` {Number|null} byte limit
* - `debug` {Boolean} warn upon ignore
* @return {Array} comprehensive list of paths to sync
*/
async function staticFiles(
path,
nowConfig = {},
{ limit = null, hasNowJson = false, debug = false } = {}
) {
const whitelist = nowConfig.files
// The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files
const search_ = whitelist || ['.']
// Convert all filenames into absolute paths
const search = Array.prototype.concat.apply(
[],
await Promise.all(
search_.map(file => glob(file, { cwd: path, absolute: true, dot: true }))
)
)
// Compile list of ignored patterns and files
const gitIgnore = await maybeRead(resolve(path, '.gitignore'))
const filter = ignore()
.add(IGNORED + '\n' + clearRelative(gitIgnore))
.createFilter()
const prefixLength = path.length + 1
// The package.json `files` whitelist still
// honors npmignores: https://docs.npmjs.com/files/package.json#files
// but we don't ignore if the user is explicitly listing files
// under the now namespace, or using files in combination with gitignore
const accepts = file => {
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true
}
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file)
}
return accepted
}
// Locate files
if (debug) {
console.time(`> [debug] locating files ${path}`)
}
const files = await explode(search, {
accepts,
limit,
debug
})
if (debug) {
console.timeEnd(`> [debug] locating files ${path}`)
}
if (hasNowJson) {
files.push(asAbsolute('now.json', path))
}
// Get files
return unique(files)
}
/**
* Returns a list of files in the given
* directory that are subject to be
* synchronized for npm.
*
* @param {String} full path to directory
* @param {String} contents of `package.json` to avoid lookup
* @param {Object} options:
* - `limit` {Number|null} byte limit
* - `debug` {Boolean} warn upon ignore
* @return {Array} comprehensive list of paths to sync
*/
async function npm(
path,
pkg = {},
nowConfig = {},
{ limit = null, hasNowJson = false, debug = false } = {}
) {
const whitelist = nowConfig.files || pkg.files || (pkg.now && pkg.now.files)
// The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files
const search_ = whitelist || ['.']
// Convert all filenames into absolute paths
const search = Array.prototype.concat.apply(
[],
await Promise.all(
search_.map(file => glob(file, { cwd: path, absolute: true, dot: true }))
)
)
// Compile list of ignored patterns and files
const npmIgnore = await maybeRead(resolve(path, '.npmignore'), null)
const gitIgnore =
npmIgnore === null ? await maybeRead(resolve(path, '.gitignore')) : null
const filter = ignore()
.add(
IGNORED + '\n' + clearRelative(npmIgnore === null ? gitIgnore : npmIgnore)
)
.createFilter()
const prefixLength = path.length + 1
// The package.json `files` whitelist still
// honors npmignores: https://docs.npmjs.com/files/package.json#files
// but we don't ignore if the user is explicitly listing files
// under the now namespace, or using files in combination with gitignore
const overrideIgnores =
(pkg.now && pkg.now.files) ||
nowConfig.files ||
(gitIgnore !== null && pkg.files)
const accepts = overrideIgnores
? () => true
: file => {
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true
}
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file)
}
return accepted
}
// Locate files
if (debug) {
console.time(`> [debug] locating files ${path}`)
}
const files = await explode(search, {
accepts,
limit,
debug
})
if (debug) {
console.timeEnd(`> [debug] locating files ${path}`)
}
// Always include manifest as npm does not allow ignoring it
// source: https://docs.npmjs.com/files/package.json#files
files.push(asAbsolute('package.json', path))
if (hasNowJson) {
files.push(asAbsolute('now.json', path))
}
// Get files
return unique(files)
}
/**
* Returns a list of files in the given
* directory that are subject to be
* sent to docker as build context.
*
* @param {String} full path to directory
* @param {String} contents of `Dockerfile`
* @param {Object} options:
* - `limit` {Number|null} byte limit
* - `debug` {Boolean} warn upon ignore
* @return {Array} comprehensive list of paths to sync
*/
async function docker(
path,
nowConfig = {},
{ limit = null, hasNowJson = false, debug = false } = {}
) {
const whitelist = nowConfig.files
// Base search path
// the now.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files
const search_ = whitelist || ['.']
// Convert all filenames into absolute paths
const search = search_.map(file => asAbsolute(file, path))
// Compile list of ignored patterns and files
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null)
const filter = ignore()
.add(
IGNORED +
'\n' +
clearRelative(
dockerIgnore === null
? await maybeRead(resolve(path, '.gitignore'))
: dockerIgnore
)
)
.createFilter()
const prefixLength = path.length + 1
const accepts = function(file) {
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true
}
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file)
}
return accepted
}
// Locate files
if (debug) {
console.time(`> [debug] locating files ${path}`)
}
const files = await explode(search, { accepts, limit, debug })
if (debug) {
console.timeEnd(`> [debug] locating files ${path}`)
}
// Always include manifest as npm does not allow ignoring it
// source: https://docs.npmjs.com/files/package.json#files
files.push(asAbsolute('Dockerfile', path))
if (hasNowJson) {
files.push(asAbsolute('now.json', path))
}
// Get files
return unique(files)
}
/**
* Explodes directories into a full list of files.
* Eg:
* in: ['/a.js', '/b']
* out: ['/a.js', '/b/c.js', '/b/d.js']
*
* @param {Array} of {String}s representing paths
* @param {Array} of ignored {String}s.
* @param {Object} options:
* - `limit` {Number|null} byte limit
* - `debug` {Boolean} warn upon ignore
* @return {Array} of {String}s of full paths
*/
async function explode(paths, { accepts, debug }) {
const list = async file => {
let path = file
let s
if (!accepts(file)) {
return null
}
try {
s = await stat(path)
} catch (e) {
// In case the file comes from `files`
// and it wasn't specified with `.js` by the user
path = file + '.js'
try {
s = await stat(path)
} catch (e2) {
if (debug) {
console.log('> [debug] ignoring invalid file "%s"', file)
}
return null
}
}
if (s.isDirectory()) {
const all = await readdir(file)
/* eslint-disable no-use-before-define */
return many(all.map(subdir => asAbsolute(subdir, file)))
/* eslint-enable no-use-before-define */
} else if (!s.isFile()) {
if (debug) {
console.log('> [debug] ignoring special file "%s"', file)
}
return null
}
return path
}
const many = all => Promise.all(all.map(file => list(file)))
return flatten(await many(paths)).filter(v => v !== null)
}
module.exports = {
npm,
docker,
staticFiles
}

221
lib/git.js

@ -1,221 +0,0 @@
// Native
const path = require('path')
const url = require('url')
const childProcess = require('child_process')
// Packages
const fs = require('fs-extra')
const download = require('download')
const tmp = require('tmp-promise')
const isURL = require('is-url')
const cloneRepo = (parts, tmpDir, { ssh }) =>
new Promise((resolve, reject) => {
let host
switch (parts.type) {
case 'GitLab':
host = `gitlab.com`
break
case 'Bitbucket':
host = `bitbucket.org`
break
default:
host = `github.com`
}
const url = ssh
? `git@${host}:${parts.main}`
: `https://${host}/${parts.main}`
const ref = parts.ref || (parts.type === 'Bitbucket' ? 'default' : 'master')
const cmd = `git clone ${url} --single-branch ${ref}`
childProcess.exec(cmd, { cwd: tmpDir.path }, (err, stdout) => {
if (err) {
reject(err)
}
resolve(stdout)
})
})
const renameRepoDir = async (pathParts, tmpDir) => {
const tmpContents = await fs.readdir(tmpDir.path)
const oldTemp = path.join(tmpDir.path, tmpContents[0])
const newTemp = path.join(tmpDir.path, pathParts.main.replace('/', '-'))
await fs.rename(oldTemp, newTemp)
tmpDir.path = newTemp
return tmpDir
}
const capitalizePlatform = name => {
const names = {
github: 'GitHub',
gitlab: 'GitLab',
bitbucket: 'Bitbucket'
}
return names[name]
}
const splittedURL = fullURL => {
const parsedURL = url.parse(fullURL)
const pathParts = parsedURL.path.split('/')
pathParts.shift()
// Set path to repo...
const main = pathParts[0] + '/' + pathParts[1]
// ...and then remove it from the parts
pathParts.splice(0, 2)
// Assign Git reference
let ref = pathParts.length >= 2 ? pathParts[1] : ''
// Firstly be sure that we haven know the ref type
if (pathParts[0]) {
// Then shorten the SHA of the commit
if (pathParts[0] === 'commit' || pathParts[0] === 'commits') {
ref = ref.substring(0, 7)
}
}
// We're deploying master by default,
// so there's no need to indicate it explicitly
if (ref === 'master') {
ref = ''
}
return {
main,
ref,
type: capitalizePlatform(parsedURL.host.split('.')[0])
}
}
const gitPathParts = main => {
let ref = ''
if (isURL(main)) {
return splittedURL(main)
}
if (main.split('/')[1].includes('#')) {
const parts = main.split('#')
ref = parts[1]
main = parts[0]
}
return {
main,
ref,
type: capitalizePlatform('github')
}
}
const downloadRepo = async repoPath => {
const pathParts = gitPathParts(repoPath)
const tmpDir = await tmp.dir({
// We'll remove it manually once deployment is done
keep: true,
// Recursively remove directory when calling respective method
unsafeCleanup: true
})
let gitInstalled = true
try {
await cloneRepo(pathParts, tmpDir)
} catch (err) {
try {
await cloneRepo(pathParts, tmpDir, { ssh: true })
} catch (err) {
gitInstalled = false
}
}
if (gitInstalled) {
const renaming = await renameRepoDir(pathParts, tmpDir)
return renaming
}
let url
switch (pathParts.type) {
case 'GitLab': {
const ref = pathParts.ref ? `?ref=${pathParts.ref}` : ''
url = `https://gitlab.com/${pathParts.main}/repository/archive.tar` + ref
break
}
case 'Bitbucket':
url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref ||
'default'}.zip`
break
default:
url = `https://api.github.com/repos/${pathParts.main}/tarball/${pathParts.ref}`
}
try {
await download(url, tmpDir.path, {
extract: true
})
} catch (err) {
tmpDir.cleanup()
return false
}
const renaming = await renameRepoDir(pathParts, tmpDir)
return renaming
}
const isRepoPath = path => {
if (!path) {
return false
}
const allowedHosts = ['github.com', 'gitlab.com', 'bitbucket.org']
if (isURL(path)) {
const urlParts = url.parse(path)
const slashSplitted = urlParts.path.split('/').filter(n => n)
const notBare = slashSplitted.length >= 2
if (allowedHosts.includes(urlParts.host) && notBare) {
return true
}
const err = new Error(`Host "${urlParts.host}" is unsupported.`)
err.code = 'INVALID_URL'
err.userError = true
throw err
}
return /[^\s\\]\/[^\s\\]/g.test(path)
}
const fromGit = async (path, debug) => {
let tmpDir = false
try {
tmpDir = await downloadRepo(path)
} catch (err) {
if (debug) {
console.log(`Could not download "${path}" repo from GitHub`)
}
}
return tmpDir
}
module.exports = {
gitPathParts,
isRepoPath,
fromGit
}

44
lib/hash.js

@ -1,44 +0,0 @@
// Native
const { createHash } = require('crypto')
// Packages
const { readFile } = require('fs-extra')
/**
* Computes hashes for the contents of each file given.
*
* @param {Array} of {String} full paths
* @return {Map}
*/
async function hashes(files) {
const map = new Map()
await Promise.all(
files.map(async name => {
const data = await readFile(name)
const h = hash(data)
const entry = map.get(h)
if (entry) {
entry.names.push(name)
} else {
map.set(hash(data), { names: [name], data })
}
})
)
return map
}
/**
* Computes a hash for the given buf.
*
* @param {Buffer} file data
* @return {String} hex digest
*/
function hash(buf) {
return createHash('sha1').update(buf).digest('hex')
}
module.exports = hashes

17
lib/ignored.js

@ -1,17 +0,0 @@
// Base `.gitignore` to which we add entries
// supplied by the user
module.exports = `.hg
.git
.gitmodules
.svn
.npmignore
.dockerignore
.gitignore
.*.swp
.DS_Store
.wafpicke-*
.lock-wscript
npm-debug.log
config.gypi
node_modules
CVS`

5
lib/indent.js

@ -1,5 +0,0 @@
function indent(text, n) {
return text.split('\n').map(l => ' '.repeat(n) + l).join('\n')
}
module.exports = indent

1029
lib/index.js

File diff suppressed because it is too large

32
lib/is-zeit-world.js

@ -1,32 +0,0 @@
/**
* List of `zeit.world` nameservers
*/
const nameservers = new Set([
'california.zeit.world',
'london.zeit.world',
'newark.zeit.world',
'sydney.zeit.world',
'iowa.zeit.world',
'dallas.zeit.world',
'amsterdam.zeit.world',
'paris.zeit.world',
'frankfurt.zeit.world',
'singapore.zeit.world'
])
/**
* Given an array of nameservers (ie: as returned
* by `resolveNs` from Node, assert that they're
* zeit.world's.
*/
function isZeitWorld(ns) {
return (
ns.length > 1 &&
ns.every(host => {
return nameservers.has(host)
})
)
}
module.exports = isZeitWorld

141
lib/login.js

@ -1,141 +0,0 @@
// Native
const os = require('os')
// Packages
const { stringify: stringifyQuery } = require('querystring')
const chalk = require('chalk')
const fetch = require('node-fetch')
const { validate } = require('email-validator')
const readEmail = require('email-prompt')
const ora = require('ora')
// Ours
const pkg = require('./pkg')
const ua = require('./ua')
const cfg = require('./cfg')
async function getVerificationData(url, email) {
const tokenName = `Now CLI ${os.platform()}-${os.arch()} ${pkg.version} (${os.hostname()})`
const data = JSON.stringify({ email, tokenName })
const res = await fetch(`${url}/now/registration`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data),
'User-Agent': ua
},
body: data
})
const body = await res.json()
if (res.status !== 200) {
throw new Error(
`Verification error: ${res.status}${JSON.stringify(body)}`
)
}
return body
}
async function verify(url, email, verificationToken) {
const query = {
email,
token: verificationToken
}
const res = await fetch(
`${url}/now/registration/verify?${stringifyQuery(query)}`,
{
headers: { 'User-Agent': ua }
}
)
const body = await res.json()
return body.token
}
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
async function register(url, { retryEmail = false } = {}) {
let email
try {
email = await readEmail({ invalid: retryEmail })
} catch (err) {
process.stdout.write('\n')
throw err
}
process.stdout.write('\n')
if (!validate(email)) {
return register(url, { retryEmail: true })
}
const { token, securityCode } = await getVerificationData(url, email)
console.log(
`> Please follow the link sent to ${chalk.bold(email)} to log in.`
)
if (securityCode) {
console.log(
`> Verify that the provided security code in the email matches ${chalk.cyan(
chalk.bold(securityCode)
)}.`
)
}
process.stdout.write('\n')
const spinner = ora({
text: 'Waiting for confirmation...'
}).start()
let final
/* eslint-disable no-await-in-loop */
do {
await sleep(2500)
try {
final = await verify(url, email, token)
} catch (err) {}
} while (!final)
/* eslint-enable no-await-in-loop */
let user
try {
user = (await (await fetch(`${url}/www/user`, {
headers: {
Authorization: `Bearer ${final}`
}
})).json()).user
} catch (err) {
spinner.stop()
throw new Error(`Couldn't retrieve user details ${err.message}`)
}
spinner.text = 'Confirmed email address!'
spinner.stopAndPersist('✔')
process.stdout.write('\n')
return {
token: final,
user: {
uid: user.uid,
username: user.username,
email: user.email
},
lastUpdate: Date.now()
}
}
module.exports = async function(url) {
const loginData = await register(url)
await cfg.merge(loginData)
await cfg.remove('currentTeam') // Make sure to logout the team too
await cfg.remove('email') // Remove old schema from previus versions
return loginData.token
}

14
lib/logs.js

@ -1,14 +0,0 @@
exports.compare = function(a, b) {
return (
a.serial.localeCompare(b.serial) ||
// For the case serials are a same value on old logs
a.created.getTime() - b.created.getTime()
)
}
exports.deserialize = function(log) {
return Object.assign({}, log, {
date: new Date(log.date),
created: new Date(log.created)
})
}

13
lib/pkg.js

@ -1,13 +0,0 @@
/* eslint-disable import/no-unresolved */
const path = require('path')
const pkg = require('../package.json')
try {
const distDir = path.dirname(process.execPath)
pkg._npmPkg = require(path.join(distDir, '../../package.json'))
} catch (err) {
pkg._npmPkg = null
}
module.exports = pkg

57
lib/plans.js

@ -1,57 +0,0 @@
const ms = require('ms')
const Now = require('../lib')
async function parsePlan(json) {
const { subscription } = json
let id
let until
let name
if (subscription) {
const planItems = subscription.items.data
const mainPlan = planItems.find(d => d.plan.metadata.is_main_plan === '1')
if (mainPlan) {
id = mainPlan.plan.id
name = mainPlan.plan.name
if (subscription.cancel_at_period_end) {
until = ms(
new Date(subscription.current_period_end * 1000) - new Date(),
{ long: true }
)
}
} else {
id = 'oss'
}
} else {
id = 'oss'
}
return { id, name, until }
}
module.exports = class Plans extends Now {
async getCurrent() {
const res = await this._fetch('/plan')
const json = await res.json()
return parsePlan(json)
}
async set(plan) {
const res = await this._fetch('/plan', {
method: 'PUT',
body: { plan }
})
const json = await res.json()
if (res.ok) {
return parsePlan(json)
}
const err = new Error(json.error.message)
err.code = json.error.code
throw err
}
}

134
lib/re-alias.js

@ -1,134 +0,0 @@
// Native
const { join } = require('path')
// Packages
const fs = require('fs-extra')
const chalk = require('chalk')
// Ours
const { error } = require('./error')
const readMetaData = require('./read-metadata')
const NowAlias = require('./alias')
const NowDomains = require('./domains')
exports.assignAlias = async (
autoAlias,
token,
deployment,
apiUrl,
debug,
currentTeam,
user
) => {
const aliases = new NowAlias({ apiUrl, token, debug, currentTeam })
const domains = new NowDomains({ apiUrl, token, debug, currentTeam })
console.log(
`> Assigning alias ${chalk.bold.underline(autoAlias)} to deployment...`
)
// Assign alias
await aliases.set(
String(deployment),
String(autoAlias),
domains,
currentTeam,
user
)
}
exports.reAlias = async (
token,
host,
pointer,
help,
exit,
apiUrl,
debug,
alias,
currentTeam,
user
) => {
const path = process.cwd()
const configFiles = {
pkg: join(path, 'package.json'),
nowJSON: join(path, 'now.json')
}
if (!fs.existsSync(configFiles.pkg) && !fs.existsSync(configFiles.nowJSON)) {
error(
`Couldn't find a now.json or package.json file with an alias list in it`
)
return
}
const { nowConfig, name } = await readMetaData(path, {
deploymentType: 'npm', // Hard coding settings…
quiet: true // `quiet`
})
if (!host) {
const lastAlias = await alias.last(name)
host = lastAlias.url
}
if (!nowConfig) {
help()
return exit(0)
}
let pointers = []
if (pointer) {
pointers.push(pointer)
} else {
if (nowConfig.alias) {
const value = nowConfig.alias
if (typeof value === 'string') {
pointers.push(value)
} else if (Array.isArray(value)) {
pointers = pointers.concat(nowConfig.alias)
} else {
error(
`Property ${chalk.grey('aliases')} is not a valid array or string`
)
return exit(1)
}
}
if (nowConfig.aliases && Array.isArray(nowConfig.aliases)) {
console.log(
`${chalk.red('Deprecated!')} The property ${chalk.grey(
'aliases'
)} will be removed from the config file soon.`
)
console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n')
pointers = pointers.concat(nowConfig.aliases)
}
}
if (pointers.length === 0) {
help()
return exit(0)
}
const assignments = []
for (const pointer of pointers) {
assignments.push(
exports.assignAlias(
pointer,
token,
host,
apiUrl,
debug,
currentTeam,
user
)
)
}
await Promise.all(assignments)
}

195
lib/read-metadata.js

@ -1,195 +0,0 @@
// Native
const { basename, resolve: resolvePath } = require('path')
// Packages
const chalk = require('chalk')
const { readFile } = require('fs-extra')
const { parse: parseDockerfile } = require('docker-file-parser')
const determineType = require('deployment-type')
module.exports = readMetaData
async function readMetaData(
path,
{
deploymentType,
deploymentName,
sessionAffinity,
quiet = false,
strict = true
}
) {
let description
let type = deploymentType
let name = deploymentName
let affinity = sessionAffinity
const pkg = await readJSON(path, 'package.json')
let nowConfig = await readJSON(path, 'now.json')
const dockerfile = await readDockerfile(path)
const hasNowJson = Boolean(nowConfig)
if (pkg && pkg.now) {
// If the project has both a `now.json` and `now` Object in the `package.json`
// file, then fail hard and let the user know that they need to pick one or the
// other
if (nowConfig) {
const err = new Error(
'You have a `now` configuration field inside `package.json` ' +
'but configuration is also present in `now.json`! ' +
"Please ensure there's a single source of configuration by removing one."
)
err.userError = true
throw err
} else {
nowConfig = pkg.now
}
}
// We can remove this once the prompt for choosing `--npm` or `--docker` is gone
if (pkg && pkg.now && pkg.now.type) {
type = nowConfig.type
}
// The same goes for this
if (nowConfig && nowConfig.type) {
type = nowConfig.type
}
if (!type) {
type = await determineType(path)
// Both `package.json` and `Dockerfile` exist! Prompt the user to pick one.
// We can remove this soon (details are internal) - also read the comment paragraph above
if (type === 'docker' && (pkg && dockerfile)) {
const err = new Error(
'Ambiguous deployment (`package.json` and `Dockerfile` found). ' +
'Please supply `--npm` or `--docker` to disambiguate.'
)
err.userError = true
err.code = 'MULTIPLE_MANIFESTS'
throw err
}
}
if (!name && nowConfig) {
name = nowConfig.name
}
if (!affinity && nowConfig) {
affinity = nowConfig.sessionAffinity
}
if (type === 'npm') {
if (pkg) {
if (!name && pkg.now && pkg.now.name) {
name = String(pkg.now.name)
}
if (!name && pkg.name) {
name = String(pkg.name)
}
description = pkg.description
}
} else if (type === 'docker') {
if (strict && dockerfile.length <= 0) {
const err = new Error('No commands found in `Dockerfile`')
err.userError = true
throw err
}
const labels = {}
dockerfile.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => {
for (const key in args) {
if (!{}.hasOwnProperty.call(args, key)) {
continue
}
// Unescape and convert into string
try {
labels[key] = args[key]
} catch (err) {
const e = new Error(
`Error parsing value for LABEL ${key} in \`Dockerfile\``
)
e.userError = true
throw e
}
}
})
if (!name) {
name = labels.name
}
description = labels.description
} else if (type === 'static') {
// Do nothing
} else {
throw new TypeError(`Unsupported "deploymentType": ${type}`)
}
// No name in `package.json` / `now.json`, or "name" label in Dockerfile.
// Default to the basename of the root dir
if (!name) {
name = basename(path)
if (!quiet && type !== 'static') {
if (type === 'docker') {
console.log(
`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`
)
} else {
console.log(
`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`
)
}
}
}
return {
name,
description,
type,
pkg,
nowConfig,
hasNowJson,
// XXX: legacy
deploymentType: type,
sessionAffinity: affinity
}
}
async function readJSON(path, name) {
try {
const contents = await readFile(resolvePath(path, name), 'utf8')
return JSON.parse(contents)
} catch (err) {
// If the file doesn't exist then that's fine; any other error bubbles up
if (err.code !== 'ENOENT') {
err.userError = true
throw err
}
}
}
async function readDockerfile(path, name = 'Dockerfile') {
try {
const contents = await readFile(resolvePath(path, name), 'utf8')
return parseDockerfile(contents, { includeComments: true })
} catch (err) {
// If the file doesn't exist then that's fine; any other error bubbles up
if (err.code !== 'ENOENT') {
err.userError = true
throw err
}
}
}

78
lib/scale-info.js

@ -1,78 +0,0 @@
const linelog = require('single-line-log').stdout
const range = require('lodash.range')
const ms = require('ms')
const chalk = require('chalk')
const retry = require('async-retry')
function barify(cur, tot) {
return (
'[' +
range(0, cur).map(() => '=').join('') +
range(cur, tot).map(() => '-').join('') +
']'
)
}
module.exports = async function(now, url) {
const match = await now.findDeployment(url)
const { min, max, current } = match.scale
let targetReplicaCount = min
if (current < min) {
targetReplicaCount = min
} else if (current > max) {
targetReplicaCount = max
} else {
return
}
if (targetReplicaCount === 0) {
console.log(`> Scaled to 0 instances`)
return
}
const startTime = Date.now()
let barcurr = current
const end = Math.max(current, max)
linelog(
`${chalk.gray('>')} Scaling to ${chalk.bold(
String(targetReplicaCount) +
(targetReplicaCount === 1 ? ' instance' : ' instances')
)}: ` + barify(barcurr, end)
)
const instances = await retry(
async () => {
const res = await now.listInstances(match.uid)
if (barcurr !== res.length) {
barcurr = res.length
linelog(
`${chalk.gray('>')} Scaling to ${chalk.bold(
String(targetReplicaCount) +
(targetReplicaCount === 1 ? ' instance' : ' instances')
)}: ` + barify(barcurr, end)
)
if (barcurr === targetReplicaCount) {
linelog.clear()
linelog(
`> Scaled to ${chalk.bold(
String(targetReplicaCount) +
(targetReplicaCount === 1 ? ' instance' : ' instances')
)}: ${chalk.gray('[' + ms(Date.now() - startTime) + ']')}\n`
)
return res
}
}
throw new Error('Not ready yet')
},
{ retries: 5000, minTimeout: 10, maxTimeout: 20 }
)
process.nextTick(() => {
instances.forEach(inst => {
console.log(`${chalk.gray('-')} ${chalk.underline(inst.url)}`)
})
})
}

40
lib/scale.js

@ -1,40 +0,0 @@
// Ours
const Now = require('../lib')
module.exports = class Scale extends Now {
getInstances(id) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /deployments/${id}/instances`)
}
const res = await this._fetch(`/now/deployments/${id}/instances`, {
method: 'GET'
})
if (this._debug) {
console.timeEnd(
`> [debug] #${attempt} GET /deployments/${id}/instances`
)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status !== 200) {
if (res.status === 404 || res.status === 400) {
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message)
}
return body
})
}
}

119
lib/secrets.js

@ -1,119 +0,0 @@
// Ours
const Now = require('../lib')
const isUserError = res => ((res.status / 100) | 0) === 4
module.exports = class Secrets extends Now {
ls() {
return this.listSecrets()
}
rm(nameOrId) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`)
}
const res = await this._fetch(`/now/secrets/${nameOrId}`, {
method: 'DELETE'
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status !== 200) {
if (isUserError(res)) {
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message)
}
return body
})
}
add(name, value) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST /secrets`)
}
const res = await this._fetch('/now/secrets', {
method: 'POST',
body: {
name,
value: value.toString()
}
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST /secrets`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status !== 200) {
if (isUserError(res)) {
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message)
}
return body
})
}
rename(nameOrId, newName) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`)
}
const res = await this._fetch(`/now/secrets/${nameOrId}`, {
method: 'PATCH',
body: {
name: newName
}
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status !== 200) {
if (isUserError(res)) {
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message)
}
return body
})
}
}

30
lib/sort-deployments.js

@ -1,30 +0,0 @@
const fs = require('fs-extra')
module.exports = async function(apps) {
let pkg
try {
const json = await fs.readFile('package.json')
pkg = JSON.parse(json)
} catch (err) {
pkg = {}
}
return apps
.map(([name, deps]) => {
deps = deps.slice().sort((a, b) => {
return b.created - a.created
})
return [name, deps]
})
.sort(([nameA, depsA], [nameB, depsB]) => {
if (pkg.name === nameA) {
return -1
}
if (pkg.name === nameB) {
return 1
}
return depsB[0].created - depsA[0].created
})
}

5
lib/strlen.js

@ -1,5 +0,0 @@
function strlen(str) {
return str.replace(/\u001b[^m]*m/g, '').length
}
module.exports = strlen

139
lib/teams.js

@ -1,139 +0,0 @@
const Now = require('../lib')
module.exports = class Teams extends Now {
async create({ slug }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST /teams}`)
}
const res = await this._fetch(`/teams`, {
method: 'POST',
body: {
slug
}
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST /teams`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status === 400) {
const e = new Error(body.error.message)
e.code = body.error.code
return bail(e)
} else if (res.status !== 200) {
const e = new Error(body.error.message)
e.code = body.error.code
throw e
}
return body
})
}
async edit({ id, slug, name }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} PATCH /teams/${id}}`)
}
const payload = {}
if (name) {
payload.name = name
}
if (slug) {
payload.slug = slug
}
const res = await this._fetch(`/teams/${id}`, {
method: 'PATCH',
body: payload
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PATCH /teams/${id}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status === 400) {
const e = new Error(body.error.message)
e.code = body.error.code
return bail(e)
} else if (res.status !== 200) {
const e = new Error(body.error.message)
e.code = body.error.code
throw e
}
return body
})
}
async inviteUser({ teamId, email }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST /teams/${teamId}/members}`)
}
const res = await this._fetch(`/teams/${teamId}/members`, {
method: 'POST',
body: {
email
}
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST /teams/${teamId}/members}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
const body = await res.json()
if (res.status === 400) {
const e = new Error(body.error.message)
e.code = body.error.code
return bail(e)
} else if (res.status !== 200) {
const e = new Error(body.error.message)
e.code = body.error.code
throw e
}
return body
})
}
async ls() {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /teams}`)
}
const res = await this._fetch(`/teams`)
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /teams`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'))
}
return res.json()
})
}
}

22
lib/test.js

@ -1,22 +0,0 @@
// Native
const { resolve } = require('path')
// Ours
const { npm: getFiles } = require('./get-files')
getFiles(resolve('../mng-test/files-in-package'))
.then(files => {
console.log(files)
getFiles(resolve('../mng-test/files-in-package-ignore'))
.then(files2 => {
console.log('ignored: ')
console.log(files2)
})
.catch(err => {
console.log(err.stack)
})
})
.catch(err => {
console.log(err.stack)
})

20
lib/to-host.js

@ -1,20 +0,0 @@
// Native
const { parse } = require('url')
/**
* Converts a valid deployment lookup parameter to a hostname.
* `http://google.com` => google.com
* google.com => google.com
*/
function toHost(url) {
if (/^https?:\/\//.test(url)) {
return parse(url).host
}
// Remove any path if present
// `a.b.c/` => `a.b.c`
return url.replace(/(\/\/)?([^/]+)(.*)/, '$2')
}
module.exports = toHost

7
lib/ua.js

@ -1,7 +0,0 @@
// Native
const os = require('os')
// Ours
const { version } = require('./pkg')
module.exports = `now ${version} node-${process.version} ${os.platform()} (${os.arch()})`

69
lib/user.js

@ -1,69 +0,0 @@
const _fetch = require('node-fetch')
const { responseError } = require('./error')
function _filter(data) {
data = data.user
return {
uid: data.uid,
username: data.username,
email: data.email
}
}
/**
* Gets all the info we have about an user
*
* @param {Object} fetch Optionally, _our_ `fetch` can be passed here
* @param {String} token Only necessary if `fetch` is undefined
* @param {String} apiUrl Only necessary if `fetch` is undefined
* @param {Boolean} filter If `true`, the `filter` used will to the data
* before returning
* @return {Object}
*/
async function get(
{ fetch, token, apiUrl = 'https://api.zeit.co', filter = true } = {}
) {
let headers = {}
const endpoint = '/www/user'
const url = fetch ? endpoint : apiUrl + endpoint
if (!fetch) {
headers = {
Authorization: `Bearer ${token}`
}
fetch = _fetch
}
const res = await fetch(url, { headers })
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 (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 = {
get,
filter: _filter
}

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

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

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

@ -1,251 +0,0 @@
{
"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"
}

33
lib/utils/billing/geocode.js

@ -1,33 +0,0 @@
// 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
})
}
)
})
}

49
lib/utils/check-path.js

@ -1,49 +0,0 @@
// Native
const os = require('os')
const path = require('path')
const checkPath = async dir => {
if (!dir) {
return
}
const home = os.homedir()
let location
const paths = {
home,
desktop: path.join(home, 'Desktop'),
downloads: path.join(home, 'Downloads')
}
for (const locationPath in paths) {
if (!{}.hasOwnProperty.call(paths, locationPath)) {
continue
}
if (dir === paths[locationPath]) {
location = locationPath
}
}
if (!location) {
return
}
let locationName
switch (location) {
case 'home':
locationName = 'user directory'
break
case 'downloads':
locationName = 'downloads directory'
break
default:
locationName = location
}
throw new Error(`You're trying to deploy your ${locationName}.`)
}
module.exports = checkPath

29
lib/utils/domains/treat-buy-error.js

@ -1,29 +0,0 @@
const error = require('../output/error')
module.exports = function(err) {
switch (err.code) {
case 'invalid_domain': {
error('Invalid domain')
break
}
case 'not_available': {
error("Domain can't be purchased at this time")
break
}
case 'service_unavailabe': {
error('Purchase failed – Service unavailable')
break
}
case 'unexpected_error': {
error('Purchase failed – Unexpected error')
break
}
case 'forbidden_premium': {
error('A coupon cannot be used to register a premium domain')
break
}
default: {
error(err.message)
}
}
}

8
lib/utils/exit.js

@ -1,8 +0,0 @@
module.exports = 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
/* eslint-disable unicorn/no-process-exit */
setTimeout(() => process.exit(code || 0), 100)
}

7
lib/utils/fatal-error.js

@ -1,7 +0,0 @@
const error = require('./output/error')
const exit = require('./exit')
module.exports = (msg, code = 1) => {
error(msg)
exit(code)
}

79
lib/utils/input/list.js

@ -1,79 +0,0 @@
const inquirer = require('inquirer')
const stripAnsi = require('strip-ansi')
// eslint-disable-next-line import/no-unassigned-import
require('./patch-inquirer')
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',
// eslint-disable-line no-unused-vars
choices = [
{
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]
}

20
lib/utils/input/patch-inquirer.js

@ -1,20 +0,0 @@
const inquirer = require('inquirer')
const chalk = require('chalk')
// Here we patch inquirer to use a `>` instead of the ugly green `?`
/* eslint-disable no-multiple-empty-lines, no-var, no-undef, no-eq-null, eqeqeq, semi */
const 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 */
inquirer.prompt.prompts.input.prototype.getQuestion = getQuestion
inquirer.prompt.prompts.list.prototype.getQuestion = getQuestion

58
lib/utils/input/prompt-bool.js

@ -1,58 +0,0 @@
const chalk = require('chalk')
module.exports = (
label,
{
defaultValue = false,
abortSequences = new Set(['\u0003']),
resolveChars = new Set(['\r']),
yesChar = 'y',
noChar = 'n',
stdin = process.stdin,
stdout = process.stdout,
trailing = ''
} = {}
) => {
return new Promise(resolve => {
const isRaw = stdin.isRaw
stdin.setRawMode(true)
stdin.resume()
function restore() {
stdout.write(trailing)
stdin.setRawMode(isRaw)
stdin.pause()
stdin.removeListener('data', onData)
}
function onData(buffer) {
const data = buffer.toString()
if (data[0].toLowerCase() === yesChar) {
restore()
resolve(true)
} else if (data[0].toLowerCase() === noChar) {
restore()
resolve(false)
} else if (abortSequences.has(data)) {
restore()
resolve(false)
} else if (resolveChars.has(data[0])) {
restore()
resolve(defaultValue)
} 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)
})
}

39
lib/utils/input/prompt-options.js

@ -1,39 +0,0 @@
// Packages
const chalk = require('chalk')
module.exports = promptOptions
function promptOptions(opts) {
return new Promise((resolve, reject) => {
opts.forEach(([, text], i) => {
console.log(`${chalk.gray('>')} [${chalk.bold(i + 1)}] ${text}`)
})
const ondata = v => {
const s = v.toString()
const cleanup = () => {
process.stdin.setRawMode(false)
process.stdin.removeListener('data', ondata)
}
// Ctrl + C
if (s === '\u0003') {
cleanup()
const err = new Error('Aborted')
err.code = 'USER_ABORT'
return reject(err)
}
const n = Number(s)
if (opts[n - 1]) {
cleanup()
resolve(opts[n - 1][0])
}
}
process.stdin.setRawMode(true)
process.stdin.resume()
process.stdin.on('data', ondata)
})
}

3
lib/utils/input/regexes.js

@ -1,3 +0,0 @@
module.exports = {
email: /.+@.+\..+$/
}

262
lib/utils/input/text.js

@ -1,262 +0,0 @@
// Packages
const ansiEscapes = require('ansi-escapes')
const ansiRegex = require('ansi-regex')
const chalk = require('chalk')
const stripAnsi = require('strip-ansi')
// Utilities
const eraseLines = require('../output/erase-lines')
const ESCAPES = {
LEFT: '\u001B[D',
RIGHT: '\u001B[C',
CTRL_C: '\u0003',
BACKSPACE: '\u0008',
CTRL_H: '\u007F',
CARRIAGE: '\r'
}
const formatCC = data => {
return data.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim()
}
module.exports = function(
{
label = '',
initialValue = '',
// If false, the `- label` will be printed as `✖ label` in red
// Until the first keypress
valid = true,
// 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
// Tab
// Right arrow
autoCompleteChars = new Set(['\t', '\x1b[C']),
// If true, converts everything the user types to to lowercase
forceLowerCase = false
} = {}
) {
return new Promise((resolve, reject) => {
const isRaw = process.stdin.isRaw
let value
let caretOffset = 0
let regex
let suggestion = ''
if (valid) {
stdout.write(label)
} else {
const _label = label.replace('-', '✖')
stdout.write(chalk.red(_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) {
let data = buffer.toString()
value = stripAnsi(value)
if (abortSequences.has(data)) {
restore()
return reject(new Error('USER_ABORT'))
}
if (forceLowerCase) {
data = data.toLowerCase()
}
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 = formatCC(value)
value = value.replace(regex, chalk.gray('$1'))
} else if (mask === 'expDate') {
value = value.replace(regex, chalk.gray('$1'))
}
const l = chalk.red(label.replace('-', '✖'))
eraseLines(1)
stdout.write(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) {
let formattedData = data
if (mask === 'cc' || mask === 'ccv') {
formattedData = formatCC(data)
}
if (value[value.length + caretOffset + 1] === ' ') {
tmp =
value.substr(0, value.length + caretOffset) +
formattedData +
value.substr(value.length + caretOffset + formattedData.length)
caretOffset += formattedData.length + 1
if (value[value.length + caretOffset] === '/') {
caretOffset += formattedData.length + 1
}
} else {
tmp =
value.substr(0, value.length + caretOffset) +
formattedData +
value.substr(value.length + caretOffset + formattedData.length)
caretOffset += formattedData.length
}
} 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 = formatCC(value)
value = value.replace(regex, chalk.gray('$1'))
} else if (mask === 'expDate') {
value = value.replace(regex, chalk.gray('$1'))
}
eraseLines(1)
stdout.write(label + value + suggestion)
if (caretOffset) {
process.stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset)))
}
}
stdin.on('data', onData)
})
}

3
lib/utils/output/chars.js

@ -1,3 +0,0 @@
module.exports = {
tick: '✓'
}

6
lib/utils/output/cmd.js

@ -1,6 +0,0 @@
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('`')}`

6
lib/utils/output/code.js

@ -1,6 +0,0 @@
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('`')}`

3
lib/utils/output/erase-lines.js

@ -1,3 +0,0 @@
const ansiEscapes = require('ansi-escapes')
module.exports = n => process.stdout.write(ansiEscapes.eraseLines(n))

9
lib/utils/output/error.js

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

6
lib/utils/output/info.js

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

1
lib/utils/output/logo.js

@ -1 +0,0 @@
module.exports = process.platform === 'win32' ? 'Δ' : '𝚫'

6
lib/utils/output/note.js

@ -1,6 +0,0 @@
const chalk = require('chalk')
// Prints a note
module.exports = msg => {
console.log(`${chalk.yellow('> NOTE:')} ${msg}`)
}

7
lib/utils/output/param.js

@ -1,7 +0,0 @@
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('"')}`)

4
lib/utils/output/right-pad.js

@ -1,4 +0,0 @@
module.exports = (string, n = 0) => {
n -= string.length
return string + ' '.repeat(n > -1 ? n : 0)
}

10
lib/utils/output/stamp.js

@ -1,10 +0,0 @@
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

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

27
lib/utils/output/table.js

@ -1,27 +0,0 @@
const chalk = require('chalk')
const printf = require('printf')
const printLine = (data, sizes) =>
data.reduce((line, col, i) => {
return line + printf(`%-${sizes[i]}s`, col)
}, '')
// Print a table
module.exports = (fieldNames = [], data = [], margins = []) => {
// Compute size of each column
const sizes = data
.reduce((acc, row) => {
return row.map((col, i) => {
const currentMaxColSize = acc[i] || 0
const colSize = (col && col.length) || 0
return Math.max(currentMaxColSize, colSize)
})
}, fieldNames.map(col => col.length))
// Add margin to all columns except the last
.map((size, i) => (i < margins.length && size + margins[i]) || size)
// Print header
console.log(chalk.grey(printLine(fieldNames, sizes)))
// Print content
data.forEach(row => console.log(printLine(row, sizes)))
}

5
lib/utils/output/uid.js

@ -1,5 +0,0 @@
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

@ -1,15 +0,0 @@
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
lib/utils/to-human-path.js

@ -1,18 +0,0 @@
// Native
const { resolve } = require('path')
const { homedir } = require('os')
// Cleaned-up `$HOME` (i.e.: no trailing slash)
const HOME = resolve(homedir())
/**
* Attempts to show the given path in
* a human-friendly form. For example,
* `/Users/rauchg/test.js` becomes `~/test.js`
*/
function toHumanPath(path) {
return path.replace(HOME, '~')
}
module.exports = toHumanPath

28
lib/utils/url.js

@ -1,28 +0,0 @@
exports.maybeURL = id => {
// E.g, "appname-asdf"
return id.includes('-')
}
exports.normalizeURL = u => {
// Normalize URL by removing slash from the end
if (u.slice(-1) === '/') {
u = u.slice(0, -1)
}
// `url` should match the hostname of the deployment
u = u.replace(/^https:\/\//i, '')
if (!u.includes('.')) {
// `.now.sh` domain is implied if just the subdomain is given
u += '.now.sh'
}
return u
}
exports.parseInstanceURL = u => {
const m = /^(.+)-([a-z0-9]{24})(\.now\.sh)$/.exec(u)
const url = m ? m[1] + m[3] : u
const instanceId = m ? m[2] : null
return [url, instanceId]
}

21
license.md

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016 Zeit, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

11
link/link.js

@ -1,11 +0,0 @@
#!/usr/bin/env node
try {
// eslint-disable-next-line import/no-unassigned-import
require('../bin/now.js')
} catch (err) {
if (err.code === 'ENOENT' && err.syscall === 'uv_cwd') {
console.error(`Current path doesn't exist!`)
process.exit(1)
}
}

4
link/package-lock.json

@ -1,4 +0,0 @@
{
"name": "now",
"lockfileVersion": 1
}

9
link/package.json

@ -1,9 +0,0 @@
{
"name": "now",
"license": "MIT",
"description": "The command line interface for Now",
"repository": "zeit/now-cli",
"bin": {
"now": "link.js"
}
}

9560
package-lock.json

File diff suppressed because it is too large

124
package.json

@ -1,124 +0,0 @@
{
"name": "now",
"version": "7.4.0",
"description": "The command line interface for Now",
"repository": "zeit/now-cli",
"license": "MIT",
"scripts": {
"postinstall": "node download/install.js",
"precommit": "xo --quiet && lint-staged",
"prepublish": "in-install || (npm run webpack && cp /dev/null download/dist/now)",
"link": "cd link && npm link",
"lint": "xo --quiet",
"test": "npm run lint && ava",
"pack": "pkg bin/now.js -c package.json -o packed/now",
"webpack": "webpack --context download --config download/webpack.js",
"clean": "rm -rf node_modules link/node_modules download/dist",
"prettify": "prettier --single-quote --no-semi --write `find ./lib -name '*.js'` `find ./bin -name '*.js'`"
},
"pkg": {
"scripts": [
"bin/*",
"lib/**/*"
],
"targets": [
"node7-alpine-x64",
"node7-linux-x64",
"node7-macos-x64",
"node7-win-x64"
]
},
"bin": {
"now": "download/dist/now"
},
"files": [
"download/dist",
"download/install.js"
],
"ava": {
"failFast": true,
"files": [
"test/*.js"
]
},
"xo": {
"ignores": [
"test/_fixtures/**"
],
"extends": "prettier"
},
"lint-staged": {
"*.js": [
"npm run lint",
"prettier --single-quote --no-semi --write",
"git add"
]
},
"greenkeeper": {
"ignore": [
"socket.io-client"
]
},
"devDependencies": {
"@google/maps": "0.4.3",
"alpha-sort": "2.0.1",
"ansi-escapes": "2.0.0",
"ansi-regex": "3.0.0",
"arr-flatten": "1.1.0",
"array-unique": "0.3.2",
"async-retry": "1.1.3",
"ava": "0.21.0",
"babel-core": "6.25.0",
"babel-loader": "7.1.1",
"babel-plugin-transform-async-to-generator": "6.24.1",
"babel-plugin-transform-runtime": "6.23.0",
"babel-preset-es2015": "6.24.1",
"bytes": "2.5.0",
"chalk": "2.0.1",
"clipboardy": "1.1.4",
"credit-card": "3.0.1",
"cross-spawn": "5.1.0",
"dateformat": "2.0.0",
"death": "1.1.0",
"deployment-type": "1.0.1",
"docker-file-parser": "1.0.2",
"dotenv": "4.0.0",
"download": "6.2.5",
"email-prompt": "0.3.1",
"email-validator": "1.0.7",
"epipebomb": "1.0.0",
"eslint-config-prettier": "2.3.0",
"fs-extra": "4.0.0",
"glob": "7.1.2",
"ignore": "3.3.3",
"in-publish": "2.0.0",
"ini": "1.3.4",
"inquirer": "3.2.1",
"is-url": "1.2.2",
"lint-staged": "4.0.2",
"lodash.range": "3.2.0",
"minimist": "1.2.0",
"ms": "2.0.0",
"node-fetch": "1.7.1",
"ora": "1.3.0",
"pkg": "4.1.4",
"prettier": "1.5.3",
"printf": "0.2.5",
"progress": "2.0.0",
"psl": "1.1.19",
"resumer": "0.0.0",
"single-line-log": "1.1.2",
"slackup": "2.0.1",
"socket.io-client": "1.7.4",
"split-array": "1.0.1",
"strip-ansi": "4.0.0",
"stripe": "4.23.1",
"supports-color": "4.2.1",
"text-table": "0.2.0",
"tmp-promise": "1.0.3",
"update-notifier": "2.2.0",
"webpack": "3.4.1",
"which": "1.3.0",
"xo": "0.18.2"
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save