import { detectPackageManager } from './pkgManager' // A compact (code-wise, probably not memory-wise) singly linked list node. type QueueNode = [value: T, next?: QueueNode] /** * Return a function for running multiple async operations with limited concurrency. */ export function limitConcurrency( concurrency = Number.POSITIVE_INFINITY, ): ( func: (...args: Args) => PromiseLike | T, ...args: Args ) => Promise { // The number of currently active + pending tasks. let count = 0 // The head and tail of the pending task queue, built using a singly linked list. // Both head and tail are initially undefined, signifying an empty queue. // They both become undefined again whenever there are no pending tasks. let head: undefined | QueueNode<() => void> let tail: undefined | QueueNode<() => void> // A bookkeeping function executed whenever a task has been run to completion. const finish = () => { count-- // Check if there are further pending tasks in the queue. if (head) { // Allow the next pending task to run and pop it from the queue. head[0]() head = head[1] // The head may now be undefined if there are no further pending tasks. // In that case, set tail to undefined as well. tail = head && tail } } return (func, ...args) => { // Create a promise chain that: // 1. Waits for its turn in the task queue (if necessary). // 2. Runs the task. // 3. Allows the next pending task (if any) to run. return new Promise((resolve) => { if (count++ < concurrency) { // No need to queue if fewer than maxConcurrency tasks are running. resolve() } else if (tail) { // There are pending tasks, so append to the queue. tail = tail[1] = [resolve] } else { // No other pending tasks, initialize the queue with a new tail and head. head = tail = [resolve] } }) .then(() => { // Running func here ensures that even a non-thenable result or an // immediately thrown error gets wrapped into a Promise. return func(...args) }) .finally(finish) } } export function assert(condition: unknown, message: string | (() => string)): asserts condition { if (!condition) { throw new Error(typeof message === 'string' ? message : message()) } } export function getSuggestedInstallCommand(cwd: string) { const pkgManager = detectPackageManager(cwd) switch (pkgManager?.name) { case 'bun': return 'bun install --dev vitest' case 'yarn': return 'yarn add -D vitest' case 'pnpm': return 'pnpm add -D vitest' case 'npm': default: return 'npm i --save-dev vitest' } } export function normalizeDriveLetter(path: string) { if (process.platform !== 'win32') return path return path[0].toUpperCase() + path.slice(1) } export function createQueuedHandler(resolver: (value: T[]) => Promise, timeout = 50) { const cached = new Set() let promise: Promise | null = null let timer: ReturnType | null = null let pendingResolvers: Array<() => void> = [] function flush() { if (promise) return const values = [...cached] cached.clear() const resolvers = pendingResolvers pendingResolvers = [] promise = resolver(values).finally(() => { promise = null resolvers.forEach((fn) => fn()) // If more items were queued while resolving, flush them if (cached.size) flush() }) } return (value: T): Promise => { cached.add(value) if (timer) { clearTimeout(timer) } timer = setTimeout(() => { timer = null flush() }, timeout) return new Promise((resolve) => { pendingResolvers.push(resolve) }) } }