Browse Source

Prettified everything

master
Leo Lamprecht 8 years ago
parent
commit
b0a88b377c
  1. 78
      bin/domains/buy.js
  2. 298
      bin/now-alias.js
  3. 134
      bin/now-billing-add.js
  4. 262
      bin/now-billing.js
  5. 256
      bin/now-certs.js
  6. 492
      bin/now-deploy.js
  7. 205
      bin/now-dns.js
  8. 212
      bin/now-domains.js
  9. 186
      bin/now-list.js
  10. 86
      bin/now-logout.js
  11. 220
      bin/now-logs.js
  12. 124
      bin/now-open.js
  13. 106
      bin/now-remove.js
  14. 222
      bin/now-scale.js
  15. 196
      bin/now-secrets.js
  16. 84
      bin/now-teams.js
  17. 144
      bin/now-upgrade.js
  18. 60
      bin/now.js
  19. 126
      bin/teams/add.js
  20. 140
      bin/teams/invite.js
  21. 116
      bin/teams/switch.js
  22. 48
      lib/agent.js
  23. 553
      lib/alias.js
  24. 106
      lib/build-logger.js
  25. 64
      lib/certs.js
  26. 58
      lib/cfg.js
  27. 44
      lib/credit-cards.js
  28. 12
      lib/dns.js
  29. 114
      lib/domain-records.js
  30. 126
      lib/domains.js
  31. 22
      lib/error.js
  32. 11
      lib/errors.js
  33. 164
      lib/get-files.js
  34. 165
      lib/git.js
  35. 34
      lib/hash.js
  36. 2
      lib/ignored.js
  37. 4
      lib/indent.js
  38. 644
      lib/index.js
  39. 12
      lib/is-zeit-world.js
  40. 98
      lib/login.js
  41. 12
      lib/logs.js
  42. 8
      lib/pkg.js
  43. 32
      lib/plans.js
  44. 88
      lib/re-alias.js
  45. 119
      lib/read-metadata.js
  46. 62
      lib/scale-info.js
  47. 29
      lib/scale.js
  48. 72
      lib/secrets.js
  49. 4
      lib/strlen.js
  50. 56
      lib/teams.js
  51. 18
      lib/test.js
  52. 8
      lib/to-host.js
  53. 6
      lib/ua.js
  54. 28
      lib/user.js
  55. 28
      lib/utils/billing/geocode.js
  56. 36
      lib/utils/check-path.js
  57. 6
      lib/utils/domains/treat-buy-error.js
  58. 4
      lib/utils/exit.js
  59. 10
      lib/utils/fatal-error.js
  60. 94
      lib/utils/input/list.js
  61. 44
      lib/utils/input/prompt-bool.js
  62. 2
      lib/utils/input/regexes.js
  63. 203
      lib/utils/input/text.js
  64. 2
      lib/utils/output/chars.js
  65. 5
      lib/utils/output/cmd.js
  66. 5
      lib/utils/output/code.js
  67. 4
      lib/utils/output/erase-lines.js
  68. 8
      lib/utils/output/error.js
  69. 6
      lib/utils/output/info.js
  70. 2
      lib/utils/output/logo.js
  71. 6
      lib/utils/output/note.js
  72. 4
      lib/utils/output/param.js
  73. 6
      lib/utils/output/right-pad.js
  74. 10
      lib/utils/output/stamp.js
  75. 6
      lib/utils/output/success.js
  76. 4
      lib/utils/output/uid.js
  77. 20
      lib/utils/output/wait.js
  78. 38
      lib/utils/prompt-options.js
  79. 10
      lib/utils/to-human-path.js
  80. 14
      lib/utils/url.js
  81. 65
      scripts/slack.js
  82. 102
      test/args-parsing.js
  83. 286
      test/index.js
  84. 58
      test/pack-now.js
  85. 31
      test/to-host.js

78
bin/domains/buy.js

@ -1,64 +1,64 @@
const { italic, bold } = require('chalk');
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 uid = require('../../lib/utils/output/uid');
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 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 uid = require('../../lib/utils/output/uid')
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')
module.exports = async function({domains, args, currentTeam, user}) {
const name = args[0];
module.exports = async function({ domains, args, currentTeam, user }) {
const name = args[0]
let elapsed
if (!name) {
return error(`Missing domain name. Run ${cmd('now domains help')}`);
return error(`Missing domain name. Run ${cmd('now domains help')}`)
}
const nameParam = param(name);
const nameParam = param(name)
elapsed = stamp()
let stopSpinner = wait(`Checking availability for ${nameParam}`);
let stopSpinner = wait(`Checking availability for ${nameParam}`)
const price = await domains.price(name);
const available = await domains.status(name);
const price = await domains.price(name)
const available = await domains.status(name)
stopSpinner();
stopSpinner()
if (! available) {
return error(`The domain ${nameParam} is ${italic('unavailable')}! ${elapsed()}`);
if (!available) {
return error(
`The domain ${nameParam} is ${italic('unavailable')}! ${elapsed()}`
)
}
info(`The domain ${nameParam} is ${italic('available')}! ${elapsed()}`);
const confirmation = await promptBool(`Buy now for ${bold(`$${price}`)} (${
bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)
})?`);
info(`The domain ${nameParam} is ${italic('available')}! ${elapsed()}`)
const confirmation = await promptBool(
`Buy now for ${bold(`$${price}`)} (${bold((currentTeam && currentTeam.slug) || user.username || user.email)})?`
)
eraseLines(1);
eraseLines(1)
if (!confirmation) {
return info('Aborted');
return info('Aborted')
}
stopSpinner = wait('Purchasing');
stopSpinner = wait('Purchasing')
elapsed = stamp()
let domain;
let domain
try {
domain = await domains.buy(name);
domain = await domains.buy(name)
} catch (err) {
stopSpinner();
return treatBuyError(err);
stopSpinner()
return treatBuyError(err)
}
stopSpinner();
stopSpinner()
success(`Domain purchased and created ${uid(domain.uid)} ${elapsed()}`);
success(`Domain purchased and created ${uid(domain.uid)} ${elapsed()}`)
info(
`You may now use your domain as an alias to your deployments. Run ${cmd('now alias help')}`
);
};
)
}

298
bin/now-alias.js

@ -1,23 +1,23 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk');
const minimist = require('minimist');
const table = require('text-table');
const ms = require('ms');
const chalk = require('chalk')
const minimist = require('minimist')
const table = require('text-table')
const ms = require('ms')
// 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 { error } = require('../lib/error');
const toHost = require('../lib/to-host');
const { reAlias } = require('../lib/re-alias');
const exit = require('../lib/utils/exit');
const logo = require('../lib/utils/output/logo');
const promptBool = require('../lib/utils/input/prompt-bool');
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 { error } = require('../lib/error')
const toHost = require('../lib/to-host')
const { reAlias } = require('../lib/re-alias')
const exit = require('../lib/utils/exit')
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'],
@ -29,9 +29,9 @@ const argv = minimist(process.argv.slice(2), {
debug: 'd',
token: 't'
}
});
})
const subcommand = argv._[0];
const subcommand = argv._[0]
// Options
const help = () => {
@ -90,69 +90,69 @@ const help = () => {
${chalk.dim('Alias:')} ln
`
);
};
)
}
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
if (argv.help) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({token, config});
await run({ token, config })
} catch (err) {
if (err.userError) {
error(err.message);
error(err.message)
} else {
error(`Unknown error: ${err}\n${err.stack}`);
error(`Unknown error: ${err}\n${err.stack}`)
}
exit(1);
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);
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 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);
error(`Could not match path alias for: ${argv._[1]}`)
return exit(1)
}
if (argv.json) {
console.log(JSON.stringify({ rules: item.rules }, null, 2));
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(
@ -163,7 +163,7 @@ async function run({token, config: {currentTeam, user}}) {
rule.pathname ? rule.pathname : '',
rule.method ? rule.method : '*',
rule.dest
];
]
})
),
{
@ -171,49 +171,49 @@ async function run({token, config: {currentTeam, user}}) {
hsep: ' '.repeat(2),
stringLength: strlen
}
);
)
console.log(text);
console.log(text)
}
break;
break
} else if (args.length !== 0) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan('`now alias ls`')}`
);
return exit(1);
)
return exit(1)
}
const start_ = new Date();
const list = await alias.list();
const urls = new Map(list.map(l => [l.uid, l.url]));
const aliases = await alias.ls();
aliases.sort((a, b) => new Date(b.created) - new Date(a.created));
const current = new Date();
const start_ = new Date()
const list = await alias.list()
const urls = new Map(list.map(l => [l.uid, l.url]))
const aliases = await alias.ls()
aliases.sort((a, b) => new Date(b.created) - new Date(a.created))
const current = new Date()
const header = [
['', 'id', 'source', 'url', 'created'].map(s => chalk.dim(s))
];
]
const text = list.length === 0
? null
: table(
header.concat(
aliases.map(_alias => {
const _url = chalk.underline(`https://${_alias.alias}`);
const target = _alias.deploymentId;
let _sourceUrl;
const _url = chalk.underline(`https://${_alias.alias}`)
const target = _alias.deploymentId
let _sourceUrl
if (urls.get(target)) {
_sourceUrl = chalk.underline(`https://${urls.get(target)}`);
_sourceUrl = chalk.underline(`https://${urls.get(target)}`)
} else if (_alias.rules) {
_sourceUrl = chalk.gray(
`[${_alias.rules.length} custom rule${_alias.rules.length > 1 ? 's' : ''}]`
);
)
} else {
_sourceUrl = chalk.gray('<null>');
_sourceUrl = chalk.gray('<null>')
}
const time = chalk.gray(
ms(current - new Date(_alias.created)) + ' ago'
);
)
return [
'',
// We default to `''` because some early aliases didn't
@ -222,7 +222,7 @@ async function run({token, config: {currentTeam, user}}) {
_sourceUrl,
_url,
time
];
]
})
),
{
@ -230,93 +230,85 @@ async function run({token, config: {currentTeam, user}}) {
hsep: ' '.repeat(2),
stringLength: strlen
}
);
)
const elapsed_ = ms(new Date() - start_);
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
)
}`
);
`> ${aliases.length} alias${aliases.length === 1 ? '' : 'es'} found ${chalk.gray(`[${elapsed_}]`)} under ${chalk.bold((currentTeam && currentTeam.slug) || user.username || user.email)}`
)
if (text) {
console.log('\n' + text + '\n');
console.log('\n' + text + '\n')
}
break;
break
}
case 'rm':
case 'remove': {
const _target = String(args[0]);
const _target = String(args[0])
if (!_target) {
const err = new Error('No alias id specified');
err.userError = true;
throw err;
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);
)
return exit(1)
}
const _aliases = await alias.ls();
const _alias = findAlias(_target, _aliases);
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;
`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);
const confirmation = await confirmDeploymentRemoval(alias, _alias)
if (!confirmation) {
console.log('\n> Aborted');
process.exit(0);
console.log('\n> Aborted')
process.exit(0)
}
const start = new Date();
await alias.rm(_alias);
const elapsed = ms(new Date() - start);
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);
error(err)
exit(1)
}
break;
break
}
case 'add':
case 'set': {
if (argv.rules) {
await updatePathAlias(alias, argv._[0], argv.rules, domains);
break;
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);
)
return exit(1)
}
await alias.set(String(args[0]), String(args[1]), currentTeam, user);
break;
await alias.set(String(args[0]), String(args[1]), currentTeam, user)
break
}
default: {
if (argv._.length === 0) {
await reAlias(token, null, null, help, exit, apiUrl, debug, alias);
break;
await reAlias(token, null, null, help, exit, apiUrl, debug, alias)
break
}
if (argv._.length === 1) {
@ -329,101 +321,109 @@ async function run({token, config: {currentTeam, user}}) {
apiUrl,
debug,
alias
);
break;
)
break
}
if (argv.rules) {
await updatePathAlias(alias, argv._[0], argv.rules, domains);
await updatePathAlias(alias, argv._[0], argv.rules, domains)
} else if (argv._.length === 2) {
await alias.set(String(argv._[0]), String(argv._[1]), domains, currentTeam, user);
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);
error('Invalid number of arguments')
help()
exit(1)
} else {
error('Please specify a valid subcommand: ls | set | rm');
help();
exit(1);
error('Please specify a valid subcommand: ls | set | rm')
help()
exit(1)
}
}
}
domains.close()
alias.close();
alias.close()
}
async function confirmDeploymentRemoval(alias, _alias) {
const deploymentsList = await alias.list();
const urls = new Map(deploymentsList.map(l => [l.uid, l.url]));
const deploymentsList = await alias.list()
const urls = new Map(deploymentsList.map(l => [l.uid, l.url]))
const time = chalk.gray(ms(new Date() - new Date(_alias.created)) + ' ago');
const _sourceUrl = chalk.underline(
`https://${urls.get(_alias.deploymentId)}`
);
const time = chalk.gray(ms(new Date() - new Date(_alias.created)) + ' ago')
const _sourceUrl = chalk.underline(`https://${urls.get(_alias.deploymentId)}`)
const tbl = table(
[
[_alias.uid, _sourceUrl, chalk.underline(`https://${_alias.alias}`), time]
],
{ align: ['l', 'r', 'l'], hsep: ' '.repeat(6) }
);
)
const msg =
'> The following alias will be removed permanently\n' +
` ${tbl} \nAre you sure?`;
` ${tbl} \nAre you sure?`
return promptBool(msg, {
trailing: '\n'
});
})
}
function findAlias(alias, list) {
let key;
let val;
let key
let val
if (/\./.test(alias)) {
val = toHost(alias);
key = 'alias';
val = toHost(alias)
key = 'alias'
} else {
val = alias;
key = 'uid';
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}`);
console.log(`> [debug] matched alias ${d.uid} by ${key} ${val}`)
}
return true;
return true
}
// Match prefix
if (`${val}.now.sh` === d.alias) {
if (debug) {
console.log(`> [debug] matched alias ${d.uid} by url ${d.host}`);
console.log(`> [debug] matched alias ${d.uid} by url ${d.host}`)
}
return true;
return true
}
return false;
});
return false
})
return _alias;
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);
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;
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}]`
);
)
}
}

134
bin/now-billing-add.js

