Added test to ensure that release notes are parsed properly Updated to latest dependencies to fix security vulnerability Updated GH Actions Workflow to run test before updating tag Updated README.md to reflect proper whatsnew documentation.
1391 lines
38 KiB
JavaScript
1391 lines
38 KiB
JavaScript
'use strict';
|
|
|
|
function _child_process() {
|
|
const data = require('child_process');
|
|
|
|
_child_process = function _child_process() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _crypto() {
|
|
const data = _interopRequireDefault(require('crypto'));
|
|
|
|
_crypto = function _crypto() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _events() {
|
|
const data = _interopRequireDefault(require('events'));
|
|
|
|
_events = function _events() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _os() {
|
|
const data = _interopRequireDefault(require('os'));
|
|
|
|
_os = function _os() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _path() {
|
|
const data = _interopRequireDefault(require('path'));
|
|
|
|
_path = function _path() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _sane() {
|
|
const data = _interopRequireDefault(require('sane'));
|
|
|
|
_sane = function _sane() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _invariant() {
|
|
const data = _interopRequireDefault(require('invariant'));
|
|
|
|
_invariant = function _invariant() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _jestSerializer() {
|
|
const data = _interopRequireDefault(require('jest-serializer'));
|
|
|
|
_jestSerializer = function _jestSerializer() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
function _jestWorker() {
|
|
const data = _interopRequireDefault(require('jest-worker'));
|
|
|
|
_jestWorker = function _jestWorker() {
|
|
return data;
|
|
};
|
|
|
|
return data;
|
|
}
|
|
|
|
var _worker = require('./worker');
|
|
|
|
var _getMockName = _interopRequireDefault(require('./getMockName'));
|
|
|
|
var _getPlatformExtension = _interopRequireDefault(
|
|
require('./lib/getPlatformExtension')
|
|
);
|
|
|
|
var _constants = _interopRequireDefault(require('./constants'));
|
|
|
|
var _HasteFS = _interopRequireDefault(require('./HasteFS'));
|
|
|
|
var _ModuleMap = _interopRequireDefault(require('./ModuleMap'));
|
|
|
|
var _node = _interopRequireDefault(require('./crawlers/node'));
|
|
|
|
var _normalizePathSep = _interopRequireDefault(
|
|
require('./lib/normalizePathSep')
|
|
);
|
|
|
|
var _watchman = _interopRequireDefault(require('./crawlers/watchman'));
|
|
|
|
var _WatchmanWatcher = _interopRequireDefault(require('./lib/WatchmanWatcher'));
|
|
|
|
var _FSEventsWatcher = _interopRequireDefault(require('./lib/FSEventsWatcher'));
|
|
|
|
var fastPath = _interopRequireWildcard(require('./lib/fast_path'));
|
|
|
|
function _interopRequireWildcard(obj) {
|
|
if (obj && obj.__esModule) {
|
|
return obj;
|
|
} else {
|
|
var newObj = {};
|
|
if (obj != null) {
|
|
for (var key in obj) {
|
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
var desc =
|
|
Object.defineProperty && Object.getOwnPropertyDescriptor
|
|
? Object.getOwnPropertyDescriptor(obj, key)
|
|
: {};
|
|
if (desc.get || desc.set) {
|
|
Object.defineProperty(newObj, key, desc);
|
|
} else {
|
|
newObj[key] = obj[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
newObj.default = obj;
|
|
return newObj;
|
|
}
|
|
}
|
|
|
|
function _interopRequireDefault(obj) {
|
|
return obj && obj.__esModule ? obj : {default: obj};
|
|
}
|
|
|
|
function _slicedToArray(arr, i) {
|
|
return (
|
|
_arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest()
|
|
);
|
|
}
|
|
|
|
function _nonIterableRest() {
|
|
throw new TypeError('Invalid attempt to destructure non-iterable instance');
|
|
}
|
|
|
|
function _iterableToArrayLimit(arr, i) {
|
|
var _arr = [];
|
|
var _n = true;
|
|
var _d = false;
|
|
var _e = undefined;
|
|
try {
|
|
for (
|
|
var _i = arr[Symbol.iterator](), _s;
|
|
!(_n = (_s = _i.next()).done);
|
|
_n = true
|
|
) {
|
|
_arr.push(_s.value);
|
|
if (i && _arr.length === i) break;
|
|
}
|
|
} catch (err) {
|
|
_d = true;
|
|
_e = err;
|
|
} finally {
|
|
try {
|
|
if (!_n && _i['return'] != null) _i['return']();
|
|
} finally {
|
|
if (_d) throw _e;
|
|
}
|
|
}
|
|
return _arr;
|
|
}
|
|
|
|
function _arrayWithHoles(arr) {
|
|
if (Array.isArray(arr)) return arr;
|
|
}
|
|
|
|
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
|
|
try {
|
|
var info = gen[key](arg);
|
|
var value = info.value;
|
|
} catch (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
if (info.done) {
|
|
resolve(value);
|
|
} else {
|
|
Promise.resolve(value).then(_next, _throw);
|
|
}
|
|
}
|
|
|
|
function _asyncToGenerator(fn) {
|
|
return function() {
|
|
var self = this,
|
|
args = arguments;
|
|
return new Promise(function(resolve, reject) {
|
|
var gen = fn.apply(self, args);
|
|
function _next(value) {
|
|
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
|
|
}
|
|
function _throw(err) {
|
|
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
|
|
}
|
|
_next(undefined);
|
|
});
|
|
};
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
const CHANGE_INTERVAL = 30;
|
|
const MAX_WAIT_TIME = 240000;
|
|
|
|
const NODE_MODULES = _path().default.sep + 'node_modules' + _path().default.sep;
|
|
|
|
const PACKAGE_JSON = _path().default.sep + 'package.json'; // TypeScript doesn't like us importing from outside `rootDir`, but it doesn't
|
|
// understand `require`.
|
|
|
|
const _require = require('../package.json'),
|
|
VERSION = _require.version;
|
|
|
|
const canUseWatchman = (() => {
|
|
try {
|
|
(0, _child_process().execSync)('watchman --version', {
|
|
stdio: ['ignore']
|
|
});
|
|
return true;
|
|
} catch (e) {}
|
|
|
|
return false;
|
|
})();
|
|
|
|
const escapePathSeparator = string =>
|
|
_path().default.sep === '\\' ? string.replace(/(\/|\\)/g, '\\\\') : string;
|
|
|
|
const getWhiteList = list => {
|
|
if (list && list.length) {
|
|
const newList = list.map(item =>
|
|
escapePathSeparator(item.replace(/(\/)/g, _path().default.sep))
|
|
);
|
|
return new RegExp(
|
|
'(' +
|
|
escapePathSeparator(NODE_MODULES) +
|
|
'(?:' +
|
|
newList.join('|') +
|
|
')(?=$|' +
|
|
escapePathSeparator(_path().default.sep) +
|
|
'))',
|
|
'g'
|
|
);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
/**
|
|
* HasteMap is a JavaScript implementation of Facebook's haste module system.
|
|
*
|
|
* This implementation is inspired by https://github.com/facebook/node-haste
|
|
* and was built with for high-performance in large code repositories with
|
|
* hundreds of thousands of files. This implementation is scalable and provides
|
|
* predictable performance.
|
|
*
|
|
* Because the haste map creation and synchronization is critical to startup
|
|
* performance and most tasks are blocked by I/O this class makes heavy use of
|
|
* synchronous operations. It uses worker processes for parallelizing file
|
|
* access and metadata extraction.
|
|
*
|
|
* The data structures created by `jest-haste-map` can be used directly from the
|
|
* cache without further processing. The metadata objects in the `files` and
|
|
* `map` objects contain cross-references: a metadata object from one can look
|
|
* up the corresponding metadata object in the other map. Note that in most
|
|
* projects, the number of files will be greater than the number of haste
|
|
* modules one module can refer to many files based on platform extensions.
|
|
*
|
|
* type HasteMap = {
|
|
* clocks: WatchmanClocks,
|
|
* files: {[filepath: string]: FileMetaData},
|
|
* map: {[id: string]: ModuleMapItem},
|
|
* mocks: {[id: string]: string},
|
|
* }
|
|
*
|
|
* // Watchman clocks are used for query synchronization and file system deltas.
|
|
* type WatchmanClocks = {[filepath: string]: string};
|
|
*
|
|
* type FileMetaData = {
|
|
* id: ?string, // used to look up module metadata objects in `map`.
|
|
* mtime: number, // check for outdated files.
|
|
* size: number, // size of the file in bytes.
|
|
* visited: boolean, // whether the file has been parsed or not.
|
|
* dependencies: Array<string>, // all relative dependencies of this file.
|
|
* sha1: ?string, // SHA-1 of the file, if requested via options.
|
|
* };
|
|
*
|
|
* // Modules can be targeted to a specific platform based on the file name.
|
|
* // Example: platform.ios.js and Platform.android.js will both map to the same
|
|
* // `Platform` module. The platform should be specified during resolution.
|
|
* type ModuleMapItem = {[platform: string]: ModuleMetaData};
|
|
*
|
|
* //
|
|
* type ModuleMetaData = {
|
|
* path: string, // the path to look up the file object in `files`.
|
|
* type: string, // the module type (either `package` or `module`).
|
|
* };
|
|
*
|
|
* Note that the data structures described above are conceptual only. The actual
|
|
* implementation uses arrays and constant keys for metadata storage. Instead of
|
|
* `{id: 'flatMap', mtime: 3421, size: 42, visited: true, dependencies: []}` the real
|
|
* representation is similar to `['flatMap', 3421, 42, 1, []]` to save storage space
|
|
* and reduce parse and write time of a big JSON blob.
|
|
*
|
|
* The HasteMap is created as follows:
|
|
* 1. read data from the cache or create an empty structure.
|
|
*
|
|
* 2. crawl the file system.
|
|
* * empty cache: crawl the entire file system.
|
|
* * cache available:
|
|
* * if watchman is available: get file system delta changes.
|
|
* * if watchman is unavailable: crawl the entire file system.
|
|
* * build metadata objects for every file. This builds the `files` part of
|
|
* the `HasteMap`.
|
|
*
|
|
* 3. parse and extract metadata from changed files.
|
|
* * this is done in parallel over worker processes to improve performance.
|
|
* * the worst case is to parse all files.
|
|
* * the best case is no file system access and retrieving all data from
|
|
* the cache.
|
|
* * the average case is a small number of changed files.
|
|
*
|
|
* 4. serialize the new `HasteMap` in a cache file.
|
|
* Worker processes can directly access the cache through `HasteMap.read()`.
|
|
*
|
|
*/
|
|
|
|
/* eslint-disable-next-line no-redeclare */
|
|
|
|
class HasteMap extends _events().default {
|
|
constructor(options) {
|
|
super();
|
|
|
|
_defineProperty(this, '_buildPromise', void 0);
|
|
|
|
_defineProperty(this, '_cachePath', void 0);
|
|
|
|
_defineProperty(this, '_changeInterval', void 0);
|
|
|
|
_defineProperty(this, '_console', void 0);
|
|
|
|
_defineProperty(this, '_options', void 0);
|
|
|
|
_defineProperty(this, '_watchers', void 0);
|
|
|
|
_defineProperty(this, '_whitelist', void 0);
|
|
|
|
_defineProperty(this, '_worker', void 0);
|
|
|
|
this._options = {
|
|
cacheDirectory: options.cacheDirectory || _os().default.tmpdir(),
|
|
computeDependencies:
|
|
options.computeDependencies === undefined
|
|
? true
|
|
: options.computeDependencies,
|
|
computeSha1: options.computeSha1 || false,
|
|
dependencyExtractor: options.dependencyExtractor,
|
|
extensions: options.extensions,
|
|
forceNodeFilesystemAPI: !!options.forceNodeFilesystemAPI,
|
|
hasteImplModulePath: options.hasteImplModulePath,
|
|
ignorePattern: options.ignorePattern,
|
|
mapper: options.mapper,
|
|
maxWorkers: options.maxWorkers,
|
|
mocksPattern: options.mocksPattern
|
|
? new RegExp(options.mocksPattern)
|
|
: null,
|
|
name: options.name,
|
|
platforms: options.platforms,
|
|
resetCache: options.resetCache,
|
|
retainAllFiles: options.retainAllFiles,
|
|
rootDir: options.rootDir,
|
|
roots: Array.from(new Set(options.roots)),
|
|
skipPackageJson: !!options.skipPackageJson,
|
|
throwOnModuleCollision: !!options.throwOnModuleCollision,
|
|
useWatchman: options.useWatchman == null ? true : options.useWatchman,
|
|
watch: !!options.watch
|
|
};
|
|
this._console = options.console || global.console;
|
|
|
|
if (options.ignorePattern && !(options.ignorePattern instanceof RegExp)) {
|
|
this._console.warn(
|
|
'jest-haste-map: the `ignorePattern` options as a function is being ' +
|
|
'deprecated. Provide a RegExp instead. See https://github.com/facebook/jest/pull/4063.'
|
|
);
|
|
}
|
|
|
|
const rootDirHash = _crypto()
|
|
.default.createHash('md5')
|
|
.update(options.rootDir)
|
|
.digest('hex');
|
|
|
|
let hasteImplHash = '';
|
|
let dependencyExtractorHash = '';
|
|
|
|
if (options.hasteImplModulePath) {
|
|
const hasteImpl = require(options.hasteImplModulePath);
|
|
|
|
if (hasteImpl.getCacheKey) {
|
|
hasteImplHash = String(hasteImpl.getCacheKey());
|
|
}
|
|
}
|
|
|
|
if (options.dependencyExtractor) {
|
|
const dependencyExtractor = require(options.dependencyExtractor);
|
|
|
|
if (dependencyExtractor.getCacheKey) {
|
|
dependencyExtractorHash = String(dependencyExtractor.getCacheKey());
|
|
}
|
|
}
|
|
|
|
this._cachePath = HasteMap.getCacheFilePath(
|
|
this._options.cacheDirectory,
|
|
`haste-map-${this._options.name}-${rootDirHash}`,
|
|
VERSION,
|
|
this._options.name,
|
|
this._options.roots
|
|
.map(root => fastPath.relative(options.rootDir, root))
|
|
.join(':'),
|
|
this._options.extensions.join(':'),
|
|
this._options.platforms.join(':'),
|
|
this._options.computeSha1.toString(),
|
|
options.mocksPattern || '',
|
|
(options.ignorePattern || '').toString(),
|
|
hasteImplHash,
|
|
dependencyExtractorHash
|
|
);
|
|
this._whitelist = getWhiteList(options.providesModuleNodeModules);
|
|
this._buildPromise = null;
|
|
this._watchers = [];
|
|
this._worker = null;
|
|
}
|
|
|
|
static getCacheFilePath(tmpdir, name, ...extra) {
|
|
const hash = _crypto()
|
|
.default.createHash('md5')
|
|
.update(extra.join(''));
|
|
|
|
return _path().default.join(
|
|
tmpdir,
|
|
name.replace(/\W/g, '-') + '-' + hash.digest('hex')
|
|
);
|
|
}
|
|
|
|
getCacheFilePath() {
|
|
return this._cachePath;
|
|
}
|
|
|
|
build() {
|
|
var _this = this;
|
|
|
|
if (!this._buildPromise) {
|
|
this._buildPromise = _asyncToGenerator(function*() {
|
|
const data = yield _this._buildFileMap(); // Persist when we don't know if files changed (changedFiles undefined)
|
|
// or when we know a file was changed or deleted.
|
|
|
|
let hasteMap;
|
|
|
|
if (
|
|
data.changedFiles === undefined ||
|
|
data.changedFiles.size > 0 ||
|
|
data.removedFiles.size > 0
|
|
) {
|
|
hasteMap = yield _this._buildHasteMap(data);
|
|
|
|
_this._persist(hasteMap);
|
|
} else {
|
|
hasteMap = data.hasteMap;
|
|
}
|
|
|
|
const rootDir = _this._options.rootDir;
|
|
const hasteFS = new _HasteFS.default({
|
|
files: hasteMap.files,
|
|
rootDir
|
|
});
|
|
const moduleMap = new _ModuleMap.default({
|
|
duplicates: hasteMap.duplicates,
|
|
map: hasteMap.map,
|
|
mocks: hasteMap.mocks,
|
|
rootDir
|
|
});
|
|
|
|
const __hasteMapForTest =
|
|
(process.env.NODE_ENV === 'test' && hasteMap) || null;
|
|
|
|
yield _this._watch(hasteMap);
|
|
return {
|
|
__hasteMapForTest,
|
|
hasteFS,
|
|
moduleMap
|
|
};
|
|
})();
|
|
}
|
|
|
|
return this._buildPromise;
|
|
}
|
|
/**
|
|
* 1. read data from the cache or create an empty structure.
|
|
*/
|
|
|
|
read() {
|
|
let hasteMap;
|
|
|
|
try {
|
|
hasteMap = _jestSerializer().default.readFileSync(this._cachePath);
|
|
} catch (err) {
|
|
hasteMap = this._createEmptyMap();
|
|
}
|
|
|
|
return hasteMap;
|
|
}
|
|
|
|
readModuleMap() {
|
|
const data = this.read();
|
|
return new _ModuleMap.default({
|
|
duplicates: data.duplicates,
|
|
map: data.map,
|
|
mocks: data.mocks,
|
|
rootDir: this._options.rootDir
|
|
});
|
|
}
|
|
/**
|
|
* 2. crawl the file system.
|
|
*/
|
|
|
|
_buildFileMap() {
|
|
var _this2 = this;
|
|
|
|
return _asyncToGenerator(function*() {
|
|
let hasteMap;
|
|
|
|
try {
|
|
const read = _this2._options.resetCache
|
|
? _this2._createEmptyMap
|
|
: _this2.read;
|
|
hasteMap = yield read.call(_this2);
|
|
} catch (_unused) {
|
|
hasteMap = _this2._createEmptyMap();
|
|
}
|
|
|
|
return _this2._crawl(hasteMap);
|
|
})();
|
|
}
|
|
/**
|
|
* 3. parse and extract metadata from changed files.
|
|
*/
|
|
|
|
_processFile(hasteMap, map, mocks, filePath, workerOptions) {
|
|
const rootDir = this._options.rootDir;
|
|
|
|
const setModule = (id, module) => {
|
|
let moduleMap = map.get(id);
|
|
|
|
if (!moduleMap) {
|
|
moduleMap = Object.create(null);
|
|
map.set(id, moduleMap);
|
|
}
|
|
|
|
const platform =
|
|
(0, _getPlatformExtension.default)(
|
|
module[_constants.default.PATH],
|
|
this._options.platforms
|
|
) || _constants.default.GENERIC_PLATFORM;
|
|
|
|
const existingModule = moduleMap[platform];
|
|
|
|
if (
|
|
existingModule &&
|
|
existingModule[_constants.default.PATH] !==
|
|
module[_constants.default.PATH]
|
|
) {
|
|
const method = this._options.throwOnModuleCollision ? 'error' : 'warn';
|
|
|
|
this._console[method](
|
|
[
|
|
'jest-haste-map: Haste module naming collision: ' + id,
|
|
' The following files share their name; please adjust your hasteImpl:',
|
|
' * <rootDir>' +
|
|
_path().default.sep +
|
|
existingModule[_constants.default.PATH],
|
|
' * <rootDir>' +
|
|
_path().default.sep +
|
|
module[_constants.default.PATH],
|
|
''
|
|
].join('\n')
|
|
);
|
|
|
|
if (this._options.throwOnModuleCollision) {
|
|
throw new DuplicateError(
|
|
existingModule[_constants.default.PATH],
|
|
module[_constants.default.PATH]
|
|
);
|
|
} // We do NOT want consumers to use a module that is ambiguous.
|
|
|
|
delete moduleMap[platform];
|
|
|
|
if (Object.keys(moduleMap).length === 1) {
|
|
map.delete(id);
|
|
}
|
|
|
|
let dupsByPlatform = hasteMap.duplicates.get(id);
|
|
|
|
if (dupsByPlatform == null) {
|
|
dupsByPlatform = new Map();
|
|
hasteMap.duplicates.set(id, dupsByPlatform);
|
|
}
|
|
|
|
const dups = new Map([
|
|
[module[_constants.default.PATH], module[_constants.default.TYPE]],
|
|
[
|
|
existingModule[_constants.default.PATH],
|
|
existingModule[_constants.default.TYPE]
|
|
]
|
|
]);
|
|
dupsByPlatform.set(platform, dups);
|
|
return;
|
|
}
|
|
|
|
const dupsByPlatform = hasteMap.duplicates.get(id);
|
|
|
|
if (dupsByPlatform != null) {
|
|
const dups = dupsByPlatform.get(platform);
|
|
|
|
if (dups != null) {
|
|
dups.set(
|
|
module[_constants.default.PATH],
|
|
module[_constants.default.TYPE]
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
moduleMap[platform] = module;
|
|
};
|
|
|
|
const relativeFilePath = fastPath.relative(rootDir, filePath);
|
|
const fileMetadata = hasteMap.files.get(relativeFilePath);
|
|
|
|
if (!fileMetadata) {
|
|
throw new Error(
|
|
'jest-haste-map: File to process was not found in the haste map.'
|
|
);
|
|
}
|
|
|
|
const moduleMetadata = hasteMap.map.get(
|
|
fileMetadata[_constants.default.ID]
|
|
);
|
|
const computeSha1 =
|
|
this._options.computeSha1 && !fileMetadata[_constants.default.SHA1]; // Callback called when the response from the worker is successful.
|
|
|
|
const workerReply = metadata => {
|
|
// `1` for truthy values instead of `true` to save cache space.
|
|
fileMetadata[_constants.default.VISITED] = 1;
|
|
const metadataId = metadata.id;
|
|
const metadataModule = metadata.module;
|
|
|
|
if (metadataId && metadataModule) {
|
|
fileMetadata[_constants.default.ID] = metadataId;
|
|
setModule(metadataId, metadataModule);
|
|
}
|
|
|
|
fileMetadata[_constants.default.DEPENDENCIES] = metadata.dependencies
|
|
? metadata.dependencies.join(_constants.default.DEPENDENCY_DELIM)
|
|
: '';
|
|
|
|
if (computeSha1) {
|
|
fileMetadata[_constants.default.SHA1] = metadata.sha1;
|
|
}
|
|
}; // Callback called when the response from the worker is an error.
|
|
|
|
const workerError = error => {
|
|
if (typeof error !== 'object' || !error.message || !error.stack) {
|
|
error = new Error(error);
|
|
error.stack = ''; // Remove stack for stack-less errors.
|
|
} // @ts-ignore: checking error code is OK if error comes from "fs".
|
|
|
|
if (!['ENOENT', 'EACCES'].includes(error.code)) {
|
|
throw error;
|
|
} // If a file cannot be read we remove it from the file list and
|
|
// ignore the failure silently.
|
|
|
|
hasteMap.files.delete(relativeFilePath);
|
|
}; // If we retain all files in the virtual HasteFS representation, we avoid
|
|
// reading them if they aren't important (node_modules).
|
|
|
|
if (this._options.retainAllFiles && this._isNodeModulesDir(filePath)) {
|
|
if (computeSha1) {
|
|
return this._getWorker(workerOptions)
|
|
.getSha1({
|
|
computeDependencies: this._options.computeDependencies,
|
|
computeSha1,
|
|
dependencyExtractor: this._options.dependencyExtractor,
|
|
filePath,
|
|
hasteImplModulePath: this._options.hasteImplModulePath,
|
|
rootDir
|
|
})
|
|
.then(workerReply, workerError);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (
|
|
this._options.mocksPattern &&
|
|
this._options.mocksPattern.test(filePath)
|
|
) {
|
|
const mockPath = (0, _getMockName.default)(filePath);
|
|
const existingMockPath = mocks.get(mockPath);
|
|
|
|
if (existingMockPath) {
|
|
const secondMockPath = fastPath.relative(rootDir, filePath);
|
|
|
|
if (existingMockPath !== secondMockPath) {
|
|
const method = this._options.throwOnModuleCollision
|
|
? 'error'
|
|
: 'warn';
|
|
|
|
this._console[method](
|
|
[
|
|
'jest-haste-map: duplicate manual mock found: ' + mockPath,
|
|
' The following files share their name; please delete one of them:',
|
|
' * <rootDir>' + _path().default.sep + existingMockPath,
|
|
' * <rootDir>' + _path().default.sep + secondMockPath,
|
|
''
|
|
].join('\n')
|
|
);
|
|
|
|
if (this._options.throwOnModuleCollision) {
|
|
throw new DuplicateError(existingMockPath, secondMockPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
mocks.set(mockPath, relativeFilePath);
|
|
}
|
|
|
|
if (fileMetadata[_constants.default.VISITED]) {
|
|
if (!fileMetadata[_constants.default.ID]) {
|
|
return null;
|
|
}
|
|
|
|
if (moduleMetadata != null) {
|
|
const platform =
|
|
(0, _getPlatformExtension.default)(
|
|
filePath,
|
|
this._options.platforms
|
|
) || _constants.default.GENERIC_PLATFORM;
|
|
|
|
const module = moduleMetadata[platform];
|
|
|
|
if (module == null) {
|
|
return null;
|
|
}
|
|
|
|
const moduleId = fileMetadata[_constants.default.ID];
|
|
let modulesByPlatform = map.get(moduleId);
|
|
|
|
if (!modulesByPlatform) {
|
|
modulesByPlatform = Object.create(null);
|
|
map.set(moduleId, modulesByPlatform);
|
|
}
|
|
|
|
modulesByPlatform[platform] = module;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return this._getWorker(workerOptions)
|
|
.worker({
|
|
computeDependencies: this._options.computeDependencies,
|
|
computeSha1,
|
|
dependencyExtractor: this._options.dependencyExtractor,
|
|
filePath,
|
|
hasteImplModulePath: this._options.hasteImplModulePath,
|
|
rootDir
|
|
})
|
|
.then(workerReply, workerError);
|
|
}
|
|
|
|
_buildHasteMap(data) {
|
|
const removedFiles = data.removedFiles,
|
|
changedFiles = data.changedFiles,
|
|
hasteMap = data.hasteMap; // If any files were removed or we did not track what files changed, process
|
|
// every file looking for changes. Otherwise, process only changed files.
|
|
|
|
let map;
|
|
let mocks;
|
|
let filesToProcess;
|
|
|
|
if (changedFiles === undefined || removedFiles.size) {
|
|
map = new Map();
|
|
mocks = new Map();
|
|
filesToProcess = hasteMap.files;
|
|
} else {
|
|
map = hasteMap.map;
|
|
mocks = hasteMap.mocks;
|
|
filesToProcess = changedFiles;
|
|
}
|
|
|
|
var _iteratorNormalCompletion = true;
|
|
var _didIteratorError = false;
|
|
var _iteratorError = undefined;
|
|
|
|
try {
|
|
for (
|
|
var _iterator = removedFiles[Symbol.iterator](), _step;
|
|
!(_iteratorNormalCompletion = (_step = _iterator.next()).done);
|
|
_iteratorNormalCompletion = true
|
|
) {
|
|
const _step$value = _slicedToArray(_step.value, 2),
|
|
relativeFilePath = _step$value[0],
|
|
fileMetadata = _step$value[1];
|
|
|
|
this._recoverDuplicates(
|
|
hasteMap,
|
|
relativeFilePath,
|
|
fileMetadata[_constants.default.ID]
|
|
);
|
|
}
|
|
} catch (err) {
|
|
_didIteratorError = true;
|
|
_iteratorError = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
_iterator.return();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError) {
|
|
throw _iteratorError;
|
|
}
|
|
}
|
|
}
|
|
|
|
const promises = [];
|
|
var _iteratorNormalCompletion2 = true;
|
|
var _didIteratorError2 = false;
|
|
var _iteratorError2 = undefined;
|
|
|
|
try {
|
|
for (
|
|
var _iterator2 = filesToProcess.keys()[Symbol.iterator](), _step2;
|
|
!(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done);
|
|
_iteratorNormalCompletion2 = true
|
|
) {
|
|
const relativeFilePath = _step2.value;
|
|
|
|
if (
|
|
this._options.skipPackageJson &&
|
|
relativeFilePath.endsWith(PACKAGE_JSON)
|
|
) {
|
|
continue;
|
|
} // SHA-1, if requested, should already be present thanks to the crawler.
|
|
|
|
const filePath = fastPath.resolve(
|
|
this._options.rootDir,
|
|
relativeFilePath
|
|
);
|
|
|
|
const promise = this._processFile(hasteMap, map, mocks, filePath);
|
|
|
|
if (promise) {
|
|
promises.push(promise);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
_didIteratorError2 = true;
|
|
_iteratorError2 = err;
|
|
} finally {
|
|
try {
|
|
if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
|
|
_iterator2.return();
|
|
}
|
|
} finally {
|
|
if (_didIteratorError2) {
|
|
throw _iteratorError2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Promise.all(promises).then(
|
|
() => {
|
|
this._cleanup();
|
|
|
|
hasteMap.map = map;
|
|
hasteMap.mocks = mocks;
|
|
return hasteMap;
|
|
},
|
|
error => {
|
|
this._cleanup();
|
|
|
|
throw error;
|
|
}
|
|
);
|
|
}
|
|
|
|
_cleanup() {
|
|
const worker = this._worker; // @ts-ignore
|
|
|
|
if (worker && typeof worker.end === 'function') {
|
|
// @ts-ignore
|
|
worker.end();
|
|
}
|
|
|
|
this._worker = null;
|
|
}
|
|
/**
|
|
* 4. serialize the new `HasteMap` in a cache file.
|
|
*/
|
|
|
|
_persist(hasteMap) {
|
|
_jestSerializer().default.writeFileSync(this._cachePath, hasteMap);
|
|
}
|
|
/**
|
|
* Creates workers or parses files and extracts metadata in-process.
|
|
*/
|
|
|
|
_getWorker(options) {
|
|
if (!this._worker) {
|
|
if ((options && options.forceInBand) || this._options.maxWorkers <= 1) {
|
|
this._worker = {
|
|
getSha1: _worker.getSha1,
|
|
worker: _worker.worker
|
|
};
|
|
} else {
|
|
// @ts-ignore: assignment of a worker with custom properties.
|
|
this._worker = new (_jestWorker()).default(
|
|
require.resolve('./worker'),
|
|
{
|
|
exposedMethods: ['getSha1', 'worker'],
|
|
maxRetries: 3,
|
|
numWorkers: this._options.maxWorkers
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
return this._worker;
|
|
}
|
|
|
|
_crawl(hasteMap) {
|
|
const options = this._options;
|
|
|
|
const ignore = this._ignore.bind(this);
|
|
|
|
const crawl =
|
|
canUseWatchman && this._options.useWatchman
|
|
? _watchman.default
|
|
: _node.default;
|
|
const crawlerOptions = {
|
|
computeSha1: options.computeSha1,
|
|
data: hasteMap,
|
|
extensions: options.extensions,
|
|
forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
|
|
ignore,
|
|
mapper: options.mapper,
|
|
rootDir: options.rootDir,
|
|
roots: options.roots
|
|
};
|
|
|
|
const retry = error => {
|
|
if (crawl === _watchman.default) {
|
|
this._console.warn(
|
|
`jest-haste-map: Watchman crawl failed. Retrying once with node ` +
|
|
`crawler.\n` +
|
|
` Usually this happens when watchman isn't running. Create an ` +
|
|
`empty \`.watchmanconfig\` file in your project's root folder or ` +
|
|
`initialize a git or hg repository in your project.\n` +
|
|
` ` +
|
|
error
|
|
);
|
|
|
|
return (0, _node.default)(crawlerOptions).catch(e => {
|
|
throw new Error(
|
|
`Crawler retry failed:\n` +
|
|
` Original error: ${error.message}\n` +
|
|
` Retry error: ${e.message}\n`
|
|
);
|
|
});
|
|
}
|
|
|
|
throw error;
|
|
};
|
|
|
|
try {
|
|
return crawl(crawlerOptions).catch(retry);
|
|
} catch (error) {
|
|
return retry(error);
|
|
}
|
|
}
|
|
/**
|
|
* Watch mode
|
|
*/
|
|
|
|
_watch(hasteMap) {
|
|
if (!this._options.watch) {
|
|
return Promise.resolve();
|
|
} // In watch mode, we'll only warn about module collisions and we'll retain
|
|
// all files, even changes to node_modules.
|
|
|
|
this._options.throwOnModuleCollision = false;
|
|
this._options.retainAllFiles = true; // WatchmanWatcher > FSEventsWatcher > sane.NodeWatcher
|
|
|
|
const Watcher =
|
|
canUseWatchman && this._options.useWatchman
|
|
? _WatchmanWatcher.default
|
|
: _FSEventsWatcher.default.isSupported()
|
|
? _FSEventsWatcher.default
|
|
: _sane().default.NodeWatcher;
|
|
const extensions = this._options.extensions;
|
|
const ignorePattern = this._options.ignorePattern;
|
|
const rootDir = this._options.rootDir;
|
|
let changeQueue = Promise.resolve();
|
|
let eventsQueue = []; // We only need to copy the entire haste map once on every "frame".
|
|
|
|
let mustCopy = true;
|
|
|
|
const createWatcher = root => {
|
|
// @ts-ignore: TODO how? "Cannot use 'new' with an expression whose type lacks a call or construct signature."
|
|
const watcher = new Watcher(root, {
|
|
dot: false,
|
|
glob: extensions.map(extension => '**/*.' + extension),
|
|
ignored: ignorePattern
|
|
});
|
|
return new Promise((resolve, reject) => {
|
|
const rejectTimeout = setTimeout(
|
|
() => reject(new Error('Failed to start watch mode.')),
|
|
MAX_WAIT_TIME
|
|
);
|
|
watcher.once('ready', () => {
|
|
clearTimeout(rejectTimeout);
|
|
watcher.on('all', onChange);
|
|
resolve(watcher);
|
|
});
|
|
});
|
|
};
|
|
|
|
const emitChange = () => {
|
|
if (eventsQueue.length) {
|
|
mustCopy = true;
|
|
const changeEvent = {
|
|
eventsQueue,
|
|
hasteFS: new _HasteFS.default({
|
|
files: hasteMap.files,
|
|
rootDir
|
|
}),
|
|
moduleMap: new _ModuleMap.default({
|
|
duplicates: hasteMap.duplicates,
|
|
map: hasteMap.map,
|
|
mocks: hasteMap.mocks,
|
|
rootDir
|
|
})
|
|
};
|
|
this.emit('change', changeEvent);
|
|
eventsQueue = [];
|
|
}
|
|
};
|
|
|
|
const onChange = (type, filePath, root, stat) => {
|
|
filePath = _path().default.join(
|
|
root,
|
|
(0, _normalizePathSep.default)(filePath)
|
|
);
|
|
|
|
if (
|
|
(stat && stat.isDirectory()) ||
|
|
this._ignore(filePath) ||
|
|
!extensions.some(extension => filePath.endsWith(extension))
|
|
) {
|
|
return;
|
|
}
|
|
|
|
changeQueue = changeQueue
|
|
.then(() => {
|
|
// If we get duplicate events for the same file, ignore them.
|
|
if (
|
|
eventsQueue.find(
|
|
event =>
|
|
event.type === type &&
|
|
event.filePath === filePath &&
|
|
((!event.stat && !stat) ||
|
|
(!!event.stat &&
|
|
!!stat &&
|
|
event.stat.mtime.getTime() === stat.mtime.getTime()))
|
|
)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
if (mustCopy) {
|
|
mustCopy = false;
|
|
hasteMap = {
|
|
clocks: new Map(hasteMap.clocks),
|
|
duplicates: new Map(hasteMap.duplicates),
|
|
files: new Map(hasteMap.files),
|
|
map: new Map(hasteMap.map),
|
|
mocks: new Map(hasteMap.mocks)
|
|
};
|
|
}
|
|
|
|
const add = () => {
|
|
eventsQueue.push({
|
|
filePath,
|
|
stat,
|
|
type
|
|
});
|
|
return null;
|
|
};
|
|
|
|
const relativeFilePath = fastPath.relative(rootDir, filePath);
|
|
const fileMetadata = hasteMap.files.get(relativeFilePath); // If it's not an addition, delete the file and all its metadata
|
|
|
|
if (fileMetadata != null) {
|
|
const moduleName = fileMetadata[_constants.default.ID];
|
|
|
|
const platform =
|
|
(0, _getPlatformExtension.default)(
|
|
filePath,
|
|
this._options.platforms
|
|
) || _constants.default.GENERIC_PLATFORM;
|
|
|
|
hasteMap.files.delete(relativeFilePath);
|
|
let moduleMap = hasteMap.map.get(moduleName);
|
|
|
|
if (moduleMap != null) {
|
|
// We are forced to copy the object because jest-haste-map exposes
|
|
// the map as an immutable entity.
|
|
moduleMap = copy(moduleMap);
|
|
delete moduleMap[platform];
|
|
|
|
if (Object.keys(moduleMap).length === 0) {
|
|
hasteMap.map.delete(moduleName);
|
|
} else {
|
|
hasteMap.map.set(moduleName, moduleMap);
|
|
}
|
|
}
|
|
|
|
if (
|
|
this._options.mocksPattern &&
|
|
this._options.mocksPattern.test(filePath)
|
|
) {
|
|
const mockName = (0, _getMockName.default)(filePath);
|
|
hasteMap.mocks.delete(mockName);
|
|
}
|
|
|
|
this._recoverDuplicates(hasteMap, relativeFilePath, moduleName);
|
|
} // If the file was added or changed,
|
|
// parse it and update the haste map.
|
|
|
|
if (type === 'add' || type === 'change') {
|
|
(0, _invariant().default)(
|
|
stat,
|
|
'since the file exists or changed, it should have stats'
|
|
);
|
|
const fileMetadata = [
|
|
'',
|
|
stat ? stat.mtime.getTime() : -1,
|
|
stat ? stat.size : 0,
|
|
0,
|
|
'',
|
|
null
|
|
];
|
|
hasteMap.files.set(relativeFilePath, fileMetadata);
|
|
|
|
const promise = this._processFile(
|
|
hasteMap,
|
|
hasteMap.map,
|
|
hasteMap.mocks,
|
|
filePath,
|
|
{
|
|
forceInBand: true
|
|
}
|
|
); // Cleanup
|
|
|
|
this._cleanup();
|
|
|
|
if (promise) {
|
|
return promise.then(add);
|
|
} else {
|
|
// If a file in node_modules has changed,
|
|
// emit an event regardless.
|
|
add();
|
|
}
|
|
} else {
|
|
add();
|
|
}
|
|
|
|
return null;
|
|
})
|
|
.catch(error => {
|
|
this._console.error(
|
|
`jest-haste-map: watch error:\n ${error.stack}\n`
|
|
);
|
|
});
|
|
};
|
|
|
|
this._changeInterval = setInterval(emitChange, CHANGE_INTERVAL);
|
|
return Promise.all(this._options.roots.map(createWatcher)).then(
|
|
watchers => {
|
|
this._watchers = watchers;
|
|
}
|
|
);
|
|
}
|
|
/**
|
|
* This function should be called when the file under `filePath` is removed
|
|
* or changed. When that happens, we want to figure out if that file was
|
|
* part of a group of files that had the same ID. If it was, we want to
|
|
* remove it from the group. Furthermore, if there is only one file
|
|
* remaining in the group, then we want to restore that single file as the
|
|
* correct resolution for its ID, and cleanup the duplicates index.
|
|
*/
|
|
|
|
_recoverDuplicates(hasteMap, relativeFilePath, moduleName) {
|
|
let dupsByPlatform = hasteMap.duplicates.get(moduleName);
|
|
|
|
if (dupsByPlatform == null) {
|
|
return;
|
|
}
|
|
|
|
const platform =
|
|
(0, _getPlatformExtension.default)(
|
|
relativeFilePath,
|
|
this._options.platforms
|
|
) || _constants.default.GENERIC_PLATFORM;
|
|
|
|
let dups = dupsByPlatform.get(platform);
|
|
|
|
if (dups == null) {
|
|
return;
|
|
}
|
|
|
|
dupsByPlatform = copyMap(dupsByPlatform);
|
|
hasteMap.duplicates.set(moduleName, dupsByPlatform);
|
|
dups = copyMap(dups);
|
|
dupsByPlatform.set(platform, dups);
|
|
dups.delete(relativeFilePath);
|
|
|
|
if (dups.size !== 1) {
|
|
return;
|
|
}
|
|
|
|
const uniqueModule = dups.entries().next().value;
|
|
|
|
if (!uniqueModule) {
|
|
return;
|
|
}
|
|
|
|
let dedupMap = hasteMap.map.get(moduleName);
|
|
|
|
if (dedupMap == null) {
|
|
dedupMap = Object.create(null);
|
|
hasteMap.map.set(moduleName, dedupMap);
|
|
}
|
|
|
|
dedupMap[platform] = uniqueModule;
|
|
dupsByPlatform.delete(platform);
|
|
|
|
if (dupsByPlatform.size === 0) {
|
|
hasteMap.duplicates.delete(moduleName);
|
|
}
|
|
}
|
|
|
|
end() {
|
|
// @ts-ignore: TODO TS cannot decide if `setInterval` and `clearInterval` comes from NodeJS or the DOM
|
|
clearInterval(this._changeInterval);
|
|
|
|
if (!this._watchers.length) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return Promise.all(
|
|
this._watchers.map(
|
|
watcher => new Promise(resolve => watcher.close(resolve))
|
|
)
|
|
).then(() => {
|
|
this._watchers = [];
|
|
});
|
|
}
|
|
/**
|
|
* Helpers
|
|
*/
|
|
|
|
_ignore(filePath) {
|
|
const ignorePattern = this._options.ignorePattern;
|
|
const ignoreMatched =
|
|
ignorePattern instanceof RegExp
|
|
? ignorePattern.test(filePath)
|
|
: ignorePattern && ignorePattern(filePath);
|
|
return (
|
|
ignoreMatched ||
|
|
(!this._options.retainAllFiles && this._isNodeModulesDir(filePath))
|
|
);
|
|
}
|
|
|
|
_isNodeModulesDir(filePath) {
|
|
if (!filePath.includes(NODE_MODULES)) {
|
|
return false;
|
|
}
|
|
|
|
if (this._whitelist) {
|
|
const whitelist = this._whitelist;
|
|
const match = whitelist.exec(filePath);
|
|
const matchEndIndex = whitelist.lastIndex;
|
|
whitelist.lastIndex = 0;
|
|
|
|
if (!match) {
|
|
return true;
|
|
}
|
|
|
|
const filePathInPackage = filePath.substr(matchEndIndex);
|
|
return filePathInPackage.startsWith(NODE_MODULES);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
_createEmptyMap() {
|
|
return {
|
|
clocks: new Map(),
|
|
duplicates: new Map(),
|
|
files: new Map(),
|
|
map: new Map(),
|
|
mocks: new Map()
|
|
};
|
|
}
|
|
}
|
|
|
|
_defineProperty(HasteMap, 'H', void 0);
|
|
|
|
_defineProperty(HasteMap, 'DuplicateError', void 0);
|
|
|
|
_defineProperty(HasteMap, 'ModuleMap', void 0);
|
|
|
|
class DuplicateError extends Error {
|
|
constructor(mockPath1, mockPath2) {
|
|
super('Duplicated files or mocks. Please check the console for more info');
|
|
|
|
_defineProperty(this, 'mockPath1', void 0);
|
|
|
|
_defineProperty(this, 'mockPath2', void 0);
|
|
|
|
this.mockPath1 = mockPath1;
|
|
this.mockPath2 = mockPath2;
|
|
}
|
|
}
|
|
|
|
function copy(object) {
|
|
return Object.assign(Object.create(null), object);
|
|
}
|
|
|
|
function copyMap(input) {
|
|
return new Map(input);
|
|
}
|
|
|
|
HasteMap.H = _constants.default;
|
|
HasteMap.DuplicateError = DuplicateError;
|
|
HasteMap.ModuleMap = _ModuleMap.default;
|
|
module.exports = HasteMap;
|