meld
Version:
Meld: A template language for LLM prompts
108 lines (94 loc) • 2.88 kB
text/typescript
import { MemfsTestFileSystem } from './MemfsTestFileSystem.js';
import * as path from 'path';
/**
* Structure representing a test project
*/
export interface ProjectStructure {
/**
* Map of file paths to their contents
*/
files: Record<string, string>;
/**
* Optional list of directories to create
*/
dirs?: string[];
/**
* Optional project root path. Defaults to 'project'
*/
projectRoot?: string;
/**
* Optional home path. Defaults to 'home'
*/
homePath?: string;
}
/**
* Builds test project structures in the in-memory filesystem
*/
export class ProjectBuilder {
constructor(private fs: MemfsTestFileSystem) {}
/**
* Create a project structure in the filesystem
*/
async create(struct: ProjectStructure): Promise<void> {
const projectRoot = struct.projectRoot || 'project';
const homePath = struct.homePath || 'home';
// Create standard directories
await this.fs.mkdir('/' + projectRoot);
await this.fs.mkdir('/' + homePath);
// Create any additional directories
for (const dir of struct.dirs || []) {
const fullPath = this.resolvePath(dir, projectRoot);
if (!(await this.fs.exists(fullPath))) {
await this.fs.mkdir(fullPath);
}
}
// Create all files
for (const [filePath, content] of Object.entries(struct.files)) {
const fullPath = this.resolvePath(filePath, projectRoot);
const resolvedContent = this.resolveContent(content, {
$PROJECTPATH: projectRoot,
$HOMEPATH: homePath
});
// Ensure parent directory exists
const dirPath = path.dirname(fullPath);
if (!(await this.fs.exists(dirPath))) {
await this.fs.mkdir(dirPath);
}
await this.fs.writeFile(fullPath, resolvedContent);
}
}
/**
* Create a basic project with common defaults
*/
async createBasicProject(): Promise<void> {
await this.create({
dirs: ['src', 'tests', '../home/.config'],
files: {
'README.md': '# Test Project',
'src/main.meld': '@text greeting = "Hello World"',
'../home/.config/settings.json': '{}'
}
});
}
/**
* Resolve a path relative to the project root
*/
private resolvePath(filePath: string, projectRoot: string): string {
// If path starts with $PROJECTPATH or $HOMEPATH, leave it as is
if (filePath.startsWith('$')) {
return filePath;
}
// Otherwise resolve relative to project root
return path.isAbsolute(filePath) ? filePath : '/' + path.join(projectRoot, filePath);
}
/**
* Replace variables in content
*/
private resolveContent(content: string, vars: Record<string, string>): string {
let result = content;
for (const [key, value] of Object.entries(vars)) {
result = result.replace(new RegExp(key, 'g'), value);
}
return result;
}
}