Browse Source

Prettified everything

master
Leo Lamprecht 8 years ago
parent
commit
4119c17abe
  1. 454
      bin/now-alias.js
  2. 192
      bin/now-billing-add.js
  3. 340
      bin/now-billing.js
  4. 399
      bin/now-certs.js
  5. 690
      bin/now-deploy.js
  6. 325
      bin/now-dns.js
  7. 376
      bin/now-domains.js
  8. 193
      bin/now-list.js
  9. 156
      bin/now-open.js
  10. 214
      bin/now-remove.js
  11. 305
      bin/now-secrets.js
  12. 237
      bin/now-upgrade.js
  13. 122
      bin/now.js
  14. 56
      lib/agent.js
  15. 569
      lib/alias.js
  16. 145
      lib/build-logger.js
  17. 69
      lib/certs.js
  18. 26
      lib/cfg.js
  19. 12
      lib/copy.js
  20. 61
      lib/credit-cards.js
  21. 12
      lib/dns.js
  22. 125
      lib/domain-records.js
  23. 70
      lib/domains.js
  24. 34
      lib/error.js
  25. 18
      lib/errors.js
  26. 263
      lib/get-files.js
  27. 208
      lib/git.js
  28. 60
      lib/hash.js
  29. 2
      lib/ignored.js
  30. 4
      lib/indent.js
  31. 824
      lib/index.js
  32. 31
      lib/is-zeit-world.js
  33. 133
      lib/login.js
  34. 12
      lib/logs.js
  35. 8
      lib/pkg.js
  36. 43
      lib/plans.js
  37. 82
      lib/re-alias.js
  38. 190
      lib/read-metadata.js
  39. 80
      lib/secrets.js
  40. 4
      lib/strlen.js
  41. 30
      lib/test.js
  42. 8
      lib/to-host.js
  43. 6
      lib/ua.js
  44. 48
      lib/utils/billing/geocode.js
  45. 44
      lib/utils/check-path.js
  46. 4
      lib/utils/exit.js
  47. 106
      lib/utils/input/list.js
  48. 75
      lib/utils/input/prompt-bool.js
  49. 294
      lib/utils/input/text.js
  50. 7
      lib/utils/output/cmd.js
  51. 7
      lib/utils/output/code.js
  52. 7
      lib/utils/output/error.js
  53. 7
      lib/utils/output/info.js
  54. 2
      lib/utils/output/logo.js
  55. 7
      lib/utils/output/param.js
  56. 12
      lib/utils/output/stamp.js
  57. 6
      lib/utils/output/success.js
  58. 6
      lib/utils/output/uid.js
  59. 20
      lib/utils/output/wait.js
  60. 42
      lib/utils/prompt-options.js
  61. 10
      lib/utils/to-human-path.js

454
bin/now-alias.js

@ -1,345 +1,397 @@
#!/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 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 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'],
boolean: ['help', 'debug'],
string: ["config", "token", "rules"],
boolean: ["help", "debug"],
alias: {
help: 'h',
config: 'c',
rules: 'r',
debug: 'd',
token: 't'
help: "h",
config: "c",
rules: "r",
debug: "d",
token: "t"
}
})
});
const subcommand = argv._[0]
const subcommand = argv._[0];
// options
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now alias`)} <ls | set | rm> <deployment> <alias>
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-r ${chalk.bold.underline('RULES_FILE')}, --rules=${chalk.bold.underline('RULES_FILE')} Rules file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-r ${chalk.bold.underline("RULES_FILE")}, --rules=${chalk.bold.underline("RULES_FILE")} Rules file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Lists all your aliases:
${chalk.gray("–")} Lists all your aliases:
${chalk.cyan('$ now alias ls')}
${chalk.cyan("$ now alias ls")}
${chalk.gray('–')} Adds a new alias to ${chalk.underline('my-api.now.sh')}:
${chalk.gray("–")} Adds a new alias to ${chalk.underline("my-api.now.sh")}:
${chalk.cyan(`$ now alias set ${chalk.underline('api-ownv3nc9f8.now.sh')} ${chalk.underline('my-api.now.sh')}`)}
${chalk.cyan(`$ now alias set ${chalk.underline("api-ownv3nc9f8.now.sh")} ${chalk.underline("my-api.now.sh")}`)}
The ${chalk.dim('`.now.sh`')} suffix can be ommited:
The ${chalk.dim("`.now.sh`")} suffix can be ommited:
${chalk.cyan('$ now alias set api-ownv3nc9f8 my-api')}
${chalk.cyan("$ now alias set api-ownv3nc9f8 my-api")}
The deployment id can be used as the source:
${chalk.cyan('$ now alias set deploymentId my-alias')}
${chalk.cyan("$ now alias set deploymentId my-alias")}
Custom domains work as alias targets:
${chalk.cyan(`$ now alias set ${chalk.underline('api-ownv3nc9f8.now.sh')} ${chalk.underline('my-api.com')}`)}
${chalk.cyan(`$ now alias set ${chalk.underline("api-ownv3nc9f8.now.sh")} ${chalk.underline("my-api.com")}`)}
${chalk.dim('–')} The subcommand ${chalk.dim('`set`')} is the default and can be skipped.
${chalk.dim('–')} ${chalk.dim('`http(s)://`')} in the URLs is unneeded / ignored.
${chalk.dim("–")} The subcommand ${chalk.dim("`set`")} is the default and can be skipped.
${chalk.dim("–")} ${chalk.dim("`http(s)://`")} in the URLs is unneeded / ignored.
${chalk.gray('–')} Add and modify path based aliases for ${chalk.underline('zeit.ninja')}:
${chalk.gray("–")} Add and modify path based aliases for ${chalk.underline("zeit.ninja")}:
${chalk.cyan(`$ now alias ${chalk.underline('zeit.ninja')} -r ${chalk.underline('rules.json')}`)}
${chalk.cyan(`$ now alias ${chalk.underline("zeit.ninja")} -r ${chalk.underline("rules.json")}`)}
Export effective routing rules:
${chalk.cyan(`$ now alias ls aliasId --json > ${chalk.underline('rules.json')}`)}
${chalk.cyan(`$ now alias ls aliasId --json > ${chalk.underline("rules.json")}`)}
${chalk.cyan(`$ now alias ls zeit.ninja`)}
${chalk.gray('–')} Removing an alias:
${chalk.gray("–")} Removing an alias:
${chalk.cyan('$ now alias rm aliasId')}
${chalk.cyan("$ now alias rm aliasId")}
To get the list of alias ids, use ${chalk.dim('`now alias ls`')}.
To get the list of alias ids, use ${chalk.dim("`now alias ls`")}.
${chalk.dim('Alias:')} ln
`)
}
${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 {
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await run(token)
} catch (err) {
if (err.userError) {
error(err.message)
} else {
error(`Unknown error: ${err}\n${err.stack}`)
.then(async token => {
try {
await run(token);
} catch (err) {
if (err.userError) {
error(err.message);
} else {
error(`Unknown error: ${err}\n${err.stack}`);
}
exit(1);
}
exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
exit(1)
})
})
.catch(e => {
error(`Authentication error – ${e.message}`);
exit(1);
});
}
async function run(token) {
const alias = new NowAlias(apiUrl, token, {debug})
const args = argv._.slice(1)
const alias = new NowAlias(apiUrl, token, { debug });
const args = argv._.slice(1);
switch (subcommand) {
case 'ls':
case 'list': {
case "ls":
case "list": {
if (args.length === 1) {
const list = await alias.listAliases()
const item = list.find(e => e.uid === argv._[1] || e.alias === argv._[1])
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(header.concat(item.rules.map(rule => {
return [
'',
rule.pathname ? rule.pathname : '',
rule.method ? rule.method : '*',
rule.dest
]
})), {align: ['l', 'l', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
console.log(text)
const header = [
["", "pathname", "method", "dest"].map(s => chalk.dim(s))
];
const text = list.length === 0
? null
: table(
header.concat(
item.rules.map(rule => {
return [
"",
rule.pathname ? rule.pathname : "",
rule.method ? rule.method : "*",
rule.dest
];
})
),
{
align: ["l", "l", "l", "l"],
hsep: " ".repeat(2),
stringLength: strlen
}
);
console.log(text);
}
break
break;
} else if (args.length !== 0) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now alias ls`')}`)
return exit(1)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now alias ls`")}`
);
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 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
if (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>')
}
const time = chalk.gray(ms(current - new Date(_alias.created)) + ' ago')
return [
'',
// we default to `''` because some early aliases didn't
// have an uid associated
_alias.uid === null ? '' : _alias.uid,
_sourceUrl,
_url,
time
]
})), {align: ['l', 'r', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
const elapsed_ = ms(new Date() - start_)
console.log(`> ${aliases.length} alias${aliases.length === 1 ? '' : 'es'} found ${chalk.gray(`[${elapsed_}]`)}`)
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;
if (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>");
}
const time = chalk.gray(
ms(current - new Date(_alias.created)) + " ago"
);
return [
"",
// we default to `''` because some early aliases didn't
// have an uid associated
_alias.uid === null ? "" : _alias.uid,
_sourceUrl,
_url,
time
];
})
),
{
align: ["l", "r", "l", "l"],
hsep: " ".repeat(2),
stringLength: strlen
}
);
const elapsed_ = ms(new Date() - start_);
console.log(
`> ${aliases.length} alias${aliases.length === 1 ? "" : "es"} found ${chalk.gray(`[${elapsed_}]`)}`
);
if (text) {
console.log('\n' + text + '\n')
console.log("\n" + text + "\n");
}
break
break;
}
case 'rm':
case 'remove': {
const _target = String(args[0])
case "rm":
case "remove": {
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)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now alias rm <id>`")}`
);
return exit(1);
}
const _aliases = await alias.ls()
const _alias = findAlias(_target, _aliases)
const _aliases = await alias.ls();
const _alias = findAlias(_target, _aliases);
if (!_alias) {
const err = new Error(`Alias not found by "${_target}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`)
err.userError = true
throw err
const err = new Error(
`Alias not found by "${_target}". 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)
console.log(`${chalk.cyan('> Success!')} Alias ${chalk.bold(_alias.uid)} removed [${elapsed}]`)
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': {
case "add":
case "set": {
if (argv.rules) {
await updatePathAlias(alias, argv._[0], argv.rules)
break
await updatePathAlias(alias, argv._[0], argv.rules);
break;
}
if (args.length !== 2) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now alias set <id> <domain>`')}`)
return exit(1)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now alias set <id> <domain>`")}`
);
return exit(1);
}
await alias.set(String(args[0]), String(args[1]))
break
await alias.set(String(args[0]), String(args[1]));
break;
}
default: {
if (argv._.length === 0) {
await reAlias(token, null, help, exit, apiUrl, debug, alias)
break
await reAlias(token, null, help, exit, apiUrl, debug, alias);
break;
}
if (argv.rules) {
await updatePathAlias(alias, argv._[0], argv.rules)
await updatePathAlias(alias, argv._[0], argv.rules);
} else if (argv._.length === 2) {
await alias.set(String(argv._[0]), String(argv._[1]))
await alias.set(String(argv._[0]), String(argv._[1]));
} 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);
}
}
}
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?`
return await promptBool(msg)
[
[_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?`;
return await promptBool(msg);
}
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) {
const start = new Date()
const res = await alias.updatePathBasedroutes(String(aliasName), rules)
const elapsed = ms(new Date() - start)
const start = new Date();
const res = await alias.updatePathBasedroutes(String(aliasName), rules);
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}]`)
console.log(
`${chalk.cyan("> Success!")} ${res.ruleCount} rules configured for ${chalk.underline(res.alias)} [${elapsed}]`
);
}
}

192
bin/now-billing-add.js

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

340
bin/now-billing.js

@ -1,78 +1,80 @@
#!/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'],
boolean: ['help', 'debug'],
string: ["config", "token"],
boolean: ["help", "debug"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
help: "h",
config: "c",
debug: "d",
token: "t"
}
})
});
const subcommand = argv._[0]
const subcommand = argv._[0];
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now billing`)} <ls | add | rm | set-default>
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Lists all your credit cards:
${chalk.gray("–")} Lists all your credit cards:
${chalk.cyan('$ now billing ls')}
${chalk.cyan("$ now billing ls")}
${chalk.gray('–')} Adds a credit card (interactively):
${chalk.gray("–")} Adds a credit card (interactively):
${chalk.cyan(`$ now billing add`)}
${chalk.gray('–')} Removes a credit card:
${chalk.gray("–")} Removes a credit card:
${chalk.cyan(`$ now billing rm <id>`)}
${chalk.gray('–')} If the id is omitted, you can choose interactively
${chalk.gray("–")} If the id is omitted, you can choose interactively
${chalk.gray('–')} Selects your default credit card:
${chalk.gray("–")} Selects your default credit card:
${chalk.cyan(`$ now billing set-default <id>`)}
${chalk.gray('–')} If the id is omitted, you can choose interactively
`)
}
${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 => {
@ -80,235 +82,247 @@ 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 {
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await run(token)
} catch (err) {
if (err.userError) {
error(err.message)
} else {
error(`Unknown error: ${err.stack}`)
.then(async token => {
try {
await run(token);
} catch (err) {
if (err.userError) {
error(err.message);
} else {
error(`Unknown error: ${err.stack}`);
}
exit(1);
}
exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
exit(1)
})
})
.catch(e => {
error(`Authentication error – ${e.message}`);
exit(1);
});
}
// Builds a `choices` object that can be passesd to inquirer.prompt()
function buildInquirerChoices(cards) {
return cards.cards.map(card => {
const _default = card.id === cards.defaultCardId ? ' ' + chalk.bold('(default)') : ''
const id = `${chalk.cyan(`ID: ${card.id}`)}${_default}`
const number = `${chalk.gray('#### ').repeat(3)}${card.last4}`
const _default = card.id === cards.defaultCardId
? " " + chalk.bold("(default)")
: "";
const id = `${chalk.cyan(`ID: ${card.id}`)}${_default}`;
const number = `${chalk.gray("#### ").repeat(3)}${card.last4}`;
const str = [
id,
indent(card.name, 2),
indent(`${card.brand} ${number}`, 2)
].join('\n')
].join("\n");
return {
name: str, // Will be displayed by Inquirer
value: card.id, // Will be used to identify the answer
short: card.id // Will be displayed after the users answers
}
})
};
});
}
async function run(token) {
const start = new Date()
const creditCards = new NowCreditCards(apiUrl, token, {debug})
const args = argv._.slice(1)
const start = new Date();
const creditCards = new NowCreditCards(apiUrl, token, { debug });
const args = argv._.slice(1);
switch (subcommand) {
case 'ls':
case 'list': {
const cards = await creditCards.ls()
const text = cards.cards.map(card => {
const _default = card.id === cards.defaultCardId ? ' ' + chalk.bold('(default)') : ''
const id = `${chalk.gray('-')} ${chalk.cyan(`ID: ${card.id}`)}${_default}`
const number = `${chalk.gray('#### ').repeat(3)}${card.last4}`
let address = card.address_line1
if (card.address_line2) {
address += `, ${card.address_line2}.`
} else {
address += '.'
}
case "ls":
case "list": {
const cards = await creditCards.ls();
const text = cards.cards
.map(card => {
const _default = card.id === cards.defaultCardId
? " " + chalk.bold("(default)")
: "";
const id = `${chalk.gray("-")} ${chalk.cyan(`ID: ${card.id}`)}${_default}`;
const number = `${chalk.gray("#### ").repeat(3)}${card.last4}`;
let address = card.address_line1;
if (card.address_line2) {
address += `, ${card.address_line2}.`;
} else {
address += ".";
}
address += `\n${card.address_city}, `
address += `\n${card.address_city}, `;
if (card.address_state) {
address += `${card.address_state}, `
}
if (card.address_state) {
address += `${card.address_state}, `;
}
// Stripe is returning a two digit code for the country,
// but we want the full country name
address += `${card.address_zip}. ${card.address_country}`
// Stripe is returning a two digit code for the country,
// but we want the full country name
address += `${card.address_zip}. ${card.address_country}`;
return [
id,
indent(card.name, 2),
indent(`${card.brand} ${number}`, 2),
indent(address, 2)
].join('\n')
}).join('\n\n')
return [
id,
indent(card.name, 2),
indent(`${card.brand} ${number}`, 2),
indent(address, 2)
].join("\n");
})
.join("\n\n");
const elapsed = ms(new Date() - start)
console.log(`> ${cards.cards.length} card${cards.cards.length === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed}]`)}`)
const elapsed = ms(new Date() - start);
console.log(
`> ${cards.cards.length} card${cards.cards.length === 1 ? "" : "s"} found ${chalk.gray(`[${elapsed}]`)}`
);
if (text) {
console.log(`\n${text}\n`)
console.log(`\n${text}\n`);
}
break
break;
}
case 'set-default': {
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 cards = await creditCards.ls()
const start = new Date();
const cards = await creditCards.ls();
if (cards.cards.length === 0) {
error('You have no credit cards to choose from')
return exit(0)
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 from ${cards.cards.length} total ${chalk.gray(`[${elapsed}]`)}`
const choices = buildInquirerChoices(cards)
const elapsed = ms(new Date() - start);
const message = `Selecting a new default payment card from ${cards.cards.length} total ${chalk.gray(`[${elapsed}]`)}`;
const choices = buildInquirerChoices(cards);
cardId = await listInput({
message,
choices,
separator: true,
abort: 'end'
})
abort: "end"
});
}
// Check if the provided cardId (in case the user
// typed `now billing set-default <some-id>`) is valid
if (cardId) {
const label = `Are you sure that you to set this card as the default?`
const confirmation = await promptBool(label)
const label = `Are you sure that you to set this card as the default?`;
const confirmation = await promptBool(label);
if (!confirmation) {
console.log('Aborted')
break
console.log("Aborted");
break;
}
const start = new Date()
await creditCards.setDefault(cardId)
const card = cards.cards.find(card => card.id === cardId)
const elapsed = ms(new Date() - start)
success(`${card.brand} ending in ${card.last4} is now the default ${chalk.gray(`[${elapsed}]`)}`)
const start = new Date();
await creditCards.setDefault(cardId);
const card = cards.cards.find(card => card.id === cardId);
const elapsed = ms(new Date() - start);
success(
`${card.brand} ending in ${card.last4} is now the default ${chalk.gray(`[${elapsed}]`)}`
);
} else {
console.log('No changes made')
console.log("No changes made");
}
break
break;
}
case 'rm':
case 'remove': {
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 cards = await creditCards.ls()
const start = new Date();
const cards = await creditCards.ls();
if (cards.cards.length === 0) {
error('You have no credit cards to choose from to delete')
return exit(0)
error("You have no credit cards to choose from to delete");
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')} from ${cards.cards.length} total ${chalk.gray(`[${elapsed}]`)}`
const choices = buildInquirerChoices(cards)
const elapsed = ms(new Date() - start);
const message = `Selecting a card to ${chalk.underline("remove")} from ${cards.cards.length} total ${chalk.gray(`[${elapsed}]`)}`;
const choices = buildInquirerChoices(cards);
cardId = await listInput({
message,
choices,
separator: true,
abort: 'start'
})
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 newDefaultCard = cards.cards.find(card => card.id === cards.defaultCardId)
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`
text += `\n${newDefaultCard.brand} ending in ${newDefaultCard.last4} in now default`;
}
}
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)
case "add": {
require(resolve(__dirname, "now-billing-add.js"))(creditCards);
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();
}

399
bin/now-certs.js

@ -1,287 +1,342 @@
#!/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 chalk = require("chalk");
const table = require("text-table");
const minimist = require("minimist");
const fs = require("fs-promise");
const ms = require("ms");
// Ours
const strlen = require('../lib/strlen')
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 strlen = require("../lib/strlen");
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'],
string: ["config", "token", "crt", "key", "ca"],
boolean: ["help", "debug"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
help: "h",
config: "c",
debug: "d",
token: "t"
}
})
});
const subcommand = argv._[0]
const subcommand = argv._[0];
// options
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now certs`)} <ls | create | renew | replace | rm> <cn>
${chalk.dim('Note:')}
${chalk.dim("Note:")}
This command is intended for advanced use only, normally ${chalk.bold('now')} manages your certificates automatically.
This command is intended for advanced use only, normally ${chalk.bold("now")} manages your certificates automatically.
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
--crt ${chalk.bold.underline('FILE')} Certificate file
--key ${chalk.bold.underline('FILE')} Certificate key file
--ca ${chalk.bold.underline('FILE')} CA certificate chain file
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
--crt ${chalk.bold.underline("FILE")} Certificate file
--key ${chalk.bold.underline("FILE")} Certificate key file
--ca ${chalk.bold.underline("FILE")} CA certificate chain file
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Listing all your certificates:
${chalk.gray("–")} Listing all your certificates:
${chalk.cyan('$ now certs ls')}
${chalk.cyan("$ now certs ls")}
${chalk.gray('–')} Creating a new certificate:
${chalk.gray("–")} Creating a new certificate:
${chalk.cyan('$ now certs create domain.com')}
${chalk.cyan("$ now certs create domain.com")}
${chalk.gray('–')} Renewing an existing certificate issued with ${chalk.bold('now')}:
${chalk.gray("–")} Renewing an existing certificate issued with ${chalk.bold("now")}:
${chalk.cyan('$ now certs renew domain.com')}
${chalk.cyan("$ now certs renew domain.com")}
${chalk.gray('–')} Replacing an existing certificate with a user-supplied certificate:
${chalk.gray("–")} Replacing an existing certificate with a user-supplied certificate:
${chalk.cyan('$ now certs replace --crt domain.crt --key domain.key --ca ca_chain.crt domain.com')}
`)
}
${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 {
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await run(token)
} catch (err) {
handleError(err)
exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
exit(1)
})
.then(async token => {
try {
await run(token);
} catch (err) {
handleError(err);
exit(1);
}
})
.catch(e => {
error(`Authentication error – ${e.message}`);
exit(1);
});
}
function formatExpirationDate(date) {
const diff = date - Date.now()
return diff < 0 ? chalk.gray(ms(-diff) + ' ago') : chalk.gray('in ' + ms(diff))
const diff = date - Date.now();
return diff < 0
? chalk.gray(ms(-diff) + " ago")
: chalk.gray("in " + ms(diff));
}
async function run(token) {
const certs = new NowCerts(apiUrl, token, {debug})
const args = argv._.slice(1)
const start = Date.now()
const certs = new NowCerts(apiUrl, token, { debug });
const args = argv._.slice(1);
const start = Date.now();
if (subcommand === 'ls' || subcommand === 'list') {
if (subcommand === "ls" || subcommand === "list") {
if (args.length !== 0) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now certs ls`')}`)
return exit(1)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now certs ls`")}`
);
return exit(1);
}
const list = await certs.ls()
const elapsed = ms(new Date() - start)
const list = await certs.ls();
const elapsed = ms(new Date() - start);
console.log(`> ${list.length} certificate${list.length === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed}]`)}`)
console.log(
`> ${list.length} certificate${list.length === 1 ? "" : "s"} found ${chalk.gray(`[${elapsed}]`)}`
);
if (list.length > 0) {
const cur = Date.now()
const cur = Date.now();
list.sort((a, b) => {
return a.cn.localeCompare(b.cn)
})
const header = [['', 'id', 'cn', 'created', 'expiration', 'auto-renew'].map(s => chalk.dim(s))]
const out = table(header.concat(list.map(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))
return [
'',
cert.uid ? cert.uid : 'unknown',
cn,
time,
expiration,
cert.autoRenew ? 'yes' : 'no'
]
})), {align: ['l', 'r', 'l', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
return a.cn.localeCompare(b.cn);
});
const header = [
["", "id", "cn", "created", "expiration", "auto-renew"].map(s =>
chalk.dim(s))
];
const out = table(
header.concat(
list.map(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));
return [
"",
cert.uid ? cert.uid : "unknown",
cn,
time,
expiration,
cert.autoRenew ? "yes" : "no"
];
})
),
{
align: ["l", "r", "l", "l", "l"],
hsep: " ".repeat(2),
stringLength: strlen
}
);
if (out) {
console.log('\n' + out + '\n')
console.log("\n" + out + "\n");
}
}
} else if (subcommand === 'create') {
} else if (subcommand === "create") {
if (args.length !== 1) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now certs create <cn>`')}`)
return exit(1)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now certs create <cn>`")}`
);
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 || 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)
error(
`Missing required arguments for a custom certificate entry. Usage: ${chalk.cyan("`now certs create --crt DOMAIN.CRT --key DOMAIN.KEY [--ca CA.CRT] <id | cn>`")}`
);
return exit(1);
}
const crt = readX509File(argv.crt)
const key = readX509File(argv.key)
const ca = argv.ca ? readX509File(argv.ca) : ''
const crt = readX509File(argv.crt);
const key = readX509File(argv.key);
const ca = argv.ca ? readX509File(argv.ca) : "";
cert = await certs.put(cn, crt, key, ca)
} else { // Issue a standard certificate
cert = await certs.create(cn)
cert = await certs.put(cn, crt, key, ca);
} else {
// Issue a standard certificate
cert = await certs.create(cn);
}
if (!cert) {
// Cert is undefined and "Cert is already issued" has been printed to stdout
return exit(1)
return exit(1);
}
const elapsed = ms(new Date() - start)
console.log(`${chalk.cyan('> Success!')} Certificate entry ${chalk.bold(cn)} ${chalk.gray(`(${cert.uid})`)} created ${chalk.gray(`[${elapsed}]`)}`)
} else if (subcommand === 'renew') {
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)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now certs renew <id | cn>`")}`
);
return exit(1);
}
const cert = await getCertIdCn(certs, args[0])
const cert = await getCertIdCn(certs, args[0]);
if (!cert) {
return exit(1)
return exit(1);
}
const yes = await readConfirmation(cert, 'The following certificate will be renewed\n')
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)
console.log(`${chalk.cyan('> Success!')} Certificate ${chalk.bold(cert.cn)} ${chalk.gray(`(${cert.uid})`)} renewed ${chalk.gray(`[${elapsed}]`)}`)
} else if (subcommand === 'replace') {
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)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now certs replace --crt DOMAIN.CRT --key DOMAIN.KEY [--ca CA.CRT] <id | cn>`")}`
);
return exit(1);
}
const crt = readX509File(argv.crt)
const key = readX509File(argv.key)
const ca = argv.ca ? readX509File(argv.ca) : ''
const crt = readX509File(argv.crt);
const key = readX509File(argv.key);
const ca = argv.ca ? readX509File(argv.ca) : "";
const cert = await getCertIdCn(certs, args[0])
const cert = await getCertIdCn(certs, args[0]);
if (!cert) {
return exit(1)
return exit(1);
}
const yes = await readConfirmation(cert, 'The following certificate will be replaced permanently\n')
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)
console.log(`${chalk.cyan('> Success!')} Certificate ${chalk.bold(cert.cn)} ${chalk.gray(`(${cert.uid})`)} replaced ${chalk.gray(`[${elapsed}]`)}`)
} else if (subcommand === 'rm' || subcommand === 'remove') {
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)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now certs rm <id | cn>`")}`
);
return exit(1);
}
const cert = await getCertIdCn(certs, args[0])
const cert = await getCertIdCn(certs, args[0]);
if (!cert) {
return exit(1)
return exit(1);
}
const yes = await readConfirmation(cert, 'The following certificate will be removed permanently\n')
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)
console.log(`${chalk.cyan('> Success!')} Certificate ${chalk.bold(cert.cn)} ${chalk.gray(`(${cert.uid})`)} removed ${chalk.gray(`[${elapsed}]`)}`)
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)
error(
"Please specify a valid subcommand: ls | create | renew | replace | rm"
);
help();
exit(1);
}
return certs.close()
return certs.close();
}
process.on('uncaughtException', err => {
handleError(err)
exit(1)
})
process.on("uncaughtException", err => {
handleError(err);
exit(1);
});
function readConfirmation(cert, msg) {
return new Promise(resolve => {
const time = chalk.gray(ms(new Date() - new Date(cert.created)) + ' ago')
const tbl = table(
[[cert.uid, chalk.bold(cert.cn), time]],
{align: ['l', 'r', 'l'], hsep: ' '.repeat(6)}
)
process.stdout.write(`> ${msg}`)
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
}).resume()
})
const time = chalk.gray(ms(new Date() - new Date(cert.created)) + " ago");
const tbl = table([[cert.uid, chalk.bold(cert.cn), time]], {
align: ["l", "r", "l"],
hsep: " ".repeat(6)
});
process.stdout.write(`> ${msg}`);
process.stdout.write(" " + tbl + "\n");
process.stdout.write(
`${chalk.bold.red("> Are you sure?")} ${chalk.gray("[y/N] ")}`
);
process.stdin
.on("data", d => {
process.stdin.pause();
resolve(d.toString().trim().toLowerCase() === "y");
})
.resume();
});
}
function readX509File(file) {
return fs.readFileSync(path.resolve(file), 'utf8')
return fs.readFileSync(path.resolve(file), "utf8");
}
async function getCertIdCn(certs, idOrCn) {
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}"`)
return null
error(`No certificate found by id or cn "${idOrCn}"`);
return null;
}
return thecert
return thecert;
}