@ -1,32 +1,28 @@
#!/usr/bin/env node
// Packages
const ansiEscapes = require('ansi-escapes');
const chalk = require('chalk');
const ccValidator = require('credit-card');
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');
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;
return data
}
module.exports = function({creditCards, currentTeam, user}) {
module.exports = function({ creditCards, currentTeam, user }) {
const state = {
error: undefined,
cardGroupLabel: `> ${chalk.bold(`Enter your card details for ${
chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)
}`)}`,
cardGroupLabel: `> ${chalk.bold(`Enter your card details for ${chalk.bold((currentTeam && currentTeam.slug) || user.username || user.email)}`)}`,
name: {
label: rightPad('Full Name', 12),
@ -40,12 +36,12 @@ module.exports = function({creditCards, currentTeam, user}) {
placeholder: '#### #### #### ####',
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
validateValue: data => {
data = data.replace(/ /g, '');
const type = ccValidator.determineCardType(data);
data = data.replace(/ /g, '')
const type = ccValidator.determineCardType(data)
if (!type) {
return false;
return false
}
return ccValidator.isValidCardNumber(data, type);
return ccValidator.isValidCardNumber(data, type)
}
},
@ -54,8 +50,8 @@ module.exports = function({creditCards, currentTeam, user}) {
mask: 'ccv',
placeholder: '###',
validateValue: data => {
const brand = state.cardNumber.brand.toLowerCase();
return ccValidator.doesCvvMatchType(data, brand);
const brand = state.cardNumber.brand.toLowerCase()
return ccValidator.doesCvvMatchType(data, brand)
}
},
@ -74,13 +70,13 @@ module.exports = function({creditCards, currentTeam, user}) {
async autoComplete(value) {
for (const country in countries) {
if (!Object.hasOwnProperty.call(countries, country)) {
continue;
continue
}
if (country.startsWith(value)) {
return country.substr(value.length);
return country.substr(value.length)
}
}
return false;
return false
},
validateValue: value => countries[value] !== undefined
},
@ -105,18 +101,18 @@ module.exports = function({creditCards, currentTeam, user}) {
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;
continue
}
const piece = state[key];
const piece = state[key]
if (typeof piece === 'string') {
console.log(piece);
console.log(piece)
} else if (typeof piece === 'object') {
let result;
let result
try {
/* eslint-disable no-await-in-loop */
result = await textInput({
@ -127,63 +123,63 @@ module.exports = function({creditCards, currentTeam, user}) {
validateKeypress: piece.validateKeypress,
validateValue: piece.validateValue,
autoComplete: piece.autoComplete
});
})
piece.value = result;
piece.value = result
if (key === 'cardNumber') {
let brand = cardBrands[ccValidator.determineCardType(result)];
piece.brand = brand;
let brand = cardBrands[ccValidator.determineCardType(result)]
piece.brand = brand
if (brand === 'American Express') {
state.ccv.placeholder = '#'.repeat(4);
state.ccv.placeholder = '#'.repeat(4)
} else {
state.ccv.placeholder = '#'.repeat(3);
state.ccv.placeholder = '#'.repeat(3)
}
brand = chalk.cyan(`[${brand}]`);
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[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`);
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 stopSpinner = wait(piece.label + result)
const addressInfo = await geocode({
country: state.country.value,
zipCode: result
});
})
if (addressInfo.state) {
state.state.initialValue = addressInfo.state;
state.state.initialValue = addressInfo.state
}
if (addressInfo.city) {
state.city.initialValue = addressInfo.city;
state.city.initialValue = addressInfo.city
}
stopSpinner();
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);
process.exit(1)
} else {
console.error(err);
console.error(err)
}
}
}
}
console.log(''); // New line
const stopSpinner = wait('Saving card');
console.log('') // New line
const stopSpinner = wait('Saving card')
try {
const res = await creditCards.add({
@ -196,23 +192,19 @@ module.exports = function({creditCards, currentTeam, user}) {
state: state.state.value,
city: state.city.value,
address1: state.address1.value
});
stopSpinner();
})
stopSpinner()
success(
`${state.cardNumber.brand} ending in ${res.last4} was added to ${
chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)
}`
);
`${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 ? 13 : 12;
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
state.error = `${chalk.red('> Error!')} ${err.message} Please make sure the info is correct`;
await render();
stopSpinner()
const linesToClear = state.error ? 13 : 12
process.stdout.write(ansiEscapes.eraseLines(linesToClear))
state.error = `${chalk.red('> Error!')} ${err.message} Please make sure the info is correct`
await render()
}
}
render().catch(console.error);
};
render().catch(console.error)
}

262
bin/now-billing.js

@ -1,23 +1,23 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path');
const { resolve } = require('path')
// Packages
const chalk = require('chalk');
const minimist = require('minimist');
const ms = require('ms');
const chalk = require('chalk')
const minimist = require('minimist')
const ms = require('ms')
// Ours
const login = require('../lib/login');
const cfg = require('../lib/cfg');
const { error } = require('../lib/error');
const NowCreditCards = require('../lib/credit-cards');
const indent = require('../lib/indent');
const listInput = require('../lib/utils/input/list');
const success = require('../lib/utils/output/success');
const promptBool = require('../lib/utils/input/prompt-bool');
const logo = require('../lib/utils/output/logo');
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const { error } = require('../lib/error')
const NowCreditCards = require('../lib/credit-cards')
const indent = require('../lib/indent')
const listInput = require('../lib/utils/input/list')
const success = require('../lib/utils/output/success')
const promptBool = require('../lib/utils/input/prompt-bool')
const logo = require('../lib/utils/output/logo')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -28,9 +28,9 @@ const argv = minimist(process.argv.slice(2), {
debug: 'd',
token: 't'
}
});
})
const subcommand = argv._[0];
const subcommand = argv._[0]
const help = () => {
console.log(
@ -66,15 +66,15 @@ const help = () => {
${chalk.gray('–')} If the id is omitted, you can choose interactively
`
);
};
)
}
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
const exit = code => {
@ -82,34 +82,34 @@ const exit = code => {
// because there's a node bug where
// stdout writes are asynchronous
// https://github.com/nodejs/node/issues/6456
setTimeout(() => process.exit(code || 0), 100);
};
setTimeout(() => process.exit(code || 0), 100)
}
if (argv.help || !subcommand) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({token, config});
await run({ token, config })
} catch (err) {
if (err.userError) {
error(err.message);
error(err.message)
} else {
error(`Unknown error: ${err.stack}`);
error(`Unknown error: ${err.stack}`)
}
exit(1);
exit(1)
}
});
})
}
// Builds a `choices` object that can be passesd to inquirer.prompt()
@ -117,34 +117,34 @@ 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 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');
].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);
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();
cards = await creditCards.ls()
} catch (err) {
error(err.message)
return
@ -153,219 +153,205 @@ async function run({token, config: {currentTeam, user}}) {
.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;
: ''
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}.`;
address += `, ${card.address_line2}.`
} else {
address += '.';
address += '.'
}
address += `\n${card.address_city}, `;
address += `\n${card.address_city}, `
if (card.address_state) {
address += `${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}`;
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')
})
.join('\n\n');
.join('\n\n')
const elapsed = ms(new Date() - start);
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}]`)}`
);
`> ${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`);
console.log(`\n${text}\n`)
}
break;
break
}
case 'set-default': {
if (args.length > 1) {
error('Invalid number of arguments');
return exit(1);
error('Invalid number of arguments')
return exit(1)
}
const start = new Date();
const start = new Date()
let cards
try {
cards = await creditCards.ls();
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);
error('You have no credit cards to choose from')
return exit(0)
}
let cardId = args[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);
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 label = `Are you sure that you to set this card as the default?`
const confirmation = await promptBool(label, {
trailing: '\n'
});
})
if (!confirmation) {
console.log('Aborted');
break;
console.log('Aborted')
break
}
const start = new Date();
await creditCards.setDefault(cardId);
const start = new Date()
await creditCards.setDefault(cardId)
const card = cards.cards.find(card => card.id === cardId);
const elapsed = ms(new Date() - start);
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');
console.log('No changes made')
}
break;
break
}
case 'rm':
case 'remove': {
if (args.length > 1) {
error('Invalid number of arguments');
return exit(1);
error('Invalid number of arguments')
return exit(1)
}
const start = new Date();
const start = new Date()
let cards
try {
cards = await creditCards.ls();
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);
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];
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);
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);
const label = `Are you sure that you want to remove this card?`
const confirmation = await promptBool(label)
if (!confirmation) {
console.log('Aborted');
break;
console.log('Aborted')
break
}
const start = new Date();
await creditCards.rm(cardId);
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);
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`;
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`;
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 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
)
}`;
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);
const elapsed = ms(new Date() - start)
text += ` ${chalk.gray(`[${elapsed}]`)}`
success(text)
} else {
console.log('No changes made');
console.log('No changes made')
}
break;
break
}
case 'add': {
require(resolve(__dirname, 'now-billing-add.js'))({creditCards, currentTeam, user});
require(resolve(__dirname, 'now-billing-add.js'))({
creditCards,
currentTeam,
user
})
break;
break
}
default:
error('Please specify a valid subcommand: ls | add | rm | set-default');
help();
exit(1);
error('Please specify a valid subcommand: ls | add | rm | set-default')
help()
exit(1)
}
creditCards.close();
creditCards.close()
}

256
bin/now-certs.js

@ -1,33 +1,33 @@
#!/usr/bin/env node
// Native
const path = require('path');
const path = require('path')
// Packages
const chalk = require('chalk');
const table = require('text-table');
const minimist = require('minimist');
const fs = require('fs-promise');
const ms = require('ms');
const printf = require('printf');
require('epipebomb')();
const supportsColor = require('supports-color');
const chalk = require('chalk')
const table = require('text-table')
const minimist = require('minimist')
const fs = require('fs-promise')
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 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];
const subcommand = argv._[0]
// Options
const help = () => {
@ -67,78 +67,78 @@ const help = () => {
${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';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
if (argv.help || !subcommand) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({ token, config });
await run({ token, config })
} catch (err) {
handleError(err);
exit(1);
handleError(err)
exit(1)
}
});
})
}
function formatExpirationDate(date) {
const diff = date - Date.now();
const diff = date - Date.now()
return diff < 0
? chalk.gray(ms(-diff) + ' ago')
: chalk.gray('in ' + ms(diff));
: 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();
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);
)
return exit(1)
}
const list = await certs.ls();
const elapsed = ms(new Date() - start);
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();
const cur = Date.now()
list.sort((a, b) => {
return a.cn.localeCompare(b.cn);
});
return a.cn.localeCompare(b.cn)
})
const maxCnLength =
list.reduce((acc, i) => {
return Math.max(acc, (i.cn && i.cn.length) || 0);
}, 0) + 1;
return Math.max(acc, (i.cn && i.cn.length) || 0)
}, 0) + 1
console.log(
chalk.dim(
@ -150,196 +150,196 @@ async function run({ token, config: { currentTeam, user } }) {
'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;
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`;
spec = ` %-${maxCnLength + 9}s %-18s %-20s %-20s\n`
} else {
spec = ` %-${maxCnLength}s %-8s %-10s %-10s\n`;
spec = ` %-${maxCnLength}s %-8s %-10s %-10s\n`
}
process.stdout.write(printf(spec, cn, time, expiration, autoRenew));
});
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);
)
return exit(1)
}
const cn = args[0];
let cert;
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);
)
return exit(1)
}
const crt = readX509File(argv.crt);
const key = readX509File(argv.key);
const ca = argv.ca ? readX509File(argv.ca) : '';
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);
cert = await certs.put(cn, crt, key, ca)
} else {
// Issue a standard certificate
cert = await certs.create(cn);
cert = await certs.create(cn)
}
if (!cert) {
// Cert is undefined and "Cert is already issued" has been printed to stdout
return exit(1);
return exit(1)
}
const elapsed = ms(new Date() - start);
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);
)
return exit(1)
}
const cert = await getCertIdCn(certs, args[0], currentTeam, user);
const cert = await getCertIdCn(certs, args[0], currentTeam, user)
if (!cert) {
return exit(1);
return exit(1)
}
const yes = await readConfirmation(
cert,
'The following certificate will be renewed\n'
);
)
if (!yes) {
error('User abort');
return exit(0);
error('User abort')
return exit(0)
}
await certs.renew(cert.cn);
const elapsed = ms(new Date() - start);
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);
)
return exit(1)
}
const crt = readX509File(argv.crt);
const key = readX509File(argv.key);
const ca = argv.ca ? readX509File(argv.ca) : '';
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);
const cert = await getCertIdCn(certs, args[0], currentTeam, user)
if (!cert) {
return exit(1);
return exit(1)
}
const yes = await readConfirmation(
cert,
'The following certificate will be replaced permanently\n'
);
)
if (!yes) {
error('User abort');
return exit(0);
error('User abort')
return exit(0)
}
await certs.put(cert.cn, crt, key, ca);
const elapsed = ms(new Date() - start);
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);
)
return exit(1)
}
const cert = await getCertIdCn(certs, args[0], currentTeam, user);
const cert = await getCertIdCn(certs, args[0], currentTeam, user)
if (!cert) {
return exit(1);
return exit(1)
}
const yes = await readConfirmation(
cert,
'The following certificate will be removed permanently\n'
);
)
if (!yes) {
error('User abort');
return exit(0);
error('User abort')
return exit(0)
}
await certs.delete(cert.cn);
const elapsed = ms(new Date() - start);
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);
)
help()
exit(1)
}
return certs.close();
return certs.close()
}
process.on('uncaughtException', err => {
handleError(err);
exit(1);
});
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 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(`> ${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');
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
})
.resume();
});
.resume()
})
}
function readX509File(file) {
return fs.readFileSync(path.resolve(file), 'utf8');
return fs.readFileSync(path.resolve(file), 'utf8')
}
async function getCertIdCn(certs, idOrCn, currentTeam, user) {
const list = await certs.ls();
const list = await certs.ls()
const thecert = list.filter(cert => {
return cert.uid === idOrCn || cert.cn === idOrCn;
})[0];
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 null
}
return thecert;
return thecert
}

492
bin/now-deploy.js

@ -1,41 +1,41 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path');
const { resolve } = require('path')
// Packages
const Progress = require('progress');
const fs = require('fs-promise');
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 Progress = require('progress')
const fs = require('fs-promise')
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')
// 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 promptOptions = require('../lib/utils/prompt-options');
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 note = require('../lib/utils/output/note');
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 promptOptions = require('../lib/utils/prompt-options')
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 note = require('../lib/utils/output/note')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token', 'name', 'alias'],
@ -70,7 +70,7 @@ const argv = minimist(process.argv.slice(2), {
name: 'n',
alias: 'a'
}
});
})
const help = () => {
console.log(
@ -149,48 +149,48 @@ const help = () => {
${chalk.cyan('$ now help list')}
`
);
};
)
}
let path = argv._[0];
let path = argv._[0]
if (path) {
// If path is relative: resolve
// if path is absolute: clear up strange `/` etc
path = resolve(process.cwd(), path);
path = resolve(process.cwd(), path)
} else {
path = process.cwd();
path = process.cwd()
}
// If the current deployment is a repo
const gitRepo = {};
const gitRepo = {}
// Options
let forceNew = argv.force;
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 deploymentName = argv.name || false;
const apiUrl = argv.url || 'https://api.zeit.co';
const isTTY = process.stdout.isTTY;
const quiet = !isTTY;
let forceNew = argv.force
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 deploymentName = argv.name || false
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]);
: flatten([argv.alias])
if (argv.config) {
cfg.setConfigFile(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');
)
console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n')
}
// Create a new deployment if user changed
@ -198,183 +198,183 @@ if (Array.isArray(autoAliases)) {
// This should just work fine because it doesn't
// force a new sync, it just forces a new deployment.
if (deploymentName || wantsPublic) {
forceNew = true;
forceNew = true
}
let alwaysForwardNpm;
let alwaysForwardNpm
Promise.resolve().then(async () => {
const config = await cfg.read();
alwaysForwardNpm = config.forwardNpm;
const config = await cfg.read()
alwaysForwardNpm = config.forwardNpm
if (argv.h || argv.help) {
help();
exit(0);
help()
exit(0)
} else if (argv.v || argv.version) {
console.log(version);
process.exit(0);
console.log(version)
process.exit(0)
} else if (!(argv.token || config.token) || shouldLogin) {
let token;
let token
try {
token = await login(apiUrl);
token = await login(apiUrl)
} catch (err) {
error(`Authentication error – ${err.message}`);
process.exit(1);
error(`Authentication error – ${err.message}`)
process.exit(1)
}
if (shouldLogin) {
console.log('> Logged in successfully. Token saved in ~/.now.json');
process.exit(0);
console.log('> Logged in successfully. Token saved in ~/.now.json')
process.exit(0)
} else {
sync({ token, config }).catch(err => {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
});
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
})
}
} else {
sync({ token: argv.token || config.token, config }).catch(err => {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
});
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
})
}
});
})
async function sync({ token, config: { currentTeam, user } }) {
const start = Date.now();
const rawPath = argv._[0];
const start = Date.now()
const rawPath = argv._[0]
const planPromise = new NowPlans({
apiUrl,
token,
debug,
currentTeam
}).getCurrent();
}).getCurrent()
const stopDeployment = msg => {
error(msg);
process.exit(1);
};
error(msg)
process.exit(1)
}
const isValidRepo = isRepoPath(rawPath);
const isValidRepo = isRepoPath(rawPath)
try {
await fs.stat(path);
await fs.stat(path)
} catch (err) {
let repo;
let repo
if (isValidRepo && isValidRepo !== 'no-valid-url') {
const gitParts = gitPathParts(rawPath);
Object.assign(gitRepo, gitParts);
const gitParts = gitPathParts(rawPath)
Object.assign(gitRepo, gitParts)
const searchMessage = setTimeout(() => {
console.log(`> Didn't find directory. Searching on ${gitRepo.type}...`);
}, 500);
console.log(`> Didn't find directory. Searching on ${gitRepo.type}...`)
}, 500)
try {
repo = await fromGit(rawPath, debug);
repo = await fromGit(rawPath, debug)
} catch (err) {}
clearTimeout(searchMessage);
clearTimeout(searchMessage)
}
if (repo) {
// Tell now which directory to deploy
path = repo.path;
path = repo.path
// Set global variable for deleting tmp dir later
// once the deployment has finished
Object.assign(gitRepo, repo);
Object.assign(gitRepo, repo)
} else if (isValidRepo === 'no-valid-url') {
stopDeployment(
`This URL is neither a valid repository from GitHub, nor from GitLab.`
);
)
} else if (isValidRepo) {
const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : '';
const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : ''
stopDeployment(
`There's no repository named "${chalk.bold(gitRepo.main)}" ${gitRef}on ${gitRepo.type}`
);
)
} else {
stopDeployment(`Could not read directory ${chalk.bold(path)}`);
stopDeployment(`Could not read directory ${chalk.bold(path)}`)
}
}
// Make sure that directory is deployable
try {
await checkPath(path);
await checkPath(path)
} catch (err) {
error(err);
return;
error(err)
return
}
if (!quiet) {
if (gitRepo.main) {
const gitRef = gitRepo.ref ? ` at "${chalk.bold(gitRepo.ref)}" ` : '';
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;
let deploymentType
let hasPackage;
let hasDockerfile;
let isStatic;
let hasPackage
let hasDockerfile
let isStatic
if (argv.docker) {
if (debug) {
console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``);
console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``)
}
deploymentType = 'docker';
deploymentType = 'docker'
} else if (argv.npm) {
deploymentType = 'npm';
deploymentType = 'npm'
} else if (argv.static) {
if (debug) {
console.log(`> [debug] Forcing static deployment`);
console.log(`> [debug] Forcing static deployment`)
}
deploymentType = 'npm';
isStatic = true;
deploymentType = 'npm'
isStatic = true
} else {
try {
await fs.stat(resolve(path, 'package.json'));
await fs.stat(resolve(path, 'package.json'))
} catch (err) {
hasPackage = true;
hasPackage = true
}
[hasPackage, hasDockerfile] = await Promise.all([
;[hasPackage, hasDockerfile] = await Promise.all([
await (async () => {
try {
await fs.stat(resolve(path, 'package.json'));
await fs.stat(resolve(path, 'package.json'))
} catch (err) {
return false;
return false
}
return true;
return true
})(),
await (async () => {
try {
await fs.stat(resolve(path, 'Dockerfile'));
await fs.stat(resolve(path, 'Dockerfile'))
} catch (err) {
return false;
return false
}
return true;
return true
})()
]);
])
if (hasPackage && hasDockerfile) {
if (debug) {
console.log('[debug] multiple manifests found, disambiguating');
console.log('[debug] multiple manifests found, disambiguating')
}
if (isTTY) {
try {
console.log(
`> Two manifests found. Press [${chalk.bold('n')}] to deploy or re-run with --flag`
);
)
deploymentType = await promptOptions([
[
'npm',
@ -384,41 +384,41 @@ async function sync({ token, config: { currentTeam, user } }) {
'docker',
`${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `
]
]);
])
} catch (err) {
error(err.message);
process.exit(1);
error(err.message)
process.exit(1)
}
} else {
error(
'Ambiguous deployment (`package.json` and `Dockerfile` found). ' +
'Please supply `--npm` or `--docker` to disambiguate.'
);
)
}
} else if (hasPackage) {
if (debug) {
console.log(
'> [debug] `package.json` found, assuming `deploymentType` = `npm`'
);
)
}
deploymentType = 'npm';
deploymentType = 'npm'
} else if (hasDockerfile) {
if (debug) {
console.log(
'> [debug] `Dockerfile` found, assuming `deploymentType` = `docker`'
);
)
}
deploymentType = 'docker';
deploymentType = 'docker'
} else {
if (debug) {
console.log(
'> [debug] No manifest files found, assuming static deployment'
);
)
}
isStatic = true;
isStatic = true
}
}
@ -427,129 +427,129 @@ async function sync({ token, config: { currentTeam, user } }) {
deploymentName,
isStatic,
quiet: true
});
})
const now = new Now({ apiUrl, token, debug, currentTeam });
const now = new Now({ apiUrl, token, debug, currentTeam })
let dotenvConfig;
let dotenvOption;
let dotenvConfig
let dotenvOption
if (argv.dotenv) {
dotenvOption = argv.dotenv;
dotenvOption = argv.dotenv
} else if (nowConfig && nowConfig.dotenv) {
dotenvOption = nowConfig.dotenv;
dotenvOption = nowConfig.dotenv
}
if (dotenvOption) {
const dotenvFileName = typeof dotenvOption === 'string'
? dotenvOption
: '.env';
: '.env'
if (!fs.existsSync(dotenvFileName)) {
error(`--dotenv flag is set but ${dotenvFileName} file is missing`);
return process.exit(1);
error(`--dotenv flag is set but ${dotenvFileName} file is missing`)
return process.exit(1)
}
const dotenvFile = await fs.readFile(dotenvFileName);
dotenvConfig = dotenv.parse(dotenvFile);
const dotenvFile = await fs.readFile(dotenvFileName)
dotenvConfig = dotenv.parse(dotenvFile)
}
// Merge `now.env` from package.json with `-e` arguments.
const pkgEnv = nowConfig && nowConfig.env;
const pkgEnv = nowConfig && nowConfig.env
const envs = [
...Object.keys(dotenvConfig || {}).map(k => `${k}=${dotenvConfig[k]}`),
...Object.keys(pkgEnv || {}).map(k => `${k}=${pkgEnv[k]}`),
...[].concat(argv.env || [])
];
]
let secrets;
let secrets
const findSecret = async uidOrName => {
if (!secrets) {
secrets = await now.listSecrets();
secrets = await now.listSecrets()
}
return secrets.filter(secret => {
return secret.name === uidOrName || secret.uid === uidOrName;
});
};
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);
error('Env key and value missing')
return process.exit(1)
}
const [key, ...rest] = kv.split('=');
let val;
const [key, ...rest] = kv.split('=')
let val
if (rest.length > 0) {
val = rest.join('=');
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);
)
return process.exit(1)
}
if (!key) {
error(`Invalid env option ${chalk.bold(`"${kv}"`)}`);
return process.exit(1);
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(/^@/, '\\@');
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);
)
return process.exit(1)
}
}
if (val[0] === '@') {
const uidOrName = val.substr(1);
const secrets = await findSecret(uidOrName);
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);
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);
)
return process.exit(1)
}
val = { uid: secrets[0].uid };
val = { uid: secrets[0].uid }
}
return [key, typeof val === 'string' ? val.replace(/^\\@/, '@') : val];
return [key, typeof val === 'string' ? val.replace(/^\\@/, '@') : val]
})
);
)
const env = {};
const env = {}
env_.filter(v => Boolean(v)).forEach(([key, val]) => {
if (key in env) {
note(`Overriding duplicate env key ${chalk.bold(`"${key}"`)}`);
note(`Overriding duplicate env key ${chalk.bold(`"${key}"`)}`)
}
env[key] = val;
});
env[key] = val
})
try {
await now.create(path, {
@ -563,87 +563,85 @@ async function sync({ token, config: { currentTeam, user } }) {
quiet,
wantsPublic,
isStatic
});
})
} catch (err) {
if (debug) {
console.log(`> [debug] error: ${err}\n${err.stack}`);
console.log(`> [debug] error: ${err}\n${err.stack}`)
}
handleError(err);
process.exit(1);
handleError(err)
process.exit(1)
}
const { url } = now;
const elapsed = ms(new Date() - start);
const { url } = now
const elapsed = ms(new Date() - start)
if (isTTY) {
if (clipboard) {
try {
await copy(url);
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}]`
);
console.log(`${chalk.cyan('> Ready!')} ${chalk.bold(url)} [${elapsed}]`)
}
} else {
console.log(`> ${url} [${elapsed}]`);
console.log(`> ${url} [${elapsed}]`)
}
} else {
process.stdout.write(url);
process.stdout.write(url)
}
const startU = new Date();
const startU = new Date()
const complete = () => {
if (!quiet) {
const elapsedU = ms(new Date() - startU);
console.log(`> Sync complete (${bytes(now.syncAmount)}) [${elapsedU}] `);
console.log('> Initializing…');
const elapsedU = ms(new Date() - startU)
console.log(`> Sync complete (${bytes(now.syncAmount)}) [${elapsedU}] `)
console.log('> Initializing…')
}
// Close http2 agent
now.close();
now.close()
// Show build logs
printLogs(now.host, token, currentTeam);
};
printLogs(now.host, token, currentTeam)
}
const plan = await planPromise;
const plan = await planPromise
if (plan.id === 'oss') {
if (isTTY) {
info(
`${chalk.bold((currentTeam && `${currentTeam.slug} is`) || `You (${user.username || user.email}) are`)} on the OSS plan. Your code will be made ${chalk.bold('public')}.`
);
)
let proceed;
let proceed
try {
const label = 'Are you sure you want to proceed with the deployment?';
proceed = await promptBool(label, { trailing: eraseLines(1) });
const label = 'Are you sure you want to proceed with the deployment?'
proceed = await promptBool(label, { trailing: eraseLines(1) })
} catch (err) {
if (err.message === 'USER_ABORT') {
proceed = false;
proceed = false
} else {
throw err;
throw err
}
}
if (!proceed) {
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();
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) {
let msg = '\nYou are on the OSS plan. Your code will be made public.';
msg += ' If you agree with that, please run again with --public.';
console.log(msg);
return exit(1);
let msg = '\nYou are on the OSS plan. Your code will be made public.'
msg += ' If you agree with that, please run again with --public.'
console.log(msg)
return exit(1)
}
}
@ -653,98 +651,98 @@ async function sync({ token, config: { currentTeam, user } }) {
complete: '=',
incomplete: '',
total: now.syncAmount
});
})
now.upload();
now.upload()
now.on('upload', ({ names, data }) => {
const amount = data.length;
const amount = data.length
if (debug) {
console.log(
`> [debug] Uploaded: ${names.join(' ')} (${bytes(data.length)})`
);
)
}
bar.tick(amount);
});
bar.tick(amount)
})
now.on('complete', complete);
now.on('complete', complete)
now.on('error', err => {
error('Upload failed');
handleError(err);
process.exit(1);
});
error('Upload failed')
handleError(err)
process.exit(1)
})
} else {
if (!quiet) {
console.log(`> Initializing…`);
console.log(`> Initializing…`)
}
// Close http2 agent
now.close();
now.close()
// Show build logs
printLogs(now.host, token, currentTeam);
printLogs(now.host, token, currentTeam)
}
}
function printLogs(host, token, currentTeam) {
// Log build
const logger = new Logger(host, token, { debug, quiet });
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');
error('Deployment failed')
}
}
if (gitRepo && gitRepo.cleanup) {
// Delete temporary directory that contains repository
gitRepo.cleanup();
gitRepo.cleanup()
if (debug) {
console.log(`> [debug] Removed temporary repo directory`);
console.log(`> [debug] Removed temporary repo directory`)
}
}
process.exit(1);
});
process.exit(1)
})
logger.on('close', async () => {
if (Array.isArray(autoAliases)) {
const aliasList = autoAliases.filter(item => item !== '');
const aliasList = autoAliases.filter(item => item !== '')
if (aliasList.length > 0) {
const assignments = [];
const assignments = []
for (const alias of aliasList) {
assignments.push(
assignAlias(alias, token, host, apiUrl, debug, currentTeam)
);
)
}
await Promise.all(assignments);
await Promise.all(assignments)
} else {
await reAlias(token, host, null, help, exit, apiUrl, debug);
await reAlias(token, host, null, help, exit, apiUrl, debug)
}
}
if (!quiet) {
console.log(`${chalk.cyan('> Deployment complete!')}`);
console.log(`${chalk.cyan('> Deployment complete!')}`)
}
if (gitRepo && gitRepo.cleanup) {
// Delete temporary directory that contains repository
gitRepo.cleanup();
gitRepo.cleanup()
if (debug) {
console.log(`> [debug] Removed temporary repo directory`);
console.log(`> [debug] Removed temporary repo directory`)
}
}
process.exit(0);
});
process.exit(0)
})
}

205
bin/now-dns.js

@ -1,20 +1,20 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk');
const minimist = require('minimist');
const ms = require('ms');
const table = require('text-table');
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 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'],
@ -25,9 +25,9 @@ const argv = minimist(process.argv.slice(2), {
debug: 'd',
token: 't'
}
});
})
const subcommand = argv._[0];
const subcommand = argv._[0]
// Options
const help = () => {
@ -62,71 +62,72 @@ const help = () => {
${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';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
if (argv.help || !subcommand) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({token, config});
await run({ token, config })
} catch (err) {
handleError(err);
exit(1);
handleError(err)
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();
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);
)
return exit(1)
}
const elapsed = ms(new Date() - start);
const res = await domainRecords.ls(args[0]);
const text = [];
let count = 0;
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;
count += records.length
if (records.length > 0) {
const cur = Date.now();
const cur = Date.now()
const header = [
['', 'id', 'name', 'type', 'value', 'aux', 'created'].map(s =>
chalk.dim(s))
];
chalk.dim(s)
)
]
const out = table(
header.concat(
records.map(record => {
const time = chalk.gray(
ms(cur - new Date(Number(record.created))) + ' ago'
);
)
return [
'',
record.id,
@ -135,7 +136,7 @@ async function run({token, config: {currentTeam, user}}) {
record.value,
record.mxPriority || record.priority || '',
time
];
]
})
),
{
@ -143,93 +144,85 @@ async function run({token, config: {currentTeam, user}}) {
hsep: ' '.repeat(2),
stringLength: strlen
}
);
text.push(`\n\n${chalk.bold(domain)}\n${indent(out, 2)}`);
)
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(''));
`> ${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);
const param = parseAddArgs(args)
if (!param) {
error(
`Invalid number of arguments. See: ${chalk.cyan('`now dns --help`')} for usage.`
);
return exit(1);
)
return exit(1)
}
const record = await domainRecords.create(param.domain, param.data);
const elapsed = ms(new Date() - start);
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
)
})`
);
`${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);
)
return exit(1)
}
const record = await domainRecords.getRecord(args[0]);
const record = await domainRecords.getRecord(args[0])
if (!record) {
error('DNS record not found');
return exit(1);
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);
error('User abort')
return exit(0)
}
await domainRecords.delete(record.domain, record.id);
const elapsed = ms(new Date() - start);
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);
error('Please specify a valid subcommand: ls | add | rm')
help()
exit(1)
}
return domainRecords.close();
return domainRecords.close()
}
process.on('uncaughtException', err => {
handleError(err);
exit(1);
});
handleError(err)
exit(1)
})
function parseAddArgs(args) {
if (!args || args.length < 4) {
return null;
return null
}
const domain = args[0];
const name = args[1] === '@' ? '' : args[1].toString();
const type = args[2];
const value = args[3];
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;
return null
}
if (type === 'MX') {
if (args.length !== 5) {
return null;
return null
}
return {
@ -240,10 +233,10 @@ function parseAddArgs(args) {
value,
mxPriority: args[4]
}
};
}
} else if (type === 'SRV') {
if (args.length !== 7) {
return null;
return null
}
return {
@ -258,11 +251,11 @@ function parseAddArgs(args) {
target: args[6]
}
}
};
}
}
if (args.length !== 4) {
return null;
return null
}
return {
@ -272,14 +265,14 @@ function parseAddArgs(args) {
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(
[
[
@ -291,20 +284,20 @@ function readConfirmation(record, msg) {
]
],
{ align: ['l', 'r', 'l'], hsep: ' '.repeat(6) }
);
)
process.stdout.write(`> ${msg}`);
process.stdout.write(' ' + tbl + '\n');
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');
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
})
.resume();
});
.resume()
})
}

