import path, { dirname, win32, join } from 'node:path'; import fs, { promises } from 'node:fs'; import fsp from 'node:fs/promises'; import process from 'node:process'; import { interopDefault, resolvePathSync } from 'mlly'; import { fileURLToPath } from 'node:url'; /* How it works: `this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value. */ class Node { value; next; constructor(value) { this.value = value; } } class Queue { #head; #tail; #size; constructor() { this.clear(); } enqueue(value) { const node = new Node(value); if (this.#head) { this.#tail.next = node; this.#tail = node; } else { this.#head = node; this.#tail = node; } this.#size++; } dequeue() { const current = this.#head; if (!current) { return; } this.#head = this.#head.next; this.#size--; return current.value; } clear() { this.#head = undefined; this.#tail = undefined; this.#size = 0; } get size() { return this.#size; } * [Symbol.iterator]() { let current = this.#head; while (current) { yield current.value; current = current.next; } } } function pLimit(concurrency) { if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) { throw new TypeError('Expected `concurrency` to be a number from 1 and up'); } const queue = new Queue(); let activeCount = 0; const next = () => { activeCount--; if (queue.size > 0) { queue.dequeue()(); } }; const run = async (fn, resolve, args) => { activeCount++; const result = (async () => fn(...args))(); resolve(result); try { await result; } catch {} next(); }; const enqueue = (fn, resolve, args) => { queue.enqueue(run.bind(undefined, fn, resolve, args)); (async () => { // This function needs to wait until the next microtask before comparing // `activeCount` to `concurrency`, because `activeCount` is updated asynchronously // when the run function is dequeued and called. The comparison in the if-statement // needs to happen asynchronously as well to get an up-to-date value for `activeCount`. await Promise.resolve(); if (activeCount < concurrency && queue.size > 0) { queue.dequeue()(); } })(); }; const generator = (fn, ...args) => new Promise(resolve => { enqueue(fn, resolve, args); }); Object.defineProperties(generator, { activeCount: { get: () => activeCount, }, pendingCount: { get: () => queue.size, }, clearQueue: { value: () => { queue.clear(); }, }, }); return generator; } class EndError extends Error { constructor(value) { super(); this.value = value; } } // The input can also be a promise, so we await it. const testElement = async (element, tester) => tester(await element); // The input can also be a promise, so we `Promise.all()` them both. const finder = async element => { const values = await Promise.all(element); if (values[1] === true) { throw new EndError(values[0]); } return false; }; async function pLocate( iterable, tester, { concurrency = Number.POSITIVE_INFINITY, preserveOrder = true, } = {}, ) { const limit = pLimit(concurrency); // Start all the promises concurrently with optional limit. const items = [...iterable].map(element => [element, limit(testElement, element, tester)]); // Check the promises either serially or concurrently. const checkLimit = pLimit(preserveOrder ? 1 : Number.POSITIVE_INFINITY); try { await Promise.all(items.map(element => checkLimit(finder, element))); } catch (error) { if (error instanceof EndError) { return error.value; } throw error; } } const typeMappings = { directory: 'isDirectory', file: 'isFile', }; function checkType(type) { if (Object.hasOwnProperty.call(typeMappings, type)) { return; } throw new Error(`Invalid type specified: ${type}`); } const matchType = (type, stat) => stat[typeMappings[type]](); const toPath$1 = urlOrPath => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath; async function locatePath( paths, { cwd = process.cwd(), type = 'file', allowSymlinks = true, concurrency, preserveOrder, } = {}, ) { checkType(type); cwd = toPath$1(cwd); const statFunction = allowSymlinks ? promises.stat : promises.lstat; return pLocate(paths, async path_ => { try { const stat = await statFunction(path.resolve(cwd, path_)); return matchType(type, stat); } catch { return false; } }, {concurrency, preserveOrder}); } const toPath = urlOrPath => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath; const findUpStop = Symbol('findUpStop'); async function findUpMultiple(name, options = {}) { let directory = path.resolve(toPath(options.cwd) || ''); const {root} = path.parse(directory); const stopAt = path.resolve(directory, options.stopAt || root); const limit = options.limit || Number.POSITIVE_INFINITY; const paths = [name].flat(); const runMatcher = async locateOptions => { if (typeof name !== 'function') { return locatePath(paths, locateOptions); } const foundPath = await name(locateOptions.cwd); if (typeof foundPath === 'string') { return locatePath([foundPath], locateOptions); } return foundPath; }; const matches = []; // eslint-disable-next-line no-constant-condition while (true) { // eslint-disable-next-line no-await-in-loop const foundPath = await runMatcher({...options, cwd: directory}); if (foundPath === findUpStop) { break; } if (foundPath) { matches.push(path.resolve(directory, foundPath)); } if (directory === stopAt || matches.length >= limit) { break; } directory = path.dirname(directory); } return matches; } async function findUp(name, options = {}) { const matches = await findUpMultiple(name, {...options, limit: 1}); return matches[0]; } function _resolve(path, options = {}) { if (options.platform === "auto" || !options.platform) options.platform = process.platform === "win32" ? "win32" : "posix"; const modulePath = resolvePathSync(path, { url: options.paths }); if (options.platform === "win32") return win32.normalize(modulePath); return modulePath; } function resolveModule(name, options = {}) { try { return _resolve(name, options); } catch (e) { return void 0; } } async function importModule(path) { const i = await import(path); if (i) return interopDefault(i); return i; } function isPackageExists(name, options = {}) { return !!resolvePackage(name, options); } function getPackageJsonPath(name, options = {}) { const entry = resolvePackage(name, options); if (!entry) return; return searchPackageJSON(entry); } async function getPackageInfo(name, options = {}) { const packageJsonPath = getPackageJsonPath(name, options); if (!packageJsonPath) return; const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf8")); return { name, version: packageJson.version, rootPath: dirname(packageJsonPath), packageJsonPath, packageJson }; } function getPackageInfoSync(name, options = {}) { const packageJsonPath = getPackageJsonPath(name, options); if (!packageJsonPath) return; const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); return { name, version: packageJson.version, rootPath: dirname(packageJsonPath), packageJsonPath, packageJson }; } function resolvePackage(name, options = {}) { try { return _resolve(`${name}/package.json`, options); } catch { } try { return _resolve(name, options); } catch (e) { if (e.code !== "MODULE_NOT_FOUND" && e.code !== "ERR_MODULE_NOT_FOUND") console.error(e); return false; } } function searchPackageJSON(dir) { let packageJsonPath; while (true) { if (!dir) return; const newDir = dirname(dir); if (newDir === dir) return; dir = newDir; packageJsonPath = join(dir, "package.json"); if (fs.existsSync(packageJsonPath)) break; } return packageJsonPath; } async function loadPackageJSON(cwd = process.cwd()) { const path = await findUp("package.json", { cwd }); if (!path || !fs.existsSync(path)) return null; return JSON.parse(await fsp.readFile(path, "utf-8")); } async function isPackageListed(name, cwd) { const pkg = await loadPackageJSON(cwd) || {}; return name in (pkg.dependencies || {}) || name in (pkg.devDependencies || {}); } export { getPackageInfo, getPackageInfoSync, importModule, isPackageExists, isPackageListed, loadPackageJSON, resolveModule };