paths.ts•2.77 kB
import path from "node:path";
import fs from "node:fs/promises";
import { RequestEntry } from "../types/index.js";
/**
 * Resolves a task file path, supporting both absolute and relative paths.
 * For relative paths, resolves against either a base directory or process.cwd().
 * Provides cross-platform compatibility for Windows and Linux.
 * 
 * @param filePath - The file path (absolute or relative)
 * @param baseDir - Optional base directory to resolve relative paths against
 * @returns Resolved absolute path
 */
export function resolveTaskFilePath(filePath: string, baseDir?: string): string {
  try {
    // If it's already absolute, return as-is
    if (path.isAbsolute(filePath)) {
      return path.normalize(filePath);
    }
    
    // Use provided base directory or fallback to process.cwd()
    const resolveBase = baseDir || process.cwd();
    const resolved = path.resolve(resolveBase, filePath);
    
    // Normalize the path for cross-platform consistency
    return path.normalize(resolved);
  } catch (error) {
    // Fallback to treating as absolute if resolution fails
    console.warn(`Warning: Failed to resolve path "${filePath}", using as-is:`, error instanceof Error ? error.message : error);
    return path.normalize(filePath);
  }
}
export function generateSafeFilename(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^a-z0-9\s-]/g, "")
    .replace(/\s+/g, "-")
    .replace(/-+/g, "-")
    .replace(/^-+|-+$/g, "")
    .substring(0, 50);
}
export async function resolveExportPath(
  req: RequestEntry,
  outputPath?: string,
  filename?: string,
  format: "markdown" | "json" | "html" = "markdown"
): Promise<string> {
  const extensions = { markdown: "md", json: "json", html: "html" } as const;
  const ext = extensions[format];
  const projectName = generateSafeFilename(req.originalRequest);
  const defaultFilename = `${projectName}_tasks.${ext}`;
  if (!outputPath) {
    return path.resolve(process.cwd(), filename || defaultFilename);
  }
  const resolvedPath = path.resolve(outputPath);
  try {
    const stats = await fs.stat(resolvedPath).catch(() => null);
    const isDirectory = stats?.isDirectory() ?? false;
    if (isDirectory || outputPath.endsWith("/") || outputPath.endsWith("\\")) {
      return path.join(resolvedPath, filename || defaultFilename);
    }
    if (path.extname(resolvedPath)) {
      return resolvedPath;
    }
    return path.join(resolvedPath, filename || defaultFilename);
  } catch {
    if (outputPath.endsWith("/") || outputPath.endsWith("\\")) {
      return path.join(resolvedPath, filename || defaultFilename);
    }
    if (!path.extname(outputPath)) {
      return path.join(resolvedPath, filename || defaultFilename);
    }
    return resolvedPath;
  }
}