212
bin/now-domains.js

@ -1,23 +1,23 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path');
const { resolve } = require('path')
// Packages
const chalk = require('chalk');
const minimist = require('minimist');
const table = require('text-table');
const ms = require('ms');
const chalk = require('chalk')
const minimist = require('minimist')
const table = require('text-table')
const ms = require('ms')
// Ours
const login = require('../lib/login');
const cfg = require('../lib/cfg');
const { error } = require('../lib/error');
const toHost = require('../lib/to-host');
const strlen = require('../lib/strlen');
const NowDomains = require('../lib/domains');
const exit = require('../lib/utils/exit');
const logo = require('../lib/utils/output/logo');
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const { error } = require('../lib/error')
const toHost = require('../lib/to-host')
const strlen = require('../lib/strlen')
const NowDomains = require('../lib/domains')
const exit = require('../lib/utils/exit')
const logo = require('../lib/utils/output/logo')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -30,9 +30,9 @@ const argv = minimist(process.argv.slice(2), {
force: 'f',
token: 't'
}
});
})
const subcommand = argv._[0];
const subcommand = argv._[0]
// Options
const help = () => {
@ -103,76 +103,76 @@ const help = () => {
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';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
if (argv.help || !subcommand) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({ token, config });
await run({ token, config })
} catch (err) {
if (err.userError) {
error(err.message);
error(err.message)
} else {
error(`Unknown error: ${err}\n${err.stack}`);
error(`Unknown error: ${err}\n${err.stack}`)
}
exit(1);
exit(1)
}
});
})
}
async function run({ token, config: { currentTeam, user } }) {
const domain = new NowDomains({ apiUrl, token, debug, currentTeam });
const args = argv._.slice(1);
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);
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 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 = [
['', 'id', 'dns', 'domain', '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 ns = domain.isExternal ? 'external' : 'zeit.world'
const url = chalk.bold(domain.name)
const time = chalk.gray(
ms(current - new Date(domain.created)) + ' ago'
);
return ['', domain.uid, ns, url, domain.verified, time];
)
return ['', domain.uid, ns, url, domain.verified, time]
})
),
{
@ -180,42 +180,42 @@ async function run({ token, config: { currentTeam, user } }) {
hsep: ' '.repeat(2),
stringLength: strlen
}
);
)
const elapsed_ = ms(new Date() - start_);
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');
console.log('\n' + out + '\n')
}
break;
break
}
case 'rm':
case 'remove': {
if (args.length !== 1) {
error('Invalid number of arguments');
return exit(1);
error('Invalid number of arguments')
return exit(1)
}
const _target = String(args[0]);
const _target = String(args[0])
if (!_target) {
const err = new Error('No domain specified');
err.userError = true;
throw err;
const err = new Error('No domain specified')
err.userError = true
throw err
}
const _domains = await domain.ls();
const _domain = findDomain(_target, _domains);
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;
)
err.userError = true
throw err
}
try {
@ -223,57 +223,57 @@ async function run({ token, config: { currentTeam, user } }) {
domain,
_domain,
_domains
)).toLowerCase();
)).toLowerCase()
if (confirmation !== 'y' && confirmation !== 'yes') {
console.log('\n> Aborted');
process.exit(0);
console.log('\n> Aborted')
process.exit(0)
}
const start = new Date();
await domain.rm(_domain.name);
const elapsed = ms(new Date() - start);
const start = new Date()
await domain.rm(_domain.name)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Domain ${chalk.bold(_domain.uid)} removed [${elapsed}]`
);
)
} catch (err) {
error(err);
exit(1);
error(err)
exit(1)
}
break;
break
}
case 'add':
case 'set': {
if (args.length !== 1) {
error('Invalid number of arguments');
return exit(1);
error('Invalid number of arguments')
return exit(1)
}
const start = new Date();
const name = String(args[0]);
const start = new Date()
const name = String(args[0])
const { uid, code, created, verified } = await domain.add(
name,
argv.force,
argv.external
);
const elapsed = ms(new Date() - start);
)
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;
break
}
case 'buy': {
await require(resolve(__dirname, 'domains', 'buy.js'))({
@ -281,73 +281,69 @@ async function run({ token, config: { currentTeam, user } }) {
args,
currentTeam,
user
});
break;
})
break
}
default:
error('Please specify a valid subcommand: ls | add | rm');
help();
exit(1);
error('Please specify a valid subcommand: ls | add | rm')
help()
exit(1)
}
domain.close();
domain.close()
}
async function readConfirmation(domain, _domain) {
return new Promise(resolve => {
const time = chalk.gray(
ms(new Date() - new Date(_domain.created)) + ' ago'
);
const time = chalk.gray(ms(new Date() - new Date(_domain.created)) + ' ago')
const tbl = table(
[[_domain.uid, chalk.underline(`https://${_domain.name}`), time]],
{ align: ['l', 'r', 'l'], hsep: ' '.repeat(6) }
);
)
process.stdout.write(
'> The following domain will be removed permanently\n'
);
process.stdout.write(' ' + tbl + '\n');
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.\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());
process.stdin.pause()
resolve(d.toString().trim())
})
.resume();
});
.resume()
})
}
function findDomain(val, list) {
return list.find(d => {
if (d.uid === val) {
if (debug) {
console.log(`> [debug] matched domain ${d.uid} by uid`);
console.log(`> [debug] matched domain ${d.uid} by uid`)
}
return true;
return true
}
// Match prefix
if (d.name === toHost(val)) {
if (debug) {
console.log(`> [debug] matched domain ${d.uid} by name ${d.name}`);
console.log(`> [debug] matched domain ${d.uid} by name ${d.name}`)
}
return true;
return true
}
return false;
});
return false
})
}

186
bin/now-list.js

@ -1,20 +1,20 @@
#!/usr/bin/env node
// Packages
const fs = require('fs-promise');
const minimist = require('minimist');
const chalk = require('chalk');
const ms = require('ms');
const printf = require('printf');
require('epipebomb')();
const supportsColor = require('supports-color');
const fs = require('fs-promise')
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 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 argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -25,7 +25,7 @@ const argv = minimist(process.argv.slice(2), {
debug: 'd',
token: 't'
}
});
})
const help = () => {
console.log(
@ -51,128 +51,128 @@ const help = () => {
${chalk.dim('Alias:')} ls
`
);
};
)
}
if (argv.help) {
help();
process.exit(0);
help()
process.exit(0)
}
const app = argv._[0];
const app = argv._[0]
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
process.exit(1);
error(`Authentication error – ${err.message}`)
process.exit(1)
}
try {
await list({ token, config });
await list({ token, config })
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
}
});
})
async function list({ token, config: { currentTeam, user } }) {
const now = new Now({ apiUrl, token, debug, currentTeam });
const start = new Date();
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);
console.log('> You must define an app when using `--all`')
process.exit(1)
}
let deployments;
let deployments
try {
deployments = await now.list(app);
deployments = await now.list(app)
} catch (err) {
handleError(err);
process.exit(1);
handleError(err)
process.exit(1)
}
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) {
const match = await now.findDeployment(app);
const match = await now.findDeployment(app)
if (match !== null && typeof match !== 'undefined') {
deployments = Array.of(match);
deployments = Array.of(match)
}
}
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) {
const aliases = await now.listAliases();
const aliases = await now.listAliases()
const item = aliases.find(e => e.uid === app || e.alias === app);
const item = aliases.find(e => e.uid === app || e.alias === app)
if (item) {
const match = await now.findDeployment(item.deploymentId);
const match = await now.findDeployment(item.deploymentId)
if (match !== null && typeof match !== 'undefined') {
deployments = Array.of(match);
deployments = Array.of(match)
}
}
}
now.close();
now.close()
const apps = new Map();
const apps = new Map()
if (argv.all) {
await Promise.all(
deployments.map(async ({ uid }, i) => {
deployments[i].instances = await now.listInstances(uid);
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 deps = apps.get(dep.name) || []
apps.set(dep.name, deps.concat(dep))
}
const sorted = await sort([...apps]);
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();
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;
let shouldShowAllInfo = false
for (const app of apps) {
shouldShowAllInfo =
app[1].length > 5 ||
app.find(depl => {
return depl.scale && depl.scale.current > 1;
});
return depl.scale && depl.scale.current > 1
})
if (shouldShowAllInfo) {
break;
break
}
}
if (!argv.all && shouldShowAllInfo) {
console.log(
`> To expand the list and see instances run ${chalk.cyan('`now ls --all [app]`')}`
);
)
}
console.log();
console.log()
sorted.forEach(([name, deps]) => {
const listedDeployments = argv.all ? deps : deps.slice(0, 5);
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`;
)
const urlSpec = `%-${urlLength}s`
console.log(
printf(
` ${chalk.grey(urlSpec + ' %8s %-16s %8s')}`,
@ -181,25 +181,25 @@ async function list({ token, config: { currentTeam, user } }) {
'state',
'age'
)
);
)
listedDeployments.forEach(dep => {
let state = dep.state;
let extraSpaceForState = 0;
let state = dep.state
let extraSpaceForState = 0
if (state === null || typeof state === 'undefined') {
state = 'DEPLOYMENT_ERROR';
state = 'DEPLOYMENT_ERROR'
}
if (/ERROR/.test(state)) {
state = chalk.red(state);
extraSpaceForState = 10;
state = chalk.red(state)
extraSpaceForState = 10
} else if (state === 'FROZEN') {
state = chalk.grey(state);
extraSpaceForState = 10;
state = chalk.grey(state)
extraSpaceForState = 10
}
let spec;
let spec
if (supportsColor) {
spec = ` %-${urlLength + 10}s %8s %-${extraSpaceForState + 16}s %8s`;
spec = ` %-${urlLength + 10}s %8s %-${extraSpaceForState + 16}s %8s`
} else {
spec = ` %-${urlLength + 1}s %8s %-${16}s %8s`;
spec = ` %-${urlLength + 1}s %8s %-${16}s %8s`
}
console.log(
@ -210,45 +210,45 @@ async function list({ token, config: { currentTeam, user } }) {
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()
}
});
console.log();
});
})
console.log()
})
}
async function sort(apps) {
let pkg;
let pkg
try {
const json = await fs.readFile('package.json');
pkg = JSON.parse(json);
const json = await fs.readFile('package.json')
pkg = JSON.parse(json)
} catch (err) {
pkg = {};
pkg = {}
}
return apps
.map(([name, deps]) => {
deps = deps.slice().sort((a, b) => {
return b.created - a.created;
});
return [name, deps];
return b.created - a.created
})
return [name, deps]
})
.sort(([nameA, depsA], [nameB, depsB]) => {
if (pkg.name === nameA) {
return -1;
return -1
}
if (pkg.name === nameB) {
return 1;
return 1
}
return depsB[0].created - depsA[0].created;
});
return depsB[0].created - depsA[0].created
})
}

86
bin/now-logout.js

@ -1,14 +1,14 @@
#!/usr/bin/env node
// Packages
const minimist = require('minimist');
const chalk = require('chalk');
const fetch = require('node-fetch');
const ora = require('ora');
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 cfg = require('../lib/cfg')
const logo = require('../lib/utils/output/logo')
const argv = minimist(process.argv.slice(2), {
string: ['config'],
@ -17,7 +17,7 @@ const argv = minimist(process.argv.slice(2), {
help: 'h',
config: 'c'
}
});
})
const help = () => {
console.log(
@ -35,92 +35,92 @@ const help = () => {
${chalk.cyan('$ now logout')}
`
);
};
)
}
if (argv.help) {
help();
process.exit(0);
help()
process.exit(0)
}
const apiUrl = argv.url || 'https://api.zeit.co';
const endpoint = apiUrl + '/www/user/tokens/';
const apiUrl = argv.url || 'https://api.zeit.co'
const endpoint = apiUrl + '/www/user/tokens/'
if (argv.config) {
cfg.setConfigFile(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();
const result = await fetch(endpoint, requestHeaders(token))
const tokenList = await result.json()
if (!tokenList.tokens) {
return;
return
}
const tokenInfo = tokenList.tokens.find(t => token === t.token);
const tokenInfo = tokenList.tokens.find(t => token === t.token)
if (!tokenInfo) {
return;
return
}
return tokenInfo.id;
};
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);
Object.assign(details, requestHeaders(token))
const result = await fetch(endpoint + encodeURIComponent(tokenId), details)
if (!result.ok) {
console.error('Not able to log out');
console.error('Not able to log out')
}
};
}
const logout = async () => {
const spinner = ora({
text: 'Logging out...'
}).start();
}).start()
const config = await cfg.read();
const config = await cfg.read()
try {
await cfg.removeFile();
await cfg.removeFile()
} catch (err) {
spinner.fail(`Couldn't remove config while logging out`);
process.exit(1);
spinner.fail(`Couldn't remove config while logging out`)
process.exit(1)
}
let tokenId;
let tokenId
try {
tokenId = await getTokenId(argv.token || config.token);
tokenId = await getTokenId(argv.token || config.token)
} catch (err) {
spinner.fail('Not able to get token id on logout');
process.exit(1);
spinner.fail('Not able to get token id on logout')
process.exit(1)
}
if (!tokenId) {
return;
return
}
try {
await revokeToken(argv.token || config.token, tokenId);
await revokeToken(argv.token || config.token, tokenId)
} catch (err) {
spinner.fail('Could not revoke token on logout');
process.exit(1);
spinner.fail('Could not revoke token on logout')
process.exit(1)
}
spinner.succeed('Logged out!');
};
spinner.succeed('Logged out!')
}
logout();
logout()

220
bin/now-logs.js

@ -1,17 +1,17 @@
#!/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 } = require('../lib/utils/url');
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 } = require('../lib/utils/url')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'query', 'since', 'token', 'until'],
@ -24,9 +24,9 @@ const argv = minimist(process.argv.slice(2), {
token: 't',
query: 'q'
}
});
})
let deploymentIdOrURL = argv._[0];
let deploymentIdOrURL = argv._[0]
const help = () => {
console.log(
@ -52,195 +52,195 @@ const help = () => {
${chalk.cyan('$ now logs deploymentId')}
`
);
};
)
}
if (argv.help || !deploymentIdOrURL) {
help();
process.exit(0);
help()
process.exit(0)
}
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(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'];
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;
let since
try {
since = argv.since ? toSerial(argv.since) : null;
since = argv.since ? toSerial(argv.since) : null
} catch (err) {
error(`Invalid date string: ${argv.since}`);
process.exit(1);
error(`Invalid date string: ${argv.since}`)
process.exit(1)
}
let until;
let until
try {
until = argv.until ? toSerial(argv.until) : null;
until = argv.until ? toSerial(argv.until) : null
} catch (err) {
error(`Invalid date string: ${argv.until}`);
process.exit(1);
error(`Invalid date string: ${argv.until}`)
process.exit(1)
}
if (maybeURL(deploymentIdOrURL)) {
const normalizedURL = normalizeURL(deploymentIdOrURL);
const normalizedURL = normalizeURL(deploymentIdOrURL)
if (normalizedURL.includes('/')) {
error(`Invalid deployment url: can't include path (${deploymentIdOrURL})`);
process.exit(1);
error(`Invalid deployment url: can't include path (${deploymentIdOrURL})`)
process.exit(1)
}
deploymentIdOrURL = normalizedURL;
deploymentIdOrURL = normalizedURL
}
Promise.resolve()
.then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || login(apiUrl);
token = argv.token || config.token || login(apiUrl)
} catch (err) {
error(`Authentication error – ${err.message}`);
process.exit(1);
error(`Authentication error – ${err.message}`)
process.exit(1)
}
await printLogs({ token, config });
await printLogs({ token, config })
})
.catch(err => {
error(`Unknown error: ${err.stack}`);
process.exit(1);
});
error(`Unknown error: ${err.stack}`)
process.exit(1)
})
async function printLogs({ token, config: { currentTeam } }) {
let buf = [];
let init = false;
let lastLog;
let buf = []
let init = false
let lastLog
if (!follow) {
onLogs(await fetchLogs({ token, currentTeam, since, until }));
return;
onLogs(await fetchLogs({ token, currentTeam, since, until }))
return
}
const isURL = deploymentIdOrURL.includes('.');
const isURL = deploymentIdOrURL.includes('.')
const q = qs.stringify({
deploymentId: isURL ? '' : deploymentIdOrURL,
host: isURL ? deploymentIdOrURL : '',
types: types.join(','),
query
});
})
const socket = io(`https://log-io.zeit.co?${q}`);
const socket = io(`https://log-io.zeit.co?${q}`)
socket.on('connect', () => {
if (debug) {
console.log('> [debug] Socket connected');
console.log('> [debug] Socket connected')
}
});
})
socket.on('auth', callback => {
if (debug) {
console.log('> [debug] Socket authenticate');
console.log('> [debug] Socket authenticate')
}
callback(token);
});
callback(token)
})
socket.on('ready', () => {
if (debug) {
console.log('> [debug] Socket ready');
console.log('> [debug] Socket ready')
}
// For the case socket reconnected
const _since = lastLog ? lastLog.serial : since;
const _since = lastLog ? lastLog.serial : since
fetchLogs({ token, currentTeam, since: _since }).then(logs => {
init = true;
const m = {};
init = true
const m = {}
logs.concat(buf.map(b => b.log)).forEach(l => {
m[l.id] = l;
});
buf = [];
onLogs(Object.values(m));
});
});
m[l.id] = l
})
buf = []
onLogs(Object.values(m))
})
})
socket.on('logs', l => {
const log = deserialize(l);
let timer;
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.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);
clearTimeout(b.timer)
onLog(b.log)
})
buf = buf.slice(idx + 1)
}, 300)
}
buf.push({ log, timer });
});
buf.push({ log, timer })
})
socket.on('disconnect', () => {
if (debug) {
console.log('> [debug] Socket disconnect');
console.log('> [debug] Socket disconnect')
}
init = false;
});
init = false
})
socket.on('error', err => {
if (debug) {
console.log('> [debug] Socket error', err.stack);
console.log('> [debug] Socket error', err.stack)
}
});
})
function onLogs(logs) {
logs.sort(compare).forEach(onLog);
logs.sort(compare).forEach(onLog)
}
function onLog(log) {
lastLog = log;
printLog(log);
lastLog = log
printLog(log)
}
}
function printLog(log) {
let data;
const obj = log.object;
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}"`;
` "${obj.referer || ''}" "${obj.userAgent}"`
} else if (log.type === 'response') {
data =
`RES "${obj.method} ${obj.uri} ${obj.protocol}"` +
` ${obj.status} ${obj.bodyBytesSent}`;
` ${obj.status} ${obj.bodyBytesSent}`
} else {
data = obj
? JSON.stringify(obj, null, 2)
: (log.text || '').replace(/\n$/, '');
: (log.text || '').replace(/\n$/, '')
}
const date = dateformat(log.date, 'mm/dd hh:MM TT');
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}`);
console.log(`${chalk.dim(date)} ${line}`)
} else {
console.log(`${repeat(' ', date.length)} ${line}`);
console.log(`${repeat(' ', date.length)} ${line}`)
}
});
})
}
async function fetchLogs({ token, currentTeam, since, until } = {}) {
const now = new Now({ apiUrl, token, debug, currentTeam });
const now = new Now({ apiUrl, token, debug, currentTeam })
let logs;
let logs
try {
logs = await now.logs(deploymentIdOrURL, {
types,
@ -248,28 +248,28 @@ async function fetchLogs({ token, currentTeam, since, until } = {}) {
query,
since,
until
});
})
} catch (err) {
handleError(err);
process.exit(1);
handleError(err)
process.exit(1)
} finally {
now.close();
now.close()
}
return logs.map(deserialize);
return logs.map(deserialize)
}
function repeat(s, n) {
return new Array(n + 1).join(s);
return new Array(n + 1).join(s)
}
function toSerial(datestr) {
const t = Date.parse(datestr);
const t = Date.parse(datestr)
if (isNaN(t)) {
throw new TypeError('Invalid date string');
throw new TypeError('Invalid date string')
}
const pidLen = 19;
const seqLen = 19;
return t + repeat('0', pidLen + seqLen);
const pidLen = 19
const seqLen = 19
return t + repeat('0', pidLen + seqLen)
}

124
bin/now-open.js

@ -1,17 +1,17 @@
#!/usr/bin/env node
// Packages
const fs = require('fs-promise');
const minimist = require('minimist');
const chalk = require('chalk');
const opn = require('opn');
const fs = require('fs-promise')
const minimist = require('minimist')
const chalk = require('chalk')
const opn = require('opn')
// 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 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 argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -22,7 +22,7 @@ const argv = minimist(process.argv.slice(2), {
debug: 'd',
token: 't'
}
});
})
const help = () => {
console.log(
@ -43,112 +43,108 @@ const help = () => {
${chalk.cyan('$ now open')}
`
);
};
)
}
if (argv.help) {
help();
process.exit(0);
help()
process.exit(0)
}
const app = argv._[0];
const app = argv._[0]
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
process.exit(1);
error(`Authentication error – ${err.message}`)
process.exit(1)
}
try {
await open({token, config});
await open({ token, config })
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
}
});
})
async function open({token, config: {currentTeam, user}}) {
const now = new Now({apiUrl, token, debug, currentTeam });
async function open({ token, config: { currentTeam, user } }) {
const now = new Now({ apiUrl, token, debug, currentTeam })
let deployments;
let deployments
try {
deployments = await now.list(app);
deployments = await now.list(app)
} catch (err) {
handleError(err);
process.exit(1);
handleError(err)
process.exit(1)
}
now.close();
now.close()
const apps = new Map();
const apps = new Map()
for (const dep of deployments) {
const deps = apps.get(dep.name) || [];
apps.set(dep.name, deps.concat(dep));
const deps = apps.get(dep.name) || []
apps.set(dep.name, deps.concat(dep))
}
let pkg;
let pkg
try {
const json = await fs.readFile('package.json');
pkg = JSON.parse(json);
const json = await fs.readFile('package.json')
pkg = JSON.parse(json)
} catch (err) {
pkg = {};
pkg = {}
}
const [currentProjectDeployments] = await getCurrentProjectDeployments(
[...apps],
pkg
);
)
if (typeof currentProjectDeployments === 'undefined') {
console.log(`No deployments found for ${chalk.bold(pkg.name)} under ${
chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)
}`);
process.exit(0);
console.log(
`No deployments found for ${chalk.bold(pkg.name)} under ${chalk.bold((currentTeam && currentTeam.slug) || user.username || user.email)}`
)
process.exit(0)
}
const sorted = await sortByCreation([...currentProjectDeployments]);
const latestDeploy = sorted[0];
const sorted = await sortByCreation([...currentProjectDeployments])
const latestDeploy = sorted[0]
try {
const url = `https://${latestDeploy.url}`;
const url = `https://${latestDeploy.url}`
console.log(`Opening the latest deployment for ${chalk.bold(pkg.name)}... under ${
chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)
}`);
console.log(`Here's the URL: ${chalk.underline(url)}`);
console.log(
`Opening the latest deployment for ${chalk.bold(pkg.name)}... under ${chalk.bold((currentTeam && currentTeam.slug) || user.username || user.email)}`
)
console.log(`Here's the URL: ${chalk.underline(url)}`)
opn(url);
process.exit(0);
opn(url)
process.exit(0)
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
}
}
async function getCurrentProjectDeployments(apps, pkg) {
return apps.filter(app => pkg.name === app[0]).map(app => app[1]);
return apps.filter(app => pkg.name === app[0]).map(app => app[1])
}
async function sortByCreation(deps) {
return deps.sort((depA, depB) => {
return depB.created - depA.created;
});
return depB.created - depA.created
})
}

