123 lines
3.1 KiB
TypeScript
123 lines
3.1 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
/**
|
|
* Trail filename utilities
|
|
*
|
|
* Build filenames with configurable prefix-root-suffix pattern.
|
|
* Root is always the timestamp (YYYYMMDDhhmm), prefix and suffix are optional.
|
|
*
|
|
* @module trail/filename
|
|
*/
|
|
|
|
export interface FilenameOptions {
|
|
/** Optional prefix (e.g., "handoff", "log") */
|
|
prefix?: string;
|
|
/** Timestamp root (YYYYMMDDhhmm) - required */
|
|
root: string;
|
|
/** Optional suffix (e.g., session ID, slug) */
|
|
suffix?: string;
|
|
/** File extension (default: "md") */
|
|
ext?: string;
|
|
}
|
|
|
|
/**
|
|
* Build a filename from parts
|
|
*
|
|
* Pattern: [prefix-]root[-suffix].ext
|
|
*
|
|
* @example
|
|
* buildFilename({ root: "202601221430", prefix: "handoff", suffix: "f4b8aa3a" })
|
|
* // => "handoff-202601221430-f4b8aa3a.md"
|
|
*
|
|
* @example
|
|
* buildFilename({ root: "202601221445", suffix: "api-research" })
|
|
* // => "202601221445-api-research.md"
|
|
*
|
|
* @example
|
|
* buildFilename({ root: "202601221500", prefix: "handoff" })
|
|
* // => "handoff-202601221500.md"
|
|
*/
|
|
export function buildFilename(options: FilenameOptions): string {
|
|
const { prefix, root, suffix, ext = "md" } = options;
|
|
|
|
const parts: string[] = [];
|
|
|
|
if (prefix) {
|
|
parts.push(prefix);
|
|
}
|
|
|
|
parts.push(root);
|
|
|
|
if (suffix) {
|
|
parts.push(suffix);
|
|
}
|
|
|
|
return `${parts.join("-")}.${ext}`;
|
|
}
|
|
|
|
/**
|
|
* Parse a trail filename into its components
|
|
*
|
|
* Expects pattern: [prefix-]YYYYMMDDhhmm[-suffix].ext
|
|
* The 12-digit timestamp is the anchor for parsing.
|
|
*/
|
|
export function parseFilename(filename: string): {
|
|
prefix?: string;
|
|
root: string;
|
|
suffix?: string;
|
|
ext: string;
|
|
} | null {
|
|
// Remove extension
|
|
const dotIndex = filename.lastIndexOf(".");
|
|
if (dotIndex === -1) return null;
|
|
|
|
const ext = filename.slice(dotIndex + 1);
|
|
const base = filename.slice(0, dotIndex);
|
|
|
|
// Find the 12-digit timestamp (YYYYMMDDhhmm)
|
|
const timestampMatch = base.match(/(\d{12})/);
|
|
if (!timestampMatch) return null;
|
|
|
|
const root = timestampMatch[1];
|
|
const rootIndex = base.indexOf(root);
|
|
|
|
// Everything before timestamp is prefix
|
|
const beforeRoot = base.slice(0, rootIndex);
|
|
const prefix = beforeRoot.endsWith("-")
|
|
? beforeRoot.slice(0, -1)
|
|
: beforeRoot || undefined;
|
|
|
|
// Everything after timestamp is suffix
|
|
const afterRoot = base.slice(rootIndex + root.length);
|
|
const suffix = afterRoot.startsWith("-")
|
|
? afterRoot.slice(1)
|
|
: afterRoot || undefined;
|
|
|
|
return { prefix: prefix || undefined, root, suffix: suffix || undefined, ext };
|
|
}
|
|
|
|
/**
|
|
* Slugify a string for use in filenames
|
|
*
|
|
* Converts to lowercase, replaces spaces/special chars with hyphens,
|
|
* removes consecutive hyphens, trims hyphens from ends.
|
|
*/
|
|
export function slugify(input: string): string {
|
|
return input
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/[^\w\s-]/g, "") // Remove special chars except hyphens
|
|
.replace(/[\s_]+/g, "-") // Replace spaces/underscores with hyphens
|
|
.replace(/-+/g, "-") // Collapse consecutive hyphens
|
|
.replace(/^-|-$/g, ""); // Trim hyphens from ends
|
|
}
|
|
|
|
/**
|
|
* Truncate session ID for filename use
|
|
*
|
|
* Takes first 8 characters of session ID for brevity while maintaining uniqueness.
|
|
*/
|
|
export function truncateSessionId(sessionId: string): string {
|
|
return sessionId.slice(0, 8);
|
|
}
|