690
bin/now-deploy.js

@ -1,85 +1,81 @@
#!/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 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");
// Ours
const copy = require('../lib/copy')
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 copy = require("../lib/copy");
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 argv = minimist(process.argv.slice(2), {
string: [
'config',
'token',
'name',
'alias'
],
string: ["config", "token", "name", "alias"],
boolean: [
'help',
'version',
'debug',
'force',
'links',
'login',
'no-clipboard',
'forward-npm',
'docker',
'npm',
'static'
"help",
"version",
"debug",
"force",
"links",
"login",
"no-clipboard",
"forward-npm",
"docker",
"npm",
"static"
],
alias: {
env: 'e',
dotenv: 'E',
help: 'h',
config: 'c',
debug: 'd',
version: 'v',
force: 'f',
token: 't',
forceSync: 'F',
links: 'l',
login: 'L',
public: 'p',
'no-clipboard': 'C',
'forward-npm': 'N',
name: 'n',
alias: 'a'
env: "e",
dotenv: "E",
help: "h",
config: "c",
debug: "d",
version: "v",
force: "f",
token: "t",
forceSync: "F",
links: "l",
login: "L",
public: "p",
"no-clipboard": "C",
"forward-npm": "N",
name: "n",
alias: "a"
}
})
});
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now`)} [options] <command | path>
${chalk.dim('Commands:')}
${chalk.dim("Commands:")}
${chalk.dim('Cloud')}
${chalk.dim("Cloud")}
deploy [path] Performs a deployment ${chalk.bold('(default)')}
deploy [path] Performs a deployment ${chalk.bold("(default)")}
ls | list [app] List deployments
rm | remove [id] Remove a deployment
ln | alias [id] [url] Configures aliases for deployments
@ -90,97 +86,102 @@ const help = () => {
open Open the latest deployment for the project
help [cmd] Displays complete help for [cmd]
${chalk.dim('Administrative')}
${chalk.dim("Administrative")}
billing | cc [cmd] Manages your credit cards and billing methods
upgrade | downgrade [plan] Upgrades or downgrades your plan
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-v, --version Output the version number
-n, --name Set the name of the deployment
-c ${chalk.underline('FILE')}, --config=${chalk.underline('FILE')} Config file
-c ${chalk.underline("FILE")}, --config=${chalk.underline("FILE")} Config file
-d, --debug Debug mode [off]
-f, --force Force a new deployment even if nothing has changed
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline('TOKEN')} Login token
-t ${chalk.underline("TOKEN")}, --token=${chalk.underline("TOKEN")} Login token
-L, --login Configure login
-l, --links Copy symlinks without resolving their target
-p, --public Deployment is public (${chalk.dim('`/_src`')} is exposed) [on for oss, off for premium]
-e, --env Include an env var (e.g.: ${chalk.dim('`-e KEY=value`')}). Can appear many times.
-E ${chalk.underline('FILE')}, --dotenv=${chalk.underline('FILE')} Include env vars from .env file. Defaults to '.env'
-p, --public Deployment is public (${chalk.dim("`/_src`")} is exposed) [on for oss, off for premium]
-e, --env Include an env var (e.g.: ${chalk.dim("`-e KEY=value`")}). Can appear many times.
-E ${chalk.underline("FILE")}, --dotenv=${chalk.underline("FILE")} Include env vars from .env file. Defaults to '.env'
-C, --no-clipboard Do not attempt to copy URL to clipboard
-N, --forward-npm Forward login information to install private npm modules
${chalk.dim('Enforcable Types (when both package.json and Dockerfile exist):')}
${chalk.dim("Enforcable Types (when both package.json and Dockerfile exist):")}
--npm Node.js application
--docker Docker container
--static Static file hosting
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Deploys the current directory
${chalk.gray("–")} Deploys the current directory
${chalk.cyan('$ now')}
${chalk.cyan("$ now")}
${chalk.gray('–')} Deploys a custom path ${chalk.dim('`/usr/src/project`')}
${chalk.gray("–")} Deploys a custom path ${chalk.dim("`/usr/src/project`")}
${chalk.cyan('$ now /usr/src/project')}
${chalk.cyan("$ now /usr/src/project")}
${chalk.gray('–')} Deploys a GitHub repository
${chalk.gray("–")} Deploys a GitHub repository
${chalk.cyan('$ now user/repo#ref')}
${chalk.cyan("$ now user/repo#ref")}
${chalk.gray('–')} Deploys a GitHub, GitLab or Bitbucket repo using its URL
${chalk.gray("–")} Deploys a GitHub, GitLab or Bitbucket repo using its URL
${chalk.cyan('$ now https://gitlab.com/user/repo')}
${chalk.cyan("$ now https://gitlab.com/user/repo")}
${chalk.gray('–')} Deploys with ENV vars
${chalk.gray("–")} Deploys with ENV vars
${chalk.cyan('$ now -e NODE_ENV=production -e MYSQL_PASSWORD=@mysql-password')}
${chalk.cyan("$ now -e NODE_ENV=production -e MYSQL_PASSWORD=@mysql-password")}
${chalk.gray('–')} Displays comprehensive help for the subcommand ${chalk.dim('`list`')}
${chalk.gray("–")} Displays comprehensive help for the subcommand ${chalk.dim("`list`")}
${chalk.cyan('$ now help list')}
`)
}
${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
const autoAliases = typeof argv.alias === 'undefined' ? false : flatten([argv.alias])
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]);
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(
`${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");
}
// Create a new deployment if user changed
@ -188,307 +189,348 @@ 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;
}
const config = cfg.read()
const alwaysForwardNpm = config.forwardNpm
const config = cfg.read();
const 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) {
login(apiUrl)
.then(token => {
if (shouldLogin) {
console.log('> Logged in successfully. Token saved in ~/.now.json')
process.exit(0)
} else {
sync(token).catch(err => {
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
})
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
process.exit(1)
})
.then(token => {
if (shouldLogin) {
console.log("> Logged in successfully. Token saved in ~/.now.json");
process.exit(0);
} else {
sync(token).catch(err => {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
});
}
})
.catch(e => {
error(`Authentication error – ${e.message}`);
process.exit(1);
});
} else {
sync(argv.token || config.token).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) {
const start = Date.now()
const rawPath = argv._[0]
const start = Date.now();
const rawPath = argv._[0];
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)
if (isValidRepo && isValidRepo !== "no-valid-url") {
const gitParts = gitPathParts(rawPath);
Object.assign(gitRepo, gitParts);
const searchMessage = setTimeout(() => {
console.log(`> Didn't find directory. Searching on ${gitRepo.type}...`)
}, 500)
const searchMessage = setTimeout(
() => {
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)
} else if (isValidRepo === 'no-valid-url') {
stopDeployment(`This URL is neither a valid repository from GitHub, nor from GitLab.`)
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)}" ` : ''
stopDeployment(`There's no repository named "${chalk.bold(gitRepo.main)}" ${gitRef}on ${gitRepo.type}`)
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
await checkPath(path)
await checkPath(path);
if (!quiet) {
if (gitRepo.main) {
const gitRef = gitRepo.ref ? ` at "${chalk.bold(gitRepo.ref)}" ` : ''
console.log(`> Deploying ${gitRepo.type} repository "${chalk.bold(gitRepo.main)}"` + gitRef)
const gitRef = gitRepo.ref ? ` at "${chalk.bold(gitRepo.ref)}" ` : "";
console.log(
`> Deploying ${gitRepo.type} repository "${chalk.bold(gitRepo.main)}"` +
gitRef
);
} else {
console.log(`> Deploying ${chalk.bold(toHumanPath(path))}`)
console.log(`> Deploying ${chalk.bold(toHumanPath(path))}`);
}
}
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([
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`)
console.log(
`> Two manifests found. Press [${chalk.bold("n")}] to deploy or re-run with --flag`
);
deploymentType = await promptOptions([
['npm', `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} `],
['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `]
])
[
"npm",
`${chalk.bold("package.json")}\t${chalk.gray(" --npm")} `
],
[
"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.')
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`')
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`')
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')
console.log(
"> [debug] No manifest files found, assuming static deployment"
);
}
isStatic = true
isStatic = true;
}
}
const {nowConfig} = await readMetaData(path, {
const { nowConfig } = await readMetaData(path, {
deploymentType,
deploymentName,
isStatic,
quiet: true
})
});
const now = new Now(apiUrl, token, {debug})
const now = new Now(apiUrl, token, { debug });
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'
const dotenvFileName = typeof dotenvOption === "string"
? dotenvOption
: ".env";
if (!fs.existsSync(dotenvFileName)) {
error(`--dotenv flag is set but ${dotenvFileName} file is missing`)
return process.exit(1)
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
})
}
const env_ = await Promise.all(envs.map(async kv => {
if (typeof kv !== 'string') {
error('Env key and value missing')
return process.exit(1)
}
const [key, ...rest] = kv.split('=')
let val
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);
}
if (rest.length > 0) {
val = rest.join('=')
}
const [key, ...rest] = kv.split("=");
let val;
if (/[^A-z0-9_]/i.test(key)) {
error(`Invalid ${chalk.dim('-e')} key ${chalk.bold(`"${chalk.bold(key)}"`)}. Only letters, digits and underscores are allowed.`)
return process.exit(1)
}
if (rest.length > 0) {
val = rest.join("=");
}
if (!key) {
error(`Invalid env option ${chalk.bold(`"${kv}"`)}`)
return process.exit(1)
}
if (/[^A-z0-9_]/i.test(key)) {
error(
`Invalid ${chalk.dim("-e")} key ${chalk.bold(`"${chalk.bold(key)}"`)}. Only letters, digits and underscores are allowed.`
);
return process.exit(1);
}
if (val === undefined) {
if ((key in process.env)) {
console.log(`> Reading ${chalk.bold(`"${chalk.bold(key)}"`)} from your env (as no value was specified)`)
// escape value if it begins with @
val = process.env[key].replace(/^@/, '\\@')
} else {
error(`No value specified for env ${chalk.bold(`"${chalk.bold(key)}"`)} and it was not found in your env.`)
return process.exit(1)
if (!key) {
error(`Invalid env option ${chalk.bold(`"${kv}"`)}`);
return process.exit(1);
}
}
if (val[0] === '@') {
const uidOrName = val.substr(1)
const secrets = await findSecret(uidOrName)
if (secrets.length === 0) {
if (uidOrName === '') {
error(`Empty reference provided for env key ${chalk.bold(`"${chalk.bold(key)}"`)}`)
if (val === undefined) {
if (key in process.env) {
console.log(
`> Reading ${chalk.bold(`"${chalk.bold(key)}"`)} from your env (as no value was specified)`
);
// escape value if it begins with @
val = process.env[key].replace(/^@/, "\\@");
} else {
error(`No secret found by uid or name ${chalk.bold(`"${uidOrName}"`)}`)
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)
} else if (secrets.length > 1) {
error(`Ambiguous secret ${chalk.bold(`"${uidOrName}"`)} (matches ${chalk.bold(secrets.length)} secrets)`)
return process.exit(1)
}
val = {uid: secrets[0].uid}
}
if (val[0] === "@") {
const uidOrName = val.substr(1);
const secrets = await findSecret(uidOrName);
if (secrets.length === 0) {
if (uidOrName === "") {
error(
`Empty reference provided for env key ${chalk.bold(`"${chalk.bold(key)}"`)}`
);
} else {
error(
`No secret found by uid or name ${chalk.bold(`"${uidOrName}"`)}`
);
}
return process.exit(1);
} else if (secrets.length > 1) {
error(
`Ambiguous secret ${chalk.bold(`"${uidOrName}"`)} (matches ${chalk.bold(secrets.length)} secrets)`
);
return process.exit(1);
}
return [
key,
typeof val === 'string' ? val.replace(/^\\@/, '@') : val
]
}))
val = { uid: secrets[0].uid };
}
const env = {}
env_
.filter(v => Boolean(v))
.forEach(([key, val]) => {
return [key, typeof val === "string" ? val.replace(/^\\@/, "@") : val];
})
);
const env = {};
env_.filter(v => Boolean(v)).forEach(([key, val]) => {
if (key in env) {
console.log(`> ${chalk.yellow('NOTE:')} Overriding duplicate env key ${chalk.bold(`"${key}"`)}`)
console.log(
`> ${chalk.yellow("NOTE:")} Overriding duplicate env key ${chalk.bold(`"${key}"`)}`
);
}
env[key] = val
})
env[key] = val;
});
try {
await now.create(path, {
@ -502,138 +544,146 @@ async function sync(token) {
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)
console.log(`${chalk.cyan('> Ready!')} ${chalk.bold(url)} (copied to clipboard) [${elapsed}]`)
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)
}
printLogs(now.host, token);
};
if (now.syncAmount) {
const bar = new Progress('> Upload [:bar] :percent :etas', {
const bar = new Progress("> Upload [:bar] :percent :etas", {
width: 20,
complete: '=',
incomplete: '',
complete: "=",
incomplete: "",
total: now.syncAmount
})
});
now.upload()
now.upload();
now.on('upload', ({names, data}) => {
const amount = data.length
now.on("upload", ({ names, data }) => {
const amount = data.length;
if (debug) {
console.log(`> [debug] Uploaded: ${names.join(' ')} (${bytes(data.length)})`)
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)
})
now.on("error", err => {
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)
printLogs(now.host, token);
}
}
function printLogs(host, token) {
// log build
const logger = new Logger(host, {debug, quiet})
const logger = new Logger(host, { debug, quiet });
logger.on('error', async err => {
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')}.`)
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 () => {
logger.on("close", async () => {
if (Array.isArray(autoAliases)) {
const aliasList = autoAliases.filter(item => item !== '')
const aliasList = autoAliases.filter(item => item !== "");
if (aliasList.length > 0) {
for (const alias of aliasList) {
await assignAlias(alias, token, host, apiUrl, debug)
await assignAlias(alias, token, host, apiUrl, debug);
}
} else {
await reAlias(token, host, help, exit, apiUrl, debug)
await reAlias(token, host, 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);
});
}

325
bin/now-dns.js

@ -1,193 +1,224 @@
#!/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'],
boolean: ['help', 'debug'],
string: ["config"],
boolean: ["help", "debug"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
help: "h",
config: "c",
debug: "d",
token: "t"
}
})
});
const subcommand = argv._[0]
const subcommand = argv._[0];
// options
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now dns ls`)} [domain]
${chalk.bold(`${logo} now dns add`)} <domain> <name> <A | AAAA | ALIAS | CNAME | TXT> <value>
${chalk.bold(`${logo} now dns add`)} <domain> <name> MX <value> <mx_priority>
${chalk.bold(`${logo} now dns add`)} <domain> <name> SRV <priority> <weight> <port> <target>
${chalk.bold(`${logo} now dns rm`)} <id>
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} config file
-d, --debug debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} login token
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} List all your DNS records
${chalk.gray("–")} List all your DNS records
${chalk.cyan('$ now dns ls')}
${chalk.cyan("$ now dns ls")}
${chalk.gray('–')} Add an A record for a subdomain
${chalk.gray("–")} Add an A record for a subdomain
${chalk.cyan('$ now dns add <YOUR DOMAIN> <SUBDOMAIN NAME> A <RECORD VALUE>')}
${chalk.cyan('$ now dns add zeit.rocks api A 198.51.100.100')}
${chalk.cyan("$ now dns add <YOUR DOMAIN> <SUBDOMAIN NAME> A <RECORD VALUE>")}
${chalk.cyan("$ now dns add zeit.rocks api A 198.51.100.100")}
${chalk.gray('–')} Add an MX record (@ as a name refers to the domain)
${chalk.gray("–")} Add an MX record (@ as a name refers to the domain)
${chalk.cyan('$ now dns add <YOUR DOMAIN> @ MX <RECORD VALUE> <PRIORITY>')}
${chalk.cyan('$ now dns add zeit.rocks @ MX mail.zeit.rocks 10')}
`)
}
${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 {
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await run(token)
} catch (err) {
handleError(err)
exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
exit(1)
})
.then(async token => {
try {
await run(token);
} catch (err) {
handleError(err);
exit(1);
}
})
.catch(e => {
error(`Authentication error – ${e.message}`);
exit(1);
});
}
async function run(token) {
const domainRecords = new DomainRecords(apiUrl, token, {debug})
const args = argv._.slice(1)
const start = Date.now()
const domainRecords = new DomainRecords(apiUrl, token, { debug });
const args = argv._.slice(1);
const start = Date.now();
if (subcommand === 'ls' || subcommand === 'list') {
if (subcommand === "ls" || subcommand === "list") {
if (args.length > 1) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now dns ls [domain]`')}`)
return exit(1)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now dns ls [domain]`")}`
);
return exit(1);
}
const elapsed = ms(new Date() - start)
const res = await domainRecords.ls(args[0])
const text = []
let count = 0
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 header = [['', 'id', 'name', 'type', 'value', 'aux', 'created'].map(s => chalk.dim(s))]
const out = table(header.concat(records.map(record => {
const time = chalk.gray(ms(cur - new Date(Number(record.created))) + ' ago')
return [
'',
record.id,
record.name,
record.type,
record.value,
record.mxPriority || record.priority || '',
time
]
})), {align: ['l', 'r', 'l', 'l', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
text.push(`\n\n${chalk.bold(domain)}\n${indent(out, 2)}`)
const cur = Date.now();
const header = [
["", "id", "name", "type", "value", "aux", "created"].map(s =>
chalk.dim(s))
];
const out = table(
header.concat(
records.map(record => {
const time = chalk.gray(
ms(cur - new Date(Number(record.created))) + " ago"
);
return [
"",
record.id,
record.name,
record.type,
record.value,
record.mxPriority || record.priority || "",
time
];
})
),
{
align: ["l", "r", "l", "l", "l", "l"],
hsep: " ".repeat(2),
stringLength: strlen
}
);
text.push(`\n\n${chalk.bold(domain)}\n${indent(out, 2)}`);
}
})
console.log(`> ${count} record${count === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed}]`)}`)
console.log(text.join(''))
} else if (subcommand === 'add') {
const param = parseAddArgs(args)
});
console.log(
`> ${count} record${count === 1 ? "" : "s"} found ${chalk.gray(`[${elapsed}]`)}`
);
console.log(text.join(""));
} else if (subcommand === "add") {
const param = parseAddArgs(args);
if (!param) {
error(`Invalid number of arguments. See: ${chalk.cyan('`now dns --help`')} for usage.`)
return exit(1)
error(
`Invalid number of arguments. See: ${chalk.cyan("`now dns --help`")} for usage.`
);
return exit(1);
}
const record = await domainRecords.create(param.domain, param.data)
const elapsed = ms(new Date() - start)
console.log(`${chalk.cyan('> Success!')} A new DNS record for domain ${chalk.bold(param.domain)} ${chalk.gray(`(${record.uid})`)} created ${chalk.gray(`[${elapsed}]`)}`)
} else if (subcommand === 'rm' || subcommand === 'remove') {
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}]`)}`
);
} 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)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now dns rm <id>`")}`
);
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')
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)
console.log(`${chalk.cyan('> Success!')} Record ${chalk.gray(`${record.id}`)} removed ${chalk.gray(`[${elapsed}]`)}`)
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)
})
process.on("uncaughtException", err => {
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]
const type = args[2]
const value = args[3]
const domain = args[0];
const name = args[1] === "@" ? "" : args[1];
const type = args[2];
const value = args[3];
if (!(domain && typeof name === 'string' && type)) {
return null
if (!(domain && typeof name === "string" && type)) {
return null;
}
if (type === 'MX') {
if (type === "MX") {
if (args.length !== 5) {
return null
return null;
}
return {
@ -198,10 +229,10 @@ function parseAddArgs(args) {
value,
mxPriority: args[4]
}
}
} else if (type === 'SRV') {
};
} else if (type === "SRV") {
if (args.length !== 7) {
return null
return null;
}
return {
@ -216,11 +247,11 @@ function parseAddArgs(args) {
target: args[6]
}
}
}
};
}
if (args.length !== 4) {
return null
return null;
}
return {
@ -230,27 +261,39 @@ 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 time = chalk.gray(
ms(new Date() - new Date(Number(record.created))) + " ago"
);
const tbl = table(
[[record.id,
chalk.bold(`${record.name.length > 0 ? record.name + '.' : ''}${record.domain} ${record.type} ${record.value} ${record.mxPriority ? record.mxPriority : ''}`),
time]],
{align: ['l', 'r', 'l'], hsep: ' '.repeat(6)}
)
process.stdout.write(`> ${msg}`)
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
}).resume()
})
[
[
record.id,
chalk.bold(
`${record.name.length > 0 ? record.name + "." : ""}${record.domain} ${record.type} ${record.value} ${record.mxPriority ? record.mxPriority : ""}`
),
time
]
],
{ align: ["l", "r", "l"], hsep: " ".repeat(6) }
);
process.stdout.write(`> ${msg}`);
process.stdout.write(" " + tbl + "\n");
process.stdout.write(
`${chalk.bold.red("> Are you sure?")} ${chalk.gray("[y/N] ")}`
);
process.stdin
.on("data", d => {
process.stdin.pause();
resolve(d.toString().trim().toLowerCase() === "y");
})
.resume();
});
}