106
bin/now-remove.js

@ -10,9 +10,9 @@ const table = require('text-table')
const Now = require('../lib')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const {handleError, error} = require('../lib/error')
const { handleError, error } = require('../lib/error')
const logo = require('../lib/utils/output/logo')
const {normalizeURL} = require('../lib/utils/url')
const { normalizeURL } = require('../lib/utils/url')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -24,9 +24,9 @@ const argv = minimist(process.argv.slice(2), {
token: 't',
yes: 'y'
}
});
})
const ids = argv._;
const ids = argv._
// Options
const help = () => {
@ -58,85 +58,85 @@ const help = () => {
${chalk.dim('Alias:')} rm
`
);
};
)
}
if (argv.help || ids.length === 0) {
help();
process.exit(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;
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);
cfg.setConfigFile(argv.config)
}
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = (await argv.token) || config.token || login(apiUrl);
token = (await argv.token) || config.token || login(apiUrl)
} catch (err) {
error(`Authentication error – ${err.message}`);
process.exit(1);
error(`Authentication error – ${err.message}`)
process.exit(1)
}
try {
await remove({token, config});
await remove({ token, config })
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
error(`Unknown error: ${err}\n${err.stack}`)
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];
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');
)
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());
process.stdin.pause()
resolve(d.toString().trim())
})
.resume();
});
.resume()
})
}
async function remove({token, config: {currentTeam}}) {
const now = new Now({apiUrl, token, debug, currentTeam });
async function remove({ token, config: { currentTeam } }) {
const now = new Now({ apiUrl, token, debug, currentTeam })
const deployments = await now.list();
const deployments = await now.list()
const matches = deployments.filter(d => {
return ids.some(id => {
@ -149,44 +149,44 @@ async function remove({token, config: {currentTeam}}) {
`Could not find any deployments matching ${ids
.map(id => chalk.bold(`"${id}"`))
.join(', ')}. Run ${chalk.dim(`\`now ls\``)} to list.`
);
return process.exit(1);
)
return process.exit(1)
}
const aliases = await Promise.all(
matches.map(depl => now.listAliases(depl.uid))
);
)
for (let i = 0; i < matches.length; i++) {
matches[i].aliases = aliases[i];
matches[i].aliases = aliases[i]
}
try {
if (!skipConfirmation) {
const confirmation = (await readConfirmation(matches)).toLowerCase();
const confirmation = (await readConfirmation(matches)).toLowerCase()
if (confirmation !== 'y' && confirmation !== 'yes') {
console.log('\n> Aborted');
process.exit(0);
console.log('\n> Aborted')
process.exit(0)
}
}
const start = new Date();
const start = new Date()
await Promise.all(matches.map(depl => now.remove(depl.uid, { hard })));
await Promise.all(matches.map(depl => now.remove(depl.uid, { hard })))
const elapsed = ms(new Date() - start);
console.log(`${chalk.cyan('> Success!')} [${elapsed}]`);
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`];
return [`Deployment ${chalk.bold(depl.uid)} removed`]
})
)
);
)
} catch (err) {
handleError(err);
process.exit(1);
handleError(err)
process.exit(1)
}
now.close();
now.close()
}

222
bin/now-scale.js

@ -1,32 +1,32 @@
#!/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');
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 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 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];
let id = argv._[0]
const scaleArg = argv._[1]
const optionalScaleArg = argv._[2]
// Options
const help = () => {
@ -64,57 +64,57 @@ const help = () => {
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 1')}
`
);
};
)
}
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
if (argv.help) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({ token, config });
await run({ token, config })
} catch (err) {
if (err.userError) {
error(err.message);
error(err.message)
} else {
error(`Unknown error: ${err}\n${err.stack}`);
error(`Unknown error: ${err}\n${err.stack}`)
}
exit(1);
exit(1)
}
});
})
}
function guessParams() {
if (Number.isInteger(scaleArg) && !optionalScaleArg) {
return { min: scaleArg, max: scaleArg };
return { min: scaleArg, max: scaleArg }
} else if (Number.isInteger(scaleArg) && Number.isInteger(optionalScaleArg)) {
return { min: scaleArg, max: optionalScaleArg };
return { min: scaleArg, max: optionalScaleArg }
} else if (Number.isInteger(scaleArg) && optionalScaleArg === 'auto') {
return { min: scaleArg, max: 'auto' };
return { min: scaleArg, max: 'auto' }
} else if (!scaleArg && !optionalScaleArg) {
return { min: 1, max: 'auto' };
return { min: 1, max: 'auto' }
}
help();
process.exit(1);
help()
process.exit(1)
}
function isHostNameOrId(str) {
@ -122,64 +122,64 @@ function isHostNameOrId(str) {
/(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();
const scale = new NowScale({ apiUrl, token, debug, currentTeam })
const start = Date.now()
if (id === 'ls') {
await list(scale);
process.exit(0);
await list(scale)
process.exit(0)
} else if (id === 'info') {
await info(scale);
process.exit(0);
await info(scale)
process.exit(0)
} else if (id && isHostNameOrId(id)) {
// Normalize URL by removing slash from the end
if (isURL(id) && id.slice(-1) === '/') {
id = id.slice(0, -1);
id = id.slice(0, -1)
}
} else {
error('Please specify a deployment: now scale <id|url>');
help();
exit(1);
error('Please specify a deployment: now scale <id|url>')
help()
exit(1)
}
const deployments = await scale.list();
const deployments = await scale.list()
const match = deployments.find(d => {
// `url` should match the hostname of the deployment
let u = id.replace(/^https:\/\//i, '');
let u = id.replace(/^https:\/\//i, '')
if (u.indexOf('.') === -1) {
// `.now.sh` domain is implied if just the subdomain is given
u += '.now.sh';
u += '.now.sh'
}
return d.uid === id || d.name === id || d.url === u;
});
return d.uid === id || d.name === id || d.url === u
})
if (!match) {
error(`Could not find any deployments matching ${id}`);
return process.exit(1);
error(`Could not find any deployments matching ${id}`)
return process.exit(1)
}
const { min, max } = guessParams();
const { min, max } = guessParams()
if (
!(Number.isInteger(min) || min === 'auto') &&
!(Number.isInteger(max) || max === 'auto')
) {
help();
return exit(1);
help()
return exit(1)
}
const {
max: currentMax,
min: currentMin,
current: currentCurrent
} = match.scale;
} = match.scale
if (
max === currentMax &&
min === currentMin &&
@ -188,75 +188,75 @@ async function run({ token, config: { currentTeam } }) {
Number.isInteger(max) &&
currentCurrent <= max
) {
console.log(`> Done`);
return;
console.log(`> Done`)
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.setScale(match.uid, { min: 0, max: 1 })
}
await scale.unfreeze(match);
await scale.unfreeze(match)
}
const { min: newMin, max: newMax } = await scale.setScale(match.uid, {
min,
max
});
})
const elapsed = ms(new Date() - start);
const elapsed = ms(new Date() - start)
const currentReplicas = match.scale.current;
const log = console.log;
log(`> ${chalk.cyan('Success!')} Configured scaling rules [${elapsed}]`);
log();
const currentReplicas = match.scale.current
const log = console.log
log(`> ${chalk.cyan('Success!')} Configured scaling rules [${elapsed}]`)
log()
log(
`${chalk.bold(match.url)} (${chalk.gray(currentReplicas)} ${chalk.gray('current')})`
);
log(printf('%6s %s', 'min', chalk.bold(newMin)));
log(printf('%6s %s', 'max', chalk.bold(newMax)));
log(printf('%6s %s', 'auto', chalk.bold(newMin === newMax ? '✖' : '✔')));
log();
await info(scale, match.url);
scale.close();
)
log(printf('%6s %s', 'min', chalk.bold(newMin)))
log(printf('%6s %s', 'max', chalk.bold(newMax)))
log(printf('%6s %s', 'auto', chalk.bold(newMin === newMax ? '✖' : '✔')))
log()
await info(scale, match.url)
scale.close()
}
async function list(scale) {
let deployments;
let deployments
try {
const app = argv._[1];
deployments = await scale.list(app);
const app = argv._[1]
deployments = await scale.list(app)
} catch (err) {
handleError(err);
process.exit(1);
handleError(err)
process.exit(1)
}
scale.close();
scale.close()
const apps = new Map();
const apps = new Map()
for (const dep of deployments) {
const deps = apps.get(dep.name) || [];
apps.set(dep.name, deps.concat(dep));
const deps = apps.get(dep.name) || []
apps.set(dep.name, deps.concat(dep))
}
const timeNow = new Date();
const timeNow = new Date()
const urlLength =
deployments.reduce((acc, i) => {
return Math.max(acc, (i.url && i.url.length) || 0);
}, 0) + 5;
return Math.max(acc, (i.url && i.url.length) || 0)
}, 0) + 5
for (const app of apps) {
const depls = argv.all ? app[1] : app[1].slice(0, 5);
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()
const urlSpec = `%-${urlLength}s`
console.log(
printf(
` ${chalk.grey(urlSpec + ' %8s %8s %8s %8s %8s')}`,
@ -267,14 +267,14 @@ async function list(scale) {
'auto',
'age'
)
);
)
for (const instance of depls) {
if (instance.scale.current > 0) {
let spec;
let spec
if (supportsColor) {
spec = ` %-${urlLength + 10}s %8s %8s %8s %8s %8s`;
spec = ` %-${urlLength + 10}s %8s %8s %8s %8s %8s`
} else {
spec = ` %-${urlLength + 1}s %8s %8s %8s %8s %8s`;
spec = ` %-${urlLength + 1}s %8s %8s %8s %8s %8s`
}
console.log(
printf(
@ -286,13 +286,13 @@ async function list(scale) {
instance.scale.max === instance.scale.min ? '✖' : '✔',
ms(timeNow - instance.created)
)
);
)
} else {
let spec;
let spec
if (supportsColor) {
spec = ` %-${urlLength + 10}s ${chalk.gray('%8s %8s %8s %8s %8s')}`;
spec = ` %-${urlLength + 10}s ${chalk.gray('%8s %8s %8s %8s %8s')}`
} else {
spec = ` %-${urlLength + 1}s ${chalk.gray('%8s %8s %8s %8s %8s')}`;
spec = ` %-${urlLength + 1}s ${chalk.gray('%8s %8s %8s %8s %8s')}`
}
console.log(
printf(
@ -304,14 +304,14 @@ async function list(scale) {
instance.scale.max === instance.scale.min ? '✖' : '✔',
ms(timeNow - instance.created)
)
);
)
}
}
console.log();
console.log()
}
}
process.on('uncaughtException', err => {
handleError(err);
exit(1);
});
handleError(err)
exit(1)
})

196
bin/now-secrets.js

@ -1,19 +1,19 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk');
const table = require('text-table');
const minimist = require('minimist');
const ms = require('ms');
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 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'],
@ -25,9 +25,9 @@ const argv = minimist(process.argv.slice(2), {
base64: 'b',
token: 't'
}
});
})
const subcommand = argv._[0];
const subcommand = argv._[0]
// Options
const help = () => {
@ -71,68 +71,64 @@ const help = () => {
${chalk.cyan(`$ now secrets rm my-secret`)}
`
);
};
)
}
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
if (argv.help || !subcommand) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({token, config});
await run({ token, config })
} catch (err) {
handleError(err);
exit(1);
handleError(err)
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();
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);
)
return exit(1)
}
const list = await secrets.ls();
const elapsed = ms(new Date() - start);
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}]`)}`
);
`> ${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 = [['', 'id', 'name', 'created'].map(s => chalk.dim(s))];
const cur = Date.now()
const header = [['', 'id', 'name', 'created'].map(s => chalk.dim(s))]
const out = table(
header.concat(
list.map(secret => {
@ -141,7 +137,7 @@ async function run({token, config: {currentTeam, user}}) {
secret.uid,
chalk.bold(secret.name),
chalk.gray(ms(cur - new Date(secret.created)) + ' ago')
];
]
})
),
{
@ -149,131 +145,125 @@ async function run({token, config: {currentTeam, user}}) {
hsep: ' '.repeat(2),
stringLength: strlen
}
);
)
if (out) {
console.log('\n' + out + '\n');
console.log('\n' + out + '\n')
}
}
return secrets.close();
return secrets.close()
}
if (subcommand === 'rm' || subcommand === 'remove') {
if (args.length !== 1) {
error(
`Invalid number of arguments. Usage: ${chalk.cyan('`now secret rm <id | name>`')}`
);
return exit(1);
)
return exit(1)
}
const list = await secrets.ls();
const list = await secrets.ls()
const theSecret = list.filter(secret => {
return secret.uid === args[0] || secret.name === args[0];
})[0];
return secret.uid === args[0] || secret.name === args[0]
})[0]
if (theSecret) {
const yes = await readConfirmation(theSecret);
const yes = await readConfirmation(theSecret)
if (!yes) {
error('User abort');
return exit(0);
error('User abort')
return exit(0)
}
} else {
error(`No secret found by id or name "${args[0]}"`);
return exit(1);
error(`No secret found by id or name "${args[0]}"`)
return exit(1)
}
const secret = await secrets.rm(args[0]);
const elapsed = ms(new Date() - start);
const secret = await secrets.rm(args[0])
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.name)} ${chalk.gray(`(${secret.uid})`)} removed ${chalk.gray(`[${elapsed}]`)}`
);
return secrets.close();
)
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);
)
return exit(1)
}
const secret = await secrets.rename(args[0], args[1]);
const elapsed = ms(new Date() - start);
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)} ${chalk.gray(`(${secret.uid})`)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}`
);
return secrets.close();
)
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]}`);
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);
return exit(1)
}
const [name, value_] = args;
let value;
const [name, value_] = args
let value
if (argv.base64) {
value = { base64: value_ };
value = { base64: value_ }
} else {
value = value_;
value = value_
}
const secret = await secrets.add(name, value);
const elapsed = ms(new Date() - start);
const secret = await secrets.add(name, value)
const elapsed = ms(new Date() - start)
console.log(
`${chalk.cyan('> Success!')} Secret ${chalk.bold(name.toLowerCase())} ${chalk.gray(`(${secret.uid})`)} added (${
chalk.bold(
(currentTeam && currentTeam.slug) || user.username || user.email
)
}) ${chalk.gray(`[${elapsed}]`)}`
);
return secrets.close();
`${chalk.cyan('> Success!')} Secret ${chalk.bold(name.toLowerCase())} ${chalk.gray(`(${secret.uid})`)} 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);
error('Please specify a valid subcommand: ls | add | rename | rm')
help()
exit(1)
}
process.on('uncaughtException', err => {
handleError(err);
exit(1);
});
handleError(err)
exit(1)
})
function readConfirmation(secret) {
return new Promise(resolve => {
const time = chalk.gray(ms(new Date() - new Date(secret.created)) + ' ago');
const time = chalk.gray(ms(new Date() - new Date(secret.created)) + ' ago')
const tbl = table([[secret.uid, chalk.bold(secret.name), time]], {
align: ['l', 'r', 'l'],
hsep: ' '.repeat(6)
});
})
process.stdout.write(
'> The following secret will be removed permanently\n'
);
process.stdout.write(' ' + tbl + '\n');
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');
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
})
.resume();
});
.resume()
})
}

84
bin/now-teams.js

@ -1,19 +1,19 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path');
const { resolve } = require('path')
// Packages
const chalk = require('chalk');
const minimist = require('minimist');
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 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 argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -25,9 +25,9 @@ const argv = minimist(process.argv.slice(2), {
token: 't',
switch: 'change'
}
});
})
const subcommand = argv._[0];
const subcommand = argv._[0]
const help = () => {
console.log(
@ -69,74 +69,74 @@ const help = () => {
${chalk.gray('–')} If the id is omitted, you can choose interactively
`
);
};
)
}
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
if (argv.help || !subcommand) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({token, config});
await run({ token, config })
} catch (err) {
if (err.userError) {
error(err.message);
error(err.message)
} else {
error(`Unknown error: ${err.stack}`);
error(`Unknown error: ${err.stack}`)
}
exit(1);
exit(1)
}
});
})
}
async function run({token, config: {currentTeam}}) {
const teams = new NowTeams({ apiUrl, token, debug, currentTeam });
const args = argv._.slice(1);
async function run({ token, config: { currentTeam } }) {
const teams = new NowTeams({ apiUrl, token, debug, currentTeam })
const args = argv._.slice(1)
switch (subcommand) {
case 'switch':
case 'change': {
await require(resolve(__dirname, 'teams', 'switch.js'))(teams, args);
break;
await require(resolve(__dirname, 'teams', 'switch.js'))(teams, args)
break
}
case 'add':
case 'create': {
await require(resolve(__dirname, 'teams', 'add.js'))(teams);
break;
await require(resolve(__dirname, 'teams', 'add.js'))(teams)
break
}
case 'invite': {
await require(resolve(__dirname, 'teams', 'invite.js'))(teams, args);
break;
await require(resolve(__dirname, 'teams', 'invite.js'))(teams, args)
break
}
default: {
let code = 0;
let code = 0
if (subcommand !== 'help') {
error('Please specify a valid subcommand: ls | add | rm | set-default');
code = 1;
error('Please specify a valid subcommand: ls | add | rm | set-default')
code = 1
}
help();
exit(code);
help()
exit(code)
}
}
}

144
bin/now-upgrade.js

@ -1,23 +1,23 @@
#!/usr/bin/env node
// Packages
const chalk = require('chalk');
const minimist = require('minimist');
const ms = require('ms');
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 {bold} = chalk
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 { bold } = chalk
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -28,7 +28,7 @@ const argv = minimist(process.argv.slice(2), {
debug: 'd',
token: 't'
}
});
})
const help = () => {
console.log(
@ -56,15 +56,15 @@ const help = () => {
${chalk.cyan(`$ now upgrade premium`)}
`
);
};
)
}
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
if (argv.config) {
cfg.setConfigFile(argv.config);
cfg.setConfigFile(argv.config)
}
const exit = code => {
@ -72,43 +72,43 @@ const exit = code => {
// because there's a node bug where
// stdout writes are asynchronous
// https://github.com/nodejs/node/issues/6456
setTimeout(() => process.exit(code || 0), 100);
};
setTimeout(() => process.exit(code || 0), 100)
}
if (argv.help) {
help();
exit(0);
help()
exit(0)
} else {
Promise.resolve().then(async () => {
const config = await cfg.read();
const config = await cfg.read()
let token;
let token
try {
token = argv.token || config.token || (await login(apiUrl));
token = argv.token || config.token || (await login(apiUrl))
} catch (err) {
error(`Authentication error – ${err.message}`);
exit(1);
error(`Authentication error – ${err.message}`)
exit(1)
}
try {
await run({token, config});
await run({ token, config })
} catch (err) {
if (err.userError) {
error(err.message);
error(err.message)
} else {
error(`Unknown error: ${err.stack}`);
error(`Unknown error: ${err.stack}`)
}
exit(1);
exit(1)
}
});
})
}
function buildInquirerChoices(current, until) {
if (until) {
until = until.split(' ');
until = ' for ' + chalk.bold(until[0]) + ' more ' + until[1];
until = until.split(' ')
until = ' for ' + chalk.bold(until[0]) + ' more ' + until[1]
} else {
until = '';
until = ''
}
const currentText = bold('(current)')
@ -159,85 +159,81 @@ function buildInquirerChoices(current, until) {
name: advancedName,
value: 'advanced',
short: `Advanced ${bold('$200')}`
},
];
}
]
}
async function run({token, config: {currentTeam, user}}) {
const args = argv._;
async function run({ token, config: { currentTeam, user } }) {
const args = argv._
if (args.length > 1) {
error('Invalid number of arguments');
return exit(1);
error('Invalid number of arguments')
return exit(1)
}
const start = new Date();
const plans = new NowPlans({ apiUrl, token, debug, currentTeam });
const start = new Date()
const plans = new NowPlans({ apiUrl, token, debug, currentTeam })
let planId = args[0];
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);
error(`Invalid plan name – should be ${code('oss')} or ${code('premium')}`)
return exit(1)
}
const currentPlan = await plans.getCurrent();
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);
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');
return console.log('No changes made')
}
let newPlan;
let newPlan
try {
newPlan = await plans.set(planId);
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}`);
error(`An unknow error occured. Please try again later ${err.message}`)
}
plans.close();
return;
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`)}`);
success(`You're now on the ${chalk.bold(`${newPlan.name} plan`)}`)
}
plans.close();
plans.close()
}

60
bin/now.js

