Guillermo Rauch
8 years ago
58 changed files with 1182 additions and 584 deletions
@ -1 +0,0 @@ |
|||
save-exact=true |
@ -1,5 +1,4 @@ |
|||
{ |
|||
"language": "node_js", |
|||
"sudo": false, |
|||
"node_js": "node" |
|||
} |
|||
|
@ -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) |
|||
|
@ -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']) |
@ -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)) |
|||
}) |
|||
} |
@ -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 |
|||
} |
|||
|
@ -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 |
|||
} |
@ -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 |
|||
|
@ -1,3 +1,5 @@ |
|||
export default function strlen(str) { |
|||
function strlen(str) { |
|||
return str.replace(/\x1b[^m]*m/g, '').length |
|||
} |
|||
|
|||
module.exports = strlen |
|||
|
@ -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()})` |
|||
|
@ -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 |
@ -0,0 +1,2 @@ |
|||
ignore-me.js |
|||
test.json |
@ -0,0 +1 @@ |
|||
// this should be ignored
|
@ -0,0 +1,6 @@ |
|||
{ |
|||
"files": [ |
|||
"test.js", |
|||
"test.json" |
|||
] |
|||
} |
@ -0,0 +1 @@ |
|||
// include me
|
@ -0,0 +1 @@ |
|||
{ "include": "me" } |
Before Width: | Height: | Size: 167 KiB |
@ -0,0 +1,2 @@ |
|||
ignore-me.js |
|||
test.json |
@ -0,0 +1 @@ |
|||
// this should be ignored
|
@ -0,0 +1,8 @@ |
|||
{ |
|||
"now": { |
|||
"files": [ |
|||
"test.js", |
|||
"test.json" |
|||
] |
|||
} |
|||
} |
@ -0,0 +1 @@ |
|||
// include me
|
@ -0,0 +1 @@ |
|||
{ "include": "me" } |
@ -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 |
|||
}) |
|||
}) |
|||
}) |
|||
} |
Loading…
Reference in new issue