@contrast/require-hook
Advanced tools
Comparing version 4.0.0 to 4.1.0
@@ -24,11 +24,22 @@ export = ExportHandlerRegistry; | ||
/** | ||
* contains all modules that have been resolved and are in require cache | ||
* Contains all registered hooks that have been added by `.update()` | ||
* Keyed by `shortname`. | ||
* @type {Record<string, ExportHookDescriptor<any>[]>} | ||
*/ | ||
registered: Record<string, ExportHookDescriptor<any>[]>; | ||
/** | ||
* Contains all modules that have been resolved and are in require cache | ||
* Keyed by absolute filename. | ||
* @type {Record<string, HandlerData<any>>} | ||
*/ | ||
resolved: Record<string, HandlerData<any>>; | ||
/** @type {Record<string, ExportHookDescriptor<any>[]>} */ | ||
registered: Record<string, ExportHookDescriptor<any>[]>; | ||
/** @type {Record<string, ExportHookDescriptor<any>[]>} */ | ||
filenameToRegistered: Record<string, ExportHookDescriptor<any>[]>; | ||
/** | ||
* Update the registry with the handler info for the given module name. | ||
* This gets called when an agent registers a handler for a given module. | ||
* @template {Object} T | ||
* @param {ExportHookDescriptor<T>} info | ||
* @returns {ExportHookDescriptor<T>[]} | ||
*/ | ||
update<T extends Object>(info: import("./export-hook-descriptor")<T>): import("./export-hook-descriptor")<T>[]; | ||
/** | ||
* Defined as a class method to allow easy stubbing in tests. | ||
@@ -38,3 +49,3 @@ * @param {string} request the string passed to require() | ||
* @param {boolean} isMain indicates whether the module executing require() is the entry point | ||
* @returns {string} | ||
* @returns {string} fully resolved filename of the required module | ||
*/ | ||
@@ -49,23 +60,22 @@ resolveFilename(request: string, parent: Module, isMain: boolean): string; | ||
/** | ||
* Update the registry with the handler info for the given module name. | ||
* This gets called when an agent registers a handler for a given module. | ||
* Iterates all descriptors pertaining to the currently-loading export. It | ||
* builds into the set of resolved paths the metadata and handlers for each | ||
* handler that is applicable to the export. | ||
* @template {Object} T | ||
* @param {string} name | ||
* @param {ExportHookDescriptor<T>} handlerInfo | ||
* @returns {ExportHookDescriptor<T>[]} | ||
* @param {PackageFinder.Metadata} metadata module metadata | ||
* @param {ExportHookDescriptor<T>[]} registered list of registered hooks | ||
*/ | ||
update<T extends Object>(name: string, handlerInfo: import("./export-hook-descriptor")<T>): import("./export-hook-descriptor")<T>[]; | ||
buildFromRegistry<T_1 extends Object>(metadata: PackageFinder.Metadata, registered: import("./export-hook-descriptor")<T_1>[]): void; | ||
/** | ||
* When an export is returned from a `require`, we want to resolve the name | ||
* used as the param, e.g. "http" in `require('http')`, to a set of | ||
* metadata and handlers that have been registered for exports by that name. | ||
* Returns registered handlers and the module metadata for a module that has | ||
* registration for the version that is being required. | ||
* @template {Object} T | ||
* @param {string} request the string passed to require() | ||
* @param {Module} parent the module executing require() | ||
* @param {boolean} isMain indicates whether the module executing require() is the entry point | ||
* @param {PackageFinder.Metadata} metadata module metadata | ||
* @return {HandlerData<T>=} | ||
*/ | ||
query<T_1 extends Object>(request: string, parent: Module, isMain: boolean): HandlerData<T_1> | undefined; | ||
findModuleHandlerData<T_2 extends Object>(metadata: PackageFinder.Metadata): HandlerData<T_2> | undefined; | ||
/** | ||
* Returns handler data for a deep reference if present. | ||
* When an export is returned from a `require`, we want to resolve the name | ||
* used as the param, e.g. "http" in `require('http')`, to a set of | ||
* metadata and handlers that have been registered for exports by that name. | ||
* @template {Object} T | ||
@@ -75,54 +85,5 @@ * @param {string} request the string passed to require() | ||
* @param {boolean} isMain indicates whether the module executing require() is the entry point | ||
* @param {string} filename fully resolved filename of the required module | ||
* @return {ExportHookDescriptor<T>[]=} handler data for name | ||
*/ | ||
findDeepReference<T_2 extends Object>(request: string, parent: Module, isMain: boolean, filename: string): import("./export-hook-descriptor")<T_2>[] | undefined; | ||
/** | ||
* Returns an array of handlers that have been registered for a given module. | ||
* @template {Object} T | ||
* @param {string} request the string passed to require() | ||
* @param {string} filename fully resolved filename of the required module | ||
* @return {ExportHookDescriptor<T>[]=} | ||
*/ | ||
findRegisteredHandlerData<T_3 extends Object>(request: string, filename: string): import("./export-hook-descriptor")<T_3>[] | undefined; | ||
/** | ||
* Returns registered handlers and the module metadata for a module that has | ||
* registration for the version that is being required. | ||
* @template {Object} T | ||
* @param {Object} params | ||
* @param {string} params.request the string passed to require() | ||
* @param {string} params.filename fully resolved filename of the required module | ||
* @param {PackageFinder.Metadata} params.metadata module metadata | ||
* @return {HandlerData<T>=} | ||
*/ | ||
findModuleHandlerData<T_4 extends Object>({ request, filename, metadata }: { | ||
request: string; | ||
filename: string; | ||
metadata: PackageFinder.Metadata; | ||
}): HandlerData<T_4> | undefined; | ||
/** | ||
* Returns true if the file exists and false if it doesn't. | ||
* @param {string} filename | ||
* @return {boolean} | ||
*/ | ||
doesFileExist(filename: string): boolean; | ||
/** | ||
* Iterates all descriptors pertaining to the currently-loading export. It | ||
* builds into the set of resolved paths the metadata and handlers for each | ||
* handler that is applicable to the export. If filepaths are present in the | ||
* descriptor, the metadata and handlers will be set for those files' absolute | ||
* paths as well. | ||
* @template {Object} T | ||
* @param {Object} options | ||
* @param {string} options.request the string passed to require() | ||
* @param {PackageFinder.Metadata} options.metadata module metadata | ||
* @param {Module} options.parent the module executing require() | ||
* @param {ExportHookDescriptor<T>[]} options.registered list of registered hooks | ||
*/ | ||
buildFromRegistry<T_5 extends Object>({ request, metadata, registered }: { | ||
request: string; | ||
metadata: PackageFinder.Metadata; | ||
parent: Module; | ||
registered: import("./export-hook-descriptor")<T_5>[]; | ||
}): void; | ||
query<T_3 extends Object>(request: string, parent: Module, isMain: boolean): HandlerData<T_3> | undefined; | ||
} | ||
@@ -132,2 +93,3 @@ declare namespace ExportHandlerRegistry { | ||
} | ||
type ExportHookDescriptor<T extends Object> = import('./export-hook-descriptor')<T>; | ||
type HandlerData<T extends Object> = { | ||
@@ -137,5 +99,5 @@ handlers: Handler<T>[]; | ||
}; | ||
type ExportHookDescriptor<T extends Object> = import('./export-hook-descriptor')<T>; | ||
import Module = require("module"); | ||
import PackageFinder = require("./package-finder"); | ||
type Handler<T extends Object> = import('./export-hook-descriptor').Handler<T>; | ||
//# sourceMappingURL=export-handler-registry.d.ts.map |
@@ -6,7 +6,4 @@ // @ts-check | ||
const semver = require('semver'); | ||
const path = require('path'); | ||
const { getShortname } = require('./helpers'); | ||
const fs = require('fs'); | ||
const PackageFinder = require('./package-finder'); | ||
const RELATIVE_RE = /^\.\.?(?:\/|\\)/; | ||
/** | ||
@@ -34,12 +31,31 @@ * @template {Object} T | ||
/** | ||
* contains all modules that have been resolved and are in require cache | ||
* Contains all registered hooks that have been added by `.update()` | ||
* Keyed by `shortname`. | ||
* @type {Record<string, ExportHookDescriptor<any>[]>} | ||
*/ | ||
this.registered = {}; | ||
/** | ||
* Contains all modules that have been resolved and are in require cache | ||
* Keyed by absolute filename. | ||
* @type {Record<string, HandlerData<any>>} | ||
*/ | ||
this.resolved = {}; | ||
/** @type {Record<string, ExportHookDescriptor<any>[]>} */ | ||
this.registered = {}; | ||
/** @type {Record<string, ExportHookDescriptor<any>[]>} */ | ||
this.filenameToRegistered = {}; | ||
} | ||
/** | ||
* Update the registry with the handler info for the given module name. | ||
* This gets called when an agent registers a handler for a given module. | ||
* @template {Object} T | ||
* @param {ExportHookDescriptor<T>} info | ||
* @returns {ExportHookDescriptor<T>[]} | ||
*/ | ||
update(info) { | ||
if (!this.registered[info.shortname]) { | ||
this.registered[info.shortname] = [info]; | ||
} | ||
else { | ||
this.registered[info.shortname].push(info); | ||
} | ||
return this.registered[info.shortname]; | ||
} | ||
/** | ||
* Defined as a class method to allow easy stubbing in tests. | ||
@@ -49,3 +65,3 @@ * @param {string} request the string passed to require() | ||
* @param {boolean} isMain indicates whether the module executing require() is the entry point | ||
* @returns {string} | ||
* @returns {string} fully resolved filename of the required module | ||
*/ | ||
@@ -68,156 +84,40 @@ resolveFilename(request, parent, isMain) { | ||
/** | ||
* Update the registry with the handler info for the given module name. | ||
* This gets called when an agent registers a handler for a given module. | ||
* Iterates all descriptors pertaining to the currently-loading export. It | ||
* builds into the set of resolved paths the metadata and handlers for each | ||
* handler that is applicable to the export. | ||
* @template {Object} T | ||
* @param {string} name | ||
* @param {ExportHookDescriptor<T>} handlerInfo | ||
* @returns {ExportHookDescriptor<T>[]} | ||
* @param {PackageFinder.Metadata} metadata module metadata | ||
* @param {ExportHookDescriptor<T>[]} registered list of registered hooks | ||
*/ | ||
update(name, handlerInfo) { | ||
if (!this.registered[name]) { | ||
this.registered[name] = [handlerInfo]; | ||
} | ||
else { | ||
this.registered[name].push(handlerInfo); | ||
} | ||
return this.registered[name]; | ||
} | ||
/** | ||
* When an export is returned from a `require`, we want to resolve the name | ||
* used as the param, e.g. "http" in `require('http')`, to a set of | ||
* metadata and handlers that have been registered for exports by that name. | ||
* @template {Object} T | ||
* @param {string} request the string passed to require() | ||
* @param {Module} parent the module executing require() | ||
* @param {boolean} isMain indicates whether the module executing require() is the entry point | ||
* @return {HandlerData<T>=} | ||
*/ | ||
query(request, parent, isMain) { | ||
const filename = this.resolveFilename(request, parent, isMain); | ||
const metadata = this.resolveMetadata(filename); | ||
if (!metadata) | ||
return; | ||
let esmImportRequest; | ||
if (!parent) { | ||
esmImportRequest = getShortname({ | ||
filename: request, | ||
location: metadata.packageDir, | ||
name: metadata.name, | ||
main: metadata.main, | ||
}); | ||
} | ||
let registered = this.findRegisteredHandlerData(esmImportRequest || request, filename); | ||
// if it wasn't found. check for a deep reference being made before the | ||
// module itself was required. | ||
// | ||
// the problem here is that if a direct (deep) reference is made to a | ||
// file within a package, e.g. 'validator/lib/isAlpha' before 'validator' | ||
// has been required directly, then `this.filenameToRegistered` has not | ||
// been populated with the resolved filenames from 'validator'. | ||
// | ||
// `this.filenameToRegistered` and `this.resolved` are only populated when | ||
// the top-level installed package is required, i.e., `require('validator')` | ||
// is executed. (populating those objects cannot be done without parent | ||
// and that cannot be known until it is actually required.) | ||
// | ||
// the solution is to check if it is a deep reference starting from an installed | ||
// module name. | ||
if (!registered) { | ||
if (RELATIVE_RE.test(request) || path.isAbsolute(request)) { | ||
return; | ||
} | ||
registered = this.findDeepReference(request, parent, isMain, filename); | ||
if (!registered) { | ||
return; | ||
} | ||
} | ||
this.buildFromRegistry({ | ||
request: esmImportRequest || request, | ||
metadata, | ||
parent, | ||
registered, | ||
}); | ||
return this.findModuleHandlerData({ | ||
request: esmImportRequest || request, | ||
filename, | ||
metadata, | ||
}); | ||
} | ||
/** | ||
* Returns handler data for a deep reference if present. | ||
* @template {Object} T | ||
* @param {string} request the string passed to require() | ||
* @param {Module} parent the module executing require() | ||
* @param {boolean} isMain indicates whether the module executing require() is the entry point | ||
* @param {string} filename fully resolved filename of the required module | ||
* @return {ExportHookDescriptor<T>[]=} handler data for name | ||
*/ | ||
findDeepReference(request, parent, isMain, filename) { | ||
const seps = [path.posix.sep, path.win32.sep]; | ||
for (const sep of seps) { | ||
let ix = request.indexOf(sep); | ||
if (request[0] === '@') { | ||
ix = request.indexOf(sep, ix + 1); | ||
} | ||
// is there maybe a module name to lookup? | ||
if (ix <= 0) { | ||
buildFromRegistry(metadata, registered) { | ||
for (const descriptor of registered) { | ||
if (!semver.satisfies(metadata.version, descriptor.version)) { | ||
this.logger?.trace('skipping handlers since package %s@%s does not satisfy semver range %s', metadata.name, metadata.version, descriptor.version); | ||
continue; | ||
} | ||
const maybeModuleName = request.slice(0, ix); | ||
let registered; | ||
try { | ||
const resolvedMaybe = this.resolveFilename(maybeModuleName, parent, false); | ||
registered = this.findRegisteredHandlerData(maybeModuleName, resolvedMaybe); | ||
const { handlers } = descriptor; | ||
if (!this.resolved[metadata.file]) { | ||
this.resolved[metadata.file] = { metadata, handlers }; | ||
} | ||
catch (err) { | ||
// some modules, like @babel/runtime, cannot be loaded because package.json | ||
// does not point to a main function and no default mains are present. if one | ||
// of these files must be hooked then it must be specified explicitly. | ||
if ( | ||
// @ts-ignore we can't specify the error type easily with JSDoc | ||
err.code === 'MODULE_NOT_FOUND' || | ||
// @ts-ignore we can't specify the error type easily with JSDoc | ||
err.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') { | ||
this.logger?.trace('cannot resolve %s for %s', maybeModuleName, request); | ||
} | ||
else { | ||
this.logger?.warn({ err }); | ||
} | ||
else { | ||
handlers.forEach((handler) => { | ||
if (this.resolved[metadata.file].handlers.indexOf(handler) < 0) { | ||
this.resolved[metadata.file].handlers.push(handler); | ||
} | ||
}); | ||
} | ||
if (!registered) { | ||
continue; | ||
} | ||
// there might be handler data associated with this but it doesn't matter | ||
// because maybe is not being directly required; this.query() is invoked | ||
// for the side effect of populating this.resolved so that the deep reference | ||
// will be found. | ||
this.query(maybeModuleName, parent, isMain); | ||
return this.findRegisteredHandlerData(request, filename); | ||
} | ||
} | ||
/** | ||
* Returns an array of handlers that have been registered for a given module. | ||
* @template {Object} T | ||
* @param {string} request the string passed to require() | ||
* @param {string} filename fully resolved filename of the required module | ||
* @return {ExportHookDescriptor<T>[]=} | ||
*/ | ||
findRegisteredHandlerData(request, filename) { | ||
return this.registered[request] || this.filenameToRegistered[filename]; | ||
} | ||
/** | ||
* Returns registered handlers and the module metadata for a module that has | ||
* registration for the version that is being required. | ||
* @template {Object} T | ||
* @param {Object} params | ||
* @param {string} params.request the string passed to require() | ||
* @param {string} params.filename fully resolved filename of the required module | ||
* @param {PackageFinder.Metadata} params.metadata module metadata | ||
* @param {PackageFinder.Metadata} metadata module metadata | ||
* @return {HandlerData<T>=} | ||
*/ | ||
findModuleHandlerData({ request, filename, metadata }) { | ||
const data = this.resolved[request] || this.resolved[filename]; | ||
findModuleHandlerData(metadata) { | ||
const data = this.resolved[metadata.file]; | ||
// Need to check the version of the instrumentation handler to the one getting required | ||
// to decide if we need they are version applicable | ||
if (data && metadata.version === (data.metadata && data.metadata.version)) { | ||
if (metadata.version === data?.metadata.version) { | ||
return data; | ||
@@ -227,91 +127,24 @@ } | ||
/** | ||
* Returns true if the file exists and false if it doesn't. | ||
* @param {string} filename | ||
* @return {boolean} | ||
*/ | ||
doesFileExist(filename) { | ||
return fs.existsSync(filename); | ||
} | ||
/** | ||
* Iterates all descriptors pertaining to the currently-loading export. It | ||
* builds into the set of resolved paths the metadata and handlers for each | ||
* handler that is applicable to the export. If filepaths are present in the | ||
* descriptor, the metadata and handlers will be set for those files' absolute | ||
* paths as well. | ||
* When an export is returned from a `require`, we want to resolve the name | ||
* used as the param, e.g. "http" in `require('http')`, to a set of | ||
* metadata and handlers that have been registered for exports by that name. | ||
* @template {Object} T | ||
* @param {Object} options | ||
* @param {string} options.request the string passed to require() | ||
* @param {PackageFinder.Metadata} options.metadata module metadata | ||
* @param {Module} options.parent the module executing require() | ||
* @param {ExportHookDescriptor<T>[]} options.registered list of registered hooks | ||
* @param {string} request the string passed to require() | ||
* @param {Module} parent the module executing require() | ||
* @param {boolean} isMain indicates whether the module executing require() is the entry point | ||
* @return {HandlerData<T>=} | ||
*/ | ||
// eslint-disable-next-line complexity | ||
buildFromRegistry({ request, metadata, registered }) { | ||
/** @type {Record<string, HandlerData<T>>} */ | ||
const fileHandlers = {}; | ||
// when `registered` was derived from an absolute file reference then | ||
// `registered === filenameToBeRegistered[absoluteFile]`. | ||
const n = registered.length; | ||
for (let i = 0; i < n; i++) { | ||
const descriptor = registered[i]; | ||
if (!semver.satisfies(metadata.version, descriptor.version)) { | ||
this.logger?.trace('skipping handlers since package %s@%s does not satisfy semver range %s', request, metadata.version, descriptor.version); | ||
continue; | ||
} | ||
const { handlers } = descriptor; | ||
if (descriptor.file) { | ||
const absoluteFile = path.resolve(metadata.packageDir || '', descriptor.file.endsWith('.js') | ||
? descriptor.file | ||
: `${descriptor.file}.js`); | ||
if (!this.doesFileExist(absoluteFile)) { | ||
this.logger?.trace('unable to resolve file %s', absoluteFile); | ||
continue; | ||
} | ||
try { | ||
addNonDuplicatedHandlers(fileHandlers, absoluteFile, { | ||
metadata, | ||
handlers, | ||
}); | ||
if (!this.filenameToRegistered[absoluteFile]) { | ||
this.filenameToRegistered[absoluteFile] = []; | ||
} | ||
if (this.filenameToRegistered[absoluteFile].indexOf(descriptor) < 0) { | ||
this.filenameToRegistered[absoluteFile].push(descriptor); | ||
} | ||
} | ||
catch (err) { | ||
this.logger?.warn({ err }); | ||
} | ||
continue; | ||
} | ||
addNonDuplicatedHandlers(fileHandlers, request, { metadata, handlers }); | ||
} | ||
for (const file in fileHandlers) { | ||
this.resolved[file] = fileHandlers[file]; | ||
} | ||
query(request, parent, isMain) { | ||
const filename = this.resolveFilename(request, parent, isMain); | ||
const metadata = this.resolveMetadata(filename); | ||
if (!metadata) | ||
return; | ||
const registered = this.registered[getShortname(metadata)]; | ||
if (!registered) | ||
return; | ||
this.buildFromRegistry(metadata, registered); | ||
return this.findModuleHandlerData(metadata); | ||
} | ||
} | ||
/** | ||
* Sets a property on the target using the provided path parameter that is a | ||
* collection containing the provided data. If the property already exists, then | ||
* the data will be added to the collection. | ||
* @template {Object} T | ||
* @param {Record<string, HandlerData<T>>} target The object on which to add the property | ||
* @param {string} path The string used to name the property path | ||
* @param {HandlerData<T>} data Data to add to the collection at the path | ||
*/ | ||
const addNonDuplicatedHandlers = (target, path, data) => { | ||
const { handlers } = data; | ||
if (!target[path]) { | ||
target[path] = data; | ||
} | ||
else { | ||
handlers.forEach((handler) => { | ||
if (target[path].handlers.indexOf(handler) < 0) { | ||
target[path].handlers.push(handler); | ||
} | ||
}); | ||
} | ||
}; | ||
module.exports = ExportHandlerRegistry; | ||
//# sourceMappingURL=export-handler-registry.js.map |
@@ -41,3 +41,3 @@ export = ExportHookDescriptor; | ||
*/ | ||
static create<T_1 extends Object>(descriptor: string | HookDescriptorOptions<T_1>): import("./export-hook-descriptor")<T_1>; | ||
static create<T_1 extends Object>(descriptor: string | HookDescriptorOptions<T_1>): ExportHookDescriptor<T_1>; | ||
/** | ||
@@ -52,2 +52,4 @@ * @param {HookDescriptorOptions<T>} options | ||
/** @type {string} */ | ||
shortname: string; | ||
/** @type {string} */ | ||
version: string; | ||
@@ -69,1 +71,2 @@ /** @type {Handler<T>[]} */ | ||
type Metadata = import('./package-finder').Metadata; | ||
//# sourceMappingURL=export-hook-descriptor.d.ts.map |
@@ -5,2 +5,3 @@ // @ts-check | ||
const Semver = require('semver'); | ||
const path = require('path'); | ||
const DEFAULT_VERSION = '*'; | ||
@@ -32,3 +33,3 @@ /** @typedef {import('./package-finder').Metadata} Metadata */ | ||
*/ | ||
// @ts-ignore this could use a refactor | ||
// @ts-expect-error typescript doesn't play well with the validation we do. | ||
constructor({ name, file, version = DEFAULT_VERSION, handlers = [], } = {}) { | ||
@@ -39,4 +40,6 @@ ExportHookDescriptor.validate({ name, version }); | ||
/** @type {string=} */ | ||
this.file = file; | ||
this.file = file?.replace(/\/?(index)?(\.js)?$/, ''); | ||
/** @type {string} */ | ||
this.shortname = this.file ? path.posix.join(this.name, this.file) : this.name; | ||
/** @type {string} */ | ||
this.version = version; | ||
@@ -43,0 +46,0 @@ /** @type {Handler<T>[]} */ |
@@ -46,1 +46,2 @@ export = HandlerInvoker; | ||
type Metadata = import('./package-finder').Metadata; | ||
//# sourceMappingURL=handler-invoker.d.ts.map |
@@ -61,3 +61,3 @@ // @ts-check | ||
this.logger?.trace({ metadata }, 'invoking handler: %s', metadata.name); | ||
return handler(acc, metadata) || acc; | ||
return handler(acc, metadata) ?? acc; | ||
} | ||
@@ -70,3 +70,3 @@ catch (err) { | ||
if (typeof rv == 'function' && rv != xport) { | ||
// @ts-ignore we've validated this.seen.get is valid in `#filter`. | ||
// @ts-expect-error we've validated this.seen.get is valid in `#filter`. | ||
this.seen.set(rv, this.seen.get(xport)); | ||
@@ -73,0 +73,0 @@ } |
/** | ||
* @param {Object} opts | ||
* @param {string} opts.filename | ||
* @param {string} opts.location | ||
* @param {string=} opts.main | ||
* @param {string} opts.name | ||
* Shortens a fully resolved filename to a short path that would be respected by | ||
* `require`. | ||
* @example | ||
* '/long/absolute/path/to/node_modules/package/lib/file.js' => 'package/lib/file' | ||
* | ||
* @param {import('./package-finder').Metadata} opts | ||
* @returns {string} | ||
*/ | ||
export function getShortname({ filename, location, main, name }: { | ||
filename: string; | ||
location: string; | ||
main?: string | undefined; | ||
name: string; | ||
}): string; | ||
export function getShortname({ name, packageDir, file, main }: import('./package-finder').Metadata): string; | ||
//# sourceMappingURL=helpers.d.ts.map |
@@ -7,19 +7,20 @@ // @ts-check | ||
/** | ||
* @param {Object} opts | ||
* @param {string} opts.filename | ||
* @param {string} opts.location | ||
* @param {string=} opts.main | ||
* @param {string} opts.name | ||
* Shortens a fully resolved filename to a short path that would be respected by | ||
* `require`. | ||
* @example | ||
* '/long/absolute/path/to/node_modules/package/lib/file.js' => 'package/lib/file' | ||
* | ||
* @param {import('./package-finder').Metadata} opts | ||
* @returns {string} | ||
*/ | ||
function getShortname({ filename, location, main, name }) { | ||
function getShortname({ name, packageDir, file, main }) { | ||
const mainPath = path | ||
.join(location, main || '') | ||
.join(packageDir, main ?? '') | ||
.replace(pathSepRegex, '\\$&'); | ||
const mainPathRegex = new RegExp(`^${mainPath}\\${path.sep}?(index)?(\\.js(on)?)?$`); | ||
const normalizedFilename = path.normalize(filename); | ||
const normalizedFilename = path.normalize(file); | ||
return mainPathRegex.test(normalizedFilename) | ||
? name | ||
: normalizedFilename | ||
.replace(path.normalize(location), name) | ||
.replace(path.normalize(packageDir), name) | ||
.replace(pathSepRegex, '/') | ||
@@ -26,0 +27,0 @@ .replace(/\/?(index)?\.js$/, ''); |
export = RequireHook; | ||
/** | ||
* @template {Object} T | ||
* @typedef {import('./export-hook-descriptor').Handler<T>} Handler | ||
*/ | ||
/** | ||
* @typedef {Object} Descriptor | ||
* @property {string=} file if provided, the file under the module's root that we want to hook. otherwise, the module's `main` will be hooked. | ||
* @property {string} name module name, as passed to `require`, to handle. | ||
* @property {string=} version if provided, hooks will only execute against an installed module that matches the SemVer version range | ||
* @property {string=} module alternative to `name`, taking precedent when provided. | ||
*/ | ||
/** | ||
* Allows clients to register function handlers which run as a 'post-hook' at | ||
@@ -19,8 +8,2 @@ * require-time. | ||
/** | ||
* Coerces a string into a minimal object that can be made into a descriptor. | ||
* @param {Descriptor | string} descriptor The export descriptor | ||
* @returns {Descriptor} | ||
*/ | ||
static normalizeDescriptor(descriptor: Descriptor | string): Descriptor; | ||
/** | ||
* @param {import('pino').Logger=} logger | ||
@@ -44,5 +27,5 @@ */ | ||
* @param {Descriptor | string} descriptor describes the module to hook | ||
* @param {Handler<T>[]} handlers the function hooks to execute after require | ||
* @param {ExportHookDescriptor.Handler<T>[]} handlers the function hooks to execute after require | ||
*/ | ||
resolve<T extends Object>(descriptor: Descriptor | string, ...handlers: Handler<T>[]): void; | ||
resolve<T extends Object>(descriptor: Descriptor | string, ...handlers: ExportHookDescriptor.Handler<T>[]): void; | ||
/** | ||
@@ -53,13 +36,8 @@ * Provided with an export, a collection of handlers, and metadata, will | ||
* @param {T} xport the exported value of a required module | ||
* @param {Handler<T>[]} handlers the function hooks to execute on require | ||
* @param {import('./package-finder').Metadata} metadata the export's metadata | ||
* @param {ExportHookDescriptor.Handler<T>[]} handlers the function hooks to execute on require | ||
* @param {PackageFinder.Metadata} metadata the export's metadata | ||
* @returns {T} | ||
*/ | ||
runRequireHandlers<T_1 extends Object>(xport: T_1, handlers: Handler<T_1>[], metadata: import('./package-finder').Metadata): T_1; | ||
runRequireHandlers<T_1 extends Object>(xport: T_1, handlers: ExportHookDescriptor.Handler<T_1>[], metadata: PackageFinder.Metadata): T_1; | ||
/** | ||
* Overrides the Module._load method to run registered handlers _after_ | ||
* the modules have loaded. This method is invoked by require. | ||
*/ | ||
install(): void; | ||
/** | ||
* Checks if module name exists in resets set. If so, it will remove it from | ||
@@ -75,2 +53,7 @@ * the set as well as remove it from the invoker WeakMap. This will force | ||
/** | ||
* Overrides the Module._load method to run registered handlers _after_ | ||
* the modules have loaded. This method is invoked by require. | ||
*/ | ||
install(): void; | ||
/** | ||
* Resets Module's _load method to the original value. | ||
@@ -87,3 +70,3 @@ */ | ||
declare namespace RequireHook { | ||
export { ExportHandlerRegistry, ExportHookDescriptor, HandlerInvoker, PackageFinder, RequireHook, Handler, Descriptor }; | ||
export { ExportHandlerRegistry, ExportHookDescriptor, HandlerInvoker, PackageFinder, RequireHook, Descriptor }; | ||
} | ||
@@ -94,2 +77,10 @@ import HandlerInvoker = require("./handler-invoker"); | ||
/** | ||
* module name, as passed to `require`, to handle. | ||
*/ | ||
name: string; | ||
/** | ||
* alternative to `name`, taking precedent when provided. | ||
*/ | ||
module?: string | undefined; | ||
/** | ||
* if provided, the file under the module's root that we want to hook. otherwise, the module's `main` will be hooked. | ||
@@ -99,16 +90,8 @@ */ | ||
/** | ||
* module name, as passed to `require`, to handle. | ||
*/ | ||
name: string; | ||
/** | ||
* if provided, hooks will only execute against an installed module that matches the SemVer version range | ||
*/ | ||
version?: string | undefined; | ||
/** | ||
* alternative to `name`, taking precedent when provided. | ||
*/ | ||
module?: string | undefined; | ||
}; | ||
type Handler<T extends Object> = import('./export-hook-descriptor').Handler<T>; | ||
import ExportHookDescriptor = require("./export-hook-descriptor"); | ||
import PackageFinder = require("./package-finder"); | ||
//# sourceMappingURL=index.d.ts.map |
@@ -10,13 +10,23 @@ // @ts-check | ||
/** | ||
* @template {Object} T | ||
* @typedef {import('./export-hook-descriptor').Handler<T>} Handler | ||
*/ | ||
/** | ||
* @typedef {Object} Descriptor | ||
* @property {string} name module name, as passed to `require`, to handle. | ||
* @property {string=} module alternative to `name`, taking precedent when provided. | ||
* @property {string=} file if provided, the file under the module's root that we want to hook. otherwise, the module's `main` will be hooked. | ||
* @property {string} name module name, as passed to `require`, to handle. | ||
* @property {string=} version if provided, hooks will only execute against an installed module that matches the SemVer version range | ||
* @property {string=} module alternative to `name`, taking precedent when provided. | ||
*/ | ||
/** | ||
* Coerces a string into a minimal object that can be made into a descriptor. | ||
* @param {Descriptor | string} descriptor The export descriptor | ||
* @returns {Descriptor} | ||
*/ | ||
const normalizeDescriptor = (descriptor) => { | ||
if (typeof descriptor === 'string') { | ||
return { name: descriptor }; | ||
} | ||
if (descriptor.module) { | ||
descriptor.name = descriptor.module; | ||
} | ||
return descriptor; | ||
}; | ||
/** | ||
* Allows clients to register function handlers which run as a 'post-hook' at | ||
@@ -47,8 +57,8 @@ * require-time. | ||
* @param {Descriptor | string} descriptor describes the module to hook | ||
* @param {Handler<T>[]} handlers the function hooks to execute after require | ||
* @param {ExportHookDescriptor.Handler<T>[]} handlers the function hooks to execute after require | ||
*/ | ||
resolve(descriptor, ...handlers) { | ||
const { file, name, version } = RequireHook.normalizeDescriptor(descriptor); | ||
const info = ExportHookDescriptor.create({ file, handlers, name, version }); | ||
this.registry.update(name, info); | ||
const normalized = normalizeDescriptor(descriptor); | ||
const info = ExportHookDescriptor.create({ ...normalized, handlers }); | ||
this.registry.update(info); | ||
} | ||
@@ -60,4 +70,4 @@ /** | ||
* @param {T} xport the exported value of a required module | ||
* @param {Handler<T>[]} handlers the function hooks to execute on require | ||
* @param {import('./package-finder').Metadata} metadata the export's metadata | ||
* @param {ExportHookDescriptor.Handler<T>[]} handlers the function hooks to execute on require | ||
* @param {PackageFinder.Metadata} metadata the export's metadata | ||
* @returns {T} | ||
@@ -69,2 +79,17 @@ */ | ||
/** | ||
* Checks if module name exists in resets set. If so, it will remove it from | ||
* the set as well as remove it from the invoker WeakMap. This will force | ||
* instrumentation handlers to re-run. This use case is only used for testing | ||
* of the node agent in certain cases. | ||
* @template {Object} T | ||
* @param {string} request the string passed to require() | ||
* @param {T} xport the exported value of a required module | ||
*/ | ||
maybeClearHandlers(request, xport) { | ||
if (this.resets.has(request)) { | ||
this.resets.delete(request); | ||
this.invoker.reset(xport); | ||
} | ||
} | ||
/** | ||
* Overrides the Module._load method to run registered handlers _after_ | ||
@@ -92,6 +117,6 @@ * the modules have loaded. This method is invoked by require. | ||
self.maybeClearHandlers(request, xport); | ||
xportSubstitution = self.runRequireHandlers(self.requiredModules.get(xport) || xport, exportHandlerInfo.handlers, exportHandlerInfo.metadata); | ||
xportSubstitution = self.runRequireHandlers(self.requiredModules.get(xport) ?? xport, exportHandlerInfo.handlers, exportHandlerInfo.metadata); | ||
self.requiredModules.set(xport, xportSubstitution); | ||
} | ||
return self.requiredModules.get(xport) || xport; | ||
return self.requiredModules.get(xport) ?? xport; | ||
}; | ||
@@ -101,17 +126,2 @@ Reflect.set(Module, '_load', __loadOverride); | ||
/** | ||
* Checks if module name exists in resets set. If so, it will remove it from | ||
* the set as well as remove it from the invoker WeakMap. This will force | ||
* instrumentation handlers to re-run. This use case is only used for testing | ||
* of the node agent in certain cases. | ||
* @template {Object} T | ||
* @param {string} request the string passed to require() | ||
* @param {T} xport the exported value of a required module | ||
*/ | ||
maybeClearHandlers(request, xport) { | ||
if (this.resets.has(request)) { | ||
this.resets.delete(request); | ||
this.invoker.reset(xport); | ||
} | ||
} | ||
/** | ||
* Resets Module's _load method to the original value. | ||
@@ -131,16 +141,2 @@ */ | ||
} | ||
/** | ||
* Coerces a string into a minimal object that can be made into a descriptor. | ||
* @param {Descriptor | string} descriptor The export descriptor | ||
* @returns {Descriptor} | ||
*/ | ||
static normalizeDescriptor(descriptor) { | ||
if (typeof descriptor === 'string') { | ||
return { name: descriptor }; | ||
} | ||
if (descriptor.module) { | ||
descriptor.name = descriptor.module; | ||
} | ||
return descriptor; | ||
} | ||
} | ||
@@ -147,0 +143,0 @@ module.exports = RequireHook; |
@@ -15,6 +15,9 @@ export type Metadata = { | ||
/** | ||
* the absolute filename of the module file being required | ||
*/ | ||
file: string; | ||
/** | ||
* `main` field from the package being required's package.json | ||
*/ | ||
main?: string | undefined; | ||
file: string; | ||
}; | ||
@@ -26,4 +29,4 @@ /** | ||
* @property {string} packageDir the location of the module being required | ||
* @property {string} file the absolute filename of the module file being required | ||
* @property {string=} main `main` field from the package being required's package.json | ||
* @property {string} file | ||
*/ | ||
@@ -42,1 +45,2 @@ /** | ||
export function resolveMetadata(filename?: string | undefined, logger?: import('pino').Logger | undefined): Metadata | null; | ||
//# sourceMappingURL=package-finder.d.ts.map |
@@ -12,4 +12,4 @@ // @ts-check | ||
* @property {string} packageDir the location of the module being required | ||
* @property {string} file the absolute filename of the module file being required | ||
* @property {string=} main `main` field from the package being required's package.json | ||
* @property {string} file | ||
*/ | ||
@@ -52,4 +52,4 @@ /** | ||
packageDir: path.dirname(manifest.path), | ||
file: filename, | ||
main, | ||
file: filename, | ||
}; | ||
@@ -56,0 +56,0 @@ } |
{ | ||
"name": "@contrast/require-hook", | ||
"version": "4.0.0", | ||
"version": "4.1.0", | ||
"description": "Post hooks for Module.prototype.require", | ||
@@ -16,3 +16,3 @@ "license": "MIT", | ||
"build": "tsc --build src/", | ||
"pretest": "./scripts/prepare.sh", | ||
"pretest": "npm run build", | ||
"test": "nyc mocha .", | ||
@@ -25,3 +25,3 @@ "test:debug": "npm run test -- --inspect-brk", | ||
"postpublish": "git push && git push --tags", | ||
"prepare": "husky install && ./scripts/prepare.sh" | ||
"prepare": "husky install && npm run build" | ||
}, | ||
@@ -36,2 +36,4 @@ "dependencies": { | ||
"@tsconfig/node14": "^14.1.0", | ||
"@types/chai": "^4.3.6", | ||
"@types/express": "^4.17.17", | ||
"@types/mocha": "^10.0.1", | ||
@@ -41,3 +43,8 @@ "@types/node": "^14.18.58", | ||
"@types/semver": "^7.5.1", | ||
"@types/sinon": "^10.0.16", | ||
"@types/sinon-chai": "^3.2.9", | ||
"chai": "^4.3.8", | ||
"express": "*", | ||
"express-4.17.0": "npm:express@4.17.0", | ||
"express-4.18.2": "npm:express@4.18.2", | ||
"husky": "^8.0.3", | ||
@@ -44,0 +51,0 @@ "lint-staged": "^14.0.1", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
26
4
52627
23
887