Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
test-vers:
strategy:
matrix:
node: ['8.6', '8', '10.0', '10', '12.0', '12', '14.0', '14', '16.0', '16', '18.0', '18', '19', '20']
node: ['8.6', '8', '10.0', '10', '12.0', '12', '14.0', '14', '16.0', '16', '18.0', '18', '20', '22']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ modules on-the-fly as they are being required.
[![npm](https://img.shields.io/npm/v/require-in-the-middle.svg)](https://www.npmjs.com/package/require-in-the-middle)
[![Test status](https://github.com/elastic/require-in-the-middle/workflows/Test/badge.svg)](https://github.com/elastic/require-in-the-middle/actions)

Also supports hooking into calls to `process.getBuiltinModule()`, which was introduced in Node.js v22.3.0.

## Installation

Expand Down Expand Up @@ -72,7 +73,7 @@ argument).
### `hook.unhook()`

Removes the `onrequire` callback so that it will not be triggerd by
subsequent calls to `require()`.
subsequent calls to `require()` or `process.getBuiltinModule()`.

## License

Expand Down
47 changes: 43 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,27 @@ function Hook (modules, options, onrequire) {
return self._origRequire.apply(this, arguments)
}

return patchedRequire.call(this, arguments, false)
}

if (typeof process.getBuiltinModule === 'function') {
this._origGetBuiltinModule = process.getBuiltinModule
this._getBuiltinModule = process.getBuiltinModule = function (id) {
if (self._unhooked === true) {
// if the patched process.getBuiltinModule function could not be removed because
// someone else patched it after it was patched here, we just abort and pass the
// request onwards to the original process.getBuiltinModule
debug('ignoring process.getBuiltinModule call - module is soft-unhooked')
return self._origGetBuiltinModule.apply(this, arguments)
}

return patchedRequire.call(this, arguments, true)
}
}

// Preserve the original require/process.getBuiltinModule arguments in `args`
function patchedRequire (args, coreOnly) {
const id = args[0]
const core = isCore(id)
let filename // the string used for caching
if (core) {
Expand All @@ -151,6 +172,12 @@ function Hook (modules, options, onrequire) {
filename = idWithoutPrefix
}
}
} else if (coreOnly) {
// `coreOnly` is `true` if this was a call to `process.getBuiltinModule`, in which case
// we don't want to return anything if the requested `id` isn't a core module. Falling
// back to default behaviour, which at the time of this wrting is simply returning `undefined`
debug('call to process.getBuiltinModule with unknown built-in id')
return self._origGetBuiltinModule.apply(this, args)
} else {
try {
filename = Module._resolveFilename(id, this)
Expand All @@ -164,7 +191,7 @@ function Hook (modules, options, onrequire) {
// where `@azure/functions-core` resolves to an internal object.
// https://github.com/Azure/azure-functions-nodejs-worker/blob/v3.5.2/src/setupCoreModule.ts#L46-L54
debug('Module._resolveFilename("%s") threw %j, calling original Module.require', id, resolveErr.message)
return self._origRequire.apply(this, arguments)
return self._origRequire.apply(this, args)
}
}

Expand All @@ -185,7 +212,9 @@ function Hook (modules, options, onrequire) {
patching.add(filename)
}

const exports = self._origRequire.apply(this, arguments)
const exports = coreOnly
? self._origGetBuiltinModule.apply(this, args)
: self._origRequire.apply(this, args)

// If it's already patched, just return it as-is.
if (isPatching === true) {
Expand Down Expand Up @@ -288,11 +317,21 @@ function Hook (modules, options, onrequire) {

Hook.prototype.unhook = function () {
this._unhooked = true

if (this._require === Module.prototype.require) {
Module.prototype.require = this._origRequire
debug('unhook successful')
debug('require unhook successful')
} else {
debug('unhook unsuccessful')
debug('require unhook unsuccessful')
}

if (process.getBuiltinModule !== undefined) {
if (this._getBuiltinModule === process.getBuiltinModule) {
process.getBuiltinModule = this._origGetBuiltinModule
debug('process.getBuiltinModule unhook successful')
} else {
debug('process.getBuiltinModule unhook unsuccessful')
}
}
}

Expand Down
65 changes: 65 additions & 0 deletions test/process-getbuiltinmodule.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use strict'

const test = require('tape')

const { Hook } = require('../')

// The `process.getBuiltinModule(id) function was added in Node.js 22.3.0
const skip = !process.getBuiltinModule

test('process.getBuiltinModule should be patched', { skip }, function (t) {
let numOnRequireCalls = 0

const hook = new Hook(['http'], function (exports, name, basedir) {
numOnRequireCalls++
return exports
})

const a = process.getBuiltinModule('http')
t.equal(numOnRequireCalls, 1)

const b = require('http')
t.equal(numOnRequireCalls, 1)

t.strictEqual(a, b, 'modules are the same')

t.end()
hook.unhook()
})

test('patched process.getBuiltinModule should work with node: prefix', { skip }, function (t) {
let numOnRequireCalls = 0

const hook = new Hook(['http'], function (exports, name, basedir) {
numOnRequireCalls++
return exports
})

process.getBuiltinModule('node:http')
t.equal(numOnRequireCalls, 1)
t.end()
hook.unhook()
})

test('patched process.getBuiltinModule should preserve default behavior for non-builtin modules', { skip }, function (t) {
const beforePatching = process.getBuiltinModule('ipp-printer')

const hook = new Hook(['ipp-printer'], function (exports, name, basedir) {
t.fail('should not call hook')
})

const afterPatching = process.getBuiltinModule('ipp-printer')

t.strictEqual(beforePatching, afterPatching, 'modules are the same')
t.end()
hook.unhook()
})

test('hook.unhook() works for process.getBuiltinModule', { skip }, function (t) {
const hook = new Hook(['http'], function (exports, name, basedir) {
t.fail('should not call onrequire')
})
hook.unhook()
process.getBuiltinModule('http')
t.end()
})