@ -1,30 +1,30 @@
#!/usr/bin/env node
// Native
const { resolve } = require('path');
const { resolve } = require('path')
// Packages
const updateNotifier = require('update-notifier');
const chalk = require('chalk');
const updateNotifier = require('update-notifier')
const chalk = require('chalk')
// Ours
const pkg = require('../lib/pkg');
const pkg = require('../lib/pkg')
if (!process.pkg) {
const notifier = updateNotifier({ pkg });
const update = notifier.update;
const notifier = updateNotifier({ pkg })
const update = notifier.update
if (update) {
let message = `Update available! ${chalk.red(update.current)}${chalk.green(update.latest)} \n`;
message += `Run ${chalk.magenta('npm i -g now')} to update!\n`;
message += `${chalk.magenta('Changelog:')} https://github.com/zeit/now-cli/releases/tag/${update.latest}`;
let message = `Update available! ${chalk.red(update.current)}${chalk.green(update.latest)} \n`
message += `Run ${chalk.magenta('npm i -g now')} to update!\n`
message += `${chalk.magenta('Changelog:')} https://github.com/zeit/now-cli/releases/tag/${update.latest}`
notifier.notify({ message });
notifier.notify({ message })
}
}
// This command will be run if no other sub command is specified
const defaultCommand = 'deploy';
const defaultCommand = 'deploy'
const commands = new Set([
defaultCommand,
@ -55,7 +55,7 @@ const commands = new Set([
'logs',
'scale',
'logout'
]);
])
const aliases = new Map([
['ls', 'list'],
@ -70,46 +70,46 @@ const aliases = new Map([
['team', 'teams'],
['switch', 'teams switch'],
['log', 'logs']
]);
])
let cmd = defaultCommand;
let args = process.argv.slice(2);
const index = args.findIndex(a => commands.has(a));
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);
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);
cmd = args[index]
args.splice(index, 1)
} else {
cmd = defaultCommand;
cmd = defaultCommand
}
args.unshift('--help');
args.unshift('--help')
}
cmd = aliases.get(cmd) || cmd;
cmd = aliases.get(cmd) || cmd
if (cmd.includes(' ')) {
const parts = cmd.split(' ');
cmd = parts.shift();
args = [].concat(parts, args);
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';
cmd = 'deploy'
}
const bin = resolve(__dirname, 'now-' + cmd + '.js');
const bin = resolve(__dirname, 'now-' + cmd + '.js')
// Prepare process.argv for subcommand
process.argv = process.argv.slice(0, 2).concat(args);
process.argv = process.argv.slice(0, 2).concat(args)
// Load sub command
// With custom parameter to make "pkg" happy
require(bin, 'may-exclude');
require(bin, 'may-exclude')

126
bin/teams/add.js

@ -1,48 +1,48 @@
// Packages
const chalk = require('chalk');
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');
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);
return /^[a-zA-Z]+[a-zA-Z0-9_-]*$/.test(value + data)
}
function gracefulExit() {
console.log(); // Blank line
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();
)
return exit()
}
const teamUrlPrefix = rightPad('Team URL', 14) + chalk.gray('zeit.co/');
const teamNamePrefix = rightPad('Team Name', 14);
const teamUrlPrefix = rightPad('Team URL', 14) + chalk.gray('zeit.co/')
const teamNamePrefix = rightPad('Team Name', 14)
module.exports = async function(teams) {
let slug;
let team;
let elapsed;
let stopSpinner;
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
@ -52,76 +52,76 @@ module.exports = async function(teams) {
initialValue: slug,
valid: team,
forceLowerCase: true
});
})
} catch (err) {
if (err.message === 'USER_ABORT') {
info('Aborted');
return exit();
info('Aborted')
return exit()
}
throw err;
throw err
}
elapsed = stamp();
stopSpinner = wait(teamUrlPrefix + slug);
elapsed = stamp()
stopSpinner = wait(teamUrlPrefix + slug)
let res
try {
// eslint-disable-next-line no-await-in-loop
res = await teams.create({ slug });
stopSpinner();
res = await teams.create({ slug })
stopSpinner()
team = res
} catch (err) {
stopSpinner();
eraseLines(2);
stopSpinner()
eraseLines(2)
error(err.message)
}
} while (!team);
} while (!team)
eraseLines(2);
success(`Team created ${uid(team.id)} ${elapsed()}`);
console.log(chalk.cyan(`${tick} `) + teamUrlPrefix + slug + '\n');
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;
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();
info('No name specified')
gracefulExit()
} else {
throw err;
throw err
}
}
elapsed = stamp();
stopSpinner = wait(teamNamePrefix + name);
const res = await teams.edit({ id: team.id, name });
stopSpinner();
elapsed = stamp()
stopSpinner = wait(teamNamePrefix + name)
const res = await teams.edit({ id: team.id, name })
stopSpinner()
eraseLines(2);
eraseLines(2)
if (res.error) {
error(res.error.message);
console.log(`${chalk.red(`${teamNamePrefix}`)}${name}`);
exit(1);
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);
team = Object.assign(team, res)
success(`Team name saved ${elapsed()}`);
console.log(chalk.cyan(`${tick} `) + teamNamePrefix + team.name + '\n');
success(`Team name saved ${elapsed()}`)
console.log(chalk.cyan(`${tick} `) + teamNamePrefix + team.name + '\n')
stopSpinner = wait('Saving');
await cfg.merge({ currentTeam: team });
stopSpinner();
stopSpinner = wait('Saving')
await cfg.merge({ currentTeam: team })
stopSpinner()
await require('./invite')(teams, [], {
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();
};
gracefulExit()
}

140
bin/teams/invite.js

@ -1,24 +1,24 @@
// Packages
const chalk = require('chalk');
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');
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;
return regexes.email.test(data.trim()) || data.length === 0
}
const domains = Array.from(
@ -36,125 +36,125 @@ const domains = Array.from(
'gmx.com',
'icloud.com'
])
);
)
function emailAutoComplete(value, teamSlug) {
const parts = value.split('@');
const parts = value.split('@')
if (parts.length === 2 && parts[1].length > 0) {
const [, host] = parts;
let suggestion = false;
const [, host] = parts
let suggestion = false
domains.unshift(teamSlug);
domains.unshift(teamSlug)
for (const domain of domains) {
if (domain.startsWith(host)) {
suggestion = domain.substr(host.length);
break;
suggestion = domain.substr(host.length)
break
}
}
domains.shift();
return suggestion;
domains.shift()
return suggestion
}
return false;
return false
}
module.exports = async function(
teams,
args,
{
introMsg,
noopMsg = 'No changes made'
} = {}
{ introMsg, noopMsg = 'No changes made' } = {}
) {
const { user, currentTeam } = await cfg.read();
const { user, currentTeam } = await cfg.read()
domains.push(user.email.split('@')[1]);
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);
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)}`);
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();
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()}`);
await teams.inviteUser({ teamId: currentTeam.id, email })
stopSpinner()
console.log(`${chalk.cyan(tick)} ${email} ${elapsed()}`)
} else {
console.log(`${chalk.red(`${email}`)} ${chalk.gray('[invalid]')}`);
console.log(`${chalk.red(`${email}`)} ${chalk.gray('[invalid]')}`)
}
}
return;
return
}
const inviteUserPrefix = rightPad('Invite User', 14);
const emails = [];
const inviteUserPrefix = rightPad('Invite User', 14)
const emails = []
let hasError = false
let email;
let email
do {
email = '';
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;
throw err
}
}
let elapsed;
let stopSpinner;
let elapsed
let stopSpinner
if (email) {
elapsed = stamp();
stopSpinner = wait(inviteUserPrefix + 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}`);
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)}`);
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}`);
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`)
}
}
} catch (err) {
stopSpinner()
eraseLines(emails.length + 2);
eraseLines(emails.length + 2)
error(err.message)
hasError = true
for (const email of emails) {
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`);
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`)
}
}
}
} while (email !== '');
} while (email !== '')
eraseLines(emails.length + 2);
eraseLines(emails.length + 2)
const n = emails.length;
const n = emails.length
if (emails.length === 0) {
info(noopMsg);
info(noopMsg)
} else {
success(`Invited ${n} team mate${n > 1 ? 's' : ''}`);
success(`Invited ${n} team mate${n > 1 ? 's' : ''}`)
for (const email of emails) {
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`);
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`)
}
}
};
}

116
bin/teams/switch.js

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

48
lib/agent.js

@ -1,10 +1,10 @@
// Native
const { parse } = require('url');
const http = require('http');
const https = require('https');
const { parse } = require('url')
const http = require('http')
const https = require('https')
// Packages
const fetch = require('node-fetch');
const fetch = require('node-fetch')
/**
* Returns a `fetch` version with a similar
@ -19,65 +19,65 @@ const fetch = require('node-fetch');
module.exports = class Agent {
constructor(url, { tls = true, debug } = {}) {
this._url = url;
const parsed = parse(url);
this._protocol = parsed.protocol;
this._debug = debug;
this._url = url
const parsed = parse(url)
this._protocol = parsed.protocol
this._debug = debug
if (tls) {
this._initAgent();
this._initAgent()
}
}
_initAgent() {
const module = this._protocol === 'https:' ? https : http;
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));
}).on('error', err => this._onError(err, this._agent))
}
_onError(err, agent) {
if (this._debug) {
console.log(`> [debug] agent connection error ${err}\n${err.stack}`);
console.log(`> [debug] agent connection error ${err}\n${err.stack}`)
}
if (this._agent === agent) {
this._agent = null;
this._agent = null
}
}
fetch(path, opts = {}) {
if (!this._agent) {
if (this._debug) {
console.log('> [debug] re-initializing agent');
console.log('> [debug] re-initializing agent')
}
this._initAgent();
this._initAgent()
}
const { body } = opts;
const { body } = opts
if (this._agent) {
opts.agent = 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);
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);
opts.headers['Content-Length'] = Buffer.byteLength(opts.body)
}
return fetch(this._url + path, opts);
return fetch(this._url + path, opts)
}
close() {
if (this._debug) {
console.log('> [debug] closing agent');
console.log('> [debug] closing agent')
}
if (this._agent) {
this._agent.destroy();
this._agent.destroy()
}
}
};
}

553
lib/alias.js

File diff suppressed because it is too large

106
lib/build-logger.js

@ -1,133 +1,133 @@
// Native
const EventEmitter = require('events');
const EventEmitter = require('events')
// Packages
const io = require('socket.io-client');
const chalk = require('chalk');
const io = require('socket.io-client')
const chalk = require('chalk')
const { compare, deserialize } = require('./logs');
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;
super()
this.host = host
this.token = token
this.debug = debug
this.quiet = quiet
// ReadyState
this.building = false;
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));
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.buf = []
}
onAuth(callback) {
if (this.debug) {
console.log('> [debug] authenticate');
console.log('> [debug] authenticate')
}
callback(this.token);
callback(this.token)
}
onState(state) {
// Console.log(state)
if (!state.id) {
console.error('> Deployment not found');
this.emit('error');
return;
console.error('> Deployment not found')
this.emit('error')
return
}
if (state.error) {
this.emit('error', state);
return;
this.emit('error', state)
return
}
if (state.backend) {
this.onComplete();
return;
this.onComplete()
return
}
if (state.logs) {
state.logs.forEach(this.onLog, this);
state.logs.forEach(this.onLog, this)
}
}
onLog(log) {
if (!this.building) {
if (!this.quiet) {
console.log('> Building');
console.log('> Building')
}
this.building = true;
this.building = true
}
if (this.quiet) {
return;
return
}
log = deserialize(log);
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;
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);
clearTimeout(b.timer)
this.printLog(b.log)
}
this.buf = this.buf.slice(idx);
}, 300);
this.buf = this.buf.slice(idx)
}, 300)
this.buf.push({ log, timer });
this.buf.push({ log, timer })
}
onComplete() {
this.socket.disconnect();
this.socket.disconnect()
if (this.building) {
this.building = false;
this.building = false
}
this.buf.sort((a, b) => compare(a.log, b.log));
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);
clearTimeout(b.timer)
this.printLog(b.log)
}
this.buf = [];
this.buf = []
this.emit('close');
this.emit('close')
}
onSocketError(err) {
if (this.debug) {
console.log(`> [debug] Socket error ${err}\n${err.stack}`);
console.log(`> [debug] Socket error ${err}\n${err.stack}`)
}
}
printLog(log) {
const data = log.object ? JSON.stringify(log.object) : log.text;
const data = log.object ? JSON.stringify(log.object) : log.text
if (log.type === 'command') {
console.log(`${chalk.gray('>')}${data}`);
console.log(`${chalk.gray('>')}${data}`)
} else if (log.type === 'stderr') {
data.split('\n').forEach(v => {
if (v.length > 0) {
console.error(chalk.gray(`> ${v}`));
console.error(chalk.gray(`> ${v}`))
}
});
})
} else if (log.type === 'stdout') {
data.split('\n').forEach(v => {
if (v.length > 0) {
console.log(`${chalk.gray('>')} ${v}`);
console.log(`${chalk.gray('>')} ${v}`)
}
});
})
}
}
};
}

64
lib/certs.js

@ -1,36 +1,36 @@
// Ours
const Now = require('../lib');
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`);
console.time(`> [debug] #${attempt} GET now/certs`)
}
const res = await this._fetch('/now/certs');
const res = await this._fetch('/now/certs')
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET now/certs`);
console.timeEnd(`> [debug] #${attempt} GET now/certs`)
}
const body = await res.json();
return body.certs;
});
const body = await res.json()
return body.certs
})
}
create(cn) {
return this.createCert(cn);
return this.createCert(cn)
}
renew(cn) {
return this.createCert(cn, { renew: true });
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`);
console.time(`> [debug] #${attempt} PUT now/certs`)
}
const res = await this._fetch('/now/certs', {
@ -41,61 +41,61 @@ module.exports = class Certs extends Now {
cert: crt,
key
}
});
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PUT now/certs`);
console.timeEnd(`> [debug] #${attempt} PUT now/certs`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
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);
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message);
throw new Error(body.error.message)
}
return body;
});
return body
})
}
delete(cn) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} DELETE now/certs/${cn}`);
console.time(`> [debug] #${attempt} DELETE now/certs/${cn}`)
}
const res = await this._fetch(`/now/certs/${cn}`, { method: 'DELETE' });
const res = await this._fetch(`/now/certs/${cn}`, { method: 'DELETE' })
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} DELETE now/certs/${cn}`);
console.timeEnd(`> [debug] #${attempt} DELETE now/certs/${cn}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
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);
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message);
throw new Error(body.error.message)
}
return body;
});
return body
})
}
};
}

58
lib/cfg.js

@ -1,27 +1,27 @@
// Native
const { homedir } = require('os');
const path = require('path');
const { homedir } = require('os')
const path = require('path')
// Packages
const fs = require('fs-promise');
const ms = require('ms');
const fs = require('fs-promise')
const ms = require('ms')
// Ours
const { get: getUser } = require('./user');
const { get: getUser } = require('./user')
// `8h` is arbitrarily used based on the average sleep time
const TTL = ms('8h');
const TTL = ms('8h')
let file = process.env.NOW_JSON
? path.resolve(process.env.NOW_JSON)
: path.resolve(homedir(), '.now.json');
: path.resolve(homedir(), '.now.json')
function setConfigFile(nowjson) {
file = path.resolve(nowjson);
file = path.resolve(nowjson)
}
function save(data) {
fs.writeFileSync(file, JSON.stringify(data, null, 2));
fs.writeFileSync(file, JSON.stringify(data, null, 2))
}
/**
@ -39,14 +39,14 @@ function save(data) {
* @return {Object}
*/
async function read({ force = false, token, apiUrl } = {}) {
let existing = null;
let existing = null
try {
existing = fs.readFileSync(file, 'utf8');
existing = JSON.parse(existing);
existing = fs.readFileSync(file, 'utf8')
existing = JSON.parse(existing)
} catch (err) {}
if (!existing && force && token) {
const user = await getUser({ token, apiUrl });
const user = await getUser({ token, apiUrl })
if (user) {
return {
token,
@ -55,27 +55,27 @@ async function read({ force = false, token, apiUrl } = {}) {
username: user.username,
email: user.email
}
};
}
}
return {};
return {}
}
if (!existing) {
return {};
return {}
}
if (!existing.lastUpdate || Date.now() - existing.lastUpdate > TTL) {
// TODO: update `teams` info
const token = existing.token;
const user = await getUser({ token });
const token = existing.token
const user = await getUser({ token })
if (user) {
existing.user = user;
existing.lastUpdate = Date.now();
save(existing);
existing.user = user
existing.lastUpdate = Date.now()
save(existing)
}
}
return existing;
return existing
}
/**
@ -86,19 +86,19 @@ async function read({ force = false, token, apiUrl } = {}) {
* @param {Object} data
*/
async function merge(data) {
const cfg = Object.assign({}, await read(), data);
save(cfg);
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));
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);
const removeFile = async () => fs.remove(file)
module.exports = {
setConfigFile,
@ -106,4 +106,4 @@ module.exports = {
merge,
remove,
removeFile
};
}

44
lib/credit-cards.js

@ -1,38 +1,38 @@
const stripe = require('stripe')('pk_live_alyEi3lN0kSwbdevK0nrGwTw');
const stripe = require('stripe')('pk_live_alyEi3lN0kSwbdevK0nrGwTw')
const Now = require('../lib');
const Now = require('../lib')
module.exports = class CreditCards extends Now {
async ls() {
const res = await this._fetch('/cards');
const body = await res.json();
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;
return body
}
async setDefault(cardId) {
await this._fetch('/cards/default', {
method: 'PUT',
body: { cardId }
});
return true;
})
return true
}
async rm(cardId) {
await this._fetch(`/cards/${encodeURIComponent(cardId)}`, {
method: 'DELETE'
});
return true;
})
return true
}
/* eslint-disable camelcase */
add(card) {
return new Promise(async (resolve, reject) => {
const expDateParts = card.expDate.split(' / ');
const expDateParts = card.expDate.split(' / ')
card = {
name: card.name,
number: card.cardNumber,
@ -42,32 +42,32 @@ module.exports = class CreditCards extends Now {
address_state: card.state,
address_city: card.city,
address_line1: card.address1
};
}
card.exp_month = expDateParts[0];
card.exp_year = expDateParts[1];
card.exp_month = expDateParts[0]
card.exp_year = expDateParts[1]
try {
const stripeToken = (await stripe.tokens.create({ card })).id;
const stripeToken = (await stripe.tokens.create({ card })).id
const res = await this._fetch('/cards', {
method: 'POST',
body: { stripeToken }
});
})
const body = await res.json();
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));
reject(new Error(body.error.message))
} else {
reject(new Error('Unknown error'));
reject(new Error('Unknown error'))
}
} catch (err) {
reject(new Error(err.message || 'Unknown error'));
reject(new Error(err.message || 'Unknown error'))
}
});
})
}
};
}

12
lib/dns.js

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

114
lib/domain-records.js

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

126
lib/domains.js

@ -1,151 +1,151 @@
// Native
const { encode: encodeQuery } = require('querystring');
const { encode: encodeQuery } = require('querystring')
// Packages
const chalk = require('chalk');
const chalk = require('chalk')
// Ours
const Now = require('../lib');
const cfg = require('../lib/cfg');
const isZeitWorld = require('./is-zeit-world');
const { DNS_VERIFICATION_ERROR } = require('./errors');
const Now = require('../lib')
const cfg = require('../lib/cfg')
const isZeitWorld = require('./is-zeit-world')
const { DNS_VERIFICATION_ERROR } = require('./errors')
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/;
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();
return this.listDomains()
}
async rm(name) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} DELETE /domains/${name}`);
console.time(`> [debug] #${attempt} DELETE /domains/${name}`)
}
const res = await this._fetch(`/domains/${name}`, { method: 'DELETE' });
const res = await this._fetch(`/domains/${name}`, { method: 'DELETE' })
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} DELETE /domains/${name}`);
console.timeEnd(`> [debug] #${attempt} DELETE /domains/${name}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
if (res.status !== 200) {
const body = await res.json();
throw new Error(body.error.message);
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;
)
err.userError = true
throw err
}
if (skipVerification || isExternal) {
return this.setupDomain(domain, { isExternal });
return this.setupDomain(domain, { isExternal })
}
let ns;
let ns
try {
console.log('> Verifying nameservers…');
const res = await this.getNameservers(domain);
ns = res.nameservers;
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;
)
err2.userError = true
throw err2
}
if (isZeitWorld(ns)) {
console.log(`> Verification ${chalk.bold('OK')}!`);
return this.setupDomain(domain);
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;
const err3 = new Error(DNS_VERIFICATION_ERROR)
err3.userError = true
throw err3
}
async status(name) {
if (!name) {
throw new Error('`domain` is not defined');
throw new Error('`domain` is not defined')
}
const query = encodeQuery({ name });
const query = encodeQuery({ name })
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/status?${query}`);
console.time(`> [debug] #${attempt} GET /domains/status?${query}`)
}
const res = await this._fetch(`/domains/status?${query}`);
const json = await res.json();
const res = await this._fetch(`/domains/status?${query}`)
const json = await res.json()
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/status?${query}`);
console.timeEnd(`> [debug] #${attempt} GET /domains/status?${query}`)
}
return json.available;
});
return json.available
})
}
async price(name) {
if (!name) {
throw new Error('`domain` is not defined');
throw new Error('`domain` is not defined')
}
const query = encodeQuery({ name });
const query = encodeQuery({ name })
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/price?${query}`);
console.time(`> [debug] #${attempt} GET /domains/price?${query}`)
}
const res = await this._fetch(`/domains/price?${query}`);
const json = await res.json();
const res = await this._fetch(`/domains/price?${query}`)
const json = await res.json()
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/price?${query}`);
console.timeEnd(`> [debug] #${attempt} GET /domains/price?${query}`)
}
return json.price;
});
return json.price
})
}
async buy(name) {
const { token } = await cfg.read();
const { token } = await cfg.read()
if (!name) {
throw new Error('`name` is not defined');
throw new Error('`name` is not defined')
}
const rawBody = { name };
const rawBody = { name }
if (name.startsWith('test')) {
rawBody.dev = true;
rawBody.dev = true
}
const body = JSON.stringify(rawBody);
const body = JSON.stringify(rawBody)
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/buy`);
console.time(`> [debug] #${attempt} GET /domains/buy`)
}
const res = await this._fetch(`/domains/buy`, {
method: 'POST',
@ -153,21 +153,21 @@ module.exports = class Domains extends Now {
Authorization: `Bearer ${token}`
},
body
});
const json = await res.json();
})
const json = await res.json()
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/buy`);
console.timeEnd(`> [debug] #${attempt} GET /domains/buy`)
}
if ([400, 403, 500, 503].includes(res.status)) {
const e = new Error();
e.code = json.error.code;
const e = new Error()
e.code = json.error.code
e.message = json.error.message
return bail(e);
return bail(e)
}
return json;
});
return json
})
}
};
}

22
lib/error.js

@ -1,37 +1,37 @@
// Packages
const ms = require('ms');
const chalk = require('chalk');
const ms = require('ms')
const chalk = require('chalk')
const error = require('./utils/output/error');
const error = require('./utils/output/error')
function handleError(err) {
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);
error(err.message)
} else if (err.retryAfter === null) {
error('Rate limit exceeded error. Please try later.');
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 runnung ' +
`${chalk.gray('`')}${chalk.cyan('now upgrade')}${chalk.gray('`')}`
);
)
}
} else if (err.userError) {
error(err.message);
error(err.message)
} else if (err.status === 500) {
error('Unexpected server error. Please retry.');
error('Unexpected server error. Please retry.')
} else {
error(`Unexpected error. Please try later. (${err.message})`);
error(`Unexpected error. Please try later. (${err.message})`)
}
}
module.exports = {
handleError,
error
};
}

11
lib/errors.js

@ -1,17 +1,18 @@
// Packages
const chalk = require('chalk');
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')}`;
> ${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')}.`;
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
};
}

164
lib/get-files.js

