diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index 945f68edc9bc..ed14bf213774 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -34,105 +34,126 @@ module.exports = async function watchmanCrawl( ['type', 'f'], ['anyof'].concat(extensions.map(extension => ['suffix', extension])), ]; + const clocks = data.clocks; + const client = new watchman.Client(); let clientError; - client.on('error', error => (clientError = error)); + client.on('error', error => (clientError = WatchmanError(error))); const cmd = (...args) => new Promise((resolve, reject) => client.command( args, - (error, result) => (error ? reject(error) : resolve(result)), + (error, result) => + error ? reject(WatchmanError(error)) : resolve(result), ), ); - const clocks = data.clocks; - let files = data.files; - - try { + async function getWatchmanRoots(roots) { const watchmanRoots = new Map(); - for (const root of roots) { - const response = await cmd('watch-project', root); - const existing = watchmanRoots.get(response.watch); - // A root can only be filtered if it was never seen with a relative_path before - const canBeFiltered = !existing || existing.length > 0; - - if (canBeFiltered) { - if (response.relative_path) { - watchmanRoots.set( - response.watch, - (existing || []).concat(response.relative_path), - ); - } else { - // Make the filter directories an empty array to signal that this root - // was already seen and needs to be watched for all files/directories - watchmanRoots.set(response.watch, []); + await Promise.all( + roots.map(async root => { + const response = await cmd('watch-project', root); + const existing = watchmanRoots.get(response.watch); + // A root can only be filtered if it was never seen with a relative_path before + const canBeFiltered = !existing || existing.length > 0; + + if (canBeFiltered) { + if (response.relative_path) { + watchmanRoots.set( + response.watch, + (existing || []).concat(response.relative_path), + ); + } else { + // Make the filter directories an empty array to signal that this root + // was already seen and needs to be watched for all files/directories + watchmanRoots.set(response.watch, []); + } } - } - } + }), + ); + return watchmanRoots; + } - let shouldReset = false; - const watchmanFileResults = new Map(); - for (const [root, directoryFilters] of watchmanRoots) { - const expression = Array.from(defaultWatchExpression); - if (directoryFilters.length > 0) { - expression.push([ - 'anyof', - ...directoryFilters.map(dir => ['dirname', dir]), - ]); - } - const fields = ['name', 'exists', 'mtime_ms']; + async function queryWatchmanForDirs(rootProjectDirMappings) { + const files = new Map(); + let isFresh = false; + await Promise.all( + Array.from(rootProjectDirMappings).map( + async ([root, directoryFilters]) => { + const expression = Array.from(defaultWatchExpression); + if (directoryFilters.length > 0) { + expression.push([ + 'anyof', + ...directoryFilters.map(dir => ['dirname', dir]), + ]); + } + const fields = ['name', 'exists', 'mtime_ms']; - const query = clocks[root] - ? // Use the `since` generator if we have a clock available - {expression, fields, since: clocks[root]} - : // Otherwise use the `suffix` generator - {expression, fields, suffix: extensions}; + const query = clocks[root] + ? // Use the `since` generator if we have a clock available + {expression, fields, since: clocks[root]} + : // Otherwise use the `suffix` generator + {expression, fields, suffix: extensions}; - const response = await cmd('query', root, query); - shouldReset = shouldReset || response.is_fresh_instance; - watchmanFileResults.set(root, response); - } + const response = await cmd('query', root, query); + if ('warning' in response) { + console.warn('watchman warning: ', response.warning); + } + isFresh = isFresh || response.is_fresh_instance; + files.set(root, response); + }, + ), + ); + + return { + files, + isFresh, + }; + } + let files = data.files; + let watchmanFiles; + try { + const watchmanRoots = await getWatchmanRoots(roots); + const watchmanFileResults = await queryWatchmanForDirs(watchmanRoots); // Reset the file map if watchman was restarted and sends us a list of files. - if (shouldReset) { + if (watchmanFileResults.isFresh) { files = Object.create(null); } - - for (const [watchRoot, response] of watchmanFileResults) { - const fsRoot = normalizePathSep(watchRoot); - if ('warning' in response) { - console.warn('watchman warning: ', response.warning); - } - clocks[fsRoot] = response.clock; - for (const fileData of response.files) { - const name = fsRoot + path.sep + normalizePathSep(fileData.name); - if (!fileData.exists) { - delete files[name]; - } else if (!ignore(name)) { - const mtime = - typeof fileData.mtime_ms === 'number' - ? fileData.mtime_ms - : fileData.mtime_ms.toNumber(); - const isOld = data.files[name] && data.files[name][H.MTIME] === mtime; - if (isOld) { - files[name] = data.files[name]; - } else { - // See ../constants.js - files[name] = ['', mtime, 0, []]; - } - } - } - } - } catch (error) { - throw WatchmanError(error); + watchmanFiles = watchmanFileResults.files; } finally { client.end(); } if (clientError) { - throw WatchmanError(clientError); + throw clientError; } + + for (const [watchRoot, response] of watchmanFiles) { + const fsRoot = normalizePathSep(watchRoot); + clocks[fsRoot] = response.clock; + for (const fileData of response.files) { + const name = fsRoot + path.sep + normalizePathSep(fileData.name); + if (!fileData.exists) { + delete files[name]; + } else if (!ignore(name)) { + const mtime = + typeof fileData.mtime_ms === 'number' + ? fileData.mtime_ms + : fileData.mtime_ms.toNumber(); + const existingFileData = data.files[name]; + const isOld = existingFileData && existingFileData[H.MTIME] === mtime; + if (isOld) { + files[name] = existingFileData; + } else { + // See ../constants.js + files[name] = ['', mtime, 0, []]; + } + } + } + } + data.files = files; return data; };