Leo Lamprecht
8 years ago
180 changed files with 0 additions and 22590 deletions
@ -1,9 +0,0 @@ |
|||
# build output |
|||
packed |
|||
download/dist |
|||
|
|||
# dependencies |
|||
node_modules |
|||
|
|||
# logs |
|||
npm-debug.log |
@ -1 +0,0 @@ |
|||
save-exact = true |
@ -1,119 +0,0 @@ |
|||
const { italic, bold } = require('chalk') |
|||
|
|||
const error = require('../../lib/utils/output/error') |
|||
const wait = require('../../lib/utils/output/wait') |
|||
const cmd = require('../../lib/utils/output/cmd') |
|||
const param = require('../../lib/utils/output/param') |
|||
const info = require('../../lib/utils/output/info') |
|||
const success = require('../../lib/utils/output/success') |
|||
const stamp = require('../../lib/utils/output/stamp') |
|||
const promptBool = require('../../lib/utils/input/prompt-bool') |
|||
const eraseLines = require('../../lib/utils/output/erase-lines') |
|||
const treatBuyError = require('../../lib/utils/domains/treat-buy-error') |
|||
const NowCreditCards = require('../../lib/credit-cards') |
|||
|
|||
module.exports = async function({ domains, args, currentTeam, user, coupon }) { |
|||
const name = args[0] |
|||
let elapsed |
|||
|
|||
if (!name) { |
|||
return error(`Missing domain name. Run ${cmd('now domains help')}`) |
|||
} |
|||
|
|||
const nameParam = param(name) |
|||
let stopSpinner |
|||
|
|||
let price |
|||
let period |
|||
let validCoupon |
|||
try { |
|||
if (coupon) { |
|||
stopSpinner = wait(`Validating coupon ${param(coupon)}`) |
|||
const creditCards = new NowCreditCards({ |
|||
apiUrl: domains._agent._url, |
|||
token: domains._token, |
|||
debug: domains._debug, |
|||
currentTeam |
|||
}) |
|||
const [couponInfo, { cards }] = await Promise.all([ |
|||
domains.coupon(coupon), |
|||
creditCards.ls() |
|||
]) |
|||
stopSpinner() |
|||
|
|||
if (!couponInfo.isValid) { |
|||
return error(`The coupon ${param(coupon)} is invalid`) |
|||
} |
|||
|
|||
if (!couponInfo.canBeUsed) { |
|||
return error(`The coupon ${param(coupon)} has already been used`) |
|||
} |
|||
|
|||
validCoupon = true |
|||
|
|||
if (cards.length === 0) { |
|||
info( |
|||
'You have no credit cards on file. Please add one in order to claim your free domain' |
|||
) |
|||
info(`Your card will ${bold('not')} be charged`) |
|||
|
|||
await require('../now-billing-add')({ |
|||
creditCards, |
|||
currentTeam, |
|||
user, |
|||
clear: true |
|||
}) |
|||
} |
|||
} |
|||
elapsed = stamp() |
|||
stopSpinner = wait(`Checking availability for ${nameParam}`) |
|||
const json = await domains.price(name) |
|||
price = validCoupon ? 0 : json.price |
|||
period = json.period |
|||
} catch (err) { |
|||
stopSpinner() |
|||
return error(err.message) |
|||
} |
|||
|
|||
const available = await domains.status(name) |
|||
|
|||
stopSpinner() |
|||
|
|||
if (!available) { |
|||
return error( |
|||
`The domain ${nameParam} is ${italic('unavailable')}! ${elapsed()}` |
|||
) |
|||
} |
|||
const periodMsg = `${period}yr${period > 1 ? 's' : ''}` |
|||
info( |
|||
`The domain ${nameParam} is ${italic('available')} to buy under ${bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}! ${elapsed()}` |
|||
) |
|||
const confirmation = await promptBool( |
|||
`Buy now for ${bold(`$${price}`)} (${periodMsg})?` |
|||
) |
|||
|
|||
eraseLines(1) |
|||
if (!confirmation) { |
|||
return info('Aborted') |
|||
} |
|||
|
|||
stopSpinner = wait('Purchasing') |
|||
elapsed = stamp() |
|||
try { |
|||
await domains.buy({ name, coupon }) |
|||
} catch (err) { |
|||
stopSpinner() |
|||
return treatBuyError(err) |
|||
} |
|||
|
|||
stopSpinner() |
|||
|
|||
success(`Domain ${nameParam} purchased ${elapsed()}`) |
|||
info( |
|||
`You may now use your domain as an alias to your deployments. Run ${cmd( |
|||
'now alias --help' |
|||
)}` |
|||
) |
|||
} |
@ -1,529 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const minimist = require('minimist') |
|||
const table = require('text-table') |
|||
const ms = require('ms') |
|||
const printf = require('printf') |
|||
require('epipebomb')() |
|||
const supportsColor = require('supports-color') |
|||
|
|||
// Ours
|
|||
const strlen = require('../lib/strlen') |
|||
const NowAlias = require('../lib/alias') |
|||
const NowDomains = require('../lib/domains') |
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const toHost = require('../lib/to-host') |
|||
const { reAlias } = require('../lib/re-alias') |
|||
const exit = require('../lib/utils/exit') |
|||
const info = require('../lib/utils/output/info') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const promptBool = require('../lib/utils/input/prompt-bool') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token', 'rules'], |
|||
boolean: ['help', 'debug'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
rules: 'r', |
|||
debug: 'd', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const subcommand = argv._[0] |
|||
|
|||
const grayWidth = 10 |
|||
const underlineWidth = 11 |
|||
|
|||
// Options
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now alias`)} <ls | set | rm> <deployment> <alias> |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-r ${chalk.bold.underline('RULES_FILE')}, --rules=${chalk.bold.underline( |
|||
'RULES_FILE' |
|||
)} Rules file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Lists all your aliases: |
|||
|
|||
${chalk.cyan('$ now alias ls')} |
|||
|
|||
${chalk.gray('–')} Adds a new alias to ${chalk.underline('my-api.now.sh')}: |
|||
|
|||
${chalk.cyan( |
|||
`$ now alias set ${chalk.underline( |
|||
'api-ownv3nc9f8.now.sh' |
|||
)} ${chalk.underline('my-api.now.sh')}` |
|||
)} |
|||
|
|||
The ${chalk.dim('`.now.sh`')} suffix can be ommited: |
|||
|
|||
${chalk.cyan('$ now alias set api-ownv3nc9f8 my-api')} |
|||
|
|||
The deployment id can be used as the source: |
|||
|
|||
${chalk.cyan('$ now alias set deploymentId my-alias')} |
|||
|
|||
Custom domains work as alias targets: |
|||
|
|||
${chalk.cyan( |
|||
`$ now alias set ${chalk.underline( |
|||
'api-ownv3nc9f8.now.sh' |
|||
)} ${chalk.underline('my-api.com')}` |
|||
)} |
|||
|
|||
${chalk.dim('–')} The subcommand ${chalk.dim( |
|||
'`set`' |
|||
)} is the default and can be skipped. |
|||
${chalk.dim('–')} ${chalk.dim( |
|||
'`http(s)://`' |
|||
)} in the URLs is unneeded / ignored. |
|||
|
|||
${chalk.gray('–')} Add and modify path based aliases for ${chalk.underline( |
|||
'zeit.ninja' |
|||
)}: |
|||
|
|||
${chalk.cyan( |
|||
`$ now alias ${chalk.underline('zeit.ninja')} -r ${chalk.underline( |
|||
'rules.json' |
|||
)}` |
|||
)} |
|||
|
|||
Export effective routing rules: |
|||
|
|||
${chalk.cyan( |
|||
`$ now alias ls aliasId --json > ${chalk.underline('rules.json')}` |
|||
)} |
|||
|
|||
${chalk.cyan(`$ now alias ls zeit.ninja`)} |
|||
|
|||
${chalk.gray('–')} Removing an alias: |
|||
|
|||
${chalk.cyan('$ now alias rm aliasId')} |
|||
|
|||
To get the list of alias ids, use ${chalk.dim('`now alias ls`')}. |
|||
|
|||
${chalk.dim('Alias:')} ln |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
if (argv.help) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
|
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
if (err.userError) { |
|||
error(err.message) |
|||
} else { |
|||
error(`Unknown error: ${err}\n${err.stack}`) |
|||
} |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam, user } }) { |
|||
const alias = new NowAlias({ apiUrl, token, debug, currentTeam }) |
|||
const domains = new NowDomains({ apiUrl, token, debug, currentTeam }) |
|||
const args = argv._.slice(1) |
|||
|
|||
switch (subcommand) { |
|||
case 'ls': |
|||
case 'list': { |
|||
if (args.length === 1) { |
|||
const list = await alias.listAliases() |
|||
const item = list.find( |
|||
e => e.uid === argv._[1] || e.alias === argv._[1] |
|||
) |
|||
if (!item || !item.rules) { |
|||
error(`Could not match path alias for: ${argv._[1]}`) |
|||
return exit(1) |
|||
} |
|||
|
|||
if (argv.json) { |
|||
console.log(JSON.stringify({ rules: item.rules }, null, 2)) |
|||
} else { |
|||
const header = [ |
|||
['', 'pathname', 'method', 'dest'].map(s => chalk.dim(s)) |
|||
] |
|||
const text = |
|||
list.length === 0 |
|||
? null |
|||
: table( |
|||
header.concat( |
|||
item.rules.map(rule => { |
|||
return [ |
|||
'', |
|||
rule.pathname ? rule.pathname : '', |
|||
rule.method ? rule.method : '*', |
|||
rule.dest |
|||
] |
|||
}) |
|||
), |
|||
{ |
|||
align: ['l', 'l', 'l', 'l'], |
|||
hsep: ' '.repeat(2), |
|||
stringLength: strlen |
|||
} |
|||
) |
|||
|
|||
console.log(text) |
|||
} |
|||
break |
|||
} else if (args.length !== 0) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan('`now alias ls`')}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const start_ = new Date() |
|||
const aliases = await alias.ls() |
|||
aliases.sort((a, b) => new Date(b.created) - new Date(a.created)) |
|||
const current = new Date() |
|||
const sourceUrlLength = |
|||
aliases.reduce((acc, i) => { |
|||
return Math.max(acc, (i.deployment && i.deployment.url.length) || 0) |
|||
}, 0) + 9 |
|||
const aliasLength = |
|||
aliases.reduce((acc, i) => { |
|||
return Math.max(acc, (i.alias && i.alias.length) || 0) |
|||
}, 0) + 8 |
|||
const elapsed_ = ms(new Date() - start_) |
|||
console.log( |
|||
`> ${aliases.length} alias${aliases.length === 1 |
|||
? '' |
|||
: 'es'} found ${chalk.gray(`[${elapsed_}]`)} under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
console.log() |
|||
|
|||
if (supportsColor) { |
|||
const urlSpecHeader = `%-${sourceUrlLength + 1}s` |
|||
const aliasSpecHeader = `%-${aliasLength + 1}s` |
|||
console.log( |
|||
printf( |
|||
` ${chalk.gray(urlSpecHeader + ' ' + aliasSpecHeader + ' %5s')}`, |
|||
'source', |
|||
'url', |
|||
'age' |
|||
) |
|||
) |
|||
} else { |
|||
const urlSpecHeader = `%-${sourceUrlLength}s` |
|||
const aliasSpecHeader = `%-${aliasLength}s` |
|||
console.log( |
|||
printf( |
|||
` ${urlSpecHeader} ${aliasSpecHeader} %5s`, |
|||
'source', |
|||
'url', |
|||
'age' |
|||
) |
|||
) |
|||
} |
|||
|
|||
let text = '' |
|||
aliases.forEach(_alias => { |
|||
let urlSpec = sourceUrlLength |
|||
let aliasSpec = aliasLength |
|||
let ageSpec = 5 |
|||
const _url = chalk.underline(_alias.alias) |
|||
let _sourceUrl |
|||
if (supportsColor) { |
|||
aliasSpec += underlineWidth |
|||
ageSpec += grayWidth |
|||
} |
|||
if (_alias.deployment) { |
|||
_sourceUrl = chalk.underline(_alias.deployment.url) |
|||
if (supportsColor) { |
|||
urlSpec += grayWidth |
|||
} |
|||
} else if (_alias.rules) { |
|||
_sourceUrl = chalk.gray( |
|||
`[${_alias.rules.length} custom rule${_alias.rules.length > 1 |
|||
? 's' |
|||
: ''}]` |
|||
) |
|||
if (supportsColor) { |
|||
urlSpec += underlineWidth |
|||
} |
|||
} else { |
|||
_sourceUrl = chalk.gray('<null>') |
|||
} |
|||
|
|||
const time = chalk.gray(ms(current - new Date(_alias.created))) |
|||
text += printf( |
|||
` %-${urlSpec}s %-${aliasSpec}s %${ageSpec}s\n`, |
|||
_sourceUrl, |
|||
_url, |
|||
time |
|||
) |
|||
}) |
|||
|
|||
console.log(text) |
|||
break |
|||
} |
|||
case 'rm': |
|||
case 'remove': { |
|||
const _target = String(args[0]) |
|||
if (!_target) { |
|||
const err = new Error('No alias id specified') |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
if (args.length !== 1) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now alias rm <id>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const _aliases = await alias.ls() |
|||
const _alias = findAlias(_target, _aliases) |
|||
|
|||
if (!_alias) { |
|||
const err = new Error( |
|||
`Alias not found by "${_target}" under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}. Run ${chalk.dim('`now alias ls`')} to see your aliases.` |
|||
) |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
try { |
|||
const confirmation = await confirmDeploymentRemoval(alias, _alias) |
|||
if (!confirmation) { |
|||
info('Aborted') |
|||
return process.exit(0) |
|||
} |
|||
|
|||
const start = new Date() |
|||
await alias.rm(_alias) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Alias ${chalk.bold( |
|||
_alias.uid |
|||
)} removed [${elapsed}]` |
|||
) |
|||
} catch (err) { |
|||
error(err) |
|||
exit(1) |
|||
} |
|||
|
|||
break |
|||
} |
|||
case 'add': |
|||
case 'set': { |
|||
if (argv.rules) { |
|||
await updatePathAlias(alias, argv._[0], argv.rules, domains) |
|||
break |
|||
} |
|||
if (args.length !== 2) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now alias set <id> <domain>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
await alias.set( |
|||
String(args[0]), |
|||
String(args[1]), |
|||
domains, |
|||
currentTeam, |
|||
user |
|||
) |
|||
break |
|||
} |
|||
default: { |
|||
if (argv._.length === 0) { |
|||
await reAlias( |
|||
token, |
|||
null, |
|||
null, |
|||
help, |
|||
exit, |
|||
apiUrl, |
|||
debug, |
|||
alias, |
|||
currentTeam, |
|||
user |
|||
) |
|||
break |
|||
} |
|||
|
|||
if (argv.rules) { |
|||
await updatePathAlias(alias, argv._[0], argv.rules, domains) |
|||
break |
|||
} |
|||
|
|||
if (argv._.length === 1) { |
|||
await reAlias( |
|||
token, |
|||
null, |
|||
String(argv._[0]), |
|||
help, |
|||
exit, |
|||
apiUrl, |
|||
debug, |
|||
alias, |
|||
currentTeam, |
|||
user |
|||
) |
|||
break |
|||
} else if (argv._.length === 2) { |
|||
await alias.set( |
|||
String(argv._[0]), |
|||
String(argv._[1]), |
|||
domains, |
|||
currentTeam, |
|||
user |
|||
) |
|||
} else if (argv._.length >= 3) { |
|||
error('Invalid number of arguments') |
|||
help() |
|||
exit(1) |
|||
} else { |
|||
error('Please specify a valid subcommand: ls | set | rm') |
|||
help() |
|||
exit(1) |
|||
} |
|||
} |
|||
} |
|||
|
|||
domains.close() |
|||
alias.close() |
|||
} |
|||
|
|||
async function confirmDeploymentRemoval(alias, _alias) { |
|||
const time = chalk.gray(ms(new Date() - new Date(_alias.created)) + ' ago') |
|||
const _sourceUrl = _alias.deployment |
|||
? chalk.underline(_alias.deployment.url) |
|||
: null |
|||
const tbl = table( |
|||
[ |
|||
[ |
|||
_alias.uid, |
|||
...(_sourceUrl ? [_sourceUrl] : []), |
|||
chalk.underline(_alias.alias), |
|||
time |
|||
] |
|||
], |
|||
{ hsep: ' '.repeat(6) } |
|||
) |
|||
|
|||
const msg = |
|||
'> The following alias will be removed permanently\n' + |
|||
` ${tbl} \nAre you sure?` |
|||
|
|||
return promptBool(msg, { |
|||
trailing: '\n' |
|||
}) |
|||
} |
|||
|
|||
function findAlias(alias, list) { |
|||
let key |
|||
let val |
|||
|
|||
if (/\./.test(alias)) { |
|||
val = toHost(alias) |
|||
key = 'alias' |
|||
} else { |
|||
val = alias |
|||
key = 'uid' |
|||
} |
|||
|
|||
const _alias = list.find(d => { |
|||
if (d[key] === val) { |
|||
if (debug) { |
|||
console.log(`> [debug] matched alias ${d.uid} by ${key} ${val}`) |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
// Match prefix
|
|||
if (`${val}.now.sh` === d.alias) { |
|||
if (debug) { |
|||
console.log(`> [debug] matched alias ${d.uid} by url ${d.host}`) |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
return false |
|||
}) |
|||
|
|||
return _alias |
|||
} |
|||
|
|||
async function updatePathAlias(alias, aliasName, rules, domains) { |
|||
const start = new Date() |
|||
const res = await alias.updatePathBasedroutes( |
|||
String(aliasName), |
|||
rules, |
|||
domains |
|||
) |
|||
const elapsed = ms(new Date() - start) |
|||
if (res.error) { |
|||
const err = new Error(res.error.message) |
|||
err.userError = true |
|||
throw err |
|||
} else { |
|||
console.log( |
|||
`${chalk.cyan( |
|||
'> Success!' |
|||
)} ${res.ruleCount} rules configured for ${chalk.underline( |
|||
res.alias |
|||
)} [${elapsed}]` |
|||
) |
|||
} |
|||
} |
@ -1,255 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const ansiEscapes = require('ansi-escapes') |
|||
const chalk = require('chalk') |
|||
const ccValidator = require('credit-card') |
|||
|
|||
// Ours
|
|||
const textInput = require('../lib/utils/input/text') |
|||
const countries = require('../lib/utils/billing/country-list') |
|||
const cardBrands = require('../lib/utils/billing/card-brands') |
|||
const geocode = require('../lib/utils/billing/geocode') |
|||
const success = require('../lib/utils/output/success') |
|||
const wait = require('../lib/utils/output/wait') |
|||
const { tick } = require('../lib/utils/output/chars') |
|||
const rightPad = require('../lib/utils/output/right-pad') |
|||
|
|||
function expDateMiddleware(data) { |
|||
return data |
|||
} |
|||
|
|||
module.exports = async function({ |
|||
creditCards, |
|||
currentTeam, |
|||
user, |
|||
clear = false |
|||
}) { |
|||
const state = { |
|||
error: undefined, |
|||
cardGroupLabel: `> ${chalk.bold( |
|||
`Enter your card details for ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
)}`,
|
|||
|
|||
name: { |
|||
label: rightPad('Full Name', 12), |
|||
placeholder: 'John Appleseed', |
|||
validateValue: data => data.trim().length > 0 |
|||
}, |
|||
|
|||
cardNumber: { |
|||
label: rightPad('Number', 12), |
|||
mask: 'cc', |
|||
placeholder: '#### #### #### ####', |
|||
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19, |
|||
validateValue: data => { |
|||
data = data.replace(/ /g, '') |
|||
const type = ccValidator.determineCardType(data) |
|||
if (!type) { |
|||
return false |
|||
} |
|||
return ccValidator.isValidCardNumber(data, type) |
|||
} |
|||
}, |
|||
|
|||
ccv: { |
|||
label: rightPad('CCV', 12), |
|||
mask: 'ccv', |
|||
placeholder: '###', |
|||
validateValue: data => { |
|||
const brand = state.cardNumber.brand.toLowerCase() |
|||
return ccValidator.doesCvvMatchType(data, brand) |
|||
} |
|||
}, |
|||
|
|||
expDate: { |
|||
label: rightPad('Exp. Date', 12), |
|||
mask: 'expDate', |
|||
placeholder: 'mm / yyyy', |
|||
middleware: expDateMiddleware, |
|||
validateValue: data => !ccValidator.isExpired(...data.split(' / ')) |
|||
}, |
|||
|
|||
addressGroupLabel: `\n> ${chalk.bold('Enter your billing address')}`, |
|||
|
|||
country: { |
|||
label: rightPad('Country', 12), |
|||
async autoComplete(value) { |
|||
for (const country in countries) { |
|||
if (!Object.hasOwnProperty.call(countries, country)) { |
|||
continue |
|||
} |
|||
|
|||
if (country.startsWith(value)) { |
|||
return country.substr(value.length) |
|||
} |
|||
|
|||
const lowercaseCountry = country.toLowerCase() |
|||
const lowercaseValue = value.toLowerCase() |
|||
|
|||
if (lowercaseCountry.startsWith(lowercaseValue)) { |
|||
return lowercaseCountry.substr(value.length) |
|||
} |
|||
} |
|||
|
|||
return false |
|||
}, |
|||
validateValue: value => { |
|||
for (const country in countries) { |
|||
if (!Object.hasOwnProperty.call(countries, country)) { |
|||
continue |
|||
} |
|||
|
|||
if (country.toLowerCase() === value.toLowerCase()) { |
|||
return true |
|||
} |
|||
} |
|||
|
|||
return false |
|||
} |
|||
}, |
|||
|
|||
zipCode: { |
|||
label: rightPad('ZIP', 12), |
|||
validadeKeypress: data => data.trim().length > 0, |
|||
validateValue: data => data.trim().length > 0 |
|||
}, |
|||
|
|||
state: { |
|||
label: rightPad('State', 12), |
|||
validateValue: data => data.trim().length > 0 |
|||
}, |
|||
|
|||
city: { |
|||
label: rightPad('City', 12), |
|||
validateValue: data => data.trim().length > 0 |
|||
}, |
|||
|
|||
address1: { |
|||
label: rightPad('Address', 12), |
|||
validateValue: data => data.trim().length > 0 |
|||
} |
|||
} |
|||
|
|||
async function render() { |
|||
for (const key in state) { |
|||
if (!Object.hasOwnProperty.call(state, key)) { |
|||
continue |
|||
} |
|||
const piece = state[key] |
|||
if (typeof piece === 'string') { |
|||
console.log(piece) |
|||
} else if (typeof piece === 'object') { |
|||
let result |
|||
try { |
|||
/* eslint-disable no-await-in-loop */ |
|||
result = await textInput({ |
|||
label: '- ' + piece.label, |
|||
initialValue: piece.initialValue || piece.value, |
|||
placeholder: piece.placeholder, |
|||
mask: piece.mask, |
|||
validateKeypress: piece.validateKeypress, |
|||
validateValue: piece.validateValue, |
|||
autoComplete: piece.autoComplete |
|||
}) |
|||
|
|||
piece.value = result |
|||
|
|||
if (key === 'cardNumber') { |
|||
let brand = cardBrands[ccValidator.determineCardType(result)] |
|||
piece.brand = brand |
|||
|
|||
if (brand === 'American Express') { |
|||
state.ccv.placeholder = '#'.repeat(4) |
|||
} else { |
|||
state.ccv.placeholder = '#'.repeat(3) |
|||
} |
|||
|
|||
brand = chalk.cyan(`[${brand}]`) |
|||
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[3] |
|||
process.stdout.write( |
|||
`${chalk.cyan(tick)} ${piece.label}${masked} ${brand}\n` |
|||
) |
|||
} else if (key === 'ccv') { |
|||
process.stdout.write( |
|||
`${chalk.cyan(tick)} ${piece.label}${'*'.repeat(result.length)}\n` |
|||
) |
|||
} else if (key === 'expDate') { |
|||
let text = result.split(' / ') |
|||
text = text[0] + chalk.gray(' / ') + text[1] |
|||
process.stdout.write(`${chalk.cyan(tick)} ${piece.label}${text}\n`) |
|||
} else if (key === 'zipCode') { |
|||
const stopSpinner = wait(piece.label + result) |
|||
const addressInfo = await geocode({ |
|||
country: state.country.value, |
|||
zipCode: result |
|||
}) |
|||
if (addressInfo.state) { |
|||
state.state.initialValue = addressInfo.state |
|||
} |
|||
if (addressInfo.city) { |
|||
state.city.initialValue = addressInfo.city |
|||
} |
|||
stopSpinner() |
|||
process.stdout.write( |
|||
`${chalk.cyan(tick)} ${piece.label}${result}\n` |
|||
) |
|||
} else { |
|||
process.stdout.write( |
|||
`${chalk.cyan(tick)} ${piece.label}${result}\n` |
|||
) |
|||
} |
|||
} catch (err) { |
|||
if (err.message === 'USER_ABORT') { |
|||
process.exit(1) |
|||
} else { |
|||
console.error(err) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
console.log('') // New line
|
|||
const stopSpinner = wait('Saving card') |
|||
|
|||
try { |
|||
const res = await creditCards.add({ |
|||
name: state.name.value, |
|||
cardNumber: state.cardNumber.value, |
|||
ccv: state.ccv.value, |
|||
expDate: state.expDate.value, |
|||
country: state.country.value, |
|||
zipCode: state.zipCode.value, |
|||
state: state.state.value, |
|||
city: state.city.value, |
|||
address1: state.address1.value |
|||
}) |
|||
stopSpinner() |
|||
if (clear) { |
|||
const linesToClear = state.error ? 15 : 14 |
|||
process.stdout.write(ansiEscapes.eraseLines(linesToClear)) |
|||
} |
|||
success( |
|||
`${state.cardNumber |
|||
.brand} ending in ${res.last4} was added to ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
} catch (err) { |
|||
stopSpinner() |
|||
const linesToClear = state.error ? 15 : 14 |
|||
process.stdout.write(ansiEscapes.eraseLines(linesToClear)) |
|||
state.error = `${chalk.red( |
|||
'> Error!' |
|||
)} ${err.message} Please make sure the info is correct` |
|||
await render() |
|||
} |
|||
} |
|||
|
|||
try { |
|||
await render() |
|||
} catch (err) { |
|||
console.erorr(err) |
|||
} |
|||
} |
@ -1,381 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Native
|
|||
const { resolve } = require('path') |
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
|
|||
// Ours
|
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const NowCreditCards = require('../lib/credit-cards') |
|||
const indent = require('../lib/indent') |
|||
const listInput = require('../lib/utils/input/list') |
|||
const success = require('../lib/utils/output/success') |
|||
const promptBool = require('../lib/utils/input/prompt-bool') |
|||
const info = require('../lib/utils/output/info') |
|||
const logo = require('../lib/utils/output/logo') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const subcommand = argv._[0] |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now billing`)} <ls | add | rm | set-default> |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Lists all your credit cards: |
|||
|
|||
${chalk.cyan('$ now billing ls')} |
|||
|
|||
${chalk.gray('–')} Adds a credit card (interactively): |
|||
|
|||
${chalk.cyan(`$ now billing add`)} |
|||
|
|||
${chalk.gray('–')} Removes a credit card: |
|||
|
|||
${chalk.cyan(`$ now billing rm <id>`)} |
|||
|
|||
${chalk.gray('–')} If the id is omitted, you can choose interactively |
|||
|
|||
${chalk.gray('–')} Selects your default credit card: |
|||
|
|||
${chalk.cyan(`$ now billing set-default <id>`)} |
|||
|
|||
${chalk.gray('–')} If the id is omitted, you can choose interactively |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
const exit = code => { |
|||
// We give stdout some time to flush out
|
|||
// because there's a node bug where
|
|||
// stdout writes are asynchronous
|
|||
// https://github.com/nodejs/node/issues/6456
|
|||
setTimeout(() => process.exit(code || 0), 100) |
|||
} |
|||
|
|||
if (argv.help || !subcommand) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
if (err.userError) { |
|||
error(err.message) |
|||
} else { |
|||
error(`Unknown error: ${err.stack}`) |
|||
} |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
// Builds a `choices` object that can be passesd to inquirer.prompt()
|
|||
function buildInquirerChoices(cards) { |
|||
return cards.cards.map(card => { |
|||
const _default = |
|||
card.id === cards.defaultCardId ? ' ' + chalk.bold('(default)') : '' |
|||
const id = `${chalk.cyan(`ID: ${card.id}`)}${_default}` |
|||
const number = `${chalk.gray('#### ').repeat(3)}${card.last4}` |
|||
const str = [ |
|||
id, |
|||
indent(card.name, 2), |
|||
indent(`${card.brand} ${number}`, 2) |
|||
].join('\n') |
|||
|
|||
return { |
|||
name: str, // Will be displayed by Inquirer
|
|||
value: card.id, // Will be used to identify the answer
|
|||
short: card.id // Will be displayed after the users answers
|
|||
} |
|||
}) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam, user } }) { |
|||
const start = new Date() |
|||
const creditCards = new NowCreditCards({ apiUrl, token, debug, currentTeam }) |
|||
const args = argv._.slice(1) |
|||
|
|||
switch (subcommand) { |
|||
case 'ls': |
|||
case 'list': { |
|||
let cards |
|||
try { |
|||
cards = await creditCards.ls() |
|||
} catch (err) { |
|||
error(err.message) |
|||
return |
|||
} |
|||
const text = cards.cards |
|||
.map(card => { |
|||
const _default = |
|||
card.id === cards.defaultCardId ? ' ' + chalk.bold('(default)') : '' |
|||
const id = `${chalk.gray('-')} ${chalk.cyan( |
|||
`ID: ${card.id}` |
|||
)}${_default}` |
|||
const number = `${chalk.gray('#### ').repeat(3)}${card.last4}` |
|||
let address = card.address_line1 |
|||
|
|||
if (card.address_line2) { |
|||
address += `, ${card.address_line2}.` |
|||
} else { |
|||
address += '.' |
|||
} |
|||
|
|||
address += `\n${card.address_city}, ` |
|||
|
|||
if (card.address_state) { |
|||
address += `${card.address_state}, ` |
|||
} |
|||
|
|||
// Stripe is returning a two digit code for the country,
|
|||
// but we want the full country name
|
|||
address += `${card.address_zip}. ${card.address_country}` |
|||
|
|||
return [ |
|||
id, |
|||
indent(card.name, 2), |
|||
indent(`${card.brand} ${number}`, 2), |
|||
indent(address, 2) |
|||
].join('\n') |
|||
}) |
|||
.join('\n\n') |
|||
|
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`> ${cards.cards.length} card${cards.cards.length === 1 |
|||
? '' |
|||
: 's'} found under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)} ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
if (text) { |
|||
console.log(`\n${text}\n`) |
|||
} |
|||
|
|||
break |
|||
} |
|||
|
|||
case 'set-default': { |
|||
if (args.length > 1) { |
|||
error('Invalid number of arguments') |
|||
return exit(1) |
|||
} |
|||
|
|||
const start = new Date() |
|||
|
|||
let cards |
|||
try { |
|||
cards = await creditCards.ls() |
|||
} catch (err) { |
|||
error(err.message) |
|||
return |
|||
} |
|||
|
|||
if (cards.cards.length === 0) { |
|||
error('You have no credit cards to choose from') |
|||
return exit(0) |
|||
} |
|||
|
|||
let cardId = args[0] |
|||
|
|||
if (cardId === undefined) { |
|||
const elapsed = ms(new Date() - start) |
|||
const message = `Selecting a new default payment card for ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)} ${chalk.gray(`[${elapsed}]`)}` |
|||
const choices = buildInquirerChoices(cards) |
|||
|
|||
cardId = await listInput({ |
|||
message, |
|||
choices, |
|||
separator: true, |
|||
abort: 'end' |
|||
}) |
|||
} |
|||
|
|||
// Check if the provided cardId (in case the user
|
|||
// typed `now billing set-default <some-id>`) is valid
|
|||
if (cardId) { |
|||
const label = `Are you sure that you to set this card as the default?` |
|||
const confirmation = await promptBool(label, { |
|||
trailing: '\n' |
|||
}) |
|||
if (!confirmation) { |
|||
info('Aborted') |
|||
break |
|||
} |
|||
const start = new Date() |
|||
await creditCards.setDefault(cardId) |
|||
|
|||
const card = cards.cards.find(card => card.id === cardId) |
|||
const elapsed = ms(new Date() - start) |
|||
success( |
|||
`${card.brand} ending in ${card.last4} is now the default ${chalk.gray( |
|||
`[${elapsed}]` |
|||
)}` |
|||
) |
|||
} else { |
|||
console.log('No changes made') |
|||
} |
|||
|
|||
break |
|||
} |
|||
|
|||
case 'rm': |
|||
case 'remove': { |
|||
if (args.length > 1) { |
|||
error('Invalid number of arguments') |
|||
return exit(1) |
|||
} |
|||
|
|||
const start = new Date() |
|||
let cards |
|||
try { |
|||
cards = await creditCards.ls() |
|||
} catch (err) { |
|||
error(err.message) |
|||
return |
|||
} |
|||
|
|||
if (cards.cards.length === 0) { |
|||
error( |
|||
`You have no credit cards to choose from to delete under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
return exit(0) |
|||
} |
|||
|
|||
let cardId = args[0] |
|||
|
|||
if (cardId === undefined) { |
|||
const elapsed = ms(new Date() - start) |
|||
const message = `Selecting a card to ${chalk.underline( |
|||
'remove' |
|||
)} under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)} ${chalk.gray(`[${elapsed}]`)}` |
|||
const choices = buildInquirerChoices(cards) |
|||
|
|||
cardId = await listInput({ |
|||
message, |
|||
choices, |
|||
separator: true, |
|||
abort: 'start' |
|||
}) |
|||
} |
|||
|
|||
// Shoud check if the provided cardId (in case the user
|
|||
// typed `now billing rm <some-id>`) is valid
|
|||
if (cardId) { |
|||
const label = `Are you sure that you want to remove this card?` |
|||
const confirmation = await promptBool(label) |
|||
if (!confirmation) { |
|||
console.log('Aborted') |
|||
break |
|||
} |
|||
const start = new Date() |
|||
await creditCards.rm(cardId) |
|||
|
|||
const deletedCard = cards.cards.find(card => card.id === cardId) |
|||
const remainingCards = cards.cards.filter(card => card.id !== cardId) |
|||
|
|||
let text = `${deletedCard.brand} ending in ${deletedCard.last4} was deleted` |
|||
// ${chalk.gray(`[${elapsed}]`)}
|
|||
|
|||
if (cardId === cards.defaultCardId) { |
|||
if (remainingCards.length === 0) { |
|||
// The user deleted the last card in their account
|
|||
text += `\n${chalk.yellow('Warning!')} You have no default card` |
|||
} else { |
|||
// We can't guess the current default card – let's ask the API
|
|||
const cards = await creditCards.ls() |
|||
const newDefaultCard = cards.cards.find( |
|||
card => card.id === cards.defaultCardId |
|||
) |
|||
|
|||
text += `\n${newDefaultCard.brand} ending in ${newDefaultCard.last4} in now default for ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
} |
|||
} |
|||
|
|||
const elapsed = ms(new Date() - start) |
|||
text += ` ${chalk.gray(`[${elapsed}]`)}` |
|||
success(text) |
|||
} else { |
|||
console.log('No changes made') |
|||
} |
|||
|
|||
break |
|||
} |
|||
|
|||
case 'add': { |
|||
require(resolve(__dirname, 'now-billing-add.js'))({ |
|||
creditCards, |
|||
currentTeam, |
|||
user |
|||
}) |
|||
|
|||
break |
|||
} |
|||
|
|||
default: |
|||
error('Please specify a valid subcommand: ls | add | rm | set-default') |
|||
help() |
|||
exit(1) |
|||
} |
|||
|
|||
creditCards.close() |
|||
} |
@ -1,384 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Native
|
|||
const path = require('path') |
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const table = require('text-table') |
|||
const minimist = require('minimist') |
|||
const fs = require('fs-extra') |
|||
const ms = require('ms') |
|||
const printf = require('printf') |
|||
require('epipebomb')() |
|||
const supportsColor = require('supports-color') |
|||
|
|||
// Ours
|
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const NowCerts = require('../lib/certs') |
|||
const login = require('../lib/login') |
|||
const exit = require('../lib/utils/exit') |
|||
const logo = require('../lib/utils/output/logo') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token', 'crt', 'key', 'ca'], |
|||
boolean: ['help', 'debug'], |
|||
alias: { help: 'h', config: 'c', debug: 'd', token: 't' } |
|||
}) |
|||
|
|||
const subcommand = argv._[0] |
|||
|
|||
// Options
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now certs`)} <ls | create | renew | replace | rm> <cn> |
|||
|
|||
${chalk.dim('Note:')} |
|||
|
|||
This command is intended for advanced use only, normally ${chalk.bold( |
|||
'now' |
|||
)} manages your certificates automatically. |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
--crt ${chalk.bold.underline('FILE')} Certificate file |
|||
--key ${chalk.bold.underline('FILE')} Certificate key file |
|||
--ca ${chalk.bold.underline('FILE')} CA certificate chain file |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Listing all your certificates: |
|||
|
|||
${chalk.cyan('$ now certs ls')} |
|||
|
|||
${chalk.gray('–')} Creating a new certificate: |
|||
|
|||
${chalk.cyan('$ now certs create domain.com')} |
|||
|
|||
${chalk.gray('–')} Renewing an existing certificate issued with ${chalk.bold( |
|||
'now' |
|||
)}: |
|||
|
|||
${chalk.cyan('$ now certs renew domain.com')} |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} Replacing an existing certificate with a user-supplied certificate: |
|||
|
|||
${chalk.cyan( |
|||
'$ now certs replace --crt domain.crt --key domain.key --ca ca_chain.crt domain.com' |
|||
)} |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
if (argv.help || !subcommand) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
|
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
handleError(err) |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
function formatExpirationDate(date) { |
|||
const diff = date - Date.now() |
|||
return diff < 0 |
|||
? chalk.gray(ms(-diff) + ' ago') |
|||
: chalk.gray('in ' + ms(diff)) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam, user } }) { |
|||
const certs = new NowCerts({ apiUrl, token, debug, currentTeam }) |
|||
const args = argv._.slice(1) |
|||
const start = Date.now() |
|||
|
|||
if (subcommand === 'ls' || subcommand === 'list') { |
|||
if (args.length !== 0) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan('`now certs ls`')}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const list = await certs.ls() |
|||
const elapsed = ms(new Date() - start) |
|||
|
|||
console.log( |
|||
`> ${list.length} certificate${list.length === 1 |
|||
? '' |
|||
: 's'} found ${chalk.gray(`[${elapsed}]`)} under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
|
|||
if (list.length > 0) { |
|||
const cur = Date.now() |
|||
list.sort((a, b) => { |
|||
return a.cn.localeCompare(b.cn) |
|||
}) |
|||
|
|||
const maxCnLength = |
|||
list.reduce((acc, i) => { |
|||
return Math.max(acc, (i.cn && i.cn.length) || 0) |
|||
}, 0) + 1 |
|||
|
|||
console.log( |
|||
chalk.dim( |
|||
printf( |
|||
` %-${maxCnLength}s %-8s %-10s %-10s`, |
|||
'cn', |
|||
'created', |
|||
'expiration', |
|||
'auto-renew' |
|||
) |
|||
) |
|||
) |
|||
|
|||
list.forEach(cert => { |
|||
const cn = chalk.bold(cert.cn) |
|||
const time = chalk.gray(ms(cur - new Date(cert.created)) + ' ago') |
|||
const expiration = formatExpirationDate(new Date(cert.expiration)) |
|||
const autoRenew = cert.autoRenew ? 'yes' : 'no' |
|||
let spec |
|||
if (supportsColor) { |
|||
spec = ` %-${maxCnLength + 9}s %-18s %-20s %-20s\n` |
|||
} else { |
|||
spec = ` %-${maxCnLength}s %-8s %-10s %-10s\n` |
|||
} |
|||
process.stdout.write(printf(spec, cn, time, expiration, autoRenew)) |
|||
}) |
|||
} |
|||
} else if (subcommand === 'create') { |
|||
if (args.length !== 1) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now certs create <cn>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
const cn = args[0] |
|||
let cert |
|||
|
|||
if (argv.crt || argv.key || argv.ca) { |
|||
// Issue a custom certificate
|
|||
if (!argv.crt || !argv.key) { |
|||
error( |
|||
`Missing required arguments for a custom certificate entry. Usage: ${chalk.cyan( |
|||
'`now certs create --crt DOMAIN.CRT --key DOMAIN.KEY [--ca CA.CRT] <id | cn>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const crt = readX509File(argv.crt) |
|||
const key = readX509File(argv.key) |
|||
const ca = argv.ca ? readX509File(argv.ca) : '' |
|||
|
|||
cert = await certs.put(cn, crt, key, ca) |
|||
} else { |
|||
// Issue a standard certificate
|
|||
cert = await certs.create(cn) |
|||
} |
|||
if (!cert) { |
|||
// Cert is undefined and "Cert is already issued" has been printed to stdout
|
|||
return exit(1) |
|||
} |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Certificate entry ${chalk.bold( |
|||
cn |
|||
)} ${chalk.gray(`(${cert.uid})`)} created ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
} else if (subcommand === 'renew') { |
|||
if (args.length !== 1) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now certs renew <id | cn>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const cert = await getCertIdCn(certs, args[0], currentTeam, user) |
|||
if (!cert) { |
|||
return exit(1) |
|||
} |
|||
const yes = await readConfirmation( |
|||
cert, |
|||
'The following certificate will be renewed\n' |
|||
) |
|||
|
|||
if (!yes) { |
|||
error('User abort') |
|||
return exit(0) |
|||
} |
|||
|
|||
await certs.renew(cert.cn) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Certificate ${chalk.bold( |
|||
cert.cn |
|||
)} ${chalk.gray(`(${cert.uid})`)} renewed ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
} else if (subcommand === 'replace') { |
|||
if (!argv.crt || !argv.key) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now certs replace --crt DOMAIN.CRT --key DOMAIN.KEY [--ca CA.CRT] <id | cn>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const crt = readX509File(argv.crt) |
|||
const key = readX509File(argv.key) |
|||
const ca = argv.ca ? readX509File(argv.ca) : '' |
|||
|
|||
const cert = await getCertIdCn(certs, args[0], currentTeam, user) |
|||
if (!cert) { |
|||
return exit(1) |
|||
} |
|||
const yes = await readConfirmation( |
|||
cert, |
|||
'The following certificate will be replaced permanently\n' |
|||
) |
|||
if (!yes) { |
|||
error('User abort') |
|||
return exit(0) |
|||
} |
|||
|
|||
await certs.put(cert.cn, crt, key, ca) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Certificate ${chalk.bold( |
|||
cert.cn |
|||
)} ${chalk.gray(`(${cert.uid})`)} replaced ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
} else if (subcommand === 'rm' || subcommand === 'remove') { |
|||
if (args.length !== 1) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now certs rm <id | cn>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const cert = await getCertIdCn(certs, args[0], currentTeam, user) |
|||
if (!cert) { |
|||
return exit(1) |
|||
} |
|||
const yes = await readConfirmation( |
|||
cert, |
|||
'The following certificate will be removed permanently\n' |
|||
) |
|||
if (!yes) { |
|||
error('User abort') |
|||
return exit(0) |
|||
} |
|||
|
|||
await certs.delete(cert.cn) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Certificate ${chalk.bold( |
|||
cert.cn |
|||
)} ${chalk.gray(`(${cert.uid})`)} removed ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
} else { |
|||
error( |
|||
'Please specify a valid subcommand: ls | create | renew | replace | rm' |
|||
) |
|||
help() |
|||
exit(1) |
|||
} |
|||
return certs.close() |
|||
} |
|||
|
|||
process.on('uncaughtException', err => { |
|||
handleError(err) |
|||
exit(1) |
|||
}) |
|||
|
|||
function readConfirmation(cert, msg) { |
|||
return new Promise(resolve => { |
|||
const time = chalk.gray(ms(new Date() - new Date(cert.created)) + ' ago') |
|||
const tbl = table([[cert.uid, chalk.bold(cert.cn), time]], { |
|||
align: ['l', 'r', 'l'], |
|||
hsep: ' '.repeat(6) |
|||
}) |
|||
|
|||
process.stdout.write(`> ${msg}`) |
|||
process.stdout.write(' ' + tbl + '\n') |
|||
|
|||
process.stdout.write( |
|||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}` |
|||
) |
|||
|
|||
process.stdin |
|||
.on('data', d => { |
|||
process.stdin.pause() |
|||
resolve(d.toString().trim().toLowerCase() === 'y') |
|||
}) |
|||
.resume() |
|||
}) |
|||
} |
|||
|
|||
function readX509File(file) { |
|||
return fs.readFileSync(path.resolve(file), 'utf8') |
|||
} |
|||
|
|||
async function getCertIdCn(certs, idOrCn, currentTeam, user) { |
|||
const list = await certs.ls() |
|||
const thecert = list.filter(cert => { |
|||
return cert.uid === idOrCn || cert.cn === idOrCn |
|||
})[0] |
|||
|
|||
if (!thecert) { |
|||
error( |
|||
`No certificate found by id or cn "${idOrCn}" under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
return null |
|||
} |
|||
|
|||
return thecert |
|||
} |
@ -1,877 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Native
|
|||
const { resolve, basename } = require('path') |
|||
|
|||
// Packages
|
|||
const Progress = require('progress') |
|||
const fs = require('fs-extra') |
|||
const bytes = require('bytes') |
|||
const chalk = require('chalk') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
const flatten = require('arr-flatten') |
|||
const dotenv = require('dotenv') |
|||
const { eraseLines } = require('ansi-escapes') |
|||
const { write: copy } = require('clipboardy') |
|||
const inquirer = require('inquirer') |
|||
|
|||
// Ours
|
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const { version } = require('../lib/pkg') |
|||
const Logger = require('../lib/build-logger') |
|||
const Now = require('../lib') |
|||
const toHumanPath = require('../lib/utils/to-human-path') |
|||
const { handleError, error } = require('../lib/error') |
|||
const { fromGit, isRepoPath, gitPathParts } = require('../lib/git') |
|||
const readMetaData = require('../lib/read-metadata') |
|||
const checkPath = require('../lib/utils/check-path') |
|||
const { reAlias, assignAlias } = require('../lib/re-alias') |
|||
const exit = require('../lib/utils/exit') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const cmd = require('../lib/utils/output/cmd') |
|||
const info = require('../lib/utils/output/info') |
|||
const wait = require('../lib/utils/output/wait') |
|||
const NowPlans = require('../lib/plans') |
|||
const promptBool = require('../lib/utils/input/prompt-bool') |
|||
const promptOptions = require('../lib/utils/input/prompt-options') |
|||
const note = require('../lib/utils/output/note') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token', 'name', 'alias', 'session-affinity'], |
|||
boolean: [ |
|||
'help', |
|||
'version', |
|||
'debug', |
|||
'force', |
|||
'links', |
|||
'login', |
|||
'no-clipboard', |
|||
'forward-npm', |
|||
'docker', |
|||
'npm', |
|||
'static' |
|||
], |
|||
alias: { |
|||
env: 'e', |
|||
dotenv: 'E', |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
version: 'v', |
|||
force: 'f', |
|||
token: 't', |
|||
forceSync: 'F', |
|||
links: 'l', |
|||
login: 'L', |
|||
public: 'p', |
|||
'no-clipboard': 'C', |
|||
'forward-npm': 'N', |
|||
'session-affinity': 'S', |
|||
name: 'n', |
|||
alias: 'a' |
|||
} |
|||
}) |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now`)} [options] <command | path> |
|||
|
|||
${chalk.dim('Commands:')} |
|||
|
|||
${chalk.dim('Cloud')} |
|||
|
|||
deploy [path] Performs a deployment ${chalk.bold( |
|||
'(default)' |
|||
)} |
|||
ls | list [app] List deployments |
|||
rm | remove [id] Remove a deployment |
|||
ln | alias [id] [url] Configures aliases for deployments |
|||
domains [name] Manages your domain names |
|||
certs [cmd] Manages your SSL certificates |
|||
secrets [name] Manages your secret environment variables |
|||
dns [name] Manages your DNS records |
|||
logs [url] Displays the logs for a deployment |
|||
scale [args] Scales the instance count of a deployment |
|||
help [cmd] Displays complete help for [cmd] |
|||
|
|||
${chalk.dim('Administrative')} |
|||
|
|||
billing | cc [cmd] Manages your credit cards and billing methods |
|||
upgrade | downgrade [plan] Upgrades or downgrades your plan |
|||
teams [team] Manages your teams |
|||
switch Switches between teams and your account |
|||
login Login into your account or creates a new one |
|||
logout Logout from your account |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-v, --version Output the version number |
|||
-n, --name Set the name of the deployment |
|||
-c ${chalk.underline('FILE')}, --config=${chalk.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-f, --force Force a new deployment even if nothing has changed |
|||
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
-L, --login Configure login |
|||
-l, --links Copy symlinks without resolving their target |
|||
-p, --public Deployment is public (${chalk.dim( |
|||
'`/_src`' |
|||
)} is exposed) [on for oss, off for premium] |
|||
-e, --env Include an env var (e.g.: ${chalk.dim( |
|||
'`-e KEY=value`' |
|||
)}). Can appear many times. |
|||
-E ${chalk.underline('FILE')}, --dotenv=${chalk.underline( |
|||
'FILE' |
|||
)} Include env vars from .env file. Defaults to '.env' |
|||
-C, --no-clipboard Do not attempt to copy URL to clipboard |
|||
-N, --forward-npm Forward login information to install private npm modules |
|||
--session-affinity Session affinity, \`ip\` (default) or \`random\` to control session affinity.
|
|||
|
|||
${chalk.dim( |
|||
'Enforcable Types (when both package.json and Dockerfile exist):' |
|||
)} |
|||
|
|||
--npm Node.js application |
|||
--docker Docker container |
|||
--static Static file hosting |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Deploys the current directory |
|||
|
|||
${chalk.cyan('$ now')} |
|||
|
|||
${chalk.gray('–')} Deploys a custom path ${chalk.dim('`/usr/src/project`')} |
|||
|
|||
${chalk.cyan('$ now /usr/src/project')} |
|||
|
|||
${chalk.gray('–')} Deploys a GitHub repository |
|||
|
|||
${chalk.cyan('$ now user/repo#ref')} |
|||
|
|||
${chalk.gray('–')} Deploys a GitHub, GitLab or Bitbucket repo using its URL |
|||
|
|||
${chalk.cyan('$ now https://gitlab.com/user/repo')} |
|||
|
|||
${chalk.gray('–')} Deploys with ENV vars |
|||
|
|||
${chalk.cyan( |
|||
'$ now -e NODE_ENV=production -e MYSQL_PASSWORD=@mysql-password' |
|||
)} |
|||
|
|||
${chalk.gray('–')} Displays comprehensive help for the subcommand ${chalk.dim( |
|||
'`list`' |
|||
)} |
|||
|
|||
${chalk.cyan('$ now help list')} |
|||
`)
|
|||
} |
|||
|
|||
let path = argv._[0] |
|||
|
|||
if (path) { |
|||
// If path is relative: resolve
|
|||
// if path is absolute: clear up strange `/` etc
|
|||
path = resolve(process.cwd(), path) |
|||
} else { |
|||
path = process.cwd() |
|||
} |
|||
|
|||
// If the current deployment is a repo
|
|||
const gitRepo = {} |
|||
|
|||
// Options
|
|||
const forceNew = argv.force |
|||
let deploymentName = argv.name |
|||
let sessionAffinity = argv['session-affinity'] |
|||
const debug = argv.debug |
|||
const clipboard = !argv['no-clipboard'] |
|||
const forwardNpm = argv['forward-npm'] |
|||
const forceSync = argv.forceSync |
|||
const shouldLogin = argv.login |
|||
const followSymlinks = !argv.links |
|||
const wantsPublic = argv.public |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
const isTTY = process.stdout.isTTY |
|||
const quiet = !isTTY |
|||
const autoAliases = |
|||
typeof argv.alias === 'undefined' ? false : flatten([argv.alias]) |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
if (Array.isArray(autoAliases)) { |
|||
console.log( |
|||
`${chalk.red('Deprecated!')} The option ${chalk.grey( |
|||
'--alias' |
|||
)} will be removed soon.` |
|||
) |
|||
console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n') |
|||
} |
|||
|
|||
const stopDeployment = msg => { |
|||
handleError(msg) |
|||
process.exit(1) |
|||
} |
|||
|
|||
const envFields = async list => { |
|||
const questions = [] |
|||
|
|||
for (const field of list) { |
|||
questions.push({ |
|||
name: field, |
|||
message: field |
|||
}) |
|||
} |
|||
|
|||
// eslint-disable-next-line import/no-unassigned-import
|
|||
require('../lib/utils/input/patch-inquirer') |
|||
|
|||
info('Please enter the values for the following environment variables:') |
|||
const answers = await inquirer.prompt(questions) |
|||
|
|||
for (const answer in answers) { |
|||
if (!{}.hasOwnProperty.call(answers, answer)) { |
|||
continue |
|||
} |
|||
|
|||
const content = answers[answer] |
|||
|
|||
if (content === '') { |
|||
stopDeployment(`Enter a value for ${answer}`) |
|||
} |
|||
} |
|||
|
|||
return answers |
|||
} |
|||
|
|||
let alwaysForwardNpm |
|||
|
|||
async function main() { |
|||
if (argv.h || argv.help) { |
|||
help() |
|||
return exit(0) |
|||
} else if (argv.v || argv.version) { |
|||
console.log(version) |
|||
return exit(0) |
|||
} |
|||
|
|||
let config |
|||
|
|||
try { |
|||
config = await cfg.read({ token: argv.token }) |
|||
} catch (err) { |
|||
if (shouldLogin && err.userError) { |
|||
// We ignore user errors here, which means for example
|
|||
// the token is mis-configured or revoked, if the user
|
|||
// is attempting to log in again
|
|||
config = {} |
|||
} else { |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
let token = argv.token || config.token |
|||
if (!token || shouldLogin) { |
|||
try { |
|||
token = await login(apiUrl) |
|||
config = await cfg.read() |
|||
} catch (err) { |
|||
return stopDeployment(`Authentication error – ${err.message}`) |
|||
} |
|||
|
|||
console.log( |
|||
`> Logged in successfully. Token saved to ${chalk.bold('~/.now.json')}.` |
|||
) |
|||
console.log( |
|||
`> Run ${cmd('now')} to deploy the current directory, or ${cmd( |
|||
'now --help' |
|||
)} for usage info.\n` |
|||
) |
|||
return exit(0) |
|||
} |
|||
|
|||
alwaysForwardNpm = config.forwardNpm |
|||
|
|||
// If we got to here then `token` should be set
|
|||
try { |
|||
await sync({ token, config }) |
|||
} catch (err) { |
|||
return stopDeployment(err) |
|||
} |
|||
} |
|||
|
|||
async function sync({ token, config: { currentTeam, user } }) { |
|||
const start = Date.now() |
|||
const rawPath = argv._[0] |
|||
|
|||
const planPromise = new NowPlans({ |
|||
apiUrl, |
|||
token, |
|||
debug, |
|||
currentTeam |
|||
}).getCurrent() |
|||
|
|||
try { |
|||
await fs.stat(path) |
|||
} catch (err) { |
|||
let repo |
|||
let isValidRepo = false |
|||
|
|||
try { |
|||
isValidRepo = isRepoPath(rawPath) |
|||
} catch (err) { |
|||
if (err.code === 'INVALID_URL') { |
|||
stopDeployment(err) |
|||
} else { |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
if (isValidRepo) { |
|||
const gitParts = gitPathParts(rawPath) |
|||
Object.assign(gitRepo, gitParts) |
|||
|
|||
const searchMessage = setTimeout(() => { |
|||
console.log(`> Didn't find directory. Searching on ${gitRepo.type}...`) |
|||
}, 500) |
|||
|
|||
try { |
|||
repo = await fromGit(rawPath, debug) |
|||
} catch (err) {} |
|||
|
|||
clearTimeout(searchMessage) |
|||
} |
|||
|
|||
if (repo) { |
|||
// Tell now which directory to deploy
|
|||
path = repo.path |
|||
|
|||
// Set global variable for deleting tmp dir later
|
|||
// once the deployment has finished
|
|||
Object.assign(gitRepo, repo) |
|||
} else if (isValidRepo) { |
|||
const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : '' |
|||
stopDeployment( |
|||
`There's no repository named "${chalk.bold( |
|||
gitRepo.main |
|||
)}" ${gitRef}on ${gitRepo.type}` |
|||
) |
|||
} else { |
|||
error(`The specified directory "${basename(path)}" doesn't exist.`) |
|||
process.exit(1) |
|||
} |
|||
} |
|||
|
|||
// Make sure that directory is deployable
|
|||
try { |
|||
await checkPath(path) |
|||
} catch (err) { |
|||
error(err) |
|||
return |
|||
} |
|||
|
|||
if (!quiet) { |
|||
if (gitRepo.main) { |
|||
const gitRef = gitRepo.ref ? ` at "${chalk.bold(gitRepo.ref)}" ` : '' |
|||
console.log( |
|||
`> Deploying ${gitRepo.type} repository "${chalk.bold( |
|||
gitRepo.main |
|||
)}" ${gitRef} under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
} else { |
|||
console.log( |
|||
`> Deploying ${chalk.bold(toHumanPath(path))} under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
} |
|||
} |
|||
|
|||
let deploymentType |
|||
|
|||
// CLI deployment type explicit overrides
|
|||
if (argv.docker) { |
|||
if (debug) { |
|||
console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``) |
|||
} |
|||
|
|||
deploymentType = 'docker' |
|||
} else if (argv.npm) { |
|||
if (debug) { |
|||
console.log(`> [debug] Forcing \`deploymentType\` = \`npm\``) |
|||
} |
|||
|
|||
deploymentType = 'npm' |
|||
} else if (argv.static) { |
|||
if (debug) { |
|||
console.log(`> [debug] Forcing \`deploymentType\` = \`static\``) |
|||
} |
|||
|
|||
deploymentType = 'static' |
|||
} |
|||
|
|||
let meta |
|||
;({ meta, deploymentName, deploymentType, sessionAffinity } = await readMeta( |
|||
path, |
|||
deploymentName, |
|||
deploymentType, |
|||
sessionAffinity |
|||
)) |
|||
const nowConfig = meta.nowConfig |
|||
|
|||
const now = new Now({ apiUrl, token, debug, currentTeam }) |
|||
|
|||
let dotenvConfig |
|||
let dotenvOption |
|||
|
|||
if (argv.dotenv) { |
|||
dotenvOption = argv.dotenv |
|||
} else if (nowConfig && nowConfig.dotenv) { |
|||
dotenvOption = nowConfig.dotenv |
|||
} |
|||
|
|||
if (dotenvOption) { |
|||
const dotenvFileName = |
|||
typeof dotenvOption === 'string' ? dotenvOption : '.env' |
|||
|
|||
if (!fs.existsSync(dotenvFileName)) { |
|||
error(`--dotenv flag is set but ${dotenvFileName} file is missing`) |
|||
return process.exit(1) |
|||
} |
|||
|
|||
const dotenvFile = await fs.readFile(dotenvFileName) |
|||
dotenvConfig = dotenv.parse(dotenvFile) |
|||
} |
|||
|
|||
let pkgEnv = nowConfig && nowConfig.env |
|||
const argEnv = [].concat(argv.env || []) |
|||
|
|||
if (pkgEnv && Array.isArray(nowConfig.env)) { |
|||
const defined = argEnv.join() |
|||
const askFor = nowConfig.env.filter(item => !defined.includes(`${item}=`)) |
|||
|
|||
pkgEnv = await envFields(askFor) |
|||
} |
|||
|
|||
// Merge `now.env` from package.json with `-e` arguments
|
|||
const envs = [ |
|||
...Object.keys(dotenvConfig || {}).map(k => `${k}=${dotenvConfig[k]}`), |
|||
...Object.keys(pkgEnv || {}).map(k => `${k}=${pkgEnv[k]}`), |
|||
...argEnv |
|||
] |
|||
|
|||
let secrets |
|||
const findSecret = async uidOrName => { |
|||
if (!secrets) { |
|||
secrets = await now.listSecrets() |
|||
} |
|||
|
|||
return secrets.filter(secret => { |
|||
return secret.name === uidOrName || secret.uid === uidOrName |
|||
}) |
|||
} |
|||
|
|||
const env_ = await Promise.all( |
|||
envs.map(async kv => { |
|||
if (typeof kv !== 'string') { |
|||
error('Env key and value missing') |
|||
return process.exit(1) |
|||
} |
|||
|
|||
const [key, ...rest] = kv.split('=') |
|||
let val |
|||
|
|||
if (rest.length > 0) { |
|||
val = rest.join('=') |
|||
} |
|||
|
|||
if (/[^A-z0-9_]/i.test(key)) { |
|||
error( |
|||
`Invalid ${chalk.dim('-e')} key ${chalk.bold( |
|||
`"${chalk.bold(key)}"` |
|||
)}. Only letters, digits and underscores are allowed.` |
|||
) |
|||
return process.exit(1) |
|||
} |
|||
|
|||
if (!key) { |
|||
error(`Invalid env option ${chalk.bold(`"${kv}"`)}`) |
|||
return process.exit(1) |
|||
} |
|||
|
|||
if (val === undefined) { |
|||
if (key in process.env) { |
|||
console.log( |
|||
`> Reading ${chalk.bold( |
|||
`"${chalk.bold(key)}"` |
|||
)} from your env (as no value was specified)` |
|||
) |
|||
// Escape value if it begins with @
|
|||
val = process.env[key].replace(/^@/, '\\@') |
|||
} else { |
|||
error( |
|||
`No value specified for env ${chalk.bold( |
|||
`"${chalk.bold(key)}"` |
|||
)} and it was not found in your env.` |
|||
) |
|||
return process.exit(1) |
|||
} |
|||
} |
|||
|
|||
if (val[0] === '@') { |
|||
const uidOrName = val.substr(1) |
|||
const secrets = await findSecret(uidOrName) |
|||
if (secrets.length === 0) { |
|||
if (uidOrName === '') { |
|||
error( |
|||
`Empty reference provided for env key ${chalk.bold( |
|||
`"${chalk.bold(key)}"` |
|||
)}` |
|||
) |
|||
} else { |
|||
error( |
|||
`No secret found by uid or name ${chalk.bold(`"${uidOrName}"`)}` |
|||
) |
|||
} |
|||
return process.exit(1) |
|||
} else if (secrets.length > 1) { |
|||
error( |
|||
`Ambiguous secret ${chalk.bold( |
|||
`"${uidOrName}"` |
|||
)} (matches ${chalk.bold(secrets.length)} secrets)` |
|||
) |
|||
return process.exit(1) |
|||
} |
|||
|
|||
val = { uid: secrets[0].uid } |
|||
} |
|||
|
|||
return [key, typeof val === 'string' ? val.replace(/^\\@/, '@') : val] |
|||
}) |
|||
) |
|||
|
|||
const env = {} |
|||
env_.filter(v => Boolean(v)).forEach(([key, val]) => { |
|||
if (key in env) { |
|||
note(`Overriding duplicate env key ${chalk.bold(`"${key}"`)}`) |
|||
} |
|||
|
|||
env[key] = val |
|||
}) |
|||
|
|||
try { |
|||
await now.create( |
|||
path, |
|||
Object.assign( |
|||
{ |
|||
env, |
|||
followSymlinks, |
|||
forceNew, |
|||
forceSync, |
|||
forwardNpm: alwaysForwardNpm || forwardNpm, |
|||
quiet, |
|||
wantsPublic, |
|||
sessionAffinity |
|||
}, |
|||
meta |
|||
) |
|||
) |
|||
} catch (err) { |
|||
if (debug) { |
|||
console.log(`> [debug] error: ${err}\n${err.stack}`) |
|||
} |
|||
|
|||
return stopDeployment(err) |
|||
} |
|||
|
|||
const { url } = now |
|||
const elapsed = ms(new Date() - start) |
|||
|
|||
if (isTTY) { |
|||
if (clipboard) { |
|||
try { |
|||
await copy(url) |
|||
console.log( |
|||
`${chalk.cyan('> Ready!')} ${chalk.bold( |
|||
url |
|||
)} (copied to clipboard) [${elapsed}]` |
|||
) |
|||
} catch (err) { |
|||
console.log(`${chalk.cyan('> Ready!')} ${chalk.bold(url)} [${elapsed}]`) |
|||
} |
|||
} else { |
|||
console.log(`> ${url} [${elapsed}]`) |
|||
} |
|||
} else { |
|||
process.stdout.write(url) |
|||
} |
|||
|
|||
const startU = new Date() |
|||
|
|||
const complete = ({ syncCount }) => { |
|||
if (!quiet) { |
|||
const elapsedU = ms(new Date() - startU) |
|||
console.log( |
|||
`> Synced ${syncCount} (${bytes(now.syncAmount)}) [${elapsedU}] ` |
|||
) |
|||
console.log('> Initializing…') |
|||
} |
|||
|
|||
// Close http2 agent
|
|||
now.close() |
|||
|
|||
// Show build logs
|
|||
if (!quiet) { |
|||
if (deploymentType === 'static') { |
|||
console.log(`${chalk.cyan('> Deployment complete!')}`) |
|||
} else { |
|||
printLogs(now.host, token, currentTeam, user) |
|||
} |
|||
} |
|||
} |
|||
|
|||
const plan = await planPromise |
|||
|
|||
if (plan.id === 'oss' && !wantsPublic) { |
|||
if (isTTY) { |
|||
info( |
|||
`${chalk.bold( |
|||
(currentTeam && `${currentTeam.slug} is`) || |
|||
`You (${user.username || user.email}) are` |
|||
)} on the OSS plan. Your code and logs will be made ${chalk.bold( |
|||
'public' |
|||
)}.` |
|||
) |
|||
|
|||
const proceed = await promptBool( |
|||
'Are you sure you want to proceed with the deployment?', |
|||
{ trailing: eraseLines(1) } |
|||
) |
|||
|
|||
if (proceed) { |
|||
note(`You can use ${cmd('now --public')} to skip this prompt`) |
|||
} else { |
|||
const stopSpinner = wait('Canceling deployment') |
|||
await now.remove(now.id, { hard: true }) |
|||
stopSpinner() |
|||
info('Deployment aborted. No files were synced.') |
|||
info(`You can upgrade by running ${cmd('now upgrade')}.`) |
|||
return exit() |
|||
} |
|||
} else if (!wantsPublic) { |
|||
const msg = |
|||
'\nYou are on the OSS plan. Your code and logs will be made public.' + |
|||
' If you agree with that, please run again with --public.' |
|||
return stopDeployment(msg) |
|||
} |
|||
} |
|||
|
|||
if (now.syncAmount) { |
|||
if (debug && now.syncFileCount !== now.fileCount) { |
|||
console.log( |
|||
`> [debug] total files ${now.fileCount}, ${now.syncFileCount} changed. ` |
|||
) |
|||
} |
|||
const size = bytes(now.syncAmount) |
|||
const syncCount = `${now.syncFileCount} file${now.syncFileCount > 1 |
|||
? 's' |
|||
: ''}` |
|||
const bar = new Progress( |
|||
`> Upload [:bar] :percent :etas (${size}) [${syncCount}]`, |
|||
{ |
|||
width: 20, |
|||
complete: '=', |
|||
incomplete: '', |
|||
total: now.syncAmount, |
|||
clear: true |
|||
} |
|||
) |
|||
|
|||
now.upload() |
|||
|
|||
now.on('upload', ({ names, data }) => { |
|||
const amount = data.length |
|||
if (debug) { |
|||
console.log( |
|||
`> [debug] Uploaded: ${names.join(' ')} (${bytes(data.length)})` |
|||
) |
|||
} |
|||
bar.tick(amount) |
|||
}) |
|||
|
|||
now.on('complete', () => complete({ syncCount })) |
|||
|
|||
now.on('error', err => { |
|||
error('Upload failed') |
|||
return stopDeployment(err) |
|||
}) |
|||
} else { |
|||
if (!quiet) { |
|||
console.log(`> Initializing…`) |
|||
} |
|||
|
|||
// Close http2 agent
|
|||
now.close() |
|||
|
|||
// Show build logs
|
|||
if (deploymentType === 'static' && !quiet) { |
|||
console.log(`${chalk.cyan('> Deployment complete!')}`) |
|||
} else { |
|||
printLogs(now.host, token, currentTeam, user) |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function readMeta(path, deploymentName, deploymentType, sessionAffinity) { |
|||
try { |
|||
const meta = await readMetaData(path, { |
|||
deploymentType, |
|||
deploymentName, |
|||
quiet: true, |
|||
sessionAffinity |
|||
}) |
|||
|
|||
if (!deploymentType) { |
|||
deploymentType = meta.type |
|||
|
|||
if (debug) { |
|||
console.log( |
|||
`> [debug] Detected \`deploymentType\` = \`${deploymentType}\`` |
|||
) |
|||
} |
|||
} |
|||
|
|||
if (!deploymentName) { |
|||
deploymentName = meta.name |
|||
|
|||
if (debug) { |
|||
console.log( |
|||
`> [debug] Detected \`deploymentName\` = "${deploymentName}"` |
|||
) |
|||
} |
|||
} |
|||
|
|||
return { |
|||
meta, |
|||
deploymentName, |
|||
deploymentType, |
|||
sessionAffinity |
|||
} |
|||
} catch (err) { |
|||
if (isTTY && err.code === 'MULTIPLE_MANIFESTS') { |
|||
if (debug) { |
|||
console.log('> [debug] Multiple manifests found, disambiguating') |
|||
} |
|||
|
|||
console.log( |
|||
`> Two manifests found. Press [${chalk.bold( |
|||
'n' |
|||
)}] to deploy or re-run with --flag` |
|||
) |
|||
|
|||
deploymentType = await promptOptions([ |
|||
['npm', `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} `], |
|||
['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `] |
|||
]) |
|||
|
|||
if (debug) { |
|||
console.log( |
|||
`> [debug] Selected \`deploymentType\` = "${deploymentType}"` |
|||
) |
|||
} |
|||
|
|||
return readMeta(path, deploymentName, deploymentType) |
|||
} |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
function printLogs(host, token, currentTeam, user) { |
|||
// Log build
|
|||
const logger = new Logger(host, token, { debug, quiet }) |
|||
|
|||
logger.on('error', async err => { |
|||
if (!quiet) { |
|||
if (err && err.type === 'BUILD_ERROR') { |
|||
error( |
|||
`The build step of your project failed. To retry, run ${cmd( |
|||
'now --force' |
|||
)}.` |
|||
) |
|||
} else { |
|||
error('Deployment failed') |
|||
} |
|||
} |
|||
|
|||
if (gitRepo && gitRepo.cleanup) { |
|||
// Delete temporary directory that contains repository
|
|||
gitRepo.cleanup() |
|||
|
|||
if (debug) { |
|||
console.log(`> [debug] Removed temporary repo directory`) |
|||
} |
|||
} |
|||
|
|||
process.exit(1) |
|||
}) |
|||
|
|||
logger.on('close', async () => { |
|||
if (Array.isArray(autoAliases)) { |
|||
const aliasList = autoAliases.filter(item => item !== '') |
|||
|
|||
if (aliasList.length > 0) { |
|||
const assignments = [] |
|||
|
|||
for (const alias of aliasList) { |
|||
assignments.push( |
|||
assignAlias(alias, token, host, apiUrl, debug, currentTeam, user) |
|||
) |
|||
} |
|||
|
|||
await Promise.all(assignments) |
|||
} else { |
|||
await reAlias( |
|||
token, |
|||
host, |
|||
null, |
|||
help, |
|||
exit, |
|||
apiUrl, |
|||
debug, |
|||
currentTeam, |
|||
user |
|||
) |
|||
} |
|||
} |
|||
|
|||
if (!quiet) { |
|||
console.log(`${chalk.cyan('> Deployment complete!')}`) |
|||
} |
|||
|
|||
if (gitRepo && gitRepo.cleanup) { |
|||
// Delete temporary directory that contains repository
|
|||
gitRepo.cleanup() |
|||
|
|||
if (debug) { |
|||
console.log(`> [debug] Removed temporary repo directory`) |
|||
} |
|||
} |
|||
|
|||
process.exit(0) |
|||
}) |
|||
} |
|||
|
|||
main().catch(err => { |
|||
handleError(err, { debug }) |
|||
process.exit(1) |
|||
}) |
@ -1,343 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
const table = require('text-table') |
|||
|
|||
// Ours
|
|||
const cfg = require('../lib/cfg') |
|||
const DomainRecords = require('../lib/domain-records') |
|||
const indent = require('../lib/indent') |
|||
const login = require('../lib/login') |
|||
const strlen = require('../lib/strlen') |
|||
const { handleError, error } = require('../lib/error') |
|||
const exit = require('../lib/utils/exit') |
|||
const logo = require('../lib/utils/output/logo') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config'], |
|||
boolean: ['help', 'debug'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const subcommand = argv._[0] |
|||
|
|||
// Options
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now dns ls`)} [domain] |
|||
${chalk.bold( |
|||
`${logo} now dns add` |
|||
)} <domain> <name> <A | AAAA | ALIAS | CNAME | TXT> <value> |
|||
${chalk.bold(`${logo} now dns add`)} <domain> <name> MX <value> <mx_priority> |
|||
${chalk.bold( |
|||
`${logo} now dns add` |
|||
)} <domain> <name> SRV <priority> <weight> <port> <target> |
|||
${chalk.bold(`${logo} now dns rm`)} <id> |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} config file |
|||
-d, --debug debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} List all your DNS records |
|||
|
|||
${chalk.cyan('$ now dns ls')} |
|||
|
|||
${chalk.gray('–')} Add an A record for a subdomain |
|||
|
|||
${chalk.cyan( |
|||
'$ now dns add <YOUR DOMAIN> <SUBDOMAIN NAME> A <RECORD VALUE>' |
|||
)} |
|||
${chalk.cyan('$ now dns add zeit.rocks api A 198.51.100.100')} |
|||
|
|||
${chalk.gray('–')} Add an MX record (@ as a name refers to the domain) |
|||
|
|||
${chalk.cyan( |
|||
'$ now dns add <YOUR DOMAIN> @ MX <RECORD VALUE> <PRIORITY>' |
|||
)} |
|||
${chalk.cyan('$ now dns add zeit.rocks @ MX mail.zeit.rocks 10')} |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
if (argv.help || !subcommand) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
|
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
handleError(err) |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam, user } }) { |
|||
const domainRecords = new DomainRecords({ apiUrl, token, debug, currentTeam }) |
|||
const args = argv._.slice(1) |
|||
const start = Date.now() |
|||
|
|||
if (subcommand === 'ls' || subcommand === 'list') { |
|||
if (args.length > 1) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now dns ls [domain]`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const elapsed = ms(new Date() - start) |
|||
const res = await domainRecords.ls(args[0]) |
|||
const text = [] |
|||
let count = 0 |
|||
res.forEach((records, domain) => { |
|||
count += records.length |
|||
if (records.length > 0) { |
|||
const cur = Date.now() |
|||
const header = [ |
|||
['', 'id', 'name', 'type', 'value', 'aux', 'created'].map(s => |
|||
chalk.dim(s) |
|||
) |
|||
] |
|||
const out = table( |
|||
header.concat( |
|||
records.map(record => { |
|||
const time = chalk.gray( |
|||
ms(cur - new Date(Number(record.created))) + ' ago' |
|||
) |
|||
const aux = (() => { |
|||
if (record.mxPriority !== undefined) return record.mxPriority |
|||
if (record.priority !== undefined) return record.priority |
|||
return '' |
|||
})() |
|||
return [ |
|||
'', |
|||
record.id, |
|||
record.name, |
|||
record.type, |
|||
record.value, |
|||
aux, |
|||
time |
|||
] |
|||
}) |
|||
), |
|||
{ |
|||
align: ['l', 'r', 'l', 'l', 'l', 'l'], |
|||
hsep: ' '.repeat(2), |
|||
stringLength: strlen |
|||
} |
|||
) |
|||
text.push(`\n\n${chalk.bold(domain)}\n${indent(out, 2)}`) |
|||
} |
|||
}) |
|||
console.log( |
|||
`> ${count} record${count === 1 ? '' : 's'} found ${chalk.gray( |
|||
`[${elapsed}]` |
|||
)} under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}` |
|||
) |
|||
console.log(text.join('')) |
|||
} else if (subcommand === 'add') { |
|||
const param = parseAddArgs(args) |
|||
if (!param) { |
|||
error( |
|||
`Invalid number of arguments. See: ${chalk.cyan( |
|||
'`now dns --help`' |
|||
)} for usage.` |
|||
) |
|||
return exit(1) |
|||
} |
|||
const record = await domainRecords.create(param.domain, param.data) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} A new DNS record for domain ${chalk.bold( |
|||
param.domain |
|||
)} ${chalk.gray(`(${record.uid})`)} created ${chalk.gray( |
|||
`[${elapsed}]` |
|||
)} (${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)})` |
|||
) |
|||
} else if (subcommand === 'rm' || subcommand === 'remove') { |
|||
if (args.length !== 1) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan('`now dns rm <id>`')}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const record = await domainRecords.getRecord(args[0]) |
|||
if (!record) { |
|||
error('DNS record not found') |
|||
return exit(1) |
|||
} |
|||
|
|||
const yes = await readConfirmation( |
|||
record, |
|||
'The following record will be removed permanently \n' |
|||
) |
|||
if (!yes) { |
|||
error('User abort') |
|||
return exit(0) |
|||
} |
|||
|
|||
await domainRecords.delete(record.domain, record.id) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Record ${chalk.gray( |
|||
`${record.id}` |
|||
)} removed ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
} else { |
|||
error('Please specify a valid subcommand: ls | add | rm') |
|||
help() |
|||
exit(1) |
|||
} |
|||
return domainRecords.close() |
|||
} |
|||
|
|||
process.on('uncaughtException', err => { |
|||
handleError(err) |
|||
exit(1) |
|||
}) |
|||
|
|||
function parseAddArgs(args) { |
|||
if (!args || args.length < 4) { |
|||
return null |
|||
} |
|||
|
|||
const domain = args[0] |
|||
const name = args[1] === '@' ? '' : args[1].toString() |
|||
const type = args[2] |
|||
const value = args[3] |
|||
|
|||
if (!(domain && typeof name === 'string' && type)) { |
|||
return null |
|||
} |
|||
|
|||
if (type === 'MX') { |
|||
if (args.length !== 5) { |
|||
return null |
|||
} |
|||
|
|||
return { |
|||
domain, |
|||
data: { |
|||
name, |
|||
type, |
|||
value, |
|||
mxPriority: args[4] |
|||
} |
|||
} |
|||
} else if (type === 'SRV') { |
|||
if (args.length !== 7) { |
|||
return null |
|||
} |
|||
|
|||
return { |
|||
domain, |
|||
data: { |
|||
name, |
|||
type, |
|||
srv: { |
|||
priority: value, |
|||
weight: args[4], |
|||
port: args[5], |
|||
target: args[6] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (args.length !== 4) { |
|||
return null |
|||
} |
|||
|
|||
return { |
|||
domain, |
|||
data: { |
|||
name, |
|||
type, |
|||
value |
|||
} |
|||
} |
|||
} |
|||
|
|||
function readConfirmation(record, msg) { |
|||
return new Promise(resolve => { |
|||
const time = chalk.gray( |
|||
ms(new Date() - new Date(Number(record.created))) + ' ago' |
|||
) |
|||
const tbl = table( |
|||
[ |
|||
[ |
|||
record.id, |
|||
chalk.bold( |
|||
`${record.name.length > 0 |
|||
? record.name + '.' |
|||
: ''}${record.domain} ${record.type} ${record.value} ${record.mxPriority |
|||
? record.mxPriority |
|||
: ''}` |
|||
), |
|||
time |
|||
] |
|||
], |
|||
{ align: ['l', 'r', 'l'], hsep: ' '.repeat(6) } |
|||
) |
|||
|
|||
process.stdout.write(`> ${msg}`) |
|||
process.stdout.write(' ' + tbl + '\n') |
|||
|
|||
process.stdout.write( |
|||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}` |
|||
) |
|||
|
|||
process.stdin |
|||
.on('data', d => { |
|||
process.stdin.pause() |
|||
resolve(d.toString().trim().toLowerCase() === 'y') |
|||
}) |
|||
.resume() |
|||
}) |
|||
} |
@ -1,423 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Native
|
|||
const { resolve } = require('path') |
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
const psl = require('psl') |
|||
const table = require('text-table') |
|||
|
|||
// Ours
|
|||
const NowDomains = require('../lib/domains') |
|||
const cfg = require('../lib/cfg') |
|||
const exit = require('../lib/utils/exit') |
|||
const login = require('../lib/login') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const promptBool = require('../lib/utils/input/prompt-bool') |
|||
const strlen = require('../lib/strlen') |
|||
const toHost = require('../lib/to-host') |
|||
const { handleError, error } = require('../lib/error') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['coupon', 'token'], |
|||
boolean: ['help', 'debug', 'external', 'force'], |
|||
alias: { |
|||
help: 'h', |
|||
coupon: 'c', |
|||
debug: 'd', |
|||
external: 'e', |
|||
force: 'f', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const subcommand = argv._[0] |
|||
|
|||
// Options
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now domains`)} <ls | add | rm | buy> <domain> |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-d, --debug Debug mode [off] |
|||
-e, --external Use external DNS server |
|||
-f, --force Skip DNS verification |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Lists all your domains: |
|||
|
|||
${chalk.cyan('$ now domains ls')} |
|||
|
|||
${chalk.gray('–')} Buy a new domain: |
|||
|
|||
${chalk.cyan(`$ now domains buy ${chalk.underline('domain-name.com')}`)} |
|||
|
|||
${chalk.gray('–')} Adds a domain name: |
|||
|
|||
${chalk.cyan(`$ now domains add ${chalk.underline('domain-name.com')}`)} |
|||
|
|||
Make sure the domain's DNS nameservers are at least 2 of these: |
|||
|
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'california.zeit.world' |
|||
)} ${chalk.dim('173.255.215.107')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'london.zeit.world' |
|||
)} ${chalk.dim('178.62.47.76')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'newark.zeit.world' |
|||
)} ${chalk.dim('173.255.231.87')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'amsterdam.zeit.world' |
|||
)} ${chalk.dim('188.226.197.55')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'dallas.zeit.world' |
|||
)} ${chalk.dim('173.192.101.194')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'paris.zeit.world' |
|||
)} ${chalk.dim('37.123.115.172')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'singapore.zeit.world' |
|||
)} ${chalk.dim('119.81.97.170')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'sydney.zeit.world' |
|||
)} ${chalk.dim('52.64.171.200')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'frankfurt.zeit.world' |
|||
)} ${chalk.dim('91.109.245.139')} |
|||
${chalk.gray('–')} ${chalk.underline( |
|||
'iowa.zeit.world' |
|||
)} ${chalk.dim('23.236.59.22')} |
|||
|
|||
${chalk.yellow('NOTE:')} running ${chalk.dim( |
|||
'`now alias`' |
|||
)} will automatically register your domain |
|||
if it's configured with these nameservers (no need to ${chalk.dim( |
|||
'`domain add`' |
|||
)}). |
|||
|
|||
For more details head to ${chalk.underline('https://zeit.world')}. |
|||
|
|||
${chalk.gray('–')} Removing a domain: |
|||
|
|||
${chalk.cyan('$ now domain rm my-app.com')} |
|||
|
|||
or |
|||
|
|||
${chalk.cyan('$ now domain rm domainId')} |
|||
|
|||
To get the list of domain ids, use ${chalk.dim('`now domains ls`')}. |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} Adding and verifying a domain name using zeit.world nameservers: |
|||
|
|||
${chalk.cyan('$ now domain add my-app.com')} |
|||
|
|||
The command will tell you if the domain was verified succesfully. In case the domain was not verified succesfully you should retry adding the domain after some time. |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} Adding and verifying a domain name using an external nameserver: |
|||
|
|||
${chalk.cyan('$ now domain add -e my-app.com')} |
|||
|
|||
and follow the verification instructions if requested. Finally, rerun the same command after completing the verification step. |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.help || !subcommand) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
if (err.userError) { |
|||
error(err.message) |
|||
} else { |
|||
error(`Unknown error: ${err}\n${err.stack}`) |
|||
} |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam, user } }) { |
|||
const domain = new NowDomains({ apiUrl, token, debug, currentTeam }) |
|||
const args = argv._.slice(1) |
|||
|
|||
switch (subcommand) { |
|||
case 'ls': |
|||
case 'list': { |
|||
if (args.length !== 0) { |
|||
error('Invalid number of arguments') |
|||
return exit(1) |
|||
} |
|||
|
|||
const start_ = new Date() |
|||
const domains = await domain.ls() |
|||
domains.sort((a, b) => new Date(b.created) - new Date(a.created)) |
|||
const current = new Date() |
|||
const header = [ |
|||
['', 'domain', 'dns', 'verified', 'created'].map(s => chalk.dim(s)) |
|||
] |
|||
const out = |
|||
domains.length === 0 |
|||
? null |
|||
: table( |
|||
header.concat( |
|||
domains.map(domain => { |
|||
const ns = domain.isExternal ? 'external' : 'zeit.world' |
|||
const url = chalk.bold(domain.name) |
|||
const time = chalk.gray( |
|||
ms(current - new Date(domain.created)) + ' ago' |
|||
) |
|||
return ['', url, ns, domain.verified, time] |
|||
}) |
|||
), |
|||
{ |
|||
align: ['l', 'l', 'l', 'l', 'l'], |
|||
hsep: ' '.repeat(2), |
|||
stringLength: strlen |
|||
} |
|||
) |
|||
|
|||
const elapsed_ = ms(new Date() - start_) |
|||
console.log( |
|||
`> ${domains.length} domain${domains.length === 1 |
|||
? '' |
|||
: 's'} found under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)} ${chalk.gray(`[${elapsed_}]`)}` |
|||
) |
|||
|
|||
if (out) { |
|||
console.log('\n' + out + '\n') |
|||
} |
|||
|
|||
break |
|||
} |
|||
case 'rm': |
|||
case 'remove': { |
|||
if (args.length !== 1) { |
|||
error('Invalid number of arguments') |
|||
return exit(1) |
|||
} |
|||
|
|||
const _target = String(args[0]) |
|||
if (!_target) { |
|||
const err = new Error('No domain specified') |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
const _domains = await domain.ls() |
|||
const _domain = findDomain(_target, _domains) |
|||
|
|||
if (!_domain) { |
|||
const err = new Error( |
|||
`Domain not found by "${_target}". Run ${chalk.dim( |
|||
'`now domains ls`' |
|||
)} to see your domains.` |
|||
) |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
try { |
|||
const confirmation = (await readConfirmation( |
|||
domain, |
|||
_domain |
|||
)).toLowerCase() |
|||
if (confirmation !== 'y' && confirmation !== 'yes') { |
|||
console.log('\n> Aborted') |
|||
process.exit(0) |
|||
} |
|||
|
|||
const start = new Date() |
|||
await domain.rm(_domain) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Domain ${chalk.bold( |
|||
_domain.uid |
|||
)} removed [${elapsed}]` |
|||
) |
|||
} catch (err) { |
|||
error(err) |
|||
exit(1) |
|||
} |
|||
break |
|||
} |
|||
case 'add': |
|||
case 'set': { |
|||
if (args.length !== 1) { |
|||
error('Invalid number of arguments') |
|||
return exit(1) |
|||
} |
|||
const name = String(args[0]) |
|||
|
|||
const parsedDomain = psl.parse(name) |
|||
if (parsedDomain.subdomain) { |
|||
const msg = |
|||
`You are adding "${name}" as a domain name which seems to contain a subdomain part "${parsedDomain.subdomain}".\n` + |
|||
' This is probably wrong unless you really know what you are doing.\n' + |
|||
` To add the root domain instead please run: ${chalk.cyan( |
|||
'now domain add ' + |
|||
(argv.external ? '-e ' : '') + |
|||
parsedDomain.domain |
|||
)}\n` +
|
|||
` Continue adding "${name}" as a domain name?` |
|||
if (!await promptBool(msg)) { |
|||
return exit(1) |
|||
} |
|||
} |
|||
|
|||
const start = new Date() |
|||
const { uid, code, created, verified } = await domain.add( |
|||
name, |
|||
argv.force, |
|||
argv.external |
|||
) |
|||
const elapsed = ms(new Date() - start) |
|||
if (created) { |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Domain ${chalk.bold( |
|||
chalk.underline(name) |
|||
)} ${chalk.dim(`(${uid})`)} added [${elapsed}]` |
|||
) |
|||
} else if (verified) { |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Domain ${chalk.bold( |
|||
chalk.underline(name) |
|||
)} ${chalk.dim(`(${uid})`)} verified [${elapsed}]` |
|||
) |
|||
} else if (code === 'not_modified') { |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Domain ${chalk.bold( |
|||
chalk.underline(name) |
|||
)} ${chalk.dim(`(${uid})`)} already exists [${elapsed}]` |
|||
) |
|||
} else { |
|||
console.log( |
|||
'> Verification required: Please rerun this command after some time' |
|||
) |
|||
} |
|||
break |
|||
} |
|||
case 'buy': { |
|||
await require(resolve(__dirname, 'domains', 'buy.js'))({ |
|||
domains: domain, |
|||
args, |
|||
currentTeam, |
|||
user, |
|||
coupon: argv.coupon |
|||
}) |
|||
break |
|||
} |
|||
default: |
|||
error('Please specify a valid subcommand: ls | add | rm') |
|||
help() |
|||
exit(1) |
|||
} |
|||
|
|||
domain.close() |
|||
} |
|||
|
|||
async function readConfirmation(domain, _domain) { |
|||
return new Promise(resolve => { |
|||
const time = chalk.gray(ms(new Date() - new Date(_domain.created)) + ' ago') |
|||
const tbl = table([[chalk.underline(`https://${_domain.name}`), time]], { |
|||
align: ['r', 'l'], |
|||
hsep: ' '.repeat(6) |
|||
}) |
|||
|
|||
process.stdout.write('> The following domain will be removed permanently\n') |
|||
process.stdout.write(' ' + tbl + '\n') |
|||
|
|||
if (_domain.aliases.length > 0) { |
|||
process.stdout.write( |
|||
`> ${chalk.yellow('Warning!')} This domain's ` + |
|||
`${chalk.bold( |
|||
_domain.aliases.length + |
|||
' alias' + |
|||
(_domain.aliases.length === 1 ? '' : 'es') |
|||
)} ` +
|
|||
`will be removed. Run ${chalk.dim('`now alias ls`')} to list them.\n` |
|||
) |
|||
} |
|||
if (_domain.certs.length > 0) { |
|||
process.stdout.write( |
|||
`> ${chalk.yellow('Warning!')} This domain's ` + |
|||
`${chalk.bold( |
|||
_domain.certs.length + |
|||
' certificate' + |
|||
(_domain.certs.length === 1 ? '' : 's') |
|||
)} ` +
|
|||
`will be removed. Run ${chalk.dim('`now cert ls`')} to list them.\n` |
|||
) |
|||
} |
|||
|
|||
process.stdout.write( |
|||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}` |
|||
) |
|||
|
|||
process.stdin |
|||
.on('data', d => { |
|||
process.stdin.pause() |
|||
resolve(d.toString().trim()) |
|||
}) |
|||
.resume() |
|||
}) |
|||
} |
|||
|
|||
function findDomain(val, list) { |
|||
return list.find(d => { |
|||
if (d.uid === val) { |
|||
if (debug) { |
|||
console.log(`> [debug] matched domain ${d.uid} by uid`) |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
// Match prefix
|
|||
if (d.name === toHost(val)) { |
|||
if (debug) { |
|||
console.log(`> [debug] matched domain ${d.uid} by name ${d.name}`) |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
return false |
|||
}) |
|||
} |
@ -1,247 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const minimist = require('minimist') |
|||
const chalk = require('chalk') |
|||
const ms = require('ms') |
|||
const printf = require('printf') |
|||
require('epipebomb')() |
|||
const supportsColor = require('supports-color') |
|||
|
|||
// Ours
|
|||
const Now = require('../lib') |
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const sort = require('../lib/sort-deployments') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug', 'all'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now list`)} [app] |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} List all deployments |
|||
|
|||
${chalk.cyan('$ now ls')} |
|||
|
|||
${chalk.gray('–')} List all deployments for the app ${chalk.dim('`my-app`')} |
|||
|
|||
${chalk.cyan('$ now ls my-app')} |
|||
|
|||
${chalk.dim('Alias:')} ls |
|||
`)
|
|||
} |
|||
|
|||
if (argv.help) { |
|||
help() |
|||
process.exit(0) |
|||
} |
|||
|
|||
const app = argv._[0] |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
process.exit(1) |
|||
} |
|||
|
|||
if (!config.token) { |
|||
console.log( |
|||
`> Logged in successfully. Token saved to ${chalk.bold('~/.now.json')}.` |
|||
) |
|||
process.exit(0) |
|||
} |
|||
|
|||
try { |
|||
await list({ token, config }) |
|||
} catch (err) { |
|||
error(`Unknown error: ${err}\n${err.stack}`) |
|||
process.exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
|
|||
async function list({ token, config: { currentTeam, user } }) { |
|||
const now = new Now({ apiUrl, token, debug, currentTeam }) |
|||
const start = new Date() |
|||
|
|||
if (argv.all && !app) { |
|||
console.log('> You must define an app when using `--all`') |
|||
process.exit(1) |
|||
} |
|||
let deployments |
|||
try { |
|||
deployments = await now.list(app) |
|||
} catch (err) { |
|||
handleError(err) |
|||
process.exit(1) |
|||
} |
|||
|
|||
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) { |
|||
const match = await now.findDeployment(app) |
|||
if (match !== null && typeof match !== 'undefined') { |
|||
deployments = Array.of(match) |
|||
} |
|||
} |
|||
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) { |
|||
const aliases = await now.listAliases() |
|||
|
|||
const item = aliases.find(e => e.uid === app || e.alias === app) |
|||
if (item) { |
|||
const match = await now.findDeployment(item.deploymentId) |
|||
if (match !== null && typeof match !== 'undefined') { |
|||
deployments = Array.of(match) |
|||
} |
|||
} |
|||
} |
|||
|
|||
now.close() |
|||
|
|||
const apps = new Map() |
|||
|
|||
if (argv.all) { |
|||
await Promise.all( |
|||
deployments.map(async ({ uid }, i) => { |
|||
deployments[i].instances = await now.listInstances(uid) |
|||
}) |
|||
) |
|||
} |
|||
|
|||
for (const dep of deployments) { |
|||
const deps = apps.get(dep.name) || [] |
|||
apps.set(dep.name, deps.concat(dep)) |
|||
} |
|||
|
|||
const sorted = await sort([...apps]) |
|||
|
|||
const urlLength = |
|||
deployments.reduce((acc, i) => { |
|||
return Math.max(acc, (i.url && i.url.length) || 0) |
|||
}, 0) + 5 |
|||
const timeNow = new Date() |
|||
console.log( |
|||
`> ${deployments.length} deployment${deployments.length === 1 |
|||
? '' |
|||
: 's'} found under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)} ${chalk.grey('[' + ms(timeNow - start) + ']')}` |
|||
) |
|||
|
|||
let shouldShowAllInfo = false |
|||
for (const app of apps) { |
|||
shouldShowAllInfo = |
|||
app[1].length > 5 || |
|||
app.find(depl => { |
|||
return depl.scale && depl.scale.current > 1 |
|||
}) |
|||
if (shouldShowAllInfo) { |
|||
break |
|||
} |
|||
} |
|||
if (!argv.all && shouldShowAllInfo) { |
|||
console.log( |
|||
`> To expand the list and see instances run ${chalk.cyan( |
|||
'`now ls --all [app]`' |
|||
)}` |
|||
) |
|||
} |
|||
console.log() |
|||
sorted.forEach(([name, deps]) => { |
|||
const listedDeployments = argv.all ? deps : deps.slice(0, 5) |
|||
console.log( |
|||
`${chalk.bold(name)} ${chalk.gray( |
|||
'(' + listedDeployments.length + ' of ' + deps.length + ' total)' |
|||
)}` |
|||
) |
|||
const urlSpec = `%-${urlLength}s` |
|||
console.log( |
|||
printf( |
|||
` ${chalk.grey(urlSpec + ' %8s %-16s %8s')}`, |
|||
'url', |
|||
'inst #', |
|||
'state', |
|||
'age' |
|||
) |
|||
) |
|||
listedDeployments.forEach(dep => { |
|||
let state = dep.state |
|||
let extraSpaceForState = 0 |
|||
if (state === null || typeof state === 'undefined') { |
|||
state = 'DEPLOYMENT_ERROR' |
|||
} |
|||
if (/ERROR/.test(state)) { |
|||
state = chalk.red(state) |
|||
extraSpaceForState = 10 |
|||
} else if (state === 'FROZEN') { |
|||
state = chalk.grey(state) |
|||
extraSpaceForState = 10 |
|||
} |
|||
let spec |
|||
if (supportsColor) { |
|||
spec = ` %-${urlLength + 10}s %8s %-${extraSpaceForState + 16}s %8s` |
|||
} else { |
|||
spec = ` %-${urlLength + 1}s %8s %-${16}s %8s` |
|||
} |
|||
|
|||
console.log( |
|||
printf( |
|||
spec, |
|||
chalk.underline(dep.url), |
|||
dep.scale ? dep.scale.current : '✖', |
|||
state, |
|||
ms(timeNow - dep.created) |
|||
) |
|||
) |
|||
if (Array.isArray(dep.instances) && dep.instances.length > 0) { |
|||
dep.instances.forEach(i => { |
|||
console.log( |
|||
printf(` %-${urlLength + 10}s`, ` - ${chalk.underline(i.url)}`) |
|||
) |
|||
}) |
|||
console.log() |
|||
} |
|||
}) |
|||
console.log() |
|||
}) |
|||
} |
@ -1,130 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const minimist = require('minimist') |
|||
const chalk = require('chalk') |
|||
const fetch = require('node-fetch') |
|||
const ora = require('ora') |
|||
|
|||
// Utilities
|
|||
const cfg = require('../lib/cfg') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const { handleError } = require('../lib/error') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config'], |
|||
boolean: ['help'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c' |
|||
} |
|||
}) |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now logout`)} |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} config file |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Logout from the CLI: |
|||
|
|||
${chalk.cyan('$ now logout')} |
|||
`)
|
|||
} |
|||
|
|||
if (argv.help) { |
|||
help() |
|||
process.exit(0) |
|||
} |
|||
|
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
const endpoint = apiUrl + '/www/user/tokens/' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
const requestHeaders = token => ({ |
|||
headers: { |
|||
Authorization: `bearer ${token}` |
|||
} |
|||
}) |
|||
|
|||
const getTokenId = async token => { |
|||
const result = await fetch(endpoint, requestHeaders(token)) |
|||
const tokenList = await result.json() |
|||
|
|||
if (!tokenList.tokens) { |
|||
return |
|||
} |
|||
|
|||
const tokenInfo = tokenList.tokens.find(t => token === t.token) |
|||
|
|||
if (!tokenInfo) { |
|||
return |
|||
} |
|||
|
|||
return tokenInfo.id |
|||
} |
|||
|
|||
const revokeToken = async (token, tokenId) => { |
|||
const details = { |
|||
method: 'DELETE' |
|||
} |
|||
|
|||
Object.assign(details, requestHeaders(token)) |
|||
const result = await fetch(endpoint + encodeURIComponent(tokenId), details) |
|||
|
|||
if (!result.ok) { |
|||
console.error('Not able to log out') |
|||
} |
|||
} |
|||
|
|||
const logout = async () => { |
|||
const spinner = ora({ |
|||
text: 'Logging out...' |
|||
}).start() |
|||
|
|||
const config = await cfg.read() |
|||
|
|||
try { |
|||
await cfg.removeFile() |
|||
} catch (err) { |
|||
spinner.fail(`Couldn't remove config while logging out`) |
|||
process.exit(1) |
|||
} |
|||
|
|||
let tokenId |
|||
|
|||
try { |
|||
tokenId = await getTokenId(argv.token || config.token) |
|||
} catch (err) { |
|||
spinner.fail('Not able to get token id on logout') |
|||
process.exit(1) |
|||
} |
|||
|
|||
if (!tokenId) { |
|||
return |
|||
} |
|||
|
|||
try { |
|||
await revokeToken(argv.token || config.token, tokenId) |
|||
} catch (err) { |
|||
spinner.fail('Could not revoke token on logout') |
|||
process.exit(1) |
|||
} |
|||
|
|||
spinner.succeed('Logged out!') |
|||
} |
|||
|
|||
logout().catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
@ -1,290 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
const qs = require('querystring') |
|||
const minimist = require('minimist') |
|||
const chalk = require('chalk') |
|||
const dateformat = require('dateformat') |
|||
const io = require('socket.io-client') |
|||
const Now = require('../lib') |
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const { compare, deserialize } = require('../lib/logs') |
|||
const { maybeURL, normalizeURL, parseInstanceURL } = require('../lib/utils/url') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'query', 'since', 'token', 'until'], |
|||
boolean: ['help', 'all', 'debug', 'f'], |
|||
alias: { |
|||
help: 'h', |
|||
all: 'a', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't', |
|||
query: 'q' |
|||
} |
|||
}) |
|||
|
|||
let deploymentIdOrURL = argv._[0] |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now logs`)} <deploymentId|url> |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help output usage information |
|||
-a, --all include access logs |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} config file |
|||
-d, --debug debug mode [off] |
|||
-f wait for additional data [off] |
|||
-n ${chalk.bold.underline('NUMBER')} number of logs [1000] |
|||
-q ${chalk.bold.underline('QUERY')}, --query=${chalk.bold.underline( |
|||
'QUERY' |
|||
)} search query |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} login token |
|||
--since=${chalk.bold.underline( |
|||
'SINCE' |
|||
)} only return logs after date (ISO 8601) |
|||
--until=${chalk.bold.underline( |
|||
'UNTIL' |
|||
)} only return logs before date (ISO 8601), ignored if the f option is enbled. |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Print logs for the deployment ${chalk.dim( |
|||
'`deploymentId`' |
|||
)} |
|||
|
|||
${chalk.cyan('$ now logs deploymentId')} |
|||
`)
|
|||
} |
|||
|
|||
if (argv.help || !deploymentIdOrURL) { |
|||
help() |
|||
process.exit(0) |
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
const limit = typeof argv.n === 'number' ? argv.n : 1000 |
|||
const query = argv.query || '' |
|||
const follow = argv.f |
|||
const types = argv.all ? [] : ['command', 'stdout', 'stderr', 'exit'] |
|||
|
|||
let since |
|||
try { |
|||
since = argv.since ? toSerial(argv.since) : null |
|||
} catch (err) { |
|||
error(`Invalid date string: ${argv.since}`) |
|||
process.exit(1) |
|||
} |
|||
|
|||
let until |
|||
try { |
|||
until = argv.until ? toSerial(argv.until) : null |
|||
} catch (err) { |
|||
error(`Invalid date string: ${argv.until}`) |
|||
process.exit(1) |
|||
} |
|||
|
|||
let instanceId |
|||
|
|||
if (maybeURL(deploymentIdOrURL)) { |
|||
const normalizedURL = normalizeURL(deploymentIdOrURL) |
|||
if (normalizedURL.includes('/')) { |
|||
error(`Invalid deployment url: can't include path (${deploymentIdOrURL})`) |
|||
process.exit(1) |
|||
} |
|||
|
|||
;[deploymentIdOrURL, instanceId] = parseInstanceURL(normalizedURL) |
|||
} |
|||
|
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
process.exit(1) |
|||
} |
|||
|
|||
await printLogs({ token, config }) |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
|
|||
async function printLogs({ token, config: { currentTeam } }) { |
|||
let buf = [] |
|||
let init = false |
|||
let lastLog |
|||
|
|||
if (!follow) { |
|||
onLogs(await fetchLogs({ token, currentTeam, since, until })) |
|||
return |
|||
} |
|||
|
|||
const isURL = deploymentIdOrURL.includes('.') |
|||
const q = qs.stringify({ |
|||
deploymentId: isURL ? '' : deploymentIdOrURL, |
|||
host: isURL ? deploymentIdOrURL : '', |
|||
instanceId, |
|||
types: types.join(','), |
|||
query |
|||
}) |
|||
|
|||
const socket = io(`https://log-io.zeit.co?${q}`) |
|||
socket.on('connect', () => { |
|||
if (debug) { |
|||
console.log('> [debug] Socket connected') |
|||
} |
|||
}) |
|||
|
|||
socket.on('auth', callback => { |
|||
if (debug) { |
|||
console.log('> [debug] Socket authenticate') |
|||
} |
|||
callback(token) |
|||
}) |
|||
|
|||
socket.on('ready', () => { |
|||
if (debug) { |
|||
console.log('> [debug] Socket ready') |
|||
} |
|||
|
|||
// For the case socket reconnected
|
|||
const _since = lastLog ? lastLog.serial : since |
|||
|
|||
fetchLogs({ token, currentTeam, since: _since }).then(logs => { |
|||
init = true |
|||
const m = {} |
|||
logs.concat(buf.map(b => b.log)).forEach(l => { |
|||
m[l.id] = l |
|||
}) |
|||
buf = [] |
|||
onLogs(Object.values(m)) |
|||
}) |
|||
}) |
|||
|
|||
socket.on('logs', l => { |
|||
const log = deserialize(l) |
|||
let timer |
|||
if (init) { |
|||
// Wait for other logs for a while
|
|||
// and sort them in the correct order
|
|||
timer = setTimeout(() => { |
|||
buf.sort((a, b) => compare(a.log, b.log)) |
|||
const idx = buf.findIndex(b => b.log.id === log.id) |
|||
buf.slice(0, idx + 1).forEach(b => { |
|||
clearTimeout(b.timer) |
|||
onLog(b.log) |
|||
}) |
|||
buf = buf.slice(idx + 1) |
|||
}, 300) |
|||
} |
|||
buf.push({ log, timer }) |
|||
}) |
|||
|
|||
socket.on('disconnect', () => { |
|||
if (debug) { |
|||
console.log('> [debug] Socket disconnect') |
|||
} |
|||
init = false |
|||
}) |
|||
|
|||
socket.on('error', err => { |
|||
if (debug) { |
|||
console.log('> [debug] Socket error', err.stack) |
|||
} |
|||
}) |
|||
|
|||
function onLogs(logs) { |
|||
logs.sort(compare).forEach(onLog) |
|||
} |
|||
|
|||
function onLog(log) { |
|||
lastLog = log |
|||
printLog(log) |
|||
} |
|||
} |
|||
|
|||
function printLog(log) { |
|||
let data |
|||
const obj = log.object |
|||
if (log.type === 'request') { |
|||
data = |
|||
`REQ "${obj.method} ${obj.uri} ${obj.protocol}"` + |
|||
` ${obj.remoteAddr} - ${obj.remoteUser || ''}` + |
|||
` "${obj.referer || ''}" "${obj.userAgent}"` |
|||
} else if (log.type === 'response') { |
|||
data = |
|||
`RES "${obj.method} ${obj.uri} ${obj.protocol}"` + |
|||
` ${obj.status} ${obj.bodyBytesSent}` |
|||
} else { |
|||
data = obj |
|||
? JSON.stringify(obj, null, 2) |
|||
: (log.text || '').replace(/\n$/, '') |
|||
} |
|||
|
|||
const date = dateformat(log.date, 'mm/dd hh:MM TT') |
|||
|
|||
data.split('\n').forEach((line, i) => { |
|||
if (i === 0) { |
|||
console.log(`${chalk.dim(date)} ${line}`) |
|||
} else { |
|||
console.log(`${repeat(' ', date.length)} ${line}`) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async function fetchLogs({ token, currentTeam, since, until } = {}) { |
|||
const now = new Now({ apiUrl, token, debug, currentTeam }) |
|||
|
|||
let logs |
|||
try { |
|||
logs = await now.logs(deploymentIdOrURL, { |
|||
instanceId, |
|||
types, |
|||
limit, |
|||
query, |
|||
since, |
|||
until |
|||
}) |
|||
} catch (err) { |
|||
handleError(err) |
|||
process.exit(1) |
|||
} finally { |
|||
now.close() |
|||
} |
|||
|
|||
return logs.map(deserialize) |
|||
} |
|||
|
|||
function repeat(s, n) { |
|||
return new Array(n + 1).join(s) |
|||
} |
|||
|
|||
function toSerial(datestr) { |
|||
const t = Date.parse(datestr) |
|||
if (isNaN(t)) { |
|||
throw new TypeError('Invalid date string') |
|||
} |
|||
|
|||
const pidLen = 19 |
|||
const seqLen = 19 |
|||
return t + repeat('0', pidLen + seqLen) |
|||
} |
@ -1,218 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const minimist = require('minimist') |
|||
const chalk = require('chalk') |
|||
const ms = require('ms') |
|||
const table = require('text-table') |
|||
|
|||
// Ours
|
|||
const Now = require('../lib') |
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const { normalizeURL } = require('../lib/utils/url') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug', 'hard', 'yes', 'safe'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't', |
|||
yes: 'y' |
|||
} |
|||
}) |
|||
|
|||
const ids = argv._ |
|||
|
|||
// Options
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold( |
|||
`${logo} now remove` |
|||
)} deploymentId|deploymentName [...deploymentId|deploymentName] |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
-y, --yes Skip confirmation |
|||
--safe Skip deployments with an active alias |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Remove a deployment identified by ${chalk.dim( |
|||
'`deploymentId`' |
|||
)}: |
|||
|
|||
${chalk.cyan('$ now rm deploymentId')} |
|||
|
|||
${chalk.gray('–')} Remove all deployments with name ${chalk.dim('`my-app`')}: |
|||
|
|||
${chalk.cyan('$ now rm my-app')} |
|||
|
|||
${chalk.gray('–')} Remove two deployments with IDs ${chalk.dim( |
|||
'`eyWt6zuSdeus`' |
|||
)} and ${chalk.dim('`uWHoA9RQ1d1o`')}: |
|||
|
|||
${chalk.cyan('$ now rm eyWt6zuSdeus uWHoA9RQ1d1o')} |
|||
|
|||
${chalk.dim('Alias:')} rm |
|||
`)
|
|||
} |
|||
|
|||
if (argv.help || ids.length === 0) { |
|||
help() |
|||
process.exit(0) |
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
const hard = argv.hard || false |
|||
const skipConfirmation = argv.yes || false |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
process.exit(1) |
|||
} |
|||
|
|||
try { |
|||
await remove({ token, config }) |
|||
} catch (err) { |
|||
error(`Unknown error: ${err}\n${err.stack}`) |
|||
process.exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
|
|||
function readConfirmation(matches) { |
|||
return new Promise(resolve => { |
|||
process.stdout.write( |
|||
`> The following deployment${matches.length === 1 |
|||
? '' |
|||
: 's'} will be removed permanently:\n` |
|||
) |
|||
|
|||
const tbl = table( |
|||
matches.map(depl => { |
|||
const time = chalk.gray(ms(new Date() - depl.created) + ' ago') |
|||
const url = depl.url ? chalk.underline(`https://${depl.url}`) : '' |
|||
return [depl.uid, url, time] |
|||
}), |
|||
{ align: ['l', 'r', 'l'], hsep: ' '.repeat(6) } |
|||
) |
|||
process.stdout.write(tbl + '\n') |
|||
|
|||
for (const depl of matches) { |
|||
for (const alias of depl.aliases) { |
|||
process.stdout.write( |
|||
`> ${chalk.yellow('Warning!')} Deployment ${chalk.bold(depl.uid)} ` + |
|||
`is an alias for ${chalk.underline( |
|||
`https://${alias.alias}` |
|||
)} and will be removed.\n` |
|||
) |
|||
} |
|||
} |
|||
|
|||
process.stdout.write( |
|||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}` |
|||
) |
|||
|
|||
process.stdin |
|||
.on('data', d => { |
|||
process.stdin.pause() |
|||
resolve(d.toString().trim()) |
|||
}) |
|||
.resume() |
|||
}) |
|||
} |
|||
|
|||
async function remove({ token, config: { currentTeam } }) { |
|||
const now = new Now({ apiUrl, token, debug, currentTeam }) |
|||
|
|||
const deployments = await now.list() |
|||
|
|||
let matches = deployments.filter(d => { |
|||
return ids.some(id => { |
|||
return d.uid === id || d.name === id || d.url === normalizeURL(id) |
|||
}) |
|||
}) |
|||
|
|||
const aliases = await Promise.all( |
|||
matches.map(depl => now.listAliases(depl.uid)) |
|||
) |
|||
|
|||
matches = matches.filter((match, i) => { |
|||
if (argv.safe && aliases[i].length > 0) { |
|||
return false |
|||
} |
|||
|
|||
match.aliases = aliases[i] |
|||
return true |
|||
}) |
|||
|
|||
if (matches.length === 0) { |
|||
error( |
|||
`Could not find ${argv.safe |
|||
? 'unaliased' |
|||
: 'any'} deployments matching ${ids |
|||
.map(id => chalk.bold(`"${id}"`)) |
|||
.join(', ')}. Run ${chalk.dim(`\`now ls\``)} to list.` |
|||
) |
|||
return process.exit(1) |
|||
} |
|||
|
|||
try { |
|||
if (!skipConfirmation) { |
|||
const confirmation = (await readConfirmation(matches)).toLowerCase() |
|||
|
|||
if (confirmation !== 'y' && confirmation !== 'yes') { |
|||
console.log('\n> Aborted') |
|||
process.exit(0) |
|||
} |
|||
} |
|||
|
|||
const start = new Date() |
|||
|
|||
await Promise.all(matches.map(depl => now.remove(depl.uid, { hard }))) |
|||
|
|||
const elapsed = ms(new Date() - start) |
|||
console.log(`${chalk.cyan('> Success!')} [${elapsed}]`) |
|||
console.log( |
|||
table( |
|||
matches.map(depl => { |
|||
return [`Deployment ${chalk.bold(depl.uid)} removed`] |
|||
}) |
|||
) |
|||
) |
|||
} catch (err) { |
|||
handleError(err) |
|||
process.exit(1) |
|||
} |
|||
|
|||
now.close() |
|||
} |
@ -1,384 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const isURL = require('is-url') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
const printf = require('printf') |
|||
require('epipebomb')() |
|||
const supportsColor = require('supports-color') |
|||
|
|||
// Ours
|
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const NowScale = require('../lib/scale') |
|||
const login = require('../lib/login') |
|||
const exit = require('../lib/utils/exit') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const info = require('../lib/scale-info') |
|||
const sort = require('../lib/sort-deployments') |
|||
const success = require('../lib/utils/output/success') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug'], |
|||
alias: { help: 'h', config: 'c', debug: 'd', token: 't' } |
|||
}) |
|||
|
|||
let id = argv._[0] |
|||
const scaleArg = argv._[1] |
|||
const optionalScaleArg = argv._[2] |
|||
|
|||
// Options
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now scale`)} ls |
|||
${chalk.bold(`${logo} now scale`)} <url> |
|||
${chalk.bold(`${logo} now scale`)} <url> <min> [max] |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Create a deployment with 3 instances, never sleeps: |
|||
|
|||
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 3')} |
|||
|
|||
${chalk.gray('–')} Create an automatically scaling deployment: |
|||
|
|||
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 1 5')} |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} Create an automatically scaling deployment without specifying max: |
|||
|
|||
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 1 auto')} |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} Create an automatically scaling deployment without specifying min or max: |
|||
|
|||
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh auto')} |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} Create a deployment that is always active and never "sleeps": |
|||
|
|||
${chalk.cyan('$ now scale my-deployment-ntahoeato.now.sh 1')} |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
if (argv.help) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
|
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
if (err.userError) { |
|||
error(err.message) |
|||
} else { |
|||
error(`Unknown error: ${err}\n${err.stack}`) |
|||
} |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
function guessParams() { |
|||
if (Number.isInteger(scaleArg) && !optionalScaleArg) { |
|||
return { min: scaleArg, max: scaleArg } |
|||
} else if (Number.isInteger(scaleArg) && Number.isInteger(optionalScaleArg)) { |
|||
return { min: scaleArg, max: optionalScaleArg } |
|||
} else if (Number.isInteger(scaleArg) && optionalScaleArg === 'auto') { |
|||
return { min: scaleArg, max: 'auto' } |
|||
} else if ( |
|||
(!scaleArg && !optionalScaleArg) || |
|||
(scaleArg === 'auto' && !optionalScaleArg) |
|||
) { |
|||
return { min: 1, max: 'auto' } |
|||
} |
|||
help() |
|||
process.exit(1) |
|||
} |
|||
|
|||
function isHostNameOrId(str) { |
|||
return ( |
|||
/(https?:\/\/)?((?:(?=[a-z0-9-]{1,63}\.)(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.)+[a-z]{2,63})/.test( |
|||
str |
|||
) || str.length === 28 |
|||
) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam } }) { |
|||
const scale = new NowScale({ apiUrl, token, debug, currentTeam }) |
|||
const start = Date.now() |
|||
|
|||
if (id === 'ls') { |
|||
await list(scale) |
|||
process.exit(0) |
|||
} else if (id === 'info') { |
|||
await info(scale) |
|||
process.exit(0) |
|||
} else if (id && isHostNameOrId(id)) { |
|||
// Normalize URL by removing slash from the end
|
|||
if (isURL(id)) { |
|||
id = id.replace(/^https:\/\//i, '') |
|||
if (id.slice(-1) === '/') { |
|||
id = id.slice(0, -1) |
|||
} |
|||
} |
|||
} else { |
|||
error('Please specify a deployment: now scale <id|url>') |
|||
help() |
|||
exit(1) |
|||
} |
|||
|
|||
const deployments = await scale.list() |
|||
|
|||
let match = deployments.find(d => { |
|||
// `url` should match the hostname of the deployment
|
|||
let u = id.replace(/^https:\/\//i, '') |
|||
|
|||
if (u.indexOf('.') === -1) { |
|||
// `.now.sh` domain is implied if just the subdomain is given
|
|||
u += '.now.sh' |
|||
} |
|||
return d.uid === id || d.name === id || d.url === u |
|||
}) |
|||
|
|||
if (!match) { |
|||
// Maybe it's an alias
|
|||
const aliasDeployment = (await scale.listAliases()).find( |
|||
e => e.alias === id |
|||
) |
|||
if (!aliasDeployment) { |
|||
error(`Could not find any deployments matching ${id}`) |
|||
return process.exit(1) |
|||
} |
|||
match = deployments.find(d => { |
|||
return d.uid === aliasDeployment.deploymentId |
|||
}) |
|||
} |
|||
|
|||
const { min, max } = guessParams() |
|||
|
|||
if ( |
|||
!(Number.isInteger(min) || min === 'auto') && |
|||
!(Number.isInteger(max) || max === 'auto') |
|||
) { |
|||
help() |
|||
return exit(1) |
|||
} |
|||
|
|||
if (match.type === 'STATIC') { |
|||
if (min === 0 && max === 0) { |
|||
error("Static deployments can't be FROZEN. Use `now rm` to remove") |
|||
return process.exit(1) |
|||
} |
|||
console.log('> Static deployments are automatically scaled!') |
|||
return process.exit(0) |
|||
} |
|||
|
|||
const { |
|||
max: currentMax, |
|||
min: currentMin, |
|||
current: currentCurrent |
|||
} = match.scale |
|||
if ( |
|||
max === currentMax && |
|||
min === currentMin && |
|||
Number.isInteger(min) && |
|||
currentCurrent >= min && |
|||
Number.isInteger(max) && |
|||
currentCurrent <= max |
|||
) { |
|||
// Nothing to do, let's print the rules
|
|||
printScaleingRules(match.url, currentCurrent, min, max) |
|||
return |
|||
} |
|||
|
|||
if ((match.state === 'FROZEN' || match.scale.current === 0) && min > 0) { |
|||
console.log( |
|||
`> Deployment is currently in 0 replicas, preparing deployment for scaling...` |
|||
) |
|||
if (match.scale.max < 1) { |
|||
await scale.setScale(match.uid, { min: 0, max: 1 }) |
|||
} |
|||
await scale.unfreeze(match) |
|||
} |
|||
|
|||
const { min: newMin, max: newMax } = await scale.setScale(match.uid, { |
|||
min, |
|||
max |
|||
}) |
|||
|
|||
const elapsed = ms(new Date() - start) |
|||
|
|||
const currentReplicas = match.scale.current |
|||
printScaleingRules(match.url, currentReplicas, newMin, newMax, elapsed) |
|||
await info(scale, match.url) |
|||
|
|||
scale.close() |
|||
} |
|||
function printScaleingRules(url, currentReplicas, min, max, elapsed) { |
|||
const log = console.log |
|||
success( |
|||
`Configured scaling rules ${chalk.gray(elapsed ? '[' + elapsed + ']' : '')}` |
|||
) |
|||
log() |
|||
log( |
|||
`${chalk.bold(url)} (${chalk.gray(currentReplicas)} ${chalk.gray( |
|||
'current' |
|||
)})` |
|||
) |
|||
log(printf('%6s %s', 'min', chalk.bold(min))) |
|||
log(printf('%6s %s', 'max', chalk.bold(max))) |
|||
log(printf('%6s %s', 'auto', chalk.bold(min === max ? '✖' : '✔'))) |
|||
log() |
|||
} |
|||
|
|||
async function list(scale) { |
|||
let deployments |
|||
try { |
|||
const app = argv._[1] |
|||
deployments = await scale.list(app) |
|||
} catch (err) { |
|||
handleError(err) |
|||
process.exit(1) |
|||
} |
|||
|
|||
scale.close() |
|||
|
|||
const apps = new Map() |
|||
|
|||
for (const dep of deployments) { |
|||
const deps = apps.get(dep.name) || [] |
|||
apps.set(dep.name, deps.concat(dep)) |
|||
} |
|||
|
|||
const sorted = await sort([...apps]) |
|||
|
|||
const timeNow = new Date() |
|||
const urlLength = |
|||
deployments.reduce((acc, i) => { |
|||
return Math.max(acc, (i.url && i.url.length) || 0) |
|||
}, 0) + 5 |
|||
|
|||
for (const app of sorted) { |
|||
const depls = argv.all ? app[1] : app[1].slice(0, 5) |
|||
console.log( |
|||
`${chalk.bold(app[0])} ${chalk.gray( |
|||
'(' + depls.length + ' of ' + app[1].length + ' total)' |
|||
)}` |
|||
) |
|||
console.log() |
|||
const urlSpec = `%-${urlLength}s` |
|||
console.log( |
|||
printf( |
|||
` ${chalk.grey(urlSpec + ' %8s %8s %8s %8s %8s')}`, |
|||
'url', |
|||
'cur', |
|||
'min', |
|||
'max', |
|||
'auto', |
|||
'age' |
|||
) |
|||
) |
|||
for (const instance of depls) { |
|||
if (!instance.scale) { |
|||
let spec |
|||
if (supportsColor) { |
|||
spec = ` %-${urlLength + 10}s %8s %8s %8s %8s %8s` |
|||
} else { |
|||
spec = ` %-${urlLength + 1}s %8s %8s %8s %8s %8s` |
|||
} |
|||
const infinite = '∞' |
|||
console.log( |
|||
printf( |
|||
spec, |
|||
chalk.underline(instance.url), |
|||
infinite, |
|||
1, |
|||
infinite, |
|||
'✔', |
|||
ms(timeNow - instance.created) |
|||
) |
|||
) |
|||
} else if (instance.scale.current > 0) { |
|||
let spec |
|||
if (supportsColor) { |
|||
spec = ` %-${urlLength + 10}s %8s %8s %8s %8s %8s` |
|||
} else { |
|||
spec = ` %-${urlLength + 1}s %8s %8s %8s %8s %8s` |
|||
} |
|||
console.log( |
|||
printf( |
|||
spec, |
|||
chalk.underline(instance.url), |
|||
instance.scale.current, |
|||
instance.scale.min, |
|||
instance.scale.max, |
|||
instance.scale.max === instance.scale.min ? '✖' : '✔', |
|||
ms(timeNow - instance.created) |
|||
) |
|||
) |
|||
} else { |
|||
let spec |
|||
if (supportsColor) { |
|||
spec = ` %-${urlLength + 10}s ${chalk.gray('%8s %8s %8s %8s %8s')}` |
|||
} else { |
|||
spec = ` %-${urlLength + 1}s ${chalk.gray('%8s %8s %8s %8s %8s')}` |
|||
} |
|||
console.log( |
|||
printf( |
|||
spec, |
|||
chalk.underline(instance.url), |
|||
instance.scale.current, |
|||
instance.scale.min, |
|||
instance.scale.max, |
|||
instance.scale.max === instance.scale.min ? '✖' : '✔', |
|||
ms(timeNow - instance.created) |
|||
) |
|||
) |
|||
} |
|||
} |
|||
console.log() |
|||
} |
|||
} |
|||
|
|||
process.on('uncaughtException', err => { |
|||
handleError(err) |
|||
exit(1) |
|||
}) |
@ -1,297 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const table = require('text-table') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
|
|||
// Ours
|
|||
const strlen = require('../lib/strlen') |
|||
const cfg = require('../lib/cfg') |
|||
const { handleError, error } = require('../lib/error') |
|||
const NowSecrets = require('../lib/secrets') |
|||
const login = require('../lib/login') |
|||
const exit = require('../lib/utils/exit') |
|||
const logo = require('../lib/utils/output/logo') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug', 'base64'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
base64: 'b', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const subcommand = argv._[0] |
|||
|
|||
// Options
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now secrets`)} <ls | add | rename | rm> <secret> |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-b, --base64 Treat value as base64-encoded |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Lists all your secrets: |
|||
|
|||
${chalk.cyan('$ now secrets ls')} |
|||
|
|||
${chalk.gray('–')} Adds a new secret: |
|||
|
|||
${chalk.cyan('$ now secrets add my-secret "my value"')} |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} Once added, a secret's value can't be retrieved in plaintext anymore |
|||
${chalk.gray( |
|||
'–' |
|||
)} If the secret's value is more than one word, wrap it in quotes |
|||
${chalk.gray('–')} Actually, when in doubt, wrap your value in quotes |
|||
|
|||
${chalk.gray('–')} Exposes a secret as an env variable: |
|||
|
|||
${chalk.cyan(`$ now -e MY_SECRET=${chalk.bold('@my-secret')}`)} |
|||
|
|||
Notice the ${chalk.cyan.bold( |
|||
'`@`' |
|||
)} symbol which makes the value a secret reference. |
|||
|
|||
${chalk.gray('–')} Renames a secret: |
|||
|
|||
${chalk.cyan(`$ now secrets rename my-secret my-renamed-secret`)} |
|||
|
|||
${chalk.gray('–')} Removes a secret: |
|||
|
|||
${chalk.cyan(`$ now secrets rm my-secret`)} |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
if (argv.help || !subcommand) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
|
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
handleError(err) |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam, user } }) { |
|||
const secrets = new NowSecrets({ apiUrl, token, debug, currentTeam }) |
|||
const args = argv._.slice(1) |
|||
const start = Date.now() |
|||
|
|||
if (subcommand === 'ls' || subcommand === 'list') { |
|||
if (args.length !== 0) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan('`now secret ls`')}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
const list = await secrets.ls() |
|||
const elapsed = ms(new Date() - start) |
|||
|
|||
console.log( |
|||
`> ${list.length} secret${list.length === 1 |
|||
? '' |
|||
: 's'} found under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)} ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
|
|||
if (list.length > 0) { |
|||
const cur = Date.now() |
|||
const header = [['', 'name', 'created'].map(s => chalk.dim(s))] |
|||
const out = table( |
|||
header.concat( |
|||
list.map(secret => { |
|||
return [ |
|||
'', |
|||
chalk.bold(secret.name), |
|||
chalk.gray(ms(cur - new Date(secret.created)) + ' ago') |
|||
] |
|||
}) |
|||
), |
|||
{ |
|||
align: ['l', 'l', 'l'], |
|||
hsep: ' '.repeat(2), |
|||
stringLength: strlen |
|||
} |
|||
) |
|||
|
|||
if (out) { |
|||
console.log('\n' + out + '\n') |
|||
} |
|||
} |
|||
return secrets.close() |
|||
} |
|||
|
|||
if (subcommand === 'rm' || subcommand === 'remove') { |
|||
if (args.length !== 1) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now secret rm <name>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
const list = await secrets.ls() |
|||
const theSecret = list.find(secret => secret.name === args[0]) |
|||
|
|||
if (theSecret) { |
|||
const yes = await readConfirmation(theSecret) |
|||
if (!yes) { |
|||
error('User abort') |
|||
return exit(0) |
|||
} |
|||
} else { |
|||
error(`No secret found by name "${args[0]}"`) |
|||
return exit(1) |
|||
} |
|||
|
|||
const secret = await secrets.rm(args[0]) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Secret ${chalk.bold( |
|||
secret.name |
|||
)} removed ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
return secrets.close() |
|||
} |
|||
|
|||
if (subcommand === 'rename') { |
|||
if (args.length !== 2) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now secret rename <old-name> <new-name>`' |
|||
)}` |
|||
) |
|||
return exit(1) |
|||
} |
|||
const secret = await secrets.rename(args[0], args[1]) |
|||
const elapsed = ms(new Date() - start) |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Secret ${chalk.bold( |
|||
secret.oldName |
|||
)} renamed to ${chalk.bold(args[1])} ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
return secrets.close() |
|||
} |
|||
|
|||
if (subcommand === 'add' || subcommand === 'set') { |
|||
if (args.length !== 2) { |
|||
error( |
|||
`Invalid number of arguments. Usage: ${chalk.cyan( |
|||
'`now secret add <name> <value>`' |
|||
)}` |
|||
) |
|||
|
|||
if (args.length > 2) { |
|||
const example = chalk.cyan(`$ now secret add ${args[0]}`) |
|||
console.log( |
|||
`> If your secret has spaces, make sure to wrap it in quotes. Example: \n ${example} ` |
|||
) |
|||
} |
|||
|
|||
return exit(1) |
|||
} |
|||
|
|||
const [name, value_] = args |
|||
let value |
|||
|
|||
if (argv.base64) { |
|||
value = { base64: value_ } |
|||
} else { |
|||
value = value_ |
|||
} |
|||
|
|||
await secrets.add(name, value) |
|||
const elapsed = ms(new Date() - start) |
|||
|
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Secret ${chalk.bold( |
|||
name.toLowerCase() |
|||
)} added (${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}) ${chalk.gray(`[${elapsed}]`)}` |
|||
) |
|||
return secrets.close() |
|||
} |
|||
|
|||
error('Please specify a valid subcommand: ls | add | rename | rm') |
|||
help() |
|||
exit(1) |
|||
} |
|||
|
|||
process.on('uncaughtException', err => { |
|||
handleError(err) |
|||
exit(1) |
|||
}) |
|||
|
|||
function readConfirmation(secret) { |
|||
return new Promise(resolve => { |
|||
const time = chalk.gray(ms(new Date() - new Date(secret.created)) + ' ago') |
|||
const tbl = table([[chalk.bold(secret.name), time]], { |
|||
align: ['r', 'l'], |
|||
hsep: ' '.repeat(6) |
|||
}) |
|||
|
|||
process.stdout.write('> The following secret will be removed permanently\n') |
|||
process.stdout.write(' ' + tbl + '\n') |
|||
|
|||
process.stdout.write( |
|||
`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}` |
|||
) |
|||
|
|||
process.stdin |
|||
.on('data', d => { |
|||
process.stdin.pause() |
|||
resolve(d.toString().trim().toLowerCase() === 'y') |
|||
}) |
|||
.resume() |
|||
}) |
|||
} |
@ -1,171 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Native
|
|||
const { resolve } = require('path') |
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const minimist = require('minimist') |
|||
|
|||
// Ours
|
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const error = require('../lib/utils/output/error') |
|||
const NowTeams = require('../lib/teams') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const exit = require('../lib/utils/exit') |
|||
const { handleError } = require('../lib/error') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't', |
|||
switch: 'change' |
|||
} |
|||
}) |
|||
|
|||
const subcommand = argv._[0] |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now teams`)} <add | ls | rm | invite> |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Add a new team: |
|||
|
|||
${chalk.cyan('$ now teams add')} |
|||
|
|||
${chalk.gray('–')} Switch to a team: |
|||
|
|||
${chalk.cyan(`$ now switch <slug>`)} |
|||
|
|||
${chalk.gray( |
|||
'–' |
|||
)} If your team's url is 'zeit.co/teams/name', 'name' is the slug |
|||
${chalk.gray('–')} If the slug is omitted, you can choose interactively |
|||
|
|||
${chalk.yellow( |
|||
'NOTE:' |
|||
)} When you switch, everything you add, list or remove will be scoped that team! |
|||
|
|||
${chalk.gray('–')} Invite new members (interactively): |
|||
|
|||
${chalk.cyan(`$ now teams invite`)} |
|||
|
|||
${chalk.gray('–')} Invite a specific email: |
|||
|
|||
${chalk.cyan(`$ now teams invite geist@zeit.co`)} |
|||
|
|||
${chalk.gray('–')} Remove a team: |
|||
|
|||
${chalk.cyan(`$ now teams rm <id>`)} |
|||
|
|||
${chalk.gray('–')} If the id is omitted, you can choose interactively |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
if (argv.help || !subcommand) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
|
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
if (err.userError) { |
|||
error(err.message) |
|||
} else { |
|||
error(`Unknown error: ${err.stack}`) |
|||
} |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam } }) { |
|||
const teams = new NowTeams({ apiUrl, token, debug, currentTeam }) |
|||
const args = argv._.slice(1) |
|||
|
|||
switch (subcommand) { |
|||
case 'list': |
|||
case 'ls': { |
|||
await require(resolve(__dirname, 'teams', 'list.js'))({ |
|||
teams, |
|||
token |
|||
}) |
|||
break |
|||
} |
|||
case 'switch': |
|||
case 'change': { |
|||
await require(resolve(__dirname, 'teams', 'switch.js'))({ |
|||
teams, |
|||
args, |
|||
token |
|||
}) |
|||
break |
|||
} |
|||
case 'add': |
|||
case 'create': { |
|||
await require(resolve(__dirname, 'teams', 'add.js'))({ teams, token }) |
|||
break |
|||
} |
|||
|
|||
case 'invite': { |
|||
await require(resolve(__dirname, 'teams', 'invite.js'))({ |
|||
teams, |
|||
args, |
|||
token |
|||
}) |
|||
break |
|||
} |
|||
|
|||
default: { |
|||
let code = 0 |
|||
if (subcommand !== 'help') { |
|||
error('Please specify a valid subcommand: ls | add | rm | set-default') |
|||
code = 1 |
|||
} |
|||
help() |
|||
exit(code) |
|||
} |
|||
} |
|||
} |
@ -1,257 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
|
|||
// Ours
|
|||
const login = require('../lib/login') |
|||
const cfg = require('../lib/cfg') |
|||
const NowPlans = require('../lib/plans') |
|||
const indent = require('../lib/indent') |
|||
const listInput = require('../lib/utils/input/list') |
|||
const code = require('../lib/utils/output/code') |
|||
const error = require('../lib/utils/output/error') |
|||
const success = require('../lib/utils/output/success') |
|||
const cmd = require('../lib/utils/output/cmd') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const { handleError } = require('../lib/error') |
|||
|
|||
const { bold } = chalk |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now upgrade`)} [plan] |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} List available plans and pick one interactively |
|||
|
|||
${chalk.cyan('$ now upgrade')} |
|||
|
|||
${chalk.yellow('NOTE:')} ${chalk.gray( |
|||
'Make sure you have a payment method, or add one:' |
|||
)} |
|||
|
|||
${chalk.cyan(`$ now billing add`)} |
|||
|
|||
${chalk.gray('–')} Pick a specific plan (premium): |
|||
|
|||
${chalk.cyan(`$ now upgrade premium`)} |
|||
`)
|
|||
} |
|||
|
|||
// Options
|
|||
const debug = argv.debug |
|||
const apiUrl = argv.url || 'https://api.zeit.co' |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
const exit = code => { |
|||
// We give stdout some time to flush out
|
|||
// because there's a node bug where
|
|||
// stdout writes are asynchronous
|
|||
// https://github.com/nodejs/node/issues/6456
|
|||
setTimeout(() => process.exit(code || 0), 100) |
|||
} |
|||
|
|||
if (argv.help) { |
|||
help() |
|||
exit(0) |
|||
} else { |
|||
Promise.resolve() |
|||
.then(async () => { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
|
|||
let token |
|||
try { |
|||
token = config.token || (await login(apiUrl)) |
|||
} catch (err) { |
|||
error(`Authentication error – ${err.message}`) |
|||
exit(1) |
|||
} |
|||
|
|||
try { |
|||
await run({ token, config }) |
|||
} catch (err) { |
|||
if (err.userError) { |
|||
error(err.message) |
|||
} else { |
|||
error(`Unknown error: ${err.stack}`) |
|||
} |
|||
exit(1) |
|||
} |
|||
}) |
|||
.catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
|||
} |
|||
|
|||
function buildInquirerChoices(current, until) { |
|||
if (until) { |
|||
until = until.split(' ') |
|||
until = ' for ' + chalk.bold(until[0]) + ' more ' + until[1] |
|||
} else { |
|||
until = '' |
|||
} |
|||
|
|||
const currentText = bold('(current)') |
|||
let ossName = `OSS ${bold('FREE')}` |
|||
let premiumName = `Premium ${bold('$15')}` |
|||
let proName = `Pro ${bold('$50')}` |
|||
let advancedName = `Advanced ${bold('$200')}` |
|||
|
|||
switch (current) { |
|||
case 'oss': { |
|||
ossName += indent(currentText, 6) |
|||
break |
|||
} |
|||
case 'premium': { |
|||
premiumName += indent(currentText, 3) |
|||
break |
|||
} |
|||
case 'pro': { |
|||
proName += indent(currentText, 7) |
|||
break |
|||
} |
|||
case 'advanced': { |
|||
advancedName += indent(currentText, 1) |
|||
break |
|||
} |
|||
default: { |
|||
ossName += indent(currentText, 6) |
|||
} |
|||
} |
|||
|
|||
return [ |
|||
{ |
|||
name: ossName, |
|||
value: 'oss', |
|||
short: `OSS ${bold('FREE')}` |
|||
}, |
|||
{ |
|||
name: premiumName, |
|||
value: 'premium', |
|||
short: `Premium ${bold('$15')}` |
|||
}, |
|||
{ |
|||
name: proName, |
|||
value: 'pro', |
|||
short: `Pro ${bold('$50')}` |
|||
}, |
|||
{ |
|||
name: advancedName, |
|||
value: 'advanced', |
|||
short: `Advanced ${bold('$200')}` |
|||
} |
|||
] |
|||
} |
|||
|
|||
async function run({ token, config: { currentTeam, user } }) { |
|||
const args = argv._ |
|||
if (args.length > 1) { |
|||
error('Invalid number of arguments') |
|||
return exit(1) |
|||
} |
|||
|
|||
const start = new Date() |
|||
const plans = new NowPlans({ apiUrl, token, debug, currentTeam }) |
|||
|
|||
let planId = args[0] |
|||
|
|||
if (![undefined, 'oss', 'premium', 'pro', 'advanced'].includes(planId)) { |
|||
error(`Invalid plan name – should be ${code('oss')} or ${code('premium')}`) |
|||
return exit(1) |
|||
} |
|||
|
|||
const currentPlan = await plans.getCurrent() |
|||
|
|||
if (planId === undefined) { |
|||
const elapsed = ms(new Date() - start) |
|||
|
|||
let message = `For more info, please head to https://zeit.co` |
|||
message = currentTeam |
|||
? `${message}/${currentTeam.slug}/settings/plan` |
|||
: `${message}/account/plan` |
|||
message += `\n> Select a plan for ${bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)} ${chalk.gray(`[${elapsed}]`)}` |
|||
const choices = buildInquirerChoices(currentPlan.id, currentPlan.until) |
|||
|
|||
planId = await listInput({ |
|||
message, |
|||
choices, |
|||
separator: false, |
|||
abort: 'end' |
|||
}) |
|||
} |
|||
|
|||
if ( |
|||
planId === undefined || |
|||
(planId === currentPlan.id && currentPlan.until === undefined) |
|||
) { |
|||
return console.log('No changes made') |
|||
} |
|||
|
|||
let newPlan |
|||
|
|||
try { |
|||
newPlan = await plans.set(planId) |
|||
} catch (err) { |
|||
if (err.code === 'customer_not_found' || err.code === 'source_not_found') { |
|||
error( |
|||
`You have no payment methods available. Run ${cmd( |
|||
'now billing add' |
|||
)} to add one` |
|||
) |
|||
} else { |
|||
error(`An unknow error occured. Please try again later ${err.message}`) |
|||
} |
|||
plans.close() |
|||
return |
|||
} |
|||
|
|||
if (currentPlan.until && newPlan.id !== 'oss') { |
|||
success( |
|||
`The cancelation has been undone. You're back on the ${chalk.bold( |
|||
`${newPlan.name} plan` |
|||
)}` |
|||
) |
|||
} else if (newPlan.until) { |
|||
success( |
|||
`Your plan will be switched to ${chalk.bold( |
|||
newPlan.name |
|||
)} in ${chalk.bold(newPlan.until)}. Your card will not be charged again` |
|||
) |
|||
} else { |
|||
success(`You're now on the ${chalk.bold(`${newPlan.name} plan`)}`) |
|||
} |
|||
|
|||
plans.close() |
|||
} |
@ -1,78 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Packages
|
|||
const minimist = require('minimist') |
|||
const chalk = require('chalk') |
|||
|
|||
// Ours
|
|||
const cfg = require('../lib/cfg') |
|||
const exit = require('../lib/utils/exit') |
|||
const cmd = require('../lib/utils/output/cmd') |
|||
const logo = require('../lib/utils/output/logo') |
|||
const { handleError } = require('../lib/error') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
string: ['config', 'token'], |
|||
boolean: ['help', 'debug', 'all'], |
|||
alias: { |
|||
help: 'h', |
|||
config: 'c', |
|||
debug: 'd', |
|||
token: 't' |
|||
} |
|||
}) |
|||
|
|||
const help = () => { |
|||
console.log(` |
|||
${chalk.bold(`${logo} now whoami`)} |
|||
|
|||
${chalk.dim('Options:')} |
|||
|
|||
-h, --help Output usage information |
|||
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( |
|||
'FILE' |
|||
)} Config file |
|||
-d, --debug Debug mode [off] |
|||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline( |
|||
'TOKEN' |
|||
)} Login token |
|||
|
|||
${chalk.dim('Examples:')} |
|||
|
|||
${chalk.gray('–')} Show the current team context |
|||
|
|||
${chalk.cyan('$ now whoami')} |
|||
`)
|
|||
} |
|||
|
|||
if (argv.help) { |
|||
help() |
|||
process.exit(0) |
|||
} |
|||
|
|||
if (argv.config) { |
|||
cfg.setConfigFile(argv.config) |
|||
} |
|||
|
|||
async function whoami() { |
|||
const config = await cfg.read({ token: argv.token }) |
|||
if (!config || !config.token) { |
|||
console.log( |
|||
`> Not currently logged in! Please run ${cmd('now --login')}.\n` |
|||
) |
|||
return exit(1) |
|||
} |
|||
|
|||
if (process.stdout.isTTY) { |
|||
process.stdout.write('> ') |
|||
} |
|||
|
|||
const { user } = config |
|||
const name = user.username || user.email |
|||
console.log(name) |
|||
} |
|||
|
|||
whoami().catch(err => { |
|||
handleError(err) |
|||
process.exit(1) |
|||
}) |
@ -1,142 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
// Native
|
|||
const { resolve } = require('path') |
|||
|
|||
// Packages
|
|||
const updateNotifier = require('update-notifier') |
|||
const chalk = require('chalk') |
|||
|
|||
// Check if the current path exists and throw and error
|
|||
// if the user is trying to deploy a non-existing path!
|
|||
// This needs to be done exactly in this place, because
|
|||
// the utility imports are taking advantage of it
|
|||
try { |
|||
process.cwd() |
|||
} catch (err) { |
|||
if (err.code === 'ENOENT' && err.syscall === 'uv_cwd') { |
|||
console.log(`Current path doesn't exist!`) |
|||
} else { |
|||
console.log(err) |
|||
} |
|||
|
|||
process.exit(1) |
|||
} |
|||
|
|||
// Utilities
|
|||
const pkg = require('../lib/pkg') |
|||
|
|||
if (process.pkg) { |
|||
const notifier = updateNotifier({ pkg }) |
|||
const update = notifier.update |
|||
|
|||
if (update) { |
|||
let message = `Update available! ${chalk.red( |
|||
update.current |
|||
)} → ${chalk.green(update.latest)} \n` |
|||
message += `${chalk.magenta( |
|||
'Changelog:' |
|||
)} https://github.com/zeit/now-cli/releases/tag/${update.latest}\n`
|
|||
|
|||
if (pkg._npmPkg) { |
|||
message += `Run ${chalk.magenta('npm i -g now')} to update!` |
|||
} else { |
|||
message += `Please download binaries from https://zeit.co/download` |
|||
} |
|||
|
|||
notifier.notify({ message }) |
|||
} |
|||
} |
|||
|
|||
// This command will be run if no other sub command is specified
|
|||
const defaultCommand = 'deploy' |
|||
|
|||
const commands = new Set([ |
|||
defaultCommand, |
|||
'help', |
|||
'list', |
|||
'ls', |
|||
'rm', |
|||
'remove', |
|||
'alias', |
|||
'aliases', |
|||
'ln', |
|||
'domain', |
|||
'domains', |
|||
'dns', |
|||
'cert', |
|||
'certs', |
|||
'secret', |
|||
'secrets', |
|||
'cc', |
|||
'billing', |
|||
'upgrade', |
|||
'downgrade', |
|||
'team', |
|||
'teams', |
|||
'switch', |
|||
'log', |
|||
'logs', |
|||
'scale', |
|||
'logout', |
|||
'whoami' |
|||
]) |
|||
|
|||
const aliases = new Map([ |
|||
['ls', 'list'], |
|||
['rm', 'remove'], |
|||
['ln', 'alias'], |
|||
['aliases', 'alias'], |
|||
['domain', 'domains'], |
|||
['cert', 'certs'], |
|||
['secret', 'secrets'], |
|||
['cc', 'billing'], |
|||
['downgrade', 'upgrade'], |
|||
['team', 'teams'], |
|||
['switch', 'teams switch'], |
|||
['log', 'logs'] |
|||
]) |
|||
|
|||
let cmd = defaultCommand |
|||
let args = process.argv.slice(2) |
|||
const index = args.findIndex(a => commands.has(a)) |
|||
|
|||
if (index > -1) { |
|||
cmd = args[index] |
|||
args.splice(index, 1) |
|||
|
|||
if (cmd === 'help') { |
|||
if (index < args.length && commands.has(args[index])) { |
|||
cmd = args[index] |
|||
args.splice(index, 1) |
|||
} else { |
|||
cmd = defaultCommand |
|||
} |
|||
|
|||
args.unshift('--help') |
|||
} |
|||
|
|||
cmd = aliases.get(cmd) || cmd |
|||
if (cmd.includes(' ')) { |
|||
const parts = cmd.split(' ') |
|||
cmd = parts.shift() |
|||
args = [].concat(parts, args) |
|||
} |
|||
} |
|||
|
|||
// Don't throw a useless error message when running `now help help`
|
|||
// rather show the general help and be useful
|
|||
if (cmd === 'help') { |
|||
cmd = 'deploy' |
|||
} else if (cmd === defaultCommand && args[0] === 'login') { |
|||
args[0] = '--login' |
|||
} |
|||
|
|||
const bin = resolve(__dirname, 'now-' + cmd + '.js') |
|||
|
|||
// Prepare process.argv for subcommand
|
|||
process.argv = process.argv.slice(0, 2).concat(args) |
|||
|
|||
// Load sub command
|
|||
// With custom parameter to make "pkg" happy
|
|||
require(bin, 'may-exclude') |
@ -1,135 +0,0 @@ |
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
|
|||
// Ours
|
|||
const stamp = require('../../lib/utils/output/stamp') |
|||
const info = require('../../lib/utils/output/info') |
|||
const error = require('../../lib/utils/output/error') |
|||
const wait = require('../../lib/utils/output/wait') |
|||
const rightPad = require('../../lib/utils/output/right-pad') |
|||
const eraseLines = require('../../lib/utils/output/erase-lines') |
|||
const { tick } = require('../../lib/utils/output/chars') |
|||
const success = require('../../lib/utils/output/success') |
|||
const cmd = require('../../lib/utils/output/cmd') |
|||
const note = require('../../lib/utils/output/note') |
|||
const uid = require('../../lib/utils/output/uid') |
|||
const textInput = require('../../lib/utils/input/text') |
|||
const exit = require('../../lib/utils/exit') |
|||
const cfg = require('../../lib/cfg') |
|||
|
|||
function validateSlugKeypress(data, value) { |
|||
// TODO: the `value` here should contain the current value + the keypress
|
|||
// should be fixed on utils/input/text.js
|
|||
return /^[a-zA-Z]+[a-zA-Z0-9_-]*$/.test(value + data) |
|||
} |
|||
|
|||
function gracefulExit() { |
|||
console.log() // Blank line
|
|||
note( |
|||
`Your team is now active for all ${cmd('now')} commands!\n Run ${cmd( |
|||
'now switch' |
|||
)} to change it in the future.` |
|||
) |
|||
return exit() |
|||
} |
|||
|
|||
const teamUrlPrefix = rightPad('Team URL', 14) + chalk.gray('zeit.co/') |
|||
const teamNamePrefix = rightPad('Team Name', 14) |
|||
|
|||
module.exports = async function({ teams, token }) { |
|||
let slug |
|||
let team |
|||
let elapsed |
|||
let stopSpinner |
|||
|
|||
info( |
|||
`Pick a team identifier for its url (e.g.: ${chalk.cyan('`zeit.co/acme`')})` |
|||
) |
|||
do { |
|||
try { |
|||
// eslint-disable-next-line no-await-in-loop
|
|||
slug = await textInput({ |
|||
label: `- ${teamUrlPrefix}`, |
|||
validateKeypress: validateSlugKeypress, |
|||
initialValue: slug, |
|||
valid: team, |
|||
forceLowerCase: true |
|||
}) |
|||
} catch (err) { |
|||
if (err.message === 'USER_ABORT') { |
|||
info('Aborted') |
|||
return exit() |
|||
} |
|||
throw err |
|||
} |
|||
elapsed = stamp() |
|||
stopSpinner = wait(teamUrlPrefix + slug) |
|||
|
|||
let res |
|||
try { |
|||
// eslint-disable-next-line no-await-in-loop
|
|||
res = await teams.create({ slug }) |
|||
stopSpinner() |
|||
team = res |
|||
} catch (err) { |
|||
stopSpinner() |
|||
eraseLines(2) |
|||
error(err.message) |
|||
} |
|||
} while (!team) |
|||
|
|||
eraseLines(2) |
|||
success(`Team created ${uid(team.id)} ${elapsed()}`) |
|||
console.log(chalk.cyan(`${tick} `) + teamUrlPrefix + slug + '\n') |
|||
|
|||
info('Pick a display name for your team') |
|||
let name |
|||
try { |
|||
name = await textInput({ |
|||
label: `- ${teamNamePrefix}`, |
|||
validateValue: value => value.trim().length > 0 |
|||
}) |
|||
} catch (err) { |
|||
if (err.message === 'USER_ABORT') { |
|||
info('No name specified') |
|||
gracefulExit() |
|||
} else { |
|||
throw err |
|||
} |
|||
} |
|||
elapsed = stamp() |
|||
stopSpinner = wait(teamNamePrefix + name) |
|||
const res = await teams.edit({ id: team.id, name }) |
|||
stopSpinner() |
|||
|
|||
eraseLines(2) |
|||
if (res.error) { |
|||
error(res.error.message) |
|||
console.log(`${chalk.red(`✖ ${teamNamePrefix}`)}${name}`) |
|||
exit(1) |
|||
// TODO: maybe we want to ask the user to retry? not sure if
|
|||
// there's a scenario where that would be wanted
|
|||
} |
|||
|
|||
team = Object.assign(team, res) |
|||
|
|||
success(`Team name saved ${elapsed()}`) |
|||
console.log(chalk.cyan(`${tick} `) + teamNamePrefix + team.name + '\n') |
|||
|
|||
stopSpinner = wait('Saving') |
|||
await cfg.merge({ currentTeam: team }) |
|||
stopSpinner() |
|||
|
|||
await require('./invite')({ |
|||
teams, |
|||
args: [], |
|||
token, |
|||
introMsg: |
|||
'Invite your team mates! When done, press enter on an empty field', |
|||
noopMsg: `You can invite team mates later by running ${cmd( |
|||
'now teams invite' |
|||
)}` |
|||
}) |
|||
|
|||
gracefulExit() |
|||
} |
@ -1,160 +0,0 @@ |
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
|
|||
// Ours
|
|||
const regexes = require('../../lib/utils/input/regexes') |
|||
const wait = require('../../lib/utils/output/wait') |
|||
const cfg = require('../../lib/cfg') |
|||
const fatalError = require('../../lib/utils/fatal-error') |
|||
const cmd = require('../../lib/utils/output/cmd') |
|||
const info = require('../../lib/utils/output/info') |
|||
const stamp = require('../../lib/utils/output/stamp') |
|||
const param = require('../../lib/utils/output/param') |
|||
const { tick } = require('../../lib/utils/output/chars') |
|||
const rightPad = require('../../lib/utils/output/right-pad') |
|||
const textInput = require('../../lib/utils/input/text') |
|||
const eraseLines = require('../../lib/utils/output/erase-lines') |
|||
const success = require('../../lib/utils/output/success') |
|||
const error = require('../../lib/utils/output/error') |
|||
|
|||
function validateEmail(data) { |
|||
return regexes.email.test(data.trim()) || data.length === 0 |
|||
} |
|||
|
|||
const domains = Array.from( |
|||
new Set([ |
|||
'aol.com', |
|||
'gmail.com', |
|||
'google.com', |
|||
'yahoo.com', |
|||
'ymail.com', |
|||
'hotmail.com', |
|||
'live.com', |
|||
'outlook.com', |
|||
'inbox.com', |
|||
'mail.com', |
|||
'gmx.com', |
|||
'icloud.com' |
|||
]) |
|||
) |
|||
|
|||
function emailAutoComplete(value, teamSlug) { |
|||
const parts = value.split('@') |
|||
|
|||
if (parts.length === 2 && parts[1].length > 0) { |
|||
const [, host] = parts |
|||
let suggestion = false |
|||
|
|||
domains.unshift(teamSlug) |
|||
for (const domain of domains) { |
|||
if (domain.startsWith(host)) { |
|||
suggestion = domain.substr(host.length) |
|||
break |
|||
} |
|||
} |
|||
|
|||
domains.shift() |
|||
return suggestion |
|||
} |
|||
|
|||
return false |
|||
} |
|||
|
|||
module.exports = async function( |
|||
{ teams, args, token, introMsg, noopMsg = 'No changes made' } = {} |
|||
) { |
|||
const { user, currentTeam } = await cfg.read({ token }) |
|||
|
|||
domains.push(user.email.split('@')[1]) |
|||
|
|||
if (!currentTeam) { |
|||
let err = `You can't run this command under ${param( |
|||
user.username || user.email |
|||
)}.\n` |
|||
err += `${chalk.gray('>')} Run ${cmd('now switch')} to choose to a team.` |
|||
return fatalError(err) |
|||
} |
|||
|
|||
info(introMsg || `Inviting team members to ${chalk.bold(currentTeam.name)}`) |
|||
|
|||
if (args.length > 0) { |
|||
for (const email of args) { |
|||
if (regexes.email.test(email)) { |
|||
const stopSpinner = wait(email) |
|||
const elapsed = stamp() |
|||
// eslint-disable-next-line no-await-in-loop
|
|||
await teams.inviteUser({ teamId: currentTeam.id, email }) |
|||
stopSpinner() |
|||
console.log(`${chalk.cyan(tick)} ${email} ${elapsed()}`) |
|||
} else { |
|||
console.log(`${chalk.red(`✖ ${email}`)} ${chalk.gray('[invalid]')}`) |
|||
} |
|||
} |
|||
return |
|||
} |
|||
|
|||
const inviteUserPrefix = rightPad('Invite User', 14) |
|||
const emails = [] |
|||
let hasError = false |
|||
let email |
|||
do { |
|||
email = '' |
|||
try { |
|||
// eslint-disable-next-line no-await-in-loop
|
|||
email = await textInput({ |
|||
label: `- ${inviteUserPrefix}`, |
|||
validateValue: validateEmail, |
|||
autoComplete: value => emailAutoComplete(value, currentTeam.slug) |
|||
}) |
|||
} catch (err) { |
|||
if (err.message !== 'USER_ABORT') { |
|||
throw err |
|||
} |
|||
} |
|||
let elapsed |
|||
let stopSpinner |
|||
if (email) { |
|||
elapsed = stamp() |
|||
stopSpinner = wait(inviteUserPrefix + email) |
|||
try { |
|||
// eslint-disable-next-line no-await-in-loop
|
|||
await teams.inviteUser({ teamId: currentTeam.id, email }) |
|||
stopSpinner() |
|||
email = `${email} ${elapsed()}` |
|||
emails.push(email) |
|||
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`) |
|||
if (hasError) { |
|||
hasError = false |
|||
eraseLines(emails.length + 2) |
|||
info( |
|||
introMsg || |
|||
`Inviting team members to ${chalk.bold(currentTeam.name)}` |
|||
) |
|||
for (const email of emails) { |
|||
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`) |
|||
} |
|||
} |
|||
} catch (err) { |
|||
stopSpinner() |
|||
eraseLines(emails.length + 2) |
|||
error(err.message) |
|||
hasError = true |
|||
for (const email of emails) { |
|||
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`) |
|||
} |
|||
} |
|||
} |
|||
} while (email !== '') |
|||
|
|||
eraseLines(emails.length + 2) |
|||
|
|||
const n = emails.length |
|||
if (emails.length === 0) { |
|||
info(noopMsg) |
|||
} else { |
|||
success(`Invited ${n} team mate${n > 1 ? 's' : ''}`) |
|||
for (const email of emails) { |
|||
console.log(`${chalk.cyan(tick)} ${inviteUserPrefix}${email}`) |
|||
} |
|||
} |
|||
} |
@ -1,62 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
const wait = require('../../lib/utils/output/wait') |
|||
const cfg = require('../../lib/cfg') |
|||
const info = require('../../lib/utils/output/info') |
|||
const error = require('../../lib/utils/output/error') |
|||
const { tick: tickChar } = require('../../lib/utils/output/chars') |
|||
const table = require('../../lib/utils/output/table') |
|||
|
|||
module.exports = async function({ teams, token }) { |
|||
const stopSpinner = wait('Fetching teams') |
|||
const list = (await teams.ls()).teams |
|||
let { user, currentTeam } = await cfg.read({ token }) |
|||
const accountIsCurrent = !currentTeam |
|||
stopSpinner() |
|||
|
|||
if (accountIsCurrent) { |
|||
currentTeam = { |
|||
slug: user.username || user.email |
|||
} |
|||
} |
|||
|
|||
const teamList = list.map(({ slug, name }) => { |
|||
return { |
|||
name, |
|||
value: slug, |
|||
current: slug === currentTeam.slug ? tickChar : '' |
|||
} |
|||
}) |
|||
|
|||
teamList.unshift({ |
|||
name: user.email, |
|||
value: user.username || user.email, |
|||
current: (accountIsCurrent && tickChar) || '' |
|||
}) |
|||
|
|||
// Let's bring the current team to the beginning of the list
|
|||
if (!accountIsCurrent) { |
|||
const index = teamList.findIndex( |
|||
choice => choice.value === currentTeam.slug |
|||
) |
|||
const choice = teamList.splice(index, 1)[0] |
|||
teamList.unshift(choice) |
|||
} |
|||
|
|||
// Printing
|
|||
const count = teamList.length |
|||
if (!count) { |
|||
// Maybe should not happen
|
|||
error(`No team found`) |
|||
return |
|||
} |
|||
|
|||
info(`${chalk.bold(count)} team${count > 1 ? 's' : ''} found`) |
|||
console.log() |
|||
|
|||
table( |
|||
['', 'id', 'email / name'], |
|||
teamList.map(team => [team.current, team.value, team.name]), |
|||
[1, 5] |
|||
) |
|||
} |
@ -1,124 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
const wait = require('../../lib/utils/output/wait') |
|||
const listInput = require('../../lib/utils/input/list') |
|||
const cfg = require('../../lib/cfg') |
|||
const exit = require('../../lib/utils/exit') |
|||
const success = require('../../lib/utils/output/success') |
|||
const info = require('../../lib/utils/output/info') |
|||
const error = require('../../lib/utils/output/error') |
|||
const param = require('../../lib/utils/output/param') |
|||
|
|||
async function updateCurrentTeam({ cfg, newTeam } = {}) { |
|||
delete newTeam.created |
|||
delete newTeam.creator_id |
|||
await cfg.merge({ currentTeam: newTeam }) |
|||
} |
|||
|
|||
module.exports = async function({ teams, args, token }) { |
|||
let stopSpinner = wait('Fetching teams') |
|||
const list = (await teams.ls()).teams |
|||
let { user, currentTeam } = await cfg.read({ token }) |
|||
const accountIsCurrent = !currentTeam |
|||
stopSpinner() |
|||
|
|||
if (accountIsCurrent) { |
|||
currentTeam = { |
|||
slug: user.username || user.email |
|||
} |
|||
} |
|||
|
|||
if (args.length !== 0) { |
|||
const desiredSlug = args[0] |
|||
|
|||
const newTeam = list.find(team => team.slug === desiredSlug) |
|||
if (newTeam) { |
|||
await updateCurrentTeam({ cfg, newTeam }) |
|||
success(`The team ${chalk.bold(newTeam.name)} is now active!`) |
|||
return exit() |
|||
} |
|||
if (desiredSlug === user.username) { |
|||
stopSpinner = wait('Saving') |
|||
await cfg.remove('currentTeam') |
|||
stopSpinner() |
|||
return success(`Your account (${chalk.bold(desiredSlug)}) is now active!`) |
|||
} |
|||
error(`Could not find membership for team ${param(desiredSlug)}`) |
|||
return exit(1) |
|||
} |
|||
|
|||
const choices = list.map(({ slug, name }) => { |
|||
name = `${slug} (${name})` |
|||
if (slug === currentTeam.slug) { |
|||
name += ` ${chalk.bold('(current)')}` |
|||
} |
|||
|
|||
return { |
|||
name, |
|||
value: slug, |
|||
short: slug |
|||
} |
|||
}) |
|||
|
|||
const suffix = accountIsCurrent ? ` ${chalk.bold('(current)')}` : '' |
|||
|
|||
const userEntryName = user.username |
|||
? `${user.username} (${user.email})${suffix}` |
|||
: user.email |
|||
|
|||
choices.unshift({ |
|||
name: userEntryName, |
|||
value: user.email, |
|||
short: user.username |
|||
}) |
|||
|
|||
// Let's bring the current team to the beginning of the list
|
|||
if (!accountIsCurrent) { |
|||
const index = choices.findIndex(choice => choice.value === currentTeam.slug) |
|||
const choice = choices.splice(index, 1)[0] |
|||
choices.unshift(choice) |
|||
} |
|||
|
|||
let message |
|||
|
|||
if (currentTeam) { |
|||
message = `Switch to:` |
|||
} |
|||
|
|||
const choice = await listInput({ |
|||
message, |
|||
choices, |
|||
separator: false |
|||
}) |
|||
|
|||
// Abort
|
|||
if (!choice) { |
|||
info('No changes made') |
|||
return exit() |
|||
} |
|||
|
|||
const newTeam = list.find(item => item.slug === choice) |
|||
|
|||
// Switch to account
|
|||
if (!newTeam) { |
|||
if (currentTeam.slug === user.username || currentTeam.slug === user.email) { |
|||
info('No changes made') |
|||
return exit() |
|||
} |
|||
stopSpinner = wait('Saving') |
|||
await cfg.remove('currentTeam') |
|||
stopSpinner() |
|||
return success(`Your account (${chalk.bold(choice)}) is now active!`) |
|||
} |
|||
|
|||
if (newTeam.slug === currentTeam.slug) { |
|||
info('No changes made') |
|||
return exit() |
|||
} |
|||
|
|||
stopSpinner = wait('Saving') |
|||
await updateCurrentTeam({ cfg, newTeam }) |
|||
stopSpinner() |
|||
|
|||
success(`The team ${chalk.bold(newTeam.name)} is now active!`) |
|||
} |
@ -1,29 +0,0 @@ |
|||
machine: |
|||
node: |
|||
version: node |
|||
|
|||
compile: |
|||
override: |
|||
- npm run pack |
|||
post: |
|||
- cp packed/* $CIRCLE_ARTIFACTS |
|||
|
|||
deployment: |
|||
default: |
|||
branch: /.*/ |
|||
owner: zeit |
|||
commands: |
|||
- node ./scripts/slack.js |
|||
release: |
|||
tag: /.*/ |
|||
owner: zeit |
|||
commands: |
|||
- ghr -t $GITHUB_TOKEN -u $CIRCLE_PROJECT_USERNAME -r $CIRCLE_PROJECT_REPONAME -replace $CIRCLE_TAG packed |
|||
- npm publish |
|||
|
|||
dependencies: |
|||
pre: |
|||
- echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc |
|||
post: |
|||
- go get github.com/tcnksm/ghr |
|||
- npm install -g slackup |
@ -1,16 +0,0 @@ |
|||
/* eslint-disable no-var */ |
|||
|
|||
// Native
|
|||
var path = require('path') |
|||
var fs = require('fs') |
|||
|
|||
var dist = path.join(__dirname, 'dist') |
|||
var src = path.join(__dirname, 'src') |
|||
|
|||
// Don't install when developing locally
|
|||
if (fs.existsSync(src)) { |
|||
// eslint-disable-next-line unicorn/no-process-exit
|
|||
process.exit(0) |
|||
} |
|||
|
|||
require(path.join(dist, 'download.js')) |
@ -1,10 +0,0 @@ |
|||
// Native
|
|||
import fs from 'fs' |
|||
|
|||
export default function (file) { |
|||
const s = fs.statSync(file) |
|||
const newMode = s.mode | 64 | 8 | 1 |
|||
if (s.mode === newMode) return |
|||
const base8 = newMode.toString(8).slice(-3) |
|||
fs.chmodSync(file, base8) |
|||
} |
@ -1,193 +0,0 @@ |
|||
/* eslint-disable unicorn/no-process-exit */ |
|||
|
|||
// Native
|
|||
import fs from 'fs' |
|||
import path from 'path' |
|||
import { spawnSync } from 'child_process' |
|||
import zlib from 'zlib' |
|||
|
|||
// Packages
|
|||
import onDeath from 'death' |
|||
import fetch from 'node-fetch' |
|||
import retry from 'async-retry' |
|||
import which from 'which' |
|||
|
|||
// Utilities
|
|||
import plusxSync from './chmod' |
|||
import { |
|||
disableProgress, |
|||
enableProgress, |
|||
info, |
|||
showProgress, |
|||
warn |
|||
} from './log' |
|||
|
|||
fetch.Promise = Promise |
|||
global.Promise = Promise |
|||
let { platform } = process |
|||
if (detectAlpine()) platform = 'alpine' |
|||
|
|||
const packagePath = path.join(__dirname, '../../package.json') |
|||
const packageJSON = JSON.parse(fs.readFileSync(packagePath, 'utf8')) |
|||
|
|||
const now = path.join(__dirname, 'now') |
|||
const targetWin32 = path.join(__dirname, 'now.exe') |
|||
const target = platform === 'win32' ? targetWin32 : now |
|||
const partial = target + '.partial' |
|||
const backup = target + '.' + packageJSON.version + '.backup' |
|||
|
|||
function whichPromise (name) { |
|||
return new Promise((resolve, reject) => { |
|||
which(name, (error, result) => { |
|||
if (error) return reject(error) |
|||
resolve(result) |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
const platformToName = { |
|||
alpine: 'now-alpine', |
|||
darwin: 'now-macos', |
|||
linux: 'now-linux', |
|||
win32: 'now-win.exe' |
|||
} |
|||
|
|||
function detectAlpine () { |
|||
if (platform !== 'linux') return false |
|||
// https://github.com/sass/node-sass/issues/1589#issuecomment-265292579
|
|||
const ldd = spawnSync('ldd', [ process.execPath ]).stdout.toString() |
|||
return /\bmusl\b/.test(ldd) |
|||
} |
|||
|
|||
async function download() { |
|||
try { |
|||
fs.writeFileSync( |
|||
now, |
|||
'#!/usr/bin/env node\n' + |
|||
'console.log("Please wait until the \'now\' installation completes!")\n' |
|||
) |
|||
} catch (err) { |
|||
if (err.code === 'EACCES') { |
|||
warn( |
|||
'Please try installing now CLI again with the `--unsafe-perm` option.' |
|||
) |
|||
info('Example: `npm i -g --unsafe-perm now`') |
|||
|
|||
process.exit() |
|||
} |
|||
|
|||
throw err |
|||
} |
|||
|
|||
onDeath(() => { |
|||
fs.writeFileSync( |
|||
now, |
|||
'#!/usr/bin/env node\n' + |
|||
'console.log("The \'now\' installation did not complete successfully.")\n' + |
|||
'console.log("Please run \'npm i -g now\' to reinstall!")\n' |
|||
) |
|||
process.exit() |
|||
}) |
|||
|
|||
info('For the source code, check out: https://github.com/zeit/now-cli') |
|||
|
|||
// Print an empty line
|
|||
console.log('') |
|||
|
|||
await retry(async () => { |
|||
enableProgress('Downloading now CLI ' + packageJSON.version) |
|||
showProgress(0) |
|||
|
|||
try { |
|||
const name = platformToName[platform] |
|||
const url = `https://cdn.zeit.co/releases/now-cli/${packageJSON.version}/${name}` |
|||
const resp = await fetch(url, { compress: false }) |
|||
|
|||
if (resp.status !== 200) { |
|||
throw new Error(resp.statusText + ' ' + url) |
|||
} |
|||
|
|||
const size = resp.headers.get('content-length') |
|||
|
|||
if (!size) { |
|||
throw new Error('Not found (content-length is absent)') |
|||
} |
|||
|
|||
const ws = fs.createWriteStream(partial) |
|||
|
|||
await new Promise((resolve, reject) => { |
|||
let bytesRead = 0 |
|||
|
|||
resp.body |
|||
.on('error', reject) |
|||
.on('data', chunk => { |
|||
bytesRead += chunk.length |
|||
showProgress(100 * bytesRead / size) |
|||
}) |
|||
|
|||
const gunzip = zlib.createGunzip() |
|||
|
|||
gunzip |
|||
.on('error', reject) |
|||
|
|||
resp.body.pipe(gunzip).pipe(ws) |
|||
|
|||
ws |
|||
.on('error', reject) |
|||
.on('close', () => { |
|||
showProgress(100) |
|||
resolve() |
|||
}) |
|||
}) |
|||
} finally { |
|||
disableProgress() |
|||
} |
|||
}, { |
|||
retries: 500, |
|||
onRetry: (err) => console.error(err) |
|||
}) |
|||
|
|||
fs.renameSync(partial, target) |
|||
fs.writeFileSync(backup, fs.readFileSync(target)) |
|||
} |
|||
|
|||
async function main() { |
|||
if (fs.existsSync(backup)) { |
|||
fs.writeFileSync(target, fs.readFileSync(backup)) |
|||
} else { |
|||
await download() |
|||
} |
|||
|
|||
if (platform === 'win32') { |
|||
// Now.exe is executed only
|
|||
fs.unlinkSync(now) |
|||
try { |
|||
// Workaround for https://github.com/npm/cmd-shim/pull/25
|
|||
const globalPath = path.dirname(await whichPromise('npm')) |
|||
const gitBashFile = path.join(globalPath, 'now') |
|||
fs.writeFileSync( |
|||
gitBashFile, |
|||
'#!/bin/sh\n' + |
|||
'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")\n' + |
|||
'\n' + |
|||
'case `uname` in\n' + |
|||
' *CYGWIN*) basedir=`cygpath -w "$basedir"`;;\n' + |
|||
'esac\n' + |
|||
'\n' + |
|||
fs.readFileSync(gitBashFile, 'utf8') |
|||
) |
|||
} catch (err) { |
|||
if (err.code !== 'ENOENT') { |
|||
// Not a problem. only git cmd will not work
|
|||
console.error(err) |
|||
} |
|||
} |
|||
} else { |
|||
plusxSync(now) |
|||
} |
|||
} |
|||
|
|||
main().catch(err => { |
|||
console.error(err) |
|||
process.exit(2) |
|||
}) |
@ -1,43 +0,0 @@ |
|||
// Packages
|
|||
import assert from 'assert' |
|||
import chalk from 'chalk' |
|||
import Progress from 'progress' |
|||
|
|||
let bar |
|||
|
|||
export function enableProgress(text) { |
|||
assert(!bar) |
|||
|
|||
bar = new Progress(`> ${text} [:bar] :percent`, { |
|||
stream: process.stdout, |
|||
width: 20, |
|||
complete: '=', |
|||
incomplete: ' ', |
|||
total: 100 |
|||
}) |
|||
} |
|||
|
|||
export function info(text) { |
|||
console.log(`> ${text}`) |
|||
} |
|||
|
|||
export function warn(text) { |
|||
console.log(chalk.red('> Warning!'), text) |
|||
} |
|||
|
|||
export function showProgress(percentage) { |
|||
assert(bar) |
|||
bar.update(percentage / 100) |
|||
} |
|||
|
|||
export function disableProgress() { |
|||
assert(bar) |
|||
|
|||
// It is auto-completed once it updates to 100
|
|||
// otherwise it outputs a blank line
|
|||
if (!bar.complete) { |
|||
bar.terminate() |
|||
} |
|||
|
|||
bar = undefined |
|||
} |
@ -1,34 +0,0 @@ |
|||
// Native
|
|||
const path = require('path') |
|||
|
|||
module.exports = { |
|||
target: 'node', |
|||
node: { |
|||
__dirname: false, |
|||
__filename: false, |
|||
process: false |
|||
}, |
|||
entry: [ |
|||
'./src/index.js' |
|||
], |
|||
output: { |
|||
path: path.join(__dirname, 'dist'), |
|||
filename: 'download.js' |
|||
}, |
|||
module: { |
|||
loaders: [ { |
|||
test: /.js$/, |
|||
loader: 'babel-loader', |
|||
exclude: /node_modules/, |
|||
query: { |
|||
plugins: [ |
|||
'transform-async-to-generator', |
|||
'transform-runtime' |
|||
], |
|||
presets: [ |
|||
'es2015' |
|||
] |
|||
} |
|||
} ] |
|||
} |
|||
} |
@ -1,83 +0,0 @@ |
|||
// Native
|
|||
const { parse } = require('url') |
|||
const http = require('http') |
|||
const https = require('https') |
|||
|
|||
// Packages
|
|||
const fetch = require('node-fetch') |
|||
|
|||
/** |
|||
* Returns a `fetch` version with a similar |
|||
* API to the browser's configured with a |
|||
* HTTP2 agent. |
|||
* |
|||
* It encodes `body` automatically as JSON. |
|||
* |
|||
* @param {String} host |
|||
* @return {Function} fetch |
|||
*/ |
|||
|
|||
module.exports = class Agent { |
|||
constructor(url, { tls = true, debug } = {}) { |
|||
this._url = url |
|||
const parsed = parse(url) |
|||
this._protocol = parsed.protocol |
|||
this._debug = debug |
|||
if (tls) { |
|||
this._initAgent() |
|||
} |
|||
} |
|||
|
|||
_initAgent() { |
|||
const module = this._protocol === 'https:' ? https : http |
|||
this._agent = new module.Agent({ |
|||
keepAlive: true, |
|||
keepAliveMsecs: 10000, |
|||
maxSockets: 8 |
|||
}).on('error', err => this._onError(err, this._agent)) |
|||
} |
|||
|
|||
_onError(err, agent) { |
|||
if (this._debug) { |
|||
console.log(`> [debug] agent connection error ${err}\n${err.stack}`) |
|||
} |
|||
if (this._agent === agent) { |
|||
this._agent = null |
|||
} |
|||
} |
|||
|
|||
fetch(path, opts = {}) { |
|||
if (!this._agent) { |
|||
if (this._debug) { |
|||
console.log('> [debug] re-initializing agent') |
|||
} |
|||
this._initAgent() |
|||
} |
|||
|
|||
const { body } = opts |
|||
if (this._agent) { |
|||
opts.agent = this._agent |
|||
} |
|||
|
|||
if (body && typeof body === 'object' && typeof body.pipe !== 'function') { |
|||
opts.headers['Content-Type'] = 'application/json' |
|||
opts.body = JSON.stringify(body) |
|||
} |
|||
|
|||
if (opts.body && typeof body.pipe !== 'function') { |
|||
opts.headers['Content-Length'] = Buffer.byteLength(opts.body) |
|||
} |
|||
|
|||
return fetch(this._url + path, opts) |
|||
} |
|||
|
|||
close() { |
|||
if (this._debug) { |
|||
console.log('> [debug] closing agent') |
|||
} |
|||
|
|||
if (this._agent) { |
|||
this._agent.destroy() |
|||
} |
|||
} |
|||
} |
@ -1,881 +0,0 @@ |
|||
// Packages
|
|||
const { readFileSync } = require('fs') |
|||
const publicSuffixList = require('psl') |
|||
const minimist = require('minimist') |
|||
const ms = require('ms') |
|||
const chalk = require('chalk') |
|||
const { write: copy } = require('clipboardy') |
|||
|
|||
// Ours
|
|||
const promptBool = require('../lib/utils/input/prompt-bool') |
|||
const info = require('../lib/utils/output/info') |
|||
const param = require('../lib/utils/output/param') |
|||
const wait = require('../lib/utils/output/wait') |
|||
const success = require('../lib/utils/output/success') |
|||
const uid = require('../lib/utils/output/uid') |
|||
const eraseLines = require('../lib/utils/output/erase-lines') |
|||
const stamp = require('../lib/utils/output/stamp') |
|||
const error = require('../lib/utils/output/error') |
|||
const treatBuyError = require('../lib/utils/domains/treat-buy-error') |
|||
const scaleInfo = require('./scale-info') |
|||
const { DOMAIN_VERIFICATION_ERROR } = require('./errors') |
|||
const isZeitWorld = require('./is-zeit-world') |
|||
const resolve4 = require('./dns') |
|||
const toHost = require('./to-host') |
|||
const exit = require('./utils/exit') |
|||
const Now = require('./') |
|||
|
|||
const argv = minimist(process.argv.slice(2), { |
|||
boolean: ['no-clipboard'], |
|||
alias: { 'no-clipboard': 'C' } |
|||
}) |
|||
|
|||
const isTTY = process.stdout.isTTY |
|||
const clipboard = !argv['no-clipboard'] |
|||
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/ |
|||
|
|||
module.exports = class Alias extends Now { |
|||
async ls(deployment) { |
|||
if (deployment) { |
|||
const target = await this.findDeployment(deployment) |
|||
|
|||
if (!target) { |
|||
const err = new Error( |
|||
`Aliases not found by "${deployment}". Run ${chalk.dim( |
|||
'`now alias ls`' |
|||
)} to see your aliases.` |
|||
) |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
return this.listAliases(target.uid) |
|||
} |
|||
|
|||
return this.listAliases() |
|||
} |
|||
|
|||
async rm(_alias) { |
|||
return this.retry(async bail => { |
|||
const res = await this._fetch(`/now/aliases/${_alias.uid}`, { |
|||
method: 'DELETE' |
|||
}) |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
if (res.status !== 200) { |
|||
const err = new Error('Deletion failed. Try again later.') |
|||
throw err |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async findDeployment(deployment) { |
|||
const list = await this.list() |
|||
|
|||
let key |
|||
let val |
|||
|
|||
if (/\./.test(deployment)) { |
|||
val = toHost(deployment) |
|||
key = 'url' |
|||
} else { |
|||
val = deployment |
|||
key = 'uid' |
|||
} |
|||
|
|||
const depl = list.find(d => { |
|||
if (d[key] === val) { |
|||
if (this._debug) { |
|||
console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`) |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
// Match prefix
|
|||
if (`${val}.now.sh` === d.url) { |
|||
if (this._debug) { |
|||
console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`) |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
return false |
|||
}) |
|||
|
|||
return depl |
|||
} |
|||
|
|||
async updatePathBasedroutes(alias, rules, domains) { |
|||
alias = await this.maybeSetUpDomain(alias, domains) |
|||
return this.upsertPathAlias(alias, rules) |
|||
} |
|||
|
|||
async upsertPathAlias(alias, rules) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] /now/aliases #${attempt}`) |
|||
} |
|||
|
|||
const rulesData = this.readRulesFile(rules) |
|||
const ruleCount = rulesData.rules.length |
|||
const res = await this._fetch(`/now/aliases`, { |
|||
method: 'POST', |
|||
body: { alias, rules: rulesData.rules } |
|||
}) |
|||
|
|||
const body = await res.json() |
|||
body.ruleCount = ruleCount |
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] /now/aliases #${attempt}`) |
|||
} |
|||
|
|||
// 409 conflict is returned if it already exists
|
|||
if (res.status === 409) { |
|||
return { uid: body.error.uid } |
|||
} |
|||
if (res.status === 422) { |
|||
return body |
|||
} |
|||
|
|||
// No retry on authorization problems
|
|||
if (res.status === 403) { |
|||
const code = body.error.code |
|||
|
|||
if (code === 'custom_domain_needs_upgrade') { |
|||
const err = new Error( |
|||
`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray( |
|||
'`' |
|||
)}${chalk.cyan('now upgrade')}${chalk.gray('`')}.` |
|||
) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
if (code === 'alias_in_use') { |
|||
const err = new Error( |
|||
`The alias you are trying to configure (${chalk.underline( |
|||
chalk.bold(alias) |
|||
)}) is already in use by a different account.` |
|||
) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
if (code === 'forbidden') { |
|||
const err = new Error( |
|||
'The domain you are trying to use as an alias is already in use by a different account.' |
|||
) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
return bail(new Error('Authorization error')) |
|||
} |
|||
|
|||
// All other errors
|
|||
if (body.error) { |
|||
const code = body.error.code |
|||
|
|||
if (code === 'cert_missing') { |
|||
console.log( |
|||
`> Provisioning certificate for ${chalk.underline( |
|||
chalk.bold(alias) |
|||
)}` |
|||
) |
|||
|
|||
try { |
|||
await this.createCert(alias) |
|||
} catch (err) { |
|||
// We bail to avoid retrying the whole process
|
|||
// of aliasing which would involve too many
|
|||
// retries on certificate provisioning
|
|||
return bail(err) |
|||
} |
|||
|
|||
// Try again, but now having provisioned the certificate
|
|||
return this.upsertPathAlias(alias, rules) |
|||
} |
|||
|
|||
if (code === 'cert_expired') { |
|||
console.log( |
|||
`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}` |
|||
) |
|||
|
|||
try { |
|||
await this.createCert(alias, { renew: true }) |
|||
} catch (err) { |
|||
return bail(err) |
|||
} |
|||
} |
|||
|
|||
return bail(new Error(body.error.message)) |
|||
} |
|||
|
|||
// The two expected succesful cods are 200 and 304
|
|||
if (res.status !== 200 && res.status !== 304) { |
|||
throw new Error('Unhandled error') |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
readRulesFile(rules) { |
|||
try { |
|||
const rulesJson = readFileSync(rules, 'utf8') |
|||
return JSON.parse(rulesJson) |
|||
} catch (err) { |
|||
console.error(`Reading rules file ${rules} failed: ${err}`) |
|||
} |
|||
} |
|||
|
|||
async set(deployment, alias, domains, currentTeam, user) { |
|||
alias = alias.replace(/^https:\/\//i, '') |
|||
|
|||
if (alias.indexOf('.') === -1) { |
|||
// `.now.sh` domain is implied if just the subdomain is given
|
|||
alias += '.now.sh' |
|||
} |
|||
const depl = await this.findDeployment(deployment) |
|||
if (!depl) { |
|||
const err = new Error( |
|||
`Deployment not found by "${deployment}". Run ${chalk.dim( |
|||
'`now ls`' |
|||
)} to see your deployments.` |
|||
) |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
const aliasDepl = (await this.listAliases()).find(e => e.alias === alias) |
|||
if (aliasDepl && aliasDepl.rules) { |
|||
if (isTTY) { |
|||
try { |
|||
const msg = |
|||
`> Path alias exists with ${aliasDepl.rules.length} rule${aliasDepl |
|||
.rules.length > 1 |
|||
? 's' |
|||
: ''}.\n` +
|
|||
`> Are you sure you want to update ${alias} to be a normal alias?\n` |
|||
|
|||
const confirmation = await promptBool(msg, { |
|||
trailing: '\n' |
|||
}) |
|||
|
|||
if (!confirmation) { |
|||
info('Aborted') |
|||
return exit(1) |
|||
} |
|||
} catch (err) { |
|||
console.log(err) |
|||
} |
|||
} else { |
|||
console.log( |
|||
`Overwriting path alias with ${aliasDepl.rules.length} rule${aliasDepl |
|||
.rules.length > 1 |
|||
? 's' |
|||
: ''} to be a normal alias.` |
|||
) |
|||
} |
|||
} |
|||
|
|||
let aliasedDeployment = null |
|||
let shouldScaleDown = false |
|||
|
|||
if (aliasDepl && depl.scale) { |
|||
aliasedDeployment = await this.findDeployment(aliasDepl.deploymentId) |
|||
if ( |
|||
aliasedDeployment && |
|||
aliasedDeployment.scale && |
|||
aliasedDeployment.scale.current >= depl.scale.current && |
|||
(aliasedDeployment.scale.min > depl.scale.min || |
|||
aliasedDeployment.scale.max > depl.scale.max) |
|||
) { |
|||
shouldScaleDown = true |
|||
console.log( |
|||
`> Alias ${alias} points to ${chalk.bold( |
|||
aliasedDeployment.url |
|||
)} (${chalk.bold(aliasedDeployment.scale.current + ' instances')})` |
|||
) |
|||
// Test if we need to change the scale or just update the rules
|
|||
console.log( |
|||
`> Scaling ${depl.url} to ${chalk.bold( |
|||
aliasedDeployment.scale.current + ' instances' |
|||
)} atomically` // Not a typo
|
|||
) |
|||
if (depl.scale.current !== aliasedDeployment.scale.current) { |
|||
if (depl.scale.max < 1) { |
|||
if (this._debug) { |
|||
console.log( |
|||
'Updating max scale to 1 so that deployment may be unfrozen.' |
|||
) |
|||
} |
|||
await this.setScale(depl.uid, { |
|||
min: depl.scale.min, |
|||
max: Math.max(aliasedDeployment.scale.max, 1) |
|||
}) |
|||
} |
|||
if (depl.scale.current < 1) { |
|||
if (this._debug) { |
|||
console.log(`> Deployment ${depl.url} is frozen, unfreezing...`) |
|||
} |
|||
await this.unfreeze(depl) |
|||
if (this._debug) { |
|||
console.log( |
|||
`> Deployment is now unfrozen, scaling it to match current instance count` |
|||
) |
|||
} |
|||
} |
|||
// Scale it to current limit
|
|||
if (depl.scale.current !== aliasedDeployment.scale.current) { |
|||
if (this._debug) { |
|||
console.log(`> Scaling deployment to match current scale.`) |
|||
} |
|||
await this.setScale(depl.uid, { |
|||
min: aliasedDeployment.scale.current, |
|||
max: aliasedDeployment.scale.current |
|||
}) |
|||
} |
|||
await scaleInfo(this, depl.url) |
|||
if (this._debug) { |
|||
console.log(`> Updating scaling rules for deployment.`) |
|||
} |
|||
} |
|||
|
|||
await this.setScale(depl.uid, { |
|||
min: Math.max(aliasedDeployment.scale.min, depl.scale.min), |
|||
max: Math.max(aliasedDeployment.scale.max, depl.scale.max) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
alias = await this.maybeSetUpDomain(alias, domains, currentTeam, user) |
|||
|
|||
const aliasTime = Date.now() |
|||
const newAlias = await this.createAlias(depl, alias) |
|||
if (!newAlias) { |
|||
throw new Error( |
|||
`Unexpected error occurred while setting up alias: ${JSON.stringify( |
|||
newAlias |
|||
)}` |
|||
) |
|||
} |
|||
const { created, uid } = newAlias |
|||
if (created) { |
|||
const output = `${chalk.cyan( |
|||
'> Success!' |
|||
)} ${alias} now points to ${chalk.bold(depl.url)}! ${chalk.grey( |
|||
'[' + ms(Date.now() - aliasTime) + ']' |
|||
)}` |
|||
if (isTTY && clipboard) { |
|||
try { |
|||
await copy(depl.url) |
|||
} catch (err) { |
|||
} finally { |
|||
console.log(output) |
|||
} |
|||
} else { |
|||
console.log(output) |
|||
} |
|||
} else { |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Alias already exists ${chalk.dim( |
|||
`(${uid})` |
|||
)}.` |
|||
) |
|||
} |
|||
if (aliasedDeployment && shouldScaleDown) { |
|||
const scaleDown = Date.now() |
|||
await this.setScale(aliasedDeployment.uid, { min: 0, max: 1 }) |
|||
console.log( |
|||
`> Scaled ${chalk.gray( |
|||
aliasedDeployment.url |
|||
)} down to 1 instance ${chalk.gray( |
|||
'[' + ms(Date.now() - scaleDown) + ']' |
|||
)}` |
|||
) |
|||
} |
|||
} |
|||
|
|||
createAlias(depl, alias) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time( |
|||
`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}` |
|||
) |
|||
} |
|||
|
|||
const res = await this._fetch(`/now/deployments/${depl.uid}/aliases`, { |
|||
method: 'POST', |
|||
body: { alias } |
|||
}) |
|||
|
|||
const body = await res.json() |
|||
if (this._debug) { |
|||
console.timeEnd( |
|||
`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}` |
|||
) |
|||
} |
|||
|
|||
// 409 conflict is returned if it already exists
|
|||
if (res.status === 409) { |
|||
return { uid: body.error.uid } |
|||
} |
|||
|
|||
// No retry on authorization problems
|
|||
if (res.status === 403) { |
|||
const code = body.error.code |
|||
|
|||
if (code === 'custom_domain_needs_upgrade') { |
|||
const err = new Error( |
|||
`Custom domains are only enabled for premium accounts. Please upgrade by running ${chalk.gray( |
|||
'`' |
|||
)}${chalk.cyan('now upgrade')}${chalk.gray('`')}.` |
|||
) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
if (code === 'alias_in_use') { |
|||
const err = new Error( |
|||
`The alias you are trying to configure (${chalk.underline( |
|||
chalk.bold(alias) |
|||
)}) is already in use by a different account.` |
|||
) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
if (code === 'forbidden') { |
|||
const err = new Error( |
|||
'The domain you are trying to use as an alias is already in use by a different account.' |
|||
) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
return bail(new Error('Authorization error')) |
|||
} |
|||
|
|||
// All other errors
|
|||
if (body.error) { |
|||
const code = body.error.code |
|||
|
|||
if (code === 'deployment_not_found') { |
|||
return bail(new Error('Deployment not found')) |
|||
} |
|||
|
|||
if (code === 'cert_missing') { |
|||
console.log( |
|||
`> Provisioning certificate for ${chalk.underline( |
|||
chalk.bold(alias) |
|||
)}` |
|||
) |
|||
|
|||
try { |
|||
await this.createCert(alias) |
|||
} catch (err) { |
|||
// We bail to avoid retrying the whole process
|
|||
// of aliasing which would involve too many
|
|||
// retries on certificate provisioning
|
|||
return bail(err) |
|||
} |
|||
|
|||
// Try again, but now having provisioned the certificate
|
|||
return this.createAlias(depl, alias) |
|||
} |
|||
|
|||
if (code === 'cert_expired') { |
|||
console.log( |
|||
`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}` |
|||
) |
|||
|
|||
try { |
|||
await this.createCert(alias, { renew: true }) |
|||
} catch (err) { |
|||
return bail(err) |
|||
} |
|||
} |
|||
|
|||
return bail(new Error(body.error.message)) |
|||
} |
|||
|
|||
// The two expected succesful cods are 200 and 304
|
|||
if (res.status !== 200 && res.status !== 304) { |
|||
throw new Error('Unhandled error') |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
async setupRecord(domain, name) { |
|||
await this.setupDomain(domain) |
|||
|
|||
if (this._debug) { |
|||
console.log(`> [debug] Setting up record "${name}" for "${domain}"`) |
|||
} |
|||
|
|||
const type = name === '' ? 'ALIAS' : 'CNAME' |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] /domains/${domain}/records #${attempt}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/domains/${domain}/records`, { |
|||
method: 'POST', |
|||
body: { type, name: name === '' ? name : '*', value: 'alias.zeit.co' } |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] /domains/${domain}/records #${attempt}`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
throw new Error(body.error.message) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
async maybeSetUpDomain(alias, domains, currentTeam, user) { |
|||
const gracefulExit = () => { |
|||
this.close() |
|||
domains.close() |
|||
// eslint-disable-next-line unicorn/no-process-exit
|
|||
process.exit() |
|||
} |
|||
// Make alias lowercase
|
|||
alias = alias.toLowerCase() |
|||
|
|||
// Trim leading and trailing dots
|
|||
// for example: `google.com.` => `google.com`
|
|||
alias = alias.replace(/^\.+/, '').replace(/\.+$/, '') |
|||
// Evaluate the alias
|
|||
if (/\./.test(alias)) { |
|||
alias = toHost(alias) |
|||
} else { |
|||
if (this._debug) { |
|||
console.log(`> [debug] suffixing \`.now.sh\` to alias ${alias}`) |
|||
} |
|||
|
|||
alias = `${alias}.now.sh` |
|||
} |
|||
|
|||
if (!domainRegex.test(alias)) { |
|||
const err = new Error(`Invalid alias "${alias}"`) |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
if (!/\.now\.sh$/.test(alias)) { |
|||
console.log(`> ${chalk.bold(chalk.underline(alias))} is a custom domain.`) |
|||
|
|||
let stopSpinner = wait('Fetching domain info') |
|||
|
|||
let elapsed = stamp() |
|||
const parsed = publicSuffixList.parse(alias) |
|||
const pricePromise = domains.price(parsed.domain).catch(() => { |
|||
// Can be safely ignored
|
|||
}) |
|||
const canBePurchased = await domains.status(parsed.domain) |
|||
const aliasParam = param(parsed.domain) |
|||
let price |
|||
let period |
|||
|
|||
stopSpinner() |
|||
|
|||
if (canBePurchased) { |
|||
try { |
|||
const json = await pricePromise |
|||
price = json.price |
|||
period = json.period |
|||
} catch (err) { |
|||
// Can be safely ignored
|
|||
} |
|||
} |
|||
if (canBePurchased && price && period) { |
|||
const periodMsg = `${period}yr${period > 1 ? 's' : ''}` |
|||
info( |
|||
`The domain ${aliasParam} is ${chalk.italic( |
|||
'available' |
|||
)} to buy under ${chalk.bold( |
|||
(currentTeam && currentTeam.slug) || user.username || user.email |
|||
)}! ${elapsed()}` |
|||
) |
|||
const confirmation = await promptBool( |
|||
`Buy now for ${chalk.bold(`$${price}`)} (${periodMsg})?` |
|||
) |
|||
eraseLines(1) |
|||
if (!confirmation) { |
|||
info('Aborted') |
|||
gracefulExit() |
|||
} |
|||
elapsed = stamp() |
|||
stopSpinner = wait('Purchasing') |
|||
let domain |
|||
try { |
|||
domain = await domains.buy(parsed.domain) |
|||
} catch (err) { |
|||
stopSpinner() |
|||
treatBuyError(err) |
|||
gracefulExit() |
|||
} |
|||
|
|||
stopSpinner() |
|||
success(`Domain purchased and created ${uid(domain.uid)} ${elapsed()}`) |
|||
|
|||
stopSpinner = wait('Verifying nameservers') |
|||
|
|||
let domainInfo |
|||
|
|||
try { |
|||
domainInfo = await this.setupDomain(parsed.domain) |
|||
} catch (err) { |
|||
if (this._debug) { |
|||
console.log('> [debug] Error while trying to setup the domain', err) |
|||
} |
|||
} |
|||
|
|||
stopSpinner() |
|||
|
|||
if (!domainInfo.verified) { |
|||
const tld = param(`.${parsed.tld}`) |
|||
error( |
|||
'The nameservers are pending propagation. Please try again shortly' |
|||
) |
|||
info( |
|||
`The ${tld} servers might take some extra time to reflect changes` |
|||
) |
|||
gracefulExit() |
|||
} |
|||
} |
|||
|
|||
console.log( |
|||
`> Verifying the DNS settings for ${chalk.bold( |
|||
chalk.underline(alias) |
|||
)} (see ${chalk.underline('https://zeit.world')} for help)` |
|||
) |
|||
|
|||
const _domain = publicSuffixList.parse(alias).domain |
|||
let _domainInfo |
|||
try { |
|||
_domainInfo = await this.getDomain(_domain) |
|||
} catch (err) { |
|||
if (err.status === 404) { |
|||
// It's ok if the domain was not found – we'll add it when creating
|
|||
// the alias
|
|||
} else { |
|||
throw err |
|||
} |
|||
} |
|||
const domainInfo = |
|||
_domainInfo && !_domainInfo.error ? _domainInfo : undefined |
|||
const { domain, nameservers } = domainInfo |
|||
? { domain: _domain } |
|||
: await this.getNameservers(alias) |
|||
const usingZeitWorld = domainInfo |
|||
? !domainInfo.isExternal |
|||
: isZeitWorld(nameservers) |
|||
let skipDNSVerification = false |
|||
|
|||
if (this._debug) { |
|||
if (domainInfo) { |
|||
console.log( |
|||
`> [debug] Found domain ${domain} with verified:${domainInfo.verified}` |
|||
) |
|||
} else { |
|||
console.log( |
|||
`> [debug] Found domain ${domain} and nameservers ${nameservers}` |
|||
) |
|||
} |
|||
} |
|||
|
|||
if (!usingZeitWorld && domainInfo) { |
|||
if (domainInfo.verified) { |
|||
skipDNSVerification = true |
|||
} else if (domainInfo.uid) { |
|||
const { verified, created } = await this.setupDomain(domain, { |
|||
isExternal: true |
|||
}) |
|||
if (!(created && verified)) { |
|||
const e = new Error( |
|||
`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.` |
|||
) |
|||
e.userError = true |
|||
throw e |
|||
} |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Domain ${chalk.bold( |
|||
chalk.underline(domain) |
|||
)} verified` |
|||
) |
|||
} |
|||
} |
|||
|
|||
try { |
|||
if (!skipDNSVerification) { |
|||
await this.verifyOwnership(alias) |
|||
} |
|||
} catch (err) { |
|||
if (err.userError) { |
|||
// A user error would imply that verification failed
|
|||
// in which case we attempt to correct the dns
|
|||
// configuration (if we can!)
|
|||
try { |
|||
if (usingZeitWorld) { |
|||
console.log( |
|||
`> Detected ${chalk.bold( |
|||
chalk.underline('zeit.world') |
|||
)} nameservers! Configuring records.` |
|||
) |
|||
const record = alias.substr(0, alias.length - domain.length) |
|||
|
|||
// Lean up trailing and leading dots
|
|||
const _record = record.replace(/^\./, '').replace(/\.$/, '') |
|||
const _domain = domain.replace(/^\./, '').replace(/\.$/, '') |
|||
|
|||
if (_record === '') { |
|||
await this.setupRecord(_domain, '*') |
|||
} |
|||
|
|||
await this.setupRecord(_domain, _record) |
|||
|
|||
this.recordSetup = true |
|||
console.log('> DNS Configured! Verifying propagation…') |
|||
|
|||
try { |
|||
await this.retry(() => this.verifyOwnership(alias), { |
|||
retries: 10, |
|||
maxTimeout: 8000 |
|||
}) |
|||
} catch (err2) { |
|||
const e = new Error( |
|||
'> We configured the DNS settings for your alias, but we were unable to ' + |
|||
"verify that they've propagated. Please try the alias again later." |
|||
) |
|||
e.userError = true |
|||
throw e |
|||
} |
|||
} else { |
|||
console.log( |
|||
`> Resolved IP: ${err.ip |
|||
? `${chalk.underline(err.ip)} (unknown)` |
|||
: chalk.dim('none')}` |
|||
) |
|||
console.log( |
|||
`> Nameservers: ${nameservers && nameservers.length |
|||
? nameservers.map(ns => chalk.underline(ns)).join(', ') |
|||
: chalk.dim('none')}` |
|||
) |
|||
throw err |
|||
} |
|||
} catch (e) { |
|||
if (e.userError) { |
|||
throw e |
|||
} |
|||
|
|||
throw err |
|||
} |
|||
} else { |
|||
throw err |
|||
} |
|||
} |
|||
|
|||
if (!usingZeitWorld && !skipDNSVerification) { |
|||
if (this._debug) { |
|||
console.log( |
|||
`> [debug] Trying to register a non-ZeitWorld domain ${domain} for the current user` |
|||
) |
|||
} |
|||
|
|||
const { uid, verified, created } = await this.setupDomain(domain, { |
|||
isExternal: true |
|||
}) |
|||
if (!(created && verified)) { |
|||
const e = new Error( |
|||
`> Failed to verify the ownership of ${domain}, please refer to 'now domain --help'.` |
|||
) |
|||
e.userError = true |
|||
throw e |
|||
} |
|||
console.log( |
|||
`${chalk.cyan('> Success!')} Domain ${chalk.bold( |
|||
chalk.underline(domain) |
|||
)} ${chalk.dim(`(${uid})`)} added` |
|||
) |
|||
} |
|||
|
|||
console.log(`> Verification ${chalk.bold('OK')}!`) |
|||
} |
|||
return alias |
|||
} |
|||
|
|||
verifyOwnership(domain) { |
|||
return this.retry( |
|||
async bail => { |
|||
const targets = await resolve4('alias.zeit.co') |
|||
|
|||
if (targets.length <= 0) { |
|||
return bail(new Error('Unable to resolve alias.zeit.co')) |
|||
} |
|||
|
|||
let ips = [] |
|||
|
|||
try { |
|||
ips = await resolve4(domain) |
|||
} catch (err) { |
|||
if ( |
|||
err.code === 'ENODATA' || |
|||
err.code === 'ESERVFAIL' || |
|||
err.code === 'ENOTFOUND' |
|||
) { |
|||
// Not errors per se, just absence of records
|
|||
if (this._debug) { |
|||
console.log(`> [debug] No records found for "${domain}"`) |
|||
} |
|||
|
|||
const err = new Error(DOMAIN_VERIFICATION_ERROR) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
throw err |
|||
} |
|||
|
|||
if (ips.length <= 0) { |
|||
const err = new Error(DOMAIN_VERIFICATION_ERROR) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
for (const ip of ips) { |
|||
if (targets.indexOf(ip) === -1) { |
|||
const err = new Error( |
|||
`The domain ${domain} has an A record ${chalk.bold( |
|||
ip |
|||
)} that doesn't resolve to ${chalk.bold( |
|||
chalk.underline('alias.zeit.co') |
|||
)}.\n> ` + DOMAIN_VERIFICATION_ERROR
|
|||
) |
|||
err.ip = ip |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
} |
|||
}, |
|||
{ retries: 5 } |
|||
) |
|||
} |
|||
} |
@ -1,138 +0,0 @@ |
|||
// Native
|
|||
const EventEmitter = require('events') |
|||
|
|||
// Packages
|
|||
const io = require('socket.io-client') |
|||
const chalk = require('chalk') |
|||
|
|||
const { compare, deserialize } = require('./logs') |
|||
|
|||
module.exports = class Logger extends EventEmitter { |
|||
constructor(host, token, { debug = false, quiet = false } = {}) { |
|||
super() |
|||
this.host = host |
|||
this.token = token |
|||
this.debug = debug |
|||
this.quiet = quiet |
|||
|
|||
// ReadyState
|
|||
this.building = false |
|||
|
|||
this.socket = io(`https://io.now.sh/states?host=${host}&v=2`) |
|||
this.socket.once('error', this.onSocketError.bind(this)) |
|||
this.socket.on('auth', this.onAuth.bind(this)) |
|||
this.socket.on('state', this.onState.bind(this)) |
|||
this.socket.on('logs', this.onLog.bind(this)) |
|||
this.socket.on('backend', this.onComplete.bind(this)) |
|||
|
|||
// Log buffer
|
|||
this.buf = [] |
|||
this.printed = new Set() |
|||
} |
|||
|
|||
onAuth(callback) { |
|||
if (this.debug) { |
|||
console.log('> [debug] authenticate') |
|||
} |
|||
callback(this.token) |
|||
} |
|||
|
|||
onState(state) { |
|||
// Console.log(state)
|
|||
if (!state.id) { |
|||
console.error('> Deployment not found') |
|||
this.emit('error') |
|||
return |
|||
} |
|||
|
|||
if (state.error) { |
|||
this.emit('error', state) |
|||
return |
|||
} |
|||
|
|||
if (state.backend) { |
|||
this.onComplete() |
|||
return |
|||
} |
|||
|
|||
if (state.logs) { |
|||
state.logs.forEach(this.onLog, this) |
|||
} |
|||
} |
|||
|
|||
onLog(log) { |
|||
if (!this.building) { |
|||
if (!this.quiet) { |
|||
console.log('> Building') |
|||
} |
|||
this.building = true |
|||
} |
|||
|
|||
if (this.quiet) { |
|||
return |
|||
} |
|||
|
|||
log = deserialize(log) |
|||
|
|||
const timer = setTimeout(() => { |
|||
this.buf.sort((a, b) => compare(a.log, b.log)) |
|||
const idx = this.buf.findIndex(b => b.log.id === log.id) + 1 |
|||
for (const b of this.buf.slice(0, idx)) { |
|||
clearTimeout(b.timer) |
|||
this.printLog(b.log) |
|||
} |
|||
this.buf = this.buf.slice(idx) |
|||
}, 500) |
|||
|
|||
this.buf.push({ log, timer }) |
|||
} |
|||
|
|||
onComplete() { |
|||
this.socket.disconnect() |
|||
|
|||
if (this.building) { |
|||
this.building = false |
|||
} |
|||
|
|||
this.buf.sort((a, b) => compare(a.log, b.log)) |
|||
|
|||
// Flush all buffer
|
|||
for (const b of this.buf) { |
|||
clearTimeout(b.timer) |
|||
this.printLog(b.log) |
|||
} |
|||
this.buf = [] |
|||
|
|||
this.emit('close') |
|||
} |
|||
|
|||
onSocketError(err) { |
|||
if (this.debug) { |
|||
console.log(`> [debug] Socket error ${err}\n${err.stack}`) |
|||
} |
|||
} |
|||
|
|||
printLog(log) { |
|||
if (this.printed.has(log.id)) return |
|||
|
|||
this.printed.add(log.id) |
|||
|
|||
const data = log.object ? JSON.stringify(log.object) : log.text |
|||
|
|||
if (log.type === 'command') { |
|||
console.log(`${chalk.gray('>')} ▲ ${data}`) |
|||
} else if (log.type === 'stderr') { |
|||
data.split('\n').forEach(v => { |
|||
if (v.length > 0) { |
|||
console.error(chalk.gray(`> ${v}`)) |
|||
} |
|||
}) |
|||
} else if (log.type === 'stdout') { |
|||
data.split('\n').forEach(v => { |
|||
if (v.length > 0) { |
|||
console.log(`${chalk.gray('>')} ${v}`) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
@ -1,101 +0,0 @@ |
|||
// Ours
|
|||
const Now = require('../lib') |
|||
|
|||
module.exports = class Certs extends Now { |
|||
ls() { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET now/certs`) |
|||
} |
|||
|
|||
const res = await this._fetch('/now/certs') |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} GET now/certs`) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
return body.certs |
|||
}) |
|||
} |
|||
|
|||
create(cn) { |
|||
return this.createCert(cn) |
|||
} |
|||
|
|||
renew(cn) { |
|||
return this.createCert(cn, { renew: true }) |
|||
} |
|||
|
|||
put(cn, crt, key, ca) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} PUT now/certs`) |
|||
} |
|||
|
|||
const res = await this._fetch('/now/certs', { |
|||
method: 'PUT', |
|||
body: { |
|||
domains: [cn], |
|||
ca, |
|||
cert: crt, |
|||
key |
|||
} |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} PUT now/certs`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
if (res.status === 404 || res.status === 400) { |
|||
const err = new Error(body.error.message) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
throw new Error(body.error.message) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
delete(cn) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} DELETE now/certs/${cn}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/now/certs/${cn}`, { method: 'DELETE' }) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} DELETE now/certs/${cn}`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
if (res.status === 404 || res.status === 400) { |
|||
const err = new Error(body.error.message) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
throw new Error(body.error.message) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
} |
@ -1,112 +0,0 @@ |
|||
// Native
|
|||
const { homedir } = require('os') |
|||
const path = require('path') |
|||
|
|||
// Packages
|
|||
const fs = require('fs-extra') |
|||
const ms = require('ms') |
|||
|
|||
// Ours
|
|||
const { get: getUser } = require('./user') |
|||
|
|||
// `8h` is arbitrarily used based on the average sleep time
|
|||
const TTL = ms('8h') |
|||
|
|||
let file = process.env.NOW_JSON |
|||
? path.resolve(process.env.NOW_JSON) |
|||
: path.resolve(homedir(), '.now.json') |
|||
|
|||
function setConfigFile(nowjson) { |
|||
file = path.resolve(nowjson) |
|||
} |
|||
|
|||
function save(data) { |
|||
fs.writeFileSync(file, JSON.stringify(data, null, 2)) |
|||
} |
|||
|
|||
/** |
|||
* Reads the config file |
|||
* |
|||
* Optionally, always queries the API to get the user info even if the |
|||
* config file is not present |
|||
* |
|||
* @param {Boolean} force [false] Queries the API even if the config |
|||
* file is not present. If `true`, `token` |
|||
* *must* be specified |
|||
* @param {String} token Will be used to autenticate in the API |
|||
if needed |
|||
* @param {String} apiUrl URL of the API to be used |
|||
* @return {Object} |
|||
*/ |
|||
async function read({ force = false, token, apiUrl } = {}) { |
|||
let existing = {} |
|||
try { |
|||
existing = fs.readFileSync(file, 'utf8') |
|||
existing = JSON.parse(existing) |
|||
} catch (err) {} |
|||
|
|||
// Will happen if `force`d or if `--token` is used and it's different from
|
|||
// The one that's stored (which can be `undefined`)
|
|||
if ((force && token) || (token && token !== existing.token)) { |
|||
const user = await getUser({ token, apiUrl }) |
|||
if (user) { |
|||
return { |
|||
token, |
|||
user: { |
|||
uid: user.uid, |
|||
username: user.username, |
|||
email: user.email |
|||
}, |
|||
currentTeam: existing.currentTeam |
|||
} |
|||
} |
|||
return {} |
|||
} |
|||
|
|||
if (!existing.token) { |
|||
return {} |
|||
} |
|||
|
|||
if (!existing.lastUpdate || Date.now() - existing.lastUpdate > TTL) { |
|||
// TODO: update `teams` info
|
|||
const token = existing.token |
|||
const user = await getUser({ token }) |
|||
|
|||
if (user) { |
|||
existing.user = user |
|||
existing.lastUpdate = Date.now() |
|||
save(existing) |
|||
} |
|||
} |
|||
return existing |
|||
} |
|||
|
|||
/** |
|||
* Merges the `data` object onto the |
|||
* JSON config stored in `.now.json`. |
|||
* |
|||
* (atomic) |
|||
* @param {Object} data |
|||
*/ |
|||
async function merge(data) { |
|||
const cfg = Object.assign({}, await read(), data) |
|||
save(cfg) |
|||
} |
|||
|
|||
// Removes a key from the config and store the result
|
|||
async function remove(key) { |
|||
const cfg = await read() |
|||
delete cfg[key] |
|||
fs.writeFileSync(file, JSON.stringify(cfg, null, 2)) |
|||
} |
|||
|
|||
// We need to remove the config file when running `now logout`
|
|||
const removeFile = async () => fs.remove(file) |
|||
|
|||
module.exports = { |
|||
setConfigFile, |
|||
read, |
|||
merge, |
|||
remove, |
|||
removeFile |
|||
} |
@ -1,73 +0,0 @@ |
|||
const stripe = require('stripe')('pk_live_alyEi3lN0kSwbdevK0nrGwTw') |
|||
|
|||
const Now = require('../lib') |
|||
|
|||
module.exports = class CreditCards extends Now { |
|||
async ls() { |
|||
const res = await this._fetch('/cards') |
|||
const body = await res.json() |
|||
if (res.status !== 200) { |
|||
const e = new Error(body.error.message) |
|||
e.code = body.error.code |
|||
throw e |
|||
} |
|||
return body |
|||
} |
|||
|
|||
async setDefault(cardId) { |
|||
await this._fetch('/cards/default', { |
|||
method: 'PUT', |
|||
body: { cardId } |
|||
}) |
|||
return true |
|||
} |
|||
|
|||
async rm(cardId) { |
|||
await this._fetch(`/cards/${encodeURIComponent(cardId)}`, { |
|||
method: 'DELETE' |
|||
}) |
|||
return true |
|||
} |
|||
|
|||
/* eslint-disable camelcase */ |
|||
add(card) { |
|||
return new Promise(async (resolve, reject) => { |
|||
const expDateParts = card.expDate.split(' / ') |
|||
card = { |
|||
name: card.name, |
|||
number: card.cardNumber, |
|||
cvc: card.ccv, |
|||
address_country: card.country, |
|||
address_zip: card.zipCode, |
|||
address_state: card.state, |
|||
address_city: card.city, |
|||
address_line1: card.address1 |
|||
} |
|||
|
|||
card.exp_month = expDateParts[0] |
|||
card.exp_year = expDateParts[1] |
|||
|
|||
try { |
|||
const stripeToken = (await stripe.tokens.create({ card })).id |
|||
const res = await this._fetch('/cards', { |
|||
method: 'POST', |
|||
body: { stripeToken } |
|||
}) |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (body && body.id) { |
|||
resolve({ |
|||
last4: body.last4 |
|||
}) |
|||
} else if (body.error && body.error.message) { |
|||
reject(new Error(body.error.message)) |
|||
} else { |
|||
reject(new Error('Unknown error')) |
|||
} |
|||
} catch (err) { |
|||
reject(new Error(err.message || 'Unknown error')) |
|||
} |
|||
}) |
|||
} |
|||
} |
@ -1,15 +0,0 @@ |
|||
// Packages
|
|||
const dns = require('dns') |
|||
|
|||
function resolve4(host) { |
|||
return new Promise((resolve, reject) => { |
|||
return dns.resolve4(host, (err, answer) => { |
|||
if (err) { |
|||
return reject(err) |
|||
} |
|||
|
|||
resolve(answer) |
|||
}) |
|||
}) |
|||
} |
|||
module.exports = resolve4 |
@ -1,148 +0,0 @@ |
|||
// Ours
|
|||
const Now = require('../lib') |
|||
|
|||
module.exports = class DomainRecords extends Now { |
|||
async getRecord(id) { |
|||
const all = (await this.ls()).entries() |
|||
for (const [domain, records] of all) { |
|||
for (const record of records) { |
|||
if (record.id === id) { |
|||
record.domain = domain |
|||
return record |
|||
} |
|||
} |
|||
} |
|||
return null |
|||
} |
|||
|
|||
async ls(dom) { |
|||
let domains |
|||
|
|||
if (dom) { |
|||
domains = [dom] |
|||
} else { |
|||
const ret = await this.listDomains() |
|||
domains = ret |
|||
.filter(x => !x.isExternal) |
|||
.map(x => x.name) |
|||
.sort((a, b) => a.localeCompare(b)) |
|||
} |
|||
|
|||
const records = new Map() |
|||
const bodies = [] |
|||
|
|||
for (const domain of domains) { |
|||
bodies.push( |
|||
this.retry(async (bail, attempt) => { |
|||
const url = `/domains/${domain}/records` |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET ${url}`) |
|||
} |
|||
const res = await this._fetch(url) |
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} GET ${url}`) |
|||
} |
|||
const body = await res.json() |
|||
|
|||
if (res.status === 404 && body.code === 'not_found') { |
|||
return bail(new Error(body.message)) |
|||
} else if (res.status !== 200) { |
|||
throw new Error(`Failed to get DNS records for domain "${domain}"`) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
) |
|||
} |
|||
|
|||
const domainList = await Promise.all(bodies) |
|||
|
|||
for (const body of domainList) { |
|||
const index = domainList.indexOf(body) |
|||
|
|||
records.set( |
|||
domains[index], |
|||
body.records.sort((a, b) => a.slug.localeCompare(b.slug)) |
|||
) |
|||
} |
|||
|
|||
return records |
|||
} |
|||
|
|||
create(domain, data) { |
|||
const url = `/domains/${domain}/records` |
|||
|
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} POST ${url}`) |
|||
} |
|||
const res = await this._fetch(url, { |
|||
method: 'POST', |
|||
body: data |
|||
}) |
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} POST ${url}`) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
if (res.status === 400) { |
|||
return bail( |
|||
new Error(body.error ? body.error.message : 'Unknown error') |
|||
) |
|||
} else if (res.status === 403) { |
|||
const err = new Error(`Not authorized to access the domain "${domain}"`) |
|||
err.userError = true |
|||
return bail(err) |
|||
} else if (res.status === 404) { |
|||
let err |
|||
|
|||
if (body.error.code === 'not_found') { |
|||
err = new Error(`The domain "${domain}" was not found`) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
} |
|||
|
|||
if (res.status !== 200) { |
|||
throw new Error(body.error ? body.error.message : 'Unknown error') |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
delete(domain, recordId) { |
|||
const url = `/domains/${domain}/records/${recordId}` |
|||
|
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} DELETE ${url}`) |
|||
} |
|||
const res = await this._fetch(url, { method: 'DELETE' }) |
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} DELETE ${url}`) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
if (res.status === 403) { |
|||
const err = new Error(`Not authorized to access domain ${domain}`) |
|||
err.userError = true |
|||
return bail(err) |
|||
} else if (res.status === 404) { |
|||
let err |
|||
|
|||
if (body.error.code === 'not_found') { |
|||
err = new Error(body.error.message) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
} |
|||
|
|||
if (res.status !== 200) { |
|||
throw new Error(body.error ? body.error.message : 'Unkown error') |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
} |
@ -1,264 +0,0 @@ |
|||
// Native
|
|||
const { encode: encodeQuery } = require('querystring') |
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
|
|||
// Ours
|
|||
const Now = require('../lib') |
|||
const isZeitWorld = require('./is-zeit-world') |
|||
const { DNS_VERIFICATION_ERROR } = require('./errors') |
|||
const cmd = require('./utils/output/param') |
|||
|
|||
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/ |
|||
|
|||
module.exports = class Domains extends Now { |
|||
async ls() { |
|||
return this.listDomains() |
|||
} |
|||
|
|||
async rm(domain) { |
|||
// Remove aliases
|
|||
for (const aliasId of domain.aliases) { |
|||
this.retry(async (bail, attempt) => { |
|||
const label = `> [debug] #${attempt} DELETE now/aliases/${aliasId}` |
|||
if (this._debug) { |
|||
console.time(label) |
|||
} |
|||
|
|||
const res = await this._fetch(`/now/aliases/${aliasId}`, { |
|||
method: 'DELETE' |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(label) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
if (res.status !== 200) { |
|||
const body = await res.json() |
|||
throw new Error(body.error.message) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// Remove certs
|
|||
for (const cn of domain.certs) { |
|||
this.retry(async (bail, attempt) => { |
|||
const label = `> [debug] #${attempt} DELETE now/certs/${cn}` |
|||
if (this._debug) { |
|||
console.time(label) |
|||
} |
|||
|
|||
const res = await this._fetch(`/now/certs/${cn}`, { method: 'DELETE' }) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(label) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
if (res.status !== 200) { |
|||
const body = await res.json() |
|||
throw new Error(body.error.message) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// Remove the domain
|
|||
const name = domain.name |
|||
return this.retry(async (bail, attempt) => { |
|||
const label = `> [debug] #${attempt} DELETE /domains/${name}` |
|||
if (this._debug) { |
|||
console.time(label) |
|||
} |
|||
|
|||
const res = await this._fetch(`/domains/${name}`, { method: 'DELETE' }) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(label) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
if (res.status === 409) { |
|||
const body = await res.json() |
|||
return bail(new Error(body.error.message)) |
|||
} |
|||
|
|||
if (res.status !== 200) { |
|||
const body = await res.json() |
|||
throw new Error(body.error.message) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async add(domain, skipVerification, isExternal) { |
|||
if (!domainRegex.test(domain)) { |
|||
const err = new Error( |
|||
`The supplied value ${chalk.bold(`"${domain}"`)} is not a valid domain.` |
|||
) |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
if (skipVerification || isExternal) { |
|||
return this.setupDomain(domain, { isExternal }) |
|||
} |
|||
|
|||
let ns |
|||
|
|||
try { |
|||
console.log('> Verifying nameservers…') |
|||
const res = await this.getNameservers(domain) |
|||
ns = res.nameservers |
|||
} catch (err) { |
|||
const err2 = new Error( |
|||
`Unable to fetch nameservers for ${chalk.underline( |
|||
chalk.bold(domain) |
|||
)}.` |
|||
) |
|||
err2.userError = true |
|||
throw err2 |
|||
} |
|||
|
|||
if (isZeitWorld(ns)) { |
|||
console.log(`> Verification ${chalk.bold('OK')}!`) |
|||
return this.setupDomain(domain) |
|||
} |
|||
|
|||
if (this._debug) { |
|||
console.log( |
|||
`> [debug] Supplied domain "${domain}" has non-zeit nameservers` |
|||
) |
|||
} |
|||
|
|||
const err3 = new Error(DNS_VERIFICATION_ERROR) |
|||
err3.userError = true |
|||
throw err3 |
|||
} |
|||
|
|||
async status(name) { |
|||
if (!name) { |
|||
throw new Error('`domain` is not defined') |
|||
} |
|||
|
|||
const query = encodeQuery({ name }) |
|||
|
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET /domains/status?${query}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/domains/status?${query}`) |
|||
const json = await res.json() |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} GET /domains/status?${query}`) |
|||
} |
|||
|
|||
return json.available |
|||
}) |
|||
} |
|||
|
|||
async price(name) { |
|||
if (!name) { |
|||
throw new Error('`domain` is not defined') |
|||
} |
|||
|
|||
const query = encodeQuery({ name }) |
|||
|
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET /domains/price?${query}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/domains/price?${query}`) |
|||
const json = await res.json() |
|||
|
|||
if (res.status === 400) { |
|||
const e = new Error(json.error.message) |
|||
e.code = json.error.code |
|||
return bail(e) |
|||
} |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} GET /domains/price?${query}`) |
|||
} |
|||
|
|||
return json |
|||
}) |
|||
} |
|||
|
|||
async buy({ name, coupon }) { |
|||
if (!name) { |
|||
throw new Error('`name` is not defined') |
|||
} |
|||
|
|||
const body = JSON.stringify({ name, coupon }) |
|||
|
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET /domains/buy`) |
|||
} |
|||
const res = await this._fetch(`/domains/buy`, { |
|||
method: 'POST', |
|||
body |
|||
}) |
|||
const json = await res.json() |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} GET /domains/buy`) |
|||
} |
|||
|
|||
if ([400, 403, 500, 503].includes(res.status)) { |
|||
const e = new Error() |
|||
e.code = json.error.code |
|||
if (json.error.code === 'source_not_found') { |
|||
e.message = `No credit cards found – please run ${cmd('now cc add')}` |
|||
} else { |
|||
e.message = json.error.message |
|||
} |
|||
return bail(e) |
|||
} |
|||
|
|||
return json |
|||
}) |
|||
} |
|||
|
|||
async coupon(coupon) { |
|||
if (!coupon) { |
|||
throw new Error('`coupon` is not defined') |
|||
} |
|||
|
|||
const query = encodeQuery({ coupon }) |
|||
|
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET /domains/buy?${query}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/domains/buy?${query}`) |
|||
const json = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
const e = new Error(json.error.message) |
|||
e.code = json.error.code |
|||
return bail(e) |
|||
} |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} GET /domains/buy?${query}`) |
|||
} |
|||
|
|||
return json |
|||
}) |
|||
} |
|||
} |
@ -1,85 +0,0 @@ |
|||
// Packages
|
|||
const ms = require('ms') |
|||
const chalk = require('chalk') |
|||
|
|||
const error = require('./utils/output/error') |
|||
const info = require('./utils/output/info') |
|||
|
|||
function handleError(err, { debug = false } = {}) { |
|||
// Coerce Strings to Error instances
|
|||
if (typeof err === 'string') { |
|||
err = new Error(err) |
|||
} |
|||
|
|||
if (debug) { |
|||
console.log(`> [debug] handling error: ${err.stack}`) |
|||
} |
|||
|
|||
if (err.status === 403) { |
|||
error( |
|||
'Authentication error. Run `now -L` or `now --login` to log-in again.' |
|||
) |
|||
} else if (err.status === 429) { |
|||
if (err.retryAfter === 'never') { |
|||
error(err.message) |
|||
} else if (err.retryAfter === null) { |
|||
error('Rate limit exceeded error. Please try later.') |
|||
} else { |
|||
error( |
|||
'Rate limit exceeded error. Try again in ' + |
|||
ms(err.retryAfter * 1000, { long: true }) + |
|||
', or upgrade your account by running ' + |
|||
`${chalk.gray('`')}${chalk.cyan('now upgrade')}${chalk.gray('`')}` |
|||
) |
|||
} |
|||
} else if (err.userError) { |
|||
error(err.message) |
|||
} else if (err.status === 500) { |
|||
error('Unexpected server error. Please retry.') |
|||
} else if (err.code === 'USER_ABORT') { |
|||
info('Aborted') |
|||
} else { |
|||
error(`Unexpected error. Please try again later. (${err.message})`) |
|||
} |
|||
} |
|||
|
|||
async function responseError(res) { |
|||
let message |
|||
let userError |
|||
|
|||
if (res.status >= 400 && res.status < 500) { |
|||
let body |
|||
|
|||
try { |
|||
body = await res.json() |
|||
} catch (err) { |
|||
body = {} |
|||
} |
|||
|
|||
// Some APIs wrongly return `err` instead of `error`
|
|||
message = (body.error || body.err || {}).message |
|||
userError = true |
|||
} else { |
|||
userError = false |
|||
} |
|||
|
|||
const err = new Error(message || 'Response error') |
|||
err.status = res.status |
|||
err.userError = userError |
|||
|
|||
if (res.status === 429) { |
|||
const retryAfter = res.headers.get('Retry-After') |
|||
|
|||
if (retryAfter) { |
|||
err.retryAfter = parseInt(retryAfter, 10) |
|||
} |
|||
} |
|||
|
|||
return err |
|||
} |
|||
|
|||
module.exports = { |
|||
handleError, |
|||
responseError, |
|||
error |
|||
} |
@ -1,30 +0,0 @@ |
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
|
|||
const DNS_VERIFICATION_ERROR = `Please make sure that your nameservers point to ${chalk.underline( |
|||
'zeit.world' |
|||
)}. |
|||
> Examples: (full list at ${chalk.underline('https://zeit.world')}) |
|||
> ${chalk.gray('-')} ${chalk.underline('california.zeit.world')} ${chalk.dim( |
|||
'173.255.215.107' |
|||
)} |
|||
> ${chalk.gray('-')} ${chalk.underline('newark.zeit.world')} ${chalk.dim( |
|||
'173.255.231.87' |
|||
)} |
|||
> ${chalk.gray('-')} ${chalk.underline('london.zeit.world')} ${chalk.dim( |
|||
'178.62.47.76' |
|||
)} |
|||
> ${chalk.gray('-')} ${chalk.underline('singapore.zeit.world')} ${chalk.dim( |
|||
'119.81.97.170' |
|||
)}` |
|||
|
|||
const DOMAIN_VERIFICATION_ERROR = |
|||
DNS_VERIFICATION_ERROR + |
|||
`\n> Alternatively, ensure it resolves to ${chalk.underline( |
|||
'alias.zeit.co' |
|||
)} via ${chalk.dim('CNAME')} / ${chalk.dim('ALIAS')}.` |
|||
|
|||
module.exports = { |
|||
DNS_VERIFICATION_ERROR, |
|||
DOMAIN_VERIFICATION_ERROR |
|||
} |
@ -1,385 +0,0 @@ |
|||
// Native
|
|||
const { resolve } = require('path') |
|||
|
|||
// Packages
|
|||
const flatten = require('arr-flatten') |
|||
const unique = require('array-unique') |
|||
const ignore = require('ignore') |
|||
const _glob = require('glob') |
|||
const { stat, readdir, readFile } = require('fs-extra') |
|||
|
|||
// Ours
|
|||
const IGNORED = require('./ignored') |
|||
|
|||
const glob = async function(pattern, options) { |
|||
return new Promise((resolve, reject) => { |
|||
_glob(pattern, options, (error, files) => { |
|||
if (error) { |
|||
reject(error) |
|||
} else { |
|||
resolve(files) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Remove leading `./` from the beginning of ignores |
|||
* because our parser doesn't like them :| |
|||
*/ |
|||
|
|||
const clearRelative = function(str) { |
|||
return str.replace(/(\n|^)\.\//g, '$1') |
|||
} |
|||
|
|||
/** |
|||
* Returns the contents of a file if it exists. |
|||
* |
|||
* @return {String} results or `''` |
|||
*/ |
|||
|
|||
const maybeRead = async function(path, default_ = '') { |
|||
try { |
|||
return await readFile(path, 'utf8') |
|||
} catch (err) { |
|||
return default_ |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Transform relative paths into absolutes, |
|||
* and maintains absolutes as such. |
|||
* |
|||
* @param {String} maybe relative path |
|||
* @param {String} parent full path |
|||
*/ |
|||
|
|||
const asAbsolute = function(path, parent) { |
|||
if (path[0] === '/') { |
|||
return path |
|||
} |
|||
|
|||
return resolve(parent, path) |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of files in the given |
|||
* directory that are subject to be |
|||
* synchronized for static deployments. |
|||
* |
|||
* @param {String} full path to directory |
|||
* @param {Object} options: |
|||
* - `limit` {Number|null} byte limit |
|||
* - `debug` {Boolean} warn upon ignore |
|||
* @return {Array} comprehensive list of paths to sync |
|||
*/ |
|||
|
|||
async function staticFiles( |
|||
path, |
|||
nowConfig = {}, |
|||
{ limit = null, hasNowJson = false, debug = false } = {} |
|||
) { |
|||
const whitelist = nowConfig.files |
|||
|
|||
// The package.json `files` whitelist still
|
|||
// honors ignores: https://docs.npmjs.com/files/package.json#files
|
|||
const search_ = whitelist || ['.'] |
|||
// Convert all filenames into absolute paths
|
|||
const search = Array.prototype.concat.apply( |
|||
[], |
|||
await Promise.all( |
|||
search_.map(file => glob(file, { cwd: path, absolute: true, dot: true })) |
|||
) |
|||
) |
|||
|
|||
// Compile list of ignored patterns and files
|
|||
const gitIgnore = await maybeRead(resolve(path, '.gitignore')) |
|||
|
|||
const filter = ignore() |
|||
.add(IGNORED + '\n' + clearRelative(gitIgnore)) |
|||
.createFilter() |
|||
|
|||
const prefixLength = path.length + 1 |
|||
|
|||
// The package.json `files` whitelist still
|
|||
// honors npmignores: https://docs.npmjs.com/files/package.json#files
|
|||
// but we don't ignore if the user is explicitly listing files
|
|||
// under the now namespace, or using files in combination with gitignore
|
|||
const accepts = file => { |
|||
const relativePath = file.substr(prefixLength) |
|||
|
|||
if (relativePath === '') { |
|||
return true |
|||
} |
|||
|
|||
const accepted = filter(relativePath) |
|||
if (!accepted && debug) { |
|||
console.log('> [debug] ignoring "%s"', file) |
|||
} |
|||
return accepted |
|||
} |
|||
|
|||
// Locate files
|
|||
if (debug) { |
|||
console.time(`> [debug] locating files ${path}`) |
|||
} |
|||
|
|||
const files = await explode(search, { |
|||
accepts, |
|||
limit, |
|||
debug |
|||
}) |
|||
|
|||
if (debug) { |
|||
console.timeEnd(`> [debug] locating files ${path}`) |
|||
} |
|||
|
|||
if (hasNowJson) { |
|||
files.push(asAbsolute('now.json', path)) |
|||
} |
|||
|
|||
// Get files
|
|||
return unique(files) |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of files in the given |
|||
* directory that are subject to be |
|||
* synchronized for npm. |
|||
* |
|||
* @param {String} full path to directory |
|||
* @param {String} contents of `package.json` to avoid lookup |
|||
* @param {Object} options: |
|||
* - `limit` {Number|null} byte limit |
|||
* - `debug` {Boolean} warn upon ignore |
|||
* @return {Array} comprehensive list of paths to sync |
|||
*/ |
|||
|
|||
async function npm( |
|||
path, |
|||
pkg = {}, |
|||
nowConfig = {}, |
|||
{ limit = null, hasNowJson = false, debug = false } = {} |
|||
) { |
|||
const whitelist = nowConfig.files || pkg.files || (pkg.now && pkg.now.files) |
|||
|
|||
// The package.json `files` whitelist still
|
|||
// honors ignores: https://docs.npmjs.com/files/package.json#files
|
|||
const search_ = whitelist || ['.'] |
|||
// Convert all filenames into absolute paths
|
|||
const search = Array.prototype.concat.apply( |
|||
[], |
|||
await Promise.all( |
|||
search_.map(file => glob(file, { cwd: path, absolute: true, dot: true })) |
|||
) |
|||
) |
|||
|
|||
// Compile list of ignored patterns and files
|
|||
const npmIgnore = await maybeRead(resolve(path, '.npmignore'), null) |
|||
const gitIgnore = |
|||
npmIgnore === null ? await maybeRead(resolve(path, '.gitignore')) : null |
|||
|
|||
const filter = ignore() |
|||
.add( |
|||
IGNORED + '\n' + clearRelative(npmIgnore === null ? gitIgnore : npmIgnore) |
|||
) |
|||
.createFilter() |
|||
|
|||
const prefixLength = path.length + 1 |
|||
|
|||
// The package.json `files` whitelist still
|
|||
// honors npmignores: https://docs.npmjs.com/files/package.json#files
|
|||
// but we don't ignore if the user is explicitly listing files
|
|||
// under the now namespace, or using files in combination with gitignore
|
|||
const overrideIgnores = |
|||
(pkg.now && pkg.now.files) || |
|||
nowConfig.files || |
|||
(gitIgnore !== null && pkg.files) |
|||
const accepts = overrideIgnores |
|||
? () => true |
|||
: file => { |
|||
const relativePath = file.substr(prefixLength) |
|||
|
|||
if (relativePath === '') { |
|||
return true |
|||
} |
|||
|
|||
const accepted = filter(relativePath) |
|||
if (!accepted && debug) { |
|||
console.log('> [debug] ignoring "%s"', file) |
|||
} |
|||
return accepted |
|||
} |
|||
|
|||
// Locate files
|
|||
if (debug) { |
|||
console.time(`> [debug] locating files ${path}`) |
|||
} |
|||
|
|||
const files = await explode(search, { |
|||
accepts, |
|||
limit, |
|||
debug |
|||
}) |
|||
|
|||
if (debug) { |
|||
console.timeEnd(`> [debug] locating files ${path}`) |
|||
} |
|||
|
|||
// Always include manifest as npm does not allow ignoring it
|
|||
// source: https://docs.npmjs.com/files/package.json#files
|
|||
files.push(asAbsolute('package.json', path)) |
|||
|
|||
if (hasNowJson) { |
|||
files.push(asAbsolute('now.json', path)) |
|||
} |
|||
|
|||
// Get files
|
|||
return unique(files) |
|||
} |
|||
|
|||
/** |
|||
* Returns a list of files in the given |
|||
* directory that are subject to be |
|||
* sent to docker as build context. |
|||
* |
|||
* @param {String} full path to directory |
|||
* @param {String} contents of `Dockerfile` |
|||
* @param {Object} options: |
|||
* - `limit` {Number|null} byte limit |
|||
* - `debug` {Boolean} warn upon ignore |
|||
* @return {Array} comprehensive list of paths to sync |
|||
*/ |
|||
|
|||
async function docker( |
|||
path, |
|||
nowConfig = {}, |
|||
{ limit = null, hasNowJson = false, debug = false } = {} |
|||
) { |
|||
const whitelist = nowConfig.files |
|||
|
|||
// Base search path
|
|||
// the now.json `files` whitelist still
|
|||
// honors ignores: https://docs.npmjs.com/files/package.json#files
|
|||
const search_ = whitelist || ['.'] |
|||
|
|||
// Convert all filenames into absolute paths
|
|||
const search = search_.map(file => asAbsolute(file, path)) |
|||
|
|||
// Compile list of ignored patterns and files
|
|||
const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null) |
|||
|
|||
const filter = ignore() |
|||
.add( |
|||
IGNORED + |
|||
'\n' + |
|||
clearRelative( |
|||
dockerIgnore === null |
|||
? await maybeRead(resolve(path, '.gitignore')) |
|||
: dockerIgnore |
|||
) |
|||
) |
|||
.createFilter() |
|||
|
|||
const prefixLength = path.length + 1 |
|||
const accepts = function(file) { |
|||
const relativePath = file.substr(prefixLength) |
|||
|
|||
if (relativePath === '') { |
|||
return true |
|||
} |
|||
|
|||
const accepted = filter(relativePath) |
|||
if (!accepted && debug) { |
|||
console.log('> [debug] ignoring "%s"', file) |
|||
} |
|||
return accepted |
|||
} |
|||
|
|||
// Locate files
|
|||
if (debug) { |
|||
console.time(`> [debug] locating files ${path}`) |
|||
} |
|||
|
|||
const files = await explode(search, { accepts, limit, debug }) |
|||
|
|||
if (debug) { |
|||
console.timeEnd(`> [debug] locating files ${path}`) |
|||
} |
|||
|
|||
// Always include manifest as npm does not allow ignoring it
|
|||
// source: https://docs.npmjs.com/files/package.json#files
|
|||
files.push(asAbsolute('Dockerfile', path)) |
|||
|
|||
if (hasNowJson) { |
|||
files.push(asAbsolute('now.json', path)) |
|||
} |
|||
|
|||
// Get files
|
|||
return unique(files) |
|||
} |
|||
|
|||
/** |
|||
* Explodes directories into a full list of files. |
|||
* Eg: |
|||
* in: ['/a.js', '/b'] |
|||
* out: ['/a.js', '/b/c.js', '/b/d.js'] |
|||
* |
|||
* @param {Array} of {String}s representing paths |
|||
* @param {Array} of ignored {String}s. |
|||
* @param {Object} options: |
|||
* - `limit` {Number|null} byte limit |
|||
* - `debug` {Boolean} warn upon ignore |
|||
* @return {Array} of {String}s of full paths |
|||
*/ |
|||
|
|||
async function explode(paths, { accepts, debug }) { |
|||
const list = async file => { |
|||
let path = file |
|||
let s |
|||
|
|||
if (!accepts(file)) { |
|||
return null |
|||
} |
|||
|
|||
try { |
|||
s = await stat(path) |
|||
} catch (e) { |
|||
// In case the file comes from `files`
|
|||
// and it wasn't specified with `.js` by the user
|
|||
path = file + '.js' |
|||
|
|||
try { |
|||
s = await stat(path) |
|||
} catch (e2) { |
|||
if (debug) { |
|||
console.log('> [debug] ignoring invalid file "%s"', file) |
|||
} |
|||
return null |
|||
} |
|||
} |
|||
|
|||
if (s.isDirectory()) { |
|||
const all = await readdir(file) |
|||
/* eslint-disable no-use-before-define */ |
|||
return many(all.map(subdir => asAbsolute(subdir, file))) |
|||
/* eslint-enable no-use-before-define */ |
|||
} else if (!s.isFile()) { |
|||
if (debug) { |
|||
console.log('> [debug] ignoring special file "%s"', file) |
|||
} |
|||
return null |
|||
} |
|||
|
|||
return path |
|||
} |
|||
|
|||
const many = all => Promise.all(all.map(file => list(file))) |
|||
return flatten(await many(paths)).filter(v => v !== null) |
|||
} |
|||
|
|||
module.exports = { |
|||
npm, |
|||
docker, |
|||
staticFiles |
|||
} |
@ -1,221 +0,0 @@ |
|||
// Native
|
|||
const path = require('path') |
|||
const url = require('url') |
|||
const childProcess = require('child_process') |
|||
|
|||
// Packages
|
|||
const fs = require('fs-extra') |
|||
const download = require('download') |
|||
const tmp = require('tmp-promise') |
|||
const isURL = require('is-url') |
|||
|
|||
const cloneRepo = (parts, tmpDir, { ssh }) => |
|||
new Promise((resolve, reject) => { |
|||
let host |
|||
|
|||
switch (parts.type) { |
|||
case 'GitLab': |
|||
host = `gitlab.com` |
|||
break |
|||
case 'Bitbucket': |
|||
host = `bitbucket.org` |
|||
break |
|||
default: |
|||
host = `github.com` |
|||
} |
|||
|
|||
const url = ssh |
|||
? `git@${host}:${parts.main}` |
|||
: `https://${host}/${parts.main}` |
|||
|
|||
const ref = parts.ref || (parts.type === 'Bitbucket' ? 'default' : 'master') |
|||
const cmd = `git clone ${url} --single-branch ${ref}` |
|||
|
|||
childProcess.exec(cmd, { cwd: tmpDir.path }, (err, stdout) => { |
|||
if (err) { |
|||
reject(err) |
|||
} |
|||
|
|||
resolve(stdout) |
|||
}) |
|||
}) |
|||
|
|||
const renameRepoDir = async (pathParts, tmpDir) => { |
|||
const tmpContents = await fs.readdir(tmpDir.path) |
|||
|
|||
const oldTemp = path.join(tmpDir.path, tmpContents[0]) |
|||
const newTemp = path.join(tmpDir.path, pathParts.main.replace('/', '-')) |
|||
|
|||
await fs.rename(oldTemp, newTemp) |
|||
tmpDir.path = newTemp |
|||
|
|||
return tmpDir |
|||
} |
|||
|
|||
const capitalizePlatform = name => { |
|||
const names = { |
|||
github: 'GitHub', |
|||
gitlab: 'GitLab', |
|||
bitbucket: 'Bitbucket' |
|||
} |
|||
|
|||
return names[name] |
|||
} |
|||
|
|||
const splittedURL = fullURL => { |
|||
const parsedURL = url.parse(fullURL) |
|||
const pathParts = parsedURL.path.split('/') |
|||
|
|||
pathParts.shift() |
|||
|
|||
// Set path to repo...
|
|||
const main = pathParts[0] + '/' + pathParts[1] |
|||
|
|||
// ...and then remove it from the parts
|
|||
pathParts.splice(0, 2) |
|||
|
|||
// Assign Git reference
|
|||
let ref = pathParts.length >= 2 ? pathParts[1] : '' |
|||
|
|||
// Firstly be sure that we haven know the ref type
|
|||
if (pathParts[0]) { |
|||
// Then shorten the SHA of the commit
|
|||
if (pathParts[0] === 'commit' || pathParts[0] === 'commits') { |
|||
ref = ref.substring(0, 7) |
|||
} |
|||
} |
|||
|
|||
// We're deploying master by default,
|
|||
// so there's no need to indicate it explicitly
|
|||
if (ref === 'master') { |
|||
ref = '' |
|||
} |
|||
|
|||
return { |
|||
main, |
|||
ref, |
|||
type: capitalizePlatform(parsedURL.host.split('.')[0]) |
|||
} |
|||
} |
|||
|
|||
const gitPathParts = main => { |
|||
let ref = '' |
|||
|
|||
if (isURL(main)) { |
|||
return splittedURL(main) |
|||
} |
|||
|
|||
if (main.split('/')[1].includes('#')) { |
|||
const parts = main.split('#') |
|||
|
|||
ref = parts[1] |
|||
main = parts[0] |
|||
} |
|||
|
|||
return { |
|||
main, |
|||
ref, |
|||
type: capitalizePlatform('github') |
|||
} |
|||
} |
|||
|
|||
const downloadRepo = async repoPath => { |
|||
const pathParts = gitPathParts(repoPath) |
|||
|
|||
const tmpDir = await tmp.dir({ |
|||
// We'll remove it manually once deployment is done
|
|||
keep: true, |
|||
// Recursively remove directory when calling respective method
|
|||
unsafeCleanup: true |
|||
}) |
|||
|
|||
let gitInstalled = true |
|||
|
|||
try { |
|||
await cloneRepo(pathParts, tmpDir) |
|||
} catch (err) { |
|||
try { |
|||
await cloneRepo(pathParts, tmpDir, { ssh: true }) |
|||
} catch (err) { |
|||
gitInstalled = false |
|||
} |
|||
} |
|||
|
|||
if (gitInstalled) { |
|||
const renaming = await renameRepoDir(pathParts, tmpDir) |
|||
return renaming |
|||
} |
|||
|
|||
let url |
|||
|
|||
switch (pathParts.type) { |
|||
case 'GitLab': { |
|||
const ref = pathParts.ref ? `?ref=${pathParts.ref}` : '' |
|||
url = `https://gitlab.com/${pathParts.main}/repository/archive.tar` + ref |
|||
break |
|||
} |
|||
case 'Bitbucket': |
|||
url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref || |
|||
'default'}.zip` |
|||
break |
|||
default: |
|||
url = `https://api.github.com/repos/${pathParts.main}/tarball/${pathParts.ref}` |
|||
} |
|||
|
|||
try { |
|||
await download(url, tmpDir.path, { |
|||
extract: true |
|||
}) |
|||
} catch (err) { |
|||
tmpDir.cleanup() |
|||
return false |
|||
} |
|||
|
|||
const renaming = await renameRepoDir(pathParts, tmpDir) |
|||
return renaming |
|||
} |
|||
|
|||
const isRepoPath = path => { |
|||
if (!path) { |
|||
return false |
|||
} |
|||
|
|||
const allowedHosts = ['github.com', 'gitlab.com', 'bitbucket.org'] |
|||
|
|||
if (isURL(path)) { |
|||
const urlParts = url.parse(path) |
|||
const slashSplitted = urlParts.path.split('/').filter(n => n) |
|||
const notBare = slashSplitted.length >= 2 |
|||
|
|||
if (allowedHosts.includes(urlParts.host) && notBare) { |
|||
return true |
|||
} |
|||
|
|||
const err = new Error(`Host "${urlParts.host}" is unsupported.`) |
|||
err.code = 'INVALID_URL' |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
return /[^\s\\]\/[^\s\\]/g.test(path) |
|||
} |
|||
|
|||
const fromGit = async (path, debug) => { |
|||
let tmpDir = false |
|||
|
|||
try { |
|||
tmpDir = await downloadRepo(path) |
|||
} catch (err) { |
|||
if (debug) { |
|||
console.log(`Could not download "${path}" repo from GitHub`) |
|||
} |
|||
} |
|||
|
|||
return tmpDir |
|||
} |
|||
|
|||
module.exports = { |
|||
gitPathParts, |
|||
isRepoPath, |
|||
fromGit |
|||
} |
@ -1,44 +0,0 @@ |
|||
// Native
|
|||
const { createHash } = require('crypto') |
|||
|
|||
// Packages
|
|||
const { readFile } = require('fs-extra') |
|||
|
|||
/** |
|||
* Computes hashes for the contents of each file given. |
|||
* |
|||
* @param {Array} of {String} full paths |
|||
* @return {Map} |
|||
*/ |
|||
|
|||
async function hashes(files) { |
|||
const map = new Map() |
|||
|
|||
await Promise.all( |
|||
files.map(async name => { |
|||
const data = await readFile(name) |
|||
|
|||
const h = hash(data) |
|||
const entry = map.get(h) |
|||
if (entry) { |
|||
entry.names.push(name) |
|||
} else { |
|||
map.set(hash(data), { names: [name], data }) |
|||
} |
|||
}) |
|||
) |
|||
return map |
|||
} |
|||
|
|||
/** |
|||
* Computes a hash for the given buf. |
|||
* |
|||
* @param {Buffer} file data |
|||
* @return {String} hex digest |
|||
*/ |
|||
|
|||
function hash(buf) { |
|||
return createHash('sha1').update(buf).digest('hex') |
|||
} |
|||
|
|||
module.exports = hashes |
@ -1,17 +0,0 @@ |
|||
// Base `.gitignore` to which we add entries
|
|||
// supplied by the user
|
|||
module.exports = `.hg
|
|||
.git |
|||
.gitmodules |
|||
.svn |
|||
.npmignore |
|||
.dockerignore |
|||
.gitignore |
|||
.*.swp |
|||
.DS_Store |
|||
.wafpicke-* |
|||
.lock-wscript |
|||
npm-debug.log |
|||
config.gypi |
|||
node_modules |
|||
CVS` |
@ -1,5 +0,0 @@ |
|||
function indent(text, n) { |
|||
return text.split('\n').map(l => ' '.repeat(n) + l).join('\n') |
|||
} |
|||
|
|||
module.exports = indent |
File diff suppressed because it is too large
@ -1,32 +0,0 @@ |
|||
/** |
|||
* List of `zeit.world` nameservers |
|||
*/ |
|||
|
|||
const nameservers = new Set([ |
|||
'california.zeit.world', |
|||
'london.zeit.world', |
|||
'newark.zeit.world', |
|||
'sydney.zeit.world', |
|||
'iowa.zeit.world', |
|||
'dallas.zeit.world', |
|||
'amsterdam.zeit.world', |
|||
'paris.zeit.world', |
|||
'frankfurt.zeit.world', |
|||
'singapore.zeit.world' |
|||
]) |
|||
|
|||
/** |
|||
* Given an array of nameservers (ie: as returned |
|||
* by `resolveNs` from Node, assert that they're |
|||
* zeit.world's. |
|||
*/ |
|||
function isZeitWorld(ns) { |
|||
return ( |
|||
ns.length > 1 && |
|||
ns.every(host => { |
|||
return nameservers.has(host) |
|||
}) |
|||
) |
|||
} |
|||
|
|||
module.exports = isZeitWorld |
@ -1,141 +0,0 @@ |
|||
// Native
|
|||
const os = require('os') |
|||
|
|||
// Packages
|
|||
const { stringify: stringifyQuery } = require('querystring') |
|||
const chalk = require('chalk') |
|||
const fetch = require('node-fetch') |
|||
const { validate } = require('email-validator') |
|||
const readEmail = require('email-prompt') |
|||
const ora = require('ora') |
|||
|
|||
// Ours
|
|||
const pkg = require('./pkg') |
|||
const ua = require('./ua') |
|||
const cfg = require('./cfg') |
|||
|
|||
async function getVerificationData(url, email) { |
|||
const tokenName = `Now CLI ${os.platform()}-${os.arch()} ${pkg.version} (${os.hostname()})` |
|||
const data = JSON.stringify({ email, tokenName }) |
|||
const res = await fetch(`${url}/now/registration`, { |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
'Content-Length': Buffer.byteLength(data), |
|||
'User-Agent': ua |
|||
}, |
|||
body: data |
|||
}) |
|||
|
|||
const body = await res.json() |
|||
if (res.status !== 200) { |
|||
throw new Error( |
|||
`Verification error: ${res.status} – ${JSON.stringify(body)}` |
|||
) |
|||
} |
|||
return body |
|||
} |
|||
|
|||
async function verify(url, email, verificationToken) { |
|||
const query = { |
|||
email, |
|||
token: verificationToken |
|||
} |
|||
|
|||
const res = await fetch( |
|||
`${url}/now/registration/verify?${stringifyQuery(query)}`, |
|||
{ |
|||
headers: { 'User-Agent': ua } |
|||
} |
|||
) |
|||
const body = await res.json() |
|||
return body.token |
|||
} |
|||
|
|||
function sleep(ms) { |
|||
return new Promise(resolve => { |
|||
setTimeout(resolve, ms) |
|||
}) |
|||
} |
|||
|
|||
async function register(url, { retryEmail = false } = {}) { |
|||
let email |
|||
try { |
|||
email = await readEmail({ invalid: retryEmail }) |
|||
} catch (err) { |
|||
process.stdout.write('\n') |
|||
throw err |
|||
} |
|||
|
|||
process.stdout.write('\n') |
|||
|
|||
if (!validate(email)) { |
|||
return register(url, { retryEmail: true }) |
|||
} |
|||
|
|||
const { token, securityCode } = await getVerificationData(url, email) |
|||
console.log( |
|||
`> Please follow the link sent to ${chalk.bold(email)} to log in.` |
|||
) |
|||
|
|||
if (securityCode) { |
|||
console.log( |
|||
`> Verify that the provided security code in the email matches ${chalk.cyan( |
|||
chalk.bold(securityCode) |
|||
)}.` |
|||
) |
|||
} |
|||
|
|||
process.stdout.write('\n') |
|||
|
|||
const spinner = ora({ |
|||
text: 'Waiting for confirmation...' |
|||
}).start() |
|||
|
|||
let final |
|||
|
|||
/* eslint-disable no-await-in-loop */ |
|||
do { |
|||
await sleep(2500) |
|||
|
|||
try { |
|||
final = await verify(url, email, token) |
|||
} catch (err) {} |
|||
} while (!final) |
|||
/* eslint-enable no-await-in-loop */ |
|||
|
|||
let user |
|||
try { |
|||
user = (await (await fetch(`${url}/www/user`, { |
|||
headers: { |
|||
Authorization: `Bearer ${final}` |
|||
} |
|||
})).json()).user |
|||
} catch (err) { |
|||
spinner.stop() |
|||
throw new Error(`Couldn't retrieve user details ${err.message}`) |
|||
} |
|||
|
|||
spinner.text = 'Confirmed email address!' |
|||
spinner.stopAndPersist('✔') |
|||
|
|||
process.stdout.write('\n') |
|||
|
|||
return { |
|||
token: final, |
|||
user: { |
|||
uid: user.uid, |
|||
username: user.username, |
|||
email: user.email |
|||
}, |
|||
lastUpdate: Date.now() |
|||
} |
|||
} |
|||
|
|||
module.exports = async function(url) { |
|||
const loginData = await register(url) |
|||
await cfg.merge(loginData) |
|||
await cfg.remove('currentTeam') // Make sure to logout the team too
|
|||
await cfg.remove('email') // Remove old schema from previus versions
|
|||
return loginData.token |
|||
} |
@ -1,14 +0,0 @@ |
|||
exports.compare = function(a, b) { |
|||
return ( |
|||
a.serial.localeCompare(b.serial) || |
|||
// For the case serials are a same value on old logs
|
|||
a.created.getTime() - b.created.getTime() |
|||
) |
|||
} |
|||
|
|||
exports.deserialize = function(log) { |
|||
return Object.assign({}, log, { |
|||
date: new Date(log.date), |
|||
created: new Date(log.created) |
|||
}) |
|||
} |
@ -1,13 +0,0 @@ |
|||
/* eslint-disable import/no-unresolved */ |
|||
|
|||
const path = require('path') |
|||
const pkg = require('../package.json') |
|||
|
|||
try { |
|||
const distDir = path.dirname(process.execPath) |
|||
pkg._npmPkg = require(path.join(distDir, '../../package.json')) |
|||
} catch (err) { |
|||
pkg._npmPkg = null |
|||
} |
|||
|
|||
module.exports = pkg |
@ -1,57 +0,0 @@ |
|||
const ms = require('ms') |
|||
|
|||
const Now = require('../lib') |
|||
|
|||
async function parsePlan(json) { |
|||
const { subscription } = json |
|||
let id |
|||
let until |
|||
let name |
|||
|
|||
if (subscription) { |
|||
const planItems = subscription.items.data |
|||
const mainPlan = planItems.find(d => d.plan.metadata.is_main_plan === '1') |
|||
|
|||
if (mainPlan) { |
|||
id = mainPlan.plan.id |
|||
name = mainPlan.plan.name |
|||
if (subscription.cancel_at_period_end) { |
|||
until = ms( |
|||
new Date(subscription.current_period_end * 1000) - new Date(), |
|||
{ long: true } |
|||
) |
|||
} |
|||
} else { |
|||
id = 'oss' |
|||
} |
|||
} else { |
|||
id = 'oss' |
|||
} |
|||
|
|||
return { id, name, until } |
|||
} |
|||
|
|||
module.exports = class Plans extends Now { |
|||
async getCurrent() { |
|||
const res = await this._fetch('/plan') |
|||
const json = await res.json() |
|||
return parsePlan(json) |
|||
} |
|||
|
|||
async set(plan) { |
|||
const res = await this._fetch('/plan', { |
|||
method: 'PUT', |
|||
body: { plan } |
|||
}) |
|||
|
|||
const json = await res.json() |
|||
|
|||
if (res.ok) { |
|||
return parsePlan(json) |
|||
} |
|||
|
|||
const err = new Error(json.error.message) |
|||
err.code = json.error.code |
|||
throw err |
|||
} |
|||
} |
@ -1,134 +0,0 @@ |
|||
// Native
|
|||
const { join } = require('path') |
|||
|
|||
// Packages
|
|||
const fs = require('fs-extra') |
|||
const chalk = require('chalk') |
|||
|
|||
// Ours
|
|||
const { error } = require('./error') |
|||
const readMetaData = require('./read-metadata') |
|||
const NowAlias = require('./alias') |
|||
const NowDomains = require('./domains') |
|||
|
|||
exports.assignAlias = async ( |
|||
autoAlias, |
|||
token, |
|||
deployment, |
|||
apiUrl, |
|||
debug, |
|||
currentTeam, |
|||
user |
|||
) => { |
|||
const aliases = new NowAlias({ apiUrl, token, debug, currentTeam }) |
|||
const domains = new NowDomains({ apiUrl, token, debug, currentTeam }) |
|||
console.log( |
|||
`> Assigning alias ${chalk.bold.underline(autoAlias)} to deployment...` |
|||
) |
|||
|
|||
// Assign alias
|
|||
await aliases.set( |
|||
String(deployment), |
|||
String(autoAlias), |
|||
domains, |
|||
currentTeam, |
|||
user |
|||
) |
|||
} |
|||
|
|||
exports.reAlias = async ( |
|||
token, |
|||
host, |
|||
pointer, |
|||
help, |
|||
exit, |
|||
apiUrl, |
|||
debug, |
|||
alias, |
|||
currentTeam, |
|||
user |
|||
) => { |
|||
const path = process.cwd() |
|||
|
|||
const configFiles = { |
|||
pkg: join(path, 'package.json'), |
|||
nowJSON: join(path, 'now.json') |
|||
} |
|||
|
|||
if (!fs.existsSync(configFiles.pkg) && !fs.existsSync(configFiles.nowJSON)) { |
|||
error( |
|||
`Couldn't find a now.json or package.json file with an alias list in it` |
|||
) |
|||
return |
|||
} |
|||
|
|||
const { nowConfig, name } = await readMetaData(path, { |
|||
deploymentType: 'npm', // Hard coding settings…
|
|||
quiet: true // `quiet`
|
|||
}) |
|||
|
|||
if (!host) { |
|||
const lastAlias = await alias.last(name) |
|||
host = lastAlias.url |
|||
} |
|||
|
|||
if (!nowConfig) { |
|||
help() |
|||
return exit(0) |
|||
} |
|||
|
|||
let pointers = [] |
|||
|
|||
if (pointer) { |
|||
pointers.push(pointer) |
|||
} else { |
|||
if (nowConfig.alias) { |
|||
const value = nowConfig.alias |
|||
|
|||
if (typeof value === 'string') { |
|||
pointers.push(value) |
|||
} else if (Array.isArray(value)) { |
|||
pointers = pointers.concat(nowConfig.alias) |
|||
} else { |
|||
error( |
|||
`Property ${chalk.grey('aliases')} is not a valid array or string` |
|||
) |
|||
return exit(1) |
|||
} |
|||
} |
|||
|
|||
if (nowConfig.aliases && Array.isArray(nowConfig.aliases)) { |
|||
console.log( |
|||
`${chalk.red('Deprecated!')} The property ${chalk.grey( |
|||
'aliases' |
|||
)} will be removed from the config file soon.` |
|||
) |
|||
console.log('Read more about the new way here: http://bit.ly/2l2v5Fg\n') |
|||
|
|||
pointers = pointers.concat(nowConfig.aliases) |
|||
} |
|||
} |
|||
|
|||
if (pointers.length === 0) { |
|||
help() |
|||
return exit(0) |
|||
} |
|||
|
|||
const assignments = [] |
|||
|
|||
for (const pointer of pointers) { |
|||
assignments.push( |
|||
exports.assignAlias( |
|||
pointer, |
|||
token, |
|||
host, |
|||
apiUrl, |
|||
debug, |
|||
currentTeam, |
|||
user |
|||
) |
|||
) |
|||
} |
|||
|
|||
await Promise.all(assignments) |
|||
} |
@ -1,195 +0,0 @@ |
|||
// Native
|
|||
const { basename, resolve: resolvePath } = require('path') |
|||
|
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
const { readFile } = require('fs-extra') |
|||
const { parse: parseDockerfile } = require('docker-file-parser') |
|||
const determineType = require('deployment-type') |
|||
|
|||
module.exports = readMetaData |
|||
|
|||
async function readMetaData( |
|||
path, |
|||
{ |
|||
deploymentType, |
|||
deploymentName, |
|||
sessionAffinity, |
|||
quiet = false, |
|||
strict = true |
|||
} |
|||
) { |
|||
let description |
|||
let type = deploymentType |
|||
let name = deploymentName |
|||
let affinity = sessionAffinity |
|||
|
|||
const pkg = await readJSON(path, 'package.json') |
|||
let nowConfig = await readJSON(path, 'now.json') |
|||
const dockerfile = await readDockerfile(path) |
|||
|
|||
const hasNowJson = Boolean(nowConfig) |
|||
|
|||
if (pkg && pkg.now) { |
|||
// If the project has both a `now.json` and `now` Object in the `package.json`
|
|||
// file, then fail hard and let the user know that they need to pick one or the
|
|||
// other
|
|||
if (nowConfig) { |
|||
const err = new Error( |
|||
'You have a `now` configuration field inside `package.json` ' + |
|||
'but configuration is also present in `now.json`! ' + |
|||
"Please ensure there's a single source of configuration by removing one." |
|||
) |
|||
err.userError = true |
|||
throw err |
|||
} else { |
|||
nowConfig = pkg.now |
|||
} |
|||
} |
|||
|
|||
// We can remove this once the prompt for choosing `--npm` or `--docker` is gone
|
|||
if (pkg && pkg.now && pkg.now.type) { |
|||
type = nowConfig.type |
|||
} |
|||
|
|||
// The same goes for this
|
|||
if (nowConfig && nowConfig.type) { |
|||
type = nowConfig.type |
|||
} |
|||
|
|||
if (!type) { |
|||
type = await determineType(path) |
|||
|
|||
// Both `package.json` and `Dockerfile` exist! Prompt the user to pick one.
|
|||
// We can remove this soon (details are internal) - also read the comment paragraph above
|
|||
if (type === 'docker' && (pkg && dockerfile)) { |
|||
const err = new Error( |
|||
'Ambiguous deployment (`package.json` and `Dockerfile` found). ' + |
|||
'Please supply `--npm` or `--docker` to disambiguate.' |
|||
) |
|||
|
|||
err.userError = true |
|||
err.code = 'MULTIPLE_MANIFESTS' |
|||
|
|||
throw err |
|||
} |
|||
} |
|||
|
|||
if (!name && nowConfig) { |
|||
name = nowConfig.name |
|||
} |
|||
|
|||
if (!affinity && nowConfig) { |
|||
affinity = nowConfig.sessionAffinity |
|||
} |
|||
|
|||
if (type === 'npm') { |
|||
if (pkg) { |
|||
if (!name && pkg.now && pkg.now.name) { |
|||
name = String(pkg.now.name) |
|||
} |
|||
|
|||
if (!name && pkg.name) { |
|||
name = String(pkg.name) |
|||
} |
|||
|
|||
description = pkg.description |
|||
} |
|||
} else if (type === 'docker') { |
|||
if (strict && dockerfile.length <= 0) { |
|||
const err = new Error('No commands found in `Dockerfile`') |
|||
err.userError = true |
|||
|
|||
throw err |
|||
} |
|||
|
|||
const labels = {} |
|||
|
|||
dockerfile.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => { |
|||
for (const key in args) { |
|||
if (!{}.hasOwnProperty.call(args, key)) { |
|||
continue |
|||
} |
|||
|
|||
// Unescape and convert into string
|
|||
try { |
|||
labels[key] = args[key] |
|||
} catch (err) { |
|||
const e = new Error( |
|||
`Error parsing value for LABEL ${key} in \`Dockerfile\`` |
|||
) |
|||
|
|||
e.userError = true |
|||
throw e |
|||
} |
|||
} |
|||
}) |
|||
|
|||
if (!name) { |
|||
name = labels.name |
|||
} |
|||
|
|||
description = labels.description |
|||
} else if (type === 'static') { |
|||
// Do nothing
|
|||
} else { |
|||
throw new TypeError(`Unsupported "deploymentType": ${type}`) |
|||
} |
|||
|
|||
// No name in `package.json` / `now.json`, or "name" label in Dockerfile.
|
|||
// Default to the basename of the root dir
|
|||
if (!name) { |
|||
name = basename(path) |
|||
|
|||
if (!quiet && type !== 'static') { |
|||
if (type === 'docker') { |
|||
console.log( |
|||
`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}` |
|||
) |
|||
} else { |
|||
console.log( |
|||
`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}` |
|||
) |
|||
} |
|||
} |
|||
} |
|||
|
|||
return { |
|||
name, |
|||
description, |
|||
type, |
|||
pkg, |
|||
nowConfig, |
|||
hasNowJson, |
|||
|
|||
// XXX: legacy
|
|||
deploymentType: type, |
|||
sessionAffinity: affinity |
|||
} |
|||
} |
|||
|
|||
async function readJSON(path, name) { |
|||
try { |
|||
const contents = await readFile(resolvePath(path, name), 'utf8') |
|||
return JSON.parse(contents) |
|||
} catch (err) { |
|||
// If the file doesn't exist then that's fine; any other error bubbles up
|
|||
if (err.code !== 'ENOENT') { |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function readDockerfile(path, name = 'Dockerfile') { |
|||
try { |
|||
const contents = await readFile(resolvePath(path, name), 'utf8') |
|||
return parseDockerfile(contents, { includeComments: true }) |
|||
} catch (err) { |
|||
// If the file doesn't exist then that's fine; any other error bubbles up
|
|||
if (err.code !== 'ENOENT') { |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
} |
|||
} |
@ -1,78 +0,0 @@ |
|||
const linelog = require('single-line-log').stdout |
|||
const range = require('lodash.range') |
|||
const ms = require('ms') |
|||
const chalk = require('chalk') |
|||
const retry = require('async-retry') |
|||
|
|||
function barify(cur, tot) { |
|||
return ( |
|||
'[' + |
|||
range(0, cur).map(() => '=').join('') + |
|||
range(cur, tot).map(() => '-').join('') + |
|||
']' |
|||
) |
|||
} |
|||
|
|||
module.exports = async function(now, url) { |
|||
const match = await now.findDeployment(url) |
|||
const { min, max, current } = match.scale |
|||
|
|||
let targetReplicaCount = min |
|||
if (current < min) { |
|||
targetReplicaCount = min |
|||
} else if (current > max) { |
|||
targetReplicaCount = max |
|||
} else { |
|||
return |
|||
} |
|||
|
|||
if (targetReplicaCount === 0) { |
|||
console.log(`> Scaled to 0 instances`) |
|||
return |
|||
} |
|||
const startTime = Date.now() |
|||
|
|||
let barcurr = current |
|||
const end = Math.max(current, max) |
|||
linelog( |
|||
`${chalk.gray('>')} Scaling to ${chalk.bold( |
|||
String(targetReplicaCount) + |
|||
(targetReplicaCount === 1 ? ' instance' : ' instances') |
|||
)}: ` + barify(barcurr, end)
|
|||
) |
|||
|
|||
const instances = await retry( |
|||
async () => { |
|||
const res = await now.listInstances(match.uid) |
|||
if (barcurr !== res.length) { |
|||
barcurr = res.length |
|||
linelog( |
|||
`${chalk.gray('>')} Scaling to ${chalk.bold( |
|||
String(targetReplicaCount) + |
|||
(targetReplicaCount === 1 ? ' instance' : ' instances') |
|||
)}: ` + barify(barcurr, end)
|
|||
) |
|||
|
|||
if (barcurr === targetReplicaCount) { |
|||
linelog.clear() |
|||
linelog( |
|||
`> Scaled to ${chalk.bold( |
|||
String(targetReplicaCount) + |
|||
(targetReplicaCount === 1 ? ' instance' : ' instances') |
|||
)}: ${chalk.gray('[' + ms(Date.now() - startTime) + ']')}\n` |
|||
) |
|||
return res |
|||
} |
|||
} |
|||
|
|||
throw new Error('Not ready yet') |
|||
}, |
|||
{ retries: 5000, minTimeout: 10, maxTimeout: 20 } |
|||
) |
|||
|
|||
process.nextTick(() => { |
|||
instances.forEach(inst => { |
|||
console.log(`${chalk.gray('-')} ${chalk.underline(inst.url)}`) |
|||
}) |
|||
}) |
|||
} |
@ -1,40 +0,0 @@ |
|||
// Ours
|
|||
const Now = require('../lib') |
|||
|
|||
module.exports = class Scale extends Now { |
|||
getInstances(id) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET /deployments/${id}/instances`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/now/deployments/${id}/instances`, { |
|||
method: 'GET' |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd( |
|||
`> [debug] #${attempt} GET /deployments/${id}/instances` |
|||
) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
if (res.status === 404 || res.status === 400) { |
|||
const err = new Error(body.error.message) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
throw new Error(body.error.message) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
} |
@ -1,119 +0,0 @@ |
|||
// Ours
|
|||
const Now = require('../lib') |
|||
|
|||
const isUserError = res => ((res.status / 100) | 0) === 4 |
|||
|
|||
module.exports = class Secrets extends Now { |
|||
ls() { |
|||
return this.listSecrets() |
|||
} |
|||
|
|||
rm(nameOrId) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/now/secrets/${nameOrId}`, { |
|||
method: 'DELETE' |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} DELETE /secrets/${nameOrId}`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
if (isUserError(res)) { |
|||
const err = new Error(body.error.message) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
throw new Error(body.error.message) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
add(name, value) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} POST /secrets`) |
|||
} |
|||
|
|||
const res = await this._fetch('/now/secrets', { |
|||
method: 'POST', |
|||
body: { |
|||
name, |
|||
value: value.toString() |
|||
} |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} POST /secrets`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
if (isUserError(res)) { |
|||
const err = new Error(body.error.message) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
throw new Error(body.error.message) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
rename(nameOrId, newName) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/now/secrets/${nameOrId}`, { |
|||
method: 'PATCH', |
|||
body: { |
|||
name: newName |
|||
} |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status !== 200) { |
|||
if (isUserError(res)) { |
|||
const err = new Error(body.error.message) |
|||
err.userError = true |
|||
return bail(err) |
|||
} |
|||
|
|||
throw new Error(body.error.message) |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
} |
@ -1,30 +0,0 @@ |
|||
const fs = require('fs-extra') |
|||
|
|||
module.exports = async function(apps) { |
|||
let pkg |
|||
try { |
|||
const json = await fs.readFile('package.json') |
|||
pkg = JSON.parse(json) |
|||
} catch (err) { |
|||
pkg = {} |
|||
} |
|||
|
|||
return apps |
|||
.map(([name, deps]) => { |
|||
deps = deps.slice().sort((a, b) => { |
|||
return b.created - a.created |
|||
}) |
|||
return [name, deps] |
|||
}) |
|||
.sort(([nameA, depsA], [nameB, depsB]) => { |
|||
if (pkg.name === nameA) { |
|||
return -1 |
|||
} |
|||
|
|||
if (pkg.name === nameB) { |
|||
return 1 |
|||
} |
|||
|
|||
return depsB[0].created - depsA[0].created |
|||
}) |
|||
} |
@ -1,5 +0,0 @@ |
|||
function strlen(str) { |
|||
return str.replace(/\u001b[^m]*m/g, '').length |
|||
} |
|||
|
|||
module.exports = strlen |
@ -1,139 +0,0 @@ |
|||
const Now = require('../lib') |
|||
|
|||
module.exports = class Teams extends Now { |
|||
async create({ slug }) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} POST /teams}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/teams`, { |
|||
method: 'POST', |
|||
body: { |
|||
slug |
|||
} |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} POST /teams`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status === 400) { |
|||
const e = new Error(body.error.message) |
|||
e.code = body.error.code |
|||
return bail(e) |
|||
} else if (res.status !== 200) { |
|||
const e = new Error(body.error.message) |
|||
e.code = body.error.code |
|||
throw e |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
async edit({ id, slug, name }) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} PATCH /teams/${id}}`) |
|||
} |
|||
|
|||
const payload = {} |
|||
if (name) { |
|||
payload.name = name |
|||
} |
|||
if (slug) { |
|||
payload.slug = slug |
|||
} |
|||
|
|||
const res = await this._fetch(`/teams/${id}`, { |
|||
method: 'PATCH', |
|||
body: payload |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} PATCH /teams/${id}`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status === 400) { |
|||
const e = new Error(body.error.message) |
|||
e.code = body.error.code |
|||
return bail(e) |
|||
} else if (res.status !== 200) { |
|||
const e = new Error(body.error.message) |
|||
e.code = body.error.code |
|||
throw e |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
async inviteUser({ teamId, email }) { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} POST /teams/${teamId}/members}`) |
|||
} |
|||
const res = await this._fetch(`/teams/${teamId}/members`, { |
|||
method: 'POST', |
|||
body: { |
|||
email |
|||
} |
|||
}) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} POST /teams/${teamId}/members}`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
const body = await res.json() |
|||
|
|||
if (res.status === 400) { |
|||
const e = new Error(body.error.message) |
|||
e.code = body.error.code |
|||
return bail(e) |
|||
} else if (res.status !== 200) { |
|||
const e = new Error(body.error.message) |
|||
e.code = body.error.code |
|||
throw e |
|||
} |
|||
|
|||
return body |
|||
}) |
|||
} |
|||
|
|||
async ls() { |
|||
return this.retry(async (bail, attempt) => { |
|||
if (this._debug) { |
|||
console.time(`> [debug] #${attempt} GET /teams}`) |
|||
} |
|||
|
|||
const res = await this._fetch(`/teams`) |
|||
|
|||
if (this._debug) { |
|||
console.timeEnd(`> [debug] #${attempt} GET /teams`) |
|||
} |
|||
|
|||
if (res.status === 403) { |
|||
return bail(new Error('Unauthorized')) |
|||
} |
|||
|
|||
return res.json() |
|||
}) |
|||
} |
|||
} |
@ -1,22 +0,0 @@ |
|||
// Native
|
|||
const { resolve } = require('path') |
|||
|
|||
// Ours
|
|||
const { npm: getFiles } = require('./get-files') |
|||
|
|||
getFiles(resolve('../mng-test/files-in-package')) |
|||
.then(files => { |
|||
console.log(files) |
|||
|
|||
getFiles(resolve('../mng-test/files-in-package-ignore')) |
|||
.then(files2 => { |
|||
console.log('ignored: ') |
|||
console.log(files2) |
|||
}) |
|||
.catch(err => { |
|||
console.log(err.stack) |
|||
}) |
|||
}) |
|||
.catch(err => { |
|||
console.log(err.stack) |
|||
}) |
@ -1,20 +0,0 @@ |
|||
// Native
|
|||
const { parse } = require('url') |
|||
|
|||
/** |
|||
* Converts a valid deployment lookup parameter to a hostname. |
|||
* `http://google.com` => google.com |
|||
* google.com => google.com |
|||
*/ |
|||
|
|||
function toHost(url) { |
|||
if (/^https?:\/\//.test(url)) { |
|||
return parse(url).host |
|||
} |
|||
|
|||
// Remove any path if present
|
|||
// `a.b.c/` => `a.b.c`
|
|||
return url.replace(/(\/\/)?([^/]+)(.*)/, '$2') |
|||
} |
|||
|
|||
module.exports = toHost |
@ -1,7 +0,0 @@ |
|||
// Native
|
|||
const os = require('os') |
|||
|
|||
// Ours
|
|||
const { version } = require('./pkg') |
|||
|
|||
module.exports = `now ${version} node-${process.version} ${os.platform()} (${os.arch()})` |
@ -1,69 +0,0 @@ |
|||
const _fetch = require('node-fetch') |
|||
const { responseError } = require('./error') |
|||
|
|||
function _filter(data) { |
|||
data = data.user |
|||
|
|||
return { |
|||
uid: data.uid, |
|||
username: data.username, |
|||
email: data.email |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Gets all the info we have about an user |
|||
* |
|||
* @param {Object} fetch Optionally, _our_ `fetch` can be passed here |
|||
* @param {String} token Only necessary if `fetch` is undefined |
|||
* @param {String} apiUrl Only necessary if `fetch` is undefined |
|||
* @param {Boolean} filter If `true`, the `filter` used will to the data |
|||
* before returning |
|||
* @return {Object} |
|||
*/ |
|||
async function get( |
|||
{ fetch, token, apiUrl = 'https://api.zeit.co', filter = true } = {} |
|||
) { |
|||
let headers = {} |
|||
const endpoint = '/www/user' |
|||
const url = fetch ? endpoint : apiUrl + endpoint |
|||
|
|||
if (!fetch) { |
|||
headers = { |
|||
Authorization: `Bearer ${token}` |
|||
} |
|||
fetch = _fetch |
|||
} |
|||
|
|||
const res = await fetch(url, { headers }) |
|||
|
|||
if (res.status === 403) { |
|||
const err = Error( |
|||
'Your authentication token is invalid. Try running `now login` to log in again.' |
|||
) |
|||
err.userError = true |
|||
throw err |
|||
} |
|||
|
|||
if (res.status >= 400 && res.status < 500) { |
|||
const err = await responseError(res) |
|||
throw err |
|||
} |
|||
|
|||
if (res.status !== 200) { |
|||
throw new Error('API Error getting user data') |
|||
} |
|||
|
|||
const json = await res.json() |
|||
|
|||
if (filter) { |
|||
return _filter(json) |
|||
} |
|||
|
|||
return json |
|||
} |
|||
|
|||
module.exports = { |
|||
get, |
|||
filter: _filter |
|||
} |
@ -1,8 +0,0 @@ |
|||
{ |
|||
"VISA": "Visa", |
|||
"MASTERCARD": "MasterCard", |
|||
"AMERICANEXPRESS": "American Express", |
|||
"DINERSCLUB": "Diners Club", |
|||
"DISCOVER": "Discover", |
|||
"JCB": "JCB" |
|||
} |
@ -1,251 +0,0 @@ |
|||
{ |
|||
"United States": "US", |
|||
"Afghanistan": "AF", |
|||
"Åland Islands": "AX", |
|||
"Albania": "AL", |
|||
"Algeria": "DZ", |
|||
"American Samoa": "AS", |
|||
"Andorra": "AD", |
|||
"Angola": "AO", |
|||
"Anguilla": "AI", |
|||
"Antarctica": "AQ", |
|||
"Antigua and Barbuda": "AG", |
|||
"Argentina": "AR", |
|||
"Armenia": "AM", |
|||
"Aruba": "AW", |
|||
"Australia": "AU", |
|||
"Austria": "AT", |
|||
"Azerbaijan": "AZ", |
|||
"Bahamas": "BS", |
|||
"Bahrain": "BH", |
|||
"Bangladesh": "BD", |
|||
"Barbados": "BB", |
|||
"Belarus": "BY", |
|||
"Belgium": "BE", |
|||
"Belize": "BZ", |
|||
"Benin": "BJ", |
|||
"Bermuda": "BM", |
|||
"Bhutan": "BT", |
|||
"Bolivia, Plurinational State of": "BO", |
|||
"Bonaire, Sint Eustatius and Saba": "BQ", |
|||
"Bosnia and Herzegovina": "BA", |
|||
"Botswana": "BW", |
|||
"Bouvet Island": "BV", |
|||
"Brazil": "BR", |
|||
"British Indian Ocean Territory": "IO", |
|||
"Brunei Darussalam": "BN", |
|||
"Bulgaria": "BG", |
|||
"Burkina Faso": "BF", |
|||
"Burundi": "BI", |
|||
"Cambodia": "KH", |
|||
"Cameroon": "CM", |
|||
"Canada": "CA", |
|||
"Cape Verde": "CV", |
|||
"Cayman Islands": "KY", |
|||
"Central African Republic": "CF", |
|||
"Chad": "TD", |
|||
"Chile": "CL", |
|||
"China": "CN", |
|||
"Christmas Island": "CX", |
|||
"Cocos (Keeling) Islands": "CC", |
|||
"Colombia": "CO", |
|||
"Comoros": "KM", |
|||
"Congo": "CG", |
|||
"CD": "Congo, the Democratic Republic of the", |
|||
"Cook Islands": "CK", |
|||
"Costa Rica": "CR", |
|||
"Côte d'Ivoire": "CI", |
|||
"Croatia": "HR", |
|||
"Cuba": "CU", |
|||
"Curaçao": "CW", |
|||
"Cyprus": "CY", |
|||
"Czech Republic": "CZ", |
|||
"Denmark": "DK", |
|||
"Djibouti": "DJ", |
|||
"Dominica": "DM", |
|||
"Dominican Republic": "DO", |
|||
"Ecuador": "EC", |
|||
"Egypt": "EG", |
|||
"El Salvador": "SV", |
|||
"Equatorial Guinea": "GQ", |
|||
"Eritrea": "ER", |
|||
"Estonia": "EE", |
|||
"Ethiopia": "ET", |
|||
"Falkland Islands (Malvinas)": "FK", |
|||
"Faroe Islands": "FO", |
|||
"Fiji": "FJ", |
|||
"Finland": "FI", |
|||
"France": "FR", |
|||
"French Guiana": "GF", |
|||
"French Polynesia": "PF", |
|||
"French Southern Territories": "TF", |
|||
"Gabon": "GA", |
|||
"Gambia": "GM", |
|||
"Georgia": "GE", |
|||
"Germany": "DE", |
|||
"Ghana": "GH", |
|||
"Gibraltar": "GI", |
|||
"Greece": "GR", |
|||
"Greenland": "GL", |
|||
"Grenada": "GD", |
|||
"Guadeloupe": "GP", |
|||
"Guam": "GU", |
|||
"Guatemala": "GT", |
|||
"Guernsey": "GG", |
|||
"Guinea": "GN", |
|||
"Guinea-Bissau": "GW", |
|||
"Guyana": "GY", |
|||
"Haiti": "HT", |
|||
"Heard Island and McDonald Islands": "HM", |
|||
"Holy See (Vatican City State)": "VA", |
|||
"Honduras": "HN", |
|||
"Hong Kong": "HK", |
|||
"Hungary": "HU", |
|||
"Iceland": "IS", |
|||
"India": "IN", |
|||
"Indonesia": "ID", |
|||
"Iran, Islamic Republic of": "IR", |
|||
"Iraq": "IQ", |
|||
"Ireland": "IE", |
|||
"Isle of Man": "IM", |
|||
"Israel": "IL", |
|||
"Italy": "IT", |
|||
"Jamaica": "JM", |
|||
"Japan": "JP", |
|||
"Jersey": "JE", |
|||
"Jordan": "JO", |
|||
"Kazakhstan": "KZ", |
|||
"Kenya": "KE", |
|||
"Kiribati": "KI", |
|||
"KP": "Korea, Democratic People's Republic of", |
|||
"Korea, Republic of": "KR", |
|||
"Kuwait": "KW", |
|||
"Kyrgyzstan": "KG", |
|||
"Lao People's Democratic Republic": "LA", |
|||
"Latvia": "LV", |
|||
"Lebanon": "LB", |
|||
"Lesotho": "LS", |
|||
"Liberia": "LR", |
|||
"Libya": "LY", |
|||
"Liechtenstein": "LI", |
|||
"Lithuania": "LT", |
|||
"Luxembourg": "LU", |
|||
"Macao": "MO", |
|||
"MK": "Macedonia, the former Yugoslav Republic of", |
|||
"Madagascar": "MG", |
|||
"Malawi": "MW", |
|||
"Malaysia": "MY", |
|||
"Maldives": "MV", |
|||
"Mali": "ML", |
|||
"Malta": "MT", |
|||
"Marshall Islands": "MH", |
|||
"Martinique": "MQ", |
|||
"Mauritania": "MR", |
|||
"Mauritius": "MU", |
|||
"Mayotte": "YT", |
|||
"Mexico": "MX", |
|||
"Micronesia, Federated States of": "FM", |
|||
"Moldova, Republic of": "MD", |
|||
"Monaco": "MC", |
|||
"Mongolia": "MN", |
|||
"Montenegro": "ME", |
|||
"Montserrat": "MS", |
|||
"Morocco": "MA", |
|||
"Mozambique": "MZ", |
|||
"Myanmar": "MM", |
|||
"Namibia": "NA", |
|||
"Nauru": "NR", |
|||
"Nepal": "NP", |
|||
"Netherlands": "NL", |
|||
"New Caledonia": "NC", |
|||
"New Zealand": "NZ", |
|||
"Nicaragua": "NI", |
|||
"Niger": "NE", |
|||
"Nigeria": "NG", |
|||
"Niue": "NU", |
|||
"Norfolk Island": "NF", |
|||
"Northern Mariana Islands": "MP", |
|||
"Norway": "NO", |
|||
"Oman": "OM", |
|||
"Pakistan": "PK", |
|||
"Palau": "PW", |
|||
"Palestinian Territory, Occupied": "PS", |
|||
"Panama": "PA", |
|||
"Papua New Guinea": "PG", |
|||
"Paraguay": "PY", |
|||
"Peru": "PE", |
|||
"Philippines": "PH", |
|||
"Pitcairn": "PN", |
|||
"Poland": "PL", |
|||
"Portugal": "PT", |
|||
"Puerto Rico": "PR", |
|||
"Qatar": "QA", |
|||
"Réunion": "RE", |
|||
"Romania": "RO", |
|||
"Russian Federation": "RU", |
|||
"Rwanda": "RW", |
|||
"Saint Barthélemy": "BL", |
|||
"SH": "Saint Helena, Ascension and Tristan da Cunha", |
|||
"Saint Kitts and Nevis": "KN", |
|||
"Saint Lucia": "LC", |
|||
"Saint Martin (French part)": "MF", |
|||
"Saint Pierre and Miquelon": "PM", |
|||
"Saint Vincent and the Grenadines": "VC", |
|||
"Samoa": "WS", |
|||
"San Marino": "SM", |
|||
"Sao Tome and Principe": "ST", |
|||
"Saudi Arabia": "SA", |
|||
"Senegal": "SN", |
|||
"Serbia": "RS", |
|||
"Seychelles": "SC", |
|||
"Sierra Leone": "SL", |
|||
"Singapore": "SG", |
|||
"Sint Maarten (Dutch part)": "SX", |
|||
"Slovakia": "SK", |
|||
"Slovenia": "SI", |
|||
"Solomon Islands": "SB", |
|||
"Somalia": "SO", |
|||
"South Africa": "ZA", |
|||
"GS": "South Georgia and the South Sandwich Islands", |
|||
"South Sudan": "SS", |
|||
"Spain": "ES", |
|||
"Sri Lanka": "LK", |
|||
"Sudan": "SD", |
|||
"Suriname": "SR", |
|||
"Svalbard and Jan Mayen": "SJ", |
|||
"Swaziland": "SZ", |
|||
"Sweden": "SE", |
|||
"Switzerland": "CH", |
|||
"Syrian Arab Republic": "SY", |
|||
"Taiwan, Province of China": "TW", |
|||
"Tajikistan": "TJ", |
|||
"Tanzania, United Republic of": "TZ", |
|||
"Thailand": "TH", |
|||
"Timor-Leste": "TL", |
|||
"Togo": "TG", |
|||
"Tokelau": "TK", |
|||
"Tonga": "TO", |
|||
"Trinidad and Tobago": "TT", |
|||
"Tunisia": "TN", |
|||
"Turkey": "TR", |
|||
"Turkmenistan": "TM", |
|||
"Turks and Caicos Islands": "TC", |
|||
"Tuvalu": "TV", |
|||
"Uganda": "UG", |
|||
"Ukraine": "UA", |
|||
"United Arab Emirates": "AE", |
|||
"United Kingdom": "GB", |
|||
"UM": "United States Minor Outlying Islands", |
|||
"Uruguay": "UY", |
|||
"Uzbekistan": "UZ", |
|||
"Vanuatu": "VU", |
|||
"Venezuela, Bolivarian Republic of": "VE", |
|||
"Viet Nam": "VN", |
|||
"Virgin Islands, British": "VG", |
|||
"Virgin Islands, U.S.": "VI", |
|||
"Wallis and Futuna": "WF", |
|||
"Western Sahara": "EH", |
|||
"Yemen": "YE", |
|||
"Zambia": "ZM", |
|||
"Zimbabwe": "ZW" |
|||
} |
@ -1,33 +0,0 @@ |
|||
// Packages
|
|||
const gMaps = require('@google/maps') |
|||
|
|||
const MAPS_API_KEY = 'AIzaSyALfKTQ6AiIoJ8WGDXR3E7IBOwlHoTPfYY' |
|||
|
|||
// eslint-disable-next-line camelcase
|
|||
module.exports = function({ country, zipCode: postal_code }) { |
|||
return new Promise(resolve => { |
|||
const maps = gMaps.createClient({ key: MAPS_API_KEY }) |
|||
maps.geocode( |
|||
{ |
|||
address: `${postal_code} ${country}` // eslint-disable-line camelcase
|
|||
}, |
|||
(err, res) => { |
|||
if (err || res.json.results.length === 0) { |
|||
resolve() |
|||
} |
|||
|
|||
const data = res.json.results[0] |
|||
const components = {} |
|||
data.address_components.forEach(c => { |
|||
components[c.types[0]] = c |
|||
}) |
|||
const state = components.administrative_area_level_1 |
|||
const city = components.locality |
|||
resolve({ |
|||
state: state && state.long_name, |
|||
city: city && city.long_name |
|||
}) |
|||
} |
|||
) |
|||
}) |
|||
} |
@ -1,49 +0,0 @@ |
|||
// Native
|
|||
const os = require('os') |
|||
const path = require('path') |
|||
|
|||
const checkPath = async dir => { |
|||
if (!dir) { |
|||
return |
|||
} |
|||
|
|||
const home = os.homedir() |
|||
let location |
|||
|
|||
const paths = { |
|||
home, |
|||
desktop: path.join(home, 'Desktop'), |
|||
downloads: path.join(home, 'Downloads') |
|||
} |
|||
|
|||
for (const locationPath in paths) { |
|||
if (!{}.hasOwnProperty.call(paths, locationPath)) { |
|||
continue |
|||
} |
|||
|
|||
if (dir === paths[locationPath]) { |
|||
location = locationPath |
|||
} |
|||
} |
|||
|
|||
if (!location) { |
|||
return |
|||
} |
|||
|
|||
let locationName |
|||
|
|||
switch (location) { |
|||
case 'home': |
|||
locationName = 'user directory' |
|||
break |
|||
case 'downloads': |
|||
locationName = 'downloads directory' |
|||
break |
|||
default: |
|||
locationName = location |
|||
} |
|||
|
|||
throw new Error(`You're trying to deploy your ${locationName}.`) |
|||
} |
|||
|
|||
module.exports = checkPath |
@ -1,29 +0,0 @@ |
|||
const error = require('../output/error') |
|||
|
|||
module.exports = function(err) { |
|||
switch (err.code) { |
|||
case 'invalid_domain': { |
|||
error('Invalid domain') |
|||
break |
|||
} |
|||
case 'not_available': { |
|||
error("Domain can't be purchased at this time") |
|||
break |
|||
} |
|||
case 'service_unavailabe': { |
|||
error('Purchase failed – Service unavailable') |
|||
break |
|||
} |
|||
case 'unexpected_error': { |
|||
error('Purchase failed – Unexpected error') |
|||
break |
|||
} |
|||
case 'forbidden_premium': { |
|||
error('A coupon cannot be used to register a premium domain') |
|||
break |
|||
} |
|||
default: { |
|||
error(err.message) |
|||
} |
|||
} |
|||
} |
@ -1,8 +0,0 @@ |
|||
module.exports = code => { |
|||
// We give stdout some time to flush out
|
|||
// because there's a node bug where
|
|||
// stdout writes are asynchronous
|
|||
// https://github.com/nodejs/node/issues/6456
|
|||
/* eslint-disable unicorn/no-process-exit */ |
|||
setTimeout(() => process.exit(code || 0), 100) |
|||
} |
@ -1,7 +0,0 @@ |
|||
const error = require('./output/error') |
|||
const exit = require('./exit') |
|||
|
|||
module.exports = (msg, code = 1) => { |
|||
error(msg) |
|||
exit(code) |
|||
} |
@ -1,79 +0,0 @@ |
|||
const inquirer = require('inquirer') |
|||
const stripAnsi = require('strip-ansi') |
|||
|
|||
// eslint-disable-next-line import/no-unassigned-import
|
|||
require('./patch-inquirer') |
|||
|
|||
function getLength(string) { |
|||
let biggestLength = 0 |
|||
string.split('\n').map(str => { |
|||
str = stripAnsi(str) |
|||
if (str.length > biggestLength) { |
|||
biggestLength = str.length |
|||
} |
|||
return undefined |
|||
}) |
|||
return biggestLength |
|||
} |
|||
|
|||
module.exports = async function({ |
|||
message = 'the question', |
|||
// eslint-disable-line no-unused-vars
|
|||
choices = [ |
|||
{ |
|||
name: 'something\ndescription\ndetails\netc', |
|||
value: 'something unique', |
|||
short: 'generally the first line of `name`' |
|||
} |
|||
], |
|||
pageSize = 15, // Show 15 lines without scrolling (~4 credit cards)
|
|||
separator = true, // Puts a blank separator between each choice
|
|||
abort = 'end' // Wether the `abort` option will be at the `start` or the `end`
|
|||
}) { |
|||
let biggestLength = 0 |
|||
|
|||
choices = choices.map(choice => { |
|||
if (choice.name) { |
|||
const length = getLength(choice.name) |
|||
if (length > biggestLength) { |
|||
biggestLength = length |
|||
} |
|||
return choice |
|||
} |
|||
throw new Error('Invalid choice') |
|||
}) |
|||
|
|||
if (separator === true) { |
|||
choices = choices.reduce( |
|||
(prev, curr) => prev.concat(new inquirer.Separator(' '), curr), |
|||
[] |
|||
) |
|||
} |
|||
|
|||
const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength)) |
|||
const _abort = { |
|||
name: 'Abort', |
|||
value: undefined |
|||
} |
|||
|
|||
if (abort === 'start') { |
|||
const blankSep = choices.shift() |
|||
choices.unshift(abortSeparator) |
|||
choices.unshift(_abort) |
|||
choices.unshift(blankSep) |
|||
} else { |
|||
choices.push(abortSeparator) |
|||
choices.push(_abort) |
|||
} |
|||
|
|||
const nonce = Date.now() |
|||
const answer = await inquirer.prompt({ |
|||
name: nonce, |
|||
type: 'list', |
|||
message, |
|||
choices, |
|||
pageSize |
|||
}) |
|||
|
|||
return answer[nonce] |
|||
} |
@ -1,20 +0,0 @@ |
|||
const inquirer = require('inquirer') |
|||
const chalk = require('chalk') |
|||
|
|||
// Here we patch inquirer to use a `>` instead of the ugly green `?`
|
|||
|
|||
/* eslint-disable no-multiple-empty-lines, no-var, no-undef, no-eq-null, eqeqeq, semi */ |
|||
const getQuestion = function() { |
|||
var message = chalk.bold('> ' + this.opt.message) + ' ' |
|||
|
|||
// Append the default if available, and if question isn't answered
|
|||
if (this.opt.default != null && this.status !== 'answered') { |
|||
message += chalk.dim('(' + this.opt.default + ') ') |
|||
} |
|||
|
|||
return message |
|||
} |
|||
/* eslint-enable */ |
|||
|
|||
inquirer.prompt.prompts.input.prototype.getQuestion = getQuestion |
|||
inquirer.prompt.prompts.list.prototype.getQuestion = getQuestion |
@ -1,58 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
module.exports = ( |
|||
label, |
|||
{ |
|||
defaultValue = false, |
|||
abortSequences = new Set(['\u0003']), |
|||
resolveChars = new Set(['\r']), |
|||
yesChar = 'y', |
|||
noChar = 'n', |
|||
stdin = process.stdin, |
|||
stdout = process.stdout, |
|||
trailing = '' |
|||
} = {} |
|||
) => { |
|||
return new Promise(resolve => { |
|||
const isRaw = stdin.isRaw |
|||
|
|||
stdin.setRawMode(true) |
|||
stdin.resume() |
|||
|
|||
function restore() { |
|||
stdout.write(trailing) |
|||
stdin.setRawMode(isRaw) |
|||
stdin.pause() |
|||
stdin.removeListener('data', onData) |
|||
} |
|||
|
|||
function onData(buffer) { |
|||
const data = buffer.toString() |
|||
|
|||
if (data[0].toLowerCase() === yesChar) { |
|||
restore() |
|||
resolve(true) |
|||
} else if (data[0].toLowerCase() === noChar) { |
|||
restore() |
|||
resolve(false) |
|||
} else if (abortSequences.has(data)) { |
|||
restore() |
|||
resolve(false) |
|||
} else if (resolveChars.has(data[0])) { |
|||
restore() |
|||
resolve(defaultValue) |
|||
} else { |
|||
// ignore extraneous input
|
|||
} |
|||
} |
|||
|
|||
const defaultText = |
|||
defaultValue === null |
|||
? `[${yesChar}|${noChar}]` |
|||
: defaultValue |
|||
? `[${chalk.bold(yesChar.toUpperCase())}|${noChar}]` |
|||
: `[${yesChar}|${chalk.bold(noChar.toUpperCase())}]` |
|||
stdout.write(`${chalk.gray('>')} ${label} ${chalk.gray(defaultText)} `) |
|||
stdin.on('data', onData) |
|||
}) |
|||
} |
@ -1,39 +0,0 @@ |
|||
// Packages
|
|||
const chalk = require('chalk') |
|||
|
|||
module.exports = promptOptions |
|||
|
|||
function promptOptions(opts) { |
|||
return new Promise((resolve, reject) => { |
|||
opts.forEach(([, text], i) => { |
|||
console.log(`${chalk.gray('>')} [${chalk.bold(i + 1)}] ${text}`) |
|||
}) |
|||
|
|||
const ondata = v => { |
|||
const s = v.toString() |
|||
|
|||
const cleanup = () => { |
|||
process.stdin.setRawMode(false) |
|||
process.stdin.removeListener('data', ondata) |
|||
} |
|||
|
|||
// Ctrl + C
|
|||
if (s === '\u0003') { |
|||
cleanup() |
|||
const err = new Error('Aborted') |
|||
err.code = 'USER_ABORT' |
|||
return reject(err) |
|||
} |
|||
|
|||
const n = Number(s) |
|||
if (opts[n - 1]) { |
|||
cleanup() |
|||
resolve(opts[n - 1][0]) |
|||
} |
|||
} |
|||
|
|||
process.stdin.setRawMode(true) |
|||
process.stdin.resume() |
|||
process.stdin.on('data', ondata) |
|||
}) |
|||
} |
@ -1,3 +0,0 @@ |
|||
module.exports = { |
|||
email: /.+@.+\..+$/ |
|||
} |
@ -1,262 +0,0 @@ |
|||
// Packages
|
|||
const ansiEscapes = require('ansi-escapes') |
|||
const ansiRegex = require('ansi-regex') |
|||
const chalk = require('chalk') |
|||
const stripAnsi = require('strip-ansi') |
|||
|
|||
// Utilities
|
|||
const eraseLines = require('../output/erase-lines') |
|||
|
|||
const ESCAPES = { |
|||
LEFT: '\u001B[D', |
|||
RIGHT: '\u001B[C', |
|||
CTRL_C: '\u0003', |
|||
BACKSPACE: '\u0008', |
|||
CTRL_H: '\u007F', |
|||
CARRIAGE: '\r' |
|||
} |
|||
|
|||
const formatCC = data => { |
|||
return data.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim() |
|||
} |
|||
|
|||
module.exports = function( |
|||
{ |
|||
label = '', |
|||
initialValue = '', |
|||
// If false, the `- label` will be printed as `✖ label` in red
|
|||
// Until the first keypress
|
|||
valid = true, |
|||
// Can be:
|
|||
// - `false`, which does nothing
|
|||
// - `cc`, for credit cards
|
|||
// - `date`, for dates in the mm / yyyy format
|
|||
mask = false, |
|||
placeholder = '', |
|||
abortSequences = new Set(['\x03']), |
|||
eraseSequences = new Set([ESCAPES.BACKSPACE, ESCAPES.CTRL_H]), |
|||
resolveChars = new Set([ESCAPES.CARRIAGE]), |
|||
stdin = process.stdin, |
|||
stdout = process.stdout, |
|||
// Char to print before resolving/rejecting the promise
|
|||
// If `false`, nothing will be printed
|
|||
trailing = ansiEscapes.eraseLines(1), |
|||
// Gets called on each keypress;
|
|||
// `data` contains the current keypress;
|
|||
// `futureValue` contains the current value + the
|
|||
// Keypress in the correct place
|
|||
validateKeypress = (data, futureValue) => true, // eslint-disable-line no-unused-vars
|
|||
// Get's called before the promise is resolved
|
|||
// Returning `false` here will prevent the user from submiting the value
|
|||
validateValue = data => true, // eslint-disable-line no-unused-vars
|
|||
// Receives the value of the input and should return a string
|
|||
// Or false if no autocomplion is available
|
|||
autoComplete = value => false, // eslint-disable-line no-unused-vars
|
|||
// Tab
|
|||
// Right arrow
|
|||
autoCompleteChars = new Set(['\t', '\x1b[C']), |
|||
// If true, converts everything the user types to to lowercase
|
|||
forceLowerCase = false |
|||
} = {} |
|||
) { |
|||
return new Promise((resolve, reject) => { |
|||
const isRaw = process.stdin.isRaw |
|||
|
|||
let value |
|||
let caretOffset = 0 |
|||
let regex |
|||
let suggestion = '' |
|||
|
|||
if (valid) { |
|||
stdout.write(label) |
|||
} else { |
|||
const _label = label.replace('-', '✖') |
|||
stdout.write(chalk.red(_label)) |
|||
} |
|||
|
|||
value = initialValue |
|||
stdout.write(initialValue) |
|||
|
|||
if (mask) { |
|||
if (!value) { |
|||
value = chalk.gray(placeholder) |
|||
caretOffset = 0 - stripAnsi(value).length |
|||
stdout.write(value) |
|||
stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset))) |
|||
} |
|||
|
|||
regex = placeholder |
|||
.split('') |
|||
.reduce((prev, curr) => { |
|||
if (curr !== ' ' && !prev.includes(curr)) { |
|||
if (curr === '/') { |
|||
prev.push(' / ') |
|||
} else { |
|||
prev.push(curr) |
|||
} |
|||
} |
|||
return prev |
|||
}, []) |
|||
.join('|') |
|||
regex = new RegExp(`(${regex})`, 'g') |
|||
} |
|||
|
|||
stdin.setRawMode(true) |
|||
stdin.resume() |
|||
|
|||
function restore() { |
|||
stdin.setRawMode(isRaw) |
|||
stdin.pause() |
|||
stdin.removeListener('data', onData) |
|||
if (trailing) { |
|||
stdout.write(trailing) |
|||
} |
|||
} |
|||
|
|||
async function onData(buffer) { |
|||
let data = buffer.toString() |
|||
|
|||
value = stripAnsi(value) |
|||
|
|||
if (abortSequences.has(data)) { |
|||
restore() |
|||
return reject(new Error('USER_ABORT')) |
|||
} |
|||
|
|||
if (forceLowerCase) { |
|||
data = data.toLowerCase() |
|||
} |
|||
|
|||
if (suggestion !== '' && !caretOffset && autoCompleteChars.has(data)) { |
|||
value += stripAnsi(suggestion) |
|||
suggestion = '' |
|||
} else if (data === ESCAPES.LEFT) { |
|||
if (value.length > Math.abs(caretOffset)) { |
|||
caretOffset-- |
|||
} |
|||
} else if (data === ESCAPES.RIGHT) { |
|||
if (caretOffset < 0) { |
|||
caretOffset++ |
|||
} |
|||
} else if (eraseSequences.has(data)) { |
|||
let char |
|||
if (mask && value.length > Math.abs(caretOffset)) { |
|||
if (value[value.length + caretOffset - 1] === ' ') { |
|||
if (value[value.length + caretOffset - 2] === '/') { |
|||
caretOffset -= 1 |
|||
} |
|||
char = placeholder[value.length + caretOffset] |
|||
value = |
|||
value.substr(0, value.length + caretOffset - 2) + |
|||
char + |
|||
value.substr(value.length + caretOffset - 1) |
|||
caretOffset-- |
|||
} else { |
|||
char = placeholder[value.length + caretOffset - 1] |
|||
value = |
|||
value.substr(0, value.length + caretOffset - 1) + |
|||
char + |
|||
value.substr(value.length + caretOffset) |
|||
} |
|||
caretOffset-- |
|||
} else { |
|||
value = |
|||
value.substr(0, value.length + caretOffset - 1) + |
|||
value.substr(value.length + caretOffset) |
|||
} |
|||
suggestion = '' |
|||
} else if (resolveChars.has(data)) { |
|||
if (validateValue(value)) { |
|||
restore() |
|||
resolve(value) |
|||
} else { |
|||
if (mask === 'cc' || mask === 'ccv') { |
|||
value = formatCC(value) |
|||
value = value.replace(regex, chalk.gray('$1')) |
|||
} else if (mask === 'expDate') { |
|||
value = value.replace(regex, chalk.gray('$1')) |
|||
} |
|||
|
|||
const l = chalk.red(label.replace('-', '✖')) |
|||
eraseLines(1) |
|||
stdout.write(l + value + ansiEscapes.beep) |
|||
if (caretOffset) { |
|||
process.stdout.write( |
|||
ansiEscapes.cursorBackward(Math.abs(caretOffset)) |
|||
) |
|||
} |
|||
} |
|||
return |
|||
} else if (!ansiRegex().test(data)) { |
|||
let tmp = |
|||
value.substr(0, value.length + caretOffset) + |
|||
data + |
|||
value.substr(value.length + caretOffset) |
|||
|
|||
if (mask) { |
|||
if (/\d/.test(data) && caretOffset !== 0) { |
|||
let formattedData = data |
|||
|
|||
if (mask === 'cc' || mask === 'ccv') { |
|||
formattedData = formatCC(data) |
|||
} |
|||
|
|||
if (value[value.length + caretOffset + 1] === ' ') { |
|||
tmp = |
|||
value.substr(0, value.length + caretOffset) + |
|||
formattedData + |
|||
value.substr(value.length + caretOffset + formattedData.length) |
|||
|
|||
caretOffset += formattedData.length + 1 |
|||
|
|||
if (value[value.length + caretOffset] === '/') { |
|||
caretOffset += formattedData.length + 1 |
|||
} |
|||
} else { |
|||
tmp = |
|||
value.substr(0, value.length + caretOffset) + |
|||
formattedData + |
|||
value.substr(value.length + caretOffset + formattedData.length) |
|||
|
|||
caretOffset += formattedData.length |
|||
} |
|||
} else if (/\s/.test(data) && caretOffset < 0) { |
|||
caretOffset++ |
|||
tmp = value |
|||
} else { |
|||
return stdout.write(ansiEscapes.beep) |
|||
} |
|||
value = tmp |
|||
} else if (validateKeypress(data, value)) { |
|||
value = tmp |
|||
if (caretOffset === 0) { |
|||
const completion = await autoComplete(value) |
|||
if (completion) { |
|||
suggestion = chalk.gray(completion) |
|||
suggestion += ansiEscapes.cursorBackward(completion.length) |
|||
} else { |
|||
suggestion = '' |
|||
} |
|||
} |
|||
} else { |
|||
return stdout.write(ansiEscapes.beep) |
|||
} |
|||
} |
|||
|
|||
if (mask === 'cc' || mask === 'ccv') { |
|||
value = formatCC(value) |
|||
value = value.replace(regex, chalk.gray('$1')) |
|||
} else if (mask === 'expDate') { |
|||
value = value.replace(regex, chalk.gray('$1')) |
|||
} |
|||
|
|||
eraseLines(1) |
|||
stdout.write(label + value + suggestion) |
|||
if (caretOffset) { |
|||
process.stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset))) |
|||
} |
|||
} |
|||
|
|||
stdin.on('data', onData) |
|||
}) |
|||
} |
@ -1,3 +0,0 @@ |
|||
module.exports = { |
|||
tick: '✓' |
|||
} |
@ -1,6 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// The equivalent of <code>, for embedding a cmd
|
|||
// eg: Please run ${cmd(woot)}
|
|||
|
|||
module.exports = cmd => `${chalk.gray('`')}${chalk.cyan(cmd)}${chalk.gray('`')}` |
@ -1,6 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// The equivalent of <code>, for embedding anything
|
|||
// you may want to take a look at ./cmd.js
|
|||
|
|||
module.exports = cmd => `${chalk.gray('`')}${chalk.bold(cmd)}${chalk.gray('`')}` |
@ -1,3 +0,0 @@ |
|||
const ansiEscapes = require('ansi-escapes') |
|||
|
|||
module.exports = n => process.stdout.write(ansiEscapes.eraseLines(n)) |
@ -1,9 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// Prints an error message
|
|||
module.exports = msg => { |
|||
if (msg.message) { |
|||
msg = msg.message |
|||
} |
|||
console.error(`${chalk.red('> Error!')} ${msg}`) |
|||
} |
@ -1,6 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// Prints an informational message
|
|||
module.exports = msg => { |
|||
console.log(`${chalk.gray('>')} ${msg}`) |
|||
} |
@ -1 +0,0 @@ |
|||
module.exports = process.platform === 'win32' ? 'Δ' : '𝚫' |
@ -1,6 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// Prints a note
|
|||
module.exports = msg => { |
|||
console.log(`${chalk.yellow('> NOTE:')} ${msg}`) |
|||
} |
@ -1,7 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// Returns a user param in a nice formatting
|
|||
// e.g.: google.com -> "google.com" (in bold)
|
|||
|
|||
module.exports = param => |
|||
chalk.bold(`${chalk.gray('"')}${chalk.bold(param)}${chalk.gray('"')}`) |
@ -1,4 +0,0 @@ |
|||
module.exports = (string, n = 0) => { |
|||
n -= string.length |
|||
return string + ' '.repeat(n > -1 ? n : 0) |
|||
} |
@ -1,10 +0,0 @@ |
|||
const ms = require('ms') |
|||
const chalk = require('chalk') |
|||
|
|||
// Returns a time delta with the right color
|
|||
// example: `[103ms]`
|
|||
|
|||
module.exports = () => { |
|||
const start = new Date() |
|||
return () => chalk.gray(`[${ms(new Date() - start)}]`) |
|||
} |
@ -1,6 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// Prints a success message
|
|||
module.exports = msg => { |
|||
console.log(`${chalk.cyan('> Success!')} ${msg}`) |
|||
} |
@ -1,27 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
const printf = require('printf') |
|||
|
|||
const printLine = (data, sizes) => |
|||
data.reduce((line, col, i) => { |
|||
return line + printf(`%-${sizes[i]}s`, col) |
|||
}, '') |
|||
|
|||
// Print a table
|
|||
module.exports = (fieldNames = [], data = [], margins = []) => { |
|||
// Compute size of each column
|
|||
const sizes = data |
|||
.reduce((acc, row) => { |
|||
return row.map((col, i) => { |
|||
const currentMaxColSize = acc[i] || 0 |
|||
const colSize = (col && col.length) || 0 |
|||
return Math.max(currentMaxColSize, colSize) |
|||
}) |
|||
}, fieldNames.map(col => col.length)) |
|||
// Add margin to all columns except the last
|
|||
.map((size, i) => (i < margins.length && size + margins[i]) || size) |
|||
|
|||
// Print header
|
|||
console.log(chalk.grey(printLine(fieldNames, sizes))) |
|||
// Print content
|
|||
data.forEach(row => console.log(printLine(row, sizes))) |
|||
} |
@ -1,5 +0,0 @@ |
|||
const chalk = require('chalk') |
|||
|
|||
// Used for including uids in the output
|
|||
// example: `(dom_ji13dj2fih4fi2hf)`
|
|||
module.exports = id => chalk.gray(`(${id})`) |
@ -1,15 +0,0 @@ |
|||
const ora = require('ora') |
|||
const chalk = require('chalk') |
|||
const { eraseLine } = require('ansi-escapes') |
|||
|
|||
// Prints a spinner followed by the given text
|
|||
module.exports = msg => { |
|||
const spinner = ora(chalk.gray(msg)) |
|||
spinner.color = 'gray' |
|||
spinner.start() |
|||
|
|||
return () => { |
|||
spinner.stop() |
|||
process.stdout.write(eraseLine) |
|||
} |
|||
} |
@ -1,18 +0,0 @@ |
|||
// Native
|
|||
const { resolve } = require('path') |
|||
const { homedir } = require('os') |
|||
|
|||
// Cleaned-up `$HOME` (i.e.: no trailing slash)
|
|||
const HOME = resolve(homedir()) |
|||
|
|||
/** |
|||
* Attempts to show the given path in |
|||
* a human-friendly form. For example, |
|||
* `/Users/rauchg/test.js` becomes `~/test.js` |
|||
*/ |
|||
|
|||
function toHumanPath(path) { |
|||
return path.replace(HOME, '~') |
|||
} |
|||
|
|||
module.exports = toHumanPath |
@ -1,28 +0,0 @@ |
|||
exports.maybeURL = id => { |
|||
// E.g, "appname-asdf"
|
|||
return id.includes('-') |
|||
} |
|||
|
|||
exports.normalizeURL = u => { |
|||
// Normalize URL by removing slash from the end
|
|||
if (u.slice(-1) === '/') { |
|||
u = u.slice(0, -1) |
|||
} |
|||
|
|||
// `url` should match the hostname of the deployment
|
|||
u = u.replace(/^https:\/\//i, '') |
|||
|
|||
if (!u.includes('.')) { |
|||
// `.now.sh` domain is implied if just the subdomain is given
|
|||
u += '.now.sh' |
|||
} |
|||
|
|||
return u |
|||
} |
|||
|
|||
exports.parseInstanceURL = u => { |
|||
const m = /^(.+)-([a-z0-9]{24})(\.now\.sh)$/.exec(u) |
|||
const url = m ? m[1] + m[3] : u |
|||
const instanceId = m ? m[2] : null |
|||
return [url, instanceId] |
|||
} |
@ -1,21 +0,0 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2016 Zeit, Inc. |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -1,11 +0,0 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
try { |
|||
// eslint-disable-next-line import/no-unassigned-import
|
|||
require('../bin/now.js') |
|||
} catch (err) { |
|||
if (err.code === 'ENOENT' && err.syscall === 'uv_cwd') { |
|||
console.error(`Current path doesn't exist!`) |
|||
process.exit(1) |
|||
} |
|||
} |
@ -1,4 +0,0 @@ |
|||
{ |
|||
"name": "now", |
|||
"lockfileVersion": 1 |
|||
} |
@ -1,9 +0,0 @@ |
|||
{ |
|||
"name": "now", |
|||
"license": "MIT", |
|||
"description": "The command line interface for Now", |
|||
"repository": "zeit/now-cli", |
|||
"bin": { |
|||
"now": "link.js" |
|||
} |
|||
} |
File diff suppressed because it is too large
@ -1,124 +0,0 @@ |
|||
{ |
|||
"name": "now", |
|||
"version": "7.4.0", |
|||
"description": "The command line interface for Now", |
|||
"repository": "zeit/now-cli", |
|||
"license": "MIT", |
|||
"scripts": { |
|||
"postinstall": "node download/install.js", |
|||
"precommit": "xo --quiet && lint-staged", |
|||
"prepublish": "in-install || (npm run webpack && cp /dev/null download/dist/now)", |
|||
"link": "cd link && npm link", |
|||
"lint": "xo --quiet", |
|||
"test": "npm run lint && ava", |
|||
"pack": "pkg bin/now.js -c package.json -o packed/now", |
|||
"webpack": "webpack --context download --config download/webpack.js", |
|||
"clean": "rm -rf node_modules link/node_modules download/dist", |
|||
"prettify": "prettier --single-quote --no-semi --write `find ./lib -name '*.js'` `find ./bin -name '*.js'`" |
|||
}, |
|||
"pkg": { |
|||
"scripts": [ |
|||
"bin/*", |
|||
"lib/**/*" |
|||
], |
|||
"targets": [ |
|||
"node7-alpine-x64", |
|||
"node7-linux-x64", |
|||
"node7-macos-x64", |
|||
"node7-win-x64" |
|||
] |
|||
}, |
|||
"bin": { |
|||
"now": "download/dist/now" |
|||
}, |
|||
"files": [ |
|||
"download/dist", |
|||
"download/install.js" |
|||
], |
|||
"ava": { |
|||
"failFast": true, |
|||
"files": [ |
|||
"test/*.js" |
|||
] |
|||
}, |
|||
"xo": { |
|||
"ignores": [ |
|||
"test/_fixtures/**" |
|||
], |
|||
"extends": "prettier" |
|||
}, |
|||
"lint-staged": { |
|||
"*.js": [ |
|||
"npm run lint", |
|||
"prettier --single-quote --no-semi --write", |
|||
"git add" |
|||
] |
|||
}, |
|||
"greenkeeper": { |
|||
"ignore": [ |
|||
"socket.io-client" |
|||
] |
|||
}, |
|||
"devDependencies": { |
|||
"@google/maps": "0.4.3", |
|||
"alpha-sort": "2.0.1", |
|||
"ansi-escapes": "2.0.0", |
|||
"ansi-regex": "3.0.0", |
|||
"arr-flatten": "1.1.0", |
|||
"array-unique": "0.3.2", |
|||
"async-retry": "1.1.3", |
|||
"ava": "0.21.0", |
|||
"babel-core": "6.25.0", |
|||
"babel-loader": "7.1.1", |
|||
"babel-plugin-transform-async-to-generator": "6.24.1", |
|||
"babel-plugin-transform-runtime": "6.23.0", |
|||
"babel-preset-es2015": "6.24.1", |
|||
"bytes": "2.5.0", |
|||
"chalk": "2.0.1", |
|||
"clipboardy": "1.1.4", |
|||
"credit-card": "3.0.1", |
|||
"cross-spawn": "5.1.0", |
|||
"dateformat": "2.0.0", |
|||
"death": "1.1.0", |
|||
"deployment-type": "1.0.1", |
|||
"docker-file-parser": "1.0.2", |
|||
"dotenv": "4.0.0", |
|||
"download": "6.2.5", |
|||
"email-prompt": "0.3.1", |
|||
"email-validator": "1.0.7", |
|||
"epipebomb": "1.0.0", |
|||
"eslint-config-prettier": "2.3.0", |
|||
"fs-extra": "4.0.0", |
|||
"glob": "7.1.2", |
|||
"ignore": "3.3.3", |
|||
"in-publish": "2.0.0", |
|||
"ini": "1.3.4", |
|||
"inquirer": "3.2.1", |
|||
"is-url": "1.2.2", |
|||
"lint-staged": "4.0.2", |
|||
"lodash.range": "3.2.0", |
|||
"minimist": "1.2.0", |
|||
"ms": "2.0.0", |
|||
"node-fetch": "1.7.1", |
|||
"ora": "1.3.0", |
|||
"pkg": "4.1.4", |
|||
"prettier": "1.5.3", |
|||
"printf": "0.2.5", |
|||
"progress": "2.0.0", |
|||
"psl": "1.1.19", |
|||
"resumer": "0.0.0", |
|||
"single-line-log": "1.1.2", |
|||
"slackup": "2.0.1", |
|||
"socket.io-client": "1.7.4", |
|||
"split-array": "1.0.1", |
|||
"strip-ansi": "4.0.0", |
|||
"stripe": "4.23.1", |
|||
"supports-color": "4.2.1", |
|||
"text-table": "0.2.0", |
|||
"tmp-promise": "1.0.3", |
|||
"update-notifier": "2.2.0", |
|||
"webpack": "3.4.1", |
|||
"which": "1.3.0", |
|||
"xo": "0.18.2" |
|||
} |
|||
} |
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue