107 lines
2.6 KiB
TypeScript
107 lines
2.6 KiB
TypeScript
/**
|
|
* Task file parser
|
|
*
|
|
* Supports both YAML (.yaml/.yml) and Markdown (.md) task files.
|
|
* YAML files are validated against TaskFileSchema.
|
|
* Markdown files are treated as plain text (backward compatible).
|
|
*/
|
|
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { parse as parseYaml } from 'yaml';
|
|
import { TaskFileSchema, type TaskFileData } from './schema.js';
|
|
|
|
/** Supported task file extensions */
|
|
const YAML_EXTENSIONS = ['.yaml', '.yml'];
|
|
const MD_EXTENSIONS = ['.md'];
|
|
export const TASK_EXTENSIONS = [...YAML_EXTENSIONS, ...MD_EXTENSIONS];
|
|
|
|
/** Parsed task with optional structured data */
|
|
export interface ParsedTask {
|
|
filePath: string;
|
|
name: string;
|
|
content: string;
|
|
createdAt: string;
|
|
/** Structured data from YAML files (null for .md files) */
|
|
data: TaskFileData | null;
|
|
}
|
|
|
|
/**
|
|
* Check if a file is a supported task file
|
|
*/
|
|
export function isTaskFile(filename: string): boolean {
|
|
const ext = path.extname(filename).toLowerCase();
|
|
return TASK_EXTENSIONS.includes(ext);
|
|
}
|
|
|
|
/**
|
|
* Check if a file is a YAML task file
|
|
*/
|
|
function isYamlFile(filename: string): boolean {
|
|
const ext = path.extname(filename).toLowerCase();
|
|
return YAML_EXTENSIONS.includes(ext);
|
|
}
|
|
|
|
/**
|
|
* Get the task name from a filename (without extension)
|
|
*/
|
|
function getTaskName(filename: string): string {
|
|
const ext = path.extname(filename);
|
|
return path.basename(filename, ext);
|
|
}
|
|
|
|
/**
|
|
* Parse a single task file
|
|
*
|
|
* @throws Error if YAML parsing or validation fails
|
|
*/
|
|
export function parseTaskFile(filePath: string): ParsedTask {
|
|
const rawContent = fs.readFileSync(filePath, 'utf-8');
|
|
const stat = fs.statSync(filePath);
|
|
const filename = path.basename(filePath);
|
|
const name = getTaskName(filename);
|
|
|
|
if (isYamlFile(filename)) {
|
|
const parsed = parseYaml(rawContent) as unknown;
|
|
const validated = TaskFileSchema.parse(parsed);
|
|
return {
|
|
filePath,
|
|
name,
|
|
content: validated.task,
|
|
createdAt: stat.birthtime.toISOString(),
|
|
data: validated,
|
|
};
|
|
}
|
|
|
|
// Markdown file: plain text, no structured data
|
|
return {
|
|
filePath,
|
|
name,
|
|
content: rawContent,
|
|
createdAt: stat.birthtime.toISOString(),
|
|
data: null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* List and parse all task files in a directory
|
|
*/
|
|
export function parseTaskFiles(tasksDir: string): ParsedTask[] {
|
|
const tasks: ParsedTask[] = [];
|
|
|
|
const files = fs.readdirSync(tasksDir)
|
|
.filter(isTaskFile)
|
|
.sort();
|
|
|
|
for (const file of files) {
|
|
const filePath = path.join(tasksDir, file);
|
|
try {
|
|
tasks.push(parseTaskFile(filePath));
|
|
} catch {
|
|
// Skip files that fail to parse
|
|
}
|
|
}
|
|
|
|
return tasks;
|
|
}
|