376
bin/now-domains.js

@ -1,291 +1,333 @@
#!/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 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'],
boolean: ['help', 'debug', 'external', 'force'],
string: ["config", "token"],
boolean: ["help", "debug", "external", "force"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
external: 'e',
force: 'f',
token: 't'
help: "h",
config: "c",
debug: "d",
external: "e",
force: "f",
token: "t"
}
})
});
const subcommand = argv._[0]
const subcommand = argv._[0];
// options
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now domains`)} <ls | add | rm> <domain>
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-d, --debug Debug mode [off]
-e, --external Use external DNS server
-f, --force Skip DNS verification
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Lists all your domains:
${chalk.gray("–")} Lists all your domains:
${chalk.cyan('$ now domains ls')}
${chalk.cyan("$ now domains ls")}
${chalk.gray('–')} Adds a domain name:
${chalk.gray("–")} Adds a domain name:
${chalk.cyan(`$ now domains add ${chalk.underline('my-app.com')}`)}
${chalk.cyan(`$ now domains add ${chalk.underline("my-app.com")}`)}
Make sure the domain's DNS nameservers are at least 2 of these:
${chalk.gray('–')} ${chalk.underline('california.zeit.world')} ${chalk.dim('173.255.215.107')}
${chalk.gray('–')} ${chalk.underline('london.zeit.world')} ${chalk.dim('178.62.47.76')}
${chalk.gray('–')} ${chalk.underline('newark.zeit.world')} ${chalk.dim('173.255.231.87')}
${chalk.gray('–')} ${chalk.underline('amsterdam.zeit.world')} ${chalk.dim('188.226.197.55')}
${chalk.gray('–')} ${chalk.underline('dallas.zeit.world')} ${chalk.dim('173.192.101.194')}
${chalk.gray('–')} ${chalk.underline('paris.zeit.world')} ${chalk.dim('37.123.115.172')}
${chalk.gray('–')} ${chalk.underline('singapore.zeit.world')} ${chalk.dim('119.81.97.170')}
${chalk.gray('–')} ${chalk.underline('sydney.zeit.world')} ${chalk.dim('52.64.171.200')}
${chalk.gray('–')} ${chalk.underline('frankfurt.zeit.world')} ${chalk.dim('91.109.245.139')}
${chalk.gray('–')} ${chalk.underline('iowa.zeit.world')} ${chalk.dim('23.236.59.22')}
${chalk.gray("–")} ${chalk.underline("california.zeit.world")} ${chalk.dim("173.255.215.107")}
${chalk.gray("–")} ${chalk.underline("london.zeit.world")} ${chalk.dim("178.62.47.76")}
${chalk.gray("–")} ${chalk.underline("newark.zeit.world")} ${chalk.dim("173.255.231.87")}
${chalk.gray("–")} ${chalk.underline("amsterdam.zeit.world")} ${chalk.dim("188.226.197.55")}
${chalk.gray("–")} ${chalk.underline("dallas.zeit.world")} ${chalk.dim("173.192.101.194")}
${chalk.gray("–")} ${chalk.underline("paris.zeit.world")} ${chalk.dim("37.123.115.172")}
${chalk.gray("–")} ${chalk.underline("singapore.zeit.world")} ${chalk.dim("119.81.97.170")}
${chalk.gray("–")} ${chalk.underline("sydney.zeit.world")} ${chalk.dim("52.64.171.200")}
${chalk.gray("–")} ${chalk.underline("frankfurt.zeit.world")} ${chalk.dim("91.109.245.139")}
${chalk.gray("–")} ${chalk.underline("iowa.zeit.world")} ${chalk.dim("23.236.59.22")}
${chalk.yellow('NOTE:')} running ${chalk.dim('`now alias`')} will automatically register your domain
if it's configured with these nameservers (no need to ${chalk.dim('`domain add`')}).
${chalk.yellow("NOTE:")} running ${chalk.dim("`now alias`")} will automatically register your domain
if it's configured with these nameservers (no need to ${chalk.dim("`domain add`")}).
For more details head to ${chalk.underline('https://zeit.world')}.
For more details head to ${chalk.underline("https://zeit.world")}.
${chalk.gray('–')} Removing a domain:
${chalk.gray("–")} Removing a domain:
${chalk.cyan('$ now domain rm my-app.com')}
${chalk.cyan("$ now domain rm my-app.com")}
or
${chalk.cyan('$ now domain rm domainId')}
${chalk.cyan("$ now domain rm domainId")}
To get the list of domain ids, use ${chalk.dim('`now domains ls`')}.
To get the list of domain ids, use ${chalk.dim("`now domains ls`")}.
${chalk.gray('–')} Adding and verifying a domain name using zeit.world nameservers:
${chalk.gray("–")} Adding and verifying a domain name using zeit.world nameservers:
${chalk.cyan('$ now domain add my-app.com')}
${chalk.cyan("$ now domain add my-app.com")}
The command will tell you if the domain was verified succesfully. In case the domain was not verified succesfully you should retry adding the domain after some time.
${chalk.gray('–')} Adding and verifying a domain name using an external nameserver:
${chalk.gray("–")} Adding and verifying a domain name using an external nameserver:
${chalk.cyan('$ now domain add -e my-app.com')}
${chalk.cyan("$ now domain add -e my-app.com")}
and follow the verification instructions if requested. Finally, rerun the same command after completing the verification step.
`)
}
`
);
};
// options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
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 {
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await run(token)
} catch (err) {
if (err.userError) {
error(err.message)
} else {
error(`Unknown error: ${err}\n${err.stack}`)
.then(async token => {
try {
await run(token);
} catch (err) {
if (err.userError) {
error(err.message);
} else {
error(`Unknown error: ${err}\n${err.stack}`);
}
exit(1);
}
exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
exit(1)
})
})
.catch(e => {
error(`Authentication error – ${e.message}`);
exit(1);
});
}
async function run(token) {
const domain = new NowDomains(apiUrl, token, {debug})
const args = argv._.slice(1)
const domain = new NowDomains(apiUrl, token, { debug });
const args = argv._.slice(1);
switch (subcommand) {
case 'ls':
case 'list': {
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 header = [['', 'id', 'dns', 'url', '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.underline(`https://${domain.name}`)
const time = chalk.gray(ms(current - new Date(domain.created)) + ' ago')
return [
'',
domain.uid,
ns,
url,
domain.verified,
time
]
})), {align: ['l', 'r', 'l', 'l', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
const elapsed_ = ms(new Date() - start_)
console.log(`> ${domains.length} domain${domains.length === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed_}]`)}`)
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", "url", "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.underline(`https://${domain.name}`);
const time = chalk.gray(
ms(current - new Date(domain.created)) + " ago"
);
return ["", domain.uid, ns, url, domain.verified, time];
})
),
{
align: ["l", "r", "l", "l", "l", "l"],
hsep: " ".repeat(2),
stringLength: strlen
}
);
const elapsed_ = ms(new Date() - start_);
console.log(
`> ${domains.length} domain${domains.length === 1 ? "" : "s"} found ${chalk.gray(`[${elapsed_}]`)}`
);
if (out) {
console.log('\n' + out + '\n')
console.log("\n" + out + "\n");
}
break
break;
}
case 'rm':
case 'remove': {
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
const err = new Error(
`Domain not found by "${_target}". Run ${chalk.dim("`now domains ls`")} to see your domains.`
);
err.userError = true;
throw err;
}
try {
const confirmation = (await readConfirmation(domain, _domain, _domains)).toLowerCase()
if (confirmation !== 'y' && confirmation !== 'yes') {
console.log('\n> Aborted')
process.exit(0)
const confirmation = (await readConfirmation(
domain,
_domain,
_domains
)).toLowerCase();
if (confirmation !== "y" && confirmation !== "yes") {
console.log("\n> Aborted");
process.exit(0);
}
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}]`)
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': {
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 {uid, code, created, verified} = await domain.add(name, argv.force, argv.external)
const elapsed = ms(new Date() - start)
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);
if (created) {
console.log(`${chalk.cyan('> Success!')} Domain ${chalk.bold(chalk.underline(name))} ${chalk.dim(`(${uid})`)} added [${elapsed}]`)
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}]`)
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')
console.log(
"> Verification required: Please rerun this command after some time"
);
}
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)}
)
{ 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.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())
}).resume()
})
process.stdout.write(
` ${chalk.bold.red("> Are you sure?")} ${chalk.gray("[y/N] ")}`
);
process.stdin
.on("data", d => {
process.stdin.pause();
resolve(d.toString().trim());
})
.resume();
});
}
function findDomain(val, list) {
return list.find(d => {
if (d.uid === val) {
if (debug) {
console.log(`> [debug] matched domain ${d.uid} by uid`)
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;
});
}

193
bin/now-list.js

@ -1,154 +1,163 @@
#!/usr/bin/env node
// Packages
const fs = require('fs-promise')
const minimist = require('minimist')
const chalk = require('chalk')
const table = require('text-table')
const ms = require('ms')
const fs = require("fs-promise");
const minimist = require("minimist");
const chalk = require("chalk");
const table = require("text-table");
const ms = require("ms");
// Ours
const strlen = require('../lib/strlen')
const indent = require('../lib/indent')
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 strlen = require("../lib/strlen");
const indent = require("../lib/indent");
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'],
boolean: ['help', 'debug'],
string: ["config", "token"],
boolean: ["help", "debug"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
help: "h",
config: "c",
debug: "d",
token: "t"
}
})
});
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now list`)} [app]
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} List all deployments
${chalk.gray("–")} List all deployments
${chalk.cyan('$ now ls')}
${chalk.cyan("$ now ls")}
${chalk.gray('–')} List all deployments for the app ${chalk.dim('`my-app`')}
${chalk.gray("–")} List all deployments for the app ${chalk.dim("`my-app`")}
${chalk.cyan('$ now ls my-app')}
${chalk.cyan("$ now ls my-app")}
${chalk.dim('Alias:')} ls
`)
}
${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);
}
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await list(token)
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
process.exit(1)
})
.then(async token => {
try {
await list(token);
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
}
})
.catch(e => {
error(`Authentication error – ${e.message}`);
process.exit(1);
});
async function list(token) {
const now = new Now(apiUrl, token, {debug})
const start = new Date()
const now = new Now(apiUrl, token, { debug });
const start = new Date();
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));
}
const sorted = await sort([...apps])
const current = Date.now()
const text = sorted.map(([name, deps]) => {
const t = table(deps.map(({uid, url, created}) => {
const _url = url ? chalk.underline(`https://${url}`) : 'incomplete'
const time = chalk.gray(ms(current - created) + ' ago')
return [uid, _url, time]
}), {align: ['l', 'r', 'l'], hsep: ' '.repeat(6), stringLength: strlen})
return chalk.bold(name) + '\n\n' + indent(t, 2)
}).join('\n\n')
const sorted = await sort([...apps]);
const current = Date.now();
const text = sorted
.map(([name, deps]) => {
const t = table(
deps.map(({ uid, url, created }) => {
const _url = url ? chalk.underline(`https://${url}`) : "incomplete";
const time = chalk.gray(ms(current - created) + " ago");
return [uid, _url, time];
}),
{ align: ["l", "r", "l"], hsep: " ".repeat(6), stringLength: strlen }
);
return chalk.bold(name) + "\n\n" + indent(t, 2);
})
.join("\n\n");
const elapsed = ms(new Date() - start)
console.log(`> ${deployments.length} deployment${deployments.length === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed}]`)}`)
const elapsed = ms(new Date() - start);
console.log(
`> ${deployments.length} deployment${deployments.length === 1 ? "" : "s"} found ${chalk.gray(`[${elapsed}]`)}`
);
if (text) {
console.log('\n' + text + '\n')
console.log("\n" + text + "\n");
}
}
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
.map(([name, deps]) => {
deps = deps.slice().sort((a, b) => {
return b.created - a.created;
});
return [name, deps];
})
return [name, deps]
})
.sort(([nameA, depsA], [nameB, depsB]) => {
if (pkg.name === nameA) {
return -1
}
.sort(([nameA, depsA], [nameB, depsB]) => {
if (pkg.name === nameA) {
return -1;
}
if (pkg.name === nameB) {
return 1
}
if (pkg.name === nameB) {
return 1;
}
return depsB[0].created - depsA[0].created
})
return depsB[0].created - depsA[0].created;
});
}

156
bin/now-open.js

@ -1,141 +1,143 @@
#!/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'],
boolean: ['help', 'debug'],
string: ["config", "token"],
boolean: ["help", "debug"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
help: "h",
config: "c",
debug: "d",
token: "t"
}
})
});
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now open`)}
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Open latest deployment for current project
${chalk.gray("–")} Open latest deployment for current project
${chalk.cyan('$ now open')}
${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);
}
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await open(token)
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
process.exit(1)
})
.then(async token => {
try {
await open(token);
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
}
})
.catch(e => {
error(`Authentication error – ${e.message}`);
process.exit(1);
});
async function open(token) {
const now = new Now(apiUrl, token, {debug})
const now = new Now(apiUrl, token, { debug });
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)
const [currentProjectDeployments] = await getCurrentProjectDeployments(
[...apps],
pkg
);
if (typeof currentProjectDeployments === 'undefined') {
console.log(`no deployments found for ${chalk.bold(pkg.name)}`)
process.exit(0)
if (typeof currentProjectDeployments === "undefined") {
console.log(`no deployments found for ${chalk.bold(pkg.name)}`);
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)}...`)
console.log(`Here's the URL: ${chalk.underline(url)}`)
console.log(`Opening the latest deployment for ${chalk.bold(pkg.name)}...`);
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 deps.sort((depA, depB) => {
return depB.created - depA.created;
});
}

