Browse Source

Merge branch 'master' of github.com:zeit/now

master
Guillermo Rauch 8 years ago
parent
commit
cb3da35156
  1. 3
      .gitignore
  2. 1
      .npmrc
  3. 1
      .travis.yml
  4. 36
      README.md
  5. 40
      bin/now-alias.js
  6. 35
      bin/now-certs.js
  7. 215
      bin/now-deploy.js
  8. 25
      bin/now-dns.js
  9. 68
      bin/now-domains.js
  10. 24
      bin/now-list.js
  11. 40
      bin/now-remove.js
  12. 20
      bin/now-secrets.js
  13. 80
      bin/now.js
  14. 31
      gulpfile.babel.js
  15. 8
      lib/agent.js
  16. 98
      lib/alias.js
  17. 10
      lib/build-logger.js
  18. 4
      lib/certs.js
  19. 18
      lib/cfg.js
  20. 95
      lib/check-update.js
  21. 6
      lib/copy.js
  22. 5
      lib/dns.js
  23. 4
      lib/domain-records.js
  24. 16
      lib/domains.js
  25. 13
      lib/error.js
  26. 11
      lib/errors.js
  27. 59
      lib/get-files.js
  28. 211
      lib/git.js
  29. 10
      lib/hash.js
  30. 2
      lib/ignored.js
  31. 4
      lib/indent.js
  32. 113
      lib/index.js
  33. 4
      lib/is-zeit-world.js
  34. 44
      lib/login.js
  35. 48
      lib/read-metadata.js
  36. 4
      lib/secrets.js
  37. 4
      lib/strlen.js
  38. 4
      lib/test.js
  39. 8
      lib/to-host.js
  40. 6
      lib/ua.js
  41. 49
      lib/utils/check-path.js
  42. 5
      lib/utils/prompt-options.js
  43. 9
      lib/utils/to-human-path.js
  44. 129
      package.json
  45. 2
      test/_fixtures/files-overrides-gitignore/.gitignore
  46. 1
      test/_fixtures/files-overrides-gitignore/ignore-me.js
  47. 6
      test/_fixtures/files-overrides-gitignore/package.json
  48. 1
      test/_fixtures/files-overrides-gitignore/test.js
  49. 1
      test/_fixtures/files-overrides-gitignore/test.json
  50. BIN
      test/_fixtures/hashes/duplicate/dei.png
  51. 2
      test/_fixtures/now-files-overrides-npmignore/.npmignore
  52. 1
      test/_fixtures/now-files-overrides-npmignore/ignore-me.js
  53. 8
      test/_fixtures/now-files-overrides-npmignore/package.json
  54. 1
      test/_fixtures/now-files-overrides-npmignore/test.js
  55. 1
      test/_fixtures/now-files-overrides-npmignore/test.json
  56. 88
      test/args-parsing.js
  57. 30
      test/index.js
  58. 4
      test/to-host.js

3
.gitignore

@ -1,6 +1,5 @@
# build output
build
out
packed
# dependencies
node_modules

1
.npmrc

@ -1 +0,0 @@
save-exact=true

1
.travis.yml

@ -1,5 +1,4 @@
{
"language": "node_js",
"sudo": false,
"node_js": "node"
}

36
README.md

@ -1,36 +1,52 @@
# now
# now CLI
[![Build Status](https://travis-ci.org/zeit/now.svg?branch=master)](https://travis-ci.org/zeit/now)
[![Build Status](https://travis-ci.org/zeit/now-cli.svg?branch=master)](https://travis-ci.org/zeit/now-cli)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
[![Slack Channel](https://zeit-slackin.now.sh/badge.svg)](https://zeit.chat)
Realtime global deployments served over HTTP/2. You can find the FAQ [here](https://github.com/zeit/now/wiki/FAQ).
Realtime global deployments served over HTTP/2. You can find the FAQs [here](https://zeit.co/now#frequently-asked-questions).
## Usage
Firstly, make sure to install the package:
Firstly, make sure to install the package globally:
```bash
$ npm install -g now
npm install -g now
```
Run this in any directory:
Run this command in your terminal:
```bash
$ now
now
```
For more examples, usage instructions and other commands run:
```bash
$ now help
now help
```
## Contribute
### Options
Run this command to get a list of all available commands:
```bash
now help
```
## Caught a Bug?
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
2. Link the package to the global module directory: `npm link`
3. Transpile the source code and watch for changes: `npm start`
3. Generate a [testing token](https://zeit.co/account#api-tokens) and put it into the `token` property within `~/.now.json`
4. You can now start using `now` from the command line!
As always, you can use `npm test` to run the tests and see if your changes have broken anything.
## Authors
- Guillermo Rauch ([@rauchg](https://twitter.com/rauchg)) - [▲ZEIT](https://zeit.co)
- Leo Lamprecht ([@notquiteleo](https://twitter.com/notquiteleo)) - [▲ZEIT](https://zeit.co)
- Tony Kovanen ([@TonyKovanen](https://twitter.com/TonyKovanen)) - [▲ZEIT](https://zeit.co)
- Olli Vanhoja ([@OVanhoja](https://twitter.com/OVanhoja)) - [▲ZEIT](https://zeit.co)
- Naoyuki Kanezawa ([@nkzawa](https://twitter.com/nkzawa)) - [▲ZEIT](https://zeit.co)

40
bin/now-alias.js

@ -1,19 +1,19 @@
#!/usr/bin/env node
// Packages
import chalk from 'chalk'
import minimist from 'minimist'
import table from 'text-table'
import ms from 'ms'
const chalk = require('chalk')
const minimist = require('minimist')
const table = require('text-table')
const ms = require('ms')
// Ours
import strlen from '../lib/strlen'
import NowAlias from '../lib/alias'
import login from '../lib/login'
import * as cfg from '../lib/cfg'
import {error} from '../lib/error'
import toHost from '../lib/to-host'
import readMetaData from '../lib/read-metadata'
const strlen = require('../lib/strlen')
const NowAlias = require('../lib/alias')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const {error} = require('../lib/error')
const toHost = require('../lib/to-host')
const readMetaData = require('../lib/read-metadata')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -25,6 +25,7 @@ const argv = minimist(process.argv.slice(2), {
token: 't'
}
})
const subcommand = argv._[0]
// options
@ -120,8 +121,8 @@ async function run(token) {
const args = argv._.slice(1)
switch (subcommand) {
case 'list':
case 'ls':
case 'list': {
if (args.length !== 0) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now alias ls`')}`)
return exit(1)
@ -160,9 +161,9 @@ async function run(token) {
}
break
case 'remove':
}
case 'rm':
case 'remove': {
const _target = String(args[0])
if (!_target) {
const err = new Error('No alias id specified')
@ -201,17 +202,17 @@ async function run(token) {
}
break
}
case 'add':
case 'set':
case 'set': {
if (args.length !== 2) {
error(`Invalid number of arguments. Usage: ${chalk.cyan('`now alias set <id> <domain>`')}`)
return exit(1)
}
await alias.set(String(args[0]), String(args[1]))
break
default:
}
default: {
if (argv._.length === 0) {
await realias(alias)
break
@ -228,6 +229,7 @@ async function run(token) {
help()
exit(1)
}
}
}
alias.close()
@ -247,7 +249,7 @@ async function readConfirmation(alias, _alias) {
process.stdout.write('> The following alias will be removed permanently\n')
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(` ${chalk.bold.red('> Are you sure?')} ${chalk.gray('[yN] ')}`)
process.stdout.write(` ${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()

35
bin/now-certs.js

@ -1,21 +1,21 @@
#!/usr/bin/env node
// Native
import path from 'path'
const path = require('path')
// Packages
import chalk from 'chalk'
import table from 'text-table'
import minimist from 'minimist'
import fs from 'fs-promise'
import ms from 'ms'
const chalk = require('chalk')
const table = require('text-table')
const minimist = require('minimist')
const fs = require('fs-promise')
const ms = require('ms')
// Ours
import strlen from '../lib/strlen'
import * as cfg from '../lib/cfg'
import {handleError, error} from '../lib/error'
import NowCerts from '../lib/certs'
import login from '../lib/login'
const strlen = require('../lib/strlen')
const cfg = require('../lib/cfg')
const {handleError, error} = require('../lib/error')
const NowCerts = require('../lib/certs')
const login = require('../lib/login')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token', 'crt', 'key', 'ca'],
@ -108,7 +108,7 @@ if (argv.help || !subcommand) {
function formatExpirationDate(date) {
const diff = date - Date.now()
return diff < 0 ? chalk.gray(ms(new Date(-diff)) + ' ago') : chalk.gray('in ' + ms(new Date(diff)))
return diff < 0 ? chalk.gray(ms(-diff) + ' ago') : chalk.gray('in ' + ms(diff))
}
async function run(token) {
@ -132,7 +132,7 @@ async function run(token) {
list.sort((a, b) => {
return a.cn.localeCompare(b.cn)
})
const header = [['', 'id', 'cn', 'created', 'expiration'].map(s => chalk.dim(s))]
const header = [['', 'id', 'cn', 'created', 'expiration', 'auto-renew'].map(s => chalk.dim(s))]
const out = table(header.concat(list.map(cert => {
const cn = chalk.bold(cert.cn)
const time = chalk.gray(ms(cur - new Date(cert.created)) + ' ago')
@ -142,7 +142,8 @@ async function run(token) {
cert.uid ? cert.uid : 'unknown',
cn,
time,
expiration
expiration,
cert.autoRenew ? 'yes' : 'no'
]
})), {align: ['l', 'r', 'l', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
@ -172,6 +173,10 @@ async function run(token) {
} else { // Issue a standard certificate
cert = await certs.create(cn)
}
if (!cert) {
// Cert is undefined and "Cert is already issued" has been printed to stdout
return exit(1)
}
const elapsed = ms(new Date() - start)
console.log(`${chalk.cyan('> Success!')} Certificate entry ${chalk.bold(cn)} ${chalk.gray(`(${cert.uid})`)} created ${chalk.gray(`[${elapsed}]`)}`)
} else if (subcommand === 'renew') {
@ -260,7 +265,7 @@ function readConfirmation(cert, msg) {
process.stdout.write(`> ${msg}`)
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[yN] ')}`)
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()

215
bin/now-deploy.js

@ -1,38 +1,46 @@
#!/usr/bin/env node
// Native
import {resolve} from 'path'
const {resolve} = require('path')
// Packages
import Progress from 'progress'
import {stat} from 'fs-promise'
import bytes from 'bytes'
import chalk from 'chalk'
import minimist from 'minimist'
import ms from 'ms'
const Progress = require('progress')
const fs = require('fs-promise')
const bytes = require('bytes')
const chalk = require('chalk')
const minimist = require('minimist')
const ms = require('ms')
const publicSuffixList = require('psl')
const flatten = require('arr-flatten')
// Ours
import copy from '../lib/copy'
import login from '../lib/login'
import * as cfg from '../lib/cfg'
import {version} from '../../package'
import Logger from '../lib/build-logger'
import Now from '../lib'
import toHumanPath from '../lib/utils/to-human-path'
import promptOptions from '../lib/utils/prompt-options'
import {handleError, error} from '../lib/error'
import readMetaData from '../lib/read-metadata'
const copy = require('../lib/copy')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const {version} = require('../package')
const Logger = require('../lib/build-logger')
const Now = require('../lib')
const toHumanPath = require('../lib/utils/to-human-path')
const promptOptions = require('../lib/utils/prompt-options')
const {handleError, error} = require('../lib/error')
const {fromGit, isRepoPath, gitPathParts} = require('../lib/git')
const readMetaData = require('../lib/read-metadata')
const checkPath = require('../lib/utils/check-path')
const NowAlias = require('../lib/alias')
const argv = minimist(process.argv.slice(2), {
string: [
'config',
'token'
'token',
'name',
'alias'
],
boolean: [
'help',
'version',
'debug',
'force',
'links',
'login',
'no-clipboard',
'forward-npm',
@ -49,10 +57,13 @@ const argv = minimist(process.argv.slice(2), {
force: 'f',
token: 't',
forceSync: 'F',
links: 'l',
login: 'L',
public: 'p',
'no-clipboard': 'C',
'forward-npm': 'N'
'forward-npm': 'N',
name: 'n',
alias: 'a'
}
})
@ -69,21 +80,25 @@ const help = () => {
domains [name] Manages your domain names
certs [cmd] Manages your SSL certificates
secrets [name] Manages your secret environment variables
dns [name] Manages your DNS records
help [cmd] Displays complete help for [cmd]
${chalk.dim('Options:')}
-h, --help Output usage information
-v, --version Output the version number
-n, --name Set the name of the deployment
-c ${chalk.underline('FILE')}, --config=${chalk.underline('FILE')} Config file
-d, --debug Debug mode [off]
-f, --force Force a new deployment even if nothing has changed
-t ${chalk.underline('TOKEN')}, --token=${chalk.underline('TOKEN')} Login token
-L, --login Configure login
-l, --links Copy symlinks without resolving their target
-p, --public Deployment is public (${chalk.dim('`/_src`')} is exposed) [on for oss, off for premium]
-e, --env Include an env var (e.g.: ${chalk.dim('`-e KEY=value`')}). Can appear many times.
-C, --no-clipboard Do not attempt to copy URL to clipboard
-N, --forward-npm Forward login information to install private NPM modules
-a, --alias Reassign an existing alias to the deployment
${chalk.dim('Enforcable Types (when both package.json and Dockerfile exist):')}
@ -101,19 +116,15 @@ const help = () => {
${chalk.cyan('$ now /usr/src/project')}
${chalk.gray('–')} Lists all deployments with their IDs
${chalk.gray('–')} Deploys a GitHub repository
${chalk.cyan('$ now ls')}
${chalk.cyan('$ now user/repo#ref')}
${chalk.gray('–')} Associates deployment ${chalk.dim('`deploymentId`')} with ${chalk.dim('`custom-domain.com`')}
${chalk.gray('–')} Deploys a GitHub, GitLab or Bitbucket repo using its URL
${chalk.cyan('$ now alias deploymentId custom-domain.com')}
${chalk.cyan('$ now https://gitlab.com/user/repo')}
${chalk.gray('–')} Stores a secret
${chalk.cyan('$ now secret add mysql-password 123456')}
${chalk.gray('–')} Deploys with ENV vars (using the ${chalk.dim('`mysql-password`')} secret stored above)
${chalk.gray('–')} Deploys with ENV vars
${chalk.cyan('$ now -e NODE_ENV=production -e MYSQL_PASSWORD=@mysql-password')}
@ -133,6 +144,9 @@ if (path) {
path = process.cwd()
}
// If the current deployment is a repo
const gitRepo = {}
const exit = code => {
// we give stdout some time to flush out
// because there's a node bug where
@ -142,21 +156,32 @@ const exit = code => {
}
// options
let forceNew = argv.force
const debug = argv.debug
const clipboard = !argv['no-clipboard']
const forwardNpm = argv['forward-npm']
const forceNew = argv.force
const forceSync = argv.forceSync
const shouldLogin = argv.login
const followSymlinks = !argv.links
const wantsPublic = argv.public
const deploymentName = argv.name || false
const apiUrl = argv.url || 'https://api.zeit.co'
const isTTY = process.stdout.isTTY
const quiet = !isTTY
const autoAliases = argv.alias ? flatten([argv.alias]) : []
if (argv.config) {
cfg.setConfigFile(argv.config)
}
// Create a new deployment if user changed
// the name or made _src public.
// This should just work fine because it doesn't
// force a new sync, it just forces a new deployment.
if (deploymentName || wantsPublic) {
forceNew = true
}
const config = cfg.read()
const alwaysForwardNpm = config.forwardNpm
@ -192,16 +217,62 @@ if (argv.h || argv.help) {
async function sync(token) {
const start = Date.now()
const rawPath = argv._[0]
if (!quiet) {
console.log(`> Deploying ${chalk.bold(toHumanPath(path))}`)
const stopDeployment = msg => {
error(msg)
process.exit(1)
}
const isValidRepo = isRepoPath(rawPath)
try {
await stat(path)
await fs.stat(path)
} catch (err) {
error(`Could not read directory ${chalk.bold(path)}`)
process.exit(1)
let repo
if (isValidRepo && isValidRepo !== 'no-valid-url') {
const gitParts = gitPathParts(rawPath)
Object.assign(gitRepo, gitParts)
const searchMessage = setTimeout(() => {
console.log(`> Didn't find directory. Searching on ${gitRepo.type}...`)
}, 500)
try {
repo = await fromGit(rawPath, debug)
} catch (err) {}
clearTimeout(searchMessage)
}
if (repo) {
// Tell now which directory to deploy
path = repo.path
// Set global variable for deleting tmp dir later
// once the deployment has finished
Object.assign(gitRepo, repo)
} else if (isValidRepo === 'no-valid-url') {
stopDeployment(`This URL is neither a valid repository from GitHub, nor from GitLab.`)
} else if (isValidRepo) {
const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : ''
stopDeployment(`There's no repository named "${chalk.bold(gitRepo.main)}" ${gitRef}on ${gitRepo.type}`)
} else {
stopDeployment(`Could not read directory ${chalk.bold(path)}`)
}
}
// Make sure that directory is not too big
await checkPath(path)
if (!quiet) {
if (gitRepo.main) {
const gitRef = gitRepo.ref ? ` at "${chalk.bold(gitRepo.ref)}" ` : ''
console.log(`> Deploying ${gitRepo.type} repository "${chalk.bold(gitRepo.main)}"` + gitRef)
} else {
console.log(`> Deploying ${chalk.bold(toHumanPath(path))}`)
}
}
let deploymentType
@ -227,7 +298,7 @@ async function sync(token) {
isStatic = true
} else {
try {
await stat(resolve(path, 'package.json'))
await fs.stat(resolve(path, 'package.json'))
} catch (err) {
hasPackage = true
}
@ -235,7 +306,7 @@ async function sync(token) {
[hasPackage, hasDockerfile] = await Promise.all([
await (async () => {
try {
await stat(resolve(path, 'package.json'))
await fs.stat(resolve(path, 'package.json'))
} catch (err) {
return false
}
@ -243,7 +314,7 @@ async function sync(token) {
})(),
await (async () => {
try {
await stat(resolve(path, 'Dockerfile'))
await fs.stat(resolve(path, 'Dockerfile'))
} catch (err) {
return false
}
@ -273,19 +344,19 @@ async function sync(token) {
}
} else if (hasPackage) {
if (debug) {
console.log('[debug] `package.json` found, assuming `deploymentType` = `npm`')
console.log('> [debug] `package.json` found, assuming `deploymentType` = `npm`')
}
deploymentType = 'npm'
} else if (hasDockerfile) {
if (debug) {
console.log('[debug] `Dockerfile` found, assuming `deploymentType` = `docker`')
console.log('> [debug] `Dockerfile` found, assuming `deploymentType` = `docker`')
}
deploymentType = 'docker'
} else {
if (debug) {
console.log('[debug] No manifest files found, assuming static deployment')
console.log('> [debug] No manifest files found, assuming static deployment')
}
isStatic = true
@ -294,6 +365,7 @@ async function sync(token) {
const {pkg: {now: pkgConfig = {}} = {}} = await readMetaData(path, {
deploymentType,
deploymentName,
isStatic,
quiet: true
})
@ -323,6 +395,7 @@ async function sync(token) {
error('Env key and value missing')
return process.exit(1)
}
const [key, ...rest] = kv.split('=')
let val
@ -344,7 +417,7 @@ async function sync(token) {
if ((key in process.env)) {
console.log(`> Reading ${chalk.bold(`"${chalk.bold(key)}"`)} from your env (as no value was specified)`)
// escape value if it begins with @
val = process.env[key].replace(/^\@/, '\\@')
val = process.env[key].replace(/^@/, '\\@')
} else {
error(`No value specified for env ${chalk.bold(`"${chalk.bold(key)}"`)} and it was not found in your env.`)
return process.exit(1)
@ -390,6 +463,8 @@ async function sync(token) {
await now.create(path, {
env,
deploymentType,
deploymentName,
followSymlinks,
forceNew,
forceSync,
forwardNpm: alwaysForwardNpm || forwardNpm,
@ -437,7 +512,7 @@ async function sync(token) {
now.close()
// show build logs
printLogs(now.host)
printLogs(now.host, token)
}
if (now.syncAmount) {
@ -474,17 +549,67 @@ async function sync(token) {
now.close()
// show build logs
printLogs(now.host)
printLogs(now.host, token)
}
}
function printLogs(host) {
const assignAlias = async (autoAlias, token, deployment) => {
const type = publicSuffixList.isValid(autoAlias) ? 'alias' : 'uid'
const aliases = new NowAlias(apiUrl, token, {debug})
const list = await aliases.ls()
let related
// Check if alias even exists
for (const alias of list) {
if (alias[type] === autoAlias) {
related = alias
break
}
}
// If alias doesn't exist
if (!related) {
// Check if the uid was actually an alias
if (type === 'uid') {
return assignAlias(`${autoAlias}.now.sh`, token, deployment)
}
// If not, throw an error
const withID = type === 'uid' ? 'with ID ' : ''
error(`Alias ${withID}"${autoAlias}" doesn't exist`)
return
}
console.log(`> Assigning alias ${chalk.bold.underline(related.alias)} to deployment...`)
// Assign alias
await aliases.set(String(deployment), String(related.alias))
}
function printLogs(host, token) {
// log build
const logger = new Logger(host, {debug, quiet})
logger.on('close', () => {
logger.on('close', async () => {
for (const autoAlias of autoAliases) {
await assignAlias(autoAlias, token, host)
}
if (!quiet) {
console.log(`${chalk.cyan('> Deployment complete!')}`)
}
if (gitRepo && gitRepo.cleanup) {
// Delete temporary directory that contains repository
gitRepo.cleanup()
if (debug) {
console.log(`> [debug] Removed temporary repo directory`)
}
}
process.exit(0)
})
}

25
bin/now-dns.js

@ -1,18 +1,18 @@
#!/usr/bin/env node
// Packages
import chalk from 'chalk'
import minimist from 'minimist'
import ms from 'ms'
import table from 'text-table'
const chalk = require('chalk')
const minimist = require('minimist')
const ms = require('ms')
const table = require('text-table')
// Ours
import * as cfg from '../lib/cfg'
import DomainRecords from '../lib/domain-records'
import indent from '../lib/indent'
import login from '../lib/login'
import strlen from '../lib/strlen'
import {handleError, error} from '../lib/error'
const cfg = require('../lib/cfg')
const DomainRecords = require('../lib/domain-records')
const indent = require('../lib/indent')
const login = require('../lib/login')
const strlen = require('../lib/strlen')
const {handleError, error} = require('../lib/error')
const argv = minimist(process.argv.slice(2), {
string: ['config'],
@ -24,13 +24,14 @@ const argv = minimist(process.argv.slice(2), {
token: 't'
}
})
const subcommand = argv._[0]
// options
const help = () => {
console.log(`
${chalk.bold('𝚫 now dns ls')} [domain]
${chalk.bold('𝚫 now dns add')} <domain> <name> <A | AAAA | ALIAS | CNAME | MX> <value> [mx_priority]
${chalk.bold('𝚫 now dns add')} <domain> <name> <A | AAAA | ALIAS | CNAME | MX | TXT> <value> [mx_priority]
${chalk.bold('𝚫 now dns rm')} <id>
${chalk.dim('Options:')}
@ -192,7 +193,7 @@ function readConfirmation(record, msg) {
process.stdout.write(`> ${msg}`)
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[yN] ')}`)
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()

68
bin/now-domains.js

@ -1,30 +1,32 @@
#!/usr/bin/env node
// Packages
import chalk from 'chalk'
import minimist from 'minimist'
import table from 'text-table'
import ms from 'ms'
const chalk = require('chalk')
const minimist = require('minimist')
const table = require('text-table')
const ms = require('ms')
// Ours
import login from '../lib/login'
import * as cfg from '../lib/cfg'
import {error} from '../lib/error'
import toHost from '../lib/to-host'
import strlen from '../lib/strlen'
import NowDomains from '../lib/domains'
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const {error} = require('../lib/error')
const toHost = require('../lib/to-host')
const strlen = require('../lib/strlen')
const NowDomains = require('../lib/domains')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug', 'force'],
boolean: ['help', 'debug', 'external', 'force'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
external: 'e',
force: 'f',
token: 't'
}
})
const subcommand = argv._[0]
// options
@ -37,6 +39,7 @@ const help = () => {
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-d, --debug Debug mode [off]
-e, --external Use external DNS server
-f, --force Skip DNS verification
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
@ -77,6 +80,18 @@ const help = () => {
${chalk.cyan('$ now domain rm domainId')}
To get the list of domain ids, use ${chalk.dim('`now domains ls`')}.
${chalk.gray('–')} Adding and verifying a domain name using zeit.world nameservers:
${chalk.cyan('$ now domain add my-app.com')}
The command will tell you if the domain was verified succesfully. In case the domain was not verified succesfully you should retry adding the domain after some time.
${chalk.gray('–')} Adding and verifying a domain name using an external nameserver:
${chalk.cyan('$ now domain add -e my-app.com')}
and follow the verification instructions if requested. Finally, rerun the same command after completing the verification step.
`)
}
@ -127,7 +142,7 @@ async function run(token) {
switch (subcommand) {
case 'ls':
case 'list':
case 'list': {
if (args.length !== 0) {
error('Invalid number of arguments')
return exit(1)
@ -137,7 +152,7 @@ async function run(token) {
const domains = await domain.ls()
domains.sort((a, b) => new Date(b.created) - new Date(a.created))
const current = new Date()
const header = [['', 'id', 'dns', 'url', 'created'].map(s => chalk.dim(s))]
const header = [['', 'id', 'dns', 'url', 'verified', 'created'].map(s => chalk.dim(s))]
const out = domains.length === 0 ? null : table(header.concat(domains.map(domain => {
const ns = domain.isExternal ? 'external' : 'zeit.world'
const url = chalk.underline(`https://${domain.name}`)
@ -147,9 +162,10 @@ async function run(token) {
domain.uid,
ns,
url,
domain.verified,
time
]
})), {align: ['l', 'r', 'l', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
})), {align: ['l', 'r', 'l', 'l', 'l', 'l'], hsep: ' '.repeat(2), stringLength: strlen})
const elapsed_ = ms(new Date() - start_)
console.log(`> ${domains.length} domain${domains.length === 1 ? '' : 's'} found ${chalk.gray(`[${elapsed_}]`)}`)
@ -159,9 +175,9 @@ async function run(token) {
}
break
}
case 'rm':
case 'remove':
case 'remove': {
if (args.length !== 1) {
error('Invalid number of arguments')
return exit(1)
@ -199,9 +215,9 @@ async function run(token) {
exit(1)
}
break
}
case 'add':
case 'set':
case 'set': {
if (args.length !== 1) {
error('Invalid number of arguments')
return exit(1)
@ -209,15 +225,21 @@ async function run(token) {
const start = new Date()
const name = String(args[0])
const {uid, created} = await domain.add(name, argv.force)
const {uid, code, verified, verifyToken, created} = await domain.add(name, argv.force, argv.external)
const elapsed = ms(new Date() - start)
if (created) {
if (created && verified) {
console.log(`${chalk.cyan('> Success!')} Domain ${chalk.bold(chalk.underline(name))} ${chalk.dim(`(${uid})`)} added [${elapsed}]`)
} else {
} else if (verified) {
console.log(`${chalk.cyan('> Success!')} Domain ${chalk.bold(chalk.underline(name))} ${chalk.dim(`(${uid})`)} verified [${elapsed}]`)
} else if (verifyToken) {
console.log(`> Verification required: Please add the following TXT record on the external DNS server: _now.${name}: ${verifyToken}`)
} else if (code === 'not_modified') {
console.log(`${chalk.cyan('> Success!')} Domain ${chalk.bold(chalk.underline(name))} ${chalk.dim(`(${uid})`)} already exists [${elapsed}]`)
} else {
console.log('> Verification required: Please rerun this command after some time')
}
break
}
default:
error('Please specify a valid subcommand: ls | add | rm')
help()
@ -244,7 +266,7 @@ async function readConfirmation(domain, _domain) {
`will be removed. Run ${chalk.dim('`now alias ls`')} to list.\n`)
}
process.stdout.write(` ${chalk.bold.red('> Are you sure?')} ${chalk.gray('[yN] ')}`)
process.stdout.write(` ${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()

24
bin/now-list.js

@ -1,19 +1,19 @@
#!/usr/bin/env node
// Packages
import fs from 'fs-promise'
import minimist from 'minimist'
import chalk from 'chalk'
import table from 'text-table'
import ms from 'ms'
const fs = require('fs-promise')
const minimist = require('minimist')
const chalk = require('chalk')
const table = require('text-table')
const ms = require('ms')
// Ours
import strlen from '../lib/strlen'
import indent from '../lib/indent'
import Now from '../lib'
import login from '../lib/login'
import * as cfg from '../lib/cfg'
import {handleError, error} from '../lib/error'
const strlen = require('../lib/strlen')
const indent = require('../lib/indent')
const Now = require('../lib')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const {handleError, error} = require('../lib/error')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -108,7 +108,7 @@ async function list(token) {
const text = sorted.map(([name, deps]) => {
const t = table(deps.map(({uid, url, created}) => {
const _url = chalk.underline(`https://${url}`)
const _url = url ? chalk.underline(`https://${url}`) : 'incomplete'
const time = chalk.gray(ms(current - created) + ' ago')
return [uid, _url, time]
}), {align: ['l', 'r', 'l'], hsep: ' '.repeat(6), stringLength: strlen})

40
bin/now-remove.js

@ -1,25 +1,26 @@
#!/usr/bin/env node
// Packages
import minimist from 'minimist'
import chalk from 'chalk'
import ms from 'ms'
import table from 'text-table'
const minimist = require('minimist')
const chalk = require('chalk')
const ms = require('ms')
const table = require('text-table')
// Ours
import Now from '../lib'
import login from '../lib/login'
import * as cfg from '../lib/cfg'
import {handleError, error} from '../lib/error'
const Now = require('../lib')
const login = require('../lib/login')
const cfg = require('../lib/cfg')
const {handleError, error} = require('../lib/error')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug', 'hard'],
boolean: ['help', 'debug', 'hard', 'yes'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
token: 't',
yes: 'y'
}
})
@ -36,6 +37,7 @@ const help = () => {
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
-y, --yes Skip confirmation
${chalk.dim('Examples:')}
@ -64,6 +66,7 @@ if (argv.help || ids.length === 0) {
const debug = argv.debug
const apiUrl = argv.url || 'https://api.zeit.co'
const hard = argv.hard || false
const skipConfirmation = argv.yes || false
if (argv.config) {
cfg.setConfigFile(argv.config)
@ -78,7 +81,7 @@ function readConfirmation(matches) {
const tbl = table(
matches.map(depl => {
const time = chalk.gray(ms(new Date() - depl.created) + ' ago')
const url = chalk.underline(`https://${depl.url}`)
const url = depl.url ? chalk.underline(`https://${depl.url}`) : ''
return [depl.uid, url, time]
}),
{align: ['l', 'r', 'l'], hsep: ' '.repeat(6)}
@ -94,7 +97,7 @@ function readConfirmation(matches) {
}
}
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[yN] ')}`)
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()
@ -125,7 +128,7 @@ async function remove(token) {
const matches = deployments.filter(d => {
return ids.find(id => {
// `url` should match the hostname of the deployment
let u = id.replace(/^https\:\/\//i, '')
let u = id.replace(/^https:\/\//i, '')
if (u.indexOf('.') === -1) {
// `.now.sh` domain is implied if just the subdomain is given
@ -147,10 +150,13 @@ async function remove(token) {
}
try {
const confirmation = (await readConfirmation(matches)).toLowerCase()
if (confirmation !== 'y' && confirmation !== 'yes') {
console.log('\n> Aborted')
process.exit(0)
if (!skipConfirmation) {
const confirmation = (await readConfirmation(matches)).toLowerCase()
if (confirmation !== 'y' && confirmation !== 'yes') {
console.log('\n> Aborted')
process.exit(0)
}
}
const start = new Date()

20
bin/now-secrets.js

@ -1,17 +1,17 @@
#!/usr/bin/env node
// Packages
import chalk from 'chalk'
import table from 'text-table'
import minimist from 'minimist'
import ms from 'ms'
const chalk = require('chalk')
const table = require('text-table')
const minimist = require('minimist')
const ms = require('ms')
// Ours
import strlen from '../lib/strlen'
import * as cfg from '../lib/cfg'
import {handleError, error} from '../lib/error'
import NowSecrets from '../lib/secrets'
import login from '../lib/login'
const strlen = require('../lib/strlen')
const cfg = require('../lib/cfg')
const {handleError, error} = require('../lib/error')
const NowSecrets = require('../lib/secrets')
const login = require('../lib/login')
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
@ -229,7 +229,7 @@ function readConfirmation(secret) {
process.stdout.write('> The following secret will be removed permanently\n')
process.stdout.write(' ' + tbl + '\n')
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[yN] ')}`)
process.stdout.write(`${chalk.bold.red('> Are you sure?')} ${chalk.gray('[y/N] ')}`)
process.stdin.on('data', d => {
process.stdin.pause()

80
bin/now.js

@ -1,34 +1,38 @@
#!/usr/bin/env node
// Native
import {resolve} from 'path'
const {resolve} = require('path')
// Packages
import minimist from 'minimist'
import {spawn} from 'cross-spawn'
const nodeVersion = require('node-version')
const updateNotifier = require('update-notifier')
// Ours
import checkUpdate from '../lib/check-update'
const {error} = require('../lib/error')
const pkg = require('../package')
const argv = minimist(process.argv.slice(2))
// options
const debug = argv.debug || argv.d
// Support for keywords "async" and "await"
require('async-to-gen/register')({
excludes: null
})
// auto-update checking
const update = checkUpdate({debug})
// Throw an error if node version is too low
if (nodeVersion.major < 6) {
error('Now requires at least version 6 of Node. Please upgrade!')
process.exit(1)
}
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)
// Only check for updates in the npm version
if (!process.pkg) {
updateNotifier({pkg}).notify()
}
// This command will be run if no other sub command is specified
const defaultCommand = 'deploy'
const commands = new Set([
defaultCommand,
'help',
'list',
'ls',
'rm',
@ -55,37 +59,33 @@ const aliases = new Map([
['secret', 'secrets']
])
let cmd = argv._[0]
let args = []
let cmd = defaultCommand
const args = process.argv.slice(2)
const index = args.findIndex(a => commands.has(a))
if (cmd === 'help') {
cmd = argv._[1]
if (index > -1) {
cmd = args[index]
args.splice(index, 1)
if (!commands.has(cmd)) {
cmd = defaultCommand
}
if (cmd === 'help') {
if (index < args.length && commands.has(args[index])) {
cmd = args[index]
args.splice(index, 1)
} else {
cmd = defaultCommand
}
args.push('--help')
}
args.unshift('--help')
}
if (commands.has(cmd)) {
cmd = aliases.get(cmd) || cmd
args = args.concat(process.argv.slice(3))
} else {
cmd = defaultCommand
args = args.concat(process.argv.slice(2))
}
let bin = resolve(__dirname, 'now-' + cmd)
if (process.pkg) {
args.unshift('--entrypoint', bin)
bin = process.execPath
}
const bin = resolve(__dirname, 'now-' + cmd + '.js')
const proc = spawn(bin, args, {
stdio: 'inherit',
customFds: [0, 1, 2]
})
// Prepare process.argv for subcommand
process.argv = process.argv.slice(0, 2).concat(args)
proc.on('close', code => exit(code))
proc.on('error', () => exit(1))
// Load sub command
// With custom parameter to make "pkg" happy
require(bin, 'may-exclude')

31
gulpfile.babel.js

@ -1,31 +0,0 @@
// Packages
import gulp from 'gulp'
import del from 'del'
import babel from 'gulp-babel'
import help from 'gulp-task-listing'
import {crop as cropExt} from 'gulp-ext'
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')))
gulp.task('compile-bin', () =>
gulp.src('bin/*')
.pipe(babel())
.pipe(cropExt())
.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', ['watch-lib', 'watch-bin'])
gulp.task('default', ['compile', 'watch'])

8
lib/agent.js

@ -1,7 +1,7 @@
// Packages
import {parse} from 'url'
import http2 from 'spdy'
import fetch from 'node-fetch'
const {parse} = require('url')
const http2 = require('spdy')
const fetch = require('node-fetch')
/**
* Returns a `fetch` version with a similar
@ -14,7 +14,7 @@ import fetch from 'node-fetch'
* @return {Function} fetch
*/
export default class Agent {
module.exports = class Agent {
constructor(url, {tls = true, debug} = {}) {
this._url = url
const parsed = parse(url)

98
lib/alias.js

@ -1,20 +1,31 @@
// Packages
import chalk from 'chalk'
const publicSuffixList = require('psl')
const minimist = require('minimist')
const chalk = require('chalk')
// 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 copy = require('./copy')
const toHost = require('./to-host')
const resolve4 = require('./dns')
const isZeitWorld = require('./is-zeit-world')
const {DOMAIN_VERIFICATION_ERROR} = require('./errors')
const Now = require('./')
const argv = minimist(process.argv.slice(2), {
boolean: ['no-clipboard'],
alias: {'no-clipboard': 'C'}
})
const isTTY = process.stdout.isTTY
const clipboard = !argv['no-clipboard']
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/
export default class Alias extends Now {
module.exports = class Alias extends Now {
async ls(deployment) {
if (deployment) {
const target = await this.findDeployment(deployment)
if (!target) {
const err = new Error(`Aliases not found by "${deployment}". Run ${chalk.dim('`now alias ls`')} to see your aliases.`)
err.userError = true
@ -63,6 +74,7 @@ export default class Alias extends Now {
if (this._debug) {
console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`)
}
return true
}
@ -71,6 +83,7 @@ export default class Alias extends Now {
if (this._debug) {
console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`)
}
return true
}
@ -118,21 +131,42 @@ export default class Alias extends Now {
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)
const _domain = publicSuffixList.parse(alias).domain
const _domainInfo = await this.getDomain(_domain)
const domainInfo = _domainInfo && !_domainInfo.error ? _domainInfo : undefined
const {domain, nameservers} = domainInfo ? {domain: _domain} : await this.getNameservers(alias)
const usingZeitWorld = domainInfo ? !domainInfo.isExternal : isZeitWorld(nameservers)
let skipDNSVerification = false
if (this._debug) {
console.log(`> [debug] Found domain ${domain} and nameservers ${nameservers}`)
if (domainInfo) {
console.log(`> [debug] Found domain ${domain} with verified:${domainInfo.verified}`)
} else {
console.log(`> [debug] Found domain ${domain} and nameservers ${nameservers}`)
}
}
if (!usingZeitWorld && domainInfo) {
if (domainInfo.verified) {
skipDNSVerification = true
} else if (domainInfo.uid) {
const e = new Error(`> The domain ${domain} is already registered with now but additional verification is needed, please refer to 'now domain --help'.`)
e.userError = true
throw e
}
}
try {
await this.verifyOwnership(alias)
if (!skipDNSVerification) {
await this.verifyOwnership(alias)
}
} catch (err) {
if (err.userError) {
// a user error would imply that verification failed
// in which case we attempt to correct the dns
// configuration (if we can!)
try {
if (isZeitWorld(nameservers)) {
if (usingZeitWorld) {
console.log(`> Detected ${chalk.bold(chalk.underline('zeit.world'))} nameservers! Configuring records.`)
const record = alias.substr(0, alias.length - domain.length)
@ -178,12 +212,19 @@ export default class Alias extends Now {
}
}
if (!isZeitWorld(nameservers)) {
if (!usingZeitWorld && !skipDNSVerification) {
if (this._debug) {
console.log(`> [debug] Trying to register a non-ZeitWorld domain ${domain} for the current user`)
}
await this.setupDomain(domain, {isExternal: true})
const {uid, verified, verifyToken, created} = await this.setupDomain(domain, {isExternal: true})
if (created && verified) {
console.log(`${chalk.cyan('> Success!')} Domain ${chalk.bold(chalk.underline(domain))} ${chalk.dim(`(${uid})`)} added`)
} else if (verifyToken) {
const e = new Error(`> Verification required: Please add the following TXT record on the external DNS server: _now.${domain}: ${verifyToken}`)
e.userError = true
throw e
}
}
console.log(`> Verification ${chalk.bold('OK')}!`)
@ -196,9 +237,27 @@ export default class Alias extends Now {
this._agent.close()
this._agent._initAgent()
const {created, uid} = await this.createAlias(depl, alias)
const newAlias = await this.createAlias(depl, alias)
if (!newAlias) {
throw new Error(`Unexpected error occurred while setting up alias: ${JSON.stringify(newAlias)}`)
}
const {created, uid} = newAlias
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})`)}`)
const pretty = `https://${alias}`
const output = `${chalk.cyan('> Success!')} Alias created ${chalk.dim(`(${uid})`)}:\n${chalk.bold(chalk.underline(pretty))} now points to ${chalk.bold(`https://${depl.url}`)} ${chalk.dim(`(${depl.uid})`)}`
if (isTTY && clipboard) {
let append
try {
await copy(pretty)
append = '[copied to clipboard]'
} catch (err) {
append = ''
} finally {
console.log(`${output} ${append}`)
}
} else {
console.log(output)
}
} else {
console.log(`${chalk.cyan('> Success!')} Alias already exists ${chalk.dim(`(${uid})`)}.`)
}
@ -354,9 +413,12 @@ export default class Alias extends Now {
if (this._debug) {
console.log(`> [debug] No records found for "${domain}"`)
}
} else {
throw err
const err = new Error(DOMAIN_VERIFICATION_ERROR)
err.userError = true
return bail(err)
}
throw err
}
if (ips.length <= 0) {

10
lib/build-logger.js

@ -1,10 +1,10 @@
// Native
import EventEmitter from 'events'
const EventEmitter = require('events')
// Packages
import ansi from 'ansi-escapes'
import io from 'socket.io-client'
import chalk from 'chalk'
const ansi = require('ansi-escapes')
const io = require('socket.io-client')
const chalk = require('chalk')
class Lines {
constructor(maxLines = 100) {
@ -30,7 +30,7 @@ class Lines {
}
}
export default class Logger extends EventEmitter {
module.exports = class Logger extends EventEmitter {
constructor(host, {debug = false, quiet = false} = {}) {
super()
this.host = host

4
lib/certs.js

@ -1,7 +1,7 @@
// Ours
import Now from '../lib'
const Now = require('../lib')
export default class Certs extends Now {
module.exports = class Certs extends Now {
ls() {
return this.retry(async (bail, attempt) => {

18
lib/cfg.js

@ -1,17 +1,17 @@
// Native
import {homedir} from 'os'
import path from 'path'
const {homedir} = require('os')
const path = require('path')
// Packages
import fs from 'fs-promise'
const fs = require('fs-promise')
let file = process.env.NOW_JSON ? path.resolve(process.env.NOW_JSON) : path.resolve(homedir(), '.now.json')
export function setConfigFile(nowjson) {
function setConfigFile(nowjson) {
file = path.resolve(nowjson)
}
export function read() {
function read() {
let existing = null
try {
existing = fs.readFileSync(file, 'utf8')
@ -28,7 +28,13 @@ export function read() {
* @param {Object} data
*/
export function merge(data) {
function merge(data) {
const cfg = Object.assign({}, read(), data)
fs.writeFileSync(file, JSON.stringify(cfg, null, 2))
}
module.exports = {
setConfigFile,
read,
merge
}

95
lib/check-update.js

@ -1,95 +0,0 @@
// Packages
import ms from 'ms'
import fetch from 'node-fetch'
import chalk from 'chalk'
import compare from 'semver-compare'
// 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 => resolve())
/**
* Configures auto updates.
* Sets up a `exit` listener to report them.
*/
export default function checkUpdate(opts = {}) {
if (!isTTY) {
// don't attempt to check for updates
// if the user is piping or redirecting
return resolvedPromise
}
let updateData
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.on('exit', () => {
if (updateData) {
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')
}
})
return update
}
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}.`)
}
resolve(false)
return
}
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}`)
}
resolve({
latest,
current,
at: new Date(data.time[latest])
})
} else {
if (debug) {
console.log(`> [debug] Up to date (${pkg.version}).`)
}
resolve(false)
}
}, () => resolve(false))
}, () => resolve(false))
})
}

6
lib/copy.js

@ -1,7 +1,7 @@
// Packages
import {copy as _copy} from 'copy-paste'
const {copy: _copy} = require('copy-paste')
export default function copy(text) {
function copy(text) {
return new Promise((resolve, reject) => {
_copy(text, err => {
if (err) {
@ -12,3 +12,5 @@ export default function copy(text) {
})
})
}
module.exports = copy

5
lib/dns.js

@ -1,7 +1,7 @@
// Packages
import dns from 'dns'
const dns = require('dns')
export default function resolve4(host) {
function resolve4(host) {
return new Promise((resolve, reject) => {
return dns.resolve4(host, (err, answer) => {
if (err) {
@ -12,3 +12,4 @@ export default function resolve4(host) {
})
})
}
module.exports = resolve4

4
lib/domain-records.js

@ -1,7 +1,7 @@
// Ours
import Now from '../lib'
const Now = require('../lib')
export default class DomainRecords extends Now {
module.exports = class DomainRecords extends Now {
async getRecord(id) {
const all = (await this.ls()).entries()

16
lib/domains.js

@ -1,14 +1,14 @@
// Packages
import chalk from 'chalk'
const chalk = require('chalk')
// Ours
import Now from '../lib'
import isZeitWorld from './is-zeit-world'
import {DNS_VERIFICATION_ERROR} from './errors'
const Now = require('../lib')
const isZeitWorld = require('./is-zeit-world')
const {DNS_VERIFICATION_ERROR} = require('./errors')
const domainRegex = /^((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/
export default class Domains extends Now {
module.exports = class Domains extends Now {
async ls() {
return await this.listDomains()
@ -37,15 +37,15 @@ export default class Domains extends Now {
})
}
async add(domain, skipVerification) {
async add(domain, skipVerification, isExternal) {
if (!domainRegex.test(domain)) {
const err = new Error(`The supplied value ${chalk.bold(`"${domain}"`)} is not a valid domain.`)
err.userError = true
throw err
}
if (skipVerification) {
return this.setupDomain(domain)
if (skipVerification || isExternal) {
return this.setupDomain(domain, {isExternal})
}
let ns

13
lib/error.js

@ -1,8 +1,8 @@
// Packages
import ms from 'ms'
import chalk from 'chalk'
const ms = require('ms')
const chalk = require('chalk')
export function handleError(err) {
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) {
@ -22,6 +22,11 @@ export function handleError(err) {
}
}
export function error(err) {
function error(err) {
console.error(`> ${chalk.red('Error!')} ${err}`)
}
module.exports = {
handleError,
error
}

11
lib/errors.js

@ -1,12 +1,17 @@
// Packages
import chalk from 'chalk'
const chalk = require('chalk')
export const DNS_VERIFICATION_ERROR = `Please make sure that your nameservers point to ${chalk.underline('zeit.world')}.
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')}`
export const DOMAIN_VERIFICATION_ERROR = DNS_VERIFICATION_ERROR +
const DOMAIN_VERIFICATION_ERROR = DNS_VERIFICATION_ERROR +
`\n> Alternatively, ensure it resolves to ${chalk.underline('alias.zeit.co')} via ${chalk.dim('CNAME')} / ${chalk.dim('ALIAS')}.`
module.exports = {
DNS_VERIFICATION_ERROR,
DOMAIN_VERIFICATION_ERROR
}

59
lib/get-files.js

@ -1,15 +1,15 @@
// Native
import {resolve} from 'path'
const {resolve} = require('path')
// Packages
import flatten from 'arr-flatten'
import unique from 'array-unique'
import ignore from 'ignore'
import _glob from 'glob'
import {stat, readdir, readFile} from 'fs-promise'
const flatten = require('arr-flatten')
const unique = require('array-unique')
const ignore = require('ignore')
const _glob = require('glob')
const {stat, readdir, readFile} = require('fs-promise')
// Ours
import IGNORED from './ignored'
const IGNORED = require('./ignored')
/**
* Returns a list of files in the given
@ -24,14 +24,11 @@ import IGNORED from './ignored'
* @return {Array} comprehensive list of paths to sync
*/
export async function npm(path, pkg, {
async function npm(path, pkg, {
limit = null,
debug = false
} = {}) {
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 || ['.']
// always include the "main" file
@ -44,27 +41,38 @@ export async function npm(path, pkg, {
// compile list of ignored patterns and files
const npmIgnore = await maybeRead(resolve(path, '.npmignore'), null)
const gitIgnore = npmIgnore === null ?
await maybeRead(resolve(path, '.gitignore')) :
null
const filter = ignore().add(
IGNORED +
'\n' +
clearRelative(npmIgnore === null ? await maybeRead(resolve(path, '.gitignore')) : npmIgnore)
clearRelative(npmIgnore === null ? gitIgnore : npmIgnore)
).createFilter()
const prefixLength = path.length + 1
const accepts = function (file) {
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true
}
// the package.json `files` whitelist still
// honors npmignores: https://docs.npmjs.com/files/package.json#files
// but we don't ignore if the user is explicitly listing files
// under the now namespace, or using files in combination with gitignore
const overrideIgnores = (pkg.now && pkg.now.files) || (gitIgnore !== null && pkg.files)
const accepts = overrideIgnores ?
() => true :
file => {
const relativePath = file.substr(prefixLength)
if (relativePath === '') {
return true
}
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file)
const accepted = filter(relativePath)
if (!accepted && debug) {
console.log('> [debug] ignoring "%s"', file)
}
return accepted
}
return accepted
}
// locate files
if (debug) {
@ -118,7 +126,7 @@ const asAbsolute = function (path, parent) {
* @return {Array} comprehensive list of paths to sync
*/
export async function docker(path, {
async function docker(path, {
limit = null,
debug = false
} = {}) {
@ -269,3 +277,8 @@ const maybeRead = async function (path, default_ = '') {
const clearRelative = function (str) {
return str.replace(/(\n|^)\.\//g, '$1')
}
module.exports = {
npm,
docker
}

211
lib/git.js

@ -0,0 +1,211 @@
// Native
const path = require('path')
const url = require('url')
const childProcess = require('child_process')
// Packages
const fs = require('fs-promise')
const download = require('download')
const tmp = require('tmp-promise')
const isURL = require('is-url')
const cloneRepo = (parts, tmpDir) => new Promise((resolve, reject) => {
let host
switch (parts.type) {
case 'GitLab':
host = `gitlab.com`
break
case 'Bitbucket':
host = `bitbucket.org`
break
default:
host = `github.com`
}
const url = `https://${host}/${parts.main}`
const ref = parts.ref || (parts.type === 'Bitbucket' ? 'default' : 'master')
const cmd = `git clone ${url} --single-branch ${ref}`
childProcess.exec(cmd, {cwd: tmpDir.path}, (err, stdout) => {
if (err) {
reject(err)
}
resolve(stdout)
})
})
const renameRepoDir = async (pathParts, tmpDir) => {
const tmpContents = await fs.readdir(tmpDir.path)
const oldTemp = path.join(tmpDir.path, tmpContents[0])
const newTemp = path.join(tmpDir.path, pathParts.main.replace('/', '-'))
await fs.rename(oldTemp, newTemp)
tmpDir.path = newTemp
return tmpDir
}
const downloadRepo = async repoPath => {
const pathParts = gitPathParts(repoPath)
const tmpDir = await tmp.dir({
// We'll remove it manually once deployment is done
keep: true,
// Recursively remove directory when calling respective method
unsafeCleanup: true
})
let gitInstalled = true
try {
await cloneRepo(pathParts, tmpDir)
} catch (err) {
gitInstalled = false
}
if (gitInstalled) {
return await renameRepoDir(pathParts, tmpDir)
}
let url
switch (pathParts.type) {
case 'GitLab': {
const ref = pathParts.ref ? `?ref=${pathParts.ref}` : ''
url = `https://gitlab.com/${pathParts.main}/repository/archive.tar` + ref
break
}
case 'Bitbucket':
url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref || 'default'}.zip`
break
default:
url = `https://api.github.com/repos/${pathParts.main}/tarball/${pathParts.ref}`
}
try {
await download(url, tmpDir.path, {
extract: true
})
} catch (err) {
tmpDir.cleanup()
return false
}
return await renameRepoDir(pathParts, tmpDir)
}
const capitalizePlatform = name => {
const names = {
github: 'GitHub',
gitlab: 'GitLab',
bitbucket: 'Bitbucket'
}
return names[name]
}
const splittedURL = fullURL => {
const parsedURL = url.parse(fullURL)
const pathParts = parsedURL.path.split('/')
pathParts.shift()
// Set path to repo...
const main = pathParts[0] + '/' + pathParts[1]
// ...and then remove it from the parts
pathParts.splice(0, 2)
// Assign Git reference
let ref = pathParts.length >= 2 ? pathParts[1] : ''
// Firstly be sure that we haven know the ref type
if (pathParts[0]) {
// Then shorten the SHA of the commit
if (pathParts[0] === 'commit' || pathParts[0] === 'commits') {
ref = ref.substring(0, 7)
}
}
// We're deploying master by default,
// so there's no need to indicate it explicitly
if (ref === 'master') {
ref = ''
}
return {
main,
ref,
type: capitalizePlatform(parsedURL.host.split('.')[0])
}
}
const gitPathParts = main => {
let ref = ''
if (isURL(main)) {
return splittedURL(main)
}
if (main.split('/')[1].includes('#')) {
const parts = main.split('#')
ref = parts[1]
main = parts[0]
}
return {
main,
ref,
type: capitalizePlatform('github')
}
}
const isRepoPath = path => {
if (!path) {
return false
}
const allowedHosts = [
'github.com',
'gitlab.com',
'bitbucket.org'
]
if (isURL(path)) {
const urlParts = url.parse(path)
const slashSplitted = urlParts.path.split('/').filter(n => n)
const notBare = slashSplitted.length >= 2
if (allowedHosts.includes(urlParts.host) && notBare) {
return true
}
return 'no-valid-url'
}
return /[^\s\\]\/[^\s\\]/g.test(path)
}
const fromGit = async (path, debug) => {
let tmpDir = false
try {
tmpDir = await downloadRepo(path)
} catch (err) {
if (debug) {
console.log(`Could not download "${path}" repo from GitHub`)
}
}
return tmpDir
}
module.exports = {
gitPathParts,
isRepoPath,
fromGit
}

10
lib/hash.js

@ -1,9 +1,9 @@
// Native
import {createHash} from 'crypto'
import path from 'path'
const {createHash} = require('crypto')
const path = require('path')
// Packages
import {readFile} from 'fs-promise'
const {readFile} = require('fs-promise')
/**
* Computes hashes for the contents of each file given.
@ -12,7 +12,7 @@ import {readFile} from 'fs-promise'
* @return {Map}
*/
export default async function hashes(files, isStatic, pkg) {
async function hashes(files, isStatic, pkg) {
const map = new Map()
await Promise.all(files.map(async name => {
@ -49,3 +49,5 @@ function hash(buf) {
.update(buf)
.digest('hex')
}
module.exports = hashes

2
lib/ignored.js

@ -1,6 +1,6 @@
// base `.gitignore` to which we add entries
// supplied by the user
export default `.hg
module.exports = `.hg
.git
.gitmodules
.svn

4
lib/indent.js

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

113
lib/index.js

@ -1,23 +1,23 @@
// Native
import {homedir} from 'os'
import {resolve as resolvePath, join as joinPaths} from 'path'
import EventEmitter from 'events'
const {homedir} = require('os')
const {resolve: resolvePath, join: joinPaths} = require('path')
const EventEmitter = require('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'
const bytes = require('bytes')
const chalk = require('chalk')
const resumer = require('resumer')
const retry = require('async-retry').default
const splitArray = require('split-array')
const {parse: parseIni} = require('ini')
const {readFile, stat, lstat} = require('fs-promise')
// Ours
import {npm as getNpmFiles, docker as getDockerFiles} from './get-files'
import ua from './ua'
import hash from './hash'
import Agent from './agent'
import readMetaData from './read-metadata'
const {npm: getNpmFiles, docker: getDockerFiles} = require('./get-files')
const ua = require('./ua')
const hash = require('./hash')
const Agent = require('./agent')
const readMetaData = require('./read-metadata')
// how many concurrent HTTP/2 stream uploads
const MAX_CONCURRENT = 10
@ -26,7 +26,7 @@ const MAX_CONCURRENT = 10
const IS_WIN = /^win/.test(process.platform)
const SEP = IS_WIN ? '\\' : '/'
export default class Now extends EventEmitter {
module.exports = class Now extends EventEmitter {
constructor(url, token, {forceNew = false, debug = false}) {
super()
this._token = token
@ -40,10 +40,12 @@ export default class Now extends EventEmitter {
wantsPublic,
quiet = false,
env = {},
followSymlinks = true,
forceNew = false,
forceSync = false,
forwardNpm = false,
deploymentType = 'npm',
deploymentName,
isStatic = false
}) {
this._path = path
@ -53,6 +55,7 @@ export default class Now extends EventEmitter {
const {pkg, name, description} = await readMetaData(path, {
deploymentType,
deploymentName,
quiet,
isStatic
})
@ -122,6 +125,39 @@ export default class Now extends EventEmitter {
console.time('> [debug] /now/create')
}
// 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
const files = await Promise.all(Array.prototype.concat.apply([], await Promise.all((Array.from(this._files)).map(async ([sha, {data, names}]) => {
const statFn = followSymlinks ? stat : lstat
return await names.map(async name => {
let mode
const getMode = async () => {
const st = await statFn(name)
return st.mode
}
if (this._static) {
if (toRelative(name, this._path) === 'package.json') {
mode = 33261
} else {
mode = await getMode()
name = this.pathInsideContent(name)
}
} else {
mode = await getMode()
}
return {
sha,
size: data.length,
file: toRelative(name, this._path),
mode
}
})
}))))
const res = await this._fetch('/now/create', {
method: 'POST',
body: {
@ -133,21 +169,7 @@ export default class Now extends EventEmitter {
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 => {
if (this._static && toRelative(n, this._path) !== 'package.json') {
n = this.pathInsideContent(n)
}
return {
sha,
size: data.length,
file: toRelative(n, this._path)
}
})
})),
files,
engines
}
})
@ -208,7 +230,7 @@ export default class Now extends EventEmitter {
}
}
if (!quiet && deployment.nodeVersion) {
if (!quiet && deploymentType === 'npm' && deployment.nodeVersion) {
if (engines && engines.node) {
if (missingVersion) {
console.log(`> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)`)
@ -385,8 +407,24 @@ export default class Now extends EventEmitter {
})
}
async getDomain(domain) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /domains/${domain}`)
}
const res = await this._fetch(`/domains/${domain}`)
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /domains/${domain}`)
}
return await res.json()
})
}
getNameservers(domain) {
return new Promise(resolve => {
return new Promise((resolve, reject) => {
let fallback = false
this.retry(async (bail, attempt) => {
@ -427,6 +465,8 @@ export default class Now extends EventEmitter {
return ns.length
})
resolve(body)
}).catch(err => {
reject(err)
})
})
}
@ -447,8 +487,9 @@ export default class Now extends EventEmitter {
console.timeEnd(`> [debug] #${attempt} POST /domains`)
}
const body = await res.json()
if (res.status === 403) {
const body = await res.json()
const code = body.error.code
let err
@ -462,15 +503,13 @@ export default class Now extends EventEmitter {
return bail(err)
}
const body = await res.json()
// domain already exists
if (res.status === 409) {
if (this._debug) {
console.log('> [debug] Domain already exists (noop)')
}
return {uid: body.error.uid}
return {uid: body.error.uid, code: body.error.code}
}
if (res.status !== 200) {

4
lib/is-zeit-world.js

@ -20,8 +20,10 @@ const nameservers = new Set([
* by `resolveNs` from Node, assert that they're
* zeit.world's.
*/
export default function isZeitWorld(ns) {
function isZeitWorld(ns) {
return ns.length > 1 && ns.every(host => {
return nameservers.has(host)
})
}
module.exports = isZeitWorld

44
lib/login.js

@ -1,17 +1,18 @@
// Native
import os from 'os'
const os = require('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'
const {stringify: stringifyQuery} = require('querystring')
const chalk = require('chalk')
const fetch = require('node-fetch')
const {validate} = require('email-validator')
const readEmail = require('email-prompt')
const ora = require('ora')
// Ours
import pkg from '../../package'
import ua from './ua'
import * as cfg from './cfg'
const pkg = require('../package')
const ua = require('./ua')
const cfg = require('./cfg')
async function getVerificationData(url, email) {
const tokenName = `Now CLI ${os.platform()}-${os.arch()} ${pkg.version} (${os.hostname()})`
@ -54,7 +55,14 @@ function sleep(ms) {
}
async function register(url, {retryEmail = false} = {}) {
const email = await readEmail({invalid: retryEmail})
let email
try {
email = await readEmail({invalid: retryEmail})
} catch (err) {
process.stdout.write('\n')
throw err
}
process.stdout.write('\n')
if (!validate(email)) {
@ -62,13 +70,18 @@ async function register(url, {retryEmail = false} = {}) {
}
const {token, securityCode} = await getVerificationData(url, email)
console.log(`> Please follow the link sent to ${chalk.bold(email)} to log in.`)
if (securityCode) {
console.log(`> Verify that the provided security code in the email matches ${chalk.cyan(chalk.bold(securityCode))}.`)
}
process.stdout.write('> Waiting for confirmation..')
process.stdout.write('\n')
const spinner = ora({
text: 'Waiting for confirmation...',
color: 'black'
}).start()
let final
@ -78,16 +91,17 @@ async function register(url, {retryEmail = false} = {}) {
try {
final = await verify(url, email, token)
} catch (err) {}
process.stdout.write('.')
} while (!final)
spinner.text = 'Confirmed email address!'
spinner.stopAndPersist('✔')
process.stdout.write('\n')
return {email, token: final}
}
export default async function (url) {
module.exports = async function (url) {
const loginData = await register(url)
cfg.merge(loginData)
return loginData.token

48
lib/read-metadata.js

@ -1,20 +1,20 @@
import {basename, resolve as resolvePath} from 'path'
import chalk from 'chalk'
import {readFile} from 'fs-promise'
import {parse as parseDockerfile} from 'docker-file-parser'
const {basename, resolve: resolvePath} = require('path')
const chalk = require('chalk')
const {readFile} = require('fs-promise')
const {parse: parseDockerfile} = require('docker-file-parser')
const listPackage = {
version: '0.0.0',
scripts: {
start: 'list ./content'
start: 'serve ./content'
},
dependencies: {
list: 'latest'
serve: '^2.4.1'
}
}
export default async function (path, {
module.exports = async function (path, {
deploymentType = 'npm',
deploymentName,
quiet = false,
isStatic = false
}) {
@ -44,13 +44,15 @@ export default async function (path, {
throw e
}
if (typeof pkg.name === 'string') {
name = pkg.name
} else {
name = basename(path)
if (!deploymentName) {
if (typeof pkg.name === 'string') {
name = pkg.name
} else {
name = basename(path)
if (!quiet && !isStatic) {
console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`)
if (!quiet && !isStatic) {
console.log(`> No \`name\` in \`package.json\`, using ${chalk.bold(name)}`)
}
}
}
@ -106,19 +108,25 @@ export default async function (path, {
}
})
if (labels.name) {
name = labels.name
} else {
name = basename(path)
if (!deploymentName) {
if (labels.name) {
name = labels.name
} else {
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)}`)
}
}
}
description = labels.description
}
if (deploymentName) {
name = deploymentName
}
return {
name,
description,

4
lib/secrets.js

@ -1,7 +1,7 @@
// Ours
import Now from '../lib'
const Now = require('../lib')
export default class Secrets extends Now {
module.exports = class Secrets extends Now {
ls() {
return this.listSecrets()
}

4
lib/strlen.js

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

4
lib/test.js

@ -1,8 +1,8 @@
// Native
import {resolve} from 'path'
const {resolve} = require('path')
// Ours
import {npm as getFiles} from './get-files'
const {npm: getFiles} = require('./get-files')
getFiles(resolve('../mng-test/files-in-package'))
.then(files => {

8
lib/to-host.js

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

6
lib/ua.js

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

49
lib/utils/check-path.js

@ -0,0 +1,49 @@
// Native
const os = require('os')
const path = require('path')
const checkPath = async dir => {
if (!dir) {
return
}
const home = os.homedir()
let location
const paths = {
home,
desktop: path.join(home, 'Desktop'),
downloads: path.join(home, 'Downloads')
}
for (const locationPath in paths) {
if (!{}.hasOwnProperty.call(paths, locationPath)) {
continue
}
if (dir === paths[locationPath]) {
location = locationPath
}
}
if (!location) {
return
}
let locationName
switch (location) {
case 'home':
locationName = 'user directory'
break
case 'downloads':
locationName = 'downloads directory'
break
default:
locationName = location
}
throw new Error(`You're trying to deploy your ${locationName}.`)
}
module.exports = checkPath

5
lib/utils/prompt-options.js

@ -1,6 +1,7 @@
import chalk from 'chalk'
// Packages
const chalk = require('chalk')
export default function (opts) {
module.exports = function (opts) {
return new Promise((resolve, reject) => {
opts.forEach(([, text], i) => {
console.log(`${chalk.gray('>')} [${chalk.bold(i + 1)}] ${text}`)

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

@ -1,5 +1,6 @@
import {resolve} from 'path'
import {homedir} from 'os'
// Native
const {resolve} = require('path')
const {homedir} = require('os')
// cleaned-up `$HOME` (i.e.: no trailing slash)
const HOME = resolve(homedir())
@ -10,6 +11,8 @@ const HOME = resolve(homedir())
* `/Users/rauchg/test.js` becomes `~/test.js`
*/
export default function toHumanPath(path) {
function toHumanPath(path) {
return path.replace(HOME, '~')
}
module.exports = toHumanPath

129
package.json

@ -1,21 +1,25 @@
{
"name": "now",
"version": "0.30.0",
"description": "Realtime global deployments",
"repository": "zeit/now",
"main": "./build/lib/index",
"version": "2.0.5",
"description": "The command line interface for Now",
"repository": "zeit/now-cli",
"license": "MIT",
"files": [
"build"
"bin",
"lib"
],
"scripts": {
"start": "gulp",
"test": "xo && ava",
"prepublish": "gulp compile",
"pkg": "pkg . --out-dir out"
"pack": "pkg . --out-dir packed"
},
"pkg": {
"scripts": "build/**/*"
"scripts": [
"./bin/*",
"./lib/**/*"
]
},
"bin": {
"now": "./bin/now.js"
},
"ava": {
"failFast": true,
@ -23,88 +27,75 @@
"test/*.js"
],
"require": [
"babel-register"
"async-to-gen/register"
]
},
"greenkeeper": {
"emails": false
},
"babel": {
"presets": [
"es2015"
],
"plugins": [
"transform-runtime",
"transform-async-to-generator"
]
},
"xo": {
"esnext": true,
"space": true,
"semicolon": false,
"ignores": [
"build/**",
"out/**",
"packed/**",
"test/_fixtures/**"
],
"rules": {
"import/no-unassigned-import": 0,
"import/no-dynamic-require": 0,
"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,
"no-control-regex": 0,
"no-case-declarations": 0,
"no-useless-escape": 0
"no-control-regex": 0
}
},
"bin": {
"now": "./build/bin/now"
"engines": {
"node": ">=6.9.0"
},
"dependencies": {
"ansi-escapes": "1.4.0",
"arr-flatten": "1.0.1",
"array-unique": "0.3.2",
"async-retry": "0.2.1",
"babel-runtime": "6.18.0",
"bytes": "2.4.0",
"chalk": "1.1.3",
"copy-paste": "1.3.0",
"cross-spawn": "5.0.1",
"docker-file-parser": "0.1.0",
"email-prompt": "0.1.8",
"email-validator": "1.0.7",
"fs-promise": "1.0.0",
"glob": "7.1.1",
"graceful-fs": "4.1.11",
"ignore": "3.2.0",
"ini": "1.3.4",
"minimist": "1.2.0",
"ms": "0.7.2",
"node-fetch": "1.6.3",
"progress": "1.1.8",
"resumer": "0.0.0",
"semver-compare": "1.0.0",
"socket.io-client": "1.6.0",
"spdy": "3.4.4",
"split-array": "1.0.1",
"text-table": "0.2.0"
"ansi-escapes": "^1.4.0",
"arr-flatten": "^1.0.1",
"array-unique": "^0.3.2",
"async-retry": "^0.2.1",
"async-to-gen": "^1.3.0",
"bytes": "^2.4.0",
"chalk": "^1.1.3",
"copy-paste": "^1.3.0",
"cross-spawn": "^5.0.1",
"docker-file-parser": "^0.1.0",
"download": "^5.0.2",
"email-prompt": "^0.2.0",
"email-validator": "^1.0.7",
"fs-promise": "^1.0.0",
"glob": "^7.1.1",
"graceful-fs": "^4.1.11",
"ignore": "^3.2.0",
"ini": "^1.3.4",
"is-url": "^1.2.2",
"minimist": "^1.2.0",
"ms": "^0.7.2",
"node-fetch": "^1.6.3",
"node-version": "^1.0.0",
"ora": "^1.0.0",
"progress": "^1.1.8",
"psl": "^1.1.15",
"resumer": "^0.0.0",
"semver-compare": "^1.0.0",
"socket.io-client": "^1.7.2",
"spdy": "^3.4.4",
"split-array": "^1.0.1",
"text-table": "^0.2.0",
"tmp-promise": "^1.0.3",
"update-notifier": "^1.0.3"
},
"devDependencies": {
"alpha-sort": "1.0.2",
"ava": "0.17.0",
"babel-plugin-transform-async-to-generator": "6.16.0",
"babel-plugin-transform-runtime": "6.15.0",
"babel-preset-es2015": "6.18.0",
"babel-register": "6.18.0",
"del": "2.2.2",
"estraverse-fb": "1.3.1",
"gulp": "3.9.1",
"gulp-babel": "6.1.2",
"gulp-ext": "1.0.0",
"gulp-task-listing": "1.0.1",
"pkg": "3.0.0-beta.21",
"xo": "0.17.1"
"alpha-sort": "^2.0.0",
"ava": "^0.17.0",
"del": "^2.2.2",
"estraverse-fb": "^1.3.1",
"pkg": "^3.0.0-beta.22",
"xo": "^0.17.1"
}
}

2
test/_fixtures/files-overrides-gitignore/.gitignore

@ -0,0 +1,2 @@
ignore-me.js
test.json

1
test/_fixtures/files-overrides-gitignore/ignore-me.js

@ -0,0 +1 @@
// this should be ignored

6
test/_fixtures/files-overrides-gitignore/package.json

@ -0,0 +1,6 @@
{
"files": [
"test.js",
"test.json"
]
}

1
test/_fixtures/files-overrides-gitignore/test.js

@ -0,0 +1 @@
// include me

1
test/_fixtures/files-overrides-gitignore/test.json

@ -0,0 +1 @@
{ "include": "me" }

BIN
test/_fixtures/hashes/duplicate/dei.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

2
test/_fixtures/now-files-overrides-npmignore/.npmignore

@ -0,0 +1,2 @@
ignore-me.js
test.json

1
test/_fixtures/now-files-overrides-npmignore/ignore-me.js

@ -0,0 +1 @@
// this should be ignored

8
test/_fixtures/now-files-overrides-npmignore/package.json

@ -0,0 +1,8 @@
{
"now": {
"files": [
"test.js",
"test.json"
]
}
}

1
test/_fixtures/now-files-overrides-npmignore/test.js

@ -0,0 +1 @@
// include me

1
test/_fixtures/now-files-overrides-npmignore/test.json

@ -0,0 +1 @@
{ "include": "me" }

88
test/args-parsing.js

@ -0,0 +1,88 @@
const path = require('path')
const test = require('ava')
const {spawn} = require('cross-spawn')
const deployHelpMessage = '𝚫 now [options] <command | path>'
const aliasHelpMessage = '𝚫 now alias <ls | set | rm> <deployment> <alias>'
test('"now help" prints deploy help message', async t => {
const result = await now('help')
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now --help" prints deploy help message', async t => {
const result = await now('--help')
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now deploy --help" prints deploy help message', async t => {
const result = await now('deploy', '--help')
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now --help deploy" prints deploy help message', async t => {
const result = await now('--help', 'deploy')
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(deployHelpMessage))
})
test('"now help alias" prints alias help message', async t => {
const result = await now('help', 'alias')
t.is(result.code, 0)
const stdout = result.stdout.split('\n')
t.true(stdout.length > 1)
t.true(stdout[1].includes(aliasHelpMessage))
})
test('"now alias --help" is the same as "now --help alias"', async t => {
const [result1, result2] = await Promise.all([now('alias', '--help'), now('--help', 'alias')])
t.is(result1.code, 0)
t.is(result1.code, result2.code)
t.is(result1.stdout, result2.stdout)
})
/**
* Run the built now binary with given arguments
*
* @param {String} args string arguements
* @return {Promise} promise that resolves to an object {code, stdout}
*/
function now(...args) {
return new Promise((resolve, reject) => {
const command = path.resolve(__dirname, '../bin/now.js')
const now = spawn(command, args)
let stdout = ''
now.stdout.on('data', data => {
stdout += data
})
now.on('error', err => {
reject(err)
})
now.on('close', code => {
resolve({
code,
stdout
})
})
})
}

30
test/index.js

@ -1,14 +1,14 @@
// Native
import {join, resolve} from 'path'
const {join, resolve} = require('path')
// Packages
import test from 'ava'
import {asc as alpha} from 'alpha-sort'
import {readFile} from 'fs-promise'
const test = require('ava')
const {asc: alpha} = require('alpha-sort')
const {readFile} = require('fs-promise')
// Ours
import {npm as getNpmFiles_, docker as getDockerFiles} from '../lib/get-files'
import hash from '../lib/hash'
const {npm: getNpmFiles_, docker: getDockerFiles} = require('../lib/get-files')
const hash = require('../lib/hash')
const prefix = join(__dirname, '_fixtures') + '/'
const base = path => path.replace(prefix, '')
@ -43,6 +43,24 @@ test('`files` + `.*.swp` + `.npmignore`', async t => {
t.is(base(files[2]), 'files-in-package-ignore/package.json')
})
test('`files` overrides `.gitignore`', async t => {
let files = await getNpmFiles(fixture('files-overrides-gitignore'))
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'files-overrides-gitignore/package.json')
t.is(base(files[1]), 'files-overrides-gitignore/test.js')
t.is(base(files[2]), 'files-overrides-gitignore/test.json')
})
test('`now.files` overrides `.npmignore`', async t => {
let files = await getNpmFiles(fixture('now-files-overrides-npmignore'))
files = files.sort(alpha)
t.is(files.length, 3)
t.is(base(files[0]), 'now-files-overrides-npmignore/package.json')
t.is(base(files[1]), 'now-files-overrides-npmignore/test.js')
t.is(base(files[2]), 'now-files-overrides-npmignore/test.json')
})
test('simple', async t => {
let files = await getNpmFiles(fixture('simple'))
files = files.sort(alpha)

4
test/to-host.js

@ -1,5 +1,5 @@
import test from 'ava'
import toHost from '../lib/to-host'
const test = require('ava')
const toHost = require('../lib/to-host')
test('simple', async t => {
t.is(toHost('zeit.co'), 'zeit.co')

Loading…
Cancel
Save