UNPKG

mock-fs-require-fix

Version:

Fork of the tschaub/mock-fs project.

200 lines (170 loc) 5.59 kB
'use strict'; var MODULE_RESOLUTION_STEP = Symbol('resolvingModule'); var realFs = require('fs'); var path = require('path'); var Module = require('module'); var rewire = require('rewire'); var semver = require('semver'); var Binding = require('./binding'); var FileSystem = require('./filesystem'); var FSError = require('./error'); var versions = { '0.8.x': 'fs-0.8.26.js', '0.9.x': 'fs-0.9.12.js', '0.10.x': 'fs-0.10.28.js', '0.11 - 0.11.14': 'fs-0.11.13.js', '0.11.15 - 0.12.x': 'fs-0.12.0.js', '1.x.x': 'fs-1.1.0.js', '2.x.x': 'fs-2.0.0.js', '3.x.x': 'fs-3.0.0.js', '4.x.x': 'fs-4.0.0.js', '5.x.x': 'fs-5.0.0.js', '6.x.x - 6.10.0': 'fs-6.3.0.js', '6.10.1 - 6.x.x': 'fs-6.10.1.js', '7.0.0 - 7.6.x': 'fs-7.0.0.js', '7.7.0 - 7.x.x': 'fs-7.7.0.js' }; var nodeVersion = process.versions.node; var fsName; Object.keys(versions).some(function(version) { if (semver.satisfies(nodeVersion, version)) { fsName = versions[version]; return true; } }); if (!fsName) { throw new Error('Unsupported Node version: ' + nodeVersion); } /** * Mark the steps of module resolution, so we can add adequate hooks. */ (function() { // Wrap in an IIFE to prevent scope pollution var _resolveFilename = Module._resolveFilename; Module._resolveFilename = function(request, parent) { Module[MODULE_RESOLUTION_STEP] = true; try { return _resolveFilename.call(this, request, parent); } finally { Module[MODULE_RESOLUTION_STEP] = false; } }; var _compile = Module.prototype._compile; Module.prototype._compile = function(content, filename) { Module[MODULE_RESOLUTION_STEP] = false; try { return _compile.call(this, content, filename); } finally { Module[MODULE_RESOLUTION_STEP] = true; } }; Object.keys(Module._extensions).forEach(function(ext) { var defaultLoader = Module._extensions[ext]; Module._extensions[ext] = function(module, filename) { Module[MODULE_RESOLUTION_STEP] = true; try { return defaultLoader(module, filename); } finally { Module[MODULE_RESOLUTION_STEP] = false; } }; }); })(); /** * Hijack the real fs module immediately so the binding can be swapped at will. * This works as expected in cases where mock-fs is required before any other * module that wraps fs exports. */ var mockFs = rewire(path.join(__dirname, '..', 'node', fsName)); var originalBinding = mockFs.__get__('binding'); var originalStats = mockFs.Stats; for (var name in mockFs) { var descriptor = Object.getOwnPropertyDescriptor(realFs, name); if (!descriptor || descriptor && descriptor.writable) { realFs[name] = (function(mockFunction, realFunction) { return function() { if (Module[MODULE_RESOLUTION_STEP]) { return realFunction.apply(realFs, arguments); } else { return mockFunction.apply(realFs, arguments); } }; }(mockFs[name], realFs[name])); } } var originalProcess = { cwd: process.cwd, chdir: process.chdir }; function setBinding(binding, Stats) { mockFs.__set__('binding', binding); mockFs.Stats = realFs.Stats = Stats; } function setProcess(cwd, chdir) { process.cwd = cwd; process.chdir = chdir; } /** * Swap out the fs bindings for a mock file system. * @param {Object} config Mock file system configuration. * @param {Object} options Any filesystem options. * @param {boolean} options.createCwd Create a directory for `process.cwd()` * (defaults to `true`). * @param {boolean} options.createTmp Create a directory for `os.tmpdir()` * (defaults to `true`). */ var exports = module.exports = function mock(config, options) { var system = FileSystem.create(config, options); var binding = new Binding(system); setBinding(binding, binding.Stats); var currentPath = process.cwd(); setProcess( function cwd() { return currentPath; }, function chdir(directory) { if (!mockFs.statSync(directory).isDirectory()) { throw new FSError('ENOTDIR'); } currentPath = path.resolve(currentPath, directory); } ); }; /** * Restore the fs bindings for the real file system. */ exports.restore = function() { setBinding(originalBinding, originalStats); setProcess(originalProcess.cwd, originalProcess.chdir); }; /** * Create a mock fs module based on the given file system configuration. * @param {Object} config File system configuration. * @param {Object} options Any filesystem options. * @param {boolean} options.createCwd Create a directory for `process.cwd()` * (defaults to `true`). * @param {boolean} options.createTmp Create a directory for `os.tmpdir()` * (defaults to `true`). * @return {Object} A fs module with a mock file system. */ exports.fs = function(config, options) { var system = FileSystem.create(config, options); var binding = new Binding(system); // inject the mock binding var newMockFs = rewire(path.join(__dirname, '..', 'node', fsName)); newMockFs.__set__('binding', binding); // overwrite fs.Stats from original binding newMockFs.Stats = binding.Stats; return newMockFs; }; /** * Create a file factory. */ exports.file = FileSystem.file; /** * Create a directory factory. */ exports.directory = FileSystem.directory; /** * Create a symbolic link factory. */ exports.symlink = FileSystem.symlink;