diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 849c7a2..49d7e0d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,6 @@ jobs: run: npm run test --if-present - name: Release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release diff --git a/README.md b/README.md index 1b0c727..86e5886 100644 --- a/README.md +++ b/README.md @@ -39,5 +39,31 @@ const helloWorld = fsRequire('/hello-world') console.log(helloWorld()) // Hello world! ``` +## ⚙️ API + +### createFsRequire(fs, options?) +Returns a `require(modulePath)` function that resolves from the file-system passed in. + +#### fs +Type: `FileSystem` + +Required + +The file-system to resolve requires from. + +#### options +##### options.fs + +Type: `boolean | FileSystem` + +Code executed the virtual file-system may `require('fs')` and this may either pose as a security concern or yield inconsistent results as the virtual file won't not accessible through the actual `fs` module. + +By default `require('fs')` is shimmed to the file-system passed into `createFsRequire`. + +To disable this behavior and resolve to the real `fs` module, set this to `true`. + +You can also pass in a different file-system too. + + ## 👨‍👩‍👧 Related - [fs-monkey](https://github.com/streamich/fs-monkey) - By the same author of [memfs](https://github.com/streamich/memfs). Patches the global `require` to access a virtual fs. diff --git a/src/fs-require.ts b/src/fs-require.ts index c3e08e4..29a5866 100644 --- a/src/fs-require.ts +++ b/src/fs-require.ts @@ -72,7 +72,14 @@ const realRequire = require; let idCounter = 0; -export const createFsRequire = (mfs: FileSystem) => { +type Options = { + fs?: boolean | FileSystem; +}; + +export const createFsRequire = ( + mfs: FileSystem, + options?: Options, +) => { idCounter += 1; const fsRequireId = idCounter; const moduleCache = new Map(); @@ -81,11 +88,19 @@ export const createFsRequire = (mfs: FileSystem) => { const require = (modulePath: string) => { if (!isFilePathPattern.test(modulePath)) { const [moduleName, moduleSubpath] = getBareSpecifier(modulePath) ?? []; + if (moduleName === 'fs') { - if (moduleSubpath) { - throw new Error(`Cannot find module '${modulePath}'`); + const { fs } = options ?? {}; + if (!fs) { + if (moduleSubpath) { + throw new Error(`Cannot find module '${modulePath}'`); + } + return mfs; + } + + if (fs !== true) { + return fs; } - return mfs; } return realRequire(modulePath); diff --git a/tests/fs-require.spec.ts b/tests/fs-require.spec.ts index 89db911..6d79b30 100644 --- a/tests/fs-require.spec.ts +++ b/tests/fs-require.spec.ts @@ -117,29 +117,66 @@ test('native module', () => { expect(fsRequire('/index')).toBe(path); }); -test('mock fs', () => { - const randomNumber = Math.random().toString(); - const vol = Volume.fromJSON({ - '/index.js': ` - const fs = require('fs'); - fs.writeFileSync('/test-write', '${randomNumber}'); - `, +describe('mock fs', () => { + test('mock fs', () => { + const randomNumber = Math.random().toString(); + const vol = Volume.fromJSON({ + '/index.js': ` + const fs = require('fs'); + fs.writeFileSync('/test-write', '${randomNumber}'); + `, + }); + const fsRequire = createFsRequire(vol); + + fsRequire('/index'); + expect(vol.readFileSync('/test-write').toString()).toBe(randomNumber); }); - const fsRequire = createFsRequire(vol); - fsRequire('/index'); - expect(vol.readFileSync('/test-write').toString()).toBe(randomNumber); -}); + test('mock fs - fs/promises fails', () => { + const vol = Volume.fromJSON({ + '/index.js': ` + const fs = require('fs/promises'); + `, + }); + const fsRequire = createFsRequire(vol); -test('mock fs - fs/promises fails', () => { - const vol = Volume.fromJSON({ - '/index.js': ` - const fs = require('fs/promises'); - `, + expect(() => fsRequire('/index')).toThrow('Cannot find module \'fs/promises\''); }); - const fsRequire = createFsRequire(vol); - expect(() => fsRequire('/index')).toThrow('Cannot find module \'fs/promises\''); + test('disabled', () => { + const vol = Volume.fromJSON({ + '/index.js': ` + const fs = require('fs'); + module.exports = fs.readdirSync('${__dirname}'); + `, + }); + + const fsRequire = createFsRequire(vol, { + fs: true, + }); + + const files = fsRequire('/index'); + expect(files.includes('fs-require.spec.ts')).toBe(true); + }); + + test('custom', () => { + const randomNumber = Math.random().toString(); + const customFs = Volume.fromJSON({ + '/some-file': randomNumber, + }); + const vol = Volume.fromJSON({ + '/index.js': ` + const fs = require('fs'); + module.exports = fs.readFileSync('/some-file').toString(); + `, + }); + + const fsRequire = createFsRequire(vol, { + fs: customFs, + }); + + expect(fsRequire('/index')).toBe(randomNumber); + }); }); test('Works with real fs', () => {