214
bin/now-remove.js

@ -1,184 +1,202 @@
#!/usr/bin/env node
// Packages
const minimist = require('minimist')
const chalk = require('chalk')
const ms = require('ms')
const table = require('text-table')
const isURL = require('is-url')
const minimist = require("minimist");
const chalk = require("chalk");
const ms = require("ms");
const table = require("text-table");
const isURL = require("is-url");
// 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'],
boolean: ['help', 'debug', 'hard', 'yes'],
string: ["config", "token"],
boolean: ["help", "debug", "hard", "yes"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't',
yes: 'y'
help: "h",
config: "c",
debug: "d",
token: "t",
yes: "y"
}
})
});
const ids = argv._
const ids = argv._;
// options
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now remove`)} deploymentId|deploymentName [...deploymentId|deploymentName]
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
-y, --yes Skip confirmation
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Remove a deployment identified by ${chalk.dim('`deploymentId`')}:
${chalk.gray("–")} Remove a deployment identified by ${chalk.dim("`deploymentId`")}:
${chalk.cyan('$ now rm deploymentId')}
${chalk.cyan("$ now rm deploymentId")}
${chalk.gray('–')} Remove all deployments with name ${chalk.dim('`my-app`')}:
${chalk.gray("–")} Remove all deployments with name ${chalk.dim("`my-app`")}:
${chalk.cyan('$ now rm my-app')}
${chalk.cyan("$ now rm my-app")}
${chalk.gray('–')} Remove two deployments with IDs ${chalk.dim('`eyWt6zuSdeus`')} and ${chalk.dim('`uWHoA9RQ1d1o`')}:
${chalk.gray("–")} Remove two deployments with IDs ${chalk.dim("`eyWt6zuSdeus`")} and ${chalk.dim("`uWHoA9RQ1d1o`")}:
${chalk.cyan('$ now rm eyWt6zuSdeus uWHoA9RQ1d1o')}
${chalk.cyan("$ now rm eyWt6zuSdeus uWHoA9RQ1d1o")}
${chalk.dim('Alias:')} rm
`)
}
${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);
}
const config = cfg.read()
const config = cfg.read();
function readConfirmation(matches) {
return new Promise(resolve => {
process.stdout.write(`> The following deployment${matches.length === 1 ? '' : 's'} will be removed permanently:\n`)
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')
{ align: ["l", "r", "l"], hsep: " ".repeat(6) }
);
process.stdout.write(tbl + "\n");
for (const depl of matches) {
for (const alias of depl.aliases) {
process.stdout.write(
`> ${chalk.yellow('Warning!')} Deployment ${chalk.bold(depl.uid)} ` +
`is an alias for ${chalk.underline(`https://${alias.alias}`)} and will be removed.\n`
)
`> ${chalk.yellow("Warning!")} Deployment ${chalk.bold(depl.uid)} ` +
`is an alias for ${chalk.underline(`https://${alias.alias}`)} and will be removed.\n`
);
}
}
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim())
}).resume()
})
process.stdout.write(
`${chalk.bold.red("> Are you sure?")} ${chalk.gray("[y/N] ")}`
);
process.stdin
.on("data", d => {
process.stdin.pause();
resolve(d.toString().trim());
})
.resume();
});
}
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await remove(token)
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`)
process.exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
process.exit(1)
})
.then(async token => {
try {
await remove(token);
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
}
})
.catch(e => {
error(`Authentication error – ${e.message}`);
process.exit(1);
});
async function remove(token) {
const now = new Now(apiUrl, token, {debug})
const now = new Now(apiUrl, token, { debug });
const deployments = await now.list()
const deployments = await now.list();
const matches = deployments.filter(d => {
return ids.find(id => {
// Normalize URL by removing slash from the end
if (isURL(id) && id.slice(-1) === '/') {
id = id.slice(0, -1)
if (isURL(id) && id.slice(-1) === "/") {
id = id.slice(0, -1);
}
// `url` should match the hostname of the deployment
let u = id.replace(/^https:\/\//i, '')
let u = id.replace(/^https:\/\//i, "");
if (u.indexOf('.') === -1) {
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 (matches.length === 0) {
error(`Could not find any deployments matching ${ids.map(id => chalk.bold(`"${id}"`)).join(', ')}. Run ${chalk.dim(`\`now ls\``)} to list.`)
return process.exit(1)
error(
`Could not find any deployments matching ${ids
.map(id => chalk.bold(`"${id}"`))
.join(", ")}. Run ${chalk.dim(`\`now ls\``)} to list.`
);
return process.exit(1);
}
const aliases = await Promise.all(matches.map(depl => now.listAliases(depl.uid)))
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)
if (confirmation !== "y" && confirmation !== "yes") {
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}]`)
console.log(table(matches.map(depl => {
return [`Deployment ${chalk.bold(depl.uid)} removed`]
})))
const elapsed = ms(new Date() - start);
console.log(`${chalk.cyan("> Success!")} [${elapsed}]`);
console.log(
table(
matches.map(depl => {
return [`Deployment ${chalk.bold(depl.uid)} removed`];
})
)
);
} catch (err) {
handleError(err)
process.exit(1)
handleError(err);
process.exit(1);
}
now.close()
now.close();
}

