Browse Source

Fixed even more XO issues

master
Leo Lamprecht 8 years ago
parent
commit
3979d8472a
No known key found for this signature in database GPG Key ID: EF804E3FF4BBA8AB
  1. 68
      bin/now
  2. 27
      gulpfile.babel.js
  3. 76
      lib/agent.js
  4. 355
      lib/alias.js
  5. 157
      lib/build-logger.js
  6. 108
      lib/certs.js
  7. 31
      lib/cfg.js
  8. 98
      lib/check-update.js
  9. 18
      lib/copy.js
  10. 16
      lib/dns.js
  11. 97
      lib/domains.js
  12. 35
      lib/error.js
  13. 7
      lib/errors.js
  14. 203
      lib/get-files.js
  15. 31
      lib/hash.js
  16. 2
      lib/ignored.js
  17. 726
      lib/index.js
  18. 10
      lib/is-zeit-world.js
  19. 102
      lib/login.js
  20. 117
      lib/secrets.js
  21. 4
      lib/strlen.js
  22. 22
      lib/test.js
  23. 15
      lib/to-host.js
  24. 9
      lib/ua.js
  25. 46
      lib/utils/prompt-options.js
  26. 10
      lib/utils/to-human-path.js
  27. 10
      package.json
  28. 210
      test/index.js
  29. 28
      test/to-host.js

68
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))

27
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'])

76
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()
}
}
}

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

157
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 = [];
}
}

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

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

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

18
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()
})
})
}

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

97
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
}
}

35
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}`)
}

7
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')}.`

203
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')
}

31
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')
}

2
lib/ignored.js

@ -14,4 +14,4 @@ export default `.hg
npm-debug.log
config.gypi
node_modules
CVS`;
CVS`

726
lib/index.js

File diff suppressed because it is too large

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

102
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
}

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

4
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
}

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

15
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')
}

9
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()})`

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

10
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, '~')
}

10
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"

210
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')
})

28
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')
})

Loading…
Cancel
Save