Skip to content

Commit dae9fe1

Browse files
committed
docs: JSDoc for 30 exported functions + real LRU in glob matcher cache
Adds concise JSDoc blocks to exports in agent, stdio, suppress-warnings, utils/get-ipc, and the constants/ getters that were previously bare. Predicates like supports*() now state the Node version threshold; flag helpers document what's included in the returned array; abort helpers document their singleton semantics. Also rewrites globs.ts getGlobMatcher LRU cache: the separate matcherAccessOrder array (with O(n) indexOf/splice on every hit) was expensive and brittle. Replace with a Map whose insertion-order iteration is the recency order — O(1) touch-on-read, O(1) eviction — mirroring the pattern applied to git.ts in the previous commit. Final @fileoverview coverage on src/ (excluding external/ and types/): 100%. Remaining undoc'd exports: two getIpc overload signatures (the primary overload carries the JSDoc that applies to all three).
1 parent a0cefcd commit dae9fe1

File tree

11 files changed

+215
-27
lines changed

11 files changed

+215
-27
lines changed

src/agent.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,17 @@ export interface ExecScriptOptions extends SpawnOptions {
437437
prepost?: boolean | undefined
438438
}
439439

440+
/**
441+
* Execute a package.json script using the detected package manager.
442+
* Picks pnpm, npm, or yarn by walking up to the nearest lockfile; falls back
443+
* to running `node --run` or `npm run` directly when no lockfile is found.
444+
* Honors `shell: true` by passing through to `spawn()` unchanged.
445+
*
446+
* @param scriptName - The package.json script to run
447+
* @param args - Either the script arguments or an options object
448+
* @param options - Spawn options plus `prepost` to force npm-style pre/post scripts
449+
* @returns The spawned `ChildProcess`-like promise from the underlying runner.
450+
*/
440451
export function execScript(
441452
scriptName: string,
442453
args?: string[] | readonly string[] | ExecScriptOptions | undefined,

src/constants/licenses.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export const UNLICENSED = 'UNLICENSED'
1111

1212
// Copy-left licenses.
1313
let _copyLeftLicenses: Set<string>
14+
/**
15+
* Get the set of SPDX identifiers considered copy-left (AGPL, GPL, EPL,
16+
* EUPL, CC-BY-SA variants). The set is lazily built and cached on first call.
17+
*
18+
* @returns A `Set` of SPDX license identifier strings.
19+
*/
1420
export function getCopyLeftLicenses(): Set<string> {
1521
if (_copyLeftLicenses === undefined) {
1622
_copyLeftLicenses = new Set([

src/constants/node.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,59 +11,126 @@ import { maintainedNodeVersions } from './maintained-node-versions'
1111
const NODE_VERSION = process.version
1212

1313
// Version detection.
14+
/**
15+
* Get the full Node.js version string from `process.version`.
16+
*
17+
* @returns The runtime version, including the leading `v` (e.g. `v22.11.0`).
18+
*/
1419
export function getNodeVersion(): string {
1520
return NODE_VERSION
1621
}
1722

23+
/**
24+
* Get the major component of the current Node.js version.
25+
*
26+
* @returns The major version number, or `0` if it cannot be parsed.
27+
*/
1828
export function getNodeMajorVersion(): number {
1929
const major = NODE_VERSION.slice(1).split('.')[0] ?? '0'
2030
return Number.parseInt(major, 10) || 0
2131
}
2232

33+
/**
34+
* Get the minor component of the current Node.js version.
35+
*
36+
* @returns The minor version number, or `0` if it cannot be parsed.
37+
*/
2338
export function getNodeMinorVersion(): number {
2439
return Number.parseInt(NODE_VERSION.split('.')[1] ?? '0', 10)
2540
}
2641

42+
/**
43+
* Get the patch component of the current Node.js version.
44+
*
45+
* @returns The patch version number, or `0` if it cannot be parsed.
46+
*/
2747
export function getNodePatchVersion(): number {
2848
return Number.parseInt(NODE_VERSION.split('.')[2] ?? '0', 10)
2949
}
3050

3151
// Maintained Node.js versions.
52+
/**
53+
* Get the list of Node.js major versions currently under long-term support.
54+
*
55+
* @returns The static `maintainedNodeVersions` array shared across the library.
56+
*/
3257
export function getMaintainedNodeVersions() {
3358
return maintainedNodeVersions
3459
}
3560

3661
// Feature detection.
62+
/**
63+
* Check whether the current runtime exposes the `module.enableCompileCache()` API.
64+
* The API is available on Node.js 24+.
65+
*
66+
* @returns `true` when the current runtime is Node.js 24 or newer.
67+
*/
3768
export function supportsNodeCompileCacheApi(): boolean {
3869
const major = getNodeMajorVersion()
3970
return major >= 24
4071
}
4172

73+
/**
74+
* Check whether the current runtime honors the `NODE_COMPILE_CACHE` env var.
75+
* Env-var-based compile caching is available on Node.js 22+.
76+
*
77+
* @returns `true` when the current runtime is Node.js 22 or newer.
78+
*/
4279
export function supportsNodeCompileCacheEnvVar(): boolean {
4380
const major = getNodeMajorVersion()
4481
return major >= 22
4582
}
4683

84+
/**
85+
* Check whether the current runtime supports the `--disable-warning` CLI flag.
86+
* The flag is available on Node.js 21+.
87+
*
88+
* @returns `true` when the current runtime is Node.js 21 or newer.
89+
*/
4790
export function supportsNodeDisableWarningFlag(): boolean {
4891
const major = getNodeMajorVersion()
4992
return major >= 21
5093
}
5194

95+
/**
96+
* Check whether the current runtime supports the permission model CLI flags
97+
* (`--experimental-permission` on Node 20-23, `--permission` on Node 24+).
98+
*
99+
* @returns `true` when the current runtime is Node.js 20 or newer.
100+
*/
52101
export function supportsNodePermissionFlag(): boolean {
53102
const major = getNodeMajorVersion()
54103
return major >= 20
55104
}
56105

106+
/**
107+
* Check whether `require()` can synchronously load ESM modules.
108+
* Requires Node.js 22.12+ or Node.js 23+.
109+
*
110+
* @returns `true` when the runtime supports `require()`-ing ES modules.
111+
*/
57112
export function supportsNodeRequireModule(): boolean {
58113
const major = getNodeMajorVersion()
59114
return major >= 23 || (major === 22 && getNodeMinorVersion() >= 12)
60115
}
61116

117+
/**
118+
* Check whether the current runtime supports `node --run <script>`.
119+
* Requires Node.js 22.11+ or Node.js 23+.
120+
*
121+
* @returns `true` when the runtime can execute package.json scripts via `--run`.
122+
*/
62123
export function supportsNodeRun(): boolean {
63124
const major = getNodeMajorVersion()
64125
return major >= 23 || (major === 22 && getNodeMinorVersion() >= 11)
65126
}
66127

128+
/**
129+
* Check whether the current runtime supports the `--disable-sigusr1` CLI flag.
130+
* Flag landed in v22.14.0 and v23.7.0 and was stabilized in v22.20.0 / v24.8.0.
131+
*
132+
* @returns `true` when the runtime exposes `--disable-sigusr1`.
133+
*/
67134
export function supportsNodeDisableSigusr1Flag(): boolean {
68135
const major = getNodeMajorVersion()
69136
const minor = getNodeMinorVersion()
@@ -82,6 +149,13 @@ export function supportsNodeDisableSigusr1Flag(): boolean {
82149
}
83150

84151
let _nodeDisableSigusr1Flags: string[]
152+
/**
153+
* Get the flags used to block Node.js debugger attachment via SIGUSR1.
154+
* Returns `['--disable-sigusr1']` on runtimes that support it and falls back
155+
* to `['--no-inspect']` on older versions.
156+
*
157+
* @returns A non-empty array of CLI flags suitable for passing to `node`.
158+
*/
85159
export function getNodeDisableSigusr1Flags(): string[] {
86160
if (_nodeDisableSigusr1Flags === undefined) {
87161
// SIGUSR1 is reserved by Node.js for starting the debugger/inspector.
@@ -99,13 +173,27 @@ export function getNodeDisableSigusr1Flags(): string[] {
99173
return _nodeDisableSigusr1Flags
100174
}
101175

176+
/**
177+
* Check whether this process was spawned with an IPC channel.
178+
* When `true`, `process.send()` is callable to message the parent process.
179+
*
180+
* @returns `true` when the current process has an IPC channel to its parent.
181+
*/
102182
export function supportsProcessSend(): boolean {
103183
return typeof process.send === 'function'
104184
}
105185

106186
// Node.js flags.
107187
let _nodeHardenFlags: string[]
108188
let _nodePermissionFlags: string[]
189+
/**
190+
* Get the permission-grant flags needed to run npm under Node.js 24+'s
191+
* `--permission` model. The array is non-empty only on Node.js 24+ and
192+
* includes `--allow-fs-read=*`, `--allow-fs-write=*`, and
193+
* `--allow-child-process`. Older versions return an empty array.
194+
*
195+
* @returns The permission flag list (possibly empty) for the current runtime.
196+
*/
109197
export function getNodePermissionFlags(): string[] {
110198
if (_nodePermissionFlags === undefined) {
111199
const major = getNodeMajorVersion()
@@ -129,6 +217,15 @@ export function getNodePermissionFlags(): string[] {
129217
return _nodePermissionFlags
130218
}
131219

220+
/**
221+
* Get the hardening flags Socket applies when spawning Node.js subprocesses.
222+
* Always includes `--disable-proto=delete`. Also adds `--permission` plus the
223+
* grants from {@link getNodePermissionFlags} on Node 24+,
224+
* `--experimental-permission` on Node 20-23, and
225+
* `--force-node-api-uncaught-exceptions-policy` on Node 22+.
226+
*
227+
* @returns A non-empty array of CLI flags suitable for passing to `node`.
228+
*/
132229
export function getNodeHardenFlags(): string[] {
133230
if (_nodeHardenFlags === undefined) {
134231
const major = getNodeMajorVersion()
@@ -156,6 +253,12 @@ export function getNodeHardenFlags(): string[] {
156253
}
157254

158255
let _nodeNoWarningsFlags: string[]
256+
/**
257+
* Get the flags that silence Node.js runtime warnings and deprecation notices.
258+
* Always returns `['--no-warnings', '--no-deprecation']` across all versions.
259+
*
260+
* @returns A non-empty array of CLI flags suitable for passing to `node`.
261+
*/
159262
export function getNodeNoWarningsFlags(): string[] {
160263
if (_nodeNoWarningsFlags === undefined) {
161264
_nodeNoWarningsFlags = ['--no-warnings', '--no-deprecation']
@@ -164,6 +267,11 @@ export function getNodeNoWarningsFlags(): string[] {
164267
}
165268

166269
// Execution path.
270+
/**
271+
* Get the absolute path to the currently running Node.js binary.
272+
*
273+
* @returns The value of `process.execPath`.
274+
*/
167275
export function getExecPath(): string {
168276
return process.execPath
169277
}

src/constants/process.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,26 @@
66

77
// Abort controller and signal.
88
let _abortController: AbortController
9+
/**
10+
* Get the process-scoped shared `AbortController` singleton.
11+
* Cooperating modules use this to coordinate cancellation across the library.
12+
*
13+
* @returns The lazily-created shared `AbortController` instance.
14+
*/
915
export function getAbortController(): AbortController {
1016
if (_abortController === undefined) {
1117
_abortController = new AbortController()
1218
}
1319
return _abortController
1420
}
1521

22+
/**
23+
* Get the process-scoped shared `AbortSignal` singleton.
24+
* This is the `signal` property of {@link getAbortController}'s controller and
25+
* is intended to be passed to APIs that accept an `AbortSignal`.
26+
*
27+
* @returns The shared `AbortSignal` instance.
28+
*/
1629
export function getAbortSignal(): AbortSignal {
1730
return getAbortController().signal
1831
}

src/constants/typescript.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ export function getTsTypesAvailable(): boolean {
1414
}
1515
}
1616

17+
/**
18+
* Check whether TypeScript's `lib/` directory is resolvable from the current
19+
* project by probing `typescript/lib`.
20+
*
21+
* @returns `true` when the `typescript` package's libs can be resolved.
22+
*/
1723
export function getTsLibsAvailable(): boolean {
1824
try {
1925
require.resolve('typescript/lib')

src/globs.ts

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -153,29 +153,34 @@ export function globStreamLicenses(
153153
}
154154

155155
const MATCHER_CACHE_MAX_SIZE = 100
156+
// LRU cache. We exploit Map's insertion-order iteration so eviction is O(1):
157+
// delete the first key. On read, delete + set moves the entry to the back,
158+
// keeping the cache in recency order.
156159
const matcherCache = new Map<string, (path: string) => boolean>()
157-
const matcherAccessOrder: string[] = []
158-
159-
function evictLRUMatcher() {
160-
if (
161-
matcherCache.size >= MATCHER_CACHE_MAX_SIZE &&
162-
matcherAccessOrder.length > 0
163-
) {
164-
const oldest = matcherAccessOrder.shift()
165-
if (oldest) {
166-
matcherCache.delete(oldest)
167-
}
168-
}
169-
}
170160

171161
/**
172-
* Get a cached glob matcher function.
162+
* Return a glob-matcher function, memoized by pattern + options.
163+
*
164+
* The returned function is a fast synchronous predicate built on picomatch.
165+
* Results are memoized — calling `getGlobMatcher(['*.ts'])` a thousand times
166+
* in a loop returns the same compiled matcher each time, so callers do not
167+
* need to hoist it themselves.
168+
*
169+
* The cache is LRU with a cap of 100 entries. Cache keys fold together the
170+
* (sorted) pattern list and (sorted) option set, so arguments that differ
171+
* only in ordering share a matcher.
172+
*
173+
* Default options: `dot: true`, `nocase: true`. Patterns starting with `!`
174+
* become ignore patterns.
173175
*
174176
* @example
175177
* ```typescript
176178
* const isMatch = getGlobMatcher('*.ts')
177179
* isMatch('index.ts') // true
178180
* isMatch('index.js') // false
181+
*
182+
* // With negation
183+
* const isSource = getGlobMatcher(['src/**', '!**\/*.test.ts'])
179184
* ```
180185
*/
181186
/*@__NO_SIDE_EFFECTS__*/
@@ -193,19 +198,21 @@ export function getGlobMatcher(
193198
.join(',')
194199
: ''
195200
const key = `${sortedPatterns.join('|')}:${sortedOptions}`
196-
let matcher: ((path: string) => boolean) | undefined = matcherCache.get(key)
197-
if (matcher) {
198-
// Move to end of access order (LRU)
199-
const index = matcherAccessOrder.indexOf(key)
200-
if (index !== -1) {
201-
matcherAccessOrder.splice(index, 1)
202-
matcherAccessOrder.push(key)
203-
}
204-
return matcher
201+
const existing = matcherCache.get(key)
202+
if (existing) {
203+
// Re-insert to mark as most-recently-used.
204+
matcherCache.delete(key)
205+
matcherCache.set(key, existing)
206+
return existing
205207
}
206208

207-
// Evict oldest entry if cache is full
208-
evictLRUMatcher()
209+
// Evict oldest entry if cache is full (Map iteration order = insertion order).
210+
if (matcherCache.size >= MATCHER_CACHE_MAX_SIZE) {
211+
const oldest = matcherCache.keys().next().value
212+
if (oldest !== undefined) {
213+
matcherCache.delete(oldest)
214+
}
215+
}
209216

210217
// Separate positive and negative patterns.
211218
const positivePatterns = patterns.filter(p => !p.startsWith('!'))
@@ -223,13 +230,12 @@ export function getGlobMatcher(
223230

224231
/* c8 ignore next 5 - External picomatch call */
225232
const picomatch = getPicomatch()
226-
matcher = picomatch(
233+
const matcher = picomatch(
227234
positivePatterns.length > 0 ? positivePatterns : patterns,
228235
matchOptions,
229236
) as (path: string) => boolean
230237

231238
matcherCache.set(key, matcher)
232-
matcherAccessOrder.push(key)
233239
return matcher
234240
}
235241

src/stdio/clear.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
* ```
2222
*/
2323
import process from 'node:process'
24+
/**
25+
* Clear the current line on the given stream.
26+
* Uses native TTY methods when available and falls back to `\r\x1b[K` ANSI
27+
* escapes on non-TTY streams.
28+
*
29+
* @param stream - Output stream to clear (defaults to `process.stdout`)
30+
*/
2431
export function clearLine(stream: NodeJS.WriteStream = process.stdout): void {
2532
if (stream.isTTY) {
2633
// TTY: Use cursor control

0 commit comments

Comments
 (0)