diff --git a/License.md b/License.md index bd04aba..35a9d38 100644 --- a/License.md +++ b/License.md @@ -1,4 +1,4 @@ -Copyright © 2018 William King, 2015-2018 Tomek Wiszniewski, 2016 Brian Dukes +Copyright © 2018 William King, 2018 Alex Korban, 2015-2018 Tomek Wiszniewski, 2016 Brian Dukes Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/source/elm-live.js b/source/elm-live.js index 0f460ec..f648755 100644 --- a/source/elm-live.js +++ b/source/elm-live.js @@ -34,6 +34,7 @@ module.exports = (argv, options) => { const elmServe = require('elm-serve') const chokidar = require('chokidar') const debounce = require('./debounce') + const getSourceDirs = require("./get-source-dirs") const auxiliaryBuild = execPath => { const process = spawnSync(execPath, [], { @@ -186,33 +187,60 @@ ${chalk.dim('elm-live:')} unlinkDir: 'removed' } - // Watch Elm files - const watcher = chokidar.watch('**/*.elm', { - ignoreInitial: true, - followSymlinks: false, - ignored: 'elm-stuff/generated-code/*' - }) + const packageFileNames = ['elm.json', 'elm-package.json'] - watcher.on( - 'all', - debounce((event, filePath) => { - const relativePath = path.relative(process.cwd(), filePath) - const eventName = eventNameMap[event] || event + const isPackageFilePath = (relativePath) => { + return packageFileNames.indexOf(relativePath) > -1 + } - outputStream.write( - ` + const watchElmFiles = () => { + const sourceDirs = getSourceDirs() + + outputStream.write( + ` +${chalk.dim('elm-live:')} + Watching ${sourceDirs.join(", ")}. + +` + ) + + let watcher = chokidar.watch(sourceDirs.concat(packageFileNames), { + ignoreInitial: true, + followSymlinks: false, + ignored: 'elm-stuff/generated-code/*' + }) + + watcher.on( + 'all', + debounce((event, filePath) => { + const relativePath = path.relative(process.cwd(), filePath) + const eventName = eventNameMap[event] || event + + outputStream.write( + ` ${chalk.dim('elm-live:')} You’ve ${eventName} \`${relativePath}\`. Rebuilding! - + ` - ) + ) + + const buildResult = build() + if (!serverStarted && buildResult.exitCode === SUCCESS) { + startServer() + } + + if (isPackageFilePath(relativePath)) { + // Package file changes may result in changes to the set + // of watched files + watcher.close() + watcher = watchElmFiles() + } + }), + 100 + ) + } + + watchElmFiles() - const buildResult = build() - if (!serverStarted && buildResult.exitCode === SUCCESS) { - startServer() - } - }), - 100 - ) return null } diff --git a/source/get-source-dirs.js b/source/get-source-dirs.js new file mode 100644 index 0000000..df05215 --- /dev/null +++ b/source/get-source-dirs.js @@ -0,0 +1,38 @@ +const path = require('path') +const fs = require('fs') + +const defaultElmDirs = ['**/*.elm'] + +function getSourceDirs(packageFilePath) { + try { + const elmPackage = JSON.parse(fs.readFileSync(packageFilePath)) + const sourceDirs = elmPackage['source-directories'] + if (sourceDirs !== undefined) { + return sourceDirs.map(dir => path.join(dir, '/**/*.elm')) + } + else { + return defaultElmDirs + } + } + catch (e) { + // Do nothing about the exception (in parsing JSON) because the Elm compiler + // will also parse the file and report the error - and elm-live will show it + // to the user. We don't want to report the same error twice. + return defaultElmDirs + } +} + +module.exports = (workPath = process.cwd()) => { + const elmJsonPath = path.join(workPath, 'elm.json') + const elmPackageJsonPath = path.join(workPath, 'elm-package.json') + + if (fs.existsSync(elmJsonPath)) { + return getSourceDirs(elmJsonPath) + } + else if (fs.existsSync(elmPackageJsonPath)) { + return getSourceDirs(elmPackageJsonPath) + } + else { + return defaultElmDirs + } +} \ No newline at end of file diff --git a/test.js b/test.js index 1e7b512..c1606d3 100644 --- a/test.js +++ b/test.js @@ -7,6 +7,7 @@ const devnull = require('dev-null') const qs = require('q-stream') const naked = require('strip-ansi') const debounce = require('./source/debounce') +const getSourceDirs = require('./source/get-source-dirs') const dummyConfig = { inputStream: devnull(), outputStream: devnull() } const dummyCrossSpawn = { sync: () => ({ status: 0 }) } @@ -323,9 +324,9 @@ test('Starts elmServe and chokidar with correct config', assert => { const chokidar = { watch: glob => { - assert.is( + assert.deepEqual( glob, - '**/*.elm', + ['**/*.elm', 'elm.json', 'elm-package.json'], 'watches all `*.elm` files in the current directory ' + 'and its subdirectories' ) @@ -425,7 +426,7 @@ test('`--pushstate to support client-side routing', assert => { test('Watches all `**/*.elm` files in the current directory', assert => new Promise(resolve => { - assert.plan(6) + assert.plan(7) const event = 'change' const relativePath = path.join('ab', 'c.elm') @@ -439,7 +440,8 @@ test('Watches all `**/*.elm` files in the current directory', assert => const chokidar = { watch: (target, options) => { - assert.is(target, '**/*.elm', 'passes the right glob to chokidar') + assert.deepEqual(target, ['**/*.elm', 'elm.json', 'elm-package.json'], + 'passes the right glob to chokidar') assert.true( options.ignoreInitial, @@ -494,23 +496,54 @@ test('Watches all `**/*.elm` files in the current directory', assert => inputStream: devnull(), outputStream: qs(chunk => { chunkNumber++ - if (chunkNumber !== 3) return - - assert.is( - naked(chunk), - ` + if (chunkNumber == 3) { + assert.is( + naked(chunk), + ` +elm-live: + Watching **/*.elm. + +`, + 'prints a message with watched paths' + ) + } + else if (chunkNumber == 4) { + assert.is( + naked(chunk), + ` elm-live: You’ve changed \`${relativePath}\`. Rebuilding! - + `, - 'prints a message when a file is changed' - ) - - resolve() + 'prints a message when a file is changed' + ) + + resolve() + } }) }) })) +test("gets watched paths from the Elm package file", assert => { + const testSourceDirs = (testDir, expectedDirs) => { + const sourceDirs = getSourceDirs(testDir) + const msg = `get elm dirs: ${sourceDirs}, expected: ${expectedDirs}` + assert.deepEqual(sourceDirs, expectedDirs, msg) + } + + new Promise(resolve => { + assert.plan(3) + + testSourceDirs("test/0.18.0", ["src18/**/*.elm", "../elsewhere18/src/**/*.elm"]) + + testSourceDirs("test/0.19.0", ["src19/**/*.elm", "../../elsewhere19/**/*.elm"]) + + testSourceDirs("test", ["**/*.elm"]) + + resolve() + }) +}) + test('--before-build and --after-build work', assert => new Promise(resolve => { const beforeCommand = 'run-me-beforehand' diff --git a/test/0.18.0/elm-package.json b/test/0.18.0/elm-package.json new file mode 100644 index 0000000..745f361 --- /dev/null +++ b/test/0.18.0/elm-package.json @@ -0,0 +1,16 @@ +{ + "version": "1.0.0", + "summary": "Project", + "repository": "https://github.com/user/project.git", + "license": "BSD3", + "source-directories": [ + "src18", "../elsewhere18/src" + ], + "exposed-modules": [], + "dependencies": { + "elm-lang/core": "5.1.1 <= v < 6.0.0", + "elm-lang/html": "2.0.0 <= v < 3.0.0", + "elm-lang/http": "1.0.0 <= v < 1.1.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} \ No newline at end of file diff --git a/test/0.19.0/elm.json b/test/0.19.0/elm.json new file mode 100644 index 0000000..fbee5f1 --- /dev/null +++ b/test/0.19.0/elm.json @@ -0,0 +1,24 @@ +{ + "type": "application", + "source-directories": [ + "src19", "../../elsewhere19" + ], + "elm-version": "0.19.0", + "dependencies": { + "direct": { + "elm/browser": "1.0.0", + "elm/core": "1.0.0", + "elm/html": "1.0.0" + }, + "indirect": { + "elm/json": "1.0.0", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm/virtual-dom": "1.0.2" + } + }, + "test-dependencies": { + "direct": {}, + "indirect": {} + } +} \ No newline at end of file