UNPKG

list-installed

Version:

Checks that all dependencies in your package.json have supported versions installed

110 lines (94 loc) 3.88 kB
import { stat, opendir } from 'node:fs/promises'; import pathModule from 'node:path'; import { bufferedAsyncMap } from 'buffered-async-iterable'; import { looksLikeAnErrnoException, platformIndependentPath } from './utils.js'; /** * @private * @param {string|import('fs').Dir} path * @param {boolean} [skipScoped] * @param {string} [prefix] * @returns {AsyncGenerator<string>} */ async function * _internalReaddirScoped (path, skipScoped, prefix) { const dir = ( typeof path === 'string' ? await opendir(path) : path ); if (!dir || typeof dir !== 'object') throw new TypeError('Invalid input to readdirScoped()'); yield * bufferedAsyncMap(dir, async function * (file) { if (file.isSymbolicLink()) { // eslint-disable-next-line security/detect-non-literal-fs-filename const resolvedLink = await stat(pathModule.join(dir.path, file.name)); if (!resolvedLink.isDirectory()) { return; } } else if (!file.isDirectory()) { return; } const moduleName = (prefix || '') + file.name; if (file.name.startsWith('@')) { if (!skipScoped) { yield * _internalReaddirScoped(pathModule.join(dir.path, file.name), false, moduleName + pathModule.sep); } } else if (!file.name.startsWith('.')) { yield platformIndependentPath(moduleName); } }); } /** * Returns all directories in `path`, with the scoped directories (like `@foo`) expanded and joined with the directories directly beneath them * * Eg. `@foo` will get expanded to `@foo/abc` and `@foo/bar` if `abc` and `bar` are the two directories in `@foo`, though it will never expand to `@`- or `.`-prefixed subdirectories and will hence never return `@foo/@xyz` or `@foo/.bin`. * * Will not return any directory with a name that begins with `.` * * @param {string|import('fs').Dir} path The path to the directory, either absolute or relative to current working directory * @returns {AsyncGenerator<string>} */ export async function * readdirScoped (path) { yield * _internalReaddirScoped(path); } /** * @private * @param {import('fs').Dir} inputDir * @param {number} [depth] * @param {string} [prefix] * @returns {AsyncGenerator<string>} */ async function * _internalReaddirModuleTree (inputDir, depth = 0, prefix) { yield * bufferedAsyncMap(_internalReaddirScoped(inputDir, false, prefix), async function * (modulePath) { yield platformIndependentPath(modulePath); if (depth < 1) return; const subModule = pathModule.join(prefix || '', modulePath, 'node_modules'); const subModulePath = pathModule.join(inputDir.path, subModule); try { const dir = await opendir(subModulePath); yield * _internalReaddirModuleTree(dir, depth - 1, subModule + pathModule.sep); } catch (err) { if (looksLikeAnErrnoException(err) && err.code === 'ENOENT' && err.path === subModulePath) { // Fail silently } else { throw err; } } }); } /** * Similar to {@link readdirScoped} but can also return nested modules * * For any result of {@link readdirScoped} a lookup towards a `node_modules` subdirectory of that result is done, with the result added and in turn also looked for `node_modules` subdirectories in until the specified `depth` has been reached. * * @param {string|import('fs').Dir} path The path to the directory, either absolute or relative to current working directory * @param {number} [depth] If not set or if set to 0, then behaves identical to {@link readdirScoped} * @returns {AsyncGenerator<string>} */ export async function * readdirModuleTree (path, depth = 0) { const dir = ( typeof path === 'string' ? await opendir(path) : path ); if (!dir || typeof dir !== 'object') throw new TypeError('Invalid input to readdirModuleTree()'); yield * _internalReaddirModuleTree(dir, depth); }