diff --git a/blueprints/ember-cli-typescript/index.js b/blueprints/ember-cli-typescript/index.js index fd1ee7358..a23733821 100644 --- a/blueprints/ember-cli-typescript/index.js +++ b/blueprints/ember-cli-typescript/index.js @@ -40,7 +40,7 @@ module.exports = { } return { - includes: JSON.stringify(includes, null, 2).replace(/\n/g, '\n '), + includes: JSON.stringify(includes.map(include => `${include}/**/*`), null, 2).replace(/\n/g, '\n '), pathsFor: dasherizedName => { let appName = isAddon ? 'dummy' : dasherizedName; let paths = { diff --git a/lib/utilities/compile.js b/lib/utilities/compile.js index 137ecc09f..9576af3bb 100644 --- a/lib/utilities/compile.js +++ b/lib/utilities/compile.js @@ -4,7 +4,11 @@ const chokidar = require('chokidar'); const fs = require('fs-extra'); const escapeRegex = require('escape-string-regexp'); -const debug = require('debug')('ember-cli-typescript:tsc:trace'); +const path = require('path'); +const glob = require('glob'); + +const trace = require('debug')('ember-cli-typescript:tsc:trace'); +const debug = require('debug')('ember-cli-typescript:watcher'); module.exports = function compile(project, tsOptions, callbacks) { // Ensure the output directory is created even if no files are generated @@ -14,8 +18,8 @@ module.exports = function compile(project, tsOptions, callbacks) { rootDir: project.root, allowJs: false, noEmit: false, - diagnostics: debug.enabled, - extendedDiagnostics: debug.enabled + diagnostics: trace.enabled, + extendedDiagnostics: trace.enabled }, tsOptions); let ts = project.require('typescript'); @@ -30,7 +34,7 @@ function createWatchCompilerHost(ts, options, project, callbacks) { let host = ts.createWatchCompilerHost( configPath, options, - buildWatchHooks(project, ts.sys, callbacks), + buildWatchHooks(project, ts, callbacks), createProgram, diagnosticCallback(callbacks.reportDiagnostic), diagnosticCallback(callbacks.reportWatchStatus) @@ -46,8 +50,8 @@ function createWatchCompilerHost(ts, options, project, callbacks) { } }; - if (debug.enabled) { - host.trace = str => debug(str.trim()); + if (trace.enabled) { + host.trace = str => trace(str.trim()); } return host; @@ -64,28 +68,53 @@ function diagnosticCallback(callback) { } } -function buildWatchHooks(project, sys, callbacks) { - let root = escapeRegex(project.root); - let sep = `[/\\\\]`; - let patterns = ['\\..*?', 'dist', 'node_modules', 'tmp']; - let ignored = new RegExp(`^${root}${sep}(${patterns.join('|')})${sep}`); +function buildWatchHooks(project, ts, callbacks) { + let ignorePatterns = ['\\..*?', 'dist', 'tmp', 'node_modules']; - return Object.assign({}, sys, { + return Object.assign({}, ts.sys, { watchFile: null, - watchDirectory(dir, callback) { - if (!fs.existsSync(dir)) return; - + watchDirectory(rawDir, callback) { + if (!fs.existsSync(rawDir)) { + debug(`skipping watch for nonexistent directory %s`, rawDir); + return; + } + + let dir = getCanonicalCapitalization(path.resolve(rawDir)); + let ignored = buildIgnoreRegex(dir, ignorePatterns); let watcher = chokidar.watch(dir, { ignored, ignoreInitial: true }); + debug(`watching directory %s %o`, dir, { ignored }); - watcher.on('all', (type, path) => { - callback(path); + watcher.on('all', (type, rawPath) => { + let resolvedPath = path.resolve(rawPath); - if (path.endsWith('.ts') && callbacks.watchedFileChanged) { + debug(`%s: %s (for directory watch on %s)`, type, resolvedPath, dir); + callback(resolvedPath); + + if (resolvedPath.endsWith('.ts') && callbacks.watchedFileChanged) { callbacks.watchedFileChanged(); } }); - return watcher; + return { + close() { + debug('closing watcher for %s', dir); + watcher.close(); + } + }; } }); } + +function buildIgnoreRegex(rootDir, patterns) { + let base = escapeRegex(rootDir); + let sep = `[/\\\\]`; + return new RegExp(`^${base}${sep}(${patterns.join('|')})${sep}`, 'i'); +} + +// On case-insensitive file systems, tsc will normalize paths to be all lowercase, +// but chokidar expects the paths it's given to watch to exactly match what it's +// delivered in fs events. +function getCanonicalCapitalization(rawPath) { + let normalized = rawPath.replace(/\\/g, '/').replace(/^[a-z]:/i, ''); + return glob.sync(normalized, { nocase: true })[0]; +} diff --git a/node-tests/blueprints/ember-cli-typescript-test.js b/node-tests/blueprints/ember-cli-typescript-test.js index 382a115ad..3116b437e 100644 --- a/node-tests/blueprints/ember-cli-typescript-test.js +++ b/node-tests/blueprints/ember-cli-typescript-test.js @@ -71,7 +71,7 @@ describe('Acceptance: ember-cli-typescript generator', function() { expect(tsconfigJson.compilerOptions.inlineSourceMap).to.equal(true); expect(tsconfigJson.compilerOptions.inlineSources).to.equal(true); - expect(tsconfigJson.include).to.deep.equal(['app', 'tests', 'types']); + expect(tsconfigJson.include).to.deep.equal(['app/**/*', 'tests/**/*', 'types/**/*']); const projectTypes = file('types/my-app/index.d.ts'); expect(projectTypes).to.exist; @@ -116,7 +116,7 @@ describe('Acceptance: ember-cli-typescript generator', function() { '*': ['types/*'], }); - expect(tsconfigJson.include).to.deep.equal(['app', 'addon', 'tests', 'types']); + expect(tsconfigJson.include).to.deep.equal(['app/**/*', 'addon/**/*', 'tests/**/*', 'types/**/*']); const projectTypes = file('types/dummy/index.d.ts'); expect(projectTypes).to.exist; @@ -159,7 +159,7 @@ describe('Acceptance: ember-cli-typescript generator', function() { '*': ['types/*'], }); - expect(json.include).to.deep.equal(['app', 'tests', 'types', 'lib/my-addon-1', 'lib/my-addon-2']); + expect(json.include).to.deep.equal(['app/**/*', 'tests/**/*', 'types/**/*', 'lib/my-addon-1/**/*', 'lib/my-addon-2/**/*']); const projectTypes = file('types/my-app/index.d.ts'); expect(projectTypes).to.exist; @@ -194,7 +194,7 @@ describe('Acceptance: ember-cli-typescript generator', function() { '*': ['types/*'], }); - expect(json.include).to.deep.equal(['app', 'tests', 'types', 'mirage']); + expect(json.include).to.deep.equal(['app/**/*', 'tests/**/*', 'types/**/*', 'mirage/**/*']); }); }); @@ -224,7 +224,7 @@ describe('Acceptance: ember-cli-typescript generator', function() { '*': ['types/*'], }); - expect(json.include).to.deep.equal(['app', 'addon', 'tests', 'types']); + expect(json.include).to.deep.equal(['app/**/*', 'addon/**/*', 'tests/**/*', 'types/**/*']); }); }); diff --git a/package.json b/package.json index 2e800b329..d785ce8c2 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "execa": "^0.9.0", "exists-sync": "^0.0.4", "fs-extra": "^5.0.0", + "glob": "^7.1.2", "inflection": "^1.12.0", "resolve": "^1.5.0", "rimraf": "^2.6.2",