305
bin/now-secrets.js

@ -1,233 +1,268 @@
#!/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'],
boolean: ['help', 'debug', 'base64'],
string: ["config", "token"],
boolean: ["help", "debug", "base64"],
alias: {
help: 'h',
config: 'c',
debug: 'd',
base64: 'b',
token: 't'
help: "h",
config: "c",
debug: "d",
base64: "b",
token: "t"
}
})
});
const subcommand = argv._[0]
const subcommand = argv._[0];
// options
const help = () => {
console.log(`
console.log(
`
${chalk.bold(`${logo} now secrets`)} <ls | add | rename | rm> <secret>
${chalk.dim('Options:')}
${chalk.dim("Options:")}
-h, --help Output usage information
-b, --base64 Treat value as base64-encoded
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-c ${chalk.bold.underline("FILE")}, --config=${chalk.bold.underline("FILE")} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-t ${chalk.bold.underline("TOKEN")}, --token=${chalk.bold.underline("TOKEN")} Login token
${chalk.dim('Examples:')}
${chalk.dim("Examples:")}
${chalk.gray('–')} Lists all your secrets:
${chalk.gray("–")} Lists all your secrets:
${chalk.cyan('$ now secrets ls')}
${chalk.cyan("$ now secrets ls")}
${chalk.gray('–')} Adds a new secret:
${chalk.gray("–")} Adds a new secret:
${chalk.cyan('$ now secrets add my-secret "my value"')}
${chalk.gray('–')} Once added, a secret's value can't be retrieved in plaintext anymore
${chalk.gray('–')} If the secret's value is more than one word, wrap it in quotes
${chalk.gray('–')} Actually, when in doubt, wrap your value in quotes
${chalk.gray("–")} Once added, a secret's value can't be retrieved in plaintext anymore
${chalk.gray("–")} If the secret's value is more than one word, wrap it in quotes
${chalk.gray("–")} Actually, when in doubt, wrap your value in quotes
${chalk.gray('–')} Exposes a secret as an env variable:
${chalk.gray("–")} Exposes a secret as an env variable:
${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold('@my-secret')}`)}
${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold("@my-secret")}`)}
Notice the ${chalk.cyan.bold('`@`')} symbol which makes the value a secret reference.
Notice the ${chalk.cyan.bold("`@`")} symbol which makes the value a secret reference.
${chalk.gray('–')} Renames a secret:
${chalk.gray("–")} Renames a secret:
${chalk.cyan(`$ now secrets rename my-secret my-renamed-secret`)}
${chalk.gray('–')} Removes a secret:
${chalk.gray("–")} Removes a secret:
${chalk.cyan(`$ now secrets rm my-secret`)}
`)
}
`
);
};
// options
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
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 {
const config = cfg.read()
const config = cfg.read();
Promise.resolve(argv.token || config.token || login(apiUrl))
.then(async token => {
try {
await run(token)
} catch (err) {
handleError(err)
exit(1)
}
})
.catch(e => {
error(`Authentication error – ${e.message}`)
exit(1)
})
.then(async token => {
try {
await run(token);
} catch (err) {
handleError(err);
exit(1);
}
})
.catch(e => {
error(`Authentication error – ${e.message}`);
exit(1);
});
}
async function run(token) {
const secrets = new NowSecrets(apiUrl, token, {debug})
const args = argv._.slice(1)
const start = Date.now()
const secrets = new NowSecrets(apiUrl, token, { debug });
const args = argv._.slice(1);
const start = Date.now();
if (subcommand === 'ls' || subcommand === 'list') {
if (subcommand === "ls" || subcommand === "list") {
if (args.length !== 0) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret ls`')}`)
return exit(1)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now secret ls`")}`
);
return exit(1);
}
const list = await secrets.ls()
const elapsed = ms(new Date() - start)
const list = await secrets.ls();
const elapsed = ms(new Date() - start);
console.log(`> ${list.length} secret${list.length === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed}]`)}`)
console.log(
`> ${list.length} secret${list.length === 1 ? "" : "s"} found ${chalk.gray(`[${elapsed}]`)}`
);
if (list.length > 0) {
const cur = Date.now()
const header = [['', 'id', 'name', 'created'].map(s => chalk.dim(s))]
const out = table(header.concat(list.map(secret => {
return [
'',
secret.uid,
chalk.bold(secret.name),
chalk.gray(ms(cur - new Date(secret.created)) + ' ago')
]
})), {align: ['l', 'r', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
const cur = Date.now();
const header = [["", "id", "name", "created"].map(s => chalk.dim(s))];
const out = table(
header.concat(
list.map(secret => {
return [
"",
secret.uid,
chalk.bold(secret.name),
chalk.gray(ms(cur - new Date(secret.created)) + " ago")
];
})
),
{
align: ["l", "r", "l", "l"],
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 (subcommand === "rm" || subcommand === "remove") {
if (args.length !== 1) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret rm <id | name>`')}`)
return exit(1)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now secret rm <id | name>`")}`
);
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)
console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.name)} ${chalk.gray(`(${secret.uid})`)} removed ${chalk.gray(`[${elapsed}]`)}`)
return secrets.close()
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();
}
if (subcommand === 'rename') {
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)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now secret rename <old-name> <new-name>`")}`
);
return exit(1);
}
const secret = await secrets.rename(args[0], args[1])
const elapsed = ms(new Date() - start)
console.log(`${chalk.cyan('> Success!')} Secret ${chalk.bold(secret.oldName)} ${chalk.gray(`(${secret.uid})`)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}`)
return secrets.close()
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();
}
if (subcommand === 'add' || subcommand === 'set') {
if (subcommand === "add" || subcommand === "set") {
if (args.length !== 2) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now secret add <name> <value>`')}`)
error(
`Invalid number of arguments. Usage: ${chalk.cyan("`now secret add <name> <value>`")}`
);
if (args.length > 2) {
const example = chalk.cyan(`$ now secret add ${args[0]}`)
console.log(`> If your secret has spaces, make sure to wrap it in quotes. Example: \n ${example} `)
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.gray(`[${elapsed}]`)}`)
return secrets.close()
console.log(
`${chalk.cyan("> Success!")} Secret ${chalk.bold(name.toLowerCase())} ${chalk.gray(`(${secret.uid})`)} added ${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)
})
process.on("uncaughtException", err => {
handleError(err);
exit(1);
});
function readConfirmation(secret) {
return new Promise(resolve => {
const time = chalk.gray(ms(new Date() - new Date(secret.created)) + ' ago')
const tbl = table(
[[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(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()
resolve(d.toString().trim().toLowerCase() === 'y')
}).resume()
})
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(
`${chalk.bold.red("> Are you sure?")} ${chalk.gray("[y/N] ")}`
);
process.stdin
.on("data", d => {
process.stdin.pause();
resolve(d.toString().trim().toLowerCase() === "y");
})
.resume();
});
}

237
bin/now-upgrade.js

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

122
bin/now.js

@ -1,102 +1,102 @@
#!/usr/bin/env node
// Native
const {resolve} = require('path')
const { resolve } = require("path");
// Packages
const nodeVersion = require('node-version')
const updateNotifier = require('update-notifier')
const chalk = require('chalk')
const nodeVersion = require("node-version");
const updateNotifier = require("update-notifier");
const chalk = require("chalk");
// Ours
const {error} = require('../lib/error')
const pkg = require('../lib/pkg')
const { error } = require("../lib/error");
const pkg = require("../lib/pkg");
// Throw an error if node version is too low
if (nodeVersion.major < 6) {
error('Now requires at least version 6 of Node. Please upgrade!')
process.exit(1)
error("Now requires at least version 6 of Node. Please upgrade!");
process.exit(1);
}
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,
'help',
'list',
'ls',
'rm',
'remove',
'alias',
'aliases',
'ln',
'domain',
'domains',
'dns',
'cert',
'certs',
'secret',
'secrets',
'cc',
'billing',
'upgrade',
'downgrade',
'open'
])
"help",
"list",
"ls",
"rm",
"remove",
"alias",
"aliases",
"ln",
"domain",
"domains",
"dns",
"cert",
"certs",
"secret",
"secrets",
"cc",
"billing",
"upgrade",
"downgrade",
"open"
]);
const aliases = new Map([
['ls', 'list'],
['rm', 'remove'],
['ln', 'alias'],
['aliases', 'alias'],
['domain', 'domains'],
['cert', 'certs'],
['secret', 'secrets'],
['cc', 'billing'],
['downgrade', 'upgrade']
])
let cmd = defaultCommand
const args = process.argv.slice(2)
const index = args.findIndex(a => commands.has(a))
["ls", "list"],
["rm", "remove"],
["ln", "alias"],
["aliases", "alias"],
["domain", "domains"],
["cert", "certs"],
["secret", "secrets"],
["cc", "billing"],
["downgrade", "upgrade"]
]);
let cmd = defaultCommand;
const 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 (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;
}
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");

