From 9b2a456ce048b7de83236d9df03e40b2f66bec23 Mon Sep 17 00:00:00 2001 From: Dan Freeman Date: Fri, 25 May 2018 11:18:20 -0400 Subject: [PATCH 1/4] Remove `node_modules` from chokidar's ignore regex --- lib/utilities/compile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utilities/compile.js b/lib/utilities/compile.js index 137ecc09f..027f4f9f5 100644 --- a/lib/utilities/compile.js +++ b/lib/utilities/compile.js @@ -67,7 +67,7 @@ function diagnosticCallback(callback) { function buildWatchHooks(project, sys, callbacks) { let root = escapeRegex(project.root); let sep = `[/\\\\]`; - let patterns = ['\\..*?', 'dist', 'node_modules', 'tmp']; + let patterns = ['\\..*?', 'dist', 'tmp']; let ignored = new RegExp(`^${root}${sep}(${patterns.join('|')})${sep}`); return Object.assign({}, sys, { From bc198ef9d759d16031550350737e4986889df39d Mon Sep 17 00:00:00 2001 From: Dan Freeman Date: Tue, 5 Jun 2018 22:21:49 -0400 Subject: [PATCH 2/4] Set up the ignore regex per-watch-root --- lib/utilities/compile.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/utilities/compile.js b/lib/utilities/compile.js index 027f4f9f5..e90e11d85 100644 --- a/lib/utilities/compile.js +++ b/lib/utilities/compile.js @@ -65,17 +65,17 @@ function diagnosticCallback(callback) { } function buildWatchHooks(project, sys, callbacks) { - let root = escapeRegex(project.root); - let sep = `[/\\\\]`; - let patterns = ['\\..*?', 'dist', 'tmp']; - let ignored = new RegExp(`^${root}${sep}(${patterns.join('|')})${sep}`); + let ignorePatterns = ['\\..*?', 'dist', 'tmp', 'node_modules']; return Object.assign({}, sys, { watchFile: null, watchDirectory(dir, callback) { if (!fs.existsSync(dir)) return; - let watcher = chokidar.watch(dir, { ignored, ignoreInitial: true }); + let watcher = chokidar.watch(dir, { + ignored: buildIgnoreRegex(dir, ignorePatterns), + ignoreInitial: true + }); watcher.on('all', (type, path) => { callback(path); @@ -89,3 +89,9 @@ function buildWatchHooks(project, sys, callbacks) { } }); } + +function buildIgnoreRegex(rootDir, patterns) { + let base = escapeRegex(rootDir); + let sep = `[/\\\\]`; + return new RegExp(`^${base}${sep}(${patterns.join('|')})${sep}`); +} From 74e053ac0e61ab25a55a5ededf6bcf5c97e9ae38 Mon Sep 17 00:00:00 2001 From: Dan Freeman Date: Mon, 18 Jun 2018 17:27:02 -0400 Subject: [PATCH 3/4] De-simplify the watcher --- lib/utilities/compile.js | 74 +++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/lib/utilities/compile.js b/lib/utilities/compile.js index e90e11d85..6b1ac6440 100644 --- a/lib/utilities/compile.js +++ b/lib/utilities/compile.js @@ -4,7 +4,10 @@ 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 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 +17,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 +33,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 +49,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,23 +67,56 @@ function diagnosticCallback(callback) { } } -function buildWatchHooks(project, sys, callbacks) { +function buildWatchHooks(project, ts, callbacks) { let ignorePatterns = ['\\..*?', 'dist', 'tmp', 'node_modules']; + let watchedFiles = new Map(); + + return Object.assign({}, ts.sys, { + watchFile(file, callback) { + let key = path.resolve(file); + if (!watchedFiles.has(key)) { + debug(`watching file %s`, key); + watchedFiles.set(key, []); + } + + watchedFiles.get(key).push(callback); + + return { + close() { + let callbacks = watchedFiles.get(key); + if (callbacks.length === 1) { + debug(`unwatching file %s`, key); + watchedFiles.delete(key); + } else { + callbacks.splice(callbacks.indexOf(callback), 1); + } + } + }; + }, - return Object.assign({}, sys, { - watchFile: null, watchDirectory(dir, callback) { - if (!fs.existsSync(dir)) return; - - let watcher = chokidar.watch(dir, { - ignored: buildIgnoreRegex(dir, ignorePatterns), - ignoreInitial: true - }); - - watcher.on('all', (type, path) => { - callback(path); + if (!fs.existsSync(dir)) { + debug(`skipping watch for nonexistent directory %s`, dir); + return; + } + + let ignored = buildIgnoreRegex(dir, ignorePatterns); + let watcher = chokidar.watch(dir, { ignored, ignoreInitial: true }); + debug(`watching directory %s %o`, dir, { ignored }); + + watcher.on('all', (type, rawPath) => { + let resolvedPath = path.resolve(rawPath); + + if (watchedFiles.has(resolvedPath) && (type === 'change' || type === 'unlink')) { + debug(`%s: watched path %s`, type, resolvedPath); + let kind = type === 'change' ? ts.FileWatcherEventKind.Changed : ts.FileWatcherEventKind.Deleted; + watchedFiles.get(resolvedPath).forEach(callback => callback(resolvedPath, kind)); + } else { + debug(`%s: unwatched path %s (for directory watch on %s)`, type, resolvedPath, dir); + callback(resolvedPath); + } - if (path.endsWith('.ts') && callbacks.watchedFileChanged) { + if (resolvedPath.endsWith('.ts') && callbacks.watchedFileChanged) { callbacks.watchedFileChanged(); } }); From 0fd9817ab666dae15072c898940086f6bb62a996 Mon Sep 17 00:00:00 2001 From: Dan Freeman Date: Tue, 19 Jun 2018 14:08:23 -0400 Subject: [PATCH 4/4] Deal with case {in,over}sensitivity --- blueprints/ember-cli-typescript/index.js | 2 +- lib/utilities/compile.js | 59 ++++++++----------- .../blueprints/ember-cli-typescript-test.js | 10 ++-- package.json | 1 + 4 files changed, 30 insertions(+), 42 deletions(-) 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 6b1ac6440..9576af3bb 100644 --- a/lib/utilities/compile.js +++ b/lib/utilities/compile.js @@ -5,6 +5,7 @@ const chokidar = require('chokidar'); const fs = require('fs-extra'); const escapeRegex = require('escape-string-regexp'); 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'); @@ -69,37 +70,16 @@ function diagnosticCallback(callback) { function buildWatchHooks(project, ts, callbacks) { let ignorePatterns = ['\\..*?', 'dist', 'tmp', 'node_modules']; - let watchedFiles = new Map(); return Object.assign({}, ts.sys, { - watchFile(file, callback) { - let key = path.resolve(file); - if (!watchedFiles.has(key)) { - debug(`watching file %s`, key); - watchedFiles.set(key, []); - } - - watchedFiles.get(key).push(callback); - - return { - close() { - let callbacks = watchedFiles.get(key); - if (callbacks.length === 1) { - debug(`unwatching file %s`, key); - watchedFiles.delete(key); - } else { - callbacks.splice(callbacks.indexOf(callback), 1); - } - } - }; - }, - - watchDirectory(dir, callback) { - if (!fs.existsSync(dir)) { - debug(`skipping watch for nonexistent directory %s`, dir); + watchFile: null, + 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 }); @@ -107,21 +87,20 @@ function buildWatchHooks(project, ts, callbacks) { watcher.on('all', (type, rawPath) => { let resolvedPath = path.resolve(rawPath); - if (watchedFiles.has(resolvedPath) && (type === 'change' || type === 'unlink')) { - debug(`%s: watched path %s`, type, resolvedPath); - let kind = type === 'change' ? ts.FileWatcherEventKind.Changed : ts.FileWatcherEventKind.Deleted; - watchedFiles.get(resolvedPath).forEach(callback => callback(resolvedPath, kind)); - } else { - debug(`%s: unwatched path %s (for directory watch on %s)`, type, resolvedPath, dir); - callback(resolvedPath); - } + 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(); + } + }; } }); } @@ -129,5 +108,13 @@ function buildWatchHooks(project, ts, callbacks) { function buildIgnoreRegex(rootDir, patterns) { let base = escapeRegex(rootDir); let sep = `[/\\\\]`; - return new RegExp(`^${base}${sep}(${patterns.join('|')})${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 a78245c8c..b2accdd1a 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",