@ -1,27 +1,27 @@
// Native
const { resolve } = require('path');
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-promise');
const flatten = require('arr-flatten')
const unique = require('array-unique')
const ignore = require('ignore')
const _glob = require('glob')
const { stat, readdir, readFile } = require('fs-promise')
// Ours
const IGNORED = require('./ignored');
const IGNORED = require('./ignored')
const glob = async function(pattern, options) {
return new Promise((resolve, reject) => {
_glob(pattern, options, (error, files) => {
if (error) {
reject(error);
reject(error)
} else {
resolve(files);
resolve(files)
}
});
});
};
})
})
}
/**
* Remove leading `./` from the beginning of ignores
@ -29,8 +29,8 @@ const glob = async function(pattern, options) {
*/
const clearRelative = function(str) {
return str.replace(/(\n|^)\.\//g, '$1');
};
return str.replace(/(\n|^)\.\//g, '$1')
}
/**
* Returns the contents of a file if it exists.
@ -40,11 +40,11 @@ const clearRelative = function(str) {
const maybeRead = async function(path, default_ = '') {
try {
return await readFile(path, 'utf8');
return await readFile(path, 'utf8')
} catch (err) {
return default_;
return default_
}
};
}
/**
* Transform relative paths into absolutes,
@ -56,11 +56,11 @@ const maybeRead = async function(path, default_ = '') {
const asAbsolute = function(path, parent) {
if (path[0] === '/') {
return path;
return path
}
return resolve(parent, path);
};
return resolve(parent, path)
}
/**
* Returns a list of files in the given
@ -79,86 +79,82 @@ async function npm(
path,
pkg,
nowConfig = null,
{
limit = null,
hasNowJson = false,
debug = false
} = {}
{ limit = null, hasNowJson = false, debug = false } = {}
) {
const whitelist = (nowConfig && nowConfig.files) || pkg.files;
const whitelist = (nowConfig && nowConfig.files) || pkg.files
// The package.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files
const search_ = whitelist || ['.'];
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 npmIgnore = await maybeRead(resolve(path, '.npmignore'), null)
const gitIgnore = npmIgnore === null
? await maybeRead(resolve(path, '.gitignore'))
: null;
: null
const filter = ignore()
.add(
IGNORED + '\n' + clearRelative(npmIgnore === null ? gitIgnore : npmIgnore)
)
.createFilter();
.createFilter()
const prefixLength = path.length + 1;
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) ||
(gitIgnore !== null && pkg.files);
const overrideIgnores =
(pkg.now && pkg.now.files) || (gitIgnore !== null && pkg.files)
const accepts = overrideIgnores
? () => true
: file => {
const relativePath = file.substr(prefixLength);
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true;
return true
}
const accepted = filter(relativePath);
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file);
console.log('> [debug] ignoring "%s"', file)
}
return accepted;
};
return accepted
}
// Locate files
if (debug) {
console.time(`> [debug] locating files ${path}`);
console.time(`> [debug] locating files ${path}`)
}
const files = await explode(search, {
accepts,
limit,
debug
});
})
if (debug) {
console.timeEnd(`> [debug] locating files ${path}`);
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));
files.push(asAbsolute('package.json', path))
if (hasNowJson) {
files.push(asAbsolute('now.json', path));
files.push(asAbsolute('now.json', path))
}
// Get files
return unique(files);
return unique(files)
}
/**
@ -177,24 +173,20 @@ async function npm(
async function docker(
path,
nowConfig = null,
{
limit = null,
hasNowJson = false,
debug = false
} = {}
{ limit = null, hasNowJson = false, debug = false } = {}
) {
const whitelist = nowConfig && nowConfig.files;
const whitelist = nowConfig && nowConfig.files
// Base search path
// the now.json `files` whitelist still
// honors ignores: https://docs.npmjs.com/files/package.json#files
const search_ = whitelist || ['.'];
const search_ = whitelist || ['.']
// Convert all filenames into absolute paths
const search = search_.map(file => asAbsolute(file, path));
const search = search_.map(file => asAbsolute(file, path))
// Compile list of ignored patterns and files
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null);
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null)
const filter = ignore()
.add(
@ -206,44 +198,44 @@ async function docker(
: dockerIgnore
)
)
.createFilter();
.createFilter()
const prefixLength = path.length + 1;
const prefixLength = path.length + 1
const accepts = function(file) {
const relativePath = file.substr(prefixLength);
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true;
return true
}
const accepted = filter(relativePath);
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file);
console.log('> [debug] ignoring "%s"', file)
}
return accepted;
};
return accepted
}
// Locate files
if (debug) {
console.time(`> [debug] locating files ${path}`);
console.time(`> [debug] locating files ${path}`)
}
const files = await explode(search, { accepts, limit, debug });
const files = await explode(search, { accepts, limit, debug })
if (debug) {
console.timeEnd(`> [debug] locating files ${path}`);
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));
files.push(asAbsolute('Dockerfile', path))
if (hasNowJson) {
files.push(asAbsolute('now.json', path));
files.push(asAbsolute('now.json', path))
}
// Get files
return unique(files);
return unique(files)
}
/**
@ -262,50 +254,50 @@ async function docker(
async function explode(paths, { accepts, debug }) {
const list = async file => {
let path = file;
let s;
let path = file
let s
if (!accepts(file)) {
return null;
return null
}
try {
s = await stat(path);
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';
path = file + '.js'
try {
s = await stat(path);
s = await stat(path)
} catch (e2) {
if (debug) {
console.log('> [debug] ignoring invalid file "%s"', file);
console.log('> [debug] ignoring invalid file "%s"', file)
}
return null;
return null
}
}
if (s.isDirectory()) {
const all = await readdir(file);
const all = await readdir(file)
/* eslint-disable no-use-before-define */
return many(all.map(subdir => asAbsolute(subdir, file)));
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);
console.log('> [debug] ignoring special file "%s"', file)
}
return null;
return null
}
return path;
};
return path
}
const many = all => Promise.all(all.map(file => list(file)));
return flatten(await many(paths)).filter(v => v !== null);
const many = all => Promise.all(all.map(file => list(file)))
return flatten(await many(paths)).filter(v => v !== null)
}
module.exports = {
npm,
docker
};
}

165
lib/git.js

@ -1,211 +1,210 @@
// Native
const path = require('path');
const url = require('url');
const childProcess = require('child_process');
const path = require('path')
const url = require('url')
const childProcess = require('child_process')
// Packages
const fs = require('fs-promise');
const download = require('download');
const tmp = require('tmp-promise');
const isURL = require('is-url');
const fs = require('fs-promise')
const download = require('download')
const tmp = require('tmp-promise')
const isURL = require('is-url')
const cloneRepo = (parts, tmpDir) =>
new Promise((resolve, reject) => {
let host;
let host
switch (parts.type) {
case 'GitLab':
host = `gitlab.com`;
break;
host = `gitlab.com`
break
case 'Bitbucket':
host = `bitbucket.org`;
break;
host = `bitbucket.org`
break
default:
host = `github.com`;
host = `github.com`
}
const url = `https://${host}/${parts.main}`;
const ref = parts.ref ||
(parts.type === 'Bitbucket' ? 'default' : 'master');
const cmd = `git clone ${url} --single-branch ${ref}`;
const url = `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);
reject(err)
}
resolve(stdout);
});
});
resolve(stdout)
})
})
const renameRepoDir = async (pathParts, tmpDir) => {
const tmpContents = await fs.readdir(tmpDir.path);
const tmpContents = await fs.readdir(tmpDir.path)
const oldTemp = path.join(tmpDir.path, tmpContents[0]);
const newTemp = path.join(tmpDir.path, pathParts.main.replace('/', '-'));
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;
await fs.rename(oldTemp, newTemp)
tmpDir.path = newTemp
return tmpDir;
};
return tmpDir
}
const capitalizePlatform = name => {
const names = {
github: 'GitHub',
gitlab: 'GitLab',
bitbucket: 'Bitbucket'
};
}
return names[name];
};
return names[name]
}
const splittedURL = fullURL => {
const parsedURL = url.parse(fullURL);
const pathParts = parsedURL.path.split('/');
const parsedURL = url.parse(fullURL)
const pathParts = parsedURL.path.split('/')
pathParts.shift();
pathParts.shift()
// Set path to repo...
const main = pathParts[0] + '/' + pathParts[1];
const main = pathParts[0] + '/' + pathParts[1]
// ...and then remove it from the parts
pathParts.splice(0, 2);
pathParts.splice(0, 2)
// Assign Git reference
let ref = pathParts.length >= 2 ? pathParts[1] : '';
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);
ref = ref.substring(0, 7)
}
}
// We're deploying master by default,
// so there's no need to indicate it explicitly
if (ref === 'master') {
ref = '';
ref = ''
}
return {
main,
ref,
type: capitalizePlatform(parsedURL.host.split('.')[0])
};
};
}
}
const gitPathParts = main => {
let ref = '';
let ref = ''
if (isURL(main)) {
return splittedURL(main);
return splittedURL(main)
}
if (main.split('/')[1].includes('#')) {
const parts = main.split('#');
const parts = main.split('#')
ref = parts[1];
main = parts[0];
ref = parts[1]
main = parts[0]
}
return {
main,
ref,
type: capitalizePlatform('github')
};
};
}
}
const downloadRepo = async repoPath => {
const pathParts = gitPathParts(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;
let gitInstalled = true
try {
await cloneRepo(pathParts, tmpDir);
await cloneRepo(pathParts, tmpDir)
} catch (err) {
gitInstalled = false;
gitInstalled = false
}
if (gitInstalled) {
const renaming = await renameRepoDir(pathParts, tmpDir);
return renaming;
const renaming = await renameRepoDir(pathParts, tmpDir)
return renaming
}
let url;
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;
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;
url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref || 'default'}.zip`
break
default:
url = `https://api.github.com/repos/${pathParts.main}/tarball/${pathParts.ref}`;
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;
tmpDir.cleanup()
return false
}
const renaming = await renameRepoDir(pathParts, tmpDir);
return renaming;
};
const renaming = await renameRepoDir(pathParts, tmpDir)
return renaming
}
const isRepoPath = path => {
if (!path) {
return false;
return false
}
const allowedHosts = ['github.com', 'gitlab.com', 'bitbucket.org'];
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;
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;
return true
}
return 'no-valid-url';
return 'no-valid-url'
}
return /[^\s\\]\/[^\s\\]/g.test(path);
};
return /[^\s\\]\/[^\s\\]/g.test(path)
}
const fromGit = async (path, debug) => {
let tmpDir = false;
let tmpDir = false
try {
tmpDir = await downloadRepo(path);
tmpDir = await downloadRepo(path)
} catch (err) {
if (debug) {
console.log(`Could not download "${path}" repo from GitHub`);
console.log(`Could not download "${path}" repo from GitHub`)
}
}
return tmpDir;
};
return tmpDir
}
module.exports = {
gitPathParts,
isRepoPath,
fromGit
};
}

34
lib/hash.js

@ -1,9 +1,9 @@
// Native
const { createHash } = require('crypto');
const path = require('path');
const { createHash } = require('crypto')
const path = require('path')
// Packages
const { readFile } = require('fs-promise');
const { readFile } = require('fs-promise')
/**
* Computes hashes for the contents of each file given.
@ -13,30 +13,30 @@ const { readFile } = require('fs-promise');
*/
async function hashes(files, isStatic, pkg) {
const map = new Map();
const map = new Map()
await Promise.all(
files.map(async name => {
const filename = path.basename(name);
let data;
const filename = path.basename(name)
let data
if (isStatic && filename === 'package.json') {
const packageString = JSON.stringify(pkg, null, 2);
data = Buffer.from(packageString);
const packageString = JSON.stringify(pkg, null, 2)
data = Buffer.from(packageString)
} else {
data = await readFile(name);
data = await readFile(name)
}
const h = hash(data);
const entry = map.get(h);
const h = hash(data)
const entry = map.get(h)
if (entry) {
entry.names.push(name);
entry.names.push(name)
} else {
map.set(hash(data), { names: [name], data });
map.set(hash(data), { names: [name], data })
}
})
);
return map;
)
return map
}
/**
@ -47,7 +47,7 @@ async function hashes(files, isStatic, pkg) {
*/
function hash(buf) {
return createHash('sha1').update(buf).digest('hex');
return createHash('sha1').update(buf).digest('hex')
}
module.exports = hashes;
module.exports = hashes

2
lib/ignored.js

@ -14,4 +14,4 @@ module.exports = `.hg
npm-debug.log
config.gypi
node_modules
CVS`;
CVS`

4
lib/indent.js

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

644
lib/index.js

File diff suppressed because it is too large

12
lib/is-zeit-world.js

@ -13,7 +13,7 @@ const nameservers = new Set([
'paris.zeit.world',
'frankfurt.zeit.world',
'singapore.zeit.world'
]);
])
/**
* Given an array of nameservers (ie: as returned
@ -21,10 +21,12 @@ const nameservers = new Set([
* zeit.world's.
*/
function isZeitWorld(ns) {
return ns.length > 1 &&
return (
ns.length > 1 &&
ns.every(host => {
return nameservers.has(host);
});
return nameservers.has(host)
})
)
}
module.exports = isZeitWorld;
module.exports = isZeitWorld

98
lib/login.js

@ -1,22 +1,22 @@
// Native
const os = require('os');
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');
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');
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 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: {
@ -25,98 +25,98 @@ async function getVerificationData(url, email) {
'User-Agent': ua
},
body: data
});
})
if (res.status !== 200) {
throw new Error('Verification error');
throw new Error('Verification error')
}
const body = await res.json();
return body;
const body = await res.json()
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;
)
const body = await res.json()
return body.token
}
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
setTimeout(resolve, ms)
})
}
async function register(url, { retryEmail = false } = {}) {
let email;
let email
try {
email = await readEmail({ invalid: retryEmail });
email = await readEmail({ invalid: retryEmail })
} catch (err) {
process.stdout.write('\n');
throw err;
process.stdout.write('\n')
throw err
}
process.stdout.write('\n');
process.stdout.write('\n')
if (!validate(email)) {
return register(url, { retryEmail: true });
return register(url, { retryEmail: true })
}
const { token, securityCode } = await getVerificationData(url, email);
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');
process.stdout.write('\n')
const spinner = ora({
text: 'Waiting for confirmation...'
}).start();
}).start()
let final;
let final
/* eslint-disable no-await-in-loop */
do {
await sleep(2500);
await sleep(2500)
try {
final = await verify(url, email, token);
final = await verify(url, email, token)
} catch (err) {}
} while (!final);
} while (!final)
/* eslint-enable no-await-in-loop */
let user;
let user
try {
user = (await (await fetch(`${url}/www/user`, {
headers: {
Authorization: `Bearer ${final}`
}
})).json()).user;
})).json()).user
} catch (err) {
spinner.stop();
throw new Error(`Couldn't retrieve user details ${err.message}`);
spinner.stop()
throw new Error(`Couldn't retrieve user details ${err.message}`)
}
spinner.text = 'Confirmed email address!';
spinner.stopAndPersist('✔');
spinner.text = 'Confirmed email address!'
spinner.stopAndPersist('✔')
process.stdout.write('\n');
process.stdout.write('\n')
return {
token: final,
@ -126,13 +126,13 @@ async function register(url, { retryEmail = false } = {}) {
email: user.email
},
lastUpdate: Date.now()
};
}
}
module.exports = async function(url) {
const loginData = await register(url);
await cfg.merge(loginData);
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;
};
return loginData.token
}

12
lib/logs.js

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

8
lib/pkg.js

@ -1,10 +1,10 @@
/* eslint-disable import/no-unresolved */
let pkg;
let pkg
try {
pkg = require('../package.json');
pkg = require('../package.json')
} catch (err) {
pkg = require('../../package.json');
pkg = require('../../package.json')
}
module.exports = pkg;
module.exports = pkg

32
lib/plans.js

@ -1,16 +1,16 @@
const ms = require('ms');
const ms = require('ms')
const Now = require('../lib');
const Now = require('../lib')
async function parsePlan(json) {
const { subscription } = json
let id;
let until;
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');
const planItems = subscription.items.data
const mainPlan = planItems.find(d => d.plan.metadata.is_main_plan === '1')
if (mainPlan) {
id = mainPlan.plan.id
@ -19,39 +19,39 @@ async function parsePlan(json) {
until = ms(
new Date(subscription.current_period_end * 1000) - new Date(),
{ long: true }
);
)
}
} else {
id = 'oss'
}
} else {
id = 'oss';
id = 'oss'
}
return { id, name, until };
return { id, name, until }
}
module.exports = class Plans extends Now {
async getCurrent() {
const res = await this._fetch('/plan');
const res = await this._fetch('/plan')
const json = await res.json()
return parsePlan(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);
return parsePlan(json)
}
const err = new Error(json.error.message);
const err = new Error(json.error.message)
err.code = json.error.code
throw err;
throw err
}
};
}

88
lib/re-alias.js

@ -1,24 +1,31 @@
// Native
const { join } = require('path');
const { join } = require('path')
// Packages
const fs = require('fs-promise');
const chalk = require('chalk');
const fs = require('fs-promise')
const chalk = require('chalk')
// Ours
const { error } = require('./error');
const readMetaData = require('./read-metadata');
const NowAlias = require('./alias');
const { error } = require('./error')
const readMetaData = require('./read-metadata')
const NowAlias = require('./alias')
exports.assignAlias = async (autoAlias, token, deployment, apiUrl, debug, currentTeam) => {
const aliases = new NowAlias({apiUrl, token, debug, currentTeam });
exports.assignAlias = async (
autoAlias,
token,
deployment,
apiUrl,
debug,
currentTeam
) => {
const aliases = new NowAlias({ apiUrl, token, debug, currentTeam })
console.log(
`> Assigning alias ${chalk.bold.underline(autoAlias)} to deployment...`
);
)
// Assign alias
await aliases.set(String(deployment), String(autoAlias));
};
await aliases.set(String(deployment), String(autoAlias))
}
exports.reAlias = async (
token,
@ -30,52 +37,52 @@ exports.reAlias = async (
debug,
alias
) => {
const path = process.cwd();
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;
)
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;
const lastAlias = await alias.last(name)
host = lastAlias.url
}
if (!nowConfig) {
help();
return exit(0);
help()
return exit(0)
}
let pointers = [];
let pointers = []
if (pointer) {
pointers.push(pointer);
pointers.push(pointer)
} else {
if (nowConfig.alias) {
const value = nowConfig.alias;
const value = nowConfig.alias
if (typeof value === 'string') {
pointers.push(value);
pointers.push(value)
} else if (Array.isArray(value)) {
pointers = pointers.concat(nowConfig.alias);
pointers = pointers.concat(nowConfig.alias)
} else {
error(
`Property ${chalk.grey('aliases')} is not a valid array or string`
);
return exit(1);
)
return exit(1)
}
}
@ -83,23 +90,32 @@ exports.reAlias = async (
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');
)
console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n')
pointers = pointers.concat(nowConfig.aliases);
pointers = pointers.concat(nowConfig.aliases)
}
}
if (pointers.length === 0) {
help();
return exit(0);
help()
return exit(0)
}
const assignments = [];
const assignments = []
for (const pointer of pointers) {
assignments.push(exports.assignAlias(pointer, token, host, apiUrl, debug, alias.currentTeam));
assignments.push(
exports.assignAlias(
pointer,
token,
host,
apiUrl,
debug,
alias.currentTeam
)
)
}
await Promise.all(assignments);
};
await Promise.all(assignments)
}

119
lib/read-metadata.js

@ -1,13 +1,13 @@
// Native
const { basename, resolve: resolvePath } = require('path');
const { basename, resolve: resolvePath } = require('path')
// Packages
const chalk = require('chalk');
const { readFile, exists } = require('fs-promise');
const { parse: parseDockerfile } = require('docker-file-parser');
const chalk = require('chalk')
const { readFile, exists } = require('fs-promise')
const { parse: parseDockerfile } = require('docker-file-parser')
// Helpers
const { error } = require('../lib/error');
const { error } = require('../lib/error')
const listPackage = {
scripts: {
@ -16,9 +16,9 @@ const listPackage = {
dependencies: {
serve: '5.0.4'
}
};
}
module.exports = readMetaData;
module.exports = readMetaData
async function readMetaData(
path,
@ -30,22 +30,22 @@ async function readMetaData(
isStatic = false
}
) {
let pkg = {};
let nowConfig = null;
let hasNowJson = false;
let pkg = {}
let nowConfig = null
let hasNowJson = false
let name;
let description;
let name
let description
try {
nowConfig = JSON.parse(await readFile(resolvePath(path, 'now.json')));
hasNowJson = true;
nowConfig = JSON.parse(await readFile(resolvePath(path, 'now.json')))
hasNowJson = true
} catch (err) {
// If the file doesn't exist then that's fine; any other error bubbles up
if (err.code !== 'ENOENT') {
const e = Error(`Failed to read JSON in "${path}/now.json"`);
e.userError = true;
throw e;
const e = Error(`Failed to read JSON in "${path}/now.json"`)
e.userError = true
throw e
}
}
@ -53,119 +53,116 @@ async function readMetaData(
// User can specify the type of deployment explicitly in the `now.json` file
// when both a package.json and Dockerfile exist
if (nowConfig.type) {
deploymentType = nowConfig.type;
deploymentType = nowConfig.type
} else if (
nowConfig.type === undefined &&
!await exists(resolvePath(path, 'package.json'))
) {
deploymentType = 'static';
deploymentType = 'static'
}
if (nowConfig.name) {
deploymentName = nowConfig.name;
deploymentName = nowConfig.name
}
}
if (deploymentType === 'static') {
isStatic = true;
deploymentType = 'npm';
isStatic = true
deploymentType = 'npm'
}
if (deploymentType === 'npm') {
if (isStatic) {
pkg = listPackage;
pkg = listPackage
} else {
try {
pkg = JSON.parse(await readFile(resolvePath(path, 'package.json')));
pkg = JSON.parse(await readFile(resolvePath(path, 'package.json')))
} catch (err) {
error(`Failed to read JSON in "${path}/package.json"`);
error(`Failed to read JSON in "${path}/package.json"`)
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
process.exit(1)
}
}
if (!deploymentName) {
if (typeof pkg.name === 'string' && pkg.name !== '') {
name = pkg.name;
name = pkg.name
} else {
name = basename(path);
name = basename(path)
if (!quiet && !isStatic) {
console.log(
`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`
);
)
}
}
}
description = pkg.description;
description = pkg.description
} else if (deploymentType === 'docker') {
let docker;
let docker
try {
const dockerfile = await readFile(
resolvePath(path, 'Dockerfile'),
'utf8'
);
docker = parseDockerfile(dockerfile, { includeComments: true });
const dockerfile = await readFile(resolvePath(path, 'Dockerfile'), 'utf8')
docker = parseDockerfile(dockerfile, { includeComments: true })
} catch (err) {
const e = Error(`Failed to parse "${path}/Dockerfile"`);
e.userError = true;
throw e;
const e = Error(`Failed to parse "${path}/Dockerfile"`)
e.userError = true
throw e
}
if (strict && docker.length <= 0) {
const e = Error('No commands found in `Dockerfile`');
e.userError = true;
throw e;
const e = Error('No commands found in `Dockerfile`')
e.userError = true
throw e
}
const labels = {};
const labels = {}
docker.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => {
for (const key in args) {
if (!{}.hasOwnProperty.call(args, key)) {
continue;
continue
}
// Unescape and convert into string
try {
labels[key] = JSON.parse(args[key]);
labels[key] = JSON.parse(args[key])
} catch (err) {
const e = Error(
`Error parsing value for LABEL ${key} in \`Dockerfile\``
);
e.userError = true;
throw e;
)
e.userError = true
throw e
}
}
});
})
if (!deploymentName) {
if (labels.name) {
name = labels.name;
name = labels.name
} else {
name = basename(path);
name = basename(path)
if (!quiet) {
if (hasNowJson) {
console.log(
`> No \`name\` LABEL in \`Dockerfile\` or \`name\` field in \`now.json\`, using ${chalk.bold(name)}`
);
)
} else {
console.log(
`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`
);
)
}
}
}
}
description = labels.description;
description = labels.description
} else {
throw new TypeError(`Unsupported "deploymentType": ${deploymentType}`);
throw new TypeError(`Unsupported "deploymentType": ${deploymentType}`)
}
if (deploymentName) {
name = deploymentName;
name = deploymentName
}
if (pkg.now) {
@ -177,11 +174,11 @@ async function readMetaData(
'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"
);
e.userError = true;
throw e;
)
e.userError = true
throw e
} else {
nowConfig = pkg.now;
nowConfig = pkg.now
}
}
@ -192,5 +189,5 @@ async function readMetaData(
pkg,
nowConfig,
hasNowJson
};
}
}

