/** * Configuration for the browse extension. * * The `file-browse` section of the shared `bytefly.json` (global then project, * project wins). Everything * is validated and clamped so a typo cannot produce a nonsensical server. */ import { randomInt } from "node:crypto"; import { clampInteger, loadConfigSection } from "../../lib/config.ts"; export interface ServeConfig { /** TCP port on 127.0.0.1; `0` picks one random port fixed for the session, a positive value pins it. */ port: number; /** Open the default browser when the server starts. */ autoOpen: boolean; /** Files larger than this are not rendered inline. */ maxFileBytes: number; /** Images larger than this get an icon instead of a thumbnail. */ maxThumbBytes: number; /** Lines rendered before a text file is truncated on the page. */ maxTextLines: number; /** Directory entries listed before the listing is truncated. */ maxDirEntries: number; /** Render inline image thumbnails in listings. */ thumbnails: boolean; /** Render Markdown files as formatted HTML instead of plain text. */ markdown: boolean; /** Files collected for the quick-open index before it is truncated. */ maxIndexEntries: number; /** Offer the read-only git diff view. */ gitDiff: boolean; /** Unified diff context lines. */ diffContext: number; /** Diff lines rendered before the view is truncated. */ maxDiffLines: number; } export const DEFAULT_CONFIG: ServeConfig = { port: 0, autoOpen: true, maxFileBytes: 1024 * 1024, maxThumbBytes: 5 * 1024 * 1024, maxTextLines: 5000, maxDirEntries: 2000, thumbnails: true, markdown: true, maxIndexEntries: 20000, gitDiff: true, diffContext: 3, maxDiffLines: 4000, }; /** Base of the per-session port range. */ export const PORT_BASE = 4780; /** Number of ports in the per-session range. */ export const PORT_RANGE = 1000; /** A random port in the per-session range `PORT_BASE`–`PORT_BASE + PORT_RANGE - 1`. */ export function randomPort(): number { return PORT_BASE + randomInt(PORT_RANGE); } function bool(value: unknown, fallback: boolean): boolean { return typeof value === "boolean" ? value : fallback; } /** Validate and clamp a raw config object over the defaults. */ export function normalizeConfig( raw: Record | undefined, base: ServeConfig = DEFAULT_CONFIG, ): ServeConfig { if (!raw) return base; return { port: clampInteger(raw.port, base.port, 0, 65535), autoOpen: bool(raw.autoOpen, base.autoOpen), maxFileBytes: clampInteger(raw.maxFileBytes, base.maxFileBytes, 0, 1024 * 1024 * 1024), maxThumbBytes: clampInteger(raw.maxThumbBytes, base.maxThumbBytes, 0, 1024 * 1024 * 1024), maxTextLines: clampInteger(raw.maxTextLines, base.maxTextLines, 1, 1_000_000), maxDirEntries: clampInteger(raw.maxDirEntries, base.maxDirEntries, 1, 1_000_000), thumbnails: bool(raw.thumbnails, base.thumbnails), markdown: bool(raw.markdown, base.markdown), maxIndexEntries: clampInteger(raw.maxIndexEntries, base.maxIndexEntries, 1, 1_000_000), gitDiff: bool(raw.gitDiff, base.gitDiff), diffContext: clampInteger(raw.diffContext, base.diffContext, 0, 100), maxDiffLines: clampInteger(raw.maxDiffLines, base.maxDiffLines, 1, 1_000_000), }; } /** Load the effective config for a working directory. */ export function loadConfig(cwd: string): ServeConfig { return loadConfigSection(cwd, "file-browse", DEFAULT_CONFIG, normalizeConfig); }