watchman.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. 'use strict';
  2. function path() {
  3. const data = _interopRequireWildcard(require('path'));
  4. path = function () {
  5. return data;
  6. };
  7. return data;
  8. }
  9. function _fbWatchman() {
  10. const data = _interopRequireDefault(require('fb-watchman'));
  11. _fbWatchman = function () {
  12. return data;
  13. };
  14. return data;
  15. }
  16. function _constants() {
  17. const data = _interopRequireDefault(require('../constants'));
  18. _constants = function () {
  19. return data;
  20. };
  21. return data;
  22. }
  23. function fastPath() {
  24. const data = _interopRequireWildcard(require('../lib/fast_path'));
  25. fastPath = function () {
  26. return data;
  27. };
  28. return data;
  29. }
  30. function _normalizePathSep() {
  31. const data = _interopRequireDefault(require('../lib/normalizePathSep'));
  32. _normalizePathSep = function () {
  33. return data;
  34. };
  35. return data;
  36. }
  37. function _interopRequireDefault(obj) {
  38. return obj && obj.__esModule ? obj : {default: obj};
  39. }
  40. function _getRequireWildcardCache() {
  41. if (typeof WeakMap !== 'function') return null;
  42. var cache = new WeakMap();
  43. _getRequireWildcardCache = function () {
  44. return cache;
  45. };
  46. return cache;
  47. }
  48. function _interopRequireWildcard(obj) {
  49. if (obj && obj.__esModule) {
  50. return obj;
  51. }
  52. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  53. return {default: obj};
  54. }
  55. var cache = _getRequireWildcardCache();
  56. if (cache && cache.has(obj)) {
  57. return cache.get(obj);
  58. }
  59. var newObj = {};
  60. var hasPropertyDescriptor =
  61. Object.defineProperty && Object.getOwnPropertyDescriptor;
  62. for (var key in obj) {
  63. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  64. var desc = hasPropertyDescriptor
  65. ? Object.getOwnPropertyDescriptor(obj, key)
  66. : null;
  67. if (desc && (desc.get || desc.set)) {
  68. Object.defineProperty(newObj, key, desc);
  69. } else {
  70. newObj[key] = obj[key];
  71. }
  72. }
  73. }
  74. newObj.default = obj;
  75. if (cache) {
  76. cache.set(obj, newObj);
  77. }
  78. return newObj;
  79. }
  80. /**
  81. * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  82. *
  83. * This source code is licensed under the MIT license found in the
  84. * LICENSE file in the root directory of this source tree.
  85. */
  86. const watchmanURL = 'https://facebook.github.io/watchman/docs/troubleshooting';
  87. function WatchmanError(error) {
  88. error.message =
  89. `Watchman error: ${error.message.trim()}. Make sure watchman ` +
  90. `is running for this project. See ${watchmanURL}.`;
  91. return error;
  92. }
  93. module.exports = async function watchmanCrawl(options) {
  94. const fields = ['name', 'exists', 'mtime_ms', 'size'];
  95. const {data, extensions, ignore, rootDir, roots} = options;
  96. const defaultWatchExpression = [
  97. 'allof',
  98. ['type', 'f'],
  99. ['anyof', ...extensions.map(extension => ['suffix', extension])]
  100. ];
  101. const clocks = data.clocks;
  102. const client = new (_fbWatchman().default.Client)();
  103. let clientError;
  104. client.on('error', error => (clientError = WatchmanError(error))); // TODO: type better than `any`
  105. const cmd = (...args) =>
  106. new Promise((resolve, reject) =>
  107. client.command(args, (error, result) =>
  108. error ? reject(WatchmanError(error)) : resolve(result)
  109. )
  110. );
  111. if (options.computeSha1) {
  112. const {capabilities} = await cmd('list-capabilities');
  113. if (capabilities.indexOf('field-content.sha1hex') !== -1) {
  114. fields.push('content.sha1hex');
  115. }
  116. }
  117. async function getWatchmanRoots(roots) {
  118. const watchmanRoots = new Map();
  119. await Promise.all(
  120. roots.map(async root => {
  121. const response = await cmd('watch-project', root);
  122. const existing = watchmanRoots.get(response.watch); // A root can only be filtered if it was never seen with a
  123. // relative_path before.
  124. const canBeFiltered = !existing || existing.length > 0;
  125. if (canBeFiltered) {
  126. if (response.relative_path) {
  127. watchmanRoots.set(
  128. response.watch,
  129. (existing || []).concat(response.relative_path)
  130. );
  131. } else {
  132. // Make the filter directories an empty array to signal that this
  133. // root was already seen and needs to be watched for all files or
  134. // directories.
  135. watchmanRoots.set(response.watch, []);
  136. }
  137. }
  138. })
  139. );
  140. return watchmanRoots;
  141. }
  142. async function queryWatchmanForDirs(rootProjectDirMappings) {
  143. const files = new Map();
  144. let isFresh = false;
  145. await Promise.all(
  146. Array.from(rootProjectDirMappings).map(
  147. async ([root, directoryFilters]) => {
  148. const expression = Array.from(defaultWatchExpression);
  149. const glob = [];
  150. if (directoryFilters.length > 0) {
  151. expression.push([
  152. 'anyof',
  153. ...directoryFilters.map(dir => ['dirname', dir])
  154. ]);
  155. for (const directory of directoryFilters) {
  156. for (const extension of extensions) {
  157. glob.push(`${directory}/**/*.${extension}`);
  158. }
  159. }
  160. } else {
  161. for (const extension of extensions) {
  162. glob.push(`**/*.${extension}`);
  163. }
  164. }
  165. const relativeRoot = fastPath().relative(rootDir, root);
  166. const query = clocks.has(relativeRoot) // Use the `since` generator if we have a clock available
  167. ? {
  168. expression,
  169. fields,
  170. since: clocks.get(relativeRoot)
  171. } // Otherwise use the `glob` filter
  172. : {
  173. expression,
  174. fields,
  175. glob,
  176. glob_includedotfiles: true
  177. };
  178. const response = await cmd('query', root, query);
  179. if ('warning' in response) {
  180. console.warn('watchman warning: ', response.warning);
  181. }
  182. isFresh = isFresh || response.is_fresh_instance;
  183. files.set(root, response);
  184. }
  185. )
  186. );
  187. return {
  188. files,
  189. isFresh
  190. };
  191. }
  192. let files = data.files;
  193. let removedFiles = new Map();
  194. const changedFiles = new Map();
  195. let watchmanFiles;
  196. let isFresh = false;
  197. try {
  198. const watchmanRoots = await getWatchmanRoots(roots);
  199. const watchmanFileResults = await queryWatchmanForDirs(watchmanRoots); // Reset the file map if watchman was restarted and sends us a list of
  200. // files.
  201. if (watchmanFileResults.isFresh) {
  202. files = new Map();
  203. removedFiles = new Map(data.files);
  204. isFresh = true;
  205. }
  206. watchmanFiles = watchmanFileResults.files;
  207. } finally {
  208. client.end();
  209. }
  210. if (clientError) {
  211. throw clientError;
  212. } // TODO: remove non-null
  213. for (const [watchRoot, response] of watchmanFiles) {
  214. const fsRoot = (0, _normalizePathSep().default)(watchRoot);
  215. const relativeFsRoot = fastPath().relative(rootDir, fsRoot);
  216. clocks.set(relativeFsRoot, response.clock);
  217. for (const fileData of response.files) {
  218. const filePath =
  219. fsRoot + path().sep + (0, _normalizePathSep().default)(fileData.name);
  220. const relativeFilePath = fastPath().relative(rootDir, filePath);
  221. const existingFileData = data.files.get(relativeFilePath); // If watchman is fresh, the removed files map starts with all files
  222. // and we remove them as we verify they still exist.
  223. if (isFresh && existingFileData && fileData.exists) {
  224. removedFiles.delete(relativeFilePath);
  225. }
  226. if (!fileData.exists) {
  227. // No need to act on files that do not exist and were not tracked.
  228. if (existingFileData) {
  229. files.delete(relativeFilePath); // If watchman is not fresh, we will know what specific files were
  230. // deleted since we last ran and can track only those files.
  231. if (!isFresh) {
  232. removedFiles.set(relativeFilePath, existingFileData);
  233. }
  234. }
  235. } else if (!ignore(filePath)) {
  236. const mtime =
  237. typeof fileData.mtime_ms === 'number'
  238. ? fileData.mtime_ms
  239. : fileData.mtime_ms.toNumber();
  240. const size = fileData.size;
  241. let sha1hex = fileData['content.sha1hex'];
  242. if (typeof sha1hex !== 'string' || sha1hex.length !== 40) {
  243. sha1hex = null;
  244. }
  245. let nextData;
  246. if (
  247. existingFileData &&
  248. existingFileData[_constants().default.MTIME] === mtime
  249. ) {
  250. nextData = existingFileData;
  251. } else if (
  252. existingFileData &&
  253. sha1hex &&
  254. existingFileData[_constants().default.SHA1] === sha1hex
  255. ) {
  256. nextData = [
  257. existingFileData[0],
  258. mtime,
  259. existingFileData[2],
  260. existingFileData[3],
  261. existingFileData[4],
  262. existingFileData[5]
  263. ];
  264. } else {
  265. // See ../constants.ts
  266. nextData = ['', mtime, size, 0, '', sha1hex];
  267. }
  268. files.set(relativeFilePath, nextData);
  269. changedFiles.set(relativeFilePath, nextData);
  270. }
  271. }
  272. }
  273. data.files = files;
  274. return {
  275. changedFiles: isFresh ? undefined : changedFiles,
  276. hasteMap: data,
  277. removedFiles
  278. };
  279. };