├── .babelrc ├── .github └── workflows │ └── checks.yml ├── .gitignore ├── .npmignore ├── .nvmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── SECURITY.md ├── demo ├── fs-1.js ├── require-1.js ├── require-2.js └── yolo-1.js ├── docs └── api │ ├── patchFs.md │ └── patchRequire.md ├── package-lock.json ├── package.json ├── renovate.json └── src ├── __tests__ └── patchFs.test.js ├── correctPath.js ├── index.js ├── patchFs.js ├── patchRequire.js └── util └── lists.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "comments": false 4 | } 5 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - next 9 | pull_request: 10 | branches: 11 | - master 12 | - main 13 | - next 14 | 15 | jobs: 16 | test-node: 17 | name: Test on Node.js v${{ matrix.node-version }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | node-version: [14.x, 16.x, 17.x, 18.x] 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | cache: npm 31 | - name: install 32 | run: npm ci 33 | - name: run tests 34 | run: npm run test 35 | env: 36 | CI: true 37 | test-os: 38 | name: Test on ${{ matrix.os }} using Node.js LTS 39 | strategy: 40 | fail-fast: false 41 | matrix: 42 | os: [ubuntu-latest, windows-latest, macOS-latest] 43 | runs-on: ${{ matrix.os }} 44 | 45 | steps: 46 | - uses: actions/checkout@v3 47 | - uses: actions/setup-node@v3 48 | with: 49 | node-version: lts/* 50 | cache: npm 51 | - name: install 52 | run: npm ci 53 | - name: run tests 54 | run: npm run test 55 | env: 56 | CI: true 57 | release: 58 | if: 59 | # prettier-ignore 60 | ${{ github.event_name == 'push' && (github.event.ref == 'refs/heads/main' || github.event.ref == 'refs/heads/next' || github.event.ref == 'refs/heads/master') }} 61 | name: Release new version 62 | needs: [test-node, test-os] 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v3 66 | - uses: actions/setup-node@v3 67 | with: 68 | node-version: lts/* 69 | cache: npm 70 | - name: install 71 | run: npm ci 72 | - run: npm run build 73 | - run: npx semantic-release 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | /.idea 4 | .nyc_output 5 | coverage 6 | package-lock.json 7 | /lib/ 8 | yarn.lock 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | /typings 3 | example.js 4 | tsd.json 5 | simple.js 6 | node_modules 7 | /test 8 | .idea 9 | .idea/ 10 | .nyc_output 11 | coverage 12 | package-lock.json 13 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8.17.0 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.0.6](https://github.com/streamich/fs-monkey/compare/v1.0.5...v1.0.6) (2024-05-01) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * remove unessessary executable bits ([#386](https://github.com/streamich/fs-monkey/issues/386)) ([d42501a](https://github.com/streamich/fs-monkey/commit/d42501a05d3d9510547c35d33d2a8886fd034ee7)) 7 | 8 | ## [1.0.5](https://github.com/streamich/fs-monkey/compare/v1.0.4...v1.0.5) (2023-09-25) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * look for `exports.require` in `package.json` in addition to `main` when patching `require` ([#378](https://github.com/streamich/fs-monkey/issues/378)) ([411b791](https://github.com/streamich/fs-monkey/commit/411b7910fed8951b0fa5bab4daa945470ce26077)) 14 | 15 | ## [1.0.4](https://github.com/streamich/fs-monkey/compare/v1.0.3...v1.0.4) (2023-06-01) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * add .rm and .rmSync to fs methods ([#362](https://github.com/streamich/fs-monkey/issues/362)) ([2f1a49f](https://github.com/streamich/fs-monkey/commit/2f1a49f7679ac14374088086f3740e76b03caf73)) 21 | 22 | ## [1.0.3](https://github.com/streamich/fs-monkey/compare/v1.0.2...v1.0.3) (2021-04-05) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * add missing parameter in jsdoc for `patchRequire` ([#233](https://github.com/streamich/fs-monkey/issues/233)) ([28a5d9b](https://github.com/streamich/fs-monkey/commit/28a5d9b46fbdfec42c6d841aec31874ea9ea9ca4)) 28 | 29 | ## [1.0.1](https://github.com/streamich/fs-monkey/compare/v1.0.0...v1.0.1) (2020-05-14) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * release broken import ([70080c5](https://github.com/streamich/fs-monkey/commit/70080c5fbb1e4ac82497016c4e06218cdedacd72)) 35 | 36 | # [1.0.0](https://github.com/streamich/fs-monkey/compare/v0.3.1...v1.0.0) (2020-02-17) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * export `unixify` from top level ([d274d74](https://github.com/streamich/fs-monkey/commit/d274d74a29c368a5f881f5f2acf81c4772497581)) 42 | * export `util` from top level ([16655c5](https://github.com/streamich/fs-monkey/commit/16655c583a7e1a3237ec7351c50a0fae373d8795)) 43 | 44 | 45 | ### Continuous Integration 46 | 47 | * 🎡 update semantic-release setup ([5553829](https://github.com/streamich/fs-monkey/commit/555382978f50646ec537330e8a50a6f06ef9b6e3)) 48 | 49 | 50 | ### BREAKING CHANGES 51 | 52 | * 🧨 Release new major 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fs-monkey 2 | 3 | [![][npm-img]][npm-url] [![][travis-badge]][travis-url] 4 | 5 | Monkey-patches for filesystem related things. 6 | 7 | - Rewrite `require` function to load Node's modules from memory. 8 | - Or rewrite the whole `fs` filesystem module. 9 | 10 | ## Install 11 | 12 | ```shell 13 | npm install --save fs-monkey 14 | ``` 15 | 16 | ## Terms 17 | 18 | An *fs-like* object is an object that implements methods of Node's 19 | [filesystem API](https://nodejs.org/api/fs.html). 20 | It is denoted as `vol`: 21 | 22 | ```js 23 | let vol = { 24 | readFile: () => { /* ... */ }, 25 | readFileSync: () => { /* ... */ }, 26 | // etc... 27 | } 28 | ``` 29 | 30 | 31 | ## Reference 32 | 33 | - [`patchFs`](./docs/api/patchFs.md) - rewrites Node's filesystem module `fs` with *fs-like* object `vol` 34 | - [`patchRequire`](./docs/api/patchRequire.md) - rewrites `require` function, patches Node's `module` module to use a given *fs-like* object for module loading 35 | 36 | 37 | [npm-img]: https://img.shields.io/npm/v/fs-monkey.svg 38 | [npm-url]: https://www.npmjs.com/package/fs-monkey 39 | [travis-url]: https://travis-ci.org/streamich/fs-monkey 40 | [travis-badge]: https://travis-ci.org/streamich/fs-monkey.svg?branch=master 41 | 42 | 43 | ## License 44 | 45 | [Unlicense](./LICENSE) - public domain. 46 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We release patches for security vulnerabilities. The latest major version 6 | will support security patches. 7 | 8 | ## Reporting a Vulnerability 9 | 10 | Please report (suspected) security vulnerabilities to 11 | **[streamich@gmail.com](mailto:streamich@gmail.com)**. We will try to respond 12 | within 48 hours. If the issue is confirmed, we will release a patch as soon 13 | as possible depending on complexity. 14 | -------------------------------------------------------------------------------- /demo/fs-1.js: -------------------------------------------------------------------------------- 1 | import {vol} from 'memfs'; 2 | import {patchFs} from 'fs-monkey'; 3 | 4 | vol.fromJSON({'/dir/foo': 'bar'}); 5 | patchFs(vol); 6 | console.log(require('fs').readdirSync('/')); 7 | -------------------------------------------------------------------------------- /demo/require-1.js: -------------------------------------------------------------------------------- 1 | import {vol} from 'memfs'; 2 | import {patchRequire} from 'fs-monkey'; 3 | 4 | 5 | vol.fromJSON({'/foo/bar.js': 'console.log("obi trice");'}); 6 | patchRequire(vol); 7 | 8 | 9 | require('/foo/bar'); 10 | -------------------------------------------------------------------------------- /demo/require-2.js: -------------------------------------------------------------------------------- 1 | import {vol} from 'memfs'; 2 | import {patchRequire} from 'fs-monkey'; 3 | import {ufs} from 'unionfs'; 4 | import * as fs from 'fs'; 5 | 6 | 7 | vol.fromJSON({'/foo/bar.js': 'console.log("obi trice");'}); 8 | ufs 9 | .use(vol) 10 | .use(fs); 11 | 12 | 13 | patchRequire(ufs); 14 | require('/foo/bar.js'); 15 | -------------------------------------------------------------------------------- /demo/yolo-1.js: -------------------------------------------------------------------------------- 1 | import {patchFs} from 'fs-monkey'; 2 | 3 | const myfs = { 4 | readFileSync: () => 'hello world', 5 | }; 6 | 7 | patchFs(myfs); 8 | console.log(require('fs').readFileSync('/foo/bar')); // hello world 9 | -------------------------------------------------------------------------------- /docs/api/patchFs.md: -------------------------------------------------------------------------------- 1 | # `patchFs(vol[, fs])` 2 | 3 | Rewrites Node's filesystem module `fs` with *fs-like* object. 4 | 5 | - `vol` - fs-like object 6 | - `fs` *(optional)* - a filesystem to patch, defaults to `require('fs')` 7 | 8 | ```js 9 | import {patchFs} from 'fs-monkey'; 10 | 11 | const myfs = { 12 | readFileSync: () => 'hello world', 13 | }; 14 | 15 | patchFs(myfs); 16 | console.log(require('fs').readFileSync('/foo/bar')); // hello world 17 | ``` 18 | 19 | You don't need to create *fs-like* objects yourself, use [`memfs`](https://github.com/streamich/memfs) 20 | to create a virtual filesystem for you: 21 | 22 | ```js 23 | import {vol} from 'memfs'; 24 | import {patchFs} from 'fs-monkey'; 25 | 26 | vol.fromJSON({'/dir/foo': 'bar'}); 27 | patchFs(vol); 28 | console.log(require('fs').readdirSync('/')); // [ 'dir' ] 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/api/patchRequire.md: -------------------------------------------------------------------------------- 1 | # `patchRequire(vol[, unixifyPaths[, Module]])` 2 | 3 | Patches Node's `module` module to use a given *fs-like* object `vol` for module loading. 4 | 5 | - `vol` - fs-like object 6 | - `unixifyPaths` *(optional)* - whether to convert Windows paths to unix style paths, defaults to `false`. 7 | - `Module` *(optional)* - a module to patch, defaults to `require('module')` 8 | 9 | Monkey-patches the `require` function in Node, this way you can make 10 | Node.js to *require* modules from your custom filesystem. 11 | 12 | It expects an object with three filesystem methods implemented that are 13 | needed for the `require` function to work. 14 | 15 | ```js 16 | let vol = { 17 | readFileSync: () => {}, 18 | realpathSync: () => {}, 19 | statSync: () => {}, 20 | }; 21 | ``` 22 | 23 | If you want to make Node.js to *require* your files from memory, you 24 | don't need to implement those functions yourself, just use the 25 | [`memfs`](https://github.com/streamich/memfs) package: 26 | 27 | ```js 28 | import {vol} from 'memfs'; 29 | import {patchRequire} from 'fs-monkey'; 30 | 31 | vol.fromJSON({'/foo/bar.js': 'console.log("obi trice");'}); 32 | patchRequire(vol); 33 | require('/foo/bar'); // obi trice 34 | ``` 35 | 36 | Now the `require` function will only load the files from the `vol` file 37 | system, but not from the actual filesystem on the disk. 38 | 39 | If you want the `require` function to load modules from both file 40 | systems, use the [`unionfs`](https://github.com/streamich/unionfs) package 41 | to combine both filesystems into a union: 42 | 43 | ```js 44 | import {vol} from 'memfs'; 45 | import {patchRequire} from 'fs-monkey'; 46 | import {ufs} from 'unionfs'; 47 | import * as fs from 'fs'; 48 | 49 | vol.fromJSON({'/foo/bar.js': 'console.log("obi trice");'}); 50 | ufs 51 | .use(vol) 52 | .use(fs); 53 | patchRequire(ufs); 54 | require('/foo/bar.js'); // obi trice 55 | ``` 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fs-monkey", 3 | "version": "1.0.6", 4 | "description": "Monkey patches for file system related things.", 5 | "main": "lib/index.js", 6 | "license": "Unlicense", 7 | "keywords": [ 8 | "fs", 9 | "file", 10 | "file system", 11 | "monkey", 12 | "fsmonkey", 13 | "monkeyfs", 14 | "monkeypatch", 15 | "patch" 16 | ], 17 | "files": [ 18 | "lib", 19 | "!lib/__tests__", 20 | "docs" 21 | ], 22 | "directories": { 23 | "doc": "docs" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/streamich/fs-monkey.git" 28 | }, 29 | "scripts": { 30 | "build": "babel src --out-dir lib", 31 | "test": "jest" 32 | }, 33 | "dependencies": {}, 34 | "devDependencies": { 35 | "@babel/cli": "^7.18.6", 36 | "@babel/core": "^7.18.6", 37 | "@babel/preset-env": "^7.18.6", 38 | "@semantic-release/changelog": "^6.0.1", 39 | "@semantic-release/git": "^10.0.1", 40 | "@semantic-release/npm": "^9.0.1", 41 | "@types/jest": "^29.0.0", 42 | "@types/node": "^8.10.66", 43 | "babel-jest": "^29.0.0", 44 | "jest": "^29.0.0", 45 | "semantic-release": "^19.0.3", 46 | "source-map-support": "^0.5.21" 47 | }, 48 | "release": { 49 | "verifyConditions": [ 50 | "@semantic-release/changelog", 51 | "@semantic-release/npm", 52 | "@semantic-release/git" 53 | ], 54 | "prepare": [ 55 | "@semantic-release/changelog", 56 | "@semantic-release/npm", 57 | "@semantic-release/git" 58 | ] 59 | }, 60 | "jest": { 61 | "collectCoverageFrom": [ 62 | "src/**/*.js" 63 | ], 64 | "transform": { 65 | "^.+\\.jsx?$": "babel-jest" 66 | }, 67 | "testRegex": ".*(__tests__/|/test/unit/).*(test|spec)\\.(t|j)sx?$" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "lockFileMaintenance": { 4 | "enabled": true, 5 | "automerge": true, 6 | "schedule": ["before 3am on the first day of the month"] 7 | }, 8 | "rangeStrategy": "replace", 9 | "postUpdateOptions": ["yarnDedupeHighest"] 10 | } 11 | -------------------------------------------------------------------------------- /src/__tests__/patchFs.test.js: -------------------------------------------------------------------------------- 1 | import patchFs from '../patchFs'; 2 | 3 | describe('patchFs', () => { 4 | it('should overwrite the .readFileSync method', () => { 5 | const vol = { 6 | readFileSync: () => 'foo', 7 | }; 8 | const fs = {}; 9 | patchFs(vol, fs); 10 | expect(typeof fs.readFileSync).toBe('function'); 11 | expect(fs.readFileSync()).toBe('foo'); 12 | }); 13 | 14 | it('should copy constants', () => { 15 | const vol = { 16 | F_OK: 123, 17 | }; 18 | const fs = {}; 19 | patchFs(vol, fs); 20 | expect(fs.F_OK).toBe(vol.F_OK); 21 | }); 22 | 23 | describe('unpatch()', () => { 24 | it('should return "unpatch" method', () => { 25 | const vol = { 26 | F_OK: 123, 27 | }; 28 | const fs = {}; 29 | 30 | expect(typeof patchFs(vol, fs)).toBe('function'); 31 | }); 32 | 33 | it('should restore the original fs', () => { 34 | const original = function () {}; 35 | const vol = { 36 | writeFileSync: () => {}, 37 | }; 38 | const fs = { 39 | writeFileSync: original, 40 | }; 41 | 42 | const unpatch = patchFs(vol, fs); 43 | 44 | expect(fs.writeFileSync).not.toBe(original); 45 | 46 | unpatch(); 47 | 48 | expect(fs.writeFileSync).toBe(original); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/correctPath.js: -------------------------------------------------------------------------------- 1 | const isWin = process.platform === 'win32'; 2 | 3 | /*! 4 | * removeTrailingSeparator 5 | * 6 | * Inlined from: 7 | * Copyright (c) darsain. 8 | * Released under the ISC License. 9 | */ 10 | function removeTrailingSeparator(str) { 11 | let i = str.length - 1; 12 | if (i < 2) { 13 | return str; 14 | } 15 | while (isSeparator(str, i)) { 16 | i--; 17 | } 18 | return str.substr(0, i + 1); 19 | } 20 | 21 | function isSeparator(str, i) { 22 | let char = str[i]; 23 | return i > 0 && (char === '/' || (isWin && char === '\\')); 24 | } 25 | 26 | /*! 27 | * normalize-path 28 | * 29 | * Inlined from: 30 | * Copyright (c) 2014-2017, Jon Schlinkert. 31 | * Released under the MIT License. 32 | */ 33 | function normalizePath(str, stripTrailing) { 34 | if (typeof str !== 'string') { 35 | throw new TypeError('expected a string'); 36 | } 37 | str = str.replace(/[\\\/]+/g, '/'); 38 | if (stripTrailing !== false) { 39 | str = removeTrailingSeparator(str); 40 | } 41 | return str; 42 | } 43 | 44 | /*! 45 | * unixify 46 | * 47 | * Inlined from: 48 | * Copyright (c) 2014, 2017, Jon Schlinkert. 49 | * Released under the MIT License. 50 | */ 51 | export function unixify(filepath, stripTrailing = true) { 52 | if(isWin) { 53 | filepath = normalizePath(filepath, stripTrailing); 54 | return filepath.replace(/^([a-zA-Z]+:|\.\/)/, ''); 55 | } 56 | return filepath; 57 | } 58 | 59 | /* 60 | * Corrects a windows path to unix format (including \\?\c:...) 61 | */ 62 | export function correctPath(filepath) { 63 | return unixify(filepath.replace(/^\\\\\?\\.:\\/,'\\')); 64 | } 65 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import patchFs from './patchFs'; 2 | import patchRequire from './patchRequire'; 3 | import { unixify } from './correctPath'; 4 | import * as util from './util/lists'; 5 | 6 | export { 7 | util, 8 | unixify, 9 | patchFs, 10 | patchRequire, 11 | }; 12 | -------------------------------------------------------------------------------- /src/patchFs.js: -------------------------------------------------------------------------------- 1 | import {fsProps, fsAsyncMethods, fsSyncMethods} from './util/lists'; 2 | 3 | 4 | export default function patchFs(vol, fs = require('fs')) { 5 | const bkp = {}; 6 | 7 | const patch = (key, newValue) => { 8 | bkp[key] = fs[key]; 9 | fs[key] = newValue; 10 | }; 11 | 12 | const patchMethod = key => patch(key, vol[key].bind(vol)); 13 | 14 | // General properties 15 | for(let prop of fsProps) 16 | if(typeof vol[prop] !== 'undefined') 17 | patch(prop, vol[prop]); 18 | 19 | 20 | // Bind the first argument of some constructors, this is relevant for `memfs`. 21 | // TODO: Maybe in the future extend this function such that it internally creates 22 | // TODO: the below four constructor functions. 23 | if(typeof vol.StatWatcher === 'function') { 24 | patch('StatWatcher', vol.FSWatcher.bind(null, vol)); 25 | } 26 | if(typeof vol.FSWatcher === 'function') { 27 | patch('FSWatcher', vol.StatWatcher.bind(null, vol)); 28 | } 29 | if(typeof vol.ReadStream === 'function') { 30 | patch('ReadStream', vol.ReadStream.bind(null, vol)); 31 | } 32 | if(typeof vol.WriteStream === 'function') { 33 | patch('WriteStream', vol.WriteStream.bind(null, vol)); 34 | } 35 | 36 | 37 | // Extra hidden function 38 | if(typeof vol._toUnixTimestamp === 'function') 39 | patchMethod('_toUnixTimestamp'); 40 | 41 | 42 | // Main API 43 | for(let method of fsAsyncMethods) 44 | if(typeof vol[method] === 'function') 45 | patchMethod(method); 46 | 47 | for(let method of fsSyncMethods) 48 | if(typeof vol[method] === 'function') 49 | patchMethod(method); 50 | 51 | // Give user back a method to revert the changes. 52 | return function unpatch () { 53 | for (const key in bkp) fs[key] = bkp[key]; 54 | }; 55 | }; 56 | -------------------------------------------------------------------------------- /src/patchRequire.js: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | const isWin32 = process.platform === 'win32'; 4 | const correctPath = isWin32 ? require('./correctPath').correctPath : p => p; 5 | 6 | /** 7 | * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) 8 | * because the buffer-to-string conversion in `fs.readFileSync()` 9 | * translates it to FEFF, the UTF-16 BOM. 10 | */ 11 | function stripBOM(content) { 12 | if (content.charCodeAt(0) === 0xFEFF) { 13 | content = content.slice(1); 14 | } 15 | return content; 16 | } 17 | 18 | /** 19 | * Rewrites `modules.js`, which is the factory for the `require` function. 20 | * You give this function your custom file system object and this function 21 | * will patch `module.js` to use that instead of the built-it `fs.js` file system. 22 | * 23 | * This function expects an object with three methods: 24 | * 25 | * patchRequire({ 26 | * readFileSync: () => {}, 27 | * realpathSync: () => {}, 28 | * statSync: () => {}, 29 | * }); 30 | * 31 | * The methods should behave like the ones on the native `fs` object. 32 | * 33 | * @param {Object} vol 34 | * @param {boolean} [unixifyPaths=false] 35 | * @param {Object} Module Module loader to patch. 36 | */ 37 | export default function patchRequire(vol, unixifyPaths = false, Module = require('module')) { 38 | 39 | // ensure all paths are corrected before use. 40 | if(isWin32 && unixifyPaths) { 41 | const original = vol; 42 | vol = { 43 | readFileSync: (path,options) => { 44 | return original.readFileSync(correctPath(path),options); 45 | }, 46 | 47 | realpathSync: (path) => { 48 | return original.realpathSync(correctPath(path)); 49 | }, 50 | 51 | statSync: (path) => { 52 | return original.statSync(correctPath(path)); 53 | } 54 | }; 55 | } 56 | 57 | // Used to speed up module loading. Returns the contents of the file as 58 | // a string or undefined when the file cannot be opened. The speedup 59 | // comes from not creating Error objects on failure. 60 | function internalModuleReadFile(path) { 61 | try { 62 | return vol.readFileSync(path, 'utf8'); 63 | } catch(err) { 64 | 65 | } 66 | } 67 | 68 | // Used to speed up module loading. Returns 0 if the path refers to 69 | // a file, 1 when it's a directory or < 0 on error (usually -ENOENT.) 70 | // The speedup comes from not creating thousands of Stat and Error objects. 71 | function internalModuleStat(filename) { 72 | try { 73 | return vol.statSync(filename).isDirectory() ? 1 : 0; 74 | } catch(err) { 75 | return -2; // ENOENT 76 | } 77 | } 78 | 79 | function stat(filename) { 80 | filename = path._makeLong(filename); 81 | const cache = stat.cache; 82 | if (cache !== null) { 83 | const result = cache.get(filename); 84 | if (result !== undefined) return result; 85 | } 86 | const result = internalModuleStat(filename); 87 | if (cache !== null) cache.set(filename, result); 88 | return result; 89 | } 90 | stat.cache = null; 91 | 92 | 93 | const preserveSymlinks = false; 94 | 95 | 96 | function toRealPath(requestPath) { 97 | return vol.realpathSync(requestPath); 98 | } 99 | 100 | 101 | const packageMainCache = Object.create(null); 102 | function readPackage(requestPath) { 103 | const entry = packageMainCache[requestPath]; 104 | if (entry) 105 | return entry; 106 | 107 | const jsonPath = path.resolve(requestPath, 'package.json'); 108 | const json = internalModuleReadFile(path._makeLong(jsonPath)); 109 | 110 | if (json === undefined) { 111 | return false; 112 | } 113 | 114 | let pkg; 115 | try { 116 | const pkgJson = JSON.parse(json); 117 | pkg = packageMainCache[requestPath] = pkgJson.exports && pkgJson.exports.require || pkgJson.main; 118 | } catch (e) { 119 | e.path = jsonPath; 120 | e.message = 'Error parsing ' + jsonPath + ': ' + e.message; 121 | throw e; 122 | } 123 | return pkg; 124 | } 125 | 126 | 127 | function tryFile(requestPath, isMain) { 128 | const rc = stat(requestPath); 129 | if (preserveSymlinks && !isMain) { 130 | return rc === 0 && path.resolve(requestPath); 131 | } 132 | return rc === 0 && toRealPath(requestPath); 133 | } 134 | 135 | 136 | // given a path check a the file exists with any of the set extensions 137 | function tryExtensions(p, exts, isMain) { 138 | for (var i = 0; i < exts.length; i++) { 139 | const filename = tryFile(p + exts[i], isMain); 140 | 141 | if (filename) { 142 | return filename; 143 | } 144 | } 145 | return false; 146 | } 147 | 148 | 149 | function tryPackage(requestPath, exts, isMain) { 150 | let pkg = readPackage(requestPath); 151 | 152 | if (!pkg) return false; 153 | 154 | let filename = path.resolve(requestPath, pkg); 155 | return tryFile(filename, isMain) || 156 | tryExtensions(filename, exts, isMain) || 157 | tryExtensions(path.resolve(filename, 'index'), exts, isMain); 158 | } 159 | 160 | 161 | // Native extension for .js 162 | Module._extensions['.js'] = function(module, filename) { 163 | let content = vol.readFileSync(filename, 'utf8'); 164 | module._compile(stripBOM(content), filename); 165 | }; 166 | 167 | // Native extension for .json 168 | Module._extensions['.json'] = function(module, filename) { 169 | let content = vol.readFileSync(filename, 'utf8'); 170 | try { 171 | module.exports = JSON.parse(stripBOM(content)); 172 | } catch (err) { 173 | err.message = filename + ': ' + err.message; 174 | throw err; 175 | } 176 | }; 177 | 178 | let warned = true; 179 | Module._findPath = function(request, paths, isMain) { 180 | if (path.isAbsolute(request)) { 181 | paths = ['']; 182 | } else if (!paths || paths.length === 0) { 183 | return false; 184 | } 185 | 186 | var cacheKey = request + '\x00' + 187 | (paths.length === 1 ? paths[0] : paths.join('\x00')); 188 | var entry = Module._pathCache[cacheKey]; 189 | if (entry) 190 | return entry; 191 | 192 | var exts; 193 | var trailingSlash = request.length > 0 && 194 | request.charCodeAt(request.length - 1) === 47/*/*/; 195 | 196 | // For each path 197 | for (var i = 0; i < paths.length; i++) { 198 | // Don't search further if path doesn't exist 199 | const curPath = paths[i]; 200 | if (curPath && stat(curPath) < 1) continue; 201 | var basePath = correctPath( path.resolve(curPath, request) ); 202 | var filename; 203 | 204 | var rc = stat(basePath); 205 | if (!trailingSlash) { 206 | if (rc === 0) { // File. 207 | if (preserveSymlinks && !isMain) { 208 | filename = path.resolve(basePath); 209 | } else { 210 | filename = toRealPath(basePath); 211 | } 212 | } else if (rc === 1) { // Directory. 213 | if (exts === undefined) 214 | exts = Object.keys(Module._extensions); 215 | filename = tryPackage(basePath, exts, isMain); 216 | } 217 | 218 | if (!filename) { 219 | // try it with each of the extensions 220 | if (exts === undefined) 221 | exts = Object.keys(Module._extensions); 222 | filename = tryExtensions(basePath, exts, isMain); 223 | } 224 | } 225 | 226 | if (!filename && rc === 1) { // Directory. 227 | if (exts === undefined) 228 | exts = Object.keys(Module._extensions); 229 | filename = tryPackage(basePath, exts, isMain); 230 | } 231 | 232 | if (!filename && rc === 1) { // Directory. 233 | // try it with each of the extensions at "index" 234 | if (exts === undefined) 235 | exts = Object.keys(Module._extensions); 236 | filename = tryExtensions(path.resolve(basePath, 'index'), exts, isMain); 237 | } 238 | 239 | if (filename) { 240 | // Warn once if '.' resolved outside the module dir 241 | if (request === '.' && i > 0) { 242 | if (!warned) { 243 | warned = true; 244 | process.emitWarning( 245 | 'warning: require(\'.\') resolved outside the package ' + 246 | 'directory. This functionality is deprecated and will be removed ' + 247 | 'soon.', 248 | 'DeprecationWarning', 'DEP0019'); 249 | } 250 | } 251 | 252 | Module._pathCache[cacheKey] = filename; 253 | return filename; 254 | } 255 | } 256 | return false; 257 | }; 258 | } 259 | -------------------------------------------------------------------------------- /src/util/lists.js: -------------------------------------------------------------------------------- 1 | 2 | export const fsProps = [ 3 | 'constants', 4 | 'F_OK', 5 | 'R_OK', 6 | 'W_OK', 7 | 'X_OK', 8 | 'Stats', 9 | ]; 10 | 11 | export const fsSyncMethods = [ 12 | 'renameSync', 13 | 'ftruncateSync', 14 | 'truncateSync', 15 | 'chownSync', 16 | 'fchownSync', 17 | 'lchownSync', 18 | 'chmodSync', 19 | 'fchmodSync', 20 | 'lchmodSync', 21 | 'statSync', 22 | 'lstatSync', 23 | 'fstatSync', 24 | 'linkSync', 25 | 'symlinkSync', 26 | 'readlinkSync', 27 | 'realpathSync', 28 | 'unlinkSync', 29 | 'rmdirSync', 30 | 'mkdirSync', 31 | 'mkdirpSync', 32 | 'readdirSync', 33 | 'closeSync', 34 | 'openSync', 35 | 'utimesSync', 36 | 'futimesSync', 37 | 'fsyncSync', 38 | 'writeSync', 39 | 'readSync', 40 | 'readFileSync', 41 | 'writeFileSync', 42 | 'appendFileSync', 43 | 'existsSync', 44 | 'accessSync', 45 | 'fdatasyncSync', 46 | 'mkdtempSync', 47 | 'copyFileSync', 48 | 'rmSync', 49 | 50 | 'createReadStream', 51 | 'createWriteStream', 52 | ]; 53 | 54 | export const fsAsyncMethods = [ 55 | 'rename', 56 | 'ftruncate', 57 | 'truncate', 58 | 'chown', 59 | 'fchown', 60 | 'lchown', 61 | 'chmod', 62 | 'fchmod', 63 | 'lchmod', 64 | 'stat', 65 | 'lstat', 66 | 'fstat', 67 | 'link', 68 | 'symlink', 69 | 'readlink', 70 | 'realpath', 71 | 'unlink', 72 | 'rmdir', 73 | 'mkdir', 74 | 'mkdirp', 75 | 'readdir', 76 | 'close', 77 | 'open', 78 | 'utimes', 79 | 'futimes', 80 | 'fsync', 81 | 'write', 82 | 'read', 83 | 'readFile', 84 | 'writeFile', 85 | 'appendFile', 86 | 'exists', 87 | 'access', 88 | 'fdatasync', 89 | 'mkdtemp', 90 | 'copyFile', 91 | 'rm', 92 | 93 | 'watchFile', 94 | 'unwatchFile', 95 | 'watch', 96 | ]; 97 | --------------------------------------------------------------------------------