diff --git a/package.json b/package.json index 0e27442..ea83056 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "find-elm-dependencies": "^1.0.1", "find-up": "^2.1.0", "fkill": "^5.0.0", + "fs-extra": "^4.0.1", "gulp": "^3.9.1", "gulp-any-template": "^0.3.1", "gulp-elm-basic": "^0.3.0", @@ -70,7 +71,6 @@ "husky": "^0.14.3", "lint-staged": "^4.0.4", "mocha": "^3.5.0", - "mute": "^2.0.6", "nyc": "^11.1.0", "portscanner": "^2.1.1", "request-promise-native": "^1.0.4", diff --git a/src/tasks/build.js b/src/tasks/build.js index e937848..fb2522b 100644 --- a/src/tasks/build.js +++ b/src/tasks/build.js @@ -129,6 +129,7 @@ const build = options => { const opts = Object.assign({}, defaults, options) // CLI spinner + spinner.space() spinner.next('old builds are being cleaned') return del(opts.outputPath) @@ -169,12 +170,11 @@ const build = options => { .then(() => { spinner.space() spinner.succeed('main application has been compiled') - spinner.space() }) .catch(e => { spinner.space() - spinner.fail(e) - spinner.space() + spinner.fail(e, false) + throw e }) } diff --git a/src/tasks/dev.js b/src/tasks/dev.js index b80394e..980ff88 100644 --- a/src/tasks/dev.js +++ b/src/tasks/dev.js @@ -5,7 +5,7 @@ const elmCss = require('elm-css') const execa = require('execa') const findAllDependencies = require('find-elm-dependencies').findAllDependencies const fkill = require('fkill') -const fs = require('fs') +const fs = require('fs-extra') const nocache = require('nocache') const path = require('path') const proxy = require('http-proxy-middleware') @@ -62,27 +62,61 @@ const loadHtmlCompiler = file => { validateParam('string', 'file', file) return new Promise((resolve, reject) => { - fs.readFile(file, (err, contents) => { - if (err) { - reject(err) - } + fs + .pathExists(file) + .then(() => fs.readFile(file)) + .then((contents) => { + const compiler = anyTemplate.compiler({ path: file, contents }) + + if (compiler) { + resolve(compiler) + } else { + reject(new Error('html template format unsupported')) + } + }) + .catch(reject) + }) +} - const compiler = anyTemplate.compiler({ path: file, contents }) +const createWatcher = (onChange, onDeps, filter, bs, entry) => { + validateParam('function', 'onChange', onChange) + validateParam('function', 'onDeps', onDeps) + validateParam('function', 'filter', filter) + validateParam('object', 'bs', bs) + validateParam('string', 'entry', entry) - if (compiler) { - resolve(compiler) - } else { - reject(new Error('html template format unsupported')) - } - }) - }) + return new Promise((resolve, reject) => + getDepTree(entry) + .then(deps => { + onDeps(deps) + + const watcher = bs.watch(['!elm-stuff', ...deps]) + + watcher.on('change', file => { + if (filter(file)) { + // stop watching temporarily + watcher && watcher.close() + + // trigger on change + onChange(file) + + // start watching again + createWatcher(onChange, onDeps, filter, bs, entry) + } + }) + + resolve(watcher) + }) + .catch(reject) + ) } const htmlCompiler = (bs, html) => (request, response, next) => { if (path.extname(request.url) === '.elm') { return loadHtmlCompiler(html) .catch(e => { - spinner(e) + spinner.fail(e, false) + throw e }) .then(compiler => compiler({ @@ -94,11 +128,6 @@ const htmlCompiler = (bs, html) => (request, response, next) => { response.write(compiledHtml) response.end() }) - .catch(e => { - response.write(e) - response.end() - throw e - }) .then(() => createWatcher( () => bs.reload(), @@ -108,6 +137,9 @@ const htmlCompiler = (bs, html) => (request, response, next) => { path.join(process.cwd(), request.url) ) ) + .catch(e => { + next() + }) } else { next() } @@ -226,53 +258,18 @@ const startBrowserSync = ( }) } -const createWatcher = (onChange, onDeps, filter, bs, entry) => { - validateParam('function', 'onChange', onChange) - validateParam('function', 'onDeps', onDeps) - validateParam('function', 'filter', filter) - validateParam('object', 'bs', bs) - validateParam('string', 'entry', entry) - - return new Promise((resolve, reject) => - getDepTree(entry) - .then(deps => { - onDeps(deps) - - const watcher = bs.watch(['!elm-stuff', ...deps]) - - watcher.on('change', file => { - if (filter(file)) { - // stop watching temporarily - watcher && watcher.close() - - // trigger on change - onChange(file) - - // start watching again - createWatcher(onChange, onDeps, filter, bs, entry) - } - }) - - resolve(watcher) - }) - .catch(reject) - ) -} - const compileCss = (stylesheets, dir) => elmCss(process.cwd(), stylesheets, dir) .then(() => { setTimeout(() => { spinner.space() spinner.succeed('css has been compiled') - spinner.space() }) }) .catch(e => { setTimeout(() => { spinner.space() spinner.fail(`elm-css ${e.message}`, false) - spinner.space() }) }) @@ -280,77 +277,68 @@ const dev = options => { const opts = Object.assign({}, defaults, options) const tmpDir = tmp.dirSync({ unsafeCleanup: true }) + spinner.space() spinner.next('elm-package install is starting') - return ( - // first install packages using elm-package - installPackages() - .catch(e => { - spinner.fail(e) - }) - // then start elm-reactor - .then(output => { - spinner.succeed('elm-package install has completed') - spinner.next('elm-reactor is starting') + return installPackages() + .catch(e => { + spinner.fail(e) + }) + .then(output => { + spinner.succeed('elm-package install has completed') + spinner.next('elm-reactor is starting') - return startReactor(opts.reactorHost, opts.reactorPort) - }) - // then start browser-sync - .then(() => { - spinner.succeed('elm-reactor is now started') - spinner.next('browser-sync is starting') - - return startBrowserSync( - opts.host, - opts.port, - `http://${opts.reactorHost}:${opts.reactorPort}`, - opts.html, - tmpDir.name, - opts.proxy, - opts.proxyRewrite - ).catch(e => { - spinner.fail(e) - }) - }) - .then(({ bs, port }) => { - spinner.succeed('browser-sync is now started') - - return Promise.all([ - Promise.resolve(port), - // check if our stylesheet exists - exists(opts.stylesheets) - .then(() => { - // compile stylesheet - spinner.next('css is now compiling') - - return compileCss(opts.stylesheets, tmpDir.name) - }) - .then(() => { - // create stylesheet watcher - return createWatcher( - file => compileCss(opts.stylesheets, tmpDir.name), - deps => (stylesheetsDeps = deps), - () => true, - bs, - opts.stylesheets - ) - }) - .catch(e => { - spinner.space() - spinner.fail(e, false) - }), - ]) - }) - .then(port => { - spinner.succeed(`ready! http://${opts.host}:${opts.port}`) - spinner.space() - }) - .catch(e => { - spinner.fail(e, false) - spinner.space() - throw e + return startReactor(opts.reactorHost, opts.reactorPort) + }) + .then(() => { + spinner.succeed('elm-reactor is now started') + spinner.next('browser-sync is starting') + + return startBrowserSync( + opts.host, + opts.port, + `http://${opts.reactorHost}:${opts.reactorPort}`, + opts.html, + tmpDir.name, + opts.proxy, + opts.proxyRewrite + ).catch(e => { + spinner.fail(e) }) - ) + }) + .then(({ bs, port }) => { + spinner.succeed('browser-sync is now started') + + return Promise.all([ + Promise.resolve(port), + fs + .readFile(opts.stylesheets) + .then(() => { + spinner.next('css is now compiling') + + return compileCss(opts.stylesheets, tmpDir.name) + }) + .then(() => { + return createWatcher( + file => compileCss(opts.stylesheets, tmpDir.name), + deps => (stylesheetsDeps = deps), + () => true, + bs, + opts.stylesheets + ) + }) + .catch(e => { + spinner.fail(e, false) + }), + ]) + }) + .then(port => { + spinner.succeed(`ready! http://${opts.host}:${opts.port}`) + }) + .catch(e => { + spinner.fail(e, false) + throw e + }) } module.exports = { @@ -358,8 +346,8 @@ module.exports = { createProxies, getDepTree, loadHtmlCompiler, + createWatcher, startReactor, startBrowserSync, - createWatcher, dev, } diff --git a/src/tasks/init.js b/src/tasks/init.js index 5209709..dabbd1d 100644 --- a/src/tasks/init.js +++ b/src/tasks/init.js @@ -1,11 +1,12 @@ const anyTemplate = require('gulp-any-template') const filter = require('gulp-filter') -const fs = require('fs') +const fs = require('fs-extra') const gulp = require('gulp') const path = require('path') const pumpify = require('pumpify') const rename = require('gulp-rename') +const defaults = require('../defaults').init const { initializeSpinner, validateParam } = require('./utils') // global reference to CLI spinner @@ -15,9 +16,12 @@ const isEmpty = dir => { validateParam('string', 'dir', dir) return new Promise((resolve, reject) => { - fs.readdir(dir, function(err, files) { - resolve(files ? files.length === 0 : true) - }) + fs + .readdir(dir) + .then(files => { + resolve(files ? files.length === 0 : true) + }) + .catch(() => resolve(true)) }) } @@ -48,6 +52,7 @@ const copy = dir => { const init = (options = {}) => { validateParam('object', 'options', options) + spinner.space() spinner.next('initializing your project') return isEmpty(options.dir) @@ -60,13 +65,10 @@ const init = (options = {}) => { }) .then(() => { spinner.succeed('project created!') - spinner.space() spinner.stopAndPersist({ symbol: '$ ', text: `cd ${options.dir}` }) - spinner.space() }) .catch(e => { spinner.fail(e, false) - spinner.space() }) } diff --git a/src/tasks/utils.js b/src/tasks/utils.js index 1ade3f8..327c784 100644 --- a/src/tasks/utils.js +++ b/src/tasks/utils.js @@ -1,7 +1,6 @@ const chalk = require('chalk') const check = require('check-types') const execa = require('execa') -const fs = require('fs') const ora = require('ora') const spacer = (count = 50) => '-'.repeat(count) @@ -15,20 +14,6 @@ const validateParam = (type, name, value, required = true) => { return checker[type](value, invalidParam(type, name)) } -const exists = path => { - validateParam('string', 'path', path) - - return new Promise((resolve, reject) => { - fs.stat(path, (err, contents) => { - if (err) { - reject(`could not find ${path}`) - } else { - resolve(contents) - } - }) - }) -} - const installPackages = (cwd = process.cwd()) => { validateParam('string', 'cwd', cwd) @@ -47,25 +32,34 @@ const installPackages = (cwd = process.cwd()) => { const initializeSpinner = spinner => { const inner = spinner || ora() + const space = () => inner.stopAndPersist({ symbol: spacer(), text: ' ' }) return { inner, - space: () => inner.stopAndPersist({ symbol: spacer(), text: ' ' }), + space, next: text => { - inner.stopAndPersist({ symbol: spacer(), text: ' ' }) inner.text = text inner.start() }, succeed: text => { inner.succeed(text) + space() + }, + warn: text => { + inner.warn(text) + space() }, fail: (e, rethrow = true) => { inner.fail((e.message || e).trim()) if (rethrow) { throw e } + space() + }, + stopAndPersist: opts => { + inner.stopAndPersist(opts) + space() }, - stopAndPersist: opts => spinner.stopAndPersist(opts), } } @@ -73,7 +67,6 @@ module.exports = { invalidParam, validateParam, spacer, - exists, installPackages, initializeSpinner, } diff --git a/test/tasks/dev-test.js b/test/tasks/dev-test.js index 4f8d319..9ee4029 100644 --- a/test/tasks/dev-test.js +++ b/test/tasks/dev-test.js @@ -3,7 +3,6 @@ import chaifs from 'chai-fs' import chaiAsPromised from 'chai-as-promised' import fs from 'fs' import http from 'http' -import mute from 'mute' import path from 'path' import portscanner from 'portscanner' import request from 'request-promise-native' diff --git a/test/tasks/utils-test.js b/test/tasks/utils-test.js index aac4dab..47ae997 100644 --- a/test/tasks/utils-test.js +++ b/test/tasks/utils-test.js @@ -2,7 +2,6 @@ import chai, { assert, expect } from 'chai' import chaifs from 'chai-fs' import chaiAsPromised from 'chai-as-promised' import check from 'check-types' -import fs from 'fs' import path from 'path' import sinon from 'sinon' import tmp from 'tmp' @@ -107,16 +106,24 @@ describe('UTILS', function() { let space let next let succeed + let warn let fail let stopAndPersist let start beforeEach(() => { succeed = sinon.spy() + warn = sinon.spy() fail = sinon.spy() stopAndPersist = sinon.spy() start = sinon.spy() - spinner = initializeSpinner({ succeed, fail, stopAndPersist, start }) + spinner = initializeSpinner({ + succeed, + warn, + fail, + stopAndPersist, + start, + }) space = sinon.spy(spinner.space) next = sinon.spy(spinner.next) }) @@ -125,10 +132,11 @@ describe('UTILS', function() { expect(spinner) .to.be.an('object') .with.all.keys( - 'space', 'inner', - 'succeed', + 'space', 'next', + 'succeed', + 'warn', 'fail', 'stopAndPersist' ) @@ -139,15 +147,18 @@ describe('UTILS', function() { true ) }) + it('#next() sets inner text and calls inner start', () => { + spinner.next('bar') + expect(spinner.inner.text).to.eql('bar') + expect(spinner.inner.start.callCount).to.eql(1) + }) it('#succeed() inserts a spacer and calls inner succeed', () => { spinner.succeed('foo') expect(succeed.callCount).to.eql(1) }) - it('#next() inserts a spacer, sets text, and restarts spinner', () => { - spinner.next('bar') - expect(stopAndPersist.callCount).to.eql(1) - expect(spinner.inner.text).to.eql('bar') - expect(spinner.inner.start.callCount).to.eql(1) + it('#warn() inserts a spacer and calls inner warn', () => { + spinner.warn('foo') + expect(warn.callCount).to.eql(1) }) it('#fail() handles an object or string', () => { expect(() => spinner.fail('foo')).to.throw() @@ -164,7 +175,7 @@ describe('UTILS', function() { }) it('#stopAndPersist() calls inner stopAndPersist', () => { spinner.stopAndPersist() - expect(stopAndPersist.callCount).to.eql(1) + expect(stopAndPersist.callCount).to.eql(2) }) }) }) diff --git a/yarn.lock b/yarn.lock index 33937f6..a35c8bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2013,6 +2013,14 @@ fs-extra@3.0.1: jsonfile "^3.0.0" universalify "^0.1.0" +fs-extra@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.1.tgz#7fc0c6c8957f983f57f306a24e5b9ddd8d0dd880" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + fs-readdir-recursive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560" @@ -3726,10 +3734,6 @@ mustache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.0.tgz#4028f7778b17708a489930a6e52ac3bca0da41d0" -mute@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/mute/-/mute-2.0.6.tgz#8bf10f1f285ea38c9db2630bff675c114c973ed5" - nan@^2.3.0: version "2.7.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46"