UNPKG

simple-task-master

Version:
353 lines 11.2 kB
"use strict"; /** * Utility functions for Simple Task Master */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.validatePath = validatePath; exports.ensureDirectory = ensureDirectory; exports.safeReadFile = safeReadFile; exports.fileExists = fileExists; exports.getFileSize = getFileSize; exports.formatTimestamp = formatTimestamp; exports.parseTimestamp = parseTimestamp; exports.sanitizeFilename = sanitizeFilename; exports.truncateString = truncateString; exports.debounce = debounce; exports.sleep = sleep; exports.retry = retry; exports.readStdin = readStdin; exports.launchEditor = launchEditor; exports.readInput = readInput; const path = __importStar(require("path")); const fs = __importStar(require("fs/promises")); const errors_1 = require("./errors"); /** * Validates that a path is safe and within allowed directories */ function validatePath(filePath) { const normalized = path.normalize(filePath); // Prevent path traversal attacks if (normalized.includes('..')) { throw new errors_1.ValidationError('Path contains directory traversal sequences'); } // Ensure path is absolute or relative to current directory if (path.isAbsolute(normalized)) { throw new errors_1.ValidationError('Absolute paths are not allowed'); } return normalized; } /** * Ensures a directory exists, creating it if necessary */ async function ensureDirectory(dirPath) { try { await fs.access(dirPath); } catch (error) { const nodeError = error; if (nodeError.code === 'ENOENT') { try { await fs.mkdir(dirPath, { recursive: true }); } catch (mkdirError) { throw new errors_1.FileSystemError(`Failed to create directory: ${dirPath}`, mkdirError); } } else { throw new errors_1.FileSystemError(`Failed to access directory: ${dirPath}`, error); } } } /** * Safely reads a file with error handling */ async function safeReadFile(filePath) { try { return await fs.readFile(filePath, 'utf8'); } catch (error) { const nodeError = error; if (nodeError.code === 'ENOENT') { throw new errors_1.FileSystemError(`File not found: ${filePath}`); } throw new errors_1.FileSystemError(`Failed to read file: ${filePath}`, error); } } /** * Checks if a file exists */ async function fileExists(filePath) { try { await fs.access(filePath); return true; } catch { return false; } } /** * Gets the size of a file in bytes */ async function getFileSize(filePath) { try { const stats = await fs.stat(filePath); return stats.size; } catch (error) { throw new errors_1.FileSystemError(`Failed to get file size: ${filePath}`, error); } } /** * Formats a timestamp to ISO string */ function formatTimestamp(date = new Date()) { return date.toISOString(); } /** * Parses an ISO timestamp string */ function parseTimestamp(timestamp) { const date = new Date(timestamp); if (isNaN(date.getTime())) { throw new errors_1.ValidationError(`Invalid timestamp format: ${timestamp}`); } return date; } /** * Sanitizes a string for use as a filename */ function sanitizeFilename(input) { return input .replace(/[<>:"|?*]/g, '-') .replace(/[\x00-\x1f]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .replace(/^-|-$/g, '') .toLowerCase(); } /** * Truncates a string to a maximum length */ function truncateString(str, maxLength) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength - 3) + '...'; } /** * Debounce function to limit how often a function can be called */ function debounce(func, wait) { let timeout; return (...args) => { const later = () => { timeout = undefined; func(...args); }; if (timeout) { clearTimeout(timeout); } timeout = setTimeout(later, wait); }; } /** * Sleep for a specified number of milliseconds */ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Retry a function with exponential backoff */ async function retry(fn, maxAttempts = 3, baseDelay = 1000) { let lastError = new Error('Retry failed'); for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error; if (attempt === maxAttempts) { break; } const delay = baseDelay * Math.pow(2, attempt - 1); await sleep(delay); } } throw lastError; } /** * Reads input from stdin with optional timeout */ async function readStdin(timeoutMs = 30000) { return new Promise((resolve, reject) => { let input = ''; let timeoutId = null; let finished = false; const finish = (result) => { if (finished) return; finished = true; if (timeoutId) { clearTimeout(timeoutId); } // Remove all listeners to clean up try { process.stdin.removeAllListeners('data'); process.stdin.removeAllListeners('end'); process.stdin.removeAllListeners('error'); // Try to pause stdin to stop any pending reads if (process.stdin.readable) { process.stdin.pause(); } } catch { // Ignore cleanup errors - they might occur if stdin is already closed // This prevents EPIPE errors during cleanup } if (result instanceof Error) { reject(result); } else { resolve(result || input.trim()); } }; // Set up timeout timeoutId = setTimeout(() => { finish(new Error(`Stdin input timeout after ${timeoutMs}ms`)); }, timeoutMs); // Handle stdin data process.stdin.setEncoding('utf8'); process.stdin.on('data', (chunk) => { input += chunk; }); process.stdin.on('end', () => { finish(); }); process.stdin.on('error', (error) => { // Handle EPIPE errors gracefully - this occurs when the writing process // has already closed the pipe but we're trying to read from it if (error.code === 'EPIPE') { // EPIPE on stdin read side is less common, but handle it gracefully finish(new Error('Input stream was closed prematurely')); } else { finish(new Error(`Failed to read from stdin: ${error.message}`)); } }); // Start reading process.stdin.resume(); }); } /** * Launches an external editor to get user input */ async function launchEditor(initialContent = '') { const { spawn } = await Promise.resolve().then(() => __importStar(require('child_process'))); const { writeFile, readFile, unlink } = await Promise.resolve().then(() => __importStar(require('fs/promises'))); const { join } = await Promise.resolve().then(() => __importStar(require('path'))); const { tmpdir } = await Promise.resolve().then(() => __importStar(require('os'))); // Create a temporary file const tempFile = join(tmpdir(), `stm-edit-${Date.now()}.md`); try { // Write initial content to temp file await writeFile(tempFile, initialContent, 'utf8'); // Determine editor to use const editor = process.env.EDITOR || process.env.VISUAL || 'vi'; // Launch editor await new Promise((resolve, reject) => { const editorProcess = spawn(editor, [tempFile], { stdio: 'inherit', shell: true }); editorProcess.on('exit', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`Editor exited with code ${code}`)); } }); editorProcess.on('error', (error) => { reject(new Error(`Failed to launch editor: ${error.message}`)); }); }); // Read the edited content const content = await readFile(tempFile, 'utf8'); return content.trim(); } finally { // Clean up temp file try { await unlink(tempFile); } catch { // Ignore cleanup errors } } } /** * Reads input from either a direct value, stdin (if value is "-"), or editor fallback */ async function readInput(value, fallbackToEditor = false, editorPrompt = '', stdinTimeoutMs = 30000) { // If no value provided and fallback to editor is disabled, return undefined if (value === undefined && !fallbackToEditor) { return undefined; } // If value is "-", read from stdin if (value === '-') { try { return await readStdin(stdinTimeoutMs); } catch (error) { if (fallbackToEditor) { // Fallback to editor if stdin fails return await launchEditor(editorPrompt); } throw error; } } // If value is provided and not "-", return it directly if (value !== undefined) { return value; } // If no value provided but fallback is enabled, launch editor if (fallbackToEditor) { return await launchEditor(editorPrompt); } return undefined; } //# sourceMappingURL=utils.js.map