62
lib/scale-info.js

@ -1,8 +1,8 @@
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');
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 (
@ -10,63 +10,63 @@ function barify(cur, tot) {
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;
const match = await now.findDeployment(url)
const { min, max, current } = match.scale
let targetReplicaCount = min;
let targetReplicaCount = min
if (current < min) {
targetReplicaCount = min;
targetReplicaCount = min
} else if (current > max) {
targetReplicaCount = max;
targetReplicaCount = max
} else {
console.log(`> Nothing to do, already scaled.`);
return;
console.log(`> Nothing to do, already scaled.`)
return
}
if (targetReplicaCount === 0) {
console.log(`> Scaled to 0 instances`);
return;
console.log(`> Scaled to 0 instances`)
return
}
const startTime = Date.now();
const startTime = Date.now()
let barcurr = current;
const end = Math.max(current, max);
let barcurr = current
const end = Math.max(current, max)
linelog(
`> 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);
const res = await now.listInstances(match.uid)
if (barcurr !== res.length) {
barcurr = res.length;
barcurr = res.length
linelog(
`> Scaling to ${chalk.bold(String(targetReplicaCount) + (targetReplicaCount === 1 ? ' instance' : ' instances'))}: ` +
barify(barcurr, end)
);
)
if (barcurr === targetReplicaCount) {
linelog.clear();
linelog.clear()
linelog(
`> Scaled to ${chalk.bold(String(targetReplicaCount) + (targetReplicaCount === 1 ? ' instance' : ' instances'))}: ${chalk.gray('[' + ms(Date.now() - startTime) + ']')}\n`
);
return res;
)
return res
}
}
throw new Error('Not ready yet');
throw new Error('Not ready yet')
},
{ retries: 5000, minTimeout: 10, maxTimeout: 20 }
);
)
process.nextTick(() => {
instances.forEach(inst => {
console.log(` - ${chalk.underline(inst.url)}`);
});
});
};
console.log(` - ${chalk.underline(inst.url)}`)
})
})
}

29
lib/scale.js

@ -1,43 +1,40 @@
// Ours
const Now = require('../lib');
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`
);
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'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
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);
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message);
throw new Error(body.error.message)
}
return body;
});
return body
})
}
};
}

72
lib/secrets.js

@ -1,49 +1,49 @@
// Ours
const Now = require('../lib');
const Now = require('../lib')
module.exports = class Secrets extends Now {
ls() {
return this.listSecrets();
return this.listSecrets()
}
rm(nameOrId) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`);
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}`);
console.timeEnd(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
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);
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message);
throw new Error(body.error.message)
}
return body;
});
return body
})
}
add(name, value) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST /secrets`);
console.time(`> [debug] #${attempt} POST /secrets`)
}
const res = await this._fetch('/now/secrets', {
@ -52,36 +52,36 @@ module.exports = class Secrets extends Now {
name,
value: value.toString()
}
});
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST /secrets`);
console.timeEnd(`> [debug] #${attempt} POST /secrets`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
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);
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message);
throw new Error(body.error.message)
}
return body;
});
return body
})
}
rename(nameOrId, newName) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`);
console.time(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`)
}
const res = await this._fetch(`/now/secrets/${nameOrId}`, {
@ -89,29 +89,29 @@ module.exports = class Secrets extends Now {
body: {
name: newName
}
});
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`);
console.timeEnd(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
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);
const err = new Error(body.error.message)
err.userError = true
return bail(err)
}
throw new Error(body.error.message);
throw new Error(body.error.message)
}
return body;
});
return body
})
}
};
}

4
lib/strlen.js

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

56
lib/teams.js

@ -1,10 +1,10 @@
const Now = require('../lib');
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}`);
console.time(`> [debug] #${attempt} POST /teams}`)
}
const res = await this._fetch(`/teams`, {
@ -12,18 +12,17 @@ module.exports = class Teams extends Now {
body: {
slug
}
});
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST /teams`);
console.timeEnd(`> [debug] #${attempt} POST /teams`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
const body = await res.json()
if (res.status === 400) {
const e = new Error(body.error.message)
@ -36,13 +35,13 @@ module.exports = class Teams extends Now {
}
return body
});
})
}
async edit({ id, slug, name }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} PATCH /teams/${id}}`);
console.time(`> [debug] #${attempt} PATCH /teams/${id}}`)
}
const payload = {}
@ -56,18 +55,17 @@ module.exports = class Teams extends Now {
const res = await this._fetch(`/teams/${id}`, {
method: 'PATCH',
body: payload
});
})
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PATCH /teams/${id}`);
console.timeEnd(`> [debug] #${attempt} PATCH /teams/${id}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
const body = await res.json()
if (res.status === 400) {
const e = new Error(body.error.message)
@ -80,30 +78,30 @@ module.exports = class Teams extends Now {
}
return body
});
})
}
async inviteUser({teamId, email}) {
async inviteUser({ teamId, email }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST /teams/${teamId}/members}`);
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}`);
console.timeEnd(`> [debug] #${attempt} POST /teams/${teamId}/members}`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
const body = await res.json();
const body = await res.json()
if (res.status === 400) {
const e = new Error(body.error.message)
@ -116,26 +114,26 @@ module.exports = class Teams extends Now {
}
return body
});
})
}
async ls() {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /teams}`);
console.time(`> [debug] #${attempt} GET /teams}`)
}
const res = await this._fetch(`/teams`);
const res = await this._fetch(`/teams`)
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /teams`);
console.timeEnd(`> [debug] #${attempt} GET /teams`)
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
return bail(new Error('Unauthorized'))
}
return res.json();
});
return res.json()
})
}
};
}

18
lib/test.js

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

8
lib/to-host.js

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

6
lib/ua.js

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

28
lib/user.js

@ -1,13 +1,13 @@
const _fetch = require('node-fetch');
const _fetch = require('node-fetch')
function _filter(data) {
data = data.user;
data = data.user
return {
userId: data.uid,
username: data.username,
email: data.email
};
}
}
/**
@ -23,32 +23,32 @@ function _filter(data) {
async function get(
{ fetch, token, apiUrl = 'https://api.zeit.co', filter = true } = {}
) {
let headers = {};
const endpoint = '/www/user';
const url = fetch ? endpoint : apiUrl + endpoint;
let headers = {}
const endpoint = '/www/user'
const url = fetch ? endpoint : apiUrl + endpoint
if (!fetch) {
headers = {
Authorization: `Bearer ${token}`
};
fetch = _fetch;
}
fetch = _fetch
}
try {
const res = await fetch(url, { headers });
const res = await fetch(url, { headers })
const json = await res.json();
const json = await res.json()
if (filter) {
return _filter(json);
return _filter(json)
}
return json;
return json
} catch (err) {
return {};
return {}
}
}
module.exports = {
get,
filter: _filter
};
}

28
lib/utils/billing/geocode.js

@ -1,33 +1,33 @@
// Packages
const gMaps = require('@google/maps');
const gMaps = require('@google/maps')
const MAPS_API_KEY = 'AIzaSyALfKTQ6AiIoJ8WGDXR3E7IBOwlHoTPfYY';
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 });
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();
resolve()
}
const data = res.json.results[0];
const components = {};
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;
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
});
})
}
);
});
};
)
})
}

36
lib/utils/check-path.js

@ -1,49 +1,49 @@
// Native
const os = require('os');
const path = require('path');
const os = require('os')
const path = require('path')
const checkPath = async dir => {
if (!dir) {
return;
return
}
const home = os.homedir();
let location;
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;
continue
}
if (dir === paths[locationPath]) {
location = locationPath;
location = locationPath
}
}
if (!location) {
return;
return
}
let locationName;
let locationName
switch (location) {
case 'home':
locationName = 'user directory';
break;
locationName = 'user directory'
break
case 'downloads':
locationName = 'downloads directory';
break;
locationName = 'downloads directory'
break
default:
locationName = location;
locationName = location
}
throw new Error(`You're trying to deploy your ${locationName}.`);
};
throw new Error(`You're trying to deploy your ${locationName}.`)
}
module.exports = checkPath;
module.exports = checkPath

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

@ -1,13 +1,13 @@
const error = require('../output/error');
const error = require('../output/error')
module.exports = function (err) {
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')
error("Domain can't be purchased at this time")
break
}
case 'service_unavailabe': {

4
lib/utils/exit.js

@ -4,5 +4,5 @@ module.exports = code => {
// stdout writes are asynchronous
// https://github.com/nodejs/node/issues/6456
/* eslint-disable unicorn/no-process-exit */
setTimeout(() => process.exit(code || 0), 100);
};
setTimeout(() => process.exit(code || 0), 100)
}

10
lib/utils/fatal-error.js

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

94
lib/utils/input/list.js

@ -1,92 +1,90 @@
const chalk = require('chalk');
const inquirer = require('inquirer');
const stripAnsi = require('strip-ansi');
const chalk = require('chalk')
const inquirer = require('inquirer')
const stripAnsi = require('strip-ansi')
/* eslint-disable no-multiple-empty-lines, no-var, no-undef, no-eq-null, eqeqeq, semi */
inquirer.prompt.prompts.list.prototype.getQuestion = function() {
var message = chalk.bold('> ' + this.opt.message) + ' ';
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 + ') ');
message += chalk.dim('(' + this.opt.default + ') ')
}
return message;
};
return message
}
/* eslint-enable */
function getLength(string) {
let biggestLength = 0;
let biggestLength = 0
string.split('\n').map(str => {
str = stripAnsi(str);
str = stripAnsi(str)
if (str.length > biggestLength) {
biggestLength = str.length;
biggestLength = str.length
}
return undefined;
});
return biggestLength;
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;
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);
const length = getLength(choice.name)
if (length > biggestLength) {
biggestLength = length;
biggestLength = length
}
return choice;
return choice
}
throw new Error('Invalid 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 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);
const blankSep = choices.shift()
choices.unshift(abortSeparator)
choices.unshift(_abort)
choices.unshift(blankSep)
} else {
choices.push(abortSeparator);
choices.push(_abort);
choices.push(abortSeparator)
choices.push(_abort)
}
const nonce = Date.now();
const nonce = Date.now()
const answer = await inquirer.prompt({
name: nonce,
type: 'list',
message,
choices,
pageSize
});
})
return answer[nonce];
};
return answer[nonce]
}

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

@ -1,4 +1,4 @@
const chalk = require('chalk');
const chalk = require('chalk')
module.exports = (
label,
@ -14,35 +14,35 @@ module.exports = (
} = {}
) => {
return new Promise((resolve, reject) => {
const isRaw = process.stdin.isRaw;
const isRaw = process.stdin.isRaw
stdin.setRawMode(true);
stdin.resume();
stdin.setRawMode(true)
stdin.resume()
function restore() {
stdout.write(trailing);
stdin.setRawMode(isRaw);
stdin.pause();
stdin.removeListener('data', onData);
stdout.write(trailing)
stdin.setRawMode(isRaw)
stdin.pause()
stdin.removeListener('data', onData)
}
function onData(buffer) {
const data = buffer.toString();
const data = buffer.toString()
if (abortSequences.has(data)) {
restore();
return reject(new Error('USER_ABORT'));
restore()
return reject(new Error('USER_ABORT'))
}
if (resolveChars.has(data[0])) {
restore();
resolve(defaultValue);
restore()
resolve(defaultValue)
} else if (data[0].toLowerCase() === yesChar) {
restore();
resolve(true);
restore()
resolve(true)
} else if (data[0].toLowerCase() === noChar) {
restore();
resolve(false);
restore()
resolve(false)
} else {
// ignore extraneous input
}
@ -52,8 +52,8 @@ module.exports = (
? `[${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);
});
};
: `[${yesChar}|${chalk.bold(noChar.toUpperCase())}]`
stdout.write(`${chalk.gray('>')} ${label} ${chalk.gray(defaultText)} `)
stdin.on('data', onData)
})
}

2
lib/utils/input/regexes.js

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

203
lib/utils/input/text.js

@ -1,9 +1,9 @@
const ansiEscapes = require('ansi-escapes');
const ansiRegex = require('ansi-regex');
const chalk = require('chalk');
const stripAnsi = require('strip-ansi');
const ansiEscapes = require('ansi-escapes')
const ansiRegex = require('ansi-regex')
const chalk = require('chalk')
const stripAnsi = require('strip-ansi')
const eraseLines = require('../output/erase-lines');
const eraseLines = require('../output/erase-lines')
const ESCAPES = {
LEFT: '\u001B[D',
@ -12,7 +12,7 @@ const ESCAPES = {
BACKSPACE: '\u0008',
CTRL_H: '\u007F',
CARRIAGE: '\r'
};
}
module.exports = function(
{
@ -54,188 +54,191 @@ module.exports = function(
} = {}
) {
return new Promise((resolve, reject) => {
const isRaw = process.stdin.isRaw;
const isRaw = process.stdin.isRaw
let value;
let caretOffset = 0;
let regex;
let suggestion = '';
let value
let caretOffset = 0
let regex
let suggestion = ''
if (valid) {
stdout.write(label);
stdout.write(label)
} else {
const _label = label.replace('-', '✖');
stdout.write(chalk.red(_label));
const _label = label.replace('-', '✖')
stdout.write(chalk.red(_label))
}
value = initialValue;
stdout.write(initialValue);
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)));
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);
}
.reduce((prev, curr) => {
if (curr !== ' ' && !prev.includes(curr)) {
if (curr === '/') {
prev.push(' / ')
} else {
prev.push(curr)
}
return prev;
},
[]
)
.join('|');
regex = new RegExp(`(${regex})`, 'g');
}
return prev
}, [])
.join('|')
regex = new RegExp(`(${regex})`, 'g')
}
stdin.setRawMode(true);
stdin.resume();
stdin.setRawMode(true)
stdin.resume()
function restore() {
stdin.setRawMode(isRaw);
stdin.pause();
stdin.removeListener('data', onData);
stdin.setRawMode(isRaw)
stdin.pause()
stdin.removeListener('data', onData)
if (trailing) {
stdout.write(trailing);
stdout.write(trailing)
}
}
async function onData(buffer) {
let data = buffer.toString();
value = stripAnsi(value);
let data = buffer.toString()
value = stripAnsi(value)
if (abortSequences.has(data)) {
restore();
return reject(new Error('USER_ABORT'));
restore()
return reject(new Error('USER_ABORT'))
}
if (forceLowerCase) {
data = data.toLowerCase();
data = data.toLowerCase()
}
if (suggestion !== '' && !caretOffset && autoCompleteChars.has(data)) {
value += stripAnsi(suggestion);
suggestion = '';
value += stripAnsi(suggestion)
suggestion = ''
} else if (data === ESCAPES.LEFT) {
if (value.length > Math.abs(caretOffset)) {
caretOffset--;
caretOffset--
}
} else if (data === ESCAPES.RIGHT) {
if (caretOffset < 0) {
caretOffset++;
caretOffset++
}
} else if (eraseSequences.has(data)) {
let char;
let char
if (mask && value.length > Math.abs(caretOffset)) {
if (value[value.length + caretOffset - 1] === ' ') {
if (value[value.length + caretOffset - 2] === '/') {
caretOffset -= 1;
caretOffset -= 1
}
char = placeholder[value.length + caretOffset];
value = value.substr(0, value.length + caretOffset - 2) +
char = placeholder[value.length + caretOffset]
value =
value.substr(0, value.length + caretOffset - 2) +
char +
value.substr(value.length + caretOffset - 1);
caretOffset--;
value.substr(value.length + caretOffset - 1)
caretOffset--
} else {
char = placeholder[value.length + caretOffset - 1];
value = value.substr(0, value.length + caretOffset - 1) +
char = placeholder[value.length + caretOffset - 1]
value =
value.substr(0, value.length + caretOffset - 1) +
char +
value.substr(value.length + caretOffset);
value.substr(value.length + caretOffset)
}
caretOffset--;
caretOffset--
} else {
value = value.substr(0, value.length + caretOffset - 1) +
value.substr(value.length + caretOffset);
value =
value.substr(0, value.length + caretOffset - 1) +
value.substr(value.length + caretOffset)
}
suggestion = '';
suggestion = ''
} else if (resolveChars.has(data)) {
if (validateValue(value)) {
restore();
resolve(value);
restore()
resolve(value)
} else {
if (mask === 'cc' || mask === 'ccv') {
value = value.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim();
value = value.replace(regex, chalk.gray('$1'));
value = value.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim()
value = value.replace(regex, chalk.gray('$1'))
} else if (mask === 'expDate') {
value = value.replace(regex, chalk.gray('$1'));
value = value.replace(regex, chalk.gray('$1'))
}
const l = chalk.red(label.replace('-', '✖'));
eraseLines(1);
stdout.write(l + value + ansiEscapes.beep);
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;
return
} else if (!ansiRegex().test(data)) {
let tmp = value.substr(0, value.length + caretOffset) +
let tmp =
value.substr(0, value.length + caretOffset) +
data +
value.substr(value.length + caretOffset);
value.substr(value.length + caretOffset)
if (mask) {
if (/\d/.test(data) && caretOffset !== 0) {
if (value[value.length + caretOffset + 1] === ' ') {
tmp = value.substr(0, value.length + caretOffset) +
tmp =
value.substr(0, value.length + caretOffset) +
data +
value.substr(value.length + caretOffset + 1);
caretOffset += 2;
value.substr(value.length + caretOffset + 1)
caretOffset += 2
if (value[value.length + caretOffset] === '/') {
caretOffset += 2;
caretOffset += 2
}
} else {
tmp = value.substr(0, value.length + caretOffset) +
tmp =
value.substr(0, value.length + caretOffset) +
data +
value.substr(value.length + caretOffset + 1);
caretOffset++;
value.substr(value.length + caretOffset + 1)
caretOffset++
}
} else if (/\s/.test(data) && caretOffset < 0) {
caretOffset++;
tmp = value;
caretOffset++
tmp = value
} else {
return stdout.write(ansiEscapes.beep);
return stdout.write(ansiEscapes.beep)
}
value = tmp;
value = tmp
} else if (validateKeypress(data, value)) {
value = tmp;
value = tmp
if (caretOffset === 0) {
const completion = await autoComplete(value);
const completion = await autoComplete(value)
if (completion) {
suggestion = chalk.gray(completion);
suggestion += ansiEscapes.cursorBackward(completion.length);
suggestion = chalk.gray(completion)
suggestion += ansiEscapes.cursorBackward(completion.length)
} else {
suggestion = '';
suggestion = ''
}
}
} else {
return stdout.write(ansiEscapes.beep);
return stdout.write(ansiEscapes.beep)
}
}
if (mask === 'cc' || mask === 'ccv') {
value = value.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim();
value = value.replace(regex, chalk.gray('$1'));
value = value.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim()
value = value.replace(regex, chalk.gray('$1'))
} else if (mask === 'expDate') {
value = value.replace(regex, chalk.gray('$1'));
value = value.replace(regex, chalk.gray('$1'))
}
eraseLines(1);
stdout.write(label + value + suggestion);
eraseLines(1)
stdout.write(label + value + suggestion)
if (caretOffset) {
process.stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset)));
process.stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset)))
}
}
stdin.on('data', onData);
});
};
stdin.on('data', onData)
})
}

2
lib/utils/output/chars.js

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

5
lib/utils/output/cmd.js

@ -1,7 +1,6 @@
const chalk = require('chalk');
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('`')}`;
module.exports = cmd => `${chalk.gray('`')}${chalk.cyan(cmd)}${chalk.gray('`')}`

5
lib/utils/output/code.js

@ -1,7 +1,6 @@
const chalk = require('chalk');
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('`')}`;
module.exports = cmd => `${chalk.gray('`')}${chalk.bold(cmd)}${chalk.gray('`')}`

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

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

8
lib/utils/output/error.js

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

6
lib/utils/output/info.js

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

2
lib/utils/output/logo.js

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

6
lib/utils/output/note.js

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

4
lib/utils/output/param.js

@ -1,7 +1,7 @@
const chalk = require('chalk');
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('"')}`);
chalk.bold(`${chalk.gray('"')}${chalk.bold(param)}${chalk.gray('"')}`)

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

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

10
lib/utils/output/stamp.js

@ -1,10 +1,10 @@
const ms = require('ms');
const chalk = require('chalk');
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)}]`);
};
const start = new Date()
return () => chalk.gray(`[${ms(new Date() - start)}]`)
}

6
lib/utils/output/success.js

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

4
lib/utils/output/uid.js

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

20
lib/utils/output/wait.js

@ -1,15 +1,15 @@
const ora = require('ora');
const chalk = require('chalk');
const { eraseLine } = require('ansi-escapes');
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();
const spinner = ora(chalk.gray(msg))
spinner.color = 'gray'
spinner.start()
return () => {
spinner.stop();
process.stdout.write(eraseLine);
};
};
spinner.stop()
process.stdout.write(eraseLine)
}
}

38
lib/utils/prompt-options.js

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

10
lib/utils/to-human-path.js

@ -1,9 +1,9 @@
// Native
const { resolve } = require('path');
const { homedir } = require('os');
const { resolve } = require('path')
const { homedir } = require('os')
// Cleaned-up `$HOME` (i.e.: no trailing slash)
const HOME = resolve(homedir());
const HOME = resolve(homedir())
/**
* Attempts to show the given path in
@ -12,7 +12,7 @@ const HOME = resolve(homedir());
*/
function toHumanPath(path) {
return path.replace(HOME, '~');
return path.replace(HOME, '~')
}
module.exports = toHumanPath;
module.exports = toHumanPath

14
lib/utils/url.js

@ -1,21 +1,21 @@
exports.maybeURL = id => {
// E.g, "appname-asdf"
return id.includes('-');
};
return id.includes('-')
}
exports.normalizeURL = u => {
// Normalize URL by removing slash from the end
if (u.slice(-1) === '/') {
u = u.slice(0, -1);
u = u.slice(0, -1)
}
// `url` should match the hostname of the deployment
u = u.replace(/^https:\/\//i, '');
u = u.replace(/^https:\/\//i, '')
if (!u.includes('.')) {
// `.now.sh` domain is implied if just the subdomain is given
u += '.now.sh';
u += '.now.sh'
}
return u;
};
return u
}

65
scripts/slack.js

@ -1,43 +1,43 @@
#!/usr/bin/env node
const slackup = require('slackup');
const fetch = require('node-fetch');
const slackup = require('slackup')
const fetch = require('node-fetch')
const repo = process.env.TRAVIS_REPO_SLUG;
const commit = process.env.TRAVIS_COMMIT;
const branch = process.env.TRAVIS_BRANCH;
const apiKey = process.env.SLACK_API_KEY;
const channel = process.env.SLACK_CHANNEL;
const githubToken = process.env.GITHUB_API_KEY;
const currentNodeVersion = process.env.TRAVIS_NODE_VERSION;
const regex = /^(node|7)\.*/;
const repo = process.env.TRAVIS_REPO_SLUG
const commit = process.env.TRAVIS_COMMIT
const branch = process.env.TRAVIS_BRANCH
const apiKey = process.env.SLACK_API_KEY
const channel = process.env.SLACK_CHANNEL
const githubToken = process.env.GITHUB_API_KEY
const currentNodeVersion = process.env.TRAVIS_NODE_VERSION
const regex = /^(node|7)\.*/
// Skip if not on a zeit repo
if (!/^zeit\//.test(repo)) {
console.log('not a zeit repo');
process.exit(0);
console.log('not a zeit repo')
process.exit(0)
}
if (!apiKey) {
console.log('$SLACKUP_TOKEN not found');
process.exit(0);
console.log('$SLACKUP_TOKEN not found')
process.exit(0)
}
if (!channel) {
console.log('$SLACKUP_CHANNEL not found');
process.exit(0);
console.log('$SLACKUP_CHANNEL not found')
process.exit(0)
}
if (!githubToken) {
console.log('$GITHUB_TOKEN not found');
process.exit(0);
console.log('$GITHUB_TOKEN not found')
process.exit(0)
}
const opts = {
headers: {
authorization: `token ${githubToken}`
}
};
}
fetch(`https://api.github.com/repos/${repo}/commits/${commit}`, opts)
.then(res => res.json())
@ -48,30 +48,27 @@ fetch(`https://api.github.com/repos/${repo}/commits/${commit}`, opts)
}))
.then(async res => {
if (regex.test(currentNodeVersion)) {
const message = `:package: Here are the binaries for the branch *${branch}* of *${repo}* (commit <https://github.com/${repo}/commit/${commit}|${commit.substr(0, 7)}> by <${res.authorUrl}|${res.authorName}>):`;
const message = `:package: Here are the binaries for the branch *${branch}* of *${repo}* (commit <https://github.com/${repo}/commit/${commit}|${commit.substr(0, 7)}> by <${res.authorUrl}|${res.authorName}>):`
const binaries = [
`${__dirname}/../../packed/now-macos`,
`${__dirname}/../../packed/now-linux`,
`${__dirname}/../../packed/now-win.exe`
];
]
try {
await slackup({ apiKey, channel, type: 'message', message });
await slackup({ apiKey, channel, type: 'file', filePath: binaries[0] });
await slackup({ apiKey, channel, type: 'file', filePath: binaries[1] });
await slackup({ apiKey, channel, type: 'file', filePath: binaries[2] });
await slackup({ apiKey, channel, type: 'message', message })
await slackup({ apiKey, channel, type: 'file', filePath: binaries[0] })
await slackup({ apiKey, channel, type: 'file', filePath: binaries[1] })
await slackup({ apiKey, channel, type: 'file', filePath: binaries[2] })
} catch (err) {
console.log(`Couldn't send messages/files to Slack`, err);
console.log(`Couldn't send messages/files to Slack`, err)
}
} else {
setTimeout(
async () => {
const message = `:white_check_mark: Build succeded on Node ${currentNodeVersion} (commit <https://github.com/${repo}/commit/${commit}|${commit.substr(0, 7)}> by <${res.authorUrl}|${res.authorName}>)`;
await slackup({ apiKey, channel, type: 'message', message });
},
10000
);
setTimeout(async () => {
const message = `:white_check_mark: Build succeded on Node ${currentNodeVersion} (commit <https://github.com/${repo}/commit/${commit}|${commit.substr(0, 7)}> by <${res.authorUrl}|${res.authorName}>)`
await slackup({ apiKey, channel, type: 'message', message })
}, 10000)
}
})
.catch(console.error);
.catch(console.error)

