/** * time string parsing utilities for timeout configuration. * supports formats like "10m", "1h30m", "10m12s", "30s". */ // special value indicating timeout is explicitly disabled via --notimeout flag export const TIMEOUT_DISABLED = "none"; // time string regex: supports formats like "10m", "1h30m", "10m12s", "30s" // at least one component (hours, minutes, or seconds) is required const TIME_STRING_REGEX = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/; /** * parse a time string like "10m", "1h30m", "10m12s" into milliseconds. * returns null if the string is not a valid time format. */ export function parseTimeString(input: string): number | null { const match = input.match(TIME_STRING_REGEX); if (!match || (!match[1] && !match[2] && !match[3])) return null; const hours = parseInt(match[1] || "0", 10); const minutes = parseInt(match[2] || "0", 10); const seconds = parseInt(match[3] || "0", 10); return (hours * 3600 + minutes * 60 + seconds) * 1000; } /** * check if a string is a valid time format. */ export function isValidTimeString(input: string): boolean { return parseTimeString(input) !== null; } /** * resolve a user-supplied timeout string into a setTimeout-safe number of * milliseconds, returning null when the input is unusable. * * "unusable" covers three cases that all cause setTimeout to misbehave if * passed through naively: * - unparseable ("abc", "10x") — parseTimeString returns null. * - zero ("0m", "0s") — setTimeout fires immediately, so the run would * look like an insta-fail with the confusing message "timed out after 0m". * - overflow (e.g. "999h") — node clamps any delay above 2^31-1 ms * (~24.8 days) to 1 ms, so a user who asked for "596h" or more would * get a timeout in a single tick instead of the multi-day window they * requested. user almost certainly meant --notimeout. * * the caller should warn and fall back to its own default when this returns * null; the reason is always "the input can't be honored" regardless of * which branch triggered it. */ const TIMEOUT_MAX_MS = 2_147_483_647; export function resolveTimeoutMs(input: string | undefined): number | null { if (!input) return null; const parsed = parseTimeString(input); if (parsed === null || parsed <= 0 || parsed > TIMEOUT_MAX_MS) return null; return parsed; }