56
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
@ -18,66 +18,66 @@ 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
constructor(url, { tls = true, 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 agent = this._agent = new module.Agent({
const module = this._protocol === "https:" ? https : http;
const agent = (this._agent = new module.Agent({
keepAlive: true,
keepAliveMsecs: 10000,
maxSockets: 8
}).on('error', err => this._onError(err, agent))
}).on("error", err => this._onError(err, 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)
if (body && typeof body === "object" && typeof body.pipe !== "function") {
opts.headers["Content-Type"] = "application/json";
opts.body = JSON.stringify(body);
}
if (opts.body && typeof body.pipe !== 'function') {
opts.headers['Content-Length'] = Buffer.byteLength(opts.body)
if (opts.body && typeof body.pipe !== "function") {
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();
}
}
}
};

569
lib/alias.js

@ -1,462 +1,513 @@
// Packages
const {readFileSync} = require('fs')
const publicSuffixList = require('psl')
const minimist = require('minimist')
const chalk = require('chalk')
const { readFileSync } = require("fs");
const publicSuffixList = require("psl");
const minimist = require("minimist");
const chalk = require("chalk");
// Ours
const promptBool = require('../lib/utils/input/prompt-bool')
const exit = require('./utils/exit')
const copy = require('./copy')
const toHost = require('./to-host')
const resolve4 = require('./dns')
const isZeitWorld = require('./is-zeit-world')
const {DOMAIN_VERIFICATION_ERROR} = require('./errors')
const Now = require('./')
const promptBool = require("../lib/utils/input/prompt-bool");
const exit = require("./utils/exit");
const copy = require("./copy");
const toHost = require("./to-host");
const resolve4 = require("./dns");
const isZeitWorld = require("./is-zeit-world");
const { DOMAIN_VERIFICATION_ERROR } = require("./errors");
const Now = require("./");
const argv = minimist(process.argv.slice(2), {
boolean: ['no-clipboard'],
alias: {'no-clipboard': 'C'}
})
boolean: ["no-clipboard"],
alias: { "no-clipboard": "C" }
});
const isTTY = process.stdout.isTTY
const clipboard = !argv['no-clipboard']
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/
const isTTY = process.stdout.isTTY;
const clipboard = !argv["no-clipboard"];
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/;
module.exports = class Alias extends Now {
async ls(deployment) {
if (deployment) {
const target = await this.findDeployment(deployment)
const target = await this.findDeployment(deployment);
if (!target) {
const err = new Error(`Aliases not found by "${deployment}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`)
err.userError = true
throw err
const err = new Error(
`Aliases not found by "${deployment}". Run ${chalk.dim("`now alias ls`")} to see your aliases.`
);
err.userError = true;
throw err;
}
return this.listAliases(target.uid)
return this.listAliases(target.uid);
}
return this.listAliases()
return this.listAliases();
}
async rm(_alias) {
return this.retry(async bail => {
const res = await this._fetch(`/now/aliases/${_alias.uid}`, {
method: 'DELETE'
})
method: "DELETE"
});
if (res.status === 403) {
return bail(new Error('Unauthorized'))
return bail(new Error("Unauthorized"));
}
if (res.status !== 200) {
const err = new Error('Deletion failed. Try again later.')
throw err
const err = new Error("Deletion failed. Try again later.");
throw err;
}
})
});
}
async findDeployment(deployment) {
const list = await this.list()
const list = await this.list();
let key
let val
let key;
let val;
if (/\./.test(deployment)) {
val = toHost(deployment)
key = 'url'
val = toHost(deployment);
key = "url";
} else {
val = deployment
key = 'uid'
val = deployment;
key = "uid";
}
const depl = list.find(d => {
if (d[key] === val) {
if (this._debug) {
console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`)
console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`);
}
return true
return true;
}
// match prefix
if (`${val}.now.sh` === d.url) {
if (this._debug) {
console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`)
console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`);
}
return true
return true;
}
return false
})
return false;
});
return depl
return depl;
}
async updatePathBasedroutes(alias, rules) {
alias = await this.maybeSetUpDomain(alias)
alias = await this.maybeSetUpDomain(alias);
return await this.upsertPathAlias(alias, rules)
return await this.upsertPathAlias(alias, rules);
}
async upsertPathAlias(alias, rules) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] /now/aliases #${attempt}`)
console.time(`> [debug] /now/aliases #${attempt}`);
}
const rulesData = this.readRulesFile(rules)
const ruleCount = rulesData.rules.length
const rulesData = this.readRulesFile(rules);
const ruleCount = rulesData.rules.length;
const res = await this._fetch(`/now/aliases`, {
method: 'POST',
body: {alias, rules: rulesData.rules}
})
method: "POST",
body: { alias, rules: rulesData.rules }
});
const body = await res.json()
body.ruleCount = ruleCount
const body = await res.json();
body.ruleCount = ruleCount;
if (this._debug) {
console.timeEnd(`> [debug] /now/aliases #${attempt}`)
console.timeEnd(`> [debug] /now/aliases #${attempt}`);
}
// 409 conflict is returned if it already exists
if (res.status === 409) {
return {uid: body.error.uid}
return { uid: body.error.uid };
}
if (res.status === 422) {
return body
return body;
}
// no retry on authorization problems
if (res.status === 403) {
const code = body.error.code
if (code === 'custom_domain_needs_upgrade') {
const err = new Error(`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray('`')}${chalk.cyan('now upgrade')}${chalk.gray('`')}.`)
err.userError = true
return bail(err)
const code = body.error.code;
if (code === "custom_domain_needs_upgrade") {
const err = new Error(
`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray("`")}${chalk.cyan("now upgrade")}${chalk.gray("`")}.`
);
err.userError = true;
return bail(err);
}
if (code === 'alias_in_use') {
const err = new Error(`The alias you are trying to configure (${chalk.underline(chalk.bold(alias))}) is already in use by a different account.`)
err.userError = true
return bail(err)
if (code === "alias_in_use") {
const err = new Error(
`The alias you are trying to configure (${chalk.underline(chalk.bold(alias))}) is already in use by a different account.`
);
err.userError = true;
return bail(err);
}
if (code === 'forbidden') {
const err = new Error('The domain you are trying to use as an alias is already in use by a different account.')
err.userError = true
return bail(err)
if (code === "forbidden") {
const err = new Error(
"The domain you are trying to use as an alias is already in use by a different account."
);
err.userError = true;
return bail(err);
}
return bail(new Error('Authorization error'))
return bail(new Error("Authorization error"));
}
// all other errors
if (body.error) {
const code = body.error.code
const code = body.error.code;
if (code === 'cert_missing') {
console.log(`> Provisioning certificate for ${chalk.underline(chalk.bold(alias))}`)
if (code === "cert_missing") {
console.log(
`> Provisioning certificate for ${chalk.underline(chalk.bold(alias))}`
);
try {
await this.createCert(alias)
await this.createCert(alias);
} catch (err) {
// we bail to avoid retrying the whole process
// of aliasing which would involve too many
// retries on certificate provisioning
return bail(err)
return bail(err);
}
// try again, but now having provisioned the certificate
return this.upsertPathAlias(alias, rules)
return this.upsertPathAlias(alias, rules);
}
if (code === 'cert_expired') {
console.log(`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`)
if (code === "cert_expired") {
console.log(
`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`
);
try {
await this.createCert(alias, {renew: true})
await this.createCert(alias, { renew: true });
} catch (err) {
return bail(err)
return bail(err);
}
}
return bail(new Error(body.error.message))
return bail(new Error(body.error.message));
}
// the two expected succesful cods are 200 and 304
if (res.status !== 200 && res.status !== 304) {
throw new Error('Unhandled error')
throw new Error("Unhandled error");
}
return body
})
return body;
});
}
readRulesFile(rules) {
try {
const rulesJson = readFileSync(rules, 'utf8')
return JSON.parse(rulesJson)
const rulesJson = readFileSync(rules, "utf8");
return JSON.parse(rulesJson);
} catch (err) {
console.error(`Reading rules file ${rules} failed: ${err}`)
console.error(`Reading rules file ${rules} failed: ${err}`);
}
}
async set(deployment, alias) {
const depl = await this.findDeployment(deployment)
const depl = await this.findDeployment(deployment);
if (!depl) {
const err = new Error(`Deployment not found by "${deployment}". Run ${chalk.dim('`now ls`')} to see your deployments.`)
err.userError = true
throw err
const err = new Error(
`Deployment not found by "${deployment}". Run ${chalk.dim("`now ls`")} to see your deployments.`
);
err.userError = true;
throw err;
}
const aliasDepl = (await this.listAliases()).find(e => e.alias === alias)
const aliasDepl = (await this.listAliases()).find(e => e.alias === alias);
if (aliasDepl && aliasDepl.rules) {
if (isTTY) {
try {
const msg = `> Path alias excists with ${aliasDepl.rules.length} rule${aliasDepl.rules.length > 1 ? 's' : ''}.\n` +
`> Are you sure you want to update ${alias} to be a normal alias?\n`
const msg = `> Path alias excists with ${aliasDepl.rules.length} rule${aliasDepl.rules.length > 1 ? "s" : ""}.\n` +
`> Are you sure you want to update ${alias} to be a normal alias?\n`;
const confirmation = await promptBool(msg)
const confirmation = await promptBool(msg);
if (!confirmation) {
console.log('\n> Aborted')
return exit(1)
console.log("\n> Aborted");
return exit(1);
}
} catch (err) {
console.log(err)
console.log(err);
}
} else {
console.log(`Overwriting path alias with ${aliasDepl.rules.length} rule${aliasDepl.rules.length > 1 ? 's' : ''} to be a normal alias.`)
console.log(
`Overwriting path alias with ${aliasDepl.rules.length} rule${aliasDepl.rules.length > 1 ? "s" : ""} to be a normal alias.`
);
}
}
alias = await this.maybeSetUpDomain(alias)
alias = await this.maybeSetUpDomain(alias);
const newAlias = await this.createAlias(depl, alias)
const newAlias = await this.createAlias(depl, alias);
if (!newAlias) {
throw new Error(`Unexpected error occurred while setting up alias: ${JSON.stringify(newAlias)}`)
throw new Error(
`Unexpected error occurred while setting up alias: ${JSON.stringify(newAlias)}`
);
}
const {created, uid} = newAlias
const { created, uid } = newAlias;
if (created) {
const pretty = `https://${alias}`
const output = `${chalk.cyan('> Success!')} Alias created ${chalk.dim(`(${uid})`)}:\n${chalk.bold(chalk.underline(pretty))} now points to ${chalk.bold(`https://${depl.url}`)} ${chalk.dim(`(${depl.uid})`)}`
const pretty = `https://${alias}`;
const output = `${chalk.cyan("> Success!")} Alias created ${chalk.dim(`(${uid})`)}:\n${chalk.bold(chalk.underline(pretty))} now points to ${chalk.bold(`https://${depl.url}`)} ${chalk.dim(`(${depl.uid})`)}`;
if (isTTY && clipboard) {
let append
let append;
try {
await copy(pretty)
append = '[copied to clipboard]'
await copy(pretty);
append = "[copied to clipboard]";
} catch (err) {
append = ''
append = "";
} finally {
console.log(`${output} ${append}`)
console.log(`${output} ${append}`);
}
} else {
console.log(output)
console.log(output);
}
} else {
console.log(`${chalk.cyan('> Success!')} Alias already exists ${chalk.dim(`(${uid})`)}.`)
console.log(
`${chalk.cyan("> Success!")} Alias already exists ${chalk.dim(`(${uid})`)}.`
);
}
}
createAlias(depl, alias) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`)
console.time(
`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`
);
}
const res = await this._fetch(`/now/deployments/${depl.uid}/aliases`, {
method: 'POST',
body: {alias}
})
method: "POST",
body: { alias }
});
const body = await res.json()
const body = await res.json();
if (this._debug) {
console.timeEnd(`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`)
console.timeEnd(
`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`
);
}
// 409 conflict is returned if it already exists
if (res.status === 409) {
return {uid: body.error.uid}
return { uid: body.error.uid };
}
// no retry on authorization problems
if (res.status === 403) {
const code = body.error.code
if (code === 'custom_domain_needs_upgrade') {
const err = new Error(`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray('`')}${chalk.cyan('now upgrade')}${chalk.gray('`')}.`)
err.userError = true
return bail(err)
const code = body.error.code;
if (code === "custom_domain_needs_upgrade") {
const err = new Error(
`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray("`")}${chalk.cyan("now upgrade")}${chalk.gray("`")}.`
);
err.userError = true;
return bail(err);
}
if (code === 'alias_in_use') {
const err = new Error(`The alias you are trying to configure (${chalk.underline(chalk.bold(alias))}) is already in use by a different account.`)
err.userError = true
return bail(err)
if (code === "alias_in_use") {
const err = new Error(
`The alias you are trying to configure (${chalk.underline(chalk.bold(alias))}) is already in use by a different account.`
);
err.userError = true;
return bail(err);
}
if (code === 'forbidden') {
const err = new Error('The domain you are trying to use as an alias is already in use by a different account.')
err.userError = true
return bail(err)
if (code === "forbidden") {
const err = new Error(
"The domain you are trying to use as an alias is already in use by a different account."
);
err.userError = true;
return bail(err);
}
return bail(new Error('Authorization error'))
return bail(new Error("Authorization error"));
}
// all other errors
if (body.error) {
const code = body.error.code
const code = body.error.code;
if (code === 'deployment_not_found') {
return bail(new Error('Deployment not found'))
if (code === "deployment_not_found") {
return bail(new Error("Deployment not found"));
}
if (code === 'cert_missing') {
console.log(`> Provisioning certificate for ${chalk.underline(chalk.bold(alias))}`)
if (code === "cert_missing") {
console.log(
`> Provisioning certificate for ${chalk.underline(chalk.bold(alias))}`
);
try {
await this.createCert(alias)
await this.createCert(alias);
} catch (err) {
// we bail to avoid retrying the whole process
// of aliasing which would involve too many
// retries on certificate provisioning
return bail(err)
return bail(err);
}
// try again, but now having provisioned the certificate
return this.createAlias(depl, alias)
return this.createAlias(depl, alias);
}
if (code === 'cert_expired') {
console.log(`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`)
if (code === "cert_expired") {
console.log(
`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`
);
try {
await this.createCert(alias, {renew: true})
await this.createCert(alias, { renew: true });
} catch (err) {
return bail(err)
return bail(err);
}
}
return bail(new Error(body.error.message))
return bail(new Error(body.error.message));
}
// the two expected succesful cods are 200 and 304
if (res.status !== 200 && res.status !== 304) {
throw new Error('Unhandled error')
throw new Error("Unhandled error");
}
return body
})
return body;
});
}
async setupRecord(domain, name) {
await this.setupDomain(domain)
await this.setupDomain(domain);
if (this._debug) {
console.log(`> [debug] Setting up record "${name}" for "${domain}"`)
console.log(`> [debug] Setting up record "${name}" for "${domain}"`);
}
const type = name === '' ? 'ALIAS' : 'CNAME'
const type = name === "" ? "ALIAS" : "CNAME";
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] /domains/${domain}/records #${attempt}`)
console.time(`> [debug] /domains/${domain}/records #${attempt}`);
}
const res = await this._fetch(`/domains/${domain}/records`, {
method: 'POST',
method: "POST",
body: {
type,
name: name === '' ? name : '*',
value: 'alias.zeit.co'
name: name === "" ? name : "*",
value: "alias.zeit.co"
}
})
});
if (this._debug) {
console.timeEnd(`> [debug] /domains/${domain}/records #${attempt}`)
console.timeEnd(`> [debug] /domains/${domain}/records #${attempt}`);
}
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) {
throw new Error(body.error.message)
throw new Error(body.error.message);
}
return body
})
return body;
});
}
async maybeSetUpDomain(alias) {
// make alias lowercase
alias = alias.toLowerCase()
alias = alias.toLowerCase();
// trim leading and trailing dots
// for example: `google.com.` => `google.com`
alias = alias
.replace(/^\.+/, '')
.replace(/\.+$/, '')
alias = alias.replace(/^\.+/, "").replace(/\.+$/, "");
// evaluate the alias
if (/\./.test(alias)) {
alias = toHost(alias)
alias = toHost(alias);
} else {
if (this._debug) {
console.log(`> [debug] suffixing \`.now.sh\` to alias ${alias}`)
console.log(`> [debug] suffixing \`.now.sh\` to alias ${alias}`);
}
alias = `${alias}.now.sh`
alias = `${alias}.now.sh`;
}
if (!domainRegex.test(alias)) {
const err = new Error(`Invalid alias "${alias}"`)
err.userError = true
throw err
const err = new Error(`Invalid alias "${alias}"`);
err.userError = true;
throw err;
}
if (!/\.now\.sh$/.test(alias)) {
console.log(`> ${chalk.bold(chalk.underline(alias))} is a custom domain.`)
console.log(`> Verifying the DNS settings for ${chalk.bold(chalk.underline(alias))} (see ${chalk.underline('https://zeit.world')} for help)`)
const _domain = publicSuffixList.parse(alias).domain
const _domainInfo = await this.getDomain(_domain)
const domainInfo = _domainInfo && !_domainInfo.error ? _domainInfo : undefined
const {domain, nameservers} = domainInfo ? {domain: _domain} : await this.getNameservers(alias)
const usingZeitWorld = domainInfo ? !domainInfo.isExternal : isZeitWorld(nameservers)
let skipDNSVerification = false
console.log(
`> ${chalk.bold(chalk.underline(alias))} is a custom domain.`
);
console.log(
`> Verifying the DNS settings for ${chalk.bold(chalk.underline(alias))} (see ${chalk.underline("https://zeit.world")} for help)`
);
const _domain = publicSuffixList.parse(alias).domain;
const _domainInfo = await this.getDomain(_domain);
const domainInfo = _domainInfo && !_domainInfo.error
? _domainInfo
: undefined;
const { domain, nameservers } = domainInfo
? { domain: _domain }
: await this.getNameservers(alias);
const usingZeitWorld = domainInfo
? !domainInfo.isExternal
: isZeitWorld(nameservers);
let skipDNSVerification = false;
if (this._debug) {
if (domainInfo) {
console.log(`> [debug] Found domain ${domain} with verified:${domainInfo.verified}`)
console.log(
`> [debug] Found domain ${domain} with verified:${domainInfo.verified}`
);
} else {
console.log(`> [debug] Found domain ${domain} and nameservers ${nameservers}`)
console.log(
`> [debug] Found domain ${domain} and nameservers ${nameservers}`
);
}
}
if (!usingZeitWorld && domainInfo) {
if (domainInfo.verified) {
skipDNSVerification = true
skipDNSVerification = true;
} else if (domainInfo.uid) {
const {verified, created} = await this.setupDomain(domain, {isExternal: true})
const { verified, created } = await this.setupDomain(domain, {
isExternal: true
});
if (!(created && verified)) {
const e = new Error(`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.`)
e.userError = true
throw e
const e = new Error(
`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.`
);
e.userError = true;
throw e;
}
console.log(`${chalk.cyan('> Success!')} Domain ${chalk.bold(chalk.underline(domain))} verified`)
console.log(
`${chalk.cyan("> Success!")} Domain ${chalk.bold(chalk.underline(domain))} verified`
);
}
}
try {
if (!skipDNSVerification) {
await this.verifyOwnership(alias)
await this.verifyOwnership(alias);
}
} catch (err) {
if (err.userError) {
@ -465,110 +516,134 @@ module.exports = class Alias extends Now {
// configuration (if we can!)
try {
if (usingZeitWorld) {
console.log(`> Detected ${chalk.bold(chalk.underline('zeit.world'))} nameservers! Configuring records.`)
const record = alias.substr(0, alias.length - domain.length)
console.log(
`> Detected ${chalk.bold(chalk.underline("zeit.world"))} nameservers! Configuring records.`
);
const record = alias.substr(0, alias.length - domain.length);
// lean up trailing and leading dots
const _record = record
.replace(/^\./, '')
.replace(/\.$/, '')
const _domain = domain
.replace(/^\./, '')
.replace(/\.$/, '')
if (_record === '') {
await this.setupRecord(_domain, '*')
const _record = record.replace(/^\./, "").replace(/\.$/, "");
const _domain = domain.replace(/^\./, "").replace(/\.$/, "");
if (_record === "") {
await this.setupRecord(_domain, "*");
}
await this.setupRecord(_domain, _record)
await this.setupRecord(_domain, _record);
this.recordSetup = true
console.log('> DNS Configured! Verifying propagation…')
this.recordSetup = true;
console.log("> DNS Configured! Verifying propagation…");
try {
await this.retry(() => this.verifyOwnership(alias), {retries: 10, maxTimeout: 8000})
await this.retry(() => this.verifyOwnership(alias), {
retries: 10,
maxTimeout: 8000
});
} catch (err2) {
const e = new Error('> We configured the DNS settings for your alias, but we were unable to ' +
'verify that they\'ve propagated. Please try the alias again later.')
e.userError = true
throw e
const e = new Error(
"> We configured the DNS settings for your alias, but we were unable to " +
"verify that they've propagated. Please try the alias again later."
);
e.userError = true;
throw e;
}
} else {
console.log(`> Resolved IP: ${err.ip ? `${chalk.underline(err.ip)} (unknown)` : chalk.dim('none')}`)
console.log(`> Nameservers: ${nameservers && nameservers.length ? nameservers.map(ns => chalk.underline(ns)).join(', ') : chalk.dim('none')}`)
throw err
console.log(
`> Resolved IP: ${err.ip ? `${chalk.underline(err.ip)} (unknown)` : chalk.dim("none")}`
);
console.log(
`> Nameservers: ${nameservers && nameservers.length ? nameservers
.map(ns => chalk.underline(ns))
.join(", ") : chalk.dim("none")}`
);
throw err;
}
} catch (e) {
if (e.userError) {
throw e
throw e;
}
throw err
throw err;
}
} else {
throw err
throw err;
}
}
if (!usingZeitWorld && !skipDNSVerification) {
if (this._debug) {
console.log(`> [debug] Trying to register a non-ZeitWorld domain ${domain} for the current user`)
console.log(
`> [debug] Trying to register a non-ZeitWorld domain ${domain} for the current user`
);
}
const {uid, verified, created} = await this.setupDomain(domain, {isExternal: true})
const { uid, verified, created } = await this.setupDomain(domain, {
isExternal: true
});
if (!(created && verified)) {
const e = new Error(`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.`)
e.userError = true
throw e
const e = new Error(
`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.`
);
e.userError = true;
throw e;
}
console.log(`${chalk.cyan('> Success!')} Domain ${chalk.bold(chalk.underline(domain))} ${chalk.dim(`(${uid})`)} added`)
console.log(
`${chalk.cyan("> Success!")} Domain ${chalk.bold(chalk.underline(domain))} ${chalk.dim(`(${uid})`)} added`
);
}
console.log(`> Verification ${chalk.bold('OK')}!`)
console.log(`> Verification ${chalk.bold("OK")}!`);
}
return alias
return alias;
}
verifyOwnership(domain) {
return this.retry(async bail => {
const targets = await resolve4('alias.zeit.co')
const targets = await resolve4("alias.zeit.co");
if (targets.length <= 0) {
return bail(new Error('Unable to resolve alias.zeit.co'))
return bail(new Error("Unable to resolve alias.zeit.co"));
}
let ips = []
let ips = [];
try {
ips = await resolve4(domain)
ips = await resolve4(domain);
} catch (err) {
if (err.code === 'ENODATA' || err.code === 'ESERVFAIL' || err.code === 'ENOTFOUND') {
if (
err.code === "ENODATA" ||
err.code === "ESERVFAIL" ||
err.code === "ENOTFOUND"
) {
// not errors per se, just absence of records
if (this._debug) {
console.log(`> [debug] No records found for "${domain}"`)
console.log(`> [debug] No records found for "${domain}"`);
}
const err = new Error(DOMAIN_VERIFICATION_ERROR)
err.userError = true
return bail(err)
const err = new Error(DOMAIN_VERIFICATION_ERROR);
err.userError = true;
return bail(err);
}
throw err
throw err;
}
if (ips.length <= 0) {
const err = new Error(DOMAIN_VERIFICATION_ERROR)
err.userError = true
return bail(err)
const err = new Error(DOMAIN_VERIFICATION_ERROR);
err.userError = true;
return bail(err);
}
for (const ip of ips) {
if (targets.indexOf(ip) === -1) {
const err = new Error(`The domain ${domain} has an A record ${chalk.bold(ip)} that doesn't resolve to ${chalk.bold(chalk.underline('alias.zeit.co'))}.\n> ` + DOMAIN_VERIFICATION_ERROR)
err.ip = ip
err.userError = true
return bail(err)
const err = new Error(
`The domain ${domain} has an A record ${chalk.bold(ip)} that doesn't resolve to ${chalk.bold(chalk.underline("alias.zeit.co"))}.\n> ` +
DOMAIN_VERIFICATION_ERROR
);
err.ip = ip;
err.userError = true;
return bail(err);
}
}
})
});
}
}
};