102
test/args-parsing.js

@ -1,67 +1,67 @@
const path = require('path');
const test = require('ava');
const { spawn } = require('cross-spawn');
const path = require('path')
const test = require('ava')
const { spawn } = require('cross-spawn')
const logo = require('../lib/utils/output/logo');
const logo = require('../lib/utils/output/logo')
const deployHelpMessage = `${logo} now [options] <command | path>`;
const aliasHelpMessage = `${logo} now alias <ls | set | rm> <deployment> <alias>`;
const deployHelpMessage = `${logo} now [options] <command | path>`
const aliasHelpMessage = `${logo} now alias <ls | set | rm> <deployment> <alias>`
test('"now help" prints deploy help message', async t => {
const result = await now('help');
const result = await now('help')
t.is(result.code, 0);
const stdout = result.stdout.split('\n');
t.true(stdout.length > 1);
t.true(stdout[1].includes(deployHelpMessage));
});
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now --help" prints deploy help message', async t => {
const result = await now('--help');
const result = await now('--help')
t.is(result.code, 0);
const stdout = result.stdout.split('\n');
t.true(stdout.length > 1);
t.true(stdout[1].includes(deployHelpMessage));
});
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now deploy --help" prints deploy help message', async t => {
const result = await now('deploy', '--help');
const result = await now('deploy', '--help')
t.is(result.code, 0);
const stdout = result.stdout.split('\n');
t.true(stdout.length > 1);
t.true(stdout[1].includes(deployHelpMessage));
});
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now --help deploy" prints deploy help message', async t => {
const result = await now('--help', 'deploy');
const result = await now('--help', 'deploy')
t.is(result.code, 0);
const stdout = result.stdout.split('\n');
t.true(stdout.length > 1);
t.true(stdout[1].includes(deployHelpMessage));
});
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now help alias" prints alias help message', async t => {
const result = await now('help', 'alias');
const result = await now('help', 'alias')
t.is(result.code, 0);
const stdout = result.stdout.split('\n');
t.true(stdout.length > 1);
t.true(stdout[1].includes(aliasHelpMessage));
});
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(aliasHelpMessage))
})
test('"now alias --help" is the same as "now --help alias"', async t => {
const [result1, result2] = await Promise.all([
now('alias', '--help'),
now('--help', 'alias')
]);
])
t.is(result1.code, 0);
t.is(result1.code, result2.code);
t.is(result1.stdout, result2.stdout);
});
t.is(result1.code, 0)
t.is(result1.code, result2.code)
t.is(result1.stdout, result2.stdout)
})
/**
* Run the built now binary with given arguments
@ -71,23 +71,23 @@ test('"now alias --help" is the same as "now --help alias"', async t => {
*/
function now(...args) {
return new Promise((resolve, reject) => {
const command = path.resolve(__dirname, '../build/bin/now.js');
const now = spawn(command, args);
const command = path.resolve(__dirname, '../build/bin/now.js')
const now = spawn(command, args)
let stdout = '';
let stdout = ''
now.stdout.on('data', data => {
stdout += data;
});
stdout += data
})
now.on('error', err => {
reject(err);
});
reject(err)
})
now.on('close', code => {
resolve({
code,
stdout
});
});
});
})
})
})
}

286
test/index.js

@ -1,214 +1,214 @@
// Native
const { join, resolve } = require('path');
const { join, resolve } = require('path')
// Packages
const test = require('ava');
const { asc: alpha } = require('alpha-sort');
const test = require('ava')
const { asc: alpha } = require('alpha-sort')
// Ours
const hash = require('../build/lib/hash');
const readMetadata = require('../build/lib/read-metadata');
const hash = require('../build/lib/hash')
const readMetadata = require('../build/lib/read-metadata')
const {
npm: getNpmFiles_,
docker: getDockerFiles
} = require('../build/lib/get-files');
} = require('../build/lib/get-files')
const prefix = join(__dirname, '_fixtures') + '/';
const base = path => path.replace(prefix, '');
const fixture = name => resolve(`./test/_fixtures/${name}`);
const prefix = join(__dirname, '_fixtures') + '/'
const base = path => path.replace(prefix, '')
const fixture = name => resolve(`./test/_fixtures/${name}`)
// Overload to force debugging
const getNpmFiles = async dir => {
const { pkg, nowConfig, hasNowJson } = await readMetadata(dir, {
quiet: true,
strict: false
});
return getNpmFiles_(dir, pkg, nowConfig, { hasNowJson });
};
})
return getNpmFiles_(dir, pkg, nowConfig, { hasNowJson })
}
test('`files`', async t => {
let files = await getNpmFiles(fixture('files-in-package'));
t.is(files.length, 3);
files = files.sort(alpha);
t.is(base(files[0]), 'files-in-package/build/a/b/c/d.js');
t.is(base(files[1]), 'files-in-package/build/a/e.js');
t.is(base(files[2]), 'files-in-package/package.json');
});
let files = await getNpmFiles(fixture('files-in-package'))
t.is(files.length, 3)
files = files.sort(alpha)
t.is(base(files[0]), 'files-in-package/build/a/b/c/d.js')
t.is(base(files[1]), 'files-in-package/build/a/e.js')
t.is(base(files[2]), 'files-in-package/package.json')
})
test('`files` + `.*.swp` + `.npmignore`', async t => {
let files = await getNpmFiles(fixture('files-in-package-ignore'));
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'files-in-package-ignore/build/a/b/c/d.js');
t.is(base(files[1]), 'files-in-package-ignore/build/a/e.js');
t.is(base(files[2]), 'files-in-package-ignore/package.json');
});
let files = await getNpmFiles(fixture('files-in-package-ignore'))
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'files-in-package-ignore/build/a/b/c/d.js')
t.is(base(files[1]), 'files-in-package-ignore/build/a/e.js')
t.is(base(files[2]), 'files-in-package-ignore/package.json')
})
test('`files` overrides `.gitignore`', async t => {
let files = await getNpmFiles(fixture('files-overrides-gitignore'));
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'files-overrides-gitignore/package.json');
t.is(base(files[1]), 'files-overrides-gitignore/test.js');
t.is(base(files[2]), 'files-overrides-gitignore/test.json');
});
let files = await getNpmFiles(fixture('files-overrides-gitignore'))
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'files-overrides-gitignore/package.json')
t.is(base(files[1]), 'files-overrides-gitignore/test.js')
t.is(base(files[2]), 'files-overrides-gitignore/test.json')
})
test('`now.files` overrides `.npmignore`', async t => {
let files = await getNpmFiles(fixture('now-files-overrides-npmignore'));
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'now-files-overrides-npmignore/package.json');
t.is(base(files[1]), 'now-files-overrides-npmignore/test.js');
t.is(base(files[2]), 'now-files-overrides-npmignore/test.json');
});
let files = await getNpmFiles(fixture('now-files-overrides-npmignore'))
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'now-files-overrides-npmignore/package.json')
t.is(base(files[1]), 'now-files-overrides-npmignore/test.js')
t.is(base(files[2]), 'now-files-overrides-npmignore/test.json')
})
test('simple', async t => {
let files = await getNpmFiles(fixture('simple'));
files = files.sort(alpha);
t.is(files.length, 5);
t.is(base(files[0]), 'simple/bin/test');
t.is(base(files[1]), 'simple/index.js');
t.is(base(files[2]), 'simple/lib/woot');
t.is(base(files[3]), 'simple/lib/woot.jsx');
t.is(base(files[4]), 'simple/package.json');
});
let files = await getNpmFiles(fixture('simple'))
files = files.sort(alpha)
t.is(files.length, 5)
t.is(base(files[0]), 'simple/bin/test')
t.is(base(files[1]), 'simple/index.js')
t.is(base(files[2]), 'simple/lib/woot')
t.is(base(files[3]), 'simple/lib/woot.jsx')
t.is(base(files[4]), 'simple/package.json')
})
test('simple with main', async t => {
let files = await getNpmFiles(fixture('simple-main'));
t.is(files.length, 3);
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'simple-main/build/a.js');
t.is(base(files[1]), 'simple-main/index.js');
t.is(base(files[2]), 'simple-main/package.json');
});
let files = await getNpmFiles(fixture('simple-main'))
t.is(files.length, 3)
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'simple-main/build/a.js')
t.is(base(files[1]), 'simple-main/index.js')
t.is(base(files[2]), 'simple-main/package.json')
})
test('directory main', async t => {
let files = await getNpmFiles(fixture('directory-main'));
t.is(files.length, 3);
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'directory-main/a/index.js');
t.is(base(files[1]), 'directory-main/build/a.js');
t.is(base(files[2]), 'directory-main/package.json');
});
let files = await getNpmFiles(fixture('directory-main'))
t.is(files.length, 3)
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'directory-main/a/index.js')
t.is(base(files[1]), 'directory-main/build/a.js')
t.is(base(files[2]), 'directory-main/package.json')
})
test('extensionless main', async t => {
let files = await getNpmFiles(fixture('extensionless-main'));
t.is(files.length, 3);
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'extensionless-main/build/a.js');
t.is(base(files[1]), 'extensionless-main/index.js');
t.is(base(files[2]), 'extensionless-main/package.json');
});
let files = await getNpmFiles(fixture('extensionless-main'))
t.is(files.length, 3)
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'extensionless-main/build/a.js')
t.is(base(files[1]), 'extensionless-main/index.js')
t.is(base(files[2]), 'extensionless-main/package.json')
})
test('hashes', async t => {
const files = await getNpmFiles(fixture('hashes'));
const hashes = await hash(files);
t.is(hashes.size, 3);
const files = await getNpmFiles(fixture('hashes'))
const hashes = await hash(files)
t.is(hashes.size, 3)
const many = new Set(
hashes.get('277c55a2042910b9fe706ad00859e008c1b7d172').names
);
t.is(many.size, 2);
t.is(many.has(prefix + 'hashes/dei.png'), true);
t.is(many.has(prefix + 'hashes/duplicate/dei.png'), true);
)
t.is(many.size, 2)
t.is(many.has(prefix + 'hashes/dei.png'), true)
t.is(many.has(prefix + 'hashes/duplicate/dei.png'), true)
t.is(
hashes.get('56c00d0466fc6bdd41b13dac5fc920cc30a63b45').names[0],
prefix + 'hashes/index.js'
);
)
t.is(
hashes.get('706214f42ae940a01d2aa60c5e32408f4d2127dd').names[0],
prefix + 'hashes/package.json'
);
});
)
})
test('ignore node_modules', async t => {
let files = await getNpmFiles(fixture('no-node_modules'));
files = files.sort(alpha);
t.is(files.length, 2);
t.is(base(files[0]), 'no-node_modules/index.js');
t.is(base(files[1]), 'no-node_modules/package.json');
});
let files = await getNpmFiles(fixture('no-node_modules'))
files = files.sort(alpha)
t.is(files.length, 2)
t.is(base(files[0]), 'no-node_modules/index.js')
t.is(base(files[1]), 'no-node_modules/package.json')
})
test('ignore nested `node_modules` with .npmignore **', async t => {
let files = await getNpmFiles(fixture('nested-node_modules'));
files = files.sort(alpha);
t.is(files.length, 2);
t.is(base(files[0]), 'nested-node_modules/index.js');
t.is(base(files[1]), 'nested-node_modules/package.json');
});
let files = await getNpmFiles(fixture('nested-node_modules'))
files = files.sort(alpha)
t.is(files.length, 2)
t.is(base(files[0]), 'nested-node_modules/index.js')
t.is(base(files[1]), 'nested-node_modules/package.json')
})
test('support whitelisting with .npmignore and !', async t => {
let files = await getNpmFiles(fixture('negation'));
files = files.sort(alpha);
t.is(files.length, 2);
t.is(base(files[0]), 'negation/a.js');
t.is(base(files[1]), 'negation/package.json');
});
let files = await getNpmFiles(fixture('negation'))
files = files.sort(alpha)
t.is(files.length, 2)
t.is(base(files[0]), 'negation/a.js')
t.is(base(files[1]), 'negation/package.json')
})
test('support `now.files`', async t => {
let files = await getNpmFiles(fixture('now-files'));
files = files.sort(alpha);
t.is(files.length, 2);
t.is(base(files[0]), 'now-files/b.js');
t.is(base(files[1]), 'now-files/package.json');
});
let files = await getNpmFiles(fixture('now-files'))
files = files.sort(alpha)
t.is(files.length, 2)
t.is(base(files[0]), 'now-files/b.js')
t.is(base(files[1]), 'now-files/package.json')
})
test('support docker', async t => {
let files = await getDockerFiles(fixture('dockerfile'));
files = files.sort(alpha);
t.is(files.length, 2);
t.is(base(files[0]), 'dockerfile/Dockerfile');
t.is(base(files[1]), 'dockerfile/a.js');
});
let files = await getDockerFiles(fixture('dockerfile'))
files = files.sort(alpha)
t.is(files.length, 2)
t.is(base(files[0]), 'dockerfile/Dockerfile')
t.is(base(files[1]), 'dockerfile/a.js')
})
test('prefix regression', async t => {
let files = await getNpmFiles(fixture('prefix-regression'));
files = files.sort(alpha);
t.is(files.length, 2);
t.is(base(files[0]), 'prefix-regression/package.json');
t.is(base(files[1]), 'prefix-regression/woot.js');
});
let files = await getNpmFiles(fixture('prefix-regression'))
files = files.sort(alpha)
t.is(files.length, 2)
t.is(base(files[0]), 'prefix-regression/package.json')
t.is(base(files[1]), 'prefix-regression/woot.js')
})
test('support `now.json` files with package.json', async t => {
let files = await getNpmFiles(fixture('now-json'));
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'now-json/b.js');
t.is(base(files[1]), 'now-json/now.json');
t.is(base(files[2]), 'now-json/package.json');
});
let files = await getNpmFiles(fixture('now-json'))
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'now-json/b.js')
t.is(base(files[1]), 'now-json/now.json')
t.is(base(files[2]), 'now-json/package.json')
})
test('support `now.json` files with Dockerfile', async t => {
const f = fixture('now-json-docker');
const f = fixture('now-json-docker')
const { deploymentType, nowConfig, hasNowJson } = await readMetadata(f, {
quiet: true,
strict: false
});
t.is(deploymentType, 'docker');
})
t.is(deploymentType, 'docker')
let files = await getDockerFiles(f, nowConfig, { hasNowJson });
files = files.sort(alpha);
t.is(files.length, 3);
t.is(base(files[0]), 'now-json-docker/Dockerfile');
t.is(base(files[1]), 'now-json-docker/b.js');
t.is(base(files[2]), 'now-json-docker/now.json');
});
let files = await getDockerFiles(f, nowConfig, { hasNowJson })
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'now-json-docker/Dockerfile')
t.is(base(files[1]), 'now-json-docker/b.js')
t.is(base(files[2]), 'now-json-docker/now.json')
})
test('throws when both `now.json` and `package.json:now` exist', async t => {
let e;
let e
try {
await readMetadata(fixture('now-json-throws'), {
quiet: true,
strict: false
});
})
} catch (err) {
e = err;
e = err
}
t.is(e.name, 'Error');
t.is(e.userError, true);
t.is(e.name, 'Error')
t.is(e.userError, true)
t.pass(
/please ensure there's a single source of configuration/i.test(e.message)
);
});
)
})

58
test/pack-now.js

@ -1,58 +1,58 @@
const path = require('path');
const crossSpawn = require('cross-spawn');
const test = require('ava');
const path = require('path')
const crossSpawn = require('cross-spawn')
const test = require('ava')
const logo = require('../lib/utils/output/logo');
const logo = require('../lib/utils/output/logo')
test.serial('make binary', async t => {
if (!process.env.CI) {
t.true(true);
return;
t.true(true)
return
}
const result = await spawn('npm', ['run', 'pack']);
t.is(result.code, 0);
});
const result = await spawn('npm', ['run', 'pack'])
t.is(result.code, 0)
})
const binary = {
darwin: 'now-macos',
linux: 'now-linux',
win32: 'now-win.exe'
}[process.platform];
}[process.platform]
const binaryPath = path.resolve(__dirname, '../packed/' + binary);
const deployHelpMessage = `${logo} now [options] <command | path>`;
const binaryPath = path.resolve(__dirname, '../packed/' + binary)
const deployHelpMessage = `${logo} now [options] <command | path>`
test.serial('packed "now help" prints deploy help message', async t => {
if (!process.env.CI) {
t.true(true);
return;
t.true(true)
return
}
const result = await spawn(binaryPath, ['help']);
const result = await spawn(binaryPath, ['help'])
t.is(result.code, 0);
const stdout = result.stdout.split('\n');
t.true(stdout.length > 1);
t.true(stdout[1].includes(deployHelpMessage));
});
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
function spawn(command, args) {
return new Promise((resolve, reject) => {
const child = crossSpawn.spawn(command, args);
const child = crossSpawn.spawn(command, args)
let stdout = '';
let stdout = ''
child.stdout.on('data', data => {
stdout += data;
});
stdout += data
})
child.on('error', err => {
reject(err);
});
reject(err)
})
child.on('close', code => {
resolve({
code,
stdout
});
});
});
})
})
})
}

31
test/to-host.js

@ -1,38 +1,35 @@
const test = require('ava');
const toHost = require('../build/lib/to-host');
const test = require('ava')
const toHost = require('../build/lib/to-host')
test('simple', t => {
t.is(toHost('zeit.co'), 'zeit.co');
});
t.is(toHost('zeit.co'), 'zeit.co')
})
test('leading //', t => {
t.is(
toHost('//zeit-logos-rnemgaicnc.now.sh'),
'zeit-logos-rnemgaicnc.now.sh'
);
});
t.is(toHost('//zeit-logos-rnemgaicnc.now.sh'), 'zeit-logos-rnemgaicnc.now.sh')
})
test('leading http://', t => {
t.is(
toHost('http://zeit-logos-rnemgaicnc.now.sh'),
'zeit-logos-rnemgaicnc.now.sh'
);
});
)
})
test('leading https://', t => {
t.is(
toHost('https://zeit-logos-rnemgaicnc.now.sh'),
'zeit-logos-rnemgaicnc.now.sh'
);
});
)
})
test('leading https:// and path', t => {
t.is(
toHost('https://zeit-logos-rnemgaicnc.now.sh/path'),
'zeit-logos-rnemgaicnc.now.sh'
);
});
)
})
test('simple and path', t => {
t.is(toHost('zeit.co/test'), 'zeit.co');
});
t.is(toHost('zeit.co/test'), 'zeit.co')
})

Loading…
Cancel
Save