load-plugin
Advanced tools
Comparing version 5.1.0 to 6.0.0
@@ -1,40 +0,4 @@ | ||
/** | ||
* Load the plugin found using `resolvePlugin`. | ||
* | ||
* @param {string} name The name to import. | ||
* @param {LoadOptions} [options] | ||
* @returns {Promise<unknown>} | ||
*/ | ||
export function loadPlugin( | ||
name: string, | ||
options?: LoadOptions | undefined | ||
): Promise<unknown> | ||
/** | ||
* Find a plugin. | ||
* | ||
* See also: | ||
* * https://docs.npmjs.com/files/folders#node-modules | ||
* * https://github.com/sindresorhus/resolve-from | ||
* | ||
* Uses the standard node module loading strategy to find `$name` in each given | ||
* `cwd` (and optionally the global `node_modules` directory). | ||
* | ||
* If a prefix is given and `$name` is not a path, `$prefix-$name` is also | ||
* searched (preferring these over non-prefixed modules). | ||
* | ||
* @param {string} name | ||
* @param {ResolveOptions} [options] | ||
* @returns {Promise<string>} | ||
*/ | ||
export function resolvePlugin( | ||
name: string, | ||
options?: ResolveOptions | undefined | ||
): Promise<string> | ||
export type ResolveOptions = { | ||
prefix?: string | undefined | ||
cwd?: string | string[] | undefined | ||
global?: boolean | undefined | ||
} | ||
export type LoadOptions = ResolveOptions & { | ||
key?: string | false | ||
} | ||
export type LoadOptions = import('./lib/index.js').LoadOptions; | ||
export type ResolveOptions = import('./lib/index.js').ResolveOptions; | ||
export { loadPlugin, resolvePlugin } from "./lib/index.js"; | ||
//# sourceMappingURL=index.d.ts.map |
175
index.js
/** | ||
* @typedef ResolveOptions | ||
* @property {string} [prefix] | ||
* @property {string|Array<string>} [cwd] | ||
* @property {boolean} [global] | ||
* | ||
* @typedef {ResolveOptions & {key?: string|false}} LoadOptions | ||
* @typedef {import('./lib/index.js').LoadOptions} LoadOptions | ||
* @typedef {import('./lib/index.js').ResolveOptions} ResolveOptions | ||
*/ | ||
import fs from 'fs' | ||
import process from 'process' | ||
import {pathToFileURL, fileURLToPath} from 'url' | ||
import path from 'path' | ||
// @ts-expect-error: untyped | ||
import NpmConfig from '@npmcli/config' | ||
import {resolve as esmResolve} from 'import-meta-resolve' | ||
const electron = process.versions.electron !== undefined | ||
const windows = process.platform === 'win32' | ||
const argv = process.argv[1] || /* c8 ignore next */ '' | ||
const nvm = process.env.NVM_BIN | ||
/* c8 ignore next */ | ||
const globalsLibrary = windows ? '' : 'lib' | ||
const config = new NpmConfig({definitions: {}}) | ||
config.loadGlobalPrefix() | ||
/** @type {string} */ | ||
let npmPrefix = config.globalPrefix | ||
// If there is no prefix defined, use the defaults | ||
// See: <https://github.com/eush77/npm-prefix/blob/master/index.js> | ||
/* c8 ignore next 5 */ | ||
if (!npmPrefix) { | ||
npmPrefix = windows | ||
? path.dirname(process.execPath) | ||
: path.resolve(process.execPath, '../..') | ||
} | ||
const globalsDefault = electron || argv.indexOf(npmPrefix) === 0 | ||
let globalDir = path.resolve(npmPrefix, globalsLibrary, 'node_modules') | ||
// If we’re in Electron, we’re running in a modified Node that cannot really | ||
// install global node modules. | ||
// To find the actual modules, the user has to set `prefix` somewhere in an | ||
// `.npmrc` (which is picked up by `libnpmconfig`). | ||
// Most people don’t do that, and some use NVM instead to manage different | ||
// versions of Node. | ||
// Luckily NVM leaks some environment variables that we can pick up on to try | ||
// and detect the actual modules. | ||
/* c8 ignore next 3 */ | ||
if (electron && nvm && !fs.existsSync(globalDir)) { | ||
globalDir = path.resolve(nvm, '..', globalsLibrary, 'node_modules') | ||
} | ||
/** | ||
* Load the plugin found using `resolvePlugin`. | ||
* | ||
* @param {string} name The name to import. | ||
* @param {LoadOptions} [options] | ||
* @returns {Promise<unknown>} | ||
*/ | ||
export async function loadPlugin(name, options = {}) { | ||
const {key = 'default', ...rest} = options | ||
const fp = await resolvePlugin(name, rest) | ||
/** @type {Record<string, unknown>} */ | ||
// Bug with coverage on Node@12. | ||
/* c8 ignore next 3 */ | ||
const mod = await import(pathToFileURL(fp).href) | ||
return key === false ? mod : mod[key] | ||
} | ||
/** | ||
* Find a plugin. | ||
* | ||
* See also: | ||
* * https://docs.npmjs.com/files/folders#node-modules | ||
* * https://github.com/sindresorhus/resolve-from | ||
* | ||
* Uses the standard node module loading strategy to find `$name` in each given | ||
* `cwd` (and optionally the global `node_modules` directory). | ||
* | ||
* If a prefix is given and `$name` is not a path, `$prefix-$name` is also | ||
* searched (preferring these over non-prefixed modules). | ||
* | ||
* @param {string} name | ||
* @param {ResolveOptions} [options] | ||
* @returns {Promise<string>} | ||
*/ | ||
export async function resolvePlugin(name, options = {}) { | ||
const prefix = options.prefix | ||
? options.prefix + | ||
(options.prefix.charAt(options.prefix.length - 1) === '-' ? '' : '-') | ||
: undefined | ||
const cwd = options.cwd | ||
const globals = | ||
options.global === undefined || options.global === null | ||
? globalsDefault | ||
: options.global | ||
const sources = Array.isArray(cwd) ? cwd.concat() : [cwd || process.cwd()] | ||
/** @type {string|undefined} */ | ||
let plugin | ||
/** @type {Error|undefined} */ | ||
let lastError | ||
// Non-path. | ||
if (name.charAt(0) !== '.') { | ||
if (globals) { | ||
sources.push(globalDir) | ||
} | ||
let scope = '' | ||
// Unprefix module. | ||
if (prefix) { | ||
// Scope? | ||
if (name.charAt(0) === '@') { | ||
const slash = name.indexOf('/') | ||
// Let’s keep the algorithm simple. | ||
// No need to care if this is a “valid” scope (I think?). | ||
// But we do check for the slash. | ||
if (slash !== -1) { | ||
scope = name.slice(0, slash + 1) | ||
name = name.slice(slash + 1) | ||
} | ||
} | ||
if (name.slice(0, prefix.length) !== prefix) { | ||
plugin = scope + prefix + name | ||
} | ||
name = scope + name | ||
} | ||
} | ||
let index = -1 | ||
/** @type {string|undefined} */ | ||
let fp | ||
while (++index < sources.length) { | ||
fp = plugin ? await attempt(sources[index], plugin) : undefined | ||
if (fp) return fp | ||
fp = await attempt(sources[index], name) | ||
if (fp) return fp | ||
} | ||
// There’s always an error. | ||
// Bug with coverage on Node@12. | ||
/* c8 ignore next 8 */ | ||
throw lastError | ||
/** | ||
* @param {string} base | ||
* @param {string} name | ||
* @returns {Promise<string|undefined>} | ||
*/ | ||
async function attempt(base, name) { | ||
try { | ||
// `import-meta-resolve` resolves from files, whereas `load-plugin` works | ||
// on folders, which is why we add a `/` at the end. | ||
return fileURLToPath( | ||
await esmResolve(name, pathToFileURL(base).href + '/') | ||
) | ||
// Bug with coverage on Node@12. | ||
/* c8 ignore next 1 */ | ||
} catch (error) { | ||
lastError = /** @type {Error} */ (error) | ||
} | ||
} | ||
} | ||
export {loadPlugin, resolvePlugin} from './lib/index.js' |
{ | ||
"name": "load-plugin", | ||
"version": "5.1.0", | ||
"version": "6.0.0", | ||
"description": "Load a submodule, plugin, or file", | ||
@@ -8,5 +8,5 @@ "license": "MIT", | ||
"load", | ||
"submodule", | ||
"package", | ||
"plugin" | ||
"plugin", | ||
"submodule" | ||
], | ||
@@ -25,16 +25,17 @@ "repository": "wooorm/load-plugin", | ||
"type": "module", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"exports": "./index.js", | ||
"files": [ | ||
"lib/", | ||
"index.d.ts", | ||
"index.js" | ||
"index.js", | ||
"index.map" | ||
], | ||
"dependencies": { | ||
"@npmcli/config": "^6.0.0", | ||
"import-meta-resolve": "^2.0.0" | ||
"@npmcli/config": "^8.0.0", | ||
"import-meta-resolve": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^18.0.0", | ||
"c8": "^7.0.0", | ||
"prettier": "^2.0.0", | ||
"@types/node": "^20.0.0", | ||
"c8": "^8.0.0", | ||
"prettier": "^3.0.0", | ||
"remark-cli": "^11.0.0", | ||
@@ -44,31 +45,24 @@ "remark-lint": "^9.0.0", | ||
"type-coverage": "^2.0.0", | ||
"typescript": "^4.0.0", | ||
"xo": "^0.53.0" | ||
"typescript": "^5.0.0", | ||
"xo": "^0.56.0" | ||
}, | ||
"scripts": { | ||
"build": "tsc --build --clean && tsc --build && type-coverage", | ||
"format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix", | ||
"prepack": "npm run build && npm run format", | ||
"build": "tsc --build --clean && tsc --build && type-coverage", | ||
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", | ||
"test": "npm run build && npm run format && npm run test-coverage", | ||
"test-api": "node --conditions development test/index.js", | ||
"test-coverage": "c8 --check-coverage --100 --reporter lcov npm run test-api", | ||
"test": "npm run build && npm run format && npm run test-coverage" | ||
"test-coverage": "c8 --100 --reporter lcov npm run test-api" | ||
}, | ||
"prettier": { | ||
"tabWidth": 2, | ||
"useTabs": false, | ||
"bracketSpacing": false, | ||
"singleQuote": true, | ||
"bracketSpacing": false, | ||
"semi": false, | ||
"trailingComma": "none" | ||
"tabWidth": 2, | ||
"trailingComma": "none", | ||
"useTabs": false | ||
}, | ||
"xo": { | ||
"prettier": true, | ||
"rules": { | ||
"unicorn/prefer-node-protocol": "off", | ||
"no-await-in-loop": "off" | ||
} | ||
}, | ||
"remarkConfig": { | ||
"plugins": [ | ||
"preset-wooorm" | ||
"remark-preset-wooorm" | ||
] | ||
@@ -79,5 +73,8 @@ }, | ||
"detail": true, | ||
"strict": true, | ||
"ignoreCatch": true | ||
"ignoreCatch": true, | ||
"strict": true | ||
}, | ||
"xo": { | ||
"prettier": true | ||
} | ||
} |
201
readme.md
# load-plugin | ||
[![Build][build-badge]][build] | ||
[![Coverage][coverage-badge]][coverage] | ||
[![Downloads][downloads-badge]][downloads] | ||
[![Build][badge-build-image]][badge-build-url] | ||
[![Coverage][badge-coverage-image]][badge-coverage-url] | ||
[![Downloads][badge-downloads-image]][badge-downloads-url] | ||
Load submodules, plugins, or files. | ||
Load a submodule, plugin, or file. | ||
@@ -18,4 +18,6 @@ ## Contents | ||
* [`resolvePlugin(name[, options])`](#resolvepluginname-options) | ||
* [Types](#types) | ||
* [`LoadOptions`](#loadoptions) | ||
* [`ResolveOptions`](#resolveoptions) | ||
* [Compatibility](#compatibility) | ||
* [Security](#security) | ||
* [Contribute](#contribute) | ||
@@ -27,5 +29,7 @@ * [License](#license) | ||
This package is useful when you want to load plugins. | ||
It resolves things like Node.js does, but supports a prefix (e.g., when given a | ||
prefix `remark` and the user provided value `gfm`, it can find `remark-gfm`), | ||
can load from several places, and optionally global too. | ||
It resolves things like Node.js does, | ||
but supports a prefix (when given a prefix `remark` and the user provided value | ||
`gfm` it can find `remark-gfm`), | ||
can load from several places, | ||
and optionally global too. | ||
@@ -41,4 +45,5 @@ ## When to use this? | ||
This package is [ESM only][esm]. | ||
In Node.js (version 14.14+, 16.0+), install with [npm][]: | ||
This package is [ESM only][github-gist-esm]. | ||
In Node.js (version 16+), | ||
install with [npm][npm-install]: | ||
@@ -57,9 +62,11 @@ ```sh | ||
console.log(await resolvePlugin('lint', {prefix: 'remark'})) | ||
// => '/Users/tilde/projects/oss/load-plugin/node_modules/remark-lint/index.js' | ||
// => 'file:///Users/tilde/Projects/oss/load-plugin/node_modules/remark-lint/index.js' | ||
console.log(await resolvePlugin('validator-identifier', {prefix: '@babel/helper'})) | ||
// => '/Users/tilde/Projects/oss/load-plugin/node_modules/@babel/helper-validator-identifier/lib/index.js' | ||
console.log( | ||
await resolvePlugin('validator-identifier', {prefix: '@babel/helper'}) | ||
) | ||
// => 'file:///Users/tilde/Projects/oss/load-plugin/node_modules/@babel/helper-validator-identifier/lib/index.js' | ||
console.log(await resolvePlugin('./index.js', {prefix: 'remark'})) | ||
// => '/Users/tilde/projects/oss/load-plugin/index.js' | ||
// => 'file:///Users/tilde/Projects/oss/load-plugin/index.js' | ||
@@ -72,122 +79,152 @@ console.log(await loadPlugin('lint', {prefix: 'remark'})) | ||
This package exports the identifiers `loadPlugin` and `resolvePlugin`. | ||
This package exports the identifiers | ||
[`loadPlugin`][api-load-plugin] and [`resolvePlugin`][api-resolve-plugin]. | ||
There is no default export. | ||
It exports the [TypeScript][] types | ||
[`LoadOptions`][api-load-options] and [`ResolveOptions`][api-resolve-options]. | ||
### `loadPlugin(name[, options])` | ||
Uses Node’s [resolution algorithm][algo] (through | ||
[`import-meta-resolve`][import-meta-resolve]) to load CJS and ESM packages and | ||
files to import `name` in each given `cwd` (and optionally the global | ||
`node_modules` directory). | ||
Import `name` from `from` (and optionally the global `node_modules` directory). | ||
If a `prefix` is given and `name` is not a path, `$prefix-$name` is also | ||
searched (preferring these over non-prefixed modules). | ||
If `name` starts with a scope (`@scope/name`), the prefix is applied after it: | ||
`@scope/$prefix-name`. | ||
Uses the Node.js [resolution algorithm][nodejs-resolution-algo] (through | ||
[`import-meta-resolve`][github-import-meta-resolve]) to resolve CJS and ESM | ||
packages and files. | ||
##### `options` | ||
If a `prefix` is given and `name` is not a path, | ||
`$prefix-$name` is also searched (preferring these over non-prefixed | ||
modules). | ||
If `name` starts with a scope (`@scope/name`), | ||
the prefix is applied after it: `@scope/$prefix-name`. | ||
Configuration (optional). | ||
###### Parameters | ||
###### `options.prefix` | ||
* `name` (`string`) | ||
— specifier | ||
* `options` ([`LoadOptions`][api-load-options], optional) | ||
— configuration | ||
Prefix to search for (`string`, optional). | ||
###### Returns | ||
###### `options.cwd` | ||
Promise to a whole module or specific export (`Promise<unknown>`). | ||
Place or places to search from (`string`, `Array<string>`, default: | ||
`process.cwd()`). | ||
### `resolvePlugin(name[, options])` | ||
###### `options.global` | ||
Resolve `name` from `from`. | ||
Whether to look for `name` in [global places][global] (`boolean`, optional, | ||
defaults to whether global is detected). | ||
If this is nullish, `load-plugin` will detect if it’s currently running in | ||
global mode: either because it’s in Electron, or because a globally installed | ||
package is running it. | ||
###### Parameters | ||
Note: Electron runs its own version of Node instead of your system Node. | ||
That means global packages cannot be found, unless you’ve [set-up][] a [`prefix` | ||
in your `.npmrc`][prefix] or are using [nvm][] to manage your system node. | ||
* `name` (`string`) | ||
— specifier | ||
* `options` ([`ResolveOptions`][api-resolve-options], optional) | ||
— configuration | ||
###### `options.key` | ||
###### Returns | ||
Identifier to take from the exports (`string` or `false`, default: `'default'`). | ||
For example when given `'whatever'`, the value of `export const whatever = 1` | ||
will be returned, when given `'default'`, the value of `export default …` is | ||
used, and when `false` the whole module object is returned. | ||
Promise to a file URL (`Promise<string>`). | ||
###### Returns | ||
### `LoadOptions` | ||
Promise yielding the results of importing the first path that exists | ||
(`Promise<unknown>`). | ||
The promise rejects if importing an existing path fails, or if no existing | ||
path exists. | ||
Configuration for `loadPlugin` (TypeScript type). | ||
### `resolvePlugin(name[, options])` | ||
This type extends `ResolveOptions` and adds: | ||
Search for `name`. | ||
Accepts the same parameters as [`loadPlugin`][load-plugin] (except `key`) but | ||
returns a promise resolving to an absolute URL (`string`) for `name` instead of | ||
importing it. | ||
Throws if `name` cannot be found. | ||
###### Fields | ||
## Types | ||
* `key` (`boolean` or `string`, default: `'default'`) | ||
— identifier to take from the exports; | ||
for example when given `'x'`, | ||
the value of `export const x = 1` will be returned; | ||
when given `'default'`, | ||
the value of `export default …` is used, | ||
and when `false` the whole module object is returned | ||
This package is fully typed with [TypeScript][]. | ||
It exports the additional types `ResolveOptions` and `LoadOptions`. | ||
### `ResolveOptions` | ||
Configuration for `resolvePlugin` (TypeScript type). | ||
###### Fields | ||
* `from` (`Array<URL | string> | URL | string`, optional) | ||
— place or places to search from; | ||
defaults to the current working directory | ||
* `global` (`boolean`, default: whether global is detected) | ||
— whether to look for `name` in [global places][npm-node-modules]; | ||
if this is nullish, | ||
`load-plugin` will detect if it’s currently running in global mode: either | ||
because it’s in Electron or because a globally installed package is running | ||
it; | ||
note that Electron runs its own version of Node instead of your system Node, | ||
meaning global packages cannot be found, | ||
unless you’ve set-up a [`prefix`][npm-prefix] in your `.npmrc` or are using | ||
[nvm][github-nvm] to manage your system node | ||
* `prefix` (`string`, optional) | ||
— prefix to search for | ||
## Compatibility | ||
This package is at least compatible with all maintained versions of Node.js. | ||
As of now, that is Node.js 14.14+ and 16.0+. | ||
It also works in Deno and modern browsers. | ||
This projects is compatible with maintained versions of Node.js. | ||
When we cut a new major release, | ||
we drop support for unmaintained versions of Node. | ||
This means we try to keep the current release line, | ||
`load-plugin@6`, | ||
compatible with Node.js 16. | ||
## Security | ||
This package reads the file system and imports things into Node.js. | ||
## Contribute | ||
Yes please! | ||
See [How to Contribute to Open Source][contribute]. | ||
See [How to Contribute to Open Source][open-source-guide-contribute]. | ||
## License | ||
[MIT][license] © [Titus Wormer][author] | ||
[MIT][file-license] © [Titus Wormer][wooorm] | ||
<!-- Definitions --> | ||
[build-badge]: https://github.com/wooorm/load-plugin/actions/workflows/main.yml/badge.svg | ||
[api-load-plugin]: #loadpluginname-options | ||
[build]: https://github.com/wooorm/load-plugin/actions | ||
[api-load-options]: #loadoptions | ||
[coverage-badge]: https://img.shields.io/codecov/c/github/wooorm/load-plugin.svg | ||
[api-resolve-plugin]: #resolvepluginname-options | ||
[coverage]: https://codecov.io/github/wooorm/load-plugin | ||
[api-resolve-options]: #resolveoptions | ||
[downloads-badge]: https://img.shields.io/npm/dm/load-plugin.svg | ||
[badge-build-image]: https://github.com/wooorm/load-plugin/workflows/main/badge.svg | ||
[downloads]: https://www.npmjs.com/package/load-plugin | ||
[badge-build-url]: https://github.com/wooorm/load-plugin/actions | ||
[npm]: https://docs.npmjs.com/cli/install | ||
[badge-coverage-image]: https://img.shields.io/codecov/c/github/wooorm/load-plugin.svg | ||
[license]: license | ||
[badge-coverage-url]: https://codecov.io/github/wooorm/load-plugin | ||
[author]: https://wooorm.com | ||
[badge-downloads-image]: https://img.shields.io/npm/dm/load-plugin.svg | ||
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c | ||
[badge-downloads-url]: https://www.npmjs.com/package/load-plugin | ||
[typescript]: https://www.typescriptlang.org | ||
[file-license]: license | ||
[contribute]: https://opensource.guide/how-to-contribute/ | ||
[github-gist-esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c | ||
[global]: https://docs.npmjs.com/files/folders#node-modules | ||
[github-import-meta-resolve]: https://github.com/wooorm/import-meta-resolve | ||
[prefix]: https://docs.npmjs.com/misc/config#prefix | ||
[github-nvm]: https://github.com/nvm-sh/nvm | ||
[set-up]: https://github.com/sindresorhus/guides/blob/master/npm-global-without-sudo.md | ||
[nodejs-resolution-algo]: https://nodejs.org/api/esm.html#esm_resolution_algorithm | ||
[nvm]: https://github.com/creationix/nvm | ||
[npm-install]: https://docs.npmjs.com/cli/install | ||
[algo]: https://nodejs.org/api/esm.html#esm_resolution_algorithm | ||
[npm-node-modules]: https://docs.npmjs.com/cli/v10/configuring-npm/folders#node-modules | ||
[import-meta-resolve]: https://github.com/wooorm/import-meta-resolve | ||
[npm-prefix]: https://docs.npmjs.com/cli/v10/using-npm/config | ||
[load-plugin]: #loadpluginname-options | ||
[open-source-guide-contribute]: https://opensource.guide/how-to-contribute/ | ||
[typescript]: https://www.typescriptlang.org | ||
[wooorm]: https://wooorm.com |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
19909
8
274
226
2
1
+ Added@npmcli/config@8.3.3(transitive)
+ Addedimport-meta-resolve@3.1.1(transitive)
+ Addedproc-log@4.2.0(transitive)
- Removed@npmcli/config@6.4.1(transitive)
- Removedimport-meta-resolve@2.2.2(transitive)
- Removedproc-log@3.0.0(transitive)
Updated@npmcli/config@^8.0.0
Updatedimport-meta-resolve@^3.0.0