/**
* File system helpers — file loading and glob discovery.
*
* Composable primitives for `analyzeFromFiles` / `discoverSourceFiles`. For
* most use cases prefer those higher-level entry points.
*
* Build tools that want to drive resolve outside the session can construct an
* `ImportResolver` directly and pass it to `createAnalysisSession`.
*
* @see `analyzeFromFiles` for the high-level disk-discovery wrapper
* @see `discoverSourceFiles` for exports-first discovery with glob fallback
*
* @module
*/
import {readFile} from 'node:fs/promises';
import {resolve, isAbsolute} from 'node:path';
import {glob} from 'tinyglobby';
import type {SourceFileInfo} from './source.js';
import {toPosixPath} from './paths.js';
import {MAX_FILE_CONCURRENCY, map_concurrent} from './concurrency.js';
/**
* Load a single source file from disk.
*
* Accepts either relative or absolute paths. Relative paths are resolved against `projectRoot`.
*
* @param path - file path (relative to `projectRoot` or absolute)
* @param projectRoot - absolute path to project root
* @returns source file info with content loaded
* @throws Error if the file cannot be read (e.g., missing or permission denied)
*
* @example
* ```ts
* const file = await loadFile('src/lib/math.ts', process.cwd());
* // {id: '/abs/path/to/src/lib/math.ts', content: '...'}
* ```
*/
export const loadFile = async (path: string, projectRoot: string): Promise<SourceFileInfo> => {
const absolutePath = isAbsolute(path) ? path : resolve(projectRoot, path);
const content = await readFile(absolutePath, 'utf-8');
return {
id: toPosixPath(absolutePath),
content,
};
};
/**
* Glob extension list for source files. Matches the extension set covered by
* `getDefaultAnalyzer` in `source.ts` — keep in sync if a new analyzable
* extension is added there.
*/
const SOURCE_FILE_EXTENSIONS = 'ts,js,svelte,css,json';
/**
* Build an include pattern array from source paths.
*
* Each path becomes a `<path>/**\/*.{ts,js,svelte,css,json}` glob. Used by
* `discoverSourceFiles` to derive a default `include` from
* `sourceOptions.sourcePaths` when no explicit pattern is supplied — keeps
* the glob fallback consistent with custom `sourcePaths` instead of silently
* defaulting to `src/lib`.
*
* @example
* ```ts
* deriveIncludePatterns(['packages/foo', 'packages/bar'])
* // => ['packages/foo/**\/*.{ts,js,svelte,css,json}', 'packages/bar/**\/*.{ts,js,svelte,css,json}']
* ```
*/
export const deriveIncludePatterns = (sourcePaths: ReadonlyArray<string>): Array<string> =>
sourcePaths.map((p) => `${p}/**/*.{${SOURCE_FILE_EXTENSIONS}}`);
/**
* Options for `globFiles`.
*/
export interface GlobFilesOptions {
/** Absolute path to project root. */
projectRoot: string;
/** Glob patterns to include (relative to `projectRoot`). */
include: Array<string>;
/** Optional glob patterns to exclude. */
exclude?: Array<string>;
}
/**
* Discover source files via glob patterns.
*
* @param options - glob configuration
* @returns array of source files with content loaded
* @throws Error if any matched file cannot be read — `Promise.all` rejects on the first read failure
*
* @example
* ```ts
* const files = await globFiles({
* projectRoot: process.cwd(),
* include: deriveIncludePatterns(['src/lib']),
* exclude: DEFAULT_SOURCE_OPTIONS.exclude,
* });
* ```
*/
export const globFiles = async (options: GlobFilesOptions): Promise<Array<SourceFileInfo>> => {
const {projectRoot, include, exclude} = options;
const filePaths = await glob(include, {
cwd: projectRoot,
ignore: exclude,
absolute: true,
});
// Bounded concurrency to keep FD pressure under the typical ulimit on
// large projects. See `concurrency.ts`.
return map_concurrent(filePaths, MAX_FILE_CONCURRENCY, async (id) => {
const content = await readFile(id, 'utf-8');
return {id: toPosixPath(id), content};
});
};
{
"path": "files.ts",
"declarations": [
{
"name": "loadFile",
"kind": "function",
"docComment": "Load a single source file from disk.\n\nAccepts either relative or absolute paths. Relative paths are resolved against `projectRoot`.",
"typeSignature": "(path: string, projectRoot: string): Promise<SourceFileInfo>",
"sourceLine": 40,
"examples": [
"```ts\nconst file = await loadFile('src/lib/math.ts', process.cwd());\n// {id: '/abs/path/to/src/lib/math.ts', content: '...'}\n```"
],
"throws": [
{
"type": "Error",
"description": "if the file cannot be read (e.g., missing or permission denied)"
}
],
"parameters": [
{
"name": "path",
"type": "string",
"description": "file path (relative to `projectRoot` or absolute)"
},
{
"name": "projectRoot",
"type": "string",
"description": "absolute path to project root"
}
],
"returnType": "Promise<SourceFileInfo>",
"returnDescription": "source file info with content loaded"
},
{
"name": "deriveIncludePatterns",
"kind": "function",
"docComment": "Build an include pattern array from source paths.\n\nEach path becomes a `<path>/**\\/*.{ts,js,svelte,css,json}` glob. Used by\n`discoverSourceFiles` to derive a default `include` from\n`sourceOptions.sourcePaths` when no explicit pattern is supplied — keeps\nthe glob fallback consistent with custom `sourcePaths` instead of silently\ndefaulting to `src/lib`.",
"typeSignature": "(sourcePaths: readonly string[]): string[]",
"sourceLine": 72,
"examples": [
"```ts\nderiveIncludePatterns(['packages/foo', 'packages/bar'])\n// => ['packages/foo/**\\/*.{ts,js,svelte,css,json}', 'packages/bar/**\\/*.{ts,js,svelte,css,json}']\n```"
],
"alsoExportedFrom": [
"index.ts"
],
"parameters": [
{
"name": "sourcePaths",
"type": "readonly string[]"
}
],
"returnType": "string[]"
},
{
"name": "GlobFilesOptions",
"kind": "interface",
"docComment": "Options for `globFiles`.",
"typeSignature": "GlobFilesOptions",
"sourceLine": 78,
"members": [
{
"name": "projectRoot",
"kind": "variable",
"docComment": "Absolute path to project root.",
"typeSignature": "string"
},
{
"name": "include",
"kind": "variable",
"docComment": "Glob patterns to include (relative to `projectRoot`).",
"typeSignature": "Array<string>"
},
{
"name": "exclude",
"kind": "variable",
"docComment": "Optional glob patterns to exclude.",
"typeSignature": "Array<string>",
"optional": true
}
]
},
{
"name": "globFiles",
"kind": "function",
"docComment": "Discover source files via glob patterns.",
"typeSignature": "(options: GlobFilesOptions): Promise<SourceFileInfo[]>",
"sourceLine": 103,
"examples": [
"```ts\nconst files = await globFiles({\n projectRoot: process.cwd(),\n include: deriveIncludePatterns(['src/lib']),\n exclude: DEFAULT_SOURCE_OPTIONS.exclude,\n});\n```"
],
"throws": [
{
"type": "Error",
"description": "if any matched file cannot be read — `Promise.all` rejects on the first read failure"
}
],
"parameters": [
{
"name": "options",
"type": "GlobFilesOptions",
"description": "glob configuration"
}
],
"returnType": "Promise<SourceFileInfo[]>",
"returnDescription": "array of source files with content loaded"
}
],
"moduleComment": "File system helpers — file loading and glob discovery.\n\nComposable primitives for `analyzeFromFiles` / `discoverSourceFiles`. For\nmost use cases prefer those higher-level entry points.\n\nBuild tools that want to drive resolve outside the session can construct an\n`ImportResolver` directly and pass it to `createAnalysisSession`.\n\n@see `analyzeFromFiles` for the high-level disk-discovery wrapper\n@see `discoverSourceFiles` for exports-first discovery with glob fallback",
"dependencies": [
"concurrency.ts",
"paths.ts",
"source.ts"
],
"dependents": [
"discovery.ts",
"index.ts"
]
}