200 lines
5.1 KiB
TypeScript
200 lines
5.1 KiB
TypeScript
import { readFileSync, existsSync } from 'node:fs';
|
|
import { dirname, resolve, relative } from 'node:path';
|
|
import { glob } from 'fast-glob';
|
|
import type { YarnProject } from './types';
|
|
import { validateYarnProject, type SchemaValidationError } from './validator';
|
|
import { parseYarn } from '../yarn-spinner/parse/parser';
|
|
import type { YarnDocument } from '../yarn-spinner/model/ast';
|
|
|
|
/**
|
|
* Options for loading a Yarn project
|
|
*/
|
|
export interface LoadOptions {
|
|
/** Base directory for resolving glob patterns (default: directory of .yarnproject file) */
|
|
baseDir?: string;
|
|
}
|
|
|
|
/**
|
|
* Loaded Yarn document with metadata
|
|
*/
|
|
export interface LoadedYarnFile {
|
|
/** File path relative to base directory */
|
|
relativePath: string;
|
|
/** Absolute file path */
|
|
absolutePath: string;
|
|
/** Parsed Yarn document */
|
|
document: YarnDocument;
|
|
}
|
|
|
|
/**
|
|
* Result of loading a Yarn project
|
|
*/
|
|
export interface LoadResult {
|
|
/** Parsed .yarnproject configuration */
|
|
project: YarnProject;
|
|
/** Directory containing the .yarnproject file */
|
|
baseDir: string;
|
|
/** All loaded and parsed .yarn files */
|
|
yarnFiles: LoadedYarnFile[];
|
|
}
|
|
|
|
/**
|
|
* Error thrown when loading fails
|
|
*/
|
|
export class LoadError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly cause?: unknown,
|
|
) {
|
|
super(message);
|
|
this.name = 'LoadError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Error thrown when validation fails
|
|
*/
|
|
export class ValidationError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly errors: SchemaValidationError[],
|
|
) {
|
|
super(message);
|
|
this.name = 'ValidationError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load and compile a .yarnproject file and all its referenced .yarn files
|
|
*/
|
|
export async function loadYarnProject(
|
|
projectFilePath: string,
|
|
options: LoadOptions = {},
|
|
): Promise<LoadResult> {
|
|
// Resolve and read .yarnproject file
|
|
const absoluteProjectPath = resolve(projectFilePath);
|
|
|
|
if (!existsSync(absoluteProjectPath)) {
|
|
throw new LoadError(`Project file not found: ${absoluteProjectPath}`);
|
|
}
|
|
|
|
const projectContent = readFileSync(absoluteProjectPath, 'utf-8');
|
|
let projectConfig: unknown;
|
|
|
|
try {
|
|
projectConfig = JSON.parse(projectContent);
|
|
} catch (error) {
|
|
throw new LoadError(`Failed to parse .yarnproject file as JSON: ${absoluteProjectPath}`, error);
|
|
}
|
|
|
|
// Validate against schema
|
|
const validation = validateYarnProject(projectConfig);
|
|
|
|
if (!validation.valid) {
|
|
throw new ValidationError(
|
|
`Invalid .yarnproject file: ${validation.errors.map(e => `${e.path}: ${e.message}`).join(', ')}`,
|
|
validation.errors,
|
|
);
|
|
}
|
|
|
|
const project = validation.data as YarnProject;
|
|
const baseDir = options.baseDir || dirname(absoluteProjectPath);
|
|
|
|
// Find all .yarn files using glob patterns
|
|
const sourcePatterns = project.sourceFiles;
|
|
const ignorePatterns = project.excludeFiles || [];
|
|
|
|
const yarnFilePaths = await glob(sourcePatterns, {
|
|
cwd: baseDir,
|
|
ignore: ignorePatterns,
|
|
absolute: true,
|
|
onlyFiles: true,
|
|
});
|
|
|
|
// Load and parse each .yarn file
|
|
const yarnFiles: LoadedYarnFile[] = yarnFilePaths.map(absolutePath => {
|
|
const relativePath = relative(baseDir, absolutePath);
|
|
const content = readFileSync(absolutePath, 'utf-8');
|
|
const document = parseYarn(content);
|
|
|
|
return {
|
|
relativePath,
|
|
absolutePath,
|
|
document,
|
|
};
|
|
});
|
|
|
|
return {
|
|
project,
|
|
baseDir,
|
|
yarnFiles,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Synchronous version of loadYarnProject
|
|
*/
|
|
export function loadYarnProjectSync(
|
|
projectFilePath: string,
|
|
options: LoadOptions = {},
|
|
): LoadResult {
|
|
// Resolve and read .yarnproject file
|
|
const absoluteProjectPath = resolve(projectFilePath);
|
|
|
|
if (!existsSync(absoluteProjectPath)) {
|
|
throw new LoadError(`Project file not found: ${absoluteProjectPath}`);
|
|
}
|
|
|
|
const projectContent = readFileSync(absoluteProjectPath, 'utf-8');
|
|
let projectConfig: unknown;
|
|
|
|
try {
|
|
projectConfig = JSON.parse(projectContent);
|
|
} catch (error) {
|
|
throw new LoadError(`Failed to parse .yarnproject file as JSON: ${absoluteProjectPath}`, error);
|
|
}
|
|
|
|
// Validate against schema
|
|
const validation = validateYarnProject(projectConfig);
|
|
|
|
if (!validation.valid) {
|
|
throw new ValidationError(
|
|
`Invalid .yarnproject file: ${validation.errors.map(e => `${e.path}: ${e.message}`).join(', ')}`,
|
|
validation.errors,
|
|
);
|
|
}
|
|
|
|
const project = validation.data as YarnProject;
|
|
const baseDir = options.baseDir || dirname(absoluteProjectPath);
|
|
|
|
// Find all .yarn files using glob patterns
|
|
const sourcePatterns = project.sourceFiles;
|
|
const ignorePatterns = project.excludeFiles || [];
|
|
|
|
const yarnFilePaths = glob.sync(sourcePatterns, {
|
|
cwd: baseDir,
|
|
ignore: ignorePatterns,
|
|
absolute: true,
|
|
onlyFiles: true,
|
|
});
|
|
|
|
// Load and parse each .yarn file
|
|
const yarnFiles: LoadedYarnFile[] = yarnFilePaths.map(absolutePath => {
|
|
const relativePath = relative(baseDir, absolutePath);
|
|
const content = readFileSync(absolutePath, 'utf-8');
|
|
const document = parseYarn(content);
|
|
|
|
return {
|
|
relativePath,
|
|
absolutePath,
|
|
document,
|
|
};
|
|
});
|
|
|
|
return {
|
|
project,
|
|
baseDir,
|
|
yarnFiles,
|
|
};
|
|
}
|