145
lib/build-logger.js

@ -1,153 +1,156 @@
// Native
const EventEmitter = require('events')
const EventEmitter = require("events");
// Packages
const ansi = require('ansi-escapes')
const io = require('socket.io-client')
const chalk = require('chalk')
const ansi = require("ansi-escapes");
const io = require("socket.io-client");
const chalk = require("chalk");
const {compare, deserialize} = require('./logs')
const { compare, deserialize } = require("./logs");
class Lines {
constructor(maxLines = 100) {
this.max = maxLines
this.buf = []
this.max = maxLines;
this.buf = [];
}
write(str) {
const {max, buf} = this
const { max, buf } = this;
if (buf.length === max) {
process.stdout.write(ansi.eraseLines(max + 1))
buf.shift()
buf.forEach(line => console.log(line))
process.stdout.write(ansi.eraseLines(max + 1));
buf.shift();
buf.forEach(line => console.log(line));
}
buf.push(str)
console.log(str)
buf.push(str);
console.log(str);
}
reset() {
this.buf = []
this.buf = [];
}
}
module.exports = class Logger extends EventEmitter {
constructor(host, {debug = false, quiet = false} = {}) {
super()
this.host = host
this.debug = debug
this.quiet = quiet
constructor(host, { debug = false, quiet = false } = {}) {
super();
this.host = host;
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('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("state", this.onState.bind(this));
this.socket.on("logs", this.onLog.bind(this));
this.socket.on("backend", this.onComplete.bind(this));
this.lines = new Lines(10)
this.lines = new Lines(10);
// log buffer
this.buf = []
this.buf = [];
}
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
for (const b of this.buf.slice(0, idx)) {
clearTimeout(b.timer)
this.printLog(b.log)
}
this.buf = this.buf.slice(idx)
}, 300)
const timer = setTimeout(
() => {
this.buf.sort((a, b) => compare(a.log, b.log));
const idx = this.buf.findIndex(b => b.log.id === log.id) + 1;
for (const b of this.buf.slice(0, idx)) {
clearTimeout(b.timer);
this.printLog(b.log);
}
this.buf = this.buf.slice(idx);
},
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}`)
this.lines.reset()
} else if (log.type === 'stderr') {
data.split('\n').forEach(v => {
if (log.type === "command") {
console.log(`${chalk.gray(">")}${data}`);
this.lines.reset();
} else if (log.type === "stderr") {
data.split("\n").forEach(v => {
if (v.length > 0) {
console.error(chalk.gray(`> ${v}`))
console.error(chalk.gray(`> ${v}`));
}
})
this.lines.reset()
} else if (log.type === 'stdout') {
data.split('\n').forEach(v => {
});
this.lines.reset();
} else if (log.type === "stdout") {
data.split("\n").forEach(v => {
if (v.length > 0) {
this.lines.write(`${chalk.gray('>')} ${v}`)
this.lines.write(`${chalk.gray(">")} ${v}`);
}
})
});
}
}
}
};

69
lib/certs.js

@ -1,102 +1,101 @@
// 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', {
method: 'PUT',
const res = await this._fetch("/now/certs", {
method: "PUT",
body: {
domains: [cn],
ca,
cert: crt,
key
}
})
});
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PUT now/certs`)
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;
});
}
}
};

26
lib/cfg.js

@ -1,23 +1,25 @@
// Native
const {homedir} = require('os')
const path = require('path')
const { homedir } = require("os");
const path = require("path");
// Packages
const fs = require('fs-promise')
const fs = require("fs-promise");
let file = process.env.NOW_JSON ? path.resolve(process.env.NOW_JSON) : path.resolve(homedir(), '.now.json')
let file = process.env.NOW_JSON
? path.resolve(process.env.NOW_JSON)
: path.resolve(homedir(), ".now.json");
function setConfigFile(nowjson) {
file = path.resolve(nowjson)
file = path.resolve(nowjson);
}
function read() {
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) {}
return existing || {}
return existing || {};
}
/**
@ -29,12 +31,12 @@ function read() {
*/
function merge(data) {
const cfg = Object.assign({}, read(), data)
fs.writeFileSync(file, JSON.stringify(cfg, null, 2))
const cfg = Object.assign({}, read(), data);
fs.writeFileSync(file, JSON.stringify(cfg, null, 2));
}
module.exports = {
setConfigFile,
read,
merge
}
};

12
lib/copy.js

@ -1,16 +1,16 @@
// Packages
const {copy: _copy} = require('copy-paste')
const { copy: _copy } = require("copy-paste");
function copy(text) {
return new Promise((resolve, reject) => {
_copy(text, err => {
if (err) {
return reject(err)
return reject(err);
}
resolve()
})
})
resolve();
});
});
}
module.exports = copy
module.exports = copy;

61
lib/credit-cards.js

@ -1,33 +1,34 @@
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('/www/user/cards')
const body = await res.json()
const res = await this._fetch("/www/user/cards");
const body = await res.json();
return body
return body;
}
async setDefault(cardId) {
await this._fetch('/www/user/cards/default', {
method: 'PUT',
body: {cardId}
})
return true
await this._fetch("/www/user/cards/default", {
method: "PUT",
body: { cardId }
});
return true;
}
async rm(cardId) {
await this._fetch(`/www/user/cards/${encodeURIComponent(cardId)}`, {method: 'DELEtE'})
return true
await this._fetch(`/www/user/cards/${encodeURIComponent(cardId)}`, {
method: "DELEtE"
});
return true;
}
/* eslint-disable camelcase */
add(card) {
return new Promise(async (resolve, reject) => {
const expDateParts = card.expDate.split(' / ')
const expDateParts = card.expDate.split(" / ");
card = {
name: card.name,
number: card.cardNumber,
@ -37,34 +38,34 @@ 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 res = await this._fetch('/www/user/cards', {
method: 'POST',
body: {stripeToken}
})
const stripeToken = (await stripe.tokens.create({ card })).id;
const res = await this._fetch("/www/user/cards", {
method: "POST",
body: { stripeToken }
});
const body = await res.json()
const body = await res.json();
if (body.card && body.card.id) {
resolve({
last4: body.card.last4
})
});
} else if (body.error && body.error.message) {
reject({message: body.error.message})
reject({ message: body.error.message });
} else {
reject('Unknown error')
reject("Unknown error");
}
} catch (err) {
reject({
message: err.message || 'Unknown error'
})
message: 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;

125
lib/domain-records.js

@ -1,130 +1,139 @@
// 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()
domains = ret.filter(x => !x.isExternal).map(x => x.name).sort((a, b) => a.localeCompare(b))
const ret = await this.listDomains();
domains = ret
.filter(x => !x.isExternal)
.map(x => x.name)
.sort((a, b) => a.localeCompare(b));
}
const records = new Map()
const records = new Map();
for (const domain of domains) {
const body = await 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))
if (res.status === 404 && body.code === "not_found") {
return bail(new Error(body.message));
} else if (res.status !== 200) {
throw new Error(`Failed to get DNS records for domain "${domain}"`)
throw new Error(`Failed to get DNS records for domain "${domain}"`);
}
return body
})
records.set(domain, body.records.sort((a, b) => a.slug.localeCompare(b.slug)))
return body;
});
records.set(
domain,
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',
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'))
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)
if (body.error.code === "not_found") {
err = new Error(`The domain "${domain}" was not found`);
err.userError = true;
return bail(err);
}
}
if (res.status !== 200) {
throw new Error(body.error ? body.error.message : 'Unknown error')
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)
if (body.error.code === "not_found") {
err = new Error(body.error.message);
err.userError = true;
return bail(err);
}
}
if (res.status !== 200) {
throw new Error(body.error ? body.error.message : 'Unkown error')
throw new Error(body.error ? body.error.message : "Unkown error");
}
return body
})
return body;
});
}
}
};

70
lib/domains.js

@ -1,77 +1,81 @@
// Packages
const chalk = require('chalk')
const chalk = require("chalk");
// Ours
const Now = require('../lib')
const isZeitWorld = require('./is-zeit-world')
const {DNS_VERIFICATION_ERROR} = require('./errors')
const Now = require("../lib");
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 await this.listDomains()
return await 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
const err = new Error(
`The supplied value ${chalk.bold(`"${domain}"`)} is not a valid domain.`
);
err.userError = true;
throw err;
}
if (skipVerification || isExternal) {
return this.setupDomain(domain, {isExternal})
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
const err2 = new Error(
`Unable to fetch nameservers for ${chalk.underline(chalk.bold(domain))}.`
);
err2.userError = true;
throw err2;
}
if (isZeitWorld(ns)) {
console.log(`> Verification ${chalk.bold('OK')}!`)
return this.setupDomain(domain)
console.log(`> Verification ${chalk.bold("OK")}!`);
return this.setupDomain(domain);
}
if (this._debug) {
console.log(`> [debug] Supplied domain "${domain}" has non-zeit nameservers`)
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;
}
}
};

34
lib/error.js

@ -1,33 +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.')
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)
if (err.retryAfter === "never") {
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('`')}`)
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
}
};

18
lib/errors.js

@ -1,17 +1,17 @@
// 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')}`
const DNS_VERIFICATION_ERROR = `Please make sure that your nameservers point to ${chalk.underline("zeit.world")}.
> Examples: (full list at ${chalk.underline("https://zeit.world")})
> ${chalk.gray("-")} ${chalk.underline("california.zeit.world")} ${chalk.dim("173.255.215.107")}
> ${chalk.gray("-")} ${chalk.underline("newark.zeit.world")} ${chalk.dim("173.255.231.87")}
> ${chalk.gray("-")} ${chalk.underline("london.zeit.world")} ${chalk.dim("178.62.47.76")}
> ${chalk.gray("-")} ${chalk.underline("singapore.zeit.world")} ${chalk.dim("119.81.97.170")}`;
const DOMAIN_VERIFICATION_ERROR = DNS_VERIFICATION_ERROR +
`\n> Alternatively, ensure it resolves to ${chalk.underline('alias.zeit.co')} via ${chalk.dim('CNAME')} / ${chalk.dim('ALIAS')}.`
`\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
}
};

263
lib/get-files.js

@ -1,15 +1,15 @@
// 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");
/**
* Returns a list of files in the given
@ -24,84 +24,95 @@ const IGNORED = require('./ignored')
* @return {Array} comprehensive list of paths to sync
*/
async function npm(path, pkg, nowConfig = null, {
limit = null,
hasNowJson = false,
debug = false
} = {}) {
const whitelist = (nowConfig && nowConfig.files) || pkg.files
async function npm(
path,
pkg,
nowConfig = null,
{
limit = null,
hasNowJson = false,
debug = false
} = {}
) {
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})))))
const search = Array.prototype.concat.apply(
[],
await Promise.all(
search_.map(file => glob(file, { cwd: path, absolute: true, dot: true }))
)
);
// always include the "main" file
if (pkg.main) {
search.push(require.resolve(resolve(path, pkg.main), 'may-exclude')) // pkg: may-exclude suppresses warnings
search.push(require.resolve(resolve(path, pkg.main), "may-exclude")); // pkg: may-exclude suppresses warnings
}
// compile list of ignored patterns and files
const npmIgnore = await maybeRead(resolve(path, '.npmignore'), null)
const gitIgnore = npmIgnore === null ?
await maybeRead(resolve(path, '.gitignore')) :
null
const npmIgnore = await maybeRead(resolve(path, ".npmignore"), null);
const gitIgnore = npmIgnore === null
? await maybeRead(resolve(path, ".gitignore"))
: null;
const filter = ignore().add(
IGNORED +
'\n' +
clearRelative(npmIgnore === null ? gitIgnore : npmIgnore)
).createFilter()
const filter = ignore()
.add(
IGNORED + "\n" + clearRelative(npmIgnore === null ? gitIgnore : npmIgnore)
)
.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 accepts = overrideIgnores ?
() => true :
file => {
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true
}
const overrideIgnores = (pkg.now && pkg.now.files) ||
(gitIgnore !== null && pkg.files);
const accepts = overrideIgnores
? () => true
: file => {
const relativePath = file.substr(prefixLength);
if (relativePath === "") {
return true;
}
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file)
}
return accepted
}
const accepted = filter(relativePath);
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file);
}
return accepted;
};
// locate files
if (debug) {
console.time(`> [debug] locating files ${path}`)
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);
}
/**
@ -112,13 +123,13 @@ async function npm(path, pkg, nowConfig = null, {
* @param {String} parent full path
*/
const asAbsolute = function (path, parent) {
if (path[0] === '/') {
return path
const asAbsolute = function(path, parent) {
if (path[0] === "/") {
return path;
}
return resolve(parent, path)
}
return resolve(parent, path);
};
/**
* Returns a list of files in the given
@ -133,81 +144,89 @@ const asAbsolute = function (path, parent) {
* @return {Array} comprehensive list of paths to sync
*/
async function docker(path, nowConfig = null, {
limit = null,
hasNowJson = false,
debug = false
} = {}) {
const whitelist = nowConfig && nowConfig.files
async function docker(
path,
nowConfig = null,
{
limit = null,
hasNowJson = false,
debug = false
} = {}
) {
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 filter = ignore().add(
IGNORED +
'\n' +
clearRelative(dockerIgnore === null ?
await maybeRead(resolve(path, '.gitignore')) :
dockerIgnore)
).createFilter()
const prefixLength = path.length + 1
const accepts = function (file) {
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true
const dockerIgnore = await maybeRead(resolve(path, ".dockerignore"), null);
const filter = ignore()
.add(
IGNORED +
"\n" +
clearRelative(
dockerIgnore === null
? await maybeRead(resolve(path, ".gitignore"))
: dockerIgnore
)
)
.createFilter();
const prefixLength = path.length + 1;
const accepts = function(file) {
const relativePath = file.substr(prefixLength);
if (relativePath === "") {
return true;
}
const accepted = filter(relativePath)
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);
}
const glob = async function (pattern, options) {
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);
}
})
})
}
});
});
};
/**
* Explodes directories into a full list of files.
@ -223,52 +242,54 @@ const glob = async function (pattern, options) {
* @return {Array} of {String}s of full paths
*/
async function explode(paths, {accepts, debug}) {
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` or `main`
// 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)
return many(all.map(subdir => asAbsolute(subdir, file)))
const all = await readdir(file);
return many(all.map(subdir => asAbsolute(subdir, file)));
} 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 = async all => {
return await Promise.all(all.map(async file => {
return await list(file)
}))
}
return flatten((await many(paths))).filter(v => v !== null)
return await Promise.all(
all.map(async file => {
return await list(file);
})
);
};
return flatten(await many(paths)).filter(v => v !== null);
}
/**
@ -277,24 +298,24 @@ async function explode(paths, {accepts, debug}) {
* @return {String} results or `''`
*/
const maybeRead = async function (path, default_ = '') {
const maybeRead = async function(path, default_ = "") {
try {
return await readFile(path, 'utf8')
return await readFile(path, "utf8");
} catch (err) {
return default_
return default_;
}
}
};
/**
* Remove leading `./` from the beginning of ignores
* because our parser doesn't like them :|
*/
const clearRelative = function (str) {
return str.replace(/(\n|^)\.\//g, '$1')
}
const clearRelative = function(str) {
return str.replace(/(\n|^)\.\//g, "$1");
};
module.exports = {
npm,
docker
}
};

208
lib/git.js

@ -1,211 +1,209 @@
// 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 cloneRepo = (parts, tmpDir) => new Promise((resolve, reject) => {
let host
switch (parts.type) {
case 'GitLab':
host = `gitlab.com`
break
case 'Bitbucket':
host = `bitbucket.org`
break
default:
host = `github.com`
}
const 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;
switch (parts.type) {
case "GitLab":
host = `gitlab.com`;
break;
case "Bitbucket":
host = `bitbucket.org`;
break;
default:
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)
}
childProcess.exec(cmd, { cwd: tmpDir.path }, (err, stdout) => {
if (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 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) {
return await renameRepoDir(pathParts, tmpDir)
return await renameRepoDir(pathParts, tmpDir);
}
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
case "GitLab": {
const ref = pathParts.ref ? `?ref=${pathParts.ref}` : "";
url = `https://gitlab.com/${pathParts.main}/repository/archive.tar` + ref;
break;
}
case 'Bitbucket':
url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref || 'default'}.zip`
break
case "Bitbucket":
url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref || "default"}.zip`;
break;
default:
url = `https://api.github.com/repos/${pathParts.main}/tarball/${pathParts.ref}`
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;
}
return await renameRepoDir(pathParts, tmpDir)
}
return await renameRepoDir(pathParts, tmpDir);
};
const capitalizePlatform = name => {
const names = {
github: 'GitHub',
gitlab: 'GitLab',
bitbucket: 'Bitbucket'
}
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)
if (pathParts[0] === "commit" || pathParts[0] === "commits") {
ref = ref.substring(0, 7);
}
}
// We're deploying master by default,
// so there's no need to indicate it explicitly
if (ref === 'master') {
ref = ''
if (ref === "master") {
ref = "";
}
return {
main,
ref,
type: capitalizePlatform(parsedURL.host.split('.')[0])
}
}
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('#')
if (main.split("/")[1].includes("#")) {
const parts = main.split("#");
ref = parts[1]
main = parts[0]
ref = parts[1];
main = parts[0];
}
return {
main,
ref,
type: capitalizePlatform('github')
}
}
type: capitalizePlatform("github")
};
};
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
}
};

60
lib/hash.js

@ -1,11 +1,11 @@
// 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.
*
* @param {Array} of {String} full paths
@ -13,28 +13,30 @@ const {readFile} = require('fs-promise')
*/
async function hashes(files, isStatic, pkg) {
const map = new Map()
await Promise.all(files.map(async name => {
const filename = path.basename(name)
let data
if (isStatic && filename === 'package.json') {
const packageString = JSON.stringify(pkg, null, 2)
data = Buffer.from(packageString)
} else {
data = await readFile(name)
}
const h = hash(data)
const entry = map.get(h)
if (entry) {
entry.names.push(name)
} else {
map.set(hash(data), {names: [name], data})
}
}))
return map
const map = new Map();
await Promise.all(
files.map(async name => {
const filename = path.basename(name);
let data;
if (isStatic && filename === "package.json") {
const packageString = JSON.stringify(pkg, null, 2);
data = Buffer.from(packageString);
} else {
data = await readFile(name);
}
const h = hash(data);
const entry = map.get(h);
if (entry) {
entry.names.push(name);
} else {
map.set(hash(data), { names: [name], data });
}
})
);
return map;
}
/**
@ -45,9 +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;

824
lib/index.js

File diff suppressed because it is too large

31
lib/is-zeit-world.js

@ -3,17 +3,17 @@
*/
const nameservers = new Set([
'california.zeit.world',
'london.zeit.world',
'newark.zeit.world',
'sydney.zeit.world',
'iowa.zeit.world',
'dallas.zeit.world',
'amsterdam.zeit.world',
'paris.zeit.world',
'frankfurt.zeit.world',
'singapore.zeit.world'
])
"california.zeit.world",
"london.zeit.world",
"newark.zeit.world",
"sydney.zeit.world",
"iowa.zeit.world",
"dallas.zeit.world",
"amsterdam.zeit.world",
"paris.zeit.world",
"frankfurt.zeit.world",
"singapore.zeit.world"
]);
/**
* Given an array of nameservers (ie: as returned
@ -21,9 +21,10 @@ const nameservers = new Set([
* zeit.world's.
*/
function isZeitWorld(ns) {
return ns.length > 1 && ns.every(host => {
return nameservers.has(host)
})
return ns.length > 1 &&
ns.every(host => {
return nameservers.has(host);
});
}
module.exports = isZeitWorld
module.exports = isZeitWorld;

133
lib/login.js

@ -1,116 +1,125 @@
// 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 info = require('./utils/output/info')
const promptBool = require('./utils/input/prompt-bool')
const pkg = require("./pkg");
const ua = require("./ua");
const cfg = require("./cfg");
const info = require("./utils/output/info");
const promptBool = require("./utils/input/prompt-bool");
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',
method: "POST",
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(data),
'User-Agent': ua
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(data),
"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 res = await fetch(
`${url}/now/registration/verify?${stringifyQuery(query)}`,
{
headers: { "User-Agent": ua }
}
);
const body = await res.json();
return body.token;
}
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
setTimeout(resolve, ms);
});
}
async function register(url, {retryEmail = false} = {}) {
let email
async function register(url, { retryEmail = false } = {}) {
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");
info(`By continuing, you declare that you agree with ${chalk.bold('https://zeit.co/terms')} and ${chalk.bold('https://zeit.co/privacy.')}`)
if (!await promptBool('Continue?')) {
info('Aborted.')
process.exit() // eslint-disable-line unicorn/no-process-exit
info(
`By continuing, you declare that you agree with ${chalk.bold("https://zeit.co/terms")} and ${chalk.bold("https://zeit.co/privacy.")}`
);
if (!await promptBool("Continue?")) {
info("Aborted.");
process.exit(); // eslint-disable-line unicorn/no-process-exit
}
if (!validate(email)) {
return register(url, {retryEmail: true})
return register(url, { retryEmail: true });
}
const {token, securityCode} = await getVerificationData(url, email)
console.log(`> Please follow the link sent to ${chalk.bold(email)} to log in.`)
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))}.`)
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...',
color: 'black'
}).start()
text: "Waiting for confirmation...",
color: "black"
}).start();
let final
let final;
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);
spinner.text = 'Confirmed email address!'
spinner.stopAndPersist('✔')
spinner.text = "Confirmed email address!";
spinner.stopAndPersist("✔");
process.stdout.write('\n')
process.stdout.write("\n");
return {email, token: final}
return { email, token: final };
}
module.exports = async function (url) {
const loginData = await register(url)
cfg.merge(loginData)
return loginData.token
}
module.exports = async function(url) {
const loginData = await register(url);
cfg.merge(loginData);
return loginData.token;
};

12
lib/logs.js

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

8
lib/pkg.js

@ -1,8 +1,8 @@
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;

43
lib/plans.js

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

82
lib/re-alias.js

@ -1,80 +1,86 @@
// 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) => {
const aliases = new NowAlias(apiUrl, token, {debug})
console.log(`> Assigning alias ${chalk.bold.underline(autoAlias)} to deployment...`)
const aliases = new NowAlias(apiUrl, token, { debug });
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, host, help, exit, apiUrl, debug, alias) => {
const path = process.cwd()
const path = process.cwd();
const configFiles = {
pkg: join(path, 'package.json'),
nowJSON: join(path, 'now.json')
}
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
error(
`Couldn't find a now.json or package.json file with an alias list in it`
);
return;
}
const {nowConfig, name} = await readMetaData(path, {
deploymentType: 'npm', // hard coding settings…
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 (nowConfig.alias) {
const value = nowConfig.alias
const value = nowConfig.alias;
if (typeof value === 'string') {
pointers.push(value)
if (typeof value === "string") {
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)
error(`Property ${chalk.grey("aliases")} is not a valid array or string`);
return exit(1);
}
}
if (nowConfig.aliases && Array.isArray(nowConfig.aliases)) {
console.log(`${chalk.red('Deprecated!')} The property ${chalk.grey('aliases')} will be ` +
`removed from the config file soon.`)
console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n')
console.log(
`${chalk.red("Deprecated!")} The property ${chalk.grey("aliases")} will be ` +
`removed from the config file soon.`
);
console.log("Read more about the new way here: http://bit.ly/2l2v5Fg\n");
pointers = pointers.concat(nowConfig.aliases)
pointers = pointers.concat(nowConfig.aliases);
}
if (pointers.length === 0) {
help()
return exit(0)
help();
return exit(0);
}
for (const pointer of pointers) {
await exports.assignAlias(pointer, token, host, apiUrl, debug)
await exports.assignAlias(pointer, token, host, apiUrl, debug);
}
}
};

190
lib/read-metadata.js

@ -1,42 +1,45 @@
const {basename, resolve: resolvePath} = require('path')
const chalk = require('chalk')
const {readFile, exists} = require('fs-promise')
const {parse: parseDockerfile} = require('docker-file-parser')
const { basename, resolve: resolvePath } = require("path");
const chalk = require("chalk");
const { readFile, exists } = require("fs-promise");
const { parse: parseDockerfile } = require("docker-file-parser");
const listPackage = {
scripts: {
start: `NODE_ENV='production' serve ./content`
},
dependencies: {
serve: '4.0.1'
serve: "4.0.1"
}
}
module.exports = readMetaData
async function readMetaData(path, {
deploymentType = 'npm',
deploymentName,
quiet = false,
strict = true,
isStatic = false
}) {
let pkg = {}
let nowConfig = null
let hasNowJson = false
};
module.exports = readMetaData;
async function readMetaData(
path,
{
deploymentType = "npm",
deploymentName,
quiet = false,
strict = true,
isStatic = 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
if (err.code !== "ENOENT") {
const e = Error(`Failed to read JSON in "${path}/now.json"`);
e.userError = true;
throw e;
}
}
@ -44,113 +47,130 @@ async function readMetaData(path, {
// 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
} else if (nowConfig.type === undefined && !await exists(resolvePath(path, 'package.json'))) {
deploymentType = 'static'
deploymentType = nowConfig.type;
} else if (
nowConfig.type === undefined &&
!await exists(resolvePath(path, "package.json"))
) {
deploymentType = "static";
}
if (nowConfig.name) {
deploymentName = nowConfig.name
deploymentName = nowConfig.name;
}
}
if (deploymentType === 'static') {
isStatic = true
deploymentType = 'npm'
if (deploymentType === "static") {
isStatic = true;
deploymentType = "npm";
}
if (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) {
const e = Error(`Failed to read JSON in "${path}/package.json"`)
e.userError = true
throw e
const e = Error(`Failed to read JSON in "${path}/package.json"`);
e.userError = true;
throw e;
}
}
if (strict && (!pkg.scripts || (!pkg.scripts.start && !pkg.scripts['now-start']))) {
const e = Error('Missing `start` (or `now-start`) script in `package.json`. ' +
'See: https://docs.npmjs.com/cli/start.')
e.userError = true
throw e
if (
strict &&
(!pkg.scripts || (!pkg.scripts.start && !pkg.scripts["now-start"]))
) {
const e = Error(
"Missing `start` (or `now-start`) script in `package.json`. " +
"See: https://docs.npmjs.com/cli/start."
);
e.userError = true;
throw e;
}
if (!deploymentName) {
if (typeof pkg.name === 'string') {
name = pkg.name
if (typeof pkg.name === "string") {
name = pkg.name;
} else {
name = basename(path)
name = basename(path);
if (!quiet && !isStatic) {
console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`)
console.log(
`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`
);
}
}
}
description = pkg.description
} else if (deploymentType === 'docker') {
let docker
description = pkg.description;
} else if (deploymentType === "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 = {}
docker
.filter(cmd => cmd.name === 'LABEL')
.forEach(({args}) => {
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
const e = Error(
`Error parsing value for LABEL ${key} in \`Dockerfile\``
);
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)}`)
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)}`)
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) {
@ -158,13 +178,15 @@ async function readMetaData(path, {
// file, then fail hard and let the user know that they need to pick one or the
// other
if (hasNowJson) {
const e = new Error('You have a `now` configuration field' +
'inside `package.json`, but configuration is also present' +
'in `now.json`! Please ensure there\'s a single source of configuration by removing one')
e.userError = true
throw e
const e = new Error(
"You have a `now` configuration field" +
"inside `package.json`, but configuration is also present" +
"in `now.json`! Please ensure there's a single source of configuration by removing one"
);
e.userError = true;
throw e;
} else {
nowConfig = pkg.now
nowConfig = pkg.now;
}
}
@ -175,5 +197,5 @@ async function readMetaData(path, {
pkg,
nowConfig,
hasNowJson
}
};
}

80
lib/secrets.js

@ -1,115 +1,117 @@
// 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'})
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', {
method: 'POST',
const res = await this._fetch("/now/secrets", {
method: "POST",
body: {
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}`, {
method: 'PATCH',
method: "PATCH",
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(/\x1b[^m]*m/g, '').length
return str.replace(/\x1b[^m]*m/g, "").length;
}
module.exports = strlen
module.exports = strlen;

30
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)
getFiles(resolve("../mng-test/files-in-package"))
.then(files => {
console.log(files);
getFiles(resolve('../mng-test/files-in-package-ignore'))
.then(files2 => {
console.log('ignored: ')
console.log(files2)
getFiles(resolve("../mng-test/files-in-package-ignore"))
.then(files2 => {
console.log("ignored: ");
console.log(files2);
})
.catch(err => {
console.log(err.stack);
});
})
.catch(err => {
console.log(err.stack)
})
})
.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()})`;

48
lib/utils/billing/geocode.js

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

44
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')
}
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
case 'downloads':
locationName = 'downloads directory'
break
case "home":
locationName = "user directory";
break;
case "downloads":
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;

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);
};

106
lib/utils/input/list.js

@ -1,86 +1,92 @@
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) + ' '
inquirer.prompt.prompts.list.prototype.getQuestion = function() {
var message = chalk.bold("> " + this.opt.message) + " ";
// Append the default if available, and if question isn't answered
if (this.opt.default != null && this.status !== 'answered') {
message += chalk.dim('(' + this.opt.default + ') ')
if (this.opt.default != null && this.status !== "answered") {
message += chalk.dim("(" + this.opt.default + ") ");
}
return message
return message;
};
/* eslint-enable */
function getLength(string) {
let biggestLength = 0
string.split('\n').map(str => {
str = stripAnsi(str)
let biggestLength = 0;
string.split("\n").map(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',
choices = [{ // eslint-disable-line no-unused-vars
name: 'something\ndescription\ndetails\netc',
value: 'something unique',
short: 'generally the first line of `name`'
}],
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
separator = true, // puts a blank separator between each choice
abort = 'end' // wether the `abort` option will be at the `start` or the `end`
}) {
let biggestLength = 0
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)
), [])
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',
name: "Abort",
value: undefined
}
};
if (abort === 'start') {
const blankSep = choices.shift()
choices.unshift(abortSeparator)
choices.unshift(_abort)
choices.unshift(blankSep)
if (abort === "start") {
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',
type: "list",
message,
choices,
pageSize
})
});
return answer[nonce]
}
return answer[nonce];
};

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

@ -1,56 +1,59 @@
const chalk = require('chalk')
const chalk = require("chalk");
module.exports = (label, {
defaultValue = false,
abortSequences = new Set(['\x03']),
resolveChars = new Set(['\r']),
yesChar = 'y',
noChar = 'n',
stdin = process.stdin,
stdout = process.stdout,
trailing = '\n'
} = {}) => {
module.exports = (
label,
{
defaultValue = false,
abortSequences = new Set(["\x03"]),
resolveChars = new Set(["\r"]),
yesChar = "y",
noChar = "n",
stdin = process.stdin,
stdout = process.stdout,
trailing = "\n"
} = {}
) => {
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() {
console.log(trailing)
stdin.setRawMode(isRaw)
stdin.pause()
stdin.removeListener('data', onData)
console.log(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
}
}
const defaultText = defaultValue === null ?
`[${yesChar}|${noChar}]` :
defaultValue ?
`[${chalk.bold(yesChar.toUpperCase())}|${noChar}]` :
`[${yesChar}|${chalk.bold(noChar.toUpperCase())}]`
stdout.write(`${chalk.gray('-')} ${label} ${chalk.gray(defaultText)} `)
stdin.on('data', onData)
})
}
const defaultText = defaultValue === null
? `[${yesChar}|${noChar}]`
: defaultValue
? `[${chalk.bold(yesChar.toUpperCase())}|${noChar}]`
: `[${yesChar}|${chalk.bold(noChar.toUpperCase())}]`;
stdout.write(`${chalk.gray("-")} ${label} ${chalk.gray(defaultText)} `);
stdin.on("data", onData);
});
};

294
lib/utils/input/text.js

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

7
lib/utils/output/cmd.js

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

7
lib/utils/output/code.js

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

7
lib/utils/output/error.js

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

7
lib/utils/output/info.js

@ -1,7 +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" ? "Δ" : "𝚫";

7
lib/utils/output/param.js

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

12
lib/utils/output/stamp.js

@ -1,12 +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}`);
};

6
lib/utils/output/uid.js

@ -1,7 +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);
};
};

42
lib/utils/prompt-options.js

@ -1,35 +1,35 @@
// Packages
const chalk = require('chalk')
const chalk = require("chalk");
module.exports = function (opts) {
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
if (s === "\u0003") {
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;

Loading…
Cancel
Save