From 3979d8472a1088fb7285504a4d26fc15e8c19ad8 Mon Sep 17 00:00:00 2001 From: Leo Lamprecht Date: Sat, 22 Oct 2016 14:16:22 +0200 Subject: [PATCH] Fixed even more XO issues --- bin/now | 68 ++-- gulpfile.babel.js | 27 +- lib/agent.js | 76 ++-- lib/alias.js | 355 ++++++++++-------- lib/build-logger.js | 157 ++++---- lib/certs.js | 108 +++--- lib/cfg.js | 31 +- lib/check-update.js | 98 ++--- lib/copy.js | 18 +- lib/dns.js | 16 +- lib/domains.js | 97 +++-- lib/error.js | 35 +- lib/errors.js | 7 +- lib/get-files.js | 203 +++++----- lib/hash.js | 31 +- lib/ignored.js | 2 +- lib/index.js | 726 ++++++++++++++++++++---------------- lib/is-zeit-world.js | 10 +- lib/login.js | 102 ++--- lib/secrets.js | 117 +++--- lib/strlen.js | 4 +- lib/test.js | 22 +- lib/to-host.js | 15 +- lib/ua.js | 9 +- lib/utils/prompt-options.js | 46 +-- lib/utils/to-human-path.js | 10 +- package.json | 10 +- test/index.js | 210 +++++------ test/to-host.js | 28 +- 29 files changed, 1471 insertions(+), 1167 deletions(-) diff --git a/bin/now b/bin/now index 75ef567..7bb019a 100755 --- a/bin/now +++ b/bin/now @@ -1,31 +1,31 @@ #!/usr/bin/env node // Native -import {resolve} from 'path'; +import {resolve} from 'path' // Packages -import minimist from 'minimist'; -import {spawn} from 'cross-spawn'; +import minimist from 'minimist' +import {spawn} from 'cross-spawn' // Ours -import checkUpdate from '../lib/check-update'; +import checkUpdate from '../lib/check-update' -const argv = minimist(process.argv.slice(2)); +const argv = minimist(process.argv.slice(2)) // options -const debug = argv.debug || argv.d; +const debug = argv.debug || argv.d // auto-update checking -const update = checkUpdate({ debug }); +const update = checkUpdate({debug}) -const exit = (code) => { - update.then(() => process.exit(code)); +const exit = code => { + update.then(() => process.exit(code)) // don't wait for updates more than a second // when the process really wants to exit - setTimeout(() => process.exit(code), 1000); -}; + setTimeout(() => process.exit(code), 1000) +} -const defaultCommand = 'deploy'; +const defaultCommand = 'deploy' const commands = new Set([ defaultCommand, @@ -43,7 +43,7 @@ const commands = new Set([ 'secret', 'secrets', 'static' -]); +]) const aliases = new Map([ ['ls', 'list'], @@ -53,31 +53,39 @@ const aliases = new Map([ ['domain', 'domains'], ['cert', 'certs'], ['secret', 'secrets'] -]); +]) + +let cmd = argv._[0] +let args = [] -let cmd = argv._[0]; -let args = []; +if (cmd === 'help') { + cmd = argv._[1] -if ('help' === cmd) { - cmd = argv._[1]; - if (!commands.has(cmd)) cmd = defaultCommand; - args.push('--help'); + if (!commands.has(cmd)) { + cmd = defaultCommand + } + + args.push('--help') } if (commands.has(cmd)) { - cmd = aliases.get(cmd) || cmd; - args = args.concat(process.argv.slice(3)); + cmd = aliases.get(cmd) || cmd + args = args.concat(process.argv.slice(3)) } else { - cmd = defaultCommand; - args = args.concat(process.argv.slice(2)); + cmd = defaultCommand + args = args.concat(process.argv.slice(2)) } -let bin = resolve(__dirname, 'now-' + cmd); +let bin = resolve(__dirname, 'now-' + cmd) if (process.pkg) { - args.unshift('--entrypoint', bin); - bin = process.execPath; + args.unshift('--entrypoint', bin) + bin = process.execPath } -const proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] }); -proc.on('close', (code) => exit(code)); -proc.on('error', () => exit(1)); +const proc = spawn(bin, args, { + stdio: 'inherit', + customFds: [0, 1, 2] +}) + +proc.on('close', code => exit(code)) +proc.on('error', () => exit(1)) diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 014be6c..d82e016 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -1,28 +1,29 @@ -import gulp from 'gulp'; -import del from 'del'; -import babel from 'gulp-babel'; -import help from 'gulp-task-listing'; +// Packages +import gulp from 'gulp' +import del from 'del' +import babel from 'gulp-babel' +import help from 'gulp-task-listing' -gulp.task('help', help); +gulp.task('help', help) gulp.task('compile', [ 'compile-lib', 'compile-bin' -]); +]) gulp.task('compile-lib', () => gulp.src('lib/**/*.js') .pipe(babel()) - .pipe(gulp.dest('build/lib'))); + .pipe(gulp.dest('build/lib'))) gulp.task('compile-bin', () => gulp.src('bin/*') .pipe(babel()) - .pipe(gulp.dest('build/bin'))); + .pipe(gulp.dest('build/bin'))) -gulp.task('watch-lib', () => gulp.watch('lib/**/*.js', ['compile-lib'])); -gulp.task('watch-bin', () => gulp.watch('bin/*', ['compile-bin'])); -gulp.task('clean', () => del(['build'])); +gulp.task('watch-lib', () => gulp.watch('lib/**/*.js', ['compile-lib'])) +gulp.task('watch-bin', () => gulp.watch('bin/*', ['compile-bin'])) +gulp.task('clean', () => del(['build'])) -gulp.task('watch', ['watch-lib', 'watch-bin']); -gulp.task('default', ['compile', 'watch']); +gulp.task('watch', ['watch-lib', 'watch-bin']) +gulp.task('default', ['compile', 'watch']) diff --git a/lib/agent.js b/lib/agent.js index 8eb1dbd..477ff70 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -1,6 +1,7 @@ -import http2 from 'spdy'; -import fetch from 'node-fetch'; -import { parse } from 'url'; +// Packages +import {parse} from 'url' +import http2 from 'spdy' +import fetch from 'node-fetch' /** * Returns a `fetch` version with a similar @@ -14,61 +15,70 @@ import { parse } from 'url'; */ export default class Agent { - constructor (url, { tls = true, debug } = {}) { - this._url = url; - const parsed = parse(url); - this._host = parsed.hostname; - this._port = parsed.port; - this._protocol = parsed.protocol; - this._debug = debug; - if (tls) this._initAgent(); + constructor(url, {tls = true, debug} = {}) { + this._url = url + const parsed = parse(url) + this._host = parsed.hostname + this._port = parsed.port + this._protocol = parsed.protocol + this._debug = debug + if (tls) { + this._initAgent() + } } - _initAgent () { - if ('https:' !== this._protocol) return; + _initAgent() { + if (this._protocol !== 'https:') { + return + } this._agent = http2.createAgent({ host: this._host, port: this._port || 443 - }).once('error', (err) => this._onError(err)); + }).once('error', err => this._onError(err)) } - _onError (err) { - // XXX: should we `this.emit()`? + _onError(err) { if (this._debug) { - console.log('> [debug] agent connection error', err.stack); + console.log('> [debug] agent connection error', err.stack) } - this._error = err; + this._error = err } - fetch (path, opts = {}) { + fetch(path, opts = {}) { if (this._error) { - if (this._debug) console.log('> [debug] re-initializing agent after error'); - this._error = null; - this._initAgent(); + if (this._debug) { + console.log('> [debug] re-initializing agent after error') + } + + this._error = null + this._initAgent() } - const { body } = opts; + const {body} = opts if (this._agent) { - opts.agent = this._agent; + opts.agent = this._agent } - if (body && 'object' === typeof body && 'function' !== typeof body.pipe) { - opts.headers['Content-Type'] = 'application/json'; - opts.body = JSON.stringify(body); + if (body && typeof body === 'object' && typeof body.pipe !== 'function') { + opts.headers['Content-Type'] = 'application/json' + opts.body = JSON.stringify(body) } - if (null != opts.body && 'function' !== typeof body.pipe) { - opts.headers['Content-Length'] = Buffer.byteLength(opts.body); + if (opts.body !== null && typeof body.pipe !== 'function') { + opts.headers['Content-Length'] = Buffer.byteLength(opts.body) } - return fetch(this._url + path, opts); + return fetch(this._url + path, opts) } - close () { - if (this._debug) console.log('> [debug] closing agent'); + close() { + if (this._debug) { + console.log('> [debug] closing agent') + } + if (this._agent) { - this._agent.close(); + this._agent.close() } } } diff --git a/lib/alias.js b/lib/alias.js index ad67ca2..104de7f 100644 --- a/lib/alias.js +++ b/lib/alias.js @@ -1,115 +1,131 @@ -import Now from './'; -import toHost from './to-host'; -import chalk from 'chalk'; -import isZeitWorld from './is-zeit-world'; -import { DOMAIN_VERIFICATION_ERROR } from './errors'; -import { resolve4 } from './dns'; +// Packages +import chalk from 'chalk' -const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/; +// Ours +import toHost from './to-host' +import resolve4 from './dns' +import isZeitWorld from './is-zeit-world' +import {DOMAIN_VERIFICATION_ERROR} from './errors' +import Now from './' + +const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/ export default class Alias extends Now { - async ls (deployment) { + async ls(deployment) { if (deployment) { - const target = await this.findDeployment(deployment); + const target = await this.findDeployment(deployment) if (!target) { - const err = new Error(`Aliases not found by "${deployment}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`); - err.userError = true; - throw err; + const err = new Error(`Aliases not found by "${deployment}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`) + err.userError = true + throw err } - return this.listAliases(target.uid); - } else { - return this.listAliases(); + return this.listAliases(target.uid) } + + return this.listAliases() } - async rm (_alias) { - return this.retry(async (bail, attempt) => { + async rm(_alias) { + return this.retry(async bail => { const res = await this._fetch(`/now/aliases/${_alias.uid}`, { method: 'DELETE' - }); + }) - if (403 === res.status) { - return bail(new Error('Unauthorized')); + if (res.status === 403) { + return bail(new Error('Unauthorized')) } if (res.status !== 200) { - const err = new Error('Deletion failed. Try again later.'); - throw err; + const err = new Error('Deletion failed. Try again later.') + throw err } - }); + }) } - async findDeployment (deployment) { - const list = await this.list(); - let key, val; + async findDeployment(deployment) { + const list = await this.list() + + let key + let val + if (/\./.test(deployment)) { - val = toHost(deployment); - key = 'url'; + val = toHost(deployment) + key = 'url' } else { - val = deployment; - key = 'uid'; + val = deployment + key = 'uid' } - const depl = list.find((d) => { + const depl = list.find(d => { if (d[key] === val) { - if (this._debug) console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`); - return true; + 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; + if (this._debug) { + console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`) + } + return true } - return false; - }); + return false + }) - return depl; + return depl } - async set (deployment, alias) { + async set(deployment, alias) { // make alias lowercase - alias = alias.toLowerCase(); + alias = alias.toLowerCase() // trim leading and trailing dots // for example: `google.com.` => `google.com` alias = alias .replace(/^\.+/, '') - .replace(/\.+$/, ''); + .replace(/\.+$/, '') - const depl = await this.findDeployment(deployment); + const depl = await this.findDeployment(deployment) if (!depl) { - const err = new Error(`Deployment not found by "${deployment}". Run ${chalk.dim('`now ls`')} to see your deployments.`); - err.userError = true; - throw err; + const err = new Error(`Deployment not found by "${deployment}". Run ${chalk.dim('`now ls`')} to see your deployments.`) + err.userError = true + throw err } // evaluate the alias - if (!/\./.test(alias)) { - if (this._debug) console.log(`> [debug] suffixing \`.now.sh\` to alias ${alias}`); - alias = `${alias}.now.sh`; + if (/\./.test(alias)) { + alias = toHost(alias) } else { - alias = toHost(alias); + 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; + const err = new Error(`Invalid alias "${alias}"`) + err.userError = true + throw err } if (!/\.now\.sh$/.test(alias)) { - console.log(`> ${chalk.bold(chalk.underline(alias))} is a custom domain.`); - console.log(`> Verifying the DNS settings for ${chalk.bold(chalk.underline(alias))} (see ${chalk.underline('https://zeit.world')} for help)`); + console.log(`> ${chalk.bold(chalk.underline(alias))} is a custom domain.`) + console.log(`> Verifying the DNS settings for ${chalk.bold(chalk.underline(alias))} (see ${chalk.underline('https://zeit.world')} for help)`) - const { domain, nameservers } = await this.getNameservers(alias); - if (this._debug) console.log(`> [debug] Found domain ${domain} and nameservers ${nameservers}`); + const {domain, nameservers} = await this.getNameservers(alias) + + if (this._debug) { + console.log(`> [debug] Found domain ${domain} and nameservers ${nameservers}`) + } try { - await this.verifyOwnership(alias); + await this.verifyOwnership(alias) } catch (err) { if (err.userError) { // a user error would imply that verification failed @@ -117,221 +133,246 @@ export default class Alias extends Now { // configuration (if we can!) try { if (isZeitWorld(nameservers)) { - console.log(`> Detected ${chalk.bold(chalk.underline('zeit.world'))} nameservers! Configuring records.`); - const record = alias.substr(0, alias.length - domain.length); + console.log(`> Detected ${chalk.bold(chalk.underline('zeit.world'))} nameservers! Configuring records.`) + const record = alias.substr(0, alias.length - domain.length) // lean up trailing and leading dots const _record = record .replace(/^\./, '') - .replace(/\.$/, ''); + .replace(/\.$/, '') const _domain = domain .replace(/^\./, '') - .replace(/\.$/, ''); + .replace(/\.$/, '') if (_record === '') { - await this.setupRecord(_domain, '*'); + await this.setupRecord(_domain, '*') } - await this.setupRecord(_domain, _record); + await this.setupRecord(_domain, _record) - this.recordSetup = true; - console.log('> DNS Configured! Verifying propagation…'); + this.recordSetup = true + console.log('> DNS Configured! Verifying propagation…') try { - await this.retry(() => this.verifyOwnership(alias), { retries: 10, maxTimeout: 8000 }); + await this.retry(() => this.verifyOwnership(alias), {retries: 10, maxTimeout: 8000}) } catch (err2) { const e = new Error('> We configured the DNS settings for your alias, but we were unable to ' + - 'verify that they\'ve propagated. Please try the alias again later.'); - e.userError = true; - throw e; + 'verify that they\'ve propagated. Please try the alias again later.') + e.userError = true + throw e } } else { - console.log(`> Resolved IP: ${err.ip ? `${chalk.underline(err.ip)} (unknown)` : chalk.dim('none')}`); - console.log(`> Nameservers: ${nameservers && nameservers.length ? nameservers.map(ns => chalk.underline(ns)).join(', ') : chalk.dim('none')}`); - throw err; + console.log(`> Resolved IP: ${err.ip ? `${chalk.underline(err.ip)} (unknown)` : chalk.dim('none')}`) + console.log(`> Nameservers: ${nameservers && nameservers.length ? nameservers.map(ns => chalk.underline(ns)).join(', ') : chalk.dim('none')}`) + throw err } } catch (e) { - if (e.userError) throw e; - throw err; + if (e.userError) { + throw e + } + + throw err } } else { - throw err; + throw err } } if (!isZeitWorld(nameservers)) { - if (this._debug) console.log(`> [debug] Trying to register a non-ZeitWorld domain ${domain} for the current user`); - await this.setupDomain(domain, { isExternal: true }); + if (this._debug) { + console.log(`> [debug] Trying to register a non-ZeitWorld domain ${domain} for the current user`) + } + + await this.setupDomain(domain, {isExternal: true}) } - console.log(`> Verification ${chalk.bold('OK')}!`); + console.log(`> Verification ${chalk.bold('OK')}!`) } // unfortunately there's a situation where the verification // ownership code path in the `catch` above makes the // agent unexpectedly close. this is a workaround until // we figure out what's going on with `node-spdy` - this._agent.close(); - this._agent._initAgent(); + this._agent.close() + this._agent._initAgent() - const { created, uid } = await this.createAlias(depl, alias); + const {created, uid} = await this.createAlias(depl, alias) if (created) { - console.log(`${chalk.cyan('> Success!')} Alias created ${chalk.dim(`(${uid})`)}: ${chalk.bold(chalk.underline(`https://${alias}`))} now points to ${chalk.bold(`https://${depl.url}`)} ${chalk.dim(`(${depl.uid})`)}`); + console.log(`${chalk.cyan('> Success!')} Alias created ${chalk.dim(`(${uid})`)}: ${chalk.bold(chalk.underline(`https://${alias}`))} now points to ${chalk.bold(`https://${depl.url}`)} ${chalk.dim(`(${depl.uid})`)}`) } else { - console.log(`${chalk.cyan('> Success!')} Alias already exists ${chalk.dim(`(${uid})`)}.`); + console.log(`${chalk.cyan('> Success!')} Alias already exists ${chalk.dim(`(${uid})`)}.`) } } - createAlias (depl, alias) { + createAlias(depl, alias) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] /now/deployments/${depl.uid}/aliases #${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 } - }); + body: {alias} + }) - const body = await res.json(); - if (this._debug) console.timeEnd(`> [debug] /now/deployments/${depl.uid}/aliases #${attempt}`); + 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 (409 === res.status) return { uid: body.error.uid }; + if (res.status === 409) { + return {uid: body.error.uid} + } // no retry on authorization problems - if (403 === res.status) { - const code = body.error.code; + if (res.status === 403) { + const code = body.error.code - if ('custom_domain_needs_upgrade' === code) { - const err = new Error(`Custom domains are only enabled for premium accounts. Please upgrade at ${chalk.underline('https://zeit.co/account')}.`); - err.userError = true; - return bail(err); + if (code === 'custom_domain_needs_upgrade') { + const err = new Error(`Custom domains are only enabled for premium accounts. Please upgrade at ${chalk.underline('https://zeit.co/account')}.`) + err.userError = true + return bail(err) } - if ('alias_in_use' === code) { - const err = new Error(`The alias you are trying to configure (${chalk.underline(chalk.bold(alias))}) is already in use by a different account.`); - err.userError = true; - return bail(err); + if (code === 'alias_in_use') { + const err = new Error(`The alias you are trying to configure (${chalk.underline(chalk.bold(alias))}) is already in use by a different account.`) + err.userError = true + return bail(err) } - if ('forbidden' === code) { - const err = new Error('The domain you are trying to use as an alias is already in use by a different account.'); - err.userError = true; - return bail(err); + if (code === 'forbidden') { + const err = new Error('The domain you are trying to use as an alias is already in use by a different account.') + err.userError = true + return bail(err) } - return bail(new Error('Authorization error')); + return bail(new Error('Authorization error')) } // all other errors if (body.error) { - const code = body.error.code; + const code = body.error.code - if ('deployment_not_found' === code) { - return bail(new Error('Deployment not found')); + if (code === 'deployment_not_found') { + return bail(new Error('Deployment not found')) } - if ('cert_missing' === code) { - console.log(`> Provisioning certificate for ${chalk.underline(chalk.bold(alias))}`); + if (code === 'cert_missing') { + console.log(`> Provisioning certificate for ${chalk.underline(chalk.bold(alias))}`) try { - await this.createCert(alias); + await this.createCert(alias) } catch (err) { // we bail to avoid retrying the whole process // of aliasing which would involve too many // retries on certificate provisioning - return bail(err); + return bail(err) } // try again, but now having provisioned the certificate - return this.createAlias(depl, alias); + return this.createAlias(depl, alias) } - if ('cert_expired' === code) { - console.log(`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`); + if (code === 'cert_expired') { + console.log(`> Renewing certificate for ${chalk.underline(chalk.bold(alias))}`) try { - await this.createCert(alias, { renew: true }); + await this.createCert(alias, {renew: true}) } catch (err) { - return bail(err); + return bail(err) } } - return bail(new Error(body.error.message)); + return bail(new Error(body.error.message)) } // the two expected succesful cods are 200 and 304 - if (200 !== res.status && 304 !== res.status) { - throw new Error('Unhandled error'); + if (res.status !== 200 && res.status !== 304) { + throw new Error('Unhandled error') } - return body; - }); + return body + }) } - async setupRecord (domain, name) { - await this.setupDomain(domain); + async setupRecord(domain, name) { + await this.setupDomain(domain) + + if (this._debug) { + console.log(`> [debug] Setting up record "${name}" for "${domain}"`) + } - if (this._debug) console.log(`> [debug] Setting up record "${name}" for "${domain}"`); - const type = '' === name ? 'ALIAS' : 'CNAME'; + const type = name === '' ? 'ALIAS' : 'CNAME' return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] /domains/${domain}/records #${attempt}`); + 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 : '*', + name: name === '' ? name : '*', value: 'alias.zeit.co' } - }); - if (this._debug) console.timeEnd(`> [debug] /domains/${domain}/records #${attempt}`); + }) + + if (this._debug) { + console.timeEnd(`> [debug] /domains/${domain}/records #${attempt}`) + } - if (403 === res.status) { - return bail(new Error('Unauthorized')); + if (res.status === 403) { + return bail(new Error('Unauthorized')) } - const body = await res.json(); + const body = await res.json() - if (200 !== res.status) { - throw new Error(body.error.message); + if (res.status !== 200) { + throw new Error(body.error.message) } - return body; - }); + return body + }) } - verifyOwnership (domain) { - return this.retry(async (bail, attempt) => { - const targets = await resolve4('alias.zeit.co'); + verifyOwnership(domain) { + return this.retry(async bail => { + const targets = await resolve4('alias.zeit.co') - if (!targets.length) { - return bail(new Error('Unable to resolve alias.zeit.co')); + if (targets.length <= 0) { + return bail(new Error('Unable to resolve alias.zeit.co')) } - let ips = []; + let ips = [] + try { - ips = await resolve4(domain); + ips = await resolve4(domain) } catch (err) { - if ('ENODATA' === err.code || 'ESERVFAIL' === err.code || 'ENOTFOUND' === err.code) { + 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}"`); + if (this._debug) { + console.log(`> [debug] No records found for "${domain}"`) + } } else { - throw err; + throw err } } - if (!ips.length) { - const err = new Error(DOMAIN_VERIFICATION_ERROR); - err.userError = true; - return bail(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)) { - 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); + 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) } } - }); + }) } } diff --git a/lib/build-logger.js b/lib/build-logger.js index f0d6700..1283519 100644 --- a/lib/build-logger.js +++ b/lib/build-logger.js @@ -1,120 +1,121 @@ -import ansi from 'ansi-escapes'; -import io from 'socket.io-client'; -import chalk from 'chalk'; -import EventEmitter from 'events'; +// Native +import EventEmitter from 'events' -export default class Logger extends EventEmitter { +// Packages +import ansi from 'ansi-escapes' +import io from 'socket.io-client' +import chalk from 'chalk' + +class Lines { + constructor(maxLines = 100) { + this.max = maxLines + this.buf = [] + } - constructor (host, { debug = false, quiet = false } = {}) { - super(); - this.host = host; - this.debug = debug; - this.quiet = quiet; + write(str) { + const {max, buf} = this + + if (buf.length === max) { + process.stdout.write(ansi.eraseLines(max + 1)) + buf.shift() + buf.forEach(line => console.log(line)) + } + + buf.push(str) + console.log(str) + } + + reset() { + this.buf = [] + } +} + +export default class Logger extends EventEmitter { + constructor(host, {debug = false, quiet = false} = {}) { + super() + this.host = host + this.debug = debug + this.quiet = quiet // readyState - this.building = false; + this.building = false - this.socket = io(`https://io.now.sh?host=${host}`); - this.socket.once('error', this.onSocketError.bind(this)); - this.socket.on('state', this.onState.bind(this)); - this.socket.on('logs', this.onLog.bind(this)); - this.socket.on('backend', this.onComplete.bind(this)); + this.socket = io(`https://io.now.sh?host=${host}`) + this.socket.once('error', this.onSocketError.bind(this)) + this.socket.on('state', this.onState.bind(this)) + this.socket.on('logs', this.onLog.bind(this)) + this.socket.on('backend', this.onComplete.bind(this)) - this.lines = new Lines(10); + this.lines = new Lines(10) } - onState (state) { + onState(state) { if (!state.id) { - console.error('> Deployment not found'); - this.emit('error'); - return; + console.error('> Deployment not found') + this.emit('error') + return } if (state.error) { - console.error('> Deployment error'); - this.emit('error'); - return; + console.error('> Deployment error') + this.emit('error') + return } if (state.backend) { - this.onComplete(); - return; + this.onComplete() + return } if (state.logs) { - state.logs.forEach(this.onLog, this); + state.logs.forEach(this.onLog, this) } } - onLog (log) { + onLog(log) { if (!this.building) { if (!this.quiet) { - console.log('> Building'); + console.log('> Building') } - this.building = true; + this.building = true } - if (this.quiet) return; + if (this.quiet) { + return + } - if ('command' === log.type) { - console.log(`${chalk.gray('>')} ▲ ${log.data}`); - this.lines.reset(); - } else if ('stderr' === log.type) { - log.data.split('\n').forEach((v) => { - if (v.length) { - console.error(chalk.red(`> ${v}`)); + if (log.type === 'command') { + console.log(`${chalk.gray('>')} ▲ ${log.data}`) + this.lines.reset() + } else if (log.type === 'stderr') { + log.data.split('\n').forEach(v => { + if (v.length > 0) { + console.error(chalk.red(`> ${v}`)) } - }); - this.lines.reset(); - } else if ('stdout' === log.type) { - log.data.split('\n').forEach((v) => { - if (v.length) { - this.lines.write(`${chalk.gray('>')} ${v}`); + }) + this.lines.reset() + } else if (log.type === 'stdout') { + log.data.split('\n').forEach(v => { + if (v.length > 0) { + this.lines.write(`${chalk.gray('>')} ${v}`) } - }); + }) } } - onComplete () { - this.socket.disconnect(); + onComplete() { + this.socket.disconnect() if (this.building) { - this.building = false; + this.building = false } - this.emit('close'); + this.emit('close') } - onSocketError (err) { + onSocketError(err) { if (this.debug) { - console.log('> [debug] Socket error', err.stack); + console.log('> [debug] Socket error', err.stack) } } - -} - -class Lines { - - constructor (maxLines = 100) { - this.max = maxLines; - this.buf = []; - } - - write (str) { - const { max, buf } = this; - - if (buf.length === max) { - process.stdout.write(ansi.eraseLines(max + 1)); - buf.shift(); - buf.forEach((line) => console.log(line)); - } - - buf.push(str); - console.log(str); - } - - reset () { - this.buf = []; - } - } diff --git a/lib/certs.js b/lib/certs.js index 7d25bc8..c479680 100644 --- a/lib/certs.js +++ b/lib/certs.js @@ -1,82 +1,102 @@ -import Now from '../lib'; +// Ours +import Now from '../lib' export default class Certs extends Now { - ls () { + 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; - }); + 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); + create(cn) { + return this.createCert(cn) } - renew (cn) { - return this.createCert(cn, { renew: true }); + renew(cn) { + return this.createCert(cn, {renew: true}) } - replace (cn, crt, key, ca) { + replace(cn, crt, key, ca) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} PUT now/certs`); + if (this._debug) { + console.time(`> [debug] #${attempt} PUT now/certs`) + } + const res = await this._fetch('/now/certs', { method: 'PUT', body: { domains: [cn], - ca: ca, + ca, cert: crt, - key: key + key } - }); - if (this._debug) console.timeEnd(`> [debug] #${attempt} PUT now/certs`); + }) - if (403 === res.status) { - return bail(new Error('Unauthorized')); + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} PUT now/certs`) } - const body = await res.json(); + if (res.status === 403) { + return bail(new Error('Unauthorized')) + } + + const body = await res.json() if (res.status !== 200) { - if (404 === res.status || 400 === res.status) { - const err = new Error(body.error.message); - err.userError = true; - return bail(err); - } else { - throw new Error(body.error.message); + 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; - }); + return body + }) } - delete (cn) { + 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 (this._debug) { + console.time(`> [debug] #${attempt} DELETE now/certs/${cn}`) + } + + const res = await this._fetch(`/now/certs/${cn}`, {method: 'DELETE'}) - if (403 === res.status) { - return bail(new Error('Unauthorized')); + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} DELETE now/certs/${cn}`) } - const body = await res.json(); + if (res.status === 403) { + return bail(new Error('Unauthorized')) + } + + const body = await res.json() if (res.status !== 200) { - if (404 === res.status || 400 === res.status) { - const err = new Error(body.error.message); - err.userError = true; - return bail(err); - } else { - throw new Error(body.error.message); + 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; - }); + return body + }) } } diff --git a/lib/cfg.js b/lib/cfg.js index 5393065..c238730 100644 --- a/lib/cfg.js +++ b/lib/cfg.js @@ -1,20 +1,23 @@ -import path from 'path'; -import fs from 'fs-promise'; -import { homedir } from 'os'; +// Native +import {homedir} from 'os' +import path from 'path' -let file = process.env.NOW_JSON ? path.resolve(process.env.NOW_JSON) : path.resolve(homedir(), '.now.json'); +// Packages +import fs from 'fs-promise' -export function setConfigFile (nowjson) { - file = path.resolve(nowjson); +let file = process.env.NOW_JSON ? path.resolve(process.env.NOW_JSON) : path.resolve(homedir(), '.now.json') + +export function setConfigFile(nowjson) { + file = path.resolve(nowjson) } -export function read () { - let existing = null; +export function read() { + let existing = null try { - existing = fs.readFileSync(file, 'utf8'); - existing = JSON.parse(existing); + existing = fs.readFileSync(file, 'utf8') + existing = JSON.parse(existing) } catch (err) {} - return existing || {}; + return existing || {} } /** @@ -25,7 +28,7 @@ export function read () { * @param {Object} data */ -export function merge (data) { - const cfg = Object.assign({}, read(), data); - fs.writeFileSync(file, JSON.stringify(cfg, null, 2)); +export function merge(data) { + const cfg = Object.assign({}, read(), data) + fs.writeFileSync(file, JSON.stringify(cfg, null, 2)) } diff --git a/lib/check-update.js b/lib/check-update.js index f64afc9..23c6579 100644 --- a/lib/check-update.js +++ b/lib/check-update.js @@ -1,81 +1,95 @@ -import ms from 'ms'; -import pkg from '../../package'; // relative to `build/` :\ -import fetch from 'node-fetch'; -import chalk from 'chalk'; -import compare from 'semver-compare'; +// Packages +import ms from 'ms' +import fetch from 'node-fetch' +import chalk from 'chalk' +import compare from 'semver-compare' -const isTTY = process.stdout.isTTY; +// Ours +import pkg from '../../package' + +const isTTY = process.stdout.isTTY // if we're not in a tty the update checker // will always return a resolved promise -const resolvedPromise = new Promise((resolve, reject) => resolve()); +const resolvedPromise = new Promise(resolve => resolve()) /** * Configures auto updates. * Sets up a `exit` listener to report them. */ -export default function checkUpdate (opts = {}) { +export default function checkUpdate(opts = {}) { if (!isTTY) { // don't attempt to check for updates // if the user is piping or redirecting - return resolvedPromise; + return resolvedPromise } - let updateData; + let updateData - const update = check(opts).then((data) => { - updateData = data; + const update = check(opts).then(data => { + updateData = data // forces the `exit` event upon Ctrl + C process.on('SIGINT', () => { // clean up output after ^C - process.stdout.write('\n'); - process.exit(1); - }); - }, (err) => console.error(err.stack)); + process.stdout.write('\n') + process.exit(1) + }) + }, err => console.error(err.stack)) - process.on('exit', (code) => { + process.on('exit', () => { if (updateData) { - const { current, latest, at } = updateData; - const ago = ms(Date.now() - at); + const {current, latest, at} = updateData + const ago = ms(Date.now() - at) console.log(`> ${chalk.white.bgRed('UPDATE NEEDED')} ` + `Current: ${current} – ` + - `Latest ${chalk.bold(latest)} (released ${ago} ago)`); - console.log('> Run `npm install -g now` to update'); + `Latest ${chalk.bold(latest)} (released ${ago} ago)`) + console.log('> Run `npm install -g now` to update') } - }); + }) - return update; + return update } -function check ({ debug = false }) { - return new Promise((resolve, reject) => { - if (debug) console.log('> [debug] Checking for updates.'); +function check({debug = false}) { + return new Promise(resolve => { + if (debug) { + console.log('> [debug] Checking for updates.') + } + + fetch('https://registry.npmjs.org/now').then(res => { + if (res.status !== 200) { + if (debug) { + console.log(`> [debug] Update check error. NPM ${res.status}.`) + } - fetch('https://registry.npmjs.org/now').then((res) => { - if (200 !== res.status) { - if (debug) console.log(`> [debug] Update check error. NPM ${res.status}.`); - resolve(false); - return; + resolve(false) + return } - res.json().then((data) => { - const { latest } = data['dist-tags']; - const current = pkg.version; + res.json().then(data => { + const {latest} = data['dist-tags'] + const current = pkg.version + + if (compare(latest, pkg.version) === 1) { + if (debug) { + console.log(`> [debug] Needs update. Current ${current}, latest ${latest}`) + } - if (1 === compare(latest, pkg.version)) { - if (debug) console.log(`> [debug] Needs update. Current ${current}, latest ${latest}`); resolve({ latest, current, at: new Date(data.time[latest]) - }); + }) } else { - if (debug) console.log(`> [debug] Up to date (${pkg.version}).`); - resolve(false); + if (debug) { + console.log(`> [debug] Up to date (${pkg.version}).`) + } + + resolve(false) } - }, () => resolve(false)); - }, () => resolve(false)); - }); + }, () => resolve(false)) + }, () => resolve(false)) + }) } diff --git a/lib/copy.js b/lib/copy.js index 01e12a8..0d7eb87 100644 --- a/lib/copy.js +++ b/lib/copy.js @@ -1,10 +1,14 @@ -import { copy as _copy } from 'copy-paste'; +// Packages +import {copy as _copy} from 'copy-paste' -export default function copy (text) { +export default function copy(text) { return new Promise((resolve, reject) => { - _copy(text, (err) => { - if (err) return reject(err); - resolve(); - }); - }); + _copy(text, err => { + if (err) { + return reject(err) + } + + resolve() + }) + }) } diff --git a/lib/dns.js b/lib/dns.js index 11fb4ce..16a3345 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -1,10 +1,14 @@ -import dns from 'dns'; +// Packages +import dns from 'dns' -export function resolve4 (host) { +export default function resolve4(host) { return new Promise((resolve, reject) => { return dns.resolve4(host, (err, answer) => { - if (err) return reject(err); - resolve(answer); - }); - }); + if (err) { + return reject(err) + } + + resolve(answer) + }) + }) } diff --git a/lib/domains.js b/lib/domains.js index 0f620d9..1cc4b83 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -1,67 +1,86 @@ -import Now from '../lib'; -import isZeitWorld from './is-zeit-world'; -import chalk from 'chalk'; -import { DNS_VERIFICATION_ERROR } from './errors'; +// Packages +import chalk from 'chalk' -const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/; +// Ours +import Now from '../lib' +import isZeitWorld from './is-zeit-world' +import {DNS_VERIFICATION_ERROR} from './errors' + +const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/ export default class Domains extends Now { - async ls () { + async ls() { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} GET /domains`); - const res = await this._fetch('/domains'); - if (this._debug) console.timeEnd(`> [debug] #${attempt} GET /domains`); - const body = await res.json(); - return body.domains; - }); + if (this._debug) { + console.time(`> [debug] #${attempt} GET /domains`) + } + + const res = await this._fetch('/domains') + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} GET /domains`) + } + + const body = await res.json() + return body.domains + }) } - async rm (name) { + async rm(name) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} DELETE /domains/${name}`); - const res = await this._fetch(`/domains/${name}`, { method: 'DELETE' }); - if (this._debug) console.timeEnd(`> [debug] #${attempt} DELETE /domains/${name}`); + if (this._debug) { + console.time(`> [debug] #${attempt} DELETE /domains/${name}`) + } - if (403 === res.status) { - return bail(new Error('Unauthorized')); + const res = await this._fetch(`/domains/${name}`, {method: 'DELETE'}) + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} DELETE /domains/${name}`) + } + + if (res.status === 403) { + return bail(new Error('Unauthorized')) } if (res.status !== 200) { - const body = await res.json(); - throw new Error(body.error.message); + const body = await res.json() + throw new Error(body.error.message) } - }); + }) } - async add (domain) { + async add(domain) { if (!domainRegex.test(domain)) { - const err = new Error(`The supplied value ${chalk.bold(`"${domain}"`)} is not a valid domain.`); - err.userError = true; - throw err; + const err = new Error(`The supplied value ${chalk.bold(`"${domain}"`)} is not a valid domain.`) + err.userError = true + throw err } - let ns; + let ns try { - console.log('> Verifying nameservers…'); - const res = await this.getNameservers(domain); - ns = res.nameservers; + console.log('> Verifying nameservers…') + const res = await this.getNameservers(domain) + ns = res.nameservers } catch (err) { - const err2 = new Error(`Unable to fetch nameservers for ${chalk.underline(chalk.bold(domain))}.`); - err2.userError = true; - throw err2; + const err2 = new Error(`Unable to fetch nameservers for ${chalk.underline(chalk.bold(domain))}.`) + err2.userError = true + throw err2 } if (isZeitWorld(ns)) { - console.log(`> Verification ${chalk.bold('OK')}!`); - return this.setupDomain(domain); - } else { - 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; + 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 } } diff --git a/lib/error.js b/lib/error.js index 52d5777..9153d46 100644 --- a/lib/error.js +++ b/lib/error.js @@ -1,26 +1,27 @@ -import ms from 'ms'; -import chalk from 'chalk'; +// Packages +import ms from 'ms' +import chalk from 'chalk' -export function handleError (err) { - if (403 === err.status) { - error('Authentication error. Run `now -L` or `now --login` to log-in again.'); - } else if (429 === err.status) { - if (null != err.retryAfter) { - error('Rate limit exceeded error. Try again in ' + - ms(err.retryAfter * 1000, { long: true }) + - ', or upgrade your account: https://zeit.co/now#pricing'); +export function handleError(err) { + if (err.status === 403) { + error('Authentication error. Run `now -L` or `now --login` to log-in again.') + } else if (err.status === 429) { + if (err.retryAfter === null) { + error('Rate limit exceeded error. Please try later.') } else { - error('Rate limit exceeded error. Please try later.'); + error('Rate limit exceeded error. Try again in ' + + ms(err.retryAfter * 1000, {long: true}) + + ', or upgrade your account: https://zeit.co/now#pricing') } } else if (err.userError) { - error(err.message); - } else if (500 === err.status) { - error('Unexpected server error. Please retry.'); + error(err.message) + } else if (err.status === 500) { + error('Unexpected server error. Please retry.') } else { - error(`Unexpected error. Please try later. (${err.message})`); + error(`Unexpected error. Please try later. (${err.message})`) } } -export function error (err) { - console.error(`> ${chalk.red('Error!')} ${err}`); +export function error(err) { + console.error(`> ${chalk.red('Error!')} ${err}`) } diff --git a/lib/errors.js b/lib/errors.js index 646b0c4..40d1ea7 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -1,11 +1,12 @@ -import chalk from 'chalk'; +// Packages +import chalk from 'chalk' export const DNS_VERIFICATION_ERROR = `Please make sure that your nameservers point to ${chalk.underline('zeit.world')}. > Examples: (full list at ${chalk.underline('https://zeit.world')}) > ${chalk.gray('-')} ${chalk.underline('california.zeit.world')} ${chalk.dim('173.255.215.107')} > ${chalk.gray('-')} ${chalk.underline('newark.zeit.world')} ${chalk.dim('173.255.231.87')} > ${chalk.gray('-')} ${chalk.underline('london.zeit.world')} ${chalk.dim('178.62.47.76')} -> ${chalk.gray('-')} ${chalk.underline('singapore.zeit.world')} ${chalk.dim('119.81.97.170')}`; +> ${chalk.gray('-')} ${chalk.underline('singapore.zeit.world')} ${chalk.dim('119.81.97.170')}` export const DOMAIN_VERIFICATION_ERROR = DNS_VERIFICATION_ERROR + -`\n> Alternatively, ensure it resolves to ${chalk.underline('alias.zeit.co')} via ${chalk.dim('CNAME')} / ${chalk.dim('ALIAS')}.`; +`\n> Alternatively, ensure it resolves to ${chalk.underline('alias.zeit.co')} via ${chalk.dim('CNAME')} / ${chalk.dim('ALIAS')}.` diff --git a/lib/get-files.js b/lib/get-files.js index 81512be..152923f 100644 --- a/lib/get-files.js +++ b/lib/get-files.js @@ -1,9 +1,14 @@ -import flatten from 'arr-flatten'; -import unique from 'array-unique'; -import IGNORED from './ignored'; -import { resolve } from 'path'; -import { stat, readdir, readFile } from 'fs-promise'; -import ignore from 'ignore'; +// Native +import {resolve} from 'path' + +// Packages +import flatten from 'arr-flatten' +import unique from 'array-unique' +import ignore from 'ignore' +import {stat, readdir, readFile} from 'fs-promise' + +// Ours +import IGNORED from './ignored' /** * Returns a list of files in the given @@ -18,58 +23,85 @@ import ignore from 'ignore'; * @return {Array} comprehensive list of paths to sync */ -export async function npm (path, pkg, { +export async function npm(path, pkg, { limit = null, debug = false } = {}) { - const whitelist = pkg.now && pkg.now.files - ? pkg.now.files - : pkg.files; + const whitelist = pkg.now && pkg.now.files ? pkg.now.files : pkg.files // the package.json `files` whitelist still // honors ignores: https://docs.npmjs.com/files/package.json#files - const search_ = whitelist || ['.']; + const search_ = whitelist || ['.'] // always include the "main" file if (pkg.main) { - search_.push(pkg.main); + search_.push(pkg.main) } // convert all filenames into absolute paths - const search = search_.map((file) => asAbsolute(file, path)); + const search = search_.map(file => asAbsolute(file, path)) // compile list of ignored patterns and files - const npmIgnore = await maybeRead(resolve(path, '.npmignore'), null); + const npmIgnore = await maybeRead(resolve(path, '.npmignore'), null) + const filter = ignore().add( IGNORED + '\n' + - clearRelative(null != npmIgnore - ? npmIgnore - : await maybeRead(resolve(path, '.gitignore'))) - ).createFilter(); + clearRelative(npmIgnore === null ? await maybeRead(resolve(path, '.gitignore')) : npmIgnore) + ).createFilter() - const prefixLength = path.length + 1; + const prefixLength = path.length + 1 const accepts = function (file) { - const relativePath = file.substr(prefixLength); - if ('' === relativePath) return true; - const accepted = filter(relativePath); + const relativePath = file.substr(prefixLength) + + if (relativePath === '') { + return true + } + + const accepted = filter(relativePath) if (!accepted && debug) { - console.log('> [debug] ignoring "%s"', file); + console.log('> [debug] ignoring "%s"', file) } - return accepted; - }; + return accepted + } // locate files - if (debug) console.time(`> [debug] locating files ${path}`); - const files = await explode(search, { accepts, limit, debug }); - if (debug) console.timeEnd(`> [debug] locating files ${path}`); + 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)); + files.push(asAbsolute('package.json', path)) // get files - return unique(files); + return unique(files) +} + +/** + * 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) } /** @@ -85,60 +117,57 @@ export async function npm (path, pkg, { * @return {Array} comprehensive list of paths to sync */ -export async function docker (path, { +export async function docker(path, { limit = null, debug = false } = {}) { // base search path - const search_ = ['.']; + const search_ = ['.'] // convert all filenames into absolute paths - const search = search_.map((file) => asAbsolute(file, path)); + const search = search_.map(file => asAbsolute(file, path)) // compile list of ignored patterns and files const filter = ignore().add( IGNORED + '\n' + await maybeRead(resolve(path, '.dockerignore')) - ).createFilter(); + ).createFilter() - const prefixLength = path.length + 1; + const prefixLength = path.length + 1 const accepts = function (file) { - const relativePath = file.substr(prefixLength); - if ('' === relativePath) return true; - const accepted = filter(relativePath); + const relativePath = file.substr(prefixLength) + + if (relativePath === '') { + return true + } + + const accepted = filter(relativePath) if (!accepted && debug) { - console.log('> [debug] ignoring "%s"', file); + console.log('> [debug] ignoring "%s"', file) } - return accepted; - }; + return accepted + } // locate files - if (debug) console.time(`> [debug] locating files ${path}`); - const files = await explode(search, { accepts, limit, debug }); - if (debug) console.timeEnd(`> [debug] locating files ${path}`); + 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)); + files.push(asAbsolute('Dockerfile', path)) // get files - return unique(files); + return unique(files) } -/** - * 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); -}; - /** * Explodes directories into a full list of files. * Eg: @@ -153,45 +182,45 @@ const asAbsolute = function (path, parent) { * @return {Array} of {String}s of full paths */ -const explode = async function (paths, { accepts, limit, debug }) { - const many = async (all) => { - return await Promise.all(all.map(async (file) => { - return await list(file); - })); - }; - - const list = async (file) => { - let path = file; - let s; +const explode = async function (paths, {accepts}) { + const list = async file => { + let path = file + let s if (!accepts(file)) { - return null; + return null } try { - s = await stat(path); + s = await stat(path) } catch (e) { // in case the file comes from `files` or `main` // and it wasn't specified with `.js` by the user - path = file + '.js'; + path = file + '.js' try { - s = await stat(path); + s = await stat(path) } catch (e2) { - return null; + return null } } if (s.isDirectory()) { - const all = await readdir(file); - return many(all.map(subdir => asAbsolute(subdir, file))); - } else { - return path; + const all = await readdir(file) + return many(all.map(subdir => asAbsolute(subdir, file))) } - }; - return flatten((await many(paths))).filter((v) => null !== v); -}; + return path + } + + const many = async all => { + return await Promise.all(all.map(async file => { + return await list(file) + })) + } + + return flatten((await many(paths))).filter(v => v !== null) +} /** * Returns the contents of a file if it exists. @@ -201,11 +230,11 @@ const explode = async function (paths, { accepts, limit, debug }) { const maybeRead = async function (path, default_ = '') { try { - return (await readFile(path, 'utf8')); - } catch (e) { - return default_; + return (await readFile(path, 'utf8')) + } catch (err) { + return default_ } -}; +} /** * Remove leading `./` from the beginning of ignores @@ -213,5 +242,5 @@ const maybeRead = async function (path, default_ = '') { */ const clearRelative = function (str) { - return str.replace(/(\n|^)\.\//g, '$1'); -}; + return str.replace(/(\n|^)\.\//g, '$1') +} diff --git a/lib/hash.js b/lib/hash.js index fea3c53..940d7db 100644 --- a/lib/hash.js +++ b/lib/hash.js @@ -1,5 +1,8 @@ -import { createHash } from 'crypto'; -import { readFile } from 'fs-promise'; +// Native +import {createHash} from 'crypto' + +// Packages +import {readFile} from 'fs-promise' /** * Computes hashes for the contents of each file given. @@ -8,19 +11,19 @@ import { readFile } from 'fs-promise'; * @return {Map} */ -export default 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); +export default 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); + entry.names.push(name) } else { - map.set(hash(data), { names: [name], data }); + map.set(hash(data), {names: [name], data}) } - })); - return map; + })) + return map } /** @@ -30,8 +33,8 @@ export default async function hashes (files) { * @return {String} hex digest */ -function hash (buf) { +function hash(buf) { return createHash('sha1') .update(buf) - .digest('hex'); + .digest('hex') } diff --git a/lib/ignored.js b/lib/ignored.js index 6a3b810..be110fd 100644 --- a/lib/ignored.js +++ b/lib/ignored.js @@ -14,4 +14,4 @@ export default `.hg npm-debug.log config.gypi node_modules -CVS`; +CVS` diff --git a/lib/index.js b/lib/index.js index f75c869..0dbb4d7 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,40 +1,42 @@ -import bytes from 'bytes'; -import chalk from 'chalk'; -import { - npm as getNpmFiles, - docker as getDockerFiles -} from './get-files'; -import ua from './ua'; -import hash from './hash'; -import retry from 'async-retry'; -import Agent from './agent'; -import EventEmitter from 'events'; -import { basename, resolve as resolvePath } from 'path'; -import { homedir } from 'os'; -import { parse as parseIni } from 'ini'; -import { readFile } from 'fs-promise'; -import resumer from 'resumer'; -import splitArray from 'split-array'; -import { parse as parseDockerfile } from 'docker-file-parser'; +// Native +import {homedir} from 'os' +import {basename, resolve as resolvePath} from 'path' +import EventEmitter from 'events' + +// Packages +import bytes from 'bytes' +import chalk from 'chalk' +import retry from 'async-retry' +import {parse as parseIni} from 'ini' +import {readFile} from 'fs-promise' +import resumer from 'resumer' +import splitArray from 'split-array' +import {parse as parseDockerfile} from 'docker-file-parser' + +// Ours +import {npm as getNpmFiles, docker as getDockerFiles} from './get-files' +import ua from './ua' +import hash from './hash' +import Agent from './agent' // how many concurrent HTTP/2 stream uploads -const MAX_CONCURRENT = 10; +const MAX_CONCURRENT = 10 // check if running windows -const IS_WIN = /^win/.test(process.platform); -const SEP = IS_WIN ? '\\' : '/'; +const IS_WIN = /^win/.test(process.platform) +const SEP = IS_WIN ? '\\' : '/' export default class Now extends EventEmitter { - constructor (url, token, { forceNew = false, debug = false }) { - super(); - this._token = token; - this._debug = debug; - this._forceNew = forceNew; - this._agent = new Agent(url, { debug }); - this._onRetry = this._onRetry.bind(this); + constructor(url, token, {forceNew = false, debug = false}) { + super() + this._token = token + this._debug = debug + this._forceNew = forceNew + this._agent = new Agent(url, {debug}) + this._onRetry = this._onRetry.bind(this) } - async create (path, { + async create(path, { wantsPublic, quiet = false, env = {}, @@ -43,138 +45,172 @@ export default class Now extends EventEmitter { forwardNpm = false, deploymentType = 'npm' }) { - this._path = path; + this._path = path - let pkg = {}; - let name, description, files; + let pkg = {} - if ('npm' === deploymentType) { + let name + let description + let files + + if (deploymentType === 'npm') { try { - pkg = await readFile(resolvePath(path, 'package.json')); - pkg = JSON.parse(pkg); + pkg = await readFile(resolvePath(path, 'package.json')) + pkg = JSON.parse(pkg) } catch (err) { - const e = Error(`Failed to read JSON in "${path}/package.json"`); - e.userError = true; - throw e; + const e = Error(`Failed to read JSON in "${path}/package.json"`) + e.userError = true + throw e } if (!pkg.scripts || (!pkg.scripts.start && !pkg.scripts['now-start'])) { const e = Error('Missing `start` (or `now-start`) script in `package.json`. ' + - 'See: https://docs.npmjs.com/cli/start.'); - e.userError = true; - throw e; + 'See: https://docs.npmjs.com/cli/start.') + e.userError = true + throw e } - if (null == pkg.name || 'string' !== typeof pkg.name) { - name = basename(path); - if (!quiet) console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`); + if (pkg.name === null || typeof pkg.name !== 'string') { + name = basename(path) + + if (!quiet) { + console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`) + } } else { - name = pkg.name; + name = pkg.name + } + + description = pkg.description + + if (this._debug) { + console.time('> [debug] Getting files') } - description = pkg.description; + files = await getNpmFiles(path, pkg, {debug: this._debug}) - if (this._debug) console.time('> [debug] Getting files'); - files = await getNpmFiles(path, pkg, { debug: this._debug }); - if (this._debug) console.timeEnd('> [debug] Getting files'); - } else if ('docker' === deploymentType) { - let docker; + if (this._debug) { + console.timeEnd('> [debug] Getting files') + } + } else if (deploymentType === 'docker') { + let docker try { - const dockerfile = await readFile(resolvePath(path, 'Dockerfile'), 'utf8'); - docker = parseDockerfile(dockerfile, { includeComments: true }); + const dockerfile = await readFile(resolvePath(path, 'Dockerfile'), 'utf8') + docker = parseDockerfile(dockerfile, {includeComments: true}) } catch (err) { - const e = Error(`Failed to parse "${path}/Dockerfile"`); - e.userError = true; - throw e; + const e = Error(`Failed to parse "${path}/Dockerfile"`) + e.userError = true + throw e } - if (!docker.length) { - const e = Error('No commands found in `Dockerfile`'); - e.userError = true; - throw e; + if (docker.length <= 0) { + const e = Error('No commands found in `Dockerfile`') + e.userError = true + throw e } - if (!docker.some((cmd) => 'CMD' === cmd.name)) { + if (!docker.some(cmd => cmd.name === 'CMD')) { const e = Error('No `CMD` found in `Dockerfile`. ' + - 'See: https://docs.docker.com/engine/reference/builder/#/cmd'); - e.userError = true; - throw e; + 'See: https://docs.docker.com/engine/reference/builder/#/cmd') + e.userError = true + throw e } - if (!docker.some((cmd) => 'EXPOSE' === cmd.name)) { + if (!docker.some(cmd => cmd.name === 'EXPOSE')) { const e = Error('No `EXPOSE` found in `Dockerfile`. A port must be supplied. ' + - 'See: https://docs.docker.com/engine/reference/builder/#/expose'); - e.userError = true; - throw e; + 'See: https://docs.docker.com/engine/reference/builder/#/expose') + e.userError = true + throw e } - const labels = {}; + const labels = {} docker - .filter(cmd => 'LABEL' === cmd.name) - .forEach(({ args }) => { - for (let key in args) { + .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] = JSON.parse(args[key]); + labels[key] = JSON.parse(args[key]) } catch (err) { - const e = Error(`Error parsing value for LABEL ${key} in \`Dockerfile\``); - e.userError = true; - throw e; + const e = Error(`Error parsing value for LABEL ${key} in \`Dockerfile\``) + e.userError = true + throw e } } - }); + }) + + if (labels.name === null) { + name = basename(path) - if (null == labels.name) { - name = basename(path); - if (!quiet) console.log(`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`); + if (!quiet) { + console.log(`> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}`) + } } else { - name = labels.name; + name = labels.name } - description = labels.description; + description = labels.description + + if (this._debug) { + console.time('> [debug] Getting files') + } - if (this._debug) console.time('> [debug] Getting files'); - files = await getDockerFiles(path, { debug: this._debug }); - if (this._debug) console.timeEnd('> [debug] Getting files'); + files = await getDockerFiles(path, {debug: this._debug}) + + if (this._debug) { + console.timeEnd('> [debug] Getting files') + } } - const nowProperties = pkg ? pkg.now || {} : {}; + const nowProperties = pkg ? pkg.now || {} : {} - forwardNpm = forwardNpm || nowProperties['forwardNpm']; + forwardNpm = forwardNpm || nowProperties.forwardNpm // Read .npmrc - let npmrc = {}; - let authToken; - if ('npm' === deploymentType && forwardNpm) { + let npmrc = {} + let authToken + if (deploymentType === 'npm' && forwardNpm) { try { - npmrc = await readFile(resolvePath(path, '.npmrc'), 'utf8'); - npmrc = parseIni(npmrc); - authToken = npmrc['//registry.npmjs.org/:_authToken']; + npmrc = await readFile(resolvePath(path, '.npmrc'), 'utf8') + npmrc = parseIni(npmrc) + authToken = npmrc['//registry.npmjs.org/:_authToken'] } catch (err) { // Do nothing } if (!authToken) { try { - npmrc = await readFile(resolvePath(homedir(), '.npmrc'), 'utf8'); - npmrc = parseIni(npmrc); - authToken = npmrc['//registry.npmjs.org/:_authToken']; + npmrc = await readFile(resolvePath(homedir(), '.npmrc'), 'utf8') + npmrc = parseIni(npmrc) + authToken = npmrc['//registry.npmjs.org/:_authToken'] } catch (err) { // Do nothing } } } - if (this._debug) console.time('> [debug] Computing hashes'); - const hashes = await hash(files); - if (this._debug) console.timeEnd('> [debug] Computing hashes'); + if (this._debug) { + console.time('> [debug] Computing hashes') + } + + const hashes = await hash(files) - this._files = hashes; + if (this._debug) { + console.timeEnd('> [debug] Computing hashes') + } - const engines = nowProperties.engines || pkg.engines; + this._files = hashes + + const engines = nowProperties.engines || pkg.engines + + const deployment = await this.retry(async bail => { + if (this._debug) { + console.time('> [debug] /now/create') + } - const deployment = await this.retry(async (bail) => { - if (this._debug) console.time('> [debug] /now/create'); const res = await this._fetch('/now/create', { method: 'POST', body: { @@ -182,113 +218,119 @@ export default class Now extends EventEmitter { public: wantsPublic, forceNew, forceSync, - name: name, + name, description, deploymentType, registryAuthToken: authToken, // Flatten the array to contain files to sync where each nested input // array has a group of files with the same sha but different path - files: Array.prototype.concat.apply([], Array.from(this._files).map(([sha, { data, names }]) => { - return names.map((n) => { + files: Array.prototype.concat.apply([], Array.from(this._files).map(([sha, {data, names}]) => { + return names.map(n => { return { sha, size: data.length, file: toRelative(n, this._path) - }; - }); + } + }) })), engines } - }); - if (this._debug) console.timeEnd('> [debug] /now/create'); + }) + + if (this._debug) { + console.timeEnd('> [debug] /now/create') + } // no retry on 4xx - let body; + let body try { - body = await res.json(); + body = await res.json() } catch (err) { - throw new Error('Unexpected response'); + throw new Error('Unexpected response') } - if (429 === res.status) { - return bail(responseError(res)); + if (res.status === 429) { + return bail(responseError(res)) } else if (res.status >= 400 && res.status < 500) { - const err = new Error(body.error.message); - err.userError = true; - return bail(err); - } else if (200 !== res.status) { - throw new Error(body.error.message); + const err = new Error(body.error.message) + err.userError = true + return bail(err) + } else if (res.status !== 200) { + throw new Error(body.error.message) } - return body; - }); + return body + }) // we report about files whose sizes are too big - let missingVersion = false; + let missingVersion = false if (deployment.warnings) { - let sizeExceeded = 0; - deployment.warnings.forEach((warning) => { - if ('size_limit_exceeded' === warning.reason) { - const { sha, limit } = warning; - const n = hashes.get(sha).names.pop(); + let sizeExceeded = 0 + deployment.warnings.forEach(warning => { + if (warning.reason === 'size_limit_exceeded') { + const {sha, limit} = warning + const n = hashes.get(sha).names.pop() console.error('> \u001b[31mWarning!\u001b[39m Skipping file %s (size exceeded %s)', n, bytes(limit) - ); - hashes.get(sha).names.unshift(n); // move name (hack, if duplicate matches we report them in order) - sizeExceeded++; - } else if ('node_version_not_found' === warning.reason) { - const { wanted, used } = warning; + ) + hashes.get(sha).names.unshift(n) // move name (hack, if duplicate matches we report them in order) + sizeExceeded++ + } else if (warning.reason === 'node_version_not_found') { + const {wanted, used} = warning console.error('> \u001b[31mWarning!\u001b[39m Requested node version %s is not available', wanted, used - ); - missingVersion = true; + ) + missingVersion = true } - }); + }) if (sizeExceeded) { console.error(`> \u001b[31mWarning!\u001b[39m ${sizeExceeded} of the files ` + 'exceeded the limit for your plan.\n' + - `> See ${chalk.underline('https://zeit.co/account')} to upgrade.`); + `> See ${chalk.underline('https://zeit.co/account')} to upgrade.`) } } if (!quiet && deployment.nodeVersion) { if (engines && engines.node) { if (missingVersion) { - console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)`); + console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)`) } else { - console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (requested: ${chalk.dim(`\`${engines.node}\``)})`); + console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (requested: ${chalk.dim(`\`${engines.node}\``)})`) } } else { - console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)`); + console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)`) } } - this._id = deployment.deploymentId; - this._host = deployment.url; - this._missing = deployment.missing || []; + this._id = deployment.deploymentId + this._host = deployment.url + this._missing = deployment.missing || [] - return this._url; + return this._url } - upload () { - const parts = splitArray(this._missing, MAX_CONCURRENT); + upload() { + const parts = splitArray(this._missing, MAX_CONCURRENT) if (this._debug) { console.log('> [debug] Will upload ' + `${this._missing.length} files in ${parts.length} ` + - `steps of ${MAX_CONCURRENT} uploads.`); + `steps of ${MAX_CONCURRENT} uploads.`) } const uploadChunk = () => { - Promise.all(parts.shift().map((sha) => retry(async (bail, attempt) => { - const file = this._files.get(sha); - const { data, names } = file; + Promise.all(parts.shift().map(sha => retry(async (bail, attempt) => { + const file = this._files.get(sha) + const {data, names} = file - if (this._debug) console.time(`> [debug] /sync #${attempt} ${names.join(' ')}`); - const stream = resumer().queue(data).end(); + if (this._debug) { + console.time(`> [debug] /sync #${attempt} ${names.join(' ')}`) + } + + const stream = resumer().queue(data).end() const res = await this._fetch('/now/sync', { method: 'POST', headers: { @@ -296,306 +338,362 @@ export default class Now extends EventEmitter { 'Content-Length': data.length, 'x-now-deployment-id': this._id, 'x-now-sha': sha, - 'x-now-file': names.map((name) => toRelative(encodeURIComponent(name), this._path)).join(','), + 'x-now-file': names.map(name => toRelative(encodeURIComponent(name), this._path)).join(','), 'x-now-size': data.length }, body: stream - }); - if (this._debug) console.timeEnd(`> [debug] /sync #${attempt} ${names.join(' ')}`); + }) + + if (this._debug) { + console.timeEnd(`> [debug] /sync #${attempt} ${names.join(' ')}`) + } // no retry on 4xx - if (200 !== res.status && (400 <= res.status || 500 > res.status)) { - if (this._debug) console.log('> [debug] bailing on creating due to %s', res.status); - return bail(responseError(res)); + if (res.status !== 200 && (res.status >= 400 || res.status < 500)) { + if (this._debug) { + console.log('> [debug] bailing on creating due to %s', res.status) + } + + return bail(responseError(res)) } - this.emit('upload', file); - }, { retries: 3, randomize: true, onRetry: this._onRetry }))) + this.emit('upload', file) + }, {retries: 3, randomize: true, onRetry: this._onRetry}))) .then(() => parts.length ? uploadChunk() : this.emit('complete')) - .catch((err) => this.emit('error', err)); - }; + .catch(err => this.emit('error', err)) + } - uploadChunk(); + uploadChunk() } - async listSecrets () { + async listSecrets() { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} GET /secrets`); - const res = await this._fetch('/now/secrets'); - if (this._debug) console.timeEnd(`> [debug] #${attempt} GET /secrets`); - const body = await res.json(); - return body.secrets; - }); + if (this._debug) { + console.time(`> [debug] #${attempt} GET /secrets`) + } + + const res = await this._fetch('/now/secrets') + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} GET /secrets`) + } + + const body = await res.json() + return body.secrets + }) } - async list (app) { - const query = app ? `?app=${encodeURIComponent(app)}` : ''; + async list(app) { + const query = app ? `?app=${encodeURIComponent(app)}` : '' - const { deployments } = await this.retry(async (bail) => { - if (this._debug) console.time('> [debug] /list'); - const res = await this._fetch('/now/list' + query); - if (this._debug) console.timeEnd('> [debug] /list'); + const {deployments} = await this.retry(async bail => { + if (this._debug) { + console.time('> [debug] /list') + } + + const res = await this._fetch('/now/list' + query) + + if (this._debug) { + console.timeEnd('> [debug] /list') + } // no retry on 4xx - if (400 <= res.status && 500 > res.status) { + if (res.status >= 400 && res.status < 500) { if (this._debug) { - console.log('> [debug] bailing on listing due to %s', res.status); + console.log('> [debug] bailing on listing due to %s', res.status) } - return bail(responseError(res)); + return bail(responseError(res)) } - if (200 !== res.status) { - throw new Error('Fetching deployment list failed'); + if (res.status !== 200) { + throw new Error('Fetching deployment list failed') } - return res.json(); - }, { retries: 3, minTimeout: 2500, onRetry: this._onRetry }); + return res.json() + }, {retries: 3, minTimeout: 2500, onRetry: this._onRetry}) - return deployments; + return deployments } - async listAliases (deploymentId) { - return this.retry(async (bail, attempt) => { - const res = await this._fetch(deploymentId - ? `/now/deployments/${deploymentId}/aliases` - : '/now/aliases'); - const body = await res.json(); - return body.aliases; - }); + async listAliases(deploymentId) { + return this.retry(async () => { + const res = await this._fetch(deploymentId ? `/now/deployments/${deploymentId}/aliases` : '/now/aliases') + const body = await res.json() + return body.aliases + }) } - getNameservers (domain) { - return new Promise((resolve, reject) => { - let fallback = false; + getNameservers(domain) { + return new Promise(resolve => { + let fallback = false this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} GET /whois-ns${fallback ? '-fallback' : ''}`); - const res = await this._fetch(`/whois-ns${fallback ? '-fallback' : ''}?domain=${encodeURIComponent(domain)}`); - if (this._debug) console.timeEnd(`> [debug] #${attempt} GET /whois-ns${fallback ? '-fallback' : ''}`); - const body = await res.json(); - if (200 === res.status) { + if (this._debug) { + console.time(`> [debug] #${attempt} GET /whois-ns${fallback ? '-fallback' : ''}`) + } + + const res = await this._fetch(`/whois-ns${fallback ? '-fallback' : ''}?domain=${encodeURIComponent(domain)}`) + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} GET /whois-ns${fallback ? '-fallback' : ''}`) + } + + const body = await res.json() + + if (res.status === 200) { if ((!body.nameservers || body.nameservers.length === 0) && !fallback) { // if the nameservers are `null` it's likely // that our whois service failed to parse it - fallback = true; - throw new Error('Invalid whois response'); + fallback = true + throw new Error('Invalid whois response') } - return body; - } else { - if (attempt > 1) fallback = true; - throw new Error(`Whois error (${res.status}): ${body.error.message}`); + return body + } + + if (attempt > 1) { + fallback = true } - }).then((body) => { - body.nameservers = body.nameservers.filter((ns) => { + + throw new Error(`Whois error (${res.status}): ${body.error.message}`) + }).then(body => { + body.nameservers = body.nameservers.filter(ns => { // temporary hack: // sometimes we get a response that looks like: // ['ns', 'ns', '', ''] // so we filter the empty ones - return ns.length; - }); - resolve(body); - }); - }); + return ns.length + }) + resolve(body) + }) + }) } // _ensures_ the domain is setup (idempotent) - setupDomain (name, { isExternal } = {}) { + setupDomain(name, {isExternal} = {}) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} POST /domains`); + if (this._debug) { + console.time(`> [debug] #${attempt} POST /domains`) + } + const res = await this._fetch('/domains', { method: 'POST', - body: { name, isExternal: !!isExternal } - }); - if (this._debug) console.timeEnd(`> [debug] #${attempt} POST /domains`); + body: {name, isExternal: Boolean(isExternal)} + }) - if (403 === res.status) { - const body = await res.json(); - const code = body.error.code; - let err; + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} POST /domains`) + } - if ('custom_domain_needs_upgrade' === code) { - err = new Error(`Custom domains are only enabled for premium accounts. Please upgrade at ${chalk.underline('https://zeit.co/account')}.`); + if (res.status === 403) { + const body = await res.json() + const code = body.error.code + let err + + if (code === 'custom_domain_needs_upgrade') { + err = new Error(`Custom domains are only enabled for premium accounts. Please upgrade at ${chalk.underline('https://zeit.co/account')}.`) } else { - err = new Error(`Not authorized to access domain ${name}`); + err = new Error(`Not authorized to access domain ${name}`) } - err.userError = true; - return bail(err); + err.userError = true + return bail(err) } - const body = await res.json(); + const body = await res.json() // domain already exists - if (409 === res.status) { - if (this._debug) console.log('> [debug] Domain already exists (noop)'); - return { uid: body.error.uid }; + if (res.status === 409) { + if (this._debug) { + console.log('> [debug] Domain already exists (noop)') + } + + return {uid: body.error.uid} } - if (200 !== res.status) { - throw new Error(body.error.message); + if (res.status !== 200) { + throw new Error(body.error.message) } - return body; - }); + return body + }) } - createCert (domain, { renew } = {}) { + createCert(domain, {renew} = {}) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] /now/certs #${attempt}`); + if (this._debug) { + console.time(`> [debug] /now/certs #${attempt}`) + } + const res = await this._fetch('/now/certs', { method: 'POST', body: { domains: [domain], renew } - }); + }) - if (304 === res.status) { - console.log('> Certificate already issued.'); - return; + if (res.status === 304) { + console.log('> Certificate already issued.') + return } - const body = await res.json(); - if (this._debug) console.timeEnd(`> [debug] /now/certs #${attempt}`); + const body = await res.json() + + if (this._debug) { + console.timeEnd(`> [debug] /now/certs #${attempt}`) + } if (body.error) { - const { code } = body.error; + const {code} = body.error - if ('verification_failed' === code) { + if (code === 'verification_failed') { const err = new Error('The certificate issuer failed to verify ownership of the domain. ' + - 'This likely has to do with DNS propagation and caching issues. Please retry later!'); - err.userError = true; + 'This likely has to do with DNS propagation and caching issues. Please retry later!') + err.userError = true // retry - throw err; - } else if ('rate_limited' === code) { - const err = new Error(body.error.message); - err.userError = true; + throw err + } else if (code === 'rate_limited') { + const err = new Error(body.error.message) + err.userError = true // dont retry - return bail(err); + return bail(err) } - throw new Error(body.error.message); + throw new Error(body.error.message) } - if (200 !== res.status && 304 !== res.status) { - throw new Error('Unhandled error'); + if (res.status !== 200 && res.status !== 304) { + throw new Error('Unhandled error') } - return body; - }, { retries: 5, minTimeout: 30000, maxTimeout: 90000 }); + return body + }, {retries: 5, minTimeout: 30000, maxTimeout: 90000}) } - deleteCert (domain) { + deleteCert(domain) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] /now/certs #${attempt}`); + if (this._debug) { + console.time(`> [debug] /now/certs #${attempt}`) + } + const res = await this._fetch(`/now/certs/${domain}`, { method: 'DELETE' - }); + }) - if (200 !== res.status) { - const err = new Error(body.error.message); + if (res.status !== 200) { + const err = new Error(res.body.error.message) + err.userError = false - err.userError = false; - if (400 === res.status || 404 === res.status) { - return bail(err); - } else { - throw err; + if (res.status === 400 || res.status === 404) { + return bail(err) } + + throw err } - }); + }) } - async remove (deploymentId, { hard }) { - const data = { deploymentId, hard }; + async remove(deploymentId, {hard}) { + const data = {deploymentId, hard} + + await this.retry(async bail => { + if (this._debug) { + console.time('> [debug] /remove') + } - await this.retry(async (bail) => { - if (this._debug) console.time('> [debug] /remove'); const res = await this._fetch('/now/remove', { method: 'DELETE', body: data - }); - if (this._debug) console.timeEnd('> [debug] /remove'); + }) + + if (this._debug) { + console.timeEnd('> [debug] /remove') + } // no retry on 4xx - if (400 <= res.status && 500 > res.status) { + if (res.status >= 400 && res.status < 500) { if (this._debug) { - console.log('> [debug] bailing on removal due to %s', res.status); + console.log('> [debug] bailing on removal due to %s', res.status) } - return bail(responseError(res)); + return bail(responseError(res)) } - if (200 !== res.status) { - throw new Error('Removing deployment failed'); + if (res.status !== 200) { + throw new Error('Removing deployment failed') } - }); + }) - return true; + return true } - retry (fn, { retries = 3, maxTimeout = Infinity } = {}) { + retry(fn, {retries = 3, maxTimeout = Infinity} = {}) { return retry(fn, { retries, maxTimeout, onRetry: this._onRetry - }); + }) } - _onRetry (err) { + _onRetry(err) { if (this._debug) { - console.log(`> [debug] Retrying: ${err.stack}`); + console.log(`> [debug] Retrying: ${err.stack}`) } } - close () { - this._agent.close(); + close() { + this._agent.close() } - get id () { - return this._id; + get id() { + return this._id } - get url () { - return `https://${this._host}`; + get url() { + return `https://${this._host}` } - get host () { - return this._host; + get host() { + return this._host } - get syncAmount () { + get syncAmount() { if (!this._syncAmount) { this._syncAmount = this._missing - .map((sha) => this._files.get(sha).data.length) - .reduce((a, b) => a + b, 0); + .map(sha => this._files.get(sha).data.length) + .reduce((a, b) => a + b, 0) } - return this._syncAmount; + return this._syncAmount } - _fetch (_url, opts = {}) { - opts.headers = opts.headers || {}; - opts.headers.authorization = `Bearer ${this._token}`; - opts.headers['user-agent'] = ua; - return this._agent.fetch(_url, opts); + _fetch(_url, opts = {}) { + opts.headers = opts.headers || {} + opts.headers.authorization = `Bearer ${this._token}` + opts.headers['user-agent'] = ua + return this._agent.fetch(_url, opts) } } -function toRelative (path, base) { - const fullBase = base.endsWith(SEP) ? base : base + SEP; - let relative = path.substr(fullBase.length); - if (relative.startsWith(SEP)) relative = relative.substr(1); - return relative.replace(/\\/g, '/'); +function toRelative(path, base) { + const fullBase = base.endsWith(SEP) ? base : base + SEP + let relative = path.substr(fullBase.length) + + if (relative.startsWith(SEP)) { + relative = relative.substr(1) + } + + return relative.replace(/\\/g, '/') } -function responseError (res) { - const err = new Error('Response error'); - err.status = res.status; +function responseError(res) { + const err = new Error('Response error') + err.status = res.status - if (429 === res.status) { - const retryAfter = res.headers.get('Retry-After'); + if (res.status === 429) { + const retryAfter = res.headers.get('Retry-After') if (retryAfter) { - err.retryAfter = parseInt(retryAfter, 10); + err.retryAfter = parseInt(retryAfter, 10) } } - return err; + return err } diff --git a/lib/is-zeit-world.js b/lib/is-zeit-world.js index e5b6702..51290a8 100644 --- a/lib/is-zeit-world.js +++ b/lib/is-zeit-world.js @@ -13,15 +13,15 @@ const nameservers = new Set([ '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. */ -export default function isZeitWorld (ns) { - return ns.length > 1 && ns.every((host) => { - return nameservers.has(host); - }); +export default function isZeitWorld(ns) { + return ns.length > 1 && ns.every(host => { + return nameservers.has(host) + }) } diff --git a/lib/login.js b/lib/login.js index 87e129a..9cc5f14 100644 --- a/lib/login.js +++ b/lib/login.js @@ -1,16 +1,21 @@ -import os from 'os'; -import chalk from 'chalk'; -import fetch from 'node-fetch'; -import * as cfg from './cfg'; -import { stringify as stringifyQuery } from 'querystring'; -import { validate } from 'email-validator'; -import readEmail from 'email-prompt'; -import ua from './ua'; -import pkg from '../../package.json'; - -async function getVerificationToken (url, email) { - const tokenName = `Now CLI ${os.platform()}-${os.arch()} ${pkg.version} (${os.hostname()})`; - const data = JSON.stringify({ email, tokenName }); +// Native +import os from 'os' + +// Packages +import {stringify as stringifyQuery} from 'querystring' +import chalk from 'chalk' +import fetch from 'node-fetch' +import {validate} from 'email-validator' +import readEmail from 'email-prompt' + +// Ours +import pkg from '../../package' +import ua from './ua' +import * as cfg from './cfg' + +async function getVerificationToken(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: { @@ -19,62 +24,67 @@ async function getVerificationToken (url, email) { 'User-Agent': ua }, body: data - }); + }) - if (200 !== res.status) { - throw new Error('Verification error'); + if (res.status !== 200) { + throw new Error('Verification error') } - const body = await res.json(); - return body.token; + const body = await res.json() + return body.token } -async function verify (url, email, verificationToken) { +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; + headers: {'User-Agent': ua} + }) + const body = await res.json() + return body.token } -function sleep (ms) { - return new Promise((resolve, reject) => { - setTimeout(resolve, ms); - }); +function sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms) + }) } -async function register (url, { retryEmail = false } = {}) { - const email = await readEmail({ invalid: retryEmail }); - process.stdout.write('\n'); +async function register(url, {retryEmail = false} = {}) { + const email = await readEmail({invalid: retryEmail}) + process.stdout.write('\n') - if (!validate(email)) return register(url, { retryEmail: true }); + if (!validate(email)) { + return register(url, {retryEmail: true}) + } + + const verificationToken = await getVerificationToken(url, email) - const verificationToken = await getVerificationToken(url, email); + console.log(`> Please follow the link sent to ${chalk.bold(email)} to log in.`) + process.stdout.write('> Waiting for confirmation..') - console.log(`> Please follow the link sent to ${chalk.bold(email)} to log in.`); - process.stdout.write('> Waiting for confirmation..'); + let final - let final; do { - await sleep(2500); + await sleep(2500) + try { - final = await verify(url, email, verificationToken); - } catch (e) {} - process.stdout.write('.'); - } while (!final); + final = await verify(url, email, verificationToken) + } catch (err) {} + + process.stdout.write('.') + } while (!final) - process.stdout.write('\n'); + process.stdout.write('\n') - return { email, token: final }; + return {email, token: final} } export default async function (url) { - const loginData = await register(url); - cfg.merge(loginData); - return loginData.token; + const loginData = await register(url) + cfg.merge(loginData) + return loginData.token } diff --git a/lib/secrets.js b/lib/secrets.js index 6b6529f..9b5105e 100644 --- a/lib/secrets.js +++ b/lib/secrets.js @@ -1,98 +1,115 @@ -import Now from '../lib'; +// Ours +import Now from '../lib' export default class Secrets extends Now { - - ls () { - return this.listSecrets(); + ls() { + return this.listSecrets() } - rm (nameOrId) { + 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 (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 (403 === res.status) { - return bail(new Error('Unauthorized')); + if (res.status === 403) { + return bail(new Error('Unauthorized')) } - const body = await res.json(); + const body = await res.json() if (res.status !== 200) { - if (404 === res.status || 400 === res.status) { - const err = new Error(body.error.message); - err.userError = true; - return bail(err); - } else { - throw new Error(body.error.message); + 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; - }); + return body + }) } - add (name, value) { + add(name, value) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} POST /secrets`); + 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 (403 === res.status) { - return bail(new Error('Unauthorized')); + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} POST /secrets`) } - const body = await res.json(); + if (res.status === 403) { + return bail(new Error('Unauthorized')) + } + + const body = await res.json() if (res.status !== 200) { - if (404 === res.status || 400 === res.status) { - const err = new Error(body.error.message); - err.userError = true; - return bail(err); - } else { - throw new Error(body.error.message); + 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; - }); + return body + }) } - rename (nameOrId, newName) { + rename(nameOrId, newName) { return this.retry(async (bail, attempt) => { - if (this._debug) console.time(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`); + 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 (403 === res.status) { - return bail(new Error('Unauthorized')); + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} PATCH /secrets/${nameOrId}`) } - const body = await res.json(); + if (res.status === 403) { + return bail(new Error('Unauthorized')) + } + + const body = await res.json() if (res.status !== 200) { - if (404 === res.status || 400 === res.status) { - const err = new Error(body.error.message); - err.userError = true; - return bail(err); - } else { - throw new Error(body.error.message); + 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; - }); + return body + }) } - } diff --git a/lib/strlen.js b/lib/strlen.js index 35c3cf2..1817bea 100644 --- a/lib/strlen.js +++ b/lib/strlen.js @@ -1,3 +1,3 @@ -export default function strlen (str) { - return str.replace(/\x1b[^m]*m/g, '').length; +export default function strlen(str) { + return str.replace(/\x1b[^m]*m/g, '').length } diff --git a/lib/test.js b/lib/test.js index 63ed825..f9f0658 100644 --- a/lib/test.js +++ b/lib/test.js @@ -1,20 +1,22 @@ -import getFiles from './get-files'; -import { resolve } from 'path'; +// Native +import {resolve} from 'path' + +// Ours +import {npm as getFiles} from './get-files' getFiles(resolve('../mng-test/files-in-package')) .then(files => { - console.log(files); + console.log(files) getFiles(resolve('../mng-test/files-in-package-ignore')) .then(files2 => { - console.log('ignored: '); - console.log(files2); + console.log('ignored: ') + console.log(files2) }) .catch(err => { - console.log(err.stack); - }); + console.log(err.stack) + }) }) .catch(err => { - console.log(err.stack); -}); - + console.log(err.stack) +}) diff --git a/lib/to-host.js b/lib/to-host.js index 0474409..1737102 100644 --- a/lib/to-host.js +++ b/lib/to-host.js @@ -1,4 +1,5 @@ -import { parse } from 'url'; +// Native +import {parse} from 'url' /** * Converts a valid deployment lookup parameter to a hostname. @@ -6,12 +7,12 @@ import { parse } from 'url'; * google.com => google.com */ -export default function toHost (url) { +export default function toHost(url) { if (/^https?:\/\//.test(url)) { - return parse(url).host; - } else { - // remove any path if present - // `a.b.c/` => `a.b.c` - return url.replace(/(\/\/)?([^\/]+)(.*)/, '$2'); + return parse(url).host } + + // remove any path if present + // `a.b.c/` => `a.b.c` + return url.replace(/(\/\/)?([^\/]+)(.*)/, '$2') } diff --git a/lib/ua.js b/lib/ua.js index 1dc04f9..3ec9f83 100644 --- a/lib/ua.js +++ b/lib/ua.js @@ -1,4 +1,7 @@ -import os from 'os'; -import { version } from '../../package'; +// Native +import os from 'os' -export default `now ${version} node-${process.version} ${os.platform()} (${os.arch()})`; +// Ours +import {version} from '../../package' + +export default `now ${version} node-${process.version} ${os.platform()} (${os.arch()})` diff --git a/lib/utils/prompt-options.js b/lib/utils/prompt-options.js index e525612..f1a09c3 100644 --- a/lib/utils/prompt-options.js +++ b/lib/utils/prompt-options.js @@ -1,30 +1,34 @@ -import chalk from 'chalk'; +import chalk from 'chalk' export default function (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(); - if ('\u0003' === s) { - cleanup(); - reject(new Error('Aborted')); - return; + 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) } - const n = Number(s); + if (s === '\u0003') { + cleanup() + reject(new Error('Aborted')) + return + } + + const n = Number(s) if (opts[n - 1]) { - cleanup(); - resolve(opts[n - 1][0]); + cleanup() + resolve(opts[n - 1][0]) } - }; - const cleanup = () => { - process.stdin.setRawMode(false); - process.stdin.removeListener('data', ondata); - }; - process.stdin.setRawMode(true); - process.stdin.resume(); - process.stdin.on('data', ondata); - }); + } + + process.stdin.setRawMode(true) + process.stdin.resume() + process.stdin.on('data', ondata) + }) } diff --git a/lib/utils/to-human-path.js b/lib/utils/to-human-path.js index 4b03f0a..c22892b 100644 --- a/lib/utils/to-human-path.js +++ b/lib/utils/to-human-path.js @@ -1,8 +1,8 @@ -import { resolve } from 'path'; -import { homedir } from 'os'; +import {resolve} from 'path' +import {homedir} from 'os' // cleaned-up `$HOME` (i.e.: no trailing slash) -const HOME = resolve(homedir()); +const HOME = resolve(homedir()) /** * Attempts to show the given path in @@ -10,6 +10,6 @@ const HOME = resolve(homedir()); * `/Users/rauchg/test.js` becomes `~/test.js` */ -export default function toHumanPath (path) { - return path.replace(HOME, '~'); +export default function toHumanPath(path) { + return path.replace(HOME, '~') } diff --git a/package.json b/package.json index 1530c51..4e5298a 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,15 @@ "build/**", "out/**", "test/_fixtures/**" - ] + ], + "rules": { + "import/no-unresolved": 0, + "ava/no-ignored-test-files": 0, + "max-depth": 0, + "no-use-before-define": 0, + "complexity": 0, + "unicorn/no-process-exit": 0 + } }, "bin": { "now": "./build/bin/now" diff --git a/test/index.js b/test/index.js index 610e362..d4d0383 100644 --- a/test/index.js +++ b/test/index.js @@ -1,130 +1,132 @@ -import test from 'ava'; -import { join, resolve } from 'path'; -import { - npm as getNpmFiles_, - docker as getDockerFiles -} from '../lib/get-files'; -import hash from '../lib/hash'; -import { asc as alpha } from 'alpha-sort'; -import { readFile } from 'fs-promise'; - -const prefix = join(__dirname, '_fixtures') + '/'; -const base = (path) => path.replace(prefix, ''); -const fixture = (name) => resolve(`./_fixtures/${name}`); +// Native +import {join, resolve} from 'path' -// overload to force debugging -const getNpmFiles = async (dir) => { - const pkg = await readJSON(resolve(dir, 'package.json')); - return getNpmFiles_(dir, pkg); -}; +// Packages +import test from 'ava' +import {asc as alpha} from 'alpha-sort' +import {readFile} from 'fs-promise' + +// Ours +import {npm as getNpmFiles_, docker as getDockerFiles} from '../lib/get-files' +import hash from '../lib/hash' + +const prefix = join(__dirname, '_fixtures') + '/' +const base = path => path.replace(prefix, '') +const fixture = name => resolve(`./_fixtures/${name}`) -const readJSON = async (file) => { - const data = await readFile(file); - return JSON.parse(data); -}; +const readJSON = async file => { + const data = await readFile(file) + return JSON.parse(data) +} + +// overload to force debugging +const getNpmFiles = async dir => { + const pkg = await readJSON(resolve(dir, 'package.json')) + return getNpmFiles_(dir, pkg) +} test('`files`', async t => { - let files = await getNpmFiles(fixture('files-in-package')); - t.is(files.length, 3); - files = files.sort(alpha); - t.is(base(files[0]), 'files-in-package/build/a/b/c/d.js'); - t.is(base(files[1]), 'files-in-package/build/a/e.js'); - t.is(base(files[2]), 'files-in-package/package.json'); -}); + let files = await getNpmFiles(fixture('files-in-package')) + t.is(files.length, 3) + files = files.sort(alpha) + t.is(base(files[0]), 'files-in-package/build/a/b/c/d.js') + t.is(base(files[1]), 'files-in-package/build/a/e.js') + t.is(base(files[2]), 'files-in-package/package.json') +}) test('`files` + `.*.swp` + `.npmignore`', async t => { - let files = await getNpmFiles(fixture('files-in-package-ignore')); - files = files.sort(alpha); - t.is(files.length, 3); - t.is(base(files[0]), 'files-in-package-ignore/build/a/b/c/d.js'); - t.is(base(files[1]), 'files-in-package-ignore/build/a/e.js'); - t.is(base(files[2]), 'files-in-package-ignore/package.json'); -}); + let files = await getNpmFiles(fixture('files-in-package-ignore')) + files = files.sort(alpha) + t.is(files.length, 3) + t.is(base(files[0]), 'files-in-package-ignore/build/a/b/c/d.js') + t.is(base(files[1]), 'files-in-package-ignore/build/a/e.js') + t.is(base(files[2]), 'files-in-package-ignore/package.json') +}) test('simple', async t => { - let files = await getNpmFiles(fixture('simple')); - files = files.sort(alpha); - t.is(files.length, 5); - t.is(base(files[0]), 'simple/bin/test'); - t.is(base(files[1]), 'simple/index.js'); - t.is(base(files[2]), 'simple/lib/woot'); - t.is(base(files[3]), 'simple/lib/woot.jsx'); - t.is(base(files[4]), 'simple/package.json'); -}); + let files = await getNpmFiles(fixture('simple')) + files = files.sort(alpha) + t.is(files.length, 5) + t.is(base(files[0]), 'simple/bin/test') + t.is(base(files[1]), 'simple/index.js') + t.is(base(files[2]), 'simple/lib/woot') + t.is(base(files[3]), 'simple/lib/woot.jsx') + t.is(base(files[4]), 'simple/package.json') +}) test('simple with main', async t => { - let files = await getNpmFiles(fixture('simple-main')); - t.is(files.length, 3); - files = files.sort(alpha); - t.is(files.length, 3); - t.is(base(files[0]), 'simple-main/build/a.js'); - t.is(base(files[1]), 'simple-main/index.js'); - t.is(base(files[2]), 'simple-main/package.json'); -}); + let files = await getNpmFiles(fixture('simple-main')) + t.is(files.length, 3) + files = files.sort(alpha) + t.is(files.length, 3) + t.is(base(files[0]), 'simple-main/build/a.js') + t.is(base(files[1]), 'simple-main/index.js') + t.is(base(files[2]), 'simple-main/package.json') +}) test('hashes', async t => { - const files = await getNpmFiles(fixture('hashes')); - const hashes = await hash(files); - t.is(hashes.size, 3); - t.is(hashes.get('277c55a2042910b9fe706ad00859e008c1b7d172').names[0], prefix + 'hashes/dei.png'); - t.is(hashes.get('277c55a2042910b9fe706ad00859e008c1b7d172').names[1], prefix + 'hashes/duplicate/dei.png'); - t.is(hashes.get('56c00d0466fc6bdd41b13dac5fc920cc30a63b45').names[0], prefix + 'hashes/index.js'); - t.is(hashes.get('706214f42ae940a01d2aa60c5e32408f4d2127dd').names[0], prefix + 'hashes/package.json'); -}); + const files = await getNpmFiles(fixture('hashes')) + const hashes = await hash(files) + t.is(hashes.size, 3) + t.is(hashes.get('277c55a2042910b9fe706ad00859e008c1b7d172').names[0], prefix + 'hashes/dei.png') + t.is(hashes.get('277c55a2042910b9fe706ad00859e008c1b7d172').names[1], prefix + 'hashes/duplicate/dei.png') + t.is(hashes.get('56c00d0466fc6bdd41b13dac5fc920cc30a63b45').names[0], prefix + 'hashes/index.js') + t.is(hashes.get('706214f42ae940a01d2aa60c5e32408f4d2127dd').names[0], prefix + 'hashes/package.json') +}) test('ignore node_modules', async t => { - let files = await getNpmFiles(fixture('no-node_modules')); - files = files.sort(alpha); - t.is(files.length, 2); - t.is(base(files[0]), 'no-node_modules/index.js'); - t.is(base(files[1]), 'no-node_modules/package.json'); -}); + let files = await getNpmFiles(fixture('no-node_modules')) + files = files.sort(alpha) + t.is(files.length, 2) + t.is(base(files[0]), 'no-node_modules/index.js') + t.is(base(files[1]), 'no-node_modules/package.json') +}) test('ignore nested `node_modules` with .npmignore **', async t => { - let files = await getNpmFiles(fixture('nested-node_modules')); - files = files.sort(alpha); - t.is(files.length, 2); - t.is(base(files[0]), 'nested-node_modules/index.js'); - t.is(base(files[1]), 'nested-node_modules/package.json'); -}); + let files = await getNpmFiles(fixture('nested-node_modules')) + files = files.sort(alpha) + t.is(files.length, 2) + t.is(base(files[0]), 'nested-node_modules/index.js') + t.is(base(files[1]), 'nested-node_modules/package.json') +}) test('include `main` even if not in files', async t => { - let files = await getNpmFiles(fixture('always-include-main')); - files = files.sort(alpha); - t.is(files.length, 3); - t.is(base(files[0]), 'always-include-main/a.js'); - t.is(base(files[1]), 'always-include-main/package.json'); - t.is(base(files[2]), 'always-include-main/woot.js'); -}); + let files = await getNpmFiles(fixture('always-include-main')) + files = files.sort(alpha) + t.is(files.length, 3) + t.is(base(files[0]), 'always-include-main/a.js') + t.is(base(files[1]), 'always-include-main/package.json') + t.is(base(files[2]), 'always-include-main/woot.js') +}) test('support whitelisting with .npmignore and !', async t => { - let files = await getNpmFiles(fixture('negation')); - files = files.sort(alpha); - t.is(files.length, 2); - t.is(base(files[0]), 'negation/a.js'); - t.is(base(files[1]), 'negation/package.json'); -}); + let files = await getNpmFiles(fixture('negation')) + files = files.sort(alpha) + t.is(files.length, 2) + t.is(base(files[0]), 'negation/a.js') + t.is(base(files[1]), 'negation/package.json') +}) test('support `now.files`', async t => { - let files = await getNpmFiles(fixture('now-files')); - files = files.sort(alpha); - t.is(files.length, 2); - t.is(base(files[0]), 'now-files/b.js'); - t.is(base(files[1]), 'now-files/package.json'); -}); + let files = await getNpmFiles(fixture('now-files')) + files = files.sort(alpha) + t.is(files.length, 2) + t.is(base(files[0]), 'now-files/b.js') + t.is(base(files[1]), 'now-files/package.json') +}) test('support docker', async t => { - let files = await getDockerFiles(fixture('dockerfile')); - files = files.sort(alpha); - t.is(files.length, 2); - t.is(base(files[0]), 'dockerfile/Dockerfile'); - t.is(base(files[1]), 'dockerfile/a.js'); -}); + let files = await getDockerFiles(fixture('dockerfile')) + files = files.sort(alpha) + t.is(files.length, 2) + t.is(base(files[0]), 'dockerfile/Dockerfile') + t.is(base(files[1]), 'dockerfile/a.js') +}) test('prefix regression', async t => { - let files = await getNpmFiles(fixture('prefix-regression')); - files = files.sort(alpha); - t.is(files.length, 2); - t.is(base(files[0]), 'prefix-regression/package.json'); - t.is(base(files[1]), 'prefix-regression/woot.js'); -}); + let files = await getNpmFiles(fixture('prefix-regression')) + files = files.sort(alpha) + t.is(files.length, 2) + t.is(base(files[0]), 'prefix-regression/package.json') + t.is(base(files[1]), 'prefix-regression/woot.js') +}) diff --git a/test/to-host.js b/test/to-host.js index fa1a591..22ba84c 100644 --- a/test/to-host.js +++ b/test/to-host.js @@ -1,26 +1,26 @@ -import test from 'ava'; -import toHost from '../lib/to-host'; +import test from 'ava' +import toHost from '../lib/to-host' test('simple', async t => { - t.is(toHost('zeit.co'), 'zeit.co'); -}); + t.is(toHost('zeit.co'), 'zeit.co') +}) test('leading //', async t => { - t.is(toHost('//zeit-logos-rnemgaicnc.now.sh'), 'zeit-logos-rnemgaicnc.now.sh'); -}); + t.is(toHost('//zeit-logos-rnemgaicnc.now.sh'), 'zeit-logos-rnemgaicnc.now.sh') +}) test('leading http://', async t => { - t.is(toHost('http://zeit-logos-rnemgaicnc.now.sh'), 'zeit-logos-rnemgaicnc.now.sh'); -}); + t.is(toHost('http://zeit-logos-rnemgaicnc.now.sh'), 'zeit-logos-rnemgaicnc.now.sh') +}) test('leading https://', async t => { - t.is(toHost('https://zeit-logos-rnemgaicnc.now.sh'), 'zeit-logos-rnemgaicnc.now.sh'); -}); + t.is(toHost('https://zeit-logos-rnemgaicnc.now.sh'), 'zeit-logos-rnemgaicnc.now.sh') +}) test('leading https:// and path', async t => { - t.is(toHost('https://zeit-logos-rnemgaicnc.now.sh/path'), 'zeit-logos-rnemgaicnc.now.sh'); -}); + t.is(toHost('https://zeit-logos-rnemgaicnc.now.sh/path'), 'zeit-logos-rnemgaicnc.now.sh') +}) test('simple and path', async t => { - t.is(toHost('zeit.co/test'), 'zeit.co'); -}); + t.is(toHost('zeit.co/test'), 'zeit.co') +})