commit f2693e19d7715d612310bdb3f473237d612a3053 Author: Guillermo Rauch Date: Tue Feb 23 17:20:22 2016 -0800 initial commit diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..df1b813 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "extends": "standard", + "parser": "babel-eslint", + "rules": { + "yoda": 0, + "semi": [2, "always"], + "no-extra-semi": 2, + "semi-spacing": [2, { "before": false, "after": true }], + "no-shadow": [2, {"builtinGlobals": true, "hoist": "functions"}] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..f8e2621 --- /dev/null +++ b/Readme.md @@ -0,0 +1,43 @@ +# now + +The fastest way to deploy a Node.JS service. + +## How it works + +In any directory with a `package.json`, run: + +```bash +$ now +> https://some-code-nd23n2.now.sh +``` + +every time you run it, you get a new URL (unless nothing's changed). + +### Conventions + +- Only files that would be included in `npm publish` are synchronized. +- `package.json` `files`, `.npmignore`, `.gitignore` supported +- `package.json` must contain a `start` task inside `scripts`. + If a `now` script is defined, that's used instead. +- Your HTTP server is expected to run in port `3000`. + +## Installing + +```bash +$ npm install -g now +> Enter your email: rauchg@gmail.com. +> We sent an email. Click to log in. +``` + +## Features + +- No reliance on Git. +- File de-duping. +- Respects NPM conventions. + +## Options + +``` +-d Debug mode. Lists all files to be uploaded. +-f Force. Creates a new URL even if nothing has changed. +``` diff --git a/bin/now b/bin/now new file mode 100755 index 0000000..5d48baf --- /dev/null +++ b/bin/now @@ -0,0 +1,45 @@ +#!/usr/bin/env node +import program from 'commander'; +import { resolve } from 'path'; +import { copy } from 'copy-paste'; +import now from '../lib'; +import ms from 'ms'; + +program +.option('-d, --debug', 'Debug mode [off]', false) +.option('-C, --no-clipboard', 'Do not attempt to copy URL to clipboard') +.parse(process.argv); + +let path = program.args[program.args.length - 1]; + +if (path) { + if ('/' !== path[0]) { + path = resolve(process.cwd(), path); + } +} else { + path = process.cwd(); +} + +const debug = !!program.debug; +const clipboard = !program.noClipboard; +const start = Date.now(); + +console.log(`> Deploying ${path}`); + +now(path, { debug }).then((url) => { + const elapsed = ms(new Date() - start); + if (clipboard) { + copy(url, (err) => { + console.log(`> ${url} ${!err ? '(copied to clipboard)' : ''} [${elapsed}]`); + }); + } else { + console.log(`> ${url} [${elapsed}]`); + } +}, (err) => { + error(err.message); + process.exit(1); +}); + +function error (err) { + console.error(`> \u001b[31mError!\u001b[39m ${err}.`); +} diff --git a/build/bin/now b/build/bin/now new file mode 100755 index 0000000..15e4148 --- /dev/null +++ b/build/bin/now @@ -0,0 +1,56 @@ +#!/usr/bin/env node +'use strict'; + +var _commander = require('commander'); + +var _commander2 = _interopRequireDefault(_commander); + +var _path = require('path'); + +var _copyPaste = require('copy-paste'); + +var _lib = require('../lib'); + +var _lib2 = _interopRequireDefault(_lib); + +var _ms = require('ms'); + +var _ms2 = _interopRequireDefault(_ms); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +_commander2.default.option('-d, --debug', 'Debug mode [off]', false).option('-C, --no-clipboard', 'Do not attempt to copy URL to clipboard').parse(process.argv); + +var path = _commander2.default.args[_commander2.default.args.length - 1]; + +if (path) { + if ('/' !== path[0]) { + path = (0, _path.resolve)(process.cwd(), path); + } +} else { + path = process.cwd(); +} + +var debug = !!_commander2.default.debug; +var clipboard = !_commander2.default.noClipboard; +var start = Date.now(); + +console.log('> Deploying ' + path); + +(0, _lib2.default)(path, { debug: debug }).then(function (url) { + var elapsed = (0, _ms2.default)(new Date() - start); + if (clipboard) { + (0, _copyPaste.copy)(url, function (err) { + console.log('> ' + url + ' ' + (!err ? '(copied to clipboard)' : '') + ' [' + elapsed + ']'); + }); + } else { + console.log('> ' + url + ' [' + elapsed + ']'); + } +}, function (err) { + error(err.message); + process.exit(1); +}); + +function error(err) { + console.error('> \u001b[31mError!\u001b[39m ' + err + '.'); +} \ No newline at end of file diff --git a/build/lib/get-files.js b/build/lib/get-files.js new file mode 100644 index 0000000..d76535c --- /dev/null +++ b/build/lib/get-files.js @@ -0,0 +1,347 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _promise = require('babel-runtime/core-js/promise'); + +var _promise2 = _interopRequireDefault(_promise); + +var _regenerator = require('babel-runtime/regenerator'); + +var _regenerator2 = _interopRequireDefault(_regenerator); + +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +var _fsPromise = require('fs-promise'); + +var _fsPromise2 = _interopRequireDefault(_fsPromise); + +var _path = require('path'); + +var _arrFlatten = require('arr-flatten'); + +var _arrFlatten2 = _interopRequireDefault(_arrFlatten); + +var _arrayUnique = require('array-unique'); + +var _arrayUnique2 = _interopRequireDefault(_arrayUnique); + +var _minimatch = require('minimatch'); + +var _minimatch2 = _interopRequireDefault(_minimatch); + +var _ignored = require('./ignored'); + +var _ignored2 = _interopRequireDefault(_ignored); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Returns a list of files in the given + * directory that are subject to be + * synchronized. + * + * @param {String} full path to directory + * @return {Array} comprehensive list of paths to sync + */ + +exports.default = function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(path) { + var pkgData, pkg, search, found, npmIgnore, gitIgnore, ignored; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return (0, _fsPromise.readFile)((0, _path.resolve)(path, 'package.json'), 'utf8'); + + case 2: + pkgData = _context.sent; + pkg = JSON.parse(pkgData); + search = (pkg.files || ['.']).concat('package.json'); + + if (pkg.main) search = search.concat(pkg.main); + search = search.map(function (file) { + return asAbsolute(file, path); + }); + + _context.next = 9; + return explode(search); + + case 9: + _context.t0 = _context.sent; + found = (0, _arrayUnique2.default)(_context.t0); + _context.next = 13; + return maybeRead((0, _path.resolve)(path, '.npmignore')); + + case 13: + npmIgnore = _context.sent; + + if (!npmIgnore) { + _context.next = 18; + break; + } + + _context.t1 = ''; + _context.next = 21; + break; + + case 18: + _context.next = 20; + return maybeRead((0, _path.resolve)(path, '.gitignore')); + + case 20: + _context.t1 = _context.sent; + + case 21: + gitIgnore = _context.t1; + ignored = (0, _arrayUnique2.default)(_ignored2.default.concat(gitIgnore.split('\n').filter(invalidFilter)).concat(npmIgnore.split('\n').filter(invalidFilter))).map(function (file) { + return (0, _path.resolve)(path, file); + }); + return _context.abrupt('return', found.filter(ignoredFilter(ignored))); + + case 24: + case 'end': + return _context.stop(); + } + } + }, _callee, this); + })); + return function getFiles(_x) { + return ref.apply(this, arguments); + }; +}(); + +/** + * Returns a filter function that + * excludes ignored files in the path. + * + * @param {String} path + * @return {Function} filter fn + */ + +var ignoredFilter = function ignoredFilter(ignored) { + return function (file) { + return !ignored.some(function (test) { + return (0, _minimatch2.default)(file, test); + }); + }; +}; + +/** + * Returns a filter function that + * excludes invalid rules for .*ignore files + * + * @param {String} path + * @return {Function} filter fn + */ + +var invalidFilter = function invalidFilter(path) { + return !( + /* commments */ + '#' === path[0] || + + /* empty lines or newlines */ + !path.trim().length); +}; + +/** + * Transform relative paths into absolutes, + * and maintains absolutes as such. + * + * @param {String} maybe relative path + * @param {String} parent full path + */ + +var asAbsolute = function asAbsolute(path, parent) { + if ('/' === path[0]) return path; + return (0, _path.resolve)(parent, path); +}; + +/** + * Explodes directories into a full list of files. + * Eg: + * in: ['/a.js', '/b'] + * out: ['/a.js', '/b/c.js', '/b/d.js'] + * + * @param {Array} of {String}s representing paths + * @return {Array} of {String}s of full paths + */ + +var explode = function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee5(paths) { + var _this2 = this; + + var many, list; + return _regenerator2.default.wrap(function _callee5$(_context5) { + while (1) { + switch (_context5.prev = _context5.next) { + case 0: + many = function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee3(all) { + return _regenerator2.default.wrap(function _callee3$(_context3) { + while (1) { + switch (_context3.prev = _context3.next) { + case 0: + _context3.next = 2; + return _promise2.default.all(all.map(function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(file) { + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return list(file); + + case 2: + return _context2.abrupt('return', _context2.sent); + + case 3: + case 'end': + return _context2.stop(); + } + } + }, _callee2, _this2); + })), + _this = _this2; + return function (_x4) { + return ref.apply(_this, arguments); + }; + }())); + + case 2: + return _context3.abrupt('return', _context3.sent); + + case 3: + case 'end': + return _context3.stop(); + } + } + }, _callee3, _this2); + })), + _this = _this2; + return function many(_x3) { + return ref.apply(_this, arguments); + }; + }(); + + list = function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee4(file) { + var path, stat, all; + return _regenerator2.default.wrap(function _callee4$(_context4) { + while (1) { + switch (_context4.prev = _context4.next) { + case 0: + path = file; + stat = undefined; + _context4.prev = 2; + _context4.next = 5; + return _fsPromise2.default.stat(path); + + case 5: + stat = _context4.sent; + _context4.next = 14; + break; + + case 8: + _context4.prev = 8; + _context4.t0 = _context4['catch'](2); + + // in case the file comes from `files` or `main` + // and it wasn't specified with `.js` by the user + path = file + '.js'; + _context4.next = 13; + return _fsPromise2.default.stat(path); + + case 13: + stat = _context4.sent; + + case 14: + if (!stat.isDirectory()) { + _context4.next = 21; + break; + } + + _context4.next = 17; + return _fsPromise2.default.readdir(file); + + case 17: + all = _context4.sent; + return _context4.abrupt('return', many(all.map(function (subdir) { + return asAbsolute(subdir, file); + }))); + + case 21: + return _context4.abrupt('return', path); + + case 22: + case 'end': + return _context4.stop(); + } + } + }, _callee4, _this2, [[2, 8]]); + })), + _this = _this2; + return function list(_x5) { + return ref.apply(_this, arguments); + }; + }(); + + _context5.next = 4; + return many(paths); + + case 4: + _context5.t0 = _context5.sent; + return _context5.abrupt('return', (0, _arrFlatten2.default)(_context5.t0)); + + case 6: + case 'end': + return _context5.stop(); + } + } + }, _callee5, this); + })); + return function explode(_x2) { + return ref.apply(this, arguments); + }; +}(); + +/** + * Returns the contents of a file if it exists. + * + * @return {String} results or `''` + */ + +var maybeRead = function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee6(path) { + return _regenerator2.default.wrap(function _callee6$(_context6) { + while (1) { + switch (_context6.prev = _context6.next) { + case 0: + _context6.prev = 0; + _context6.next = 3; + return _fsPromise2.default.readFile(path, 'utf8'); + + case 3: + return _context6.abrupt('return', _context6.sent); + + case 6: + _context6.prev = 6; + _context6.t0 = _context6['catch'](0); + return _context6.abrupt('return', ''); + + case 9: + case 'end': + return _context6.stop(); + } + } + }, _callee6, this, [[0, 6]]); + })); + return function maybeRead(_x6) { + return ref.apply(this, arguments); + }; +}(); \ No newline at end of file diff --git a/build/lib/hash.js b/build/lib/hash.js new file mode 100644 index 0000000..65d9aca --- /dev/null +++ b/build/lib/hash.js @@ -0,0 +1,100 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _map = require('babel-runtime/core-js/map'); + +var _map2 = _interopRequireDefault(_map); + +var _regenerator = require('babel-runtime/regenerator'); + +var _regenerator2 = _interopRequireDefault(_regenerator); + +var _promise = require('babel-runtime/core-js/promise'); + +var _promise2 = _interopRequireDefault(_promise); + +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +var _fsPromise = require('fs-promise'); + +var _fsPromise2 = _interopRequireDefault(_fsPromise); + +var _crypto = require('crypto'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/** + * Computes hashes for the contents of each file given. + * + * @param {Array} of {String} full paths + * @return {Map} + */ + +exports.default = function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee2(files) { + var _this2 = this; + + var entries; + return _regenerator2.default.wrap(function _callee2$(_context2) { + while (1) { + switch (_context2.prev = _context2.next) { + case 0: + _context2.next = 2; + return _promise2.default.all(files.map(function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(file) { + var data; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.next = 2; + return _fsPromise2.default.readFile(file); + + case 2: + data = _context.sent; + return _context.abrupt('return', [file, hash(data)]); + + case 4: + case 'end': + return _context.stop(); + } + } + }, _callee, _this2); + })), + _this = _this2; + return function (_x2) { + return ref.apply(_this, arguments); + }; + }())); + + case 2: + entries = _context2.sent; + return _context2.abrupt('return', new _map2.default(entries)); + + case 4: + case 'end': + return _context2.stop(); + } + } + }, _callee2, this); + })); + return function hashes(_x) { + return ref.apply(this, arguments); + }; +}(); + +/** + * Computes a hash for the given buf. + * + * @param {Buffer} file data + * @return {String} hex digest + */ + +function hash(buf) { + return (0, _crypto.createHash)('sha1').update(buf).digest('hex'); +} \ No newline at end of file diff --git a/build/lib/ignored.js b/build/lib/ignored.js new file mode 100644 index 0000000..bc4e237 --- /dev/null +++ b/build/lib/ignored.js @@ -0,0 +1,6 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = ['._*', '.hg', '.git', '.svn', '.npmrc', '.*.swp', '.DS_Store', '.wafpickle-*', '.lock-wscript', 'npm-debug.log', 'config.gypi', 'node_modules', 'CVS', 'README', 'README.*', 'CHANGELOG', 'History.md', 'LICENSE', 'Readme', 'Readme.*', 'test', 'tests']; \ No newline at end of file diff --git a/build/lib/index.js b/build/lib/index.js new file mode 100644 index 0000000..ea23f28 --- /dev/null +++ b/build/lib/index.js @@ -0,0 +1,88 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _regenerator = require('babel-runtime/regenerator'); + +var _regenerator2 = _interopRequireDefault(_regenerator); + +var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); + +var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); + +var _fsPromise = require('fs-promise'); + +var _fsPromise2 = _interopRequireDefault(_fsPromise); + +var _getFiles = require('./get-files'); + +var _getFiles2 = _interopRequireDefault(_getFiles); + +var _hash = require('./hash'); + +var _hash2 = _interopRequireDefault(_hash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +exports.default = function () { + var ref = (0, _asyncToGenerator3.default)(_regenerator2.default.mark(function _callee(path, _ref) { + var debug = _ref.debug; + var files, hashes; + return _regenerator2.default.wrap(function _callee$(_context) { + while (1) { + switch (_context.prev = _context.next) { + case 0: + _context.prev = 0; + _context.next = 3; + return _fsPromise2.default.stat(path); + + case 3: + _context.next = 8; + break; + + case 5: + _context.prev = 5; + _context.t0 = _context['catch'](0); + throw new Error('Could not read directory ' + path + '.'); + + case 8: + + if (debug) console.time('> [debug] Getting files'); + _context.next = 11; + return (0, _getFiles2.default)(path); + + case 11: + files = _context.sent; + + if (debug) console.timeEnd('> [debug] Getting files'); + + if (debug) console.time('> [debug] Computing hashes'); + _context.next = 16; + return (0, _hash2.default)(files); + + case 16: + hashes = _context.sent; + + if (debug) console.timeEnd('> [debug] Computing hashes'); + + if (debug) { + hashes.forEach(function (val, key) { + console.log('> [debug] Found "' + key + '" [' + val + ']'); + }); + } + + return _context.abrupt('return', 'https://test.now.run'); + + case 20: + case 'end': + return _context.stop(); + } + } + }, _callee, this, [[0, 5]]); + })); + return function now(_x, _x2) { + return ref.apply(this, arguments); + }; +}(); \ No newline at end of file diff --git a/build/lib/test.js b/build/lib/test.js new file mode 100644 index 0000000..c0c6220 --- /dev/null +++ b/build/lib/test.js @@ -0,0 +1,22 @@ +'use strict'; + +var _getFiles = require('./get-files'); + +var _getFiles2 = _interopRequireDefault(_getFiles); + +var _path = require('path'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +(0, _getFiles2.default)((0, _path.resolve)('../mng-test/files-in-package')).then(function (files) { + console.log(files); + + (0, _getFiles2.default)((0, _path.resolve)('../mng-test/files-in-package-ignore')).then(function (files2) { + console.log('ignored: '); + console.log(files2); + }).catch(function (err) { + console.log(err.stack); + }); +}).catch(function (err) { + console.log(err.stack); +}); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..8038273 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,57 @@ +const gulp = require('gulp'); +const del = require('del'); +const ext = require('gulp-ext'); +const babel = require('gulp-babel'); +const eslint = require('gulp-eslint'); +const help = require('gulp-task-listing'); + +gulp.task('help', help); + +gulp.task('compile', [ + 'compile-lib', + 'compile-bin' +]); + +gulp.task('compile-lib', function () { + return gulp.src('lib/**/*.js') + .pipe(babel({ + presets: ['es2015'], + plugins: [ + 'syntax-async-functions', + 'transform-async-to-generator', + 'transform-runtime' + ] + })) + .pipe(gulp.dest('build/lib')); +}); + +gulp.task('compile-bin', function () { + return gulp.src('bin/*') + .pipe(babel({ + presets: ['es2015'], + plugins: [ + 'syntax-async-functions', + 'transform-async-to-generator', + 'transform-runtime' + ] + })) + .pipe(ext.crop()) + .pipe(gulp.dest('build/bin')); +}); + +gulp.task('lint', function () { + return gulp.src([ + 'gulpfile.js', + 'lib/**/*.js', + 'bin/*' + ]) + .pipe(eslint()) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()); +}); + +gulp.task('clean', function () { + return del(['build']); +}); + +gulp.task('default', ['lint', 'compile']); diff --git a/lib/get-files.js b/lib/get-files.js new file mode 100644 index 0000000..058b36d --- /dev/null +++ b/lib/get-files.js @@ -0,0 +1,138 @@ +import fs, { readFile as read } from 'fs-promise'; +import { resolve } from 'path'; +import flatten from 'arr-flatten'; +import unique from 'array-unique'; +import minimatch from 'minimatch'; +import IGNORED from './ignored'; + +/** + * Returns a list of files in the given + * directory that are subject to be + * synchronized. + * + * @param {String} full path to directory + * @return {Array} comprehensive list of paths to sync + */ + +export default async function getFiles (path) { + const pkgData = await read(resolve(path, 'package.json'), 'utf8'); + const pkg = JSON.parse(pkgData); + + let search = (pkg.files || ['.']).concat('package.json'); + if (pkg.main) search = search.concat(pkg.main); + search = search.map((file) => asAbsolute(file, path)); + + const found = unique((await explode(search))); + + const npmIgnore = await maybeRead(resolve(path, '.npmignore')); + const gitIgnore = npmIgnore + ? '' + : (await maybeRead(resolve(path, '.gitignore'))); + + const ignored = unique(IGNORED + .concat(gitIgnore.split('\n').filter(invalidFilter)) + .concat(npmIgnore.split('\n').filter(invalidFilter))) + .map(file => resolve(path, file)); + + return found.filter(ignoredFilter(ignored)); +} + +/** + * Returns a filter function that + * excludes ignored files in the path. + * + * @param {String} path + * @return {Function} filter fn + */ + +const ignoredFilter = (ignored) => (file) => { + return !ignored.some((test) => { + return minimatch(file, test); + }); +}; + +/** + * Returns a filter function that + * excludes invalid rules for .*ignore files + * + * @param {String} path + * @return {Function} filter fn + */ + +const invalidFilter = (path) => { + return !( + /* commments */ + '#' === path[0] || + + /* empty lines or newlines */ + !path.trim().length + ); +}; + +/** + * Transform relative paths into absolutes, + * and maintains absolutes as such. + * + * @param {String} maybe relative path + * @param {String} parent full path + */ + +const asAbsolute = function (path, parent) { + if ('/' === path[0]) return path; + return resolve(parent, path); +}; + +/** + * Explodes directories into a full list of files. + * Eg: + * in: ['/a.js', '/b'] + * out: ['/a.js', '/b/c.js', '/b/d.js'] + * + * @param {Array} of {String}s representing paths + * @return {Array} of {String}s of full paths + */ + +const explode = async function (paths) { + const many = async (all) => { + return await Promise.all(all.map(async (file) => { + return await list(file); + })); + }; + + const list = async (file) => { + let path = file; + let stat; + + try { + stat = await fs.stat(path); + } catch (e) { + // in case the file comes from `files` or `main` + // and it wasn't specified with `.js` by the user + path = file + '.js'; + stat = await fs.stat(path); + } + + if (stat.isDirectory()) { + const all = await fs.readdir(file); + return many(all.map(subdir => asAbsolute(subdir, file))); + } else { + return path; + } + }; + + return flatten((await many(paths))); +}; + +/** + * Returns the contents of a file if it exists. + * + * @return {String} results or `''` + */ + +const maybeRead = async function (path) { + try { + return (await fs.readFile(path, 'utf8')); + } catch (e) { + return ''; + } +}; diff --git a/lib/hash.js b/lib/hash.js new file mode 100644 index 0000000..7a2c93f --- /dev/null +++ b/lib/hash.js @@ -0,0 +1,30 @@ +import fs from 'fs-promise'; +import { createHash } from 'crypto'; + +/** + * Computes hashes for the contents of each file given. + * + * @param {Array} of {String} full paths + * @return {Map} + */ + +export default async function hashes (files) { + const entries = await Promise.all(files.map(async (file) => { + const data = await fs.readFile(file); + return [file, hash(data)]; + })); + return new Map(entries); +} + +/** + * Computes a hash for the given buf. + * + * @param {Buffer} file data + * @return {String} hex digest + */ + +function hash (buf) { + return createHash('sha1') + .update(buf) + .digest('hex'); +} diff --git a/lib/ignored.js b/lib/ignored.js new file mode 100644 index 0000000..aa46eb0 --- /dev/null +++ b/lib/ignored.js @@ -0,0 +1,24 @@ +export default [ + '._*', + '.hg', + '.git', + '.svn', + '.npmrc', + '.*.swp', + '.DS_Store', + '.wafpickle-*', + '.lock-wscript', + 'npm-debug.log', + 'config.gypi', + 'node_modules', + 'CVS', + 'README', + 'README.*', + 'CHANGELOG', + 'History.md', + 'LICENSE', + 'Readme', + 'Readme.*', + 'test', + 'tests' +]; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..5b81c28 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,27 @@ +import fs from 'fs-promise'; +import getFiles from './get-files'; +import hash from './hash'; + +export default async function now (path, { debug }) { + try { + await fs.stat(path); + } catch (err) { + throw new Error(`Could not read directory ${path}.`); + } + + if (debug) console.time('> [debug] Getting files'); + const files = await getFiles(path); + if (debug) console.timeEnd('> [debug] Getting files'); + + if (debug) console.time('> [debug] Computing hashes'); + const hashes = await hash(files); + if (debug) console.timeEnd('> [debug] Computing hashes'); + + if (debug) { + hashes.forEach((val, key) => { + console.log(`> [debug] Found "${key}" [${val}]`); + }); + } + + return 'https://test.now.run'; +} diff --git a/lib/test.js b/lib/test.js new file mode 100644 index 0000000..63ed825 --- /dev/null +++ b/lib/test.js @@ -0,0 +1,20 @@ +import getFiles from './get-files'; +import { resolve } from 'path'; + +getFiles(resolve('../mng-test/files-in-package')) +.then(files => { + console.log(files); + + getFiles(resolve('../mng-test/files-in-package-ignore')) + .then(files2 => { + console.log('ignored: '); + console.log(files2); + }) + .catch(err => { + console.log(err.stack); + }); +}) +.catch(err => { + console.log(err.stack); +}); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..a90791c --- /dev/null +++ b/package.json @@ -0,0 +1,63 @@ +{ + "name": "now", + "private": true, + "version": "0.0.1", + "description": "", + "main": "./build/lib/index", + "files": [ + "build/lib", + "build/bin" + ], + "bin": { + "now": "./build/bin/now" + }, + "dependencies": { + "arr-flatten": "1.0.1", + "array-unique": "0.2.1", + "commander": "2.9.0", + "copy-paste": "1.1.4", + "fs-promise": "0.4.1", + "http2": "3.3.2", + "ms": "0.7.1" + }, + "devDependencies": { + "alpha-sort": "1.0.2", + "ava": "0.12.0", + "babel-eslint": "4.1.8", + "babel-plugin-syntax-async-functions": "6.3.13", + "babel-plugin-transform-async-to-generator": "6.4.6", + "babel-preset-es2015": "6.3.13", + "babel-register": "6.5.2", + "del": "2.2.0", + "eslint-config-standard": "4.4.0", + "eslint-plugin-standard": "1.3.1", + "gulp": "3.9.0", + "gulp-babel": "6.1.2", + "gulp-eslint": "1.1.1", + "gulp-ext": "1.0.0", + "gulp-task-listing": "1.0.1" + }, + "scripts": { + "gulp": "gulp", + "test": "ava" + }, + "babel": { + "presets": [ + "es2015" + ], + "plugins": [ + "transform-runtime", + "syntax-async-functions", + "transform-async-to-generator" + ] + }, + "ava": { + "failFast": true, + "files": [ + "test/*.js" + ], + "require": [ + "babel-register" + ] + } +} diff --git a/test/_fixtures/files-in-package-ignore/.a.swp b/test/_fixtures/files-in-package-ignore/.a.swp new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package-ignore/.npmignore b/test/_fixtures/files-in-package-ignore/.npmignore new file mode 100644 index 0000000..cec0cb5 --- /dev/null +++ b/test/_fixtures/files-in-package-ignore/.npmignore @@ -0,0 +1,2 @@ +should-be-excluded.js +./build/a/should-be-excluded.js diff --git a/test/_fixtures/files-in-package-ignore/README b/test/_fixtures/files-in-package-ignore/README new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package-ignore/build/a/b/c/d.js b/test/_fixtures/files-in-package-ignore/build/a/b/c/d.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package-ignore/build/a/e.js b/test/_fixtures/files-in-package-ignore/build/a/e.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package-ignore/build/a/should-be-excluded.js b/test/_fixtures/files-in-package-ignore/build/a/should-be-excluded.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package-ignore/package.json b/test/_fixtures/files-in-package-ignore/package.json new file mode 100644 index 0000000..cc6680e --- /dev/null +++ b/test/_fixtures/files-in-package-ignore/package.json @@ -0,0 +1,5 @@ +{ + "files": [ + "build" + ] +} diff --git a/test/_fixtures/files-in-package-ignore/should-be-excluded.js b/test/_fixtures/files-in-package-ignore/should-be-excluded.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package/.DS_Store b/test/_fixtures/files-in-package/.DS_Store new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package/build/a/b/c/d.js b/test/_fixtures/files-in-package/build/a/b/c/d.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package/build/a/e.js b/test/_fixtures/files-in-package/build/a/e.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/files-in-package/package.json b/test/_fixtures/files-in-package/package.json new file mode 100644 index 0000000..cc6680e --- /dev/null +++ b/test/_fixtures/files-in-package/package.json @@ -0,0 +1,5 @@ +{ + "files": [ + "build" + ] +} diff --git a/test/_fixtures/files-in-package/should-be-excluded.js b/test/_fixtures/files-in-package/should-be-excluded.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/hashes/dei.png b/test/_fixtures/hashes/dei.png new file mode 100644 index 0000000..54279df Binary files /dev/null and b/test/_fixtures/hashes/dei.png differ diff --git a/test/_fixtures/hashes/index.js b/test/_fixtures/hashes/index.js new file mode 100644 index 0000000..96b3774 --- /dev/null +++ b/test/_fixtures/hashes/index.js @@ -0,0 +1 @@ +woot diff --git a/test/_fixtures/hashes/package.json b/test/_fixtures/hashes/package.json new file mode 100644 index 0000000..2f91cf5 --- /dev/null +++ b/test/_fixtures/hashes/package.json @@ -0,0 +1,6 @@ +{ + "name": "hashes", + "version": "0.0.1", + "description": "", + "dependencies": {} +} diff --git a/test/_fixtures/simple-main/build/a.js b/test/_fixtures/simple-main/build/a.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/simple-main/index.js b/test/_fixtures/simple-main/index.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/simple-main/package.json b/test/_fixtures/simple-main/package.json new file mode 100644 index 0000000..57424e1 --- /dev/null +++ b/test/_fixtures/simple-main/package.json @@ -0,0 +1,8 @@ +{ + "name": "simple-with-main", + "version": "0.0.1", + "description": "", + "main": "./index.js", + "files": ["build/a.js"], + "dependencies": {} +} diff --git a/test/_fixtures/simple/bin/test b/test/_fixtures/simple/bin/test new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/simple/index.js b/test/_fixtures/simple/index.js new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/simple/lib/woot b/test/_fixtures/simple/lib/woot new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/simple/lib/woot.jsx b/test/_fixtures/simple/lib/woot.jsx new file mode 100644 index 0000000..e69de29 diff --git a/test/_fixtures/simple/package.json b/test/_fixtures/simple/package.json new file mode 100644 index 0000000..6d41fe6 --- /dev/null +++ b/test/_fixtures/simple/package.json @@ -0,0 +1,3 @@ +{ + "name": "simple" +} diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..783081d --- /dev/null +++ b/test/index.js @@ -0,0 +1,56 @@ +import test from 'ava'; +import { join, resolve } from 'path'; +import getFiles from '../lib/get-files'; +import hash from '../lib/hash'; +import { asc as alpha } from 'alpha-sort'; + +const prefix = join(__dirname, '_fixtures') + '/'; +const base = (path) => path.replace(prefix, ''); +const fixture = (name) => resolve(`./_fixtures/${name}`); + +test('`files` + README', async t => { + let files = await getFiles(fixture('files-in-package')); + t.same(files.length, 3); + files = files.sort(alpha); + t.same(base(files[0]), 'files-in-package/build/a/b/c/d.js'); + t.same(base(files[1]), 'files-in-package/build/a/e.js'); + t.same(base(files[2]), 'files-in-package/package.json'); +}); + +test('`files` + README + `.*.swp` + `.npmignore`', async t => { + let files = await getFiles(fixture('files-in-package-ignore')); + t.same(files.length, 3); + files = files.sort(alpha); + t.same(base(files[0]), 'files-in-package-ignore/build/a/b/c/d.js'); + t.same(base(files[1]), 'files-in-package-ignore/build/a/e.js'); + t.same(base(files[2]), 'files-in-package-ignore/package.json'); +}); + +test('simple', async t => { + let files = await getFiles(fixture('simple')); + t.same(files.length, 5); + files = files.sort(alpha); + t.same(base(files[0]), 'simple/bin/test'); + t.same(base(files[1]), 'simple/index.js'); + t.same(base(files[2]), 'simple/lib/woot'); + t.same(base(files[3]), 'simple/lib/woot.jsx'); + t.same(base(files[4]), 'simple/package.json'); +}); + +test('simple with main', async t => { + let files = await getFiles(fixture('simple-main')); + t.same(files.length, 3); + files = files.sort(alpha); + t.same(base(files[0]), 'simple-main/build/a.js'); + t.same(base(files[1]), 'simple-main/index.js'); + t.same(base(files[2]), 'simple-main/package.json'); +}); + +test('hashes', async t => { + const files = await getFiles(fixture('hashes')); + const hashes = await hash(files); + t.same(hashes.size, 3); + t.same(hashes.get(prefix + 'hashes/dei.png'), '277c55a2042910b9fe706ad00859e008c1b7d172'); + t.same(hashes.get(prefix + 'hashes/index.js'), '56c00d0466fc6bdd41b13dac5fc920cc30a63b45'); + t.same(hashes.get(prefix + 'hashes/package.json'), '706214f42ae940a01d2aa60c5e32408f4d2127dd'); +});