envprism 0.1.0-dev.10-1179a3d → 0.1.0-dev.12-c99a64a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matrix-C1pErf9Z.mjs","sources":["../../src/core/mask.ts","../../src/config/schema.ts","../../src/tui/format.ts","../../src/config/resolve.ts","../../src/config/load.ts","../../src/core/base.ts","../../src/core/diff.ts","../../src/core/parse.ts","../../src/core/discover.ts","../../src/core/sections.ts","../../src/core/matrix.ts"],"sourcesContent":["const SECRET_TOKENS = [\n 'SECRET',\n 'TOKEN',\n 'PASSWORD',\n 'PASSWD',\n 'PWD',\n 'KEY',\n 'PRIVATE',\n 'CREDENTIAL',\n 'AUTH',\n 'DSN'\n] as const;\n\n/**\n * Heuristic: should a value with this key be masked by default? Matches any\n * underscore-separated segment of {@link SECRET_TOKENS} (case-insensitive),\n * with carve-outs for false positives like `*_PUBLIC_KEY`, `PUBLIC_*`, and\n * keys whose final segment is `ID` (e.g. `API_KEY_ID` is an identifier, not\n * a secret).\n */\nexport function isSecretKey(\n key: string,\n tokens: readonly string[] = SECRET_TOKENS\n): boolean {\n const upper = key.toUpperCase();\n const segments = upper.split('_').filter(Boolean);\n if (segments.length === 0) return false;\n if (segments[segments.length - 1] === 'ID') return false;\n if (segments[0] === 'PUBLIC') return false;\n if (segments.includes('PUBLIC')) return false;\n\n return segments.some((seg) =>\n tokens.some((token) => seg === token || seg.endsWith(token))\n );\n}\n\n/**\n * Render a masked placeholder that hints at the original value's length\n * without leaking content. Returns `••••` when the input is empty.\n */\nexport function maskValue(value: string): string {\n if (value.length === 0) return '••••';\n const dots = '•'.repeat(Math.min(value.length, 8));\n return value.length > 8 ? `${dots} (${value.length})` : dots;\n}\n","// Config schema for envprism. Kept RGBA-free (no @opentui/core import) so the\n// public `envprism/config` entry and the Node-safe `diff` path never pull in\n// the TUI runtime. The resolved RGBA theme lives in src/tui/theme.ts.\n\nexport type GroupingMode = 'auto' | 'banner' | 'prefix';\n\nexport interface DiscoveryConfig {\n /** Paths scanned when none are passed on the CLI. */\n paths: string[];\n /** Filename suffixes to skip (editor/backup files). Replaces the default. */\n skipSuffixes: string[];\n /** Appended to {@link skipSuffixes} (or its default). */\n skipSuffixesExtra: string[];\n /** Sort `.env.example` first in the discovered file order. */\n exampleFirst: boolean;\n}\n\nexport interface BaseConfig {\n /** Filename treated as the default base when present. */\n name: string;\n /** Ordered basenames tried before falling back to the first file. */\n priority: string[];\n}\n\nexport interface HeuristicsConfig {\n /** Secret-key tokens (case-insensitive). Replaces the built-in list. */\n secretTokens: string[];\n /** Appended to {@link secretTokens} (or its default). */\n secretTokensExtra: string[];\n /** Placeholder regex alternation atoms. Replaces the built-in list. */\n placeholders: string[];\n /** Appended to {@link placeholders} (or its default). */\n placeholdersExtra: string[];\n /** Default TUI grouping. 'auto' picks banner when the base has banners. */\n grouping: GroupingMode;\n}\n\nexport interface DiffConfig {\n /** Emit JSON instead of the text table by default. */\n json: boolean;\n /** Exit code used by `--check` when files drift. */\n checkExitCode: number;\n}\n\n/** Partial hex (`#rrggbb`) overrides for the TUI palette; gaps use defaults. */\nexport interface ThemeConfig {\n fg?: string;\n fgDim?: string;\n fgHeader?: string;\n fgBase?: string;\n fgSection?: string;\n differs?: string;\n extra?: string;\n placeholder?: string;\n modified?: string;\n fgDirty?: string;\n missing?: string;\n focusBg?: string;\n}\n\nexport type ThemeKey = keyof ThemeConfig;\n\nexport interface LayoutConfig {\n keyColWidth: number;\n valueColMin: number;\n sidebarWidth: number;\n rowGap: number;\n cellPadX: number;\n}\n\nexport interface TuiConfig {\n theme: ThemeConfig;\n layout: LayoutConfig;\n undoLimit: number;\n /** Start with secret-suspect values masked (toggle in-app with Ctrl-T). */\n maskSecrets: boolean;\n}\n\n/** Fully-resolved, internal config shape (every field populated). */\nexport interface EnvprismConfig {\n discovery: DiscoveryConfig;\n base: BaseConfig;\n heuristics: HeuristicsConfig;\n diff: DiffConfig;\n tui: TuiConfig;\n}\n\n/** Deep-partial shape authored in envprism.config.{ts,js,mjs,json}. */\nexport interface EnvprismUserConfig {\n discovery?: Partial<DiscoveryConfig>;\n base?: Partial<BaseConfig>;\n heuristics?: Partial<HeuristicsConfig>;\n diff?: Partial<DiffConfig>;\n tui?: {\n theme?: ThemeConfig;\n layout?: Partial<LayoutConfig>;\n undoLimit?: number;\n maskSecrets?: boolean;\n };\n}\n\n/**\n * Built-in defaults — the single source of truth for every tunable value.\n * Mirrors the previously hardcoded constants (mask.ts SECRET_TOKENS,\n * format.ts PLACEHOLDER_RE atoms, discover.ts SKIP_SUFFIXES, theme.ts layout).\n */\nexport const DEFAULT_CONFIG: EnvprismConfig = {\n discovery: {\n paths: ['.'],\n skipSuffixes: ['.swp', '~', '.bak'],\n skipSuffixesExtra: [],\n exampleFirst: true\n },\n base: {\n name: '.env.example',\n priority: []\n },\n heuristics: {\n secretTokens: [\n 'SECRET',\n 'TOKEN',\n 'PASSWORD',\n 'PASSWD',\n 'PWD',\n 'KEY',\n 'PRIVATE',\n 'CREDENTIAL',\n 'AUTH',\n 'DSN'\n ],\n secretTokensExtra: [],\n placeholders: [\n 'todo',\n 'fixme',\n 'changeme',\n 'placeholder',\n 'tbd',\n 'x{3,}',\n 'your[_-]?(secret|key|token|password|api[_-]?key)(_here)?',\n 'replace[_-]?me'\n ],\n placeholdersExtra: [],\n grouping: 'auto'\n },\n diff: {\n json: false,\n checkExitCode: 1\n },\n tui: {\n theme: {},\n layout: {\n keyColWidth: 22,\n valueColMin: 18,\n sidebarWidth: 30,\n rowGap: 0,\n cellPadX: 1\n },\n undoLimit: 50,\n maskSecrets: true\n }\n};\n","import { maskValue } from '@/core/mask.ts';\nimport type { EnvFile, KvEntry } from '@/core/types.ts';\n\nexport const PLACEHOLDER_RE =\n /^(todo|fixme|changeme|placeholder|tbd|x{3,}|your[_-]?(secret|key|token|password|api[_-]?key)(_here)?|replace[_-]?me)$/i;\n\nexport function isPlaceholderValue(\n value: string,\n re: RegExp = PLACEHOLDER_RE\n): boolean {\n const v = value.trim();\n if (v.length === 0) return false;\n return re.test(v);\n}\n\nexport function formatValue(\n value: string | undefined,\n secret: boolean\n): string {\n if (value === undefined) return '';\n if (secret) return maskValue(value);\n return value;\n}\n\nexport function matchesFilter(key: string, filter: string): boolean {\n if (!filter) return true;\n return key.toLowerCase().includes(filter.toLowerCase());\n}\n\nexport function truncate(text: string, width: number): string {\n if (width <= 0) return '';\n if (text.length <= width) return text;\n if (width <= 1) return '…';\n return `${text.slice(0, width - 1)}…`;\n}\n\nexport function findKvEntry(file: EnvFile, key: string): KvEntry | undefined {\n for (const e of file.entries) {\n if (e.kind === 'kv' && e.key === key) return e;\n }\n return undefined;\n}\n","import consola from 'consola';\nimport { defu } from 'defu';\nimport { isSecretKey } from '@/core/mask.ts';\nimport {\n DEFAULT_CONFIG,\n type EnvprismConfig,\n type EnvprismUserConfig,\n type GroupingMode,\n type ThemeConfig,\n type ThemeKey\n} from '@/config/schema.ts';\nimport { isPlaceholderValue } from '@tui/format.ts';\n\n/** Hex source of truth for the TUI palette (resolved to RGBA in theme.ts). */\nexport const DEFAULT_THEME_HEX: Record<ThemeKey, string> = {\n fg: '#cccccc',\n fgDim: '#666666',\n fgHeader: '#ffffff',\n fgBase: '#82aaff',\n fgSection: '#82aaff',\n differs: '#ffd866',\n extra: '#ffd866',\n placeholder: '#ffd866',\n modified: '#7fce6a',\n fgDirty: '#7fce6a',\n missing: '#ff6b6b',\n focusBg: '#3a3f4b'\n};\n\nconst HEX_RE = /^#[0-9a-fA-F]{6}$/;\n\n/**\n * Merge user hex overrides onto {@link DEFAULT_THEME_HEX}. Invalid hex values\n * warn (light runtime guard) and fall back to the default. RGBA-free so it is\n * unit-testable under Node without loading the TUI runtime; theme.ts wraps the\n * result with RGBA.fromHex.\n */\nexport function resolveThemeHex(\n theme: ThemeConfig = {},\n warn: (msg: string) => void = consola.warn\n): Record<ThemeKey, string> {\n const out = { ...DEFAULT_THEME_HEX };\n for (const key of Object.keys(DEFAULT_THEME_HEX) as ThemeKey[]) {\n const override = theme[key];\n if (override === undefined) continue;\n if (HEX_RE.test(override)) {\n out[key] = override;\n } else {\n warn(`envprism: ignoring invalid hex for theme.${key}: \"${override}\"`);\n }\n }\n return out;\n}\n\n/**\n * Resolve a replace-or-extend list field. An explicit `replace` list wins over\n * the built-in `fallback`; `extra` is always appended to whatever the current\n * list is. De-duplicated so repeated entries are harmless.\n */\nfunction pickList(\n replace: string[] | undefined,\n extra: string[] | undefined,\n fallback: string[]\n): string[] {\n const base = replace ?? fallback;\n return [...new Set([...base, ...(extra ?? [])])];\n}\n\n/**\n * Merge a deep-partial user config onto {@link DEFAULT_CONFIG}. Scalars and\n * plain objects use defu (user wins, gaps fall back). List fields are handled\n * manually because defu concatenates arrays — wrong for our replace semantics:\n * `*` lists replace the default, `*Extra` lists append. The folded result puts\n * everything into the canonical field and leaves `*Extra` empty.\n */\nexport function mergeConfig(user: EnvprismUserConfig = {}): EnvprismConfig {\n return {\n discovery: {\n paths: user.discovery?.paths ?? DEFAULT_CONFIG.discovery.paths,\n skipSuffixes: pickList(\n user.discovery?.skipSuffixes,\n user.discovery?.skipSuffixesExtra,\n DEFAULT_CONFIG.discovery.skipSuffixes\n ),\n skipSuffixesExtra: [],\n exampleFirst:\n user.discovery?.exampleFirst ?? DEFAULT_CONFIG.discovery.exampleFirst\n },\n base: {\n name: user.base?.name ?? DEFAULT_CONFIG.base.name,\n priority: user.base?.priority ?? DEFAULT_CONFIG.base.priority\n },\n heuristics: {\n secretTokens: pickList(\n user.heuristics?.secretTokens,\n user.heuristics?.secretTokensExtra,\n DEFAULT_CONFIG.heuristics.secretTokens\n ),\n secretTokensExtra: [],\n placeholders: pickList(\n user.heuristics?.placeholders,\n user.heuristics?.placeholdersExtra,\n DEFAULT_CONFIG.heuristics.placeholders\n ),\n placeholdersExtra: [],\n grouping: user.heuristics?.grouping ?? DEFAULT_CONFIG.heuristics.grouping\n },\n diff: defu(user.diff, DEFAULT_CONFIG.diff),\n tui: {\n theme: defu(user.tui?.theme, DEFAULT_CONFIG.tui.theme),\n layout: defu(user.tui?.layout, DEFAULT_CONFIG.tui.layout),\n undoLimit: user.tui?.undoLimit ?? DEFAULT_CONFIG.tui.undoLimit,\n maskSecrets: user.tui?.maskSecrets ?? DEFAULT_CONFIG.tui.maskSecrets\n }\n };\n}\n\nexport interface ResolvedHeuristics {\n isSecretKey(key: string): boolean;\n isPlaceholderValue(value: string): boolean;\n grouping: GroupingMode;\n}\n\n/**\n * Compile the resolved heuristics config into ready-to-call matchers. Secret\n * tokens are upper-cased (the matcher upper-cases keys); placeholder atoms are\n * compiled into a single case-insensitive alternation. RGBA-free.\n */\nexport function resolveHeuristics(c: EnvprismConfig): ResolvedHeuristics {\n const tokens = c.heuristics.secretTokens.map((t) => t.toUpperCase());\n const placeholderRe = new RegExp(\n `^(${c.heuristics.placeholders.join('|')})$`,\n 'i'\n );\n return {\n isSecretKey: (key) => isSecretKey(key, tokens),\n isPlaceholderValue: (value) => isPlaceholderValue(value, placeholderRe),\n grouping: c.heuristics.grouping\n };\n}\n","import { existsSync } from 'node:fs';\nimport { loadConfig } from 'c12';\nimport { dirname, join } from 'pathe';\nimport { mergeConfig } from '@/config/resolve.ts';\nimport type { EnvprismConfig, EnvprismUserConfig } from '@/config/schema.ts';\n\nconst CONFIG_EXTS = ['ts', 'js', 'mjs', 'json'] as const;\n\n/**\n * Walk up from `start` looking for envprism.config.{ts,js,mjs,json}. c12 does\n * not climb ancestors itself, so we resolve the file path here and hand it to\n * c12 explicitly. Returns the first match or undefined at the filesystem root.\n */\nfunction findConfigUp(start: string): string | undefined {\n let dir = start;\n for (;;) {\n for (const ext of CONFIG_EXTS) {\n const candidate = join(dir, `envprism.config.${ext}`);\n if (existsSync(candidate)) return candidate;\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\nexport interface LoadOptions {\n /** Directory to start the walk-up search from (default: process.cwd()). */\n cwd?: string;\n /** Explicit config file path; overrides walk-up and the env var. */\n configFile?: string;\n}\n\nexport interface LoadedConfig {\n config: EnvprismConfig;\n /** Absolute path of the config file that was loaded, if any. */\n configFile: string | undefined;\n /** Directory the search ran from. */\n cwd: string;\n}\n\n/**\n * Load and resolve the envprism config. Precedence for *where* to look:\n * explicit `configFile` > `ENVPRISM_CONFIG` env var > walk-up from cwd.\n * c12 handles the walk-up (envprism.config.{ts,js,mjs,json}); we keep\n * rc/global/dotenv off and apply our own merge (defu deep-merges defaults\n * into user lists, which we don't want — see resolve.ts).\n */\nexport async function loadEnvprismConfig(\n options: LoadOptions = {}\n): Promise<LoadedConfig> {\n const envConfig = process.env.ENVPRISM_CONFIG;\n const startCwd = options.cwd ?? process.cwd();\n // Precedence: explicit flag > env var > walk-up from cwd.\n const resolved = options.configFile ?? envConfig ?? findConfigUp(startCwd);\n const cwd = resolved ? dirname(resolved) : startCwd;\n\n if (!resolved) {\n return { config: mergeConfig({}), configFile: undefined, cwd };\n }\n\n const { config } = await loadConfig<EnvprismUserConfig>({\n name: 'envprism',\n cwd,\n configFile: resolved,\n rcFile: false,\n globalRc: false,\n dotenv: false\n });\n\n return {\n config: mergeConfig(config ?? {}),\n configFile: resolved,\n cwd\n };\n}\n","import { basename } from 'pathe';\nimport type { EnvFile } from '@/core/types.ts';\n\nexport interface BaseOptions {\n /** Filename treated as the default base when present (default `.env.example`). */\n name?: string;\n /** Ordered basenames tried before falling back to the first file. */\n priority?: readonly string[];\n}\n\n/**\n * Resolve which {@link EnvFile} acts as the base (reference) for diff. Order:\n *\n * 1. `override` path argument, matched by full path or basename.\n * 2. `options.name` (default `.env.example`) if present.\n * 3. The first file matching `options.priority`, in priority order.\n * 4. The first file (already sorted by {@link discoverEnvFiles}).\n *\n * Returns `null` if `files` is empty.\n */\nexport function resolveBase(\n files: EnvFile[],\n override?: string,\n options: BaseOptions = {}\n): EnvFile | null {\n if (files.length === 0) return null;\n\n if (override) {\n const match = files.find(\n (f) => f.path === override || basename(f.path) === override\n );\n if (!match) {\n throw new Error(\n `--base ${override} did not match any discovered env file`\n );\n }\n return match;\n }\n\n const name = options.name ?? '.env.example';\n const example = files.find((f) => basename(f.path) === name);\n if (example) return example;\n\n for (const candidate of options.priority ?? []) {\n const match = files.find((f) => basename(f.path) === candidate);\n if (match) return match;\n }\n\n return files[0] ?? null;\n}\n","import { basename } from 'pathe';\nimport type { Matrix, CellState } from '@/core/matrix.ts';\nimport type { EnvFile } from '@/core/types.ts';\n\nexport interface DiffReport {\n base: string;\n /** Compared files, excluding the base. */\n files: DiffFileReport[];\n /** True when no non-base cell is `differs`, `missing`, or `extra`. */\n inSync: boolean;\n}\n\nexport interface DiffFileReport {\n path: string;\n keys: Record<string, CellState>;\n /** Number of keys with state !== 'same'. */\n drift: number;\n}\n\nconst DRIFT_STATES: ReadonlySet<CellState> = new Set([\n 'differs',\n 'missing',\n 'extra'\n]);\n\nexport function computeDiff(matrix: Matrix): DiffReport {\n const others = matrix.files.filter((f) => f !== matrix.base);\n const files = others.map((f) => buildFileReport(matrix, f));\n return {\n base: matrix.base.path,\n files,\n inSync: files.every((f) => f.drift === 0)\n };\n}\n\nfunction buildFileReport(matrix: Matrix, file: EnvFile): DiffFileReport {\n const keys: Record<string, CellState> = {};\n let drift = 0;\n for (const key of matrix.keys) {\n const { state } = matrix.cell(key, file);\n keys[key] = state;\n if (DRIFT_STATES.has(state)) drift++;\n }\n return { path: file.path, keys, drift };\n}\n\nexport function formatDiffText(report: DiffReport): string {\n const baseName = basename(report.base);\n const otherNames = report.files.map((f) => basename(f.path));\n const lines: string[] = [];\n lines.push(`Base: ${baseName} (vs. ${otherNames.join(', ')})`);\n lines.push('');\n\n if (report.files.length === 0) {\n lines.push('No other env files to compare.');\n return lines.join('\\n') + '\\n';\n }\n\n const driftKeys = new Set<string>();\n for (const f of report.files) {\n for (const [k, s] of Object.entries(f.keys)) {\n if (DRIFT_STATES.has(s)) driftKeys.add(k);\n }\n }\n\n if (driftKeys.size === 0) {\n lines.push('All env files are in sync with the base.');\n return lines.join('\\n') + '\\n';\n }\n\n const keyWidth = Math.max(3, ...[...driftKeys].map((k) => k.length));\n const colWidth = Math.max(12, ...otherNames.map((n) => n.length));\n\n lines.push(\n formatRow('KEY', otherNames, keyWidth, colWidth, (n) => n.padEnd(colWidth))\n );\n for (const key of [...driftKeys].sort()) {\n const cells = report.files.map((f) => stateLabel(f.keys[key] ?? 'missing'));\n lines.push(\n formatRow(key, cells, keyWidth, colWidth, (n) => n.padEnd(colWidth))\n );\n }\n\n lines.push('');\n const totalDrift = report.files.reduce((sum, f) => sum + f.drift, 0);\n lines.push(\n `${driftKeys.size} key(s) differ across ${report.files.length} file(s) (${totalDrift} cell drift).`\n );\n\n return lines.join('\\n') + '\\n';\n}\n\nfunction formatRow(\n key: string,\n cells: string[],\n keyWidth: number,\n colWidth: number,\n pad: (s: string) => string\n): string {\n return [key.padEnd(keyWidth), ...cells.map(pad)].join(' ');\n}\n\nfunction stateLabel(state: CellState): string {\n switch (state) {\n case 'same':\n return '— same';\n case 'differs':\n return '≠ differs';\n case 'missing':\n return '✗ missing';\n case 'extra':\n return '★ extra';\n case 'base':\n return '· base';\n }\n}\n","import type { EnvEntry, EnvFile, KvEntry, Quoting } from '@/core/types.ts';\n\nconst KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * Parse a `.env` file into a structured, round-trippable representation.\n * Each entry stores its original raw line so the serializer can emit it\n * byte-for-byte when nothing changes.\n */\nexport function parseEnv(source: string, path = ''): EnvFile {\n const trailingNewline = source.endsWith('\\n');\n const body = trailingNewline ? source.slice(0, -1) : source;\n const lines = body.length === 0 ? [] : body.split('\\n');\n\n const entries: EnvEntry[] = [];\n for (const raw of lines) {\n entries.push(parseLine(raw));\n }\n\n return { path, entries, trailingNewline };\n}\n\nfunction parseLine(raw: string): EnvEntry {\n if (raw.trim().length === 0) {\n return { kind: 'blank', raw };\n }\n\n // Comment line: leading whitespace then `#`.\n const trimmedStart = raw.replace(/^\\s+/, '');\n if (trimmedStart.startsWith('#')) {\n return { kind: 'comment', raw };\n }\n\n const kv = tryParseKv(raw);\n if (kv) return kv;\n\n // Unrecognised line — treat as a comment-like passthrough so round-trip\n // still works. Diff/matrix code ignores non-kv entries.\n return { kind: 'comment', raw };\n}\n\nfunction tryParseKv(raw: string): KvEntry | null {\n let rest = raw;\n // Optional leading whitespace is preserved in `raw`; the structured key\n // identifier itself is captured separately so we can rebuild the line.\n const leadingWs = rest.match(/^[ \\t]*/)?.[0] ?? '';\n rest = rest.slice(leadingWs.length);\n\n let exportPrefix = false;\n if (rest.startsWith('export ')) {\n exportPrefix = true;\n rest = rest.slice('export '.length).replace(/^[ \\t]*/, '');\n }\n\n const eqIdx = rest.indexOf('=');\n if (eqIdx < 0) return null;\n\n const key = rest.slice(0, eqIdx).trimEnd();\n if (!KEY_RE.test(key)) return null;\n\n const after = rest.slice(eqIdx + 1);\n const valueStart = after.replace(/^[ \\t]*/, '');\n const valueLeadingWs = after.slice(0, after.length - valueStart.length);\n\n const parsed = parseValue(valueStart);\n if (!parsed) return null;\n\n // Preserve trailing whitespace before any inline comment so round-trip\n // matches the source exactly (we don't need to model it separately — `raw`\n // is what gets emitted).\n void leadingWs;\n void valueLeadingWs;\n\n return {\n kind: 'kv',\n key,\n rawValue: parsed.rawValue,\n value: parsed.value,\n quoting: parsed.quoting,\n exportPrefix,\n inlineComment: parsed.inlineComment,\n raw\n };\n}\n\ninterface ParsedValue {\n rawValue: string;\n value: string;\n quoting: Quoting;\n inlineComment: string;\n}\n\nfunction parseValue(input: string): ParsedValue | null {\n if (input.length === 0) {\n return { rawValue: '', value: '', quoting: 'none', inlineComment: '' };\n }\n\n const first = input[0];\n if (first === '\"' || first === \"'\") {\n const close = findClosingQuote(input, first);\n if (close < 0) return null;\n const rawValue = input.slice(1, close);\n const value = first === '\"' ? decodeDoubleQuoted(rawValue) : rawValue;\n const tail = input.slice(close + 1);\n const inlineComment = extractInlineComment(tail);\n return {\n rawValue,\n value,\n quoting: first === '\"' ? 'double' : 'single',\n inlineComment\n };\n }\n\n // Unquoted: value runs until ` #` or end-of-line, trailing whitespace\n // belongs to the inline-comment slot (so round-trip is preserved via `raw`).\n const hashIdx = findUnquotedCommentStart(input);\n const valuePart = (hashIdx < 0 ? input : input.slice(0, hashIdx)).trimEnd();\n const inlineComment = hashIdx < 0 ? '' : input.slice(valuePart.length);\n return {\n rawValue: valuePart,\n value: valuePart,\n quoting: 'none',\n inlineComment\n };\n}\n\nfunction findClosingQuote(input: string, quote: '\"' | \"'\"): number {\n for (let i = 1; i < input.length; i++) {\n const ch = input[i];\n if (quote === '\"' && ch === '\\\\') {\n i++;\n continue;\n }\n if (ch === quote) return i;\n }\n return -1;\n}\n\nfunction findUnquotedCommentStart(input: string): number {\n for (let i = 0; i < input.length; i++) {\n if (input[i] !== '#') continue;\n if (i === 0) return i;\n const prev = input[i - 1];\n if (prev === ' ' || prev === '\\t') return i;\n }\n return -1;\n}\n\nfunction extractInlineComment(tail: string): string {\n // Tail is everything after the closing quote. It is either empty, pure\n // whitespace, or whitespace + `#...`. Preserve it verbatim.\n return tail;\n}\n\nfunction decodeDoubleQuoted(raw: string): string {\n return raw.replace(/\\\\(.)/g, (_, ch: string) => {\n switch (ch) {\n case 'n':\n return '\\n';\n case 'r':\n return '\\r';\n case 't':\n return '\\t';\n case '\\\\':\n return '\\\\';\n case '\"':\n return '\"';\n default:\n return `\\\\${ch}`;\n }\n });\n}\n","import { readdir, readFile, stat } from 'node:fs/promises';\nimport { basename, join } from 'pathe';\nimport { parseEnv } from '@/core/parse.ts';\nimport type { EnvFile } from '@/core/types.ts';\n\nconst SKIP_SUFFIXES = ['.swp', '~', '.bak'];\n\nexport interface DiscoverOptions {\n /** Filename suffixes to skip (default: editor/backup suffixes). */\n skipSuffixes?: readonly string[];\n /** Sort `.env.example` first in the result (default: true). */\n exampleFirst?: boolean;\n}\n\n/**\n * Discover `.env*` files in the given path(s). Each path may be a directory\n * (glob-like discovery happens at its top level) or an explicit file. Editor\n * and backup files are skipped.\n *\n * Files are returned sorted: `.env.example` first if present (unless\n * `exampleFirst` is false), then the rest alphabetically. Base-resolution\n * lives in `base.ts` and depends on this order.\n */\nexport async function discoverEnvFiles(\n paths: string[],\n options: DiscoverOptions = {}\n): Promise<EnvFile[]> {\n const skipSuffixes = options.skipSuffixes ?? SKIP_SUFFIXES;\n const exampleFirst = options.exampleFirst ?? true;\n const filePaths = new Set<string>();\n\n for (const p of paths) {\n const info = await stat(p);\n if (info.isDirectory()) {\n const entries = await readdir(p);\n for (const name of entries) {\n if (!looksLikeEnvFile(name, skipSuffixes)) continue;\n filePaths.add(join(p, name));\n }\n } else {\n filePaths.add(p);\n }\n }\n\n const sorted = [...filePaths].sort((a, b) =>\n envPathOrder(a, b, exampleFirst)\n );\n const files: EnvFile[] = [];\n for (const filePath of sorted) {\n const source = await readFile(filePath, 'utf8');\n files.push(parseEnv(source, filePath));\n }\n return files;\n}\n\nfunction looksLikeEnvFile(\n name: string,\n skipSuffixes: readonly string[]\n): boolean {\n if (!name.startsWith('.env')) return false;\n if (skipSuffixes.some((s) => name.endsWith(s))) return false;\n return true;\n}\n\nfunction envPathOrder(a: string, b: string, exampleFirst: boolean): number {\n const an = basename(a);\n const bn = basename(b);\n if (exampleFirst) {\n if (an === '.env.example') return -1;\n if (bn === '.env.example') return 1;\n }\n return an.localeCompare(bn);\n}\n","import type { EnvEntry, EnvFile } from '@/core/types.ts';\n\nconst SEP_CHARS = new Set(['=', '-', '#', '~', '*']);\nconst SPACE_CHARS = new Set([' ', '\\t']);\n\n/**\n * Walk the base file in source order and decide which section each kv entry\n * belongs to. Returns a `key → section` map; keys with no inferred section\n * are absent from the map.\n *\n * A \"section header\" is one of:\n * 1. An inline banner: `# === Section name ===` (or `---`, `~~~`, `***`).\n * 2. A block banner — a single comment line `# Section name` whose\n * immediately preceding or following comment line is purely decorative\n * (e.g. `# ===========================`).\n *\n * The detected name applies to every subsequent kv entry until the next\n * detected section header.\n */\nexport function computeSections(base: EnvFile): Map<string, string> {\n const out = new Map<string, string>();\n let current: string | null = null;\n for (let i = 0; i < base.entries.length; i++) {\n const name = detectSectionName(base.entries, i);\n if (name !== null) current = name;\n const e = base.entries[i]!;\n if (e.kind === 'kv' && current) out.set(e.key, current);\n }\n return out;\n}\n\nfunction detectSectionName(entries: EnvEntry[], idx: number): string | null {\n const e = entries[idx];\n if (!e || e.kind !== 'comment') return null;\n if (isDecorativeLine(e.raw)) return null;\n\n const inline = parseInlineBanner(e.raw);\n if (inline !== null) return inline;\n\n const text = stripCommentPrefix(e.raw);\n if (!text) return null;\n\n if (isDecorative(entries[idx - 1]) || isDecorative(entries[idx + 1])) {\n return text;\n }\n return null;\n}\n\nfunction isDecorative(e: EnvEntry | undefined): boolean {\n if (!e || e.kind !== 'comment') return false;\n return isDecorativeLine(e.raw);\n}\n\n/**\n * True for comment lines whose body is nothing but separator chars and\n * whitespace (e.g. \"# ======\", \"###\", \"# -=-=-=-\").\n *\n * Implemented with a manual scan rather than a regex with overlapping\n * `\\s*` / `[\\s…]+` groups, which CodeQL flags as ReDoS-prone.\n */\nfunction isDecorativeLine(raw: string): boolean {\n const start = skipSpaces(raw, 0);\n if (start >= raw.length || raw[start] !== '#') return false;\n let i = start + 1;\n let sawSeparator = false;\n while (i < raw.length) {\n const ch = raw[i]!;\n if (SEP_CHARS.has(ch)) sawSeparator = true;\n else if (!SPACE_CHARS.has(ch)) return false;\n i++;\n }\n return sawSeparator;\n}\n\n/**\n * Parse a single-line inline banner like `# === Name ===` (also `---`,\n * `~~~`, `***`) and return the inner name, or null if the line isn't an\n * inline banner.\n *\n * Manual scan, again to avoid the overlapping `\\s*` regex backtracking\n * CodeQL warned about.\n */\nfunction parseInlineBanner(raw: string): string | null {\n const start = skipSpaces(raw, 0);\n if (start >= raw.length || raw[start] !== '#') return null;\n\n // After '#', skip spaces, then read 2+ separator chars (single char repeated).\n let i = skipSpaces(raw, start + 1);\n if (i >= raw.length || !SEP_CHARS.has(raw[i]!)) return null;\n let leadCount = 0;\n while (i < raw.length && SEP_CHARS.has(raw[i]!)) {\n leadCount++;\n i++;\n }\n if (leadCount < 2) return null;\n\n // Walk from the end inward to find the trailing separator run.\n let j = raw.length;\n while (j > i && SPACE_CHARS.has(raw[j - 1]!)) j--;\n if (j <= i || !SEP_CHARS.has(raw[j - 1]!)) return null;\n let trailCount = 0;\n while (j > i && SEP_CHARS.has(raw[j - 1]!)) {\n trailCount++;\n j--;\n }\n if (trailCount < 2) return null;\n\n // Between the two separator runs lives the name (after stripping spaces).\n const inner = raw.slice(i, j).trim();\n return inner.length > 0 ? inner : null;\n}\n\nfunction stripCommentPrefix(raw: string): string {\n // Leading whitespace, then one or more '#', then whitespace, then content.\n let i = skipSpaces(raw, 0);\n while (i < raw.length && raw[i] === '#') i++;\n i = skipSpaces(raw, i);\n // Trim trailing whitespace and decorative chars.\n let j = raw.length;\n while (\n j > i &&\n (SPACE_CHARS.has(raw[j - 1]!) || SEP_CHARS.has(raw[j - 1]!))\n ) {\n j--;\n }\n return raw.slice(i, j);\n}\n\nfunction skipSpaces(raw: string, from: number): number {\n let i = from;\n while (i < raw.length && SPACE_CHARS.has(raw[i]!)) i++;\n return i;\n}\n","import { computeSections } from '@/core/sections.ts';\nimport type { EnvFile, KvEntry } from '@/core/types.ts';\n\nexport type CellState = 'same' | 'differs' | 'missing' | 'extra' | 'base';\n\nexport interface Cell {\n state: CellState;\n /** Decoded value if present in this file, else `undefined`. */\n value: string | undefined;\n}\n\nexport interface Matrix {\n /** Union of all keys across all files, base keys first (in source order). */\n keys: string[];\n /** Files in display order; base is included. */\n files: EnvFile[];\n base: EnvFile;\n cell(key: string, file: EnvFile): Cell;\n /**\n * Section name inferred from the base file's comment banners, or\n * `undefined` for keys outside any section (and for keys only present in\n * non-base files).\n */\n sectionOf(key: string): string | undefined;\n}\n\nexport function buildMatrix(files: EnvFile[], base: EnvFile): Matrix {\n const keys = collectKeys(files, base);\n const lookups = new Map<EnvFile, Map<string, KvEntry>>();\n for (const file of files) {\n lookups.set(file, indexKv(file));\n }\n const baseIndex = lookups.get(base);\n if (!baseIndex) {\n throw new Error('base file is not in the files list');\n }\n const sections = computeSections(base);\n\n return {\n keys,\n files,\n base,\n sectionOf(key) {\n return sections.get(key);\n },\n cell(key, file) {\n const ownIndex = lookups.get(file);\n if (!ownIndex) {\n throw new Error(`file not in matrix: ${file.path}`);\n }\n const own = ownIndex.get(key);\n const baseEntry = baseIndex.get(key);\n\n if (file === base) {\n return {\n state: own ? 'base' : 'missing',\n value: own?.value\n };\n }\n\n if (!own && !baseEntry) {\n return { state: 'missing', value: undefined };\n }\n if (!own) {\n return { state: 'missing', value: undefined };\n }\n if (!baseEntry) {\n return { state: 'extra', value: own.value };\n }\n return {\n state: own.value === baseEntry.value ? 'same' : 'differs',\n value: own.value\n };\n }\n };\n}\n\nfunction collectKeys(files: EnvFile[], base: EnvFile): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n\n // Base file keys first in their authored order so the matrix view tracks\n // the .env.example layout most users curate.\n for (const e of base.entries) {\n if (e.kind === 'kv' && !seen.has(e.key)) {\n seen.add(e.key);\n out.push(e.key);\n }\n }\n // Then any extras only present in non-base files, in alpha order so the\n // result is deterministic.\n const extras = new Set<string>();\n for (const file of files) {\n if (file === base) continue;\n for (const e of file.entries) {\n if (e.kind === 'kv' && !seen.has(e.key)) {\n extras.add(e.key);\n }\n }\n }\n for (const k of [...extras].sort()) out.push(k);\n return out;\n}\n\nfunction indexKv(file: EnvFile): Map<string, KvEntry> {\n const m = new Map<string, KvEntry>();\n for (const e of file.entries) {\n if (e.kind === 'kv') m.set(e.key, e);\n }\n return m;\n}\n"],"names":["inlineComment"],"mappings":";;;;;;AAAA,MAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AASO,SAAS,YACd,KACA,SAA4B,eACnB;AACT,QAAM,QAAQ,IAAI,YAAA;AAClB,QAAM,WAAW,MAAM,MAAM,GAAG,EAAE,OAAO,OAAO;AAChD,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI,SAAS,SAAS,SAAS,CAAC,MAAM,KAAM,QAAO;AACnD,MAAI,SAAS,CAAC,MAAM,SAAU,QAAO;AACrC,MAAI,SAAS,SAAS,QAAQ,EAAG,QAAO;AAExC,SAAO,SAAS;AAAA,IAAK,CAAC,QACpB,OAAO,KAAK,CAAC,UAAU,QAAQ,SAAS,IAAI,SAAS,KAAK,CAAC;AAAA,EAAA;AAE/D;AAMO,SAAS,UAAU,OAAuB;AAC/C,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,IAAI,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC,CAAC;AACjD,SAAO,MAAM,SAAS,IAAI,GAAG,IAAI,KAAK,MAAM,MAAM,MAAM;AAC1D;AC8DO,MAAM,iBAAiC;AAAA,EAC5C,WAAW;AAAA,IACT,OAAO,CAAC,GAAG;AAAA,IACX,cAAc,CAAC,QAAQ,KAAK,MAAM;AAAA,IAClC,mBAAmB,CAAA;AAAA,IACnB,cAAc;AAAA,EAAA;AAAA,EAEhB,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,UAAU,CAAA;AAAA,EAAC;AAAA,EAEb,YAAY;AAAA,IACV,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,mBAAmB,CAAA;AAAA,IACnB,cAAc;AAAA,MACZ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,mBAAmB,CAAA;AAAA,IACnB,UAAU;AAAA,EAAA;AAAA,EAEZ,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,eAAe;AAAA,EAAA;AAAA,EAEjB,KAAK;AAAA,IACH,OAAO,CAAA;AAAA,IACP,QAAQ;AAAA,MACN,aAAa;AAAA,MACb,aAAa;AAAA,MACb,cAAc;AAAA,MACd,QAAQ;AAAA,MACR,UAAU;AAAA,IAAA;AAAA,IAEZ,WAAW;AAAA,IACX,aAAa;AAAA,EAAA;AAEjB;AC7JO,MAAM,iBACX;AAEK,SAAS,mBACd,OACA,KAAa,gBACJ;AACT,QAAM,IAAI,MAAM,KAAA;AAChB,MAAI,EAAE,WAAW,EAAG,QAAO;AAC3B,SAAO,GAAG,KAAK,CAAC;AAClB;AAEO,SAAS,YACd,OACA,QACQ;AACR,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAQ,QAAO,UAAU,KAAK;AAClC,SAAO;AACT;AAEO,SAAS,cAAc,KAAa,QAAyB;AAClE,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,IAAI,YAAA,EAAc,SAAS,OAAO,aAAa;AACxD;AAEO,SAAS,SAAS,MAAc,OAAuB;AAC5D,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,KAAK,UAAU,MAAO,QAAO;AACjC,MAAI,SAAS,EAAG,QAAO;AACvB,SAAO,GAAG,KAAK,MAAM,GAAG,QAAQ,CAAC,CAAC;AACpC;AAEO,SAAS,YAAY,MAAe,KAAkC;AAC3E,aAAW,KAAK,KAAK,SAAS;AAC5B,QAAI,EAAE,SAAS,QAAQ,EAAE,QAAQ,IAAK,QAAO;AAAA,EAC/C;AACA,SAAO;AACT;AC3BO,MAAM,oBAA8C;AAAA,EACzD,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,SAAS;AAAA,EACT,OAAO;AAAA,EACP,aAAa;AAAA,EACb,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AACX;AAEA,MAAM,SAAS;AAQR,SAAS,gBACd,QAAqB,CAAA,GACrB,OAA8B,QAAQ,MACZ;AAC1B,QAAM,MAAM,EAAE,GAAG,kBAAA;AACjB,aAAW,OAAO,OAAO,KAAK,iBAAiB,GAAiB;AAC9D,UAAM,WAAW,MAAM,GAAG;AAC1B,QAAI,aAAa,OAAW;AAC5B,QAAI,OAAO,KAAK,QAAQ,GAAG;AACzB,UAAI,GAAG,IAAI;AAAA,IACb,OAAO;AACL,WAAK,4CAA4C,GAAG,MAAM,QAAQ,GAAG;AAAA,IACvE;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,SACP,SACA,OACA,UACU;AACV,QAAM,OAAO,WAAW;AACxB,SAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,MAAM,GAAI,SAAS,CAAA,CAAG,CAAC,CAAC;AACjD;AASO,SAAS,YAAY,OAA2B,IAAoB;AACzE,SAAO;AAAA,IACL,WAAW;AAAA,MACT,OAAO,KAAK,WAAW,SAAS,eAAe,UAAU;AAAA,MACzD,cAAc;AAAA,QACZ,KAAK,WAAW;AAAA,QAChB,KAAK,WAAW;AAAA,QAChB,eAAe,UAAU;AAAA,MAAA;AAAA,MAE3B,mBAAmB,CAAA;AAAA,MACnB,cACE,KAAK,WAAW,gBAAgB,eAAe,UAAU;AAAA,IAAA;AAAA,IAE7D,MAAM;AAAA,MACJ,MAAM,KAAK,MAAM,QAAQ,eAAe,KAAK;AAAA,MAC7C,UAAU,KAAK,MAAM,YAAY,eAAe,KAAK;AAAA,IAAA;AAAA,IAEvD,YAAY;AAAA,MACV,cAAc;AAAA,QACZ,KAAK,YAAY;AAAA,QACjB,KAAK,YAAY;AAAA,QACjB,eAAe,WAAW;AAAA,MAAA;AAAA,MAE5B,mBAAmB,CAAA;AAAA,MACnB,cAAc;AAAA,QACZ,KAAK,YAAY;AAAA,QACjB,KAAK,YAAY;AAAA,QACjB,eAAe,WAAW;AAAA,MAAA;AAAA,MAE5B,mBAAmB,CAAA;AAAA,MACnB,UAAU,KAAK,YAAY,YAAY,eAAe,WAAW;AAAA,IAAA;AAAA,IAEnE,MAAM,KAAK,KAAK,MAAM,eAAe,IAAI;AAAA,IACzC,KAAK;AAAA,MACH,OAAO,KAAK,KAAK,KAAK,OAAO,eAAe,IAAI,KAAK;AAAA,MACrD,QAAQ,KAAK,KAAK,KAAK,QAAQ,eAAe,IAAI,MAAM;AAAA,MACxD,WAAW,KAAK,KAAK,aAAa,eAAe,IAAI;AAAA,MACrD,aAAa,KAAK,KAAK,eAAe,eAAe,IAAI;AAAA,IAAA;AAAA,EAC3D;AAEJ;AAaO,SAAS,kBAAkB,GAAuC;AACvE,QAAM,SAAS,EAAE,WAAW,aAAa,IAAI,CAAC,MAAM,EAAE,aAAa;AACnE,QAAM,gBAAgB,IAAI;AAAA,IACxB,KAAK,EAAE,WAAW,aAAa,KAAK,GAAG,CAAC;AAAA,IACxC;AAAA,EAAA;AAEF,SAAO;AAAA,IACL,aAAa,CAAC,QAAQ,YAAY,KAAK,MAAM;AAAA,IAC7C,oBAAoB,CAAC,UAAU,mBAAmB,OAAO,aAAa;AAAA,IACtE,UAAU,EAAE,WAAW;AAAA,EAAA;AAE3B;ACrIA,MAAM,cAAc,CAAC,MAAM,MAAM,OAAO,MAAM;AAO9C,SAAS,aAAa,OAAmC;AACvD,MAAI,MAAM;AACV,aAAS;AACP,eAAW,OAAO,aAAa;AAC7B,YAAM,YAAY,KAAK,KAAK,mBAAmB,GAAG,EAAE;AACpD,UAAI,WAAW,SAAS,EAAG,QAAO;AAAA,IACpC;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAwBA,eAAsB,mBACpB,UAAuB,IACA;AACvB,QAAM,YAAY,QAAQ,IAAI;AAC9B,QAAM,WAAW,QAAQ,OAAO,QAAQ,IAAA;AAExC,QAAM,WAAW,QAAQ,cAAc,aAAa,aAAa,QAAQ;AACzE,QAAM,MAAM,WAAW,QAAQ,QAAQ,IAAI;AAE3C,MAAI,CAAC,UAAU;AACb,WAAO,EAAE,QAAQ,YAAY,CAAA,CAAE,GAAG,YAAY,QAAW,IAAA;AAAA,EAC3D;AAEA,QAAM,EAAE,WAAW,MAAM,WAA+B;AAAA,IACtD,MAAM;AAAA,IACN;AAAA,IACA,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,QAAQ;AAAA,EAAA,CACT;AAED,SAAO;AAAA,IACL,QAAQ,YAAY,UAAU,EAAE;AAAA,IAChC,YAAY;AAAA,IACZ;AAAA,EAAA;AAEJ;ACvDO,SAAS,YACd,OACA,UACA,UAAuB,CAAA,GACP;AAChB,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,MAAI,UAAU;AACZ,UAAM,QAAQ,MAAM;AAAA,MAClB,CAAC,MAAM,EAAE,SAAS,YAAY,SAAS,EAAE,IAAI,MAAM;AAAA,IAAA;AAErD,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR,UAAU,QAAQ;AAAA,MAAA;AAAA,IAEtB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,UAAU,MAAM,KAAK,CAAC,MAAM,SAAS,EAAE,IAAI,MAAM,IAAI;AAC3D,MAAI,QAAS,QAAO;AAEpB,aAAW,aAAa,QAAQ,YAAY,CAAA,GAAI;AAC9C,UAAM,QAAQ,MAAM,KAAK,CAAC,MAAM,SAAS,EAAE,IAAI,MAAM,SAAS;AAC9D,QAAI,MAAO,QAAO;AAAA,EACpB;AAEA,SAAO,MAAM,CAAC,KAAK;AACrB;AC9BA,MAAM,mCAA2C,IAAI;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,YAAY,QAA4B;AACtD,QAAM,SAAS,OAAO,MAAM,OAAO,CAAC,MAAM,MAAM,OAAO,IAAI;AAC3D,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,gBAAgB,QAAQ,CAAC,CAAC;AAC1D,SAAO;AAAA,IACL,MAAM,OAAO,KAAK;AAAA,IAClB;AAAA,IACA,QAAQ,MAAM,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC;AAAA,EAAA;AAE5C;AAEA,SAAS,gBAAgB,QAAgB,MAA+B;AACtE,QAAM,OAAkC,CAAA;AACxC,MAAI,QAAQ;AACZ,aAAW,OAAO,OAAO,MAAM;AAC7B,UAAM,EAAE,MAAA,IAAU,OAAO,KAAK,KAAK,IAAI;AACvC,SAAK,GAAG,IAAI;AACZ,QAAI,aAAa,IAAI,KAAK,EAAG;AAAA,EAC/B;AACA,SAAO,EAAE,MAAM,KAAK,MAAM,MAAM,MAAA;AAClC;AAEO,SAAS,eAAe,QAA4B;AACzD,QAAM,WAAW,SAAS,OAAO,IAAI;AACrC,QAAM,aAAa,OAAO,MAAM,IAAI,CAAC,MAAM,SAAS,EAAE,IAAI,CAAC;AAC3D,QAAM,QAAkB,CAAA;AACxB,QAAM,KAAK,SAAS,QAAQ,UAAU,WAAW,KAAK,IAAI,CAAC,GAAG;AAC9D,QAAM,KAAK,EAAE;AAEb,MAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,UAAM,KAAK,gCAAgC;AAC3C,WAAO,MAAM,KAAK,IAAI,IAAI;AAAA,EAC5B;AAEA,QAAM,gCAAgB,IAAA;AACtB,aAAW,KAAK,OAAO,OAAO;AAC5B,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,EAAE,IAAI,GAAG;AAC3C,UAAI,aAAa,IAAI,CAAC,EAAG,WAAU,IAAI,CAAC;AAAA,IAC1C;AAAA,EACF;AAEA,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,KAAK,0CAA0C;AACrD,WAAO,MAAM,KAAK,IAAI,IAAI;AAAA,EAC5B;AAEA,QAAM,WAAW,KAAK,IAAI,GAAG,GAAG,CAAC,GAAG,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AACnE,QAAM,WAAW,KAAK,IAAI,IAAI,GAAG,WAAW,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AAEhE,QAAM;AAAA,IACJ,UAAU,OAAO,YAAY,UAAU,UAAU,CAAC,MAAM,EAAE,OAAO,QAAQ,CAAC;AAAA,EAAA;AAE5E,aAAW,OAAO,CAAC,GAAG,SAAS,EAAE,QAAQ;AACvC,UAAM,QAAQ,OAAO,MAAM,IAAI,CAAC,MAAM,WAAW,EAAE,KAAK,GAAG,KAAK,SAAS,CAAC;AAC1E,UAAM;AAAA,MACJ,UAAU,KAAK,OAAO,UAAU,UAAU,CAAC,MAAM,EAAE,OAAO,QAAQ,CAAC;AAAA,IAAA;AAAA,EAEvE;AAEA,QAAM,KAAK,EAAE;AACb,QAAM,aAAa,OAAO,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC;AACnE,QAAM;AAAA,IACJ,GAAG,UAAU,IAAI,yBAAyB,OAAO,MAAM,MAAM,aAAa,UAAU;AAAA,EAAA;AAGtF,SAAO,MAAM,KAAK,IAAI,IAAI;AAC5B;AAEA,SAAS,UACP,KACA,OACA,UACA,UACA,KACQ;AACR,SAAO,CAAC,IAAI,OAAO,QAAQ,GAAG,GAAG,MAAM,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI;AAC5D;AAEA,SAAS,WAAW,OAA0B;AAC5C,UAAQ,OAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,EAAA;AAEb;ACjHA,MAAM,SAAS;AAOR,SAAS,SAAS,QAAgB,OAAO,IAAa;AAC3D,QAAM,kBAAkB,OAAO,SAAS,IAAI;AAC5C,QAAM,OAAO,kBAAkB,OAAO,MAAM,GAAG,EAAE,IAAI;AACrD,QAAM,QAAQ,KAAK,WAAW,IAAI,CAAA,IAAK,KAAK,MAAM,IAAI;AAEtD,QAAM,UAAsB,CAAA;AAC5B,aAAW,OAAO,OAAO;AACvB,YAAQ,KAAK,UAAU,GAAG,CAAC;AAAA,EAC7B;AAEA,SAAO,EAAE,MAAM,SAAS,gBAAA;AAC1B;AAEA,SAAS,UAAU,KAAuB;AACxC,MAAI,IAAI,OAAO,WAAW,GAAG;AAC3B,WAAO,EAAE,MAAM,SAAS,IAAA;AAAA,EAC1B;AAGA,QAAM,eAAe,IAAI,QAAQ,QAAQ,EAAE;AAC3C,MAAI,aAAa,WAAW,GAAG,GAAG;AAChC,WAAO,EAAE,MAAM,WAAW,IAAA;AAAA,EAC5B;AAEA,QAAM,KAAK,WAAW,GAAG;AACzB,MAAI,GAAI,QAAO;AAIf,SAAO,EAAE,MAAM,WAAW,IAAA;AAC5B;AAEA,SAAS,WAAW,KAA6B;AAC/C,MAAI,OAAO;AAGX,QAAM,YAAY,KAAK,MAAM,SAAS,IAAI,CAAC,KAAK;AAChD,SAAO,KAAK,MAAM,UAAU,MAAM;AAElC,MAAI,eAAe;AACnB,MAAI,KAAK,WAAW,SAAS,GAAG;AAC9B,mBAAe;AACf,WAAO,KAAK,MAAM,UAAU,MAAM,EAAE,QAAQ,WAAW,EAAE;AAAA,EAC3D;AAEA,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,MAAI,QAAQ,EAAG,QAAO;AAEtB,QAAM,MAAM,KAAK,MAAM,GAAG,KAAK,EAAE,QAAA;AACjC,MAAI,CAAC,OAAO,KAAK,GAAG,EAAG,QAAO;AAE9B,QAAM,QAAQ,KAAK,MAAM,QAAQ,CAAC;AAClC,QAAM,aAAa,MAAM,QAAQ,WAAW,EAAE;AACvB,QAAM,MAAM,GAAG,MAAM,SAAS,WAAW,MAAM;AAEtE,QAAM,SAAS,WAAW,UAAU;AACpC,MAAI,CAAC,OAAQ,QAAO;AAQpB,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,IAChB;AAAA,IACA,eAAe,OAAO;AAAA,IACtB;AAAA,EAAA;AAEJ;AASA,SAAS,WAAW,OAAmC;AACrD,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,EAAE,UAAU,IAAI,OAAO,IAAI,SAAS,QAAQ,eAAe,GAAA;AAAA,EACpE;AAEA,QAAM,QAAQ,MAAM,CAAC;AACrB,MAAI,UAAU,OAAO,UAAU,KAAK;AAClC,UAAM,QAAQ,iBAAiB,OAAO,KAAK;AAC3C,QAAI,QAAQ,EAAG,QAAO;AACtB,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK;AACrC,UAAM,QAAQ,UAAU,MAAM,mBAAmB,QAAQ,IAAI;AAC7D,UAAM,OAAO,MAAM,MAAM,QAAQ,CAAC;AAClC,UAAMA,iBAAgB,qBAAqB,IAAI;AAC/C,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,SAAS,UAAU,MAAM,WAAW;AAAA,MACpC,eAAAA;AAAAA,IAAA;AAAA,EAEJ;AAIA,QAAM,UAAU,yBAAyB,KAAK;AAC9C,QAAM,aAAa,UAAU,IAAI,QAAQ,MAAM,MAAM,GAAG,OAAO,GAAG,QAAA;AAClE,QAAM,gBAAgB,UAAU,IAAI,KAAK,MAAM,MAAM,UAAU,MAAM;AACrE,SAAO;AAAA,IACL,UAAU;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT;AAAA,EAAA;AAEJ;AAEA,SAAS,iBAAiB,OAAe,OAA0B;AACjE,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,KAAK,MAAM,CAAC;AAClB,QAAI,UAAU,OAAO,OAAO,MAAM;AAChC;AACA;AAAA,IACF;AACA,QAAI,OAAO,MAAO,QAAO;AAAA,EAC3B;AACA,SAAO;AACT;AAEA,SAAS,yBAAyB,OAAuB;AACvD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,QAAI,MAAM,CAAC,MAAM,IAAK;AACtB,QAAI,MAAM,EAAG,QAAO;AACpB,UAAM,OAAO,MAAM,IAAI,CAAC;AACxB,QAAI,SAAS,OAAO,SAAS,IAAM,QAAO;AAAA,EAC5C;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,MAAsB;AAGlD,SAAO;AACT;AAEA,SAAS,mBAAmB,KAAqB;AAC/C,SAAO,IAAI,QAAQ,UAAU,CAAC,GAAG,OAAe;AAC9C,YAAQ,IAAA;AAAA,MACN,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO,KAAK,EAAE;AAAA,IAAA;AAAA,EAEpB,CAAC;AACH;ACtKA,MAAM,gBAAgB,CAAC,QAAQ,KAAK,MAAM;AAkB1C,eAAsB,iBACpB,OACA,UAA2B,IACP;AACpB,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,eAAe,QAAQ,gBAAgB;AAC7C,QAAM,gCAAgB,IAAA;AAEtB,aAAW,KAAK,OAAO;AACrB,UAAM,OAAO,MAAM,KAAK,CAAC;AACzB,QAAI,KAAK,eAAe;AACtB,YAAM,UAAU,MAAM,QAAQ,CAAC;AAC/B,iBAAW,QAAQ,SAAS;AAC1B,YAAI,CAAC,iBAAiB,MAAM,YAAY,EAAG;AAC3C,kBAAU,IAAI,KAAK,GAAG,IAAI,CAAC;AAAA,MAC7B;AAAA,IACF,OAAO;AACL,gBAAU,IAAI,CAAC;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,GAAG,SAAS,EAAE;AAAA,IAAK,CAAC,GAAG,MACrC,aAAa,GAAG,GAAG,YAAY;AAAA,EAAA;AAEjC,QAAM,QAAmB,CAAA;AACzB,aAAW,YAAY,QAAQ;AAC7B,UAAM,SAAS,MAAM,SAAS,UAAU,MAAM;AAC9C,UAAM,KAAK,SAAS,QAAQ,QAAQ,CAAC;AAAA,EACvC;AACA,SAAO;AACT;AAEA,SAAS,iBACP,MACA,cACS;AACT,MAAI,CAAC,KAAK,WAAW,MAAM,EAAG,QAAO;AACrC,MAAI,aAAa,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,EAAG,QAAO;AACvD,SAAO;AACT;AAEA,SAAS,aAAa,GAAW,GAAW,cAA+B;AACzE,QAAM,KAAK,SAAS,CAAC;AACrB,QAAM,KAAK,SAAS,CAAC;AACrB,MAAI,cAAc;AAChB,QAAI,OAAO,eAAgB,QAAO;AAClC,QAAI,OAAO,eAAgB,QAAO;AAAA,EACpC;AACA,SAAO,GAAG,cAAc,EAAE;AAC5B;ACtEA,MAAM,gCAAgB,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AACnD,MAAM,cAAc,oBAAI,IAAI,CAAC,KAAK,GAAI,CAAC;AAgBhC,SAAS,gBAAgB,MAAoC;AAClE,QAAM,0BAAU,IAAA;AAChB,MAAI,UAAyB;AAC7B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,QAAQ,KAAK;AAC5C,UAAM,OAAO,kBAAkB,KAAK,SAAS,CAAC;AAC9C,QAAI,SAAS,KAAM,WAAU;AAC7B,UAAM,IAAI,KAAK,QAAQ,CAAC;AACxB,QAAI,EAAE,SAAS,QAAQ,aAAa,IAAI,EAAE,KAAK,OAAO;AAAA,EACxD;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAqB,KAA4B;AAC1E,QAAM,IAAI,QAAQ,GAAG;AACrB,MAAI,CAAC,KAAK,EAAE,SAAS,UAAW,QAAO;AACvC,MAAI,iBAAiB,EAAE,GAAG,EAAG,QAAO;AAEpC,QAAM,SAAS,kBAAkB,EAAE,GAAG;AACtC,MAAI,WAAW,KAAM,QAAO;AAE5B,QAAM,OAAO,mBAAmB,EAAE,GAAG;AACrC,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,aAAa,QAAQ,MAAM,CAAC,CAAC,KAAK,aAAa,QAAQ,MAAM,CAAC,CAAC,GAAG;AACpE,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,aAAa,GAAkC;AACtD,MAAI,CAAC,KAAK,EAAE,SAAS,UAAW,QAAO;AACvC,SAAO,iBAAiB,EAAE,GAAG;AAC/B;AASA,SAAS,iBAAiB,KAAsB;AAC9C,QAAM,QAAQ,WAAW,KAAK,CAAC;AAC/B,MAAI,SAAS,IAAI,UAAU,IAAI,KAAK,MAAM,IAAK,QAAO;AACtD,MAAI,IAAI,QAAQ;AAChB,MAAI,eAAe;AACnB,SAAO,IAAI,IAAI,QAAQ;AACrB,UAAM,KAAK,IAAI,CAAC;AAChB,QAAI,UAAU,IAAI,EAAE,EAAG,gBAAe;AAAA,aAC7B,CAAC,YAAY,IAAI,EAAE,EAAG,QAAO;AACtC;AAAA,EACF;AACA,SAAO;AACT;AAUA,SAAS,kBAAkB,KAA4B;AACrD,QAAM,QAAQ,WAAW,KAAK,CAAC;AAC/B,MAAI,SAAS,IAAI,UAAU,IAAI,KAAK,MAAM,IAAK,QAAO;AAGtD,MAAI,IAAI,WAAW,KAAK,QAAQ,CAAC;AACjC,MAAI,KAAK,IAAI,UAAU,CAAC,UAAU,IAAI,IAAI,CAAC,CAAE,EAAG,QAAO;AACvD,MAAI,YAAY;AAChB,SAAO,IAAI,IAAI,UAAU,UAAU,IAAI,IAAI,CAAC,CAAE,GAAG;AAC/C;AACA;AAAA,EACF;AACA,MAAI,YAAY,EAAG,QAAO;AAG1B,MAAI,IAAI,IAAI;AACZ,SAAO,IAAI,KAAK,YAAY,IAAI,IAAI,IAAI,CAAC,CAAE,EAAG;AAC9C,MAAI,KAAK,KAAK,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,CAAE,EAAG,QAAO;AAClD,MAAI,aAAa;AACjB,SAAO,IAAI,KAAK,UAAU,IAAI,IAAI,IAAI,CAAC,CAAE,GAAG;AAC1C;AACA;AAAA,EACF;AACA,MAAI,aAAa,EAAG,QAAO;AAG3B,QAAM,QAAQ,IAAI,MAAM,GAAG,CAAC,EAAE,KAAA;AAC9B,SAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;AAEA,SAAS,mBAAmB,KAAqB;AAE/C,MAAI,IAAI,WAAW,KAAK,CAAC;AACzB,SAAO,IAAI,IAAI,UAAU,IAAI,CAAC,MAAM,IAAK;AACzC,MAAI,WAAW,KAAK,CAAC;AAErB,MAAI,IAAI,IAAI;AACZ,SACE,IAAI,MACH,YAAY,IAAI,IAAI,IAAI,CAAC,CAAE,KAAK,UAAU,IAAI,IAAI,IAAI,CAAC,CAAE,IAC1D;AACA;AAAA,EACF;AACA,SAAO,IAAI,MAAM,GAAG,CAAC;AACvB;AAEA,SAAS,WAAW,KAAa,MAAsB;AACrD,MAAI,IAAI;AACR,SAAO,IAAI,IAAI,UAAU,YAAY,IAAI,IAAI,CAAC,CAAE,EAAG;AACnD,SAAO;AACT;AC1GO,SAAS,YAAY,OAAkB,MAAuB;AACnE,QAAM,OAAO,YAAY,OAAO,IAAI;AACpC,QAAM,8BAAc,IAAA;AACpB,aAAW,QAAQ,OAAO;AACxB,YAAQ,IAAI,MAAM,QAAQ,IAAI,CAAC;AAAA,EACjC;AACA,QAAM,YAAY,QAAQ,IAAI,IAAI;AAClC,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,oCAAoC;AAAA,EACtD;AACA,QAAM,WAAW,gBAAgB,IAAI;AAErC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,KAAK;AACb,aAAO,SAAS,IAAI,GAAG;AAAA,IACzB;AAAA,IACA,KAAK,KAAK,MAAM;AACd,YAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,UAAI,CAAC,UAAU;AACb,cAAM,IAAI,MAAM,uBAAuB,KAAK,IAAI,EAAE;AAAA,MACpD;AACA,YAAM,MAAM,SAAS,IAAI,GAAG;AAC5B,YAAM,YAAY,UAAU,IAAI,GAAG;AAEnC,UAAI,SAAS,MAAM;AACjB,eAAO;AAAA,UACL,OAAO,MAAM,SAAS;AAAA,UACtB,OAAO,KAAK;AAAA,QAAA;AAAA,MAEhB;AAEA,UAAI,CAAC,OAAO,CAAC,WAAW;AACtB,eAAO,EAAE,OAAO,WAAW,OAAO,OAAA;AAAA,MACpC;AACA,UAAI,CAAC,KAAK;AACR,eAAO,EAAE,OAAO,WAAW,OAAO,OAAA;AAAA,MACpC;AACA,UAAI,CAAC,WAAW;AACd,eAAO,EAAE,OAAO,SAAS,OAAO,IAAI,MAAA;AAAA,MACtC;AACA,aAAO;AAAA,QACL,OAAO,IAAI,UAAU,UAAU,QAAQ,SAAS;AAAA,QAChD,OAAO,IAAI;AAAA,MAAA;AAAA,IAEf;AAAA,EAAA;AAEJ;AAEA,SAAS,YAAY,OAAkB,MAAyB;AAC9D,QAAM,2BAAW,IAAA;AACjB,QAAM,MAAgB,CAAA;AAItB,aAAW,KAAK,KAAK,SAAS;AAC5B,QAAI,EAAE,SAAS,QAAQ,CAAC,KAAK,IAAI,EAAE,GAAG,GAAG;AACvC,WAAK,IAAI,EAAE,GAAG;AACd,UAAI,KAAK,EAAE,GAAG;AAAA,IAChB;AAAA,EACF;AAGA,QAAM,6BAAa,IAAA;AACnB,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,KAAM;AACnB,eAAW,KAAK,KAAK,SAAS;AAC5B,UAAI,EAAE,SAAS,QAAQ,CAAC,KAAK,IAAI,EAAE,GAAG,GAAG;AACvC,eAAO,IAAI,EAAE,GAAG;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACA,aAAW,KAAK,CAAC,GAAG,MAAM,EAAE,OAAQ,KAAI,KAAK,CAAC;AAC9C,SAAO;AACT;AAEA,SAAS,QAAQ,MAAqC;AACpD,QAAM,wBAAQ,IAAA;AACd,aAAW,KAAK,KAAK,SAAS;AAC5B,QAAI,EAAE,SAAS,QAAQ,IAAI,EAAE,KAAK,CAAC;AAAA,EACrC;AACA,SAAO;AACT;"}
@@ -34,38 +34,8 @@ function needsQuoting(value, current) {
34
34
  function escapeDoubleQuoted(value) {
35
35
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
36
36
  }
37
- const SECRET_TOKENS = [
38
- "SECRET",
39
- "TOKEN",
40
- "PASSWORD",
41
- "PASSWD",
42
- "PWD",
43
- "KEY",
44
- "PRIVATE",
45
- "CREDENTIAL",
46
- "AUTH",
47
- "DSN"
48
- ];
49
- function isSecretKey(key) {
50
- const upper = key.toUpperCase();
51
- const segments = upper.split("_").filter(Boolean);
52
- if (segments.length === 0) return false;
53
- if (segments[segments.length - 1] === "ID") return false;
54
- if (segments[0] === "PUBLIC") return false;
55
- if (segments.includes("PUBLIC")) return false;
56
- return segments.some(
57
- (seg) => SECRET_TOKENS.some((token) => seg === token || seg.endsWith(token))
58
- );
59
- }
60
- function maskValue(value) {
61
- if (value.length === 0) return "••••";
62
- const dots = "•".repeat(Math.min(value.length, 8));
63
- return value.length > 8 ? `${dots} (${value.length})` : dots;
64
- }
65
37
  export {
66
- isSecretKey as i,
67
- maskValue as m,
68
38
  rebuildKvLine as r,
69
39
  serializeEnv as s
70
40
  };
71
- //# sourceMappingURL=mask-Bv9W6Ei8.mjs.map
41
+ //# sourceMappingURL=serialize-D_ZNIgRa.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize-D_ZNIgRa.mjs","sources":["../../src/core/serialize.ts"],"sourcesContent":["import type { EnvFile, KvEntry, Quoting } from '@/core/types.ts';\n\n/**\n * Serialize an {@link EnvFile} back to a string. Unchanged entries are emitted\n * from their captured `raw` line so round-trip is byte-exact; modified entries\n * must have their `raw` field re-built via {@link rebuildKvLine} first.\n */\nexport function serializeEnv(file: EnvFile): string {\n const body = file.entries.map((e) => e.raw).join('\\n');\n return file.trailingNewline ? `${body}\\n` : body;\n}\n\n/**\n * Rebuild the `raw` line of a kv entry from its structured fields. Call this\n * after mutating `key`, `value`, `quoting`, or `exportPrefix` so the next\n * serialize emits the new line.\n */\nexport function rebuildKvLine(entry: KvEntry): void {\n const prefix = entry.exportPrefix ? 'export ' : '';\n const encoded = encodeValue(entry.value, entry.quoting);\n // Update rawValue to match the (possibly new) encoded inner form.\n entry.rawValue = encoded.inner;\n entry.raw = `${prefix}${entry.key}=${encoded.full}${entry.inlineComment}`;\n}\n\ninterface EncodedValue {\n /** Inner string without surrounding quotes (for `rawValue`). */\n inner: string;\n /** Fully formed value including surrounding quotes if any. */\n full: string;\n}\n\nfunction encodeValue(value: string, quoting: Quoting): EncodedValue {\n // Auto-promote to double quotes when an unquoted value would be ambiguous\n // (contains whitespace, `#`, or characters that require escaping). Single\n // quotes are preserved as authored but can't represent embedded `'`.\n const effective: Quoting = needsQuoting(value, quoting) ? 'double' : quoting;\n\n switch (effective) {\n case 'none':\n return { inner: value, full: value };\n case 'single': {\n if (value.includes(\"'\")) {\n // Fall back to double-quoting if a single quote sneaks in.\n const escaped = escapeDoubleQuoted(value);\n return { inner: escaped, full: `\"${escaped}\"` };\n }\n return { inner: value, full: `'${value}'` };\n }\n case 'double': {\n const escaped = escapeDoubleQuoted(value);\n return { inner: escaped, full: `\"${escaped}\"` };\n }\n }\n}\n\nfunction needsQuoting(value: string, current: Quoting): boolean {\n if (current !== 'none') return false;\n return /[\\s#\"'\\\\]/.test(value);\n}\n\nfunction escapeDoubleQuoted(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t');\n}\n"],"names":[],"mappings":"AAOO,SAAS,aAAa,MAAuB;AAClD,QAAM,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,IAAI;AACrD,SAAO,KAAK,kBAAkB,GAAG,IAAI;AAAA,IAAO;AAC9C;AAOO,SAAS,cAAc,OAAsB;AAClD,QAAM,SAAS,MAAM,eAAe,YAAY;AAChD,QAAM,UAAU,YAAY,MAAM,OAAO,MAAM,OAAO;AAEtD,QAAM,WAAW,QAAQ;AACzB,QAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,QAAQ,IAAI,GAAG,MAAM,aAAa;AACzE;AASA,SAAS,YAAY,OAAe,SAAgC;AAIlE,QAAM,YAAqB,aAAa,OAAO,OAAO,IAAI,WAAW;AAErE,UAAQ,WAAA;AAAA,IACN,KAAK;AACH,aAAO,EAAE,OAAO,OAAO,MAAM,MAAA;AAAA,IAC/B,KAAK,UAAU;AACb,UAAI,MAAM,SAAS,GAAG,GAAG;AAEvB,cAAM,UAAU,mBAAmB,KAAK;AACxC,eAAO,EAAE,OAAO,SAAS,MAAM,IAAI,OAAO,IAAA;AAAA,MAC5C;AACA,aAAO,EAAE,OAAO,OAAO,MAAM,IAAI,KAAK,IAAA;AAAA,IACxC;AAAA,IACA,KAAK,UAAU;AACb,YAAM,UAAU,mBAAmB,KAAK;AACxC,aAAO,EAAE,OAAO,SAAS,MAAM,IAAI,OAAO,IAAA;AAAA,IAC5C;AAAA,EAAA;AAEJ;AAEA,SAAS,aAAa,OAAe,SAA2B;AAC9D,MAAI,YAAY,OAAQ,QAAO;AAC/B,SAAO,YAAY,KAAK,KAAK;AAC/B;AAEA,SAAS,mBAAmB,OAAuB;AACjD,SAAO,MACJ,QAAQ,OAAO,MAAM,EACrB,QAAQ,MAAM,KAAK,EACnB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK;AACzB;"}
@@ -0,0 +1,7 @@
1
+ function defineEnvprismConfig(config) {
2
+ return config;
3
+ }
4
+ export {
5
+ defineEnvprismConfig
6
+ };
7
+ //# sourceMappingURL=define.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.mjs","sources":["../../src/config/define.ts"],"sourcesContent":["// Public, dependency-light entry for `envprism/config`. Types + identity\n// helper only — must never import c12, defu, or @opentui/core so config\n// authoring stays cheap to load.\n\nimport type { EnvprismUserConfig } from '@/config/schema.ts';\n\nexport type {\n BaseConfig,\n DiffConfig,\n DiscoveryConfig,\n EnvprismConfig,\n EnvprismUserConfig,\n GroupingMode,\n HeuristicsConfig,\n LayoutConfig,\n ThemeConfig,\n TuiConfig\n} from '@/config/schema.ts';\n\n/**\n * Identity helper for type-safe config authoring:\n *\n * ```ts\n * import { defineEnvprismConfig } from 'envprism/config';\n * export default defineEnvprismConfig({ heuristics: { grouping: 'prefix' } });\n * ```\n */\nexport function defineEnvprismConfig(\n config: EnvprismUserConfig\n): EnvprismUserConfig {\n return config;\n}\n"],"names":[],"mappings":"AA2BO,SAAS,qBACd,QACoB;AACpB,SAAO;AACT;"}
package/dist/index.mjs CHANGED
@@ -1,12 +1,15 @@
1
- import { b, c, a, d, f, p, r } from "./chunks/matrix-nBq_GY2v.mjs";
2
- import { i, m, r as r2, s } from "./chunks/mask-Bv9W6Ei8.mjs";
1
+ import { b, c, d, e, g, i, l, m, p, r } from "./chunks/matrix-C1pErf9Z.mjs";
2
+ import { r as r2, s } from "./chunks/serialize-D_ZNIgRa.mjs";
3
+ import { defineEnvprismConfig } from "./config/define.mjs";
3
4
  export {
4
5
  b as buildMatrix,
5
6
  c as computeDiff,
6
- a as computeSections,
7
- d as discoverEnvFiles,
8
- f as formatDiffText,
7
+ d as computeSections,
8
+ defineEnvprismConfig,
9
+ e as discoverEnvFiles,
10
+ g as formatDiffText,
9
11
  i as isSecretKey,
12
+ l as loadEnvprismConfig,
10
13
  m as maskValue,
11
14
  p as parseEnv,
12
15
  r2 as rebuildKvLine,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "envprism",
4
- "version": "0.1.0-dev.10-1179a3d",
4
+ "version": "0.1.0-dev.12-c99a64a",
5
5
  "description": "TUI-based env file manager — refract one set of variables into many environment views.",
6
6
  "keywords": [
7
7
  "bun",
@@ -41,6 +41,10 @@
41
41
  ".": {
42
42
  "types": "./dist/index.d.ts",
43
43
  "import": "./dist/index.mjs"
44
+ },
45
+ "./config": {
46
+ "types": "./dist/config/define.d.ts",
47
+ "import": "./dist/config/define.mjs"
44
48
  }
45
49
  },
46
50
  "publishConfig": {
@@ -49,8 +53,10 @@
49
53
  },
50
54
  "dependencies": {
51
55
  "@opentui/core": "^0.2.15",
56
+ "c12": "^3.0.0",
52
57
  "citty": "^0.1.6",
53
58
  "consola": "^3.4.0",
59
+ "defu": "^6.1.4",
54
60
  "pathe": "^2.0.0"
55
61
  },
56
62
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-CgJxxbhr.mjs","sources":["../../src/tui/format.ts","../../src/tui/grouping.ts","../../src/tui/envfile.ts","../../src/tui/types.ts","../../src/tui/state/visible.ts","../../src/tui/actions/batch.ts","../../src/tui/actions/io.ts","../../src/tui/actions/prompt.ts","../../src/tui/keys/browse.ts","../../src/tui/keys/filter.ts","../../src/tui/keys/help.ts","../../src/tui/keys/prompt.ts","../../src/tui/keys/onKey.ts","../../src/tui/theme.ts","../../src/tui/render/layout.ts","../../src/tui/render/filter.ts","../../src/tui/render/dom.ts","../../src/tui/render/footer.ts","../../src/tui/help.ts","../../src/tui/render/builders.ts","../../src/tui/render/help.ts","../../src/tui/render/matrix.ts","../../src/tui/render/prompt.ts","../../src/tui/render/sidebar.ts","../../src/tui/render/index.ts","../../src/tui/app.ts"],"sourcesContent":["import { maskValue } from '@/core/mask.ts';\nimport type { EnvFile, KvEntry } from '@/core/types.ts';\n\nexport const PLACEHOLDER_RE =\n /^(todo|fixme|changeme|placeholder|tbd|x{3,}|your[_-]?(secret|key|token|password|api[_-]?key)(_here)?|replace[_-]?me)$/i;\n\nexport function isPlaceholderValue(value: string): boolean {\n const v = value.trim();\n if (v.length === 0) return false;\n return PLACEHOLDER_RE.test(v);\n}\n\nexport function formatValue(\n value: string | undefined,\n secret: boolean\n): string {\n if (value === undefined) return '';\n if (secret) return maskValue(value);\n return value;\n}\n\nexport function matchesFilter(key: string, filter: string): boolean {\n if (!filter) return true;\n return key.toLowerCase().includes(filter.toLowerCase());\n}\n\nexport function truncate(text: string, width: number): string {\n if (width <= 0) return '';\n if (text.length <= width) return text;\n if (width <= 1) return '…';\n return `${text.slice(0, width - 1)}…`;\n}\n\nexport function findKvEntry(file: EnvFile, key: string): KvEntry | undefined {\n for (const e of file.entries) {\n if (e.kind === 'kv' && e.key === key) return e;\n }\n return undefined;\n}\n","import type { Matrix } from '@/core/matrix.ts';\nimport { matchesFilter } from '@tui/format.ts';\nimport type { State } from '@tui/types.ts';\n\n// Lookup name for keys/sections that have no banner or prefix bucket.\nexport const SECTION_COLLAPSE_KEY = '__other__';\n\nexport function prefixSection(key: string): string | undefined {\n const idx = key.indexOf('_');\n if (idx <= 0) return undefined;\n return key.slice(0, idx);\n}\n\n/**\n * Sort by first-underscore-prefix while preserving the relative order each\n * prefix first appeared in. Keys without an underscore land in a trailing\n * \"Other\" group keeping their authored order.\n */\nexport function groupByPrefix(keys: string[]): string[] {\n const groups = new Map<string, string[]>();\n const order: string[] = [];\n const OTHER = SECTION_COLLAPSE_KEY;\n for (const k of keys) {\n const p = prefixSection(k) ?? OTHER;\n let bucket = groups.get(p);\n if (!bucket) {\n bucket = [];\n groups.set(p, bucket);\n if (p !== OTHER) order.push(p);\n }\n bucket.push(k);\n }\n if (groups.has(OTHER)) order.push(OTHER);\n return order.flatMap((p) => groups.get(p)!);\n}\n\n/**\n * Move row focus by `delta` while skipping section dividers that are not\n * collapsed. The user only needs to land on a divider when its section is\n * folded — that's the only context in which 'c' on the divider does work\n * the focused-key path doesn't already cover.\n */\nexport function stepRow(state: State, delta: number): number {\n const items = state.visibleItems;\n if (items.length === 0) return 0;\n const canFocus = (i: number) => {\n const it = items[i];\n if (!it) return false;\n if (it.kind === 'key') return true;\n // divider — focusable only when collapsed\n return state.collapsed.has(it.ref);\n };\n let i = state.rowIdx + delta;\n while (i >= 0 && i < items.length) {\n if (canFocus(i)) return i;\n i += delta;\n }\n // No focusable item further along — clamp to current.\n return state.rowIdx;\n}\n\nexport function orderedKeys(\n matrix: Matrix,\n state: State,\n sectionOf: (key: string) => string | undefined\n): string[] {\n void sectionOf;\n const filtered = matrix.keys.filter((k) => {\n if (!matchesFilter(k, state.filter)) return false;\n if (state.driftOnly && !keyDrifts(matrix, k)) return false;\n return true;\n });\n return state.grouping === 'prefix' ? groupByPrefix(filtered) : filtered;\n}\n\nexport interface SectionStats {\n drift: number;\n missing: number;\n total: number;\n}\n\nexport function sectionMetadata(\n matrix: Matrix,\n sectionOf: (key: string) => string | undefined,\n state: State\n): Map<string, SectionStats> {\n const out = new Map<string, SectionStats>();\n for (const key of orderedKeys(matrix, state, sectionOf)) {\n const k = sectionOf(key) ?? SECTION_COLLAPSE_KEY;\n const bucket = out.get(k) ?? { drift: 0, missing: 0, total: 0 };\n bucket.total += 1;\n let drifts = false;\n let missing = false;\n for (const file of matrix.files) {\n if (file === matrix.base) continue;\n const s = matrix.cell(key, file).state;\n if (s === 'missing') missing = true;\n if (s === 'differs' || s === 'missing' || s === 'extra') drifts = true;\n }\n if (drifts) bucket.drift += 1;\n if (missing) bucket.missing += 1;\n out.set(k, bucket);\n }\n return out;\n}\n\nexport function keyDrifts(matrix: Matrix, key: string): boolean {\n for (const file of matrix.files) {\n if (file === matrix.base) continue;\n const s = matrix.cell(key, file).state;\n if (s === 'differs' || s === 'missing' || s === 'extra') return true;\n }\n return false;\n}\n","import { basename } from 'pathe';\nimport { rebuildKvLine } from '@/core/serialize.ts';\nimport type { EnvFile, KvEntry } from '@/core/types.ts';\n\nexport function isValidEnvFileName(name: string): boolean {\n if (!name.startsWith('.env')) return false;\n if (name.includes('/') || name.includes('\\\\')) return false;\n if (name.endsWith('.swp') || name.endsWith('~') || name.endsWith('.bak'))\n return false;\n return true;\n}\n\nexport function createEmptyEnvFile(path: string): EnvFile {\n return {\n path,\n entries: [{ kind: 'comment', raw: `# ${basename(path)}` }],\n trailingNewline: true\n };\n}\n\nexport function appendKv(file: EnvFile, key: string, value: string): KvEntry {\n const entry: KvEntry = {\n kind: 'kv',\n key,\n rawValue: '',\n value,\n quoting: 'none',\n exportPrefix: false,\n inlineComment: '',\n raw: ''\n };\n rebuildKvLine(entry);\n file.entries.push(entry);\n // Round-trip semantics: serializeEnv joins with \\n and appends a trailing\n // newline if trailingNewline is set. Push alone gives us \"...\\nKEY=val\" when\n // trailingNewline=false, or \"...\\nKEY=val\\n\" when true. Either case is\n // sane; we make sure the file ends with a newline so editors don't complain.\n file.trailingNewline = true;\n return entry;\n}\n","import type { RGBA } from '@opentui/core';\nimport type { EnvFile, KvEntry } from '@/core/types.ts';\n\nexport type Mode = 'browse' | 'filter' | 'prompt';\n\nexport type Prompt =\n | { kind: 'edit'; key: string; file: EnvFile }\n | { kind: 'add-key'; file: EnvFile }\n | { kind: 'add-value'; key: string; file: EnvFile }\n | { kind: 'new-file' };\n\nexport type Grouping = 'banner' | 'prefix';\n\nexport type UndoEntry =\n | {\n kind: 'edit';\n file: EnvFile;\n entry: KvEntry;\n prevValue: string;\n prevRaw: string;\n }\n | { kind: 'add-kv'; file: EnvFile; entry: KvEntry }\n | { kind: 'delete-kv'; file: EnvFile; entry: KvEntry; idx: number };\n\nexport const UNDO_LIMIT = 50;\n\nexport type Pane = 'matrix' | 'sidebar';\n\nexport type ItemKind = 'key' | 'divider';\nexport interface MatrixItem {\n kind: ItemKind;\n // For 'key': the variable name; for 'divider': the section's lookup name\n // (or '__other__'). Holding the raw key/section here keeps focus stable\n // across rebuilds.\n ref: string;\n}\n\nexport interface State {\n mode: Mode;\n filter: string;\n rowIdx: number;\n colIdx: number;\n prompt: Prompt | null;\n dirty: Set<EnvFile>;\n visibleKeys: string[]; // kept for callers that just want the keys\n visibleItems: MatrixItem[];\n message: string | null;\n driftOnly: boolean;\n confirmQuit: boolean;\n grouping: Grouping;\n helpOpen: boolean;\n undo: UndoEntry[];\n pane: Pane;\n sidebarIdx: number;\n enabled: Set<EnvFile>;\n showSecrets: boolean;\n collapsed: Set<string>;\n // (key + \"|\" + file.path) of cells the user has touched in this session.\n // Drives a green ● marker so unsaved local changes are visually distinct\n // from \"this file disagrees with base\", which uses the diff icons.\n modified: Set<string>;\n // Current value of the prompt input. We accumulate characters ourselves\n // in the global key handler instead of relying on opentui's InputRenderable,\n // because the InputRenderable swallows Esc (it interprets it as \"blur\").\n promptInput: string;\n}\n\nexport const KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport type HelpLine =\n | { kind: 'header'; text: string }\n | { kind: 'entry'; text: string }\n | { kind: 'legend'; symbol: string; color: RGBA; description: string }\n | { kind: 'blank' };\n","import { buildMatrix } from '@/core/matrix.ts';\nimport type { EnvFile } from '@/core/types.ts';\nimport type { TuiContext } from '@tui/context.ts';\nimport { matchesFilter } from '@tui/format.ts';\nimport {\n keyDrifts,\n orderedKeys,\n SECTION_COLLAPSE_KEY,\n stepRow\n} from '@tui/grouping.ts';\nimport { type MatrixItem, UNDO_LIMIT, type UndoEntry } from '@tui/types.ts';\n\nexport function cellKey(key: string, file: EnvFile): string {\n return `${key}|${file.path}`;\n}\n\nexport function markModified(\n ctx: TuiContext,\n key: string,\n file: EnvFile\n): void {\n ctx.state.modified.add(cellKey(key, file));\n}\n\nexport function pushUndo(ctx: TuiContext, entry: UndoEntry): void {\n ctx.state.undo.push(entry);\n if (ctx.state.undo.length > UNDO_LIMIT) ctx.state.undo.shift();\n}\n\nexport function focusedKey(ctx: TuiContext): string | null {\n const item = ctx.state.visibleItems[ctx.state.rowIdx];\n return item && item.kind === 'key' ? item.ref : null;\n}\n\nexport function focusKey(ctx: TuiContext, key: string): void {\n const idx = ctx.state.visibleKeys.indexOf(key);\n if (idx >= 0) ctx.state.rowIdx = idx;\n}\n\nexport function recomputeVisibleKeys(ctx: TuiContext): void {\n const { state } = ctx;\n // Two parallel structures:\n // visibleKeys — just the key names (used by editing helpers)\n // visibleItems — dividers + visible keys, in render order\n // Dividers stay in the item list even when their section is collapsed,\n // so the user can navigate onto one and expand it with 'c'.\n const visibleKeys: string[] = [];\n const items: MatrixItem[] = [];\n const orderedAll = orderedKeys(ctx.matrix, state, ctx.sectionOf);\n const seen = new Set<string>();\n const focusedRef = state.visibleItems[state.rowIdx]?.ref;\n for (const k of orderedAll) {\n if (!matchesFilter(k, state.filter)) continue;\n if (state.driftOnly && !keyDrifts(ctx.matrix, k)) continue;\n const secKey = ctx.sectionOf(k) ?? SECTION_COLLAPSE_KEY;\n if (!seen.has(secKey)) {\n seen.add(secKey);\n items.push({ kind: 'divider', ref: secKey });\n }\n if (state.collapsed.has(secKey)) continue;\n items.push({ kind: 'key', ref: k });\n visibleKeys.push(k);\n }\n state.visibleKeys = visibleKeys;\n state.visibleItems = items;\n // Try to keep focus on the same item across rebuilds.\n if (focusedRef) {\n const i = items.findIndex((it) => it.ref === focusedRef);\n if (i >= 0) state.rowIdx = i;\n }\n if (state.rowIdx >= items.length) {\n state.rowIdx = Math.max(0, items.length - 1);\n }\n // Make sure we don't land on an expanded divider after a rebuild.\n if (\n items[state.rowIdx]?.kind === 'divider' &&\n !state.collapsed.has(items[state.rowIdx]!.ref)\n ) {\n const next = stepRow(state, 1);\n const prev = stepRow(state, -1);\n state.rowIdx = next !== state.rowIdx ? next : prev;\n }\n}\n\nexport function rebuildMatrix(ctx: TuiContext): void {\n const { state } = ctx;\n const enabledList = ctx.allFiles.filter((f) => state.enabled.has(f));\n if (!state.enabled.has(ctx.currentBase)) {\n // Base got disabled — promote the first enabled file.\n const next = enabledList[0];\n if (next) ctx.currentBase = next;\n }\n ctx.matrix = buildMatrix(enabledList, ctx.currentBase);\n if (state.colIdx >= ctx.matrix.files.length) {\n state.colIdx = Math.max(0, ctx.matrix.files.length - 1);\n }\n if (state.sidebarIdx >= ctx.allFiles.length) {\n state.sidebarIdx = Math.max(0, ctx.allFiles.length - 1);\n }\n recomputeVisibleKeys(ctx);\n}\n","import { rebuildKvLine } from '@/core/serialize.ts';\nimport { basename } from 'pathe';\nimport type { TuiContext } from '@tui/context.ts';\nimport { appendKv } from '@tui/envfile.ts';\nimport { findKvEntry } from '@tui/format.ts';\nimport {\n focusedKey,\n markModified,\n pushUndo,\n rebuildMatrix\n} from '@tui/state/visible.ts';\n\nexport function applyToAllFiles(\n ctx: TuiContext,\n key: string,\n value: string\n): number {\n // Set key=value in every enabled file. Used by '=' in the matrix and\n // Ctrl-A from the edit prompt. Each per-file mutation is undone\n // individually (one Ctrl-Z per file) — not perfect but predictable.\n const { state } = ctx;\n let touched = 0;\n for (const file of ctx.matrix.files) {\n const existing = findKvEntry(file, key);\n if (existing) {\n if (existing.value === value) continue;\n pushUndo(ctx, {\n kind: 'edit',\n file,\n entry: existing,\n prevValue: existing.value,\n prevRaw: existing.raw\n });\n existing.value = value;\n rebuildKvLine(existing);\n } else {\n const added = appendKv(file, key, value);\n pushUndo(ctx, { kind: 'add-kv', file, entry: added });\n }\n state.dirty.add(file);\n markModified(ctx, key, file);\n touched++;\n }\n return touched;\n}\n\nexport function syncToAll(ctx: TuiContext): void {\n const { state } = ctx;\n const key = focusedKey(ctx);\n const file = ctx.matrix.files[state.colIdx];\n if (!key || !file) {\n state.message = 'Move onto a variable row to sync.';\n ctx.refresh();\n return;\n }\n const entry = findKvEntry(file, key);\n if (!entry) {\n state.message = `${key} has no value in ${basename(file.path)} to sync.`;\n ctx.refresh();\n return;\n }\n const touched = applyToAllFiles(ctx, key, entry.value);\n rebuildMatrix(ctx);\n state.message =\n touched > 0\n ? `Synced ${key} to ${touched} file(s). Ctrl-S to save.`\n : `${key} is already in sync.`;\n ctx.refresh();\n}\n\nexport function undo(ctx: TuiContext): void {\n const { state } = ctx;\n const last = state.undo.pop();\n if (!last) {\n state.message = 'Nothing to undo.';\n ctx.refresh();\n return;\n }\n switch (last.kind) {\n case 'edit':\n last.entry.value = last.prevValue;\n last.entry.raw = last.prevRaw;\n state.dirty.add(last.file);\n state.message = `Undid edit on ${last.entry.key} in ${basename(last.file.path)}.`;\n break;\n case 'add-kv': {\n const i = last.file.entries.indexOf(last.entry);\n if (i >= 0) last.file.entries.splice(i, 1);\n state.dirty.add(last.file);\n state.message = `Undid add of ${last.entry.key} in ${basename(last.file.path)}.`;\n break;\n }\n case 'delete-kv':\n last.file.entries.splice(last.idx, 0, last.entry);\n state.dirty.add(last.file);\n state.message = `Undid delete of ${last.entry.key} in ${basename(last.file.path)}.`;\n break;\n }\n rebuildMatrix(ctx);\n ctx.refresh();\n}\n\nexport function toggleEnabled(ctx: TuiContext): void {\n const { state } = ctx;\n const file = ctx.allFiles[state.sidebarIdx];\n if (!file) return;\n if (state.enabled.has(file)) {\n if (state.enabled.size === 1) {\n state.message = 'At least one file must stay enabled.';\n ctx.refresh();\n return;\n }\n state.enabled.delete(file);\n state.message = `Hidden ${basename(file.path)} from the matrix.`;\n } else {\n state.enabled.add(file);\n state.message = `Showing ${basename(file.path)} in the matrix.`;\n }\n rebuildMatrix(ctx);\n ctx.refresh();\n}\n\nexport function setBase(ctx: TuiContext): void {\n const { state } = ctx;\n const file = ctx.allFiles[state.sidebarIdx];\n if (!file) return;\n if (file === ctx.currentBase) {\n state.message = `${basename(file.path)} is already the base.`;\n ctx.refresh();\n return;\n }\n const wasDisabled = !state.enabled.has(file);\n if (wasDisabled) state.enabled.add(file);\n ctx.currentBase = file;\n rebuildMatrix(ctx);\n state.message = wasDisabled\n ? `${basename(file.path)} is now the base (re-enabled).`\n : `${basename(file.path)} is now the base.`;\n ctx.refresh();\n}\n","import { writeFile } from 'node:fs/promises';\nimport { serializeEnv } from '@/core/serialize.ts';\nimport { basename } from 'pathe';\nimport type { TuiContext } from '@tui/context.ts';\n\nexport async function saveDirty(ctx: TuiContext): Promise<void> {\n const { state } = ctx;\n if (state.dirty.size === 0) {\n state.message = 'Nothing to save.';\n ctx.refresh();\n return;\n }\n const count = state.dirty.size;\n const errors: string[] = [];\n for (const file of state.dirty) {\n try {\n await writeFile(file.path, serializeEnv(file), 'utf8');\n } catch (err) {\n errors.push(\n `${basename(file.path)}: ${(err as Error).message ?? String(err)}`\n );\n }\n }\n if (errors.length === 0) {\n state.dirty.clear();\n state.modified.clear();\n state.message = `Saved ${count} file${count === 1 ? '' : 's'}.`;\n } else {\n state.message = `Save failed: ${errors.join('; ')}`;\n }\n ctx.refresh();\n}\n","import { rebuildKvLine } from '@/core/serialize.ts';\nimport { basename, dirname, join } from 'pathe';\nimport type { TuiContext } from '@tui/context.ts';\nimport {\n appendKv,\n createEmptyEnvFile,\n isValidEnvFileName\n} from '@tui/envfile.ts';\nimport { findKvEntry } from '@tui/format.ts';\nimport {\n focusedKey,\n focusKey,\n markModified,\n pushUndo,\n rebuildMatrix\n} from '@tui/state/visible.ts';\nimport { KEY_RE, type Prompt } from '@tui/types.ts';\n\nexport function openPrompt(ctx: TuiContext, prompt: Prompt, value = ''): void {\n const { state } = ctx;\n state.prompt = prompt;\n state.mode = 'prompt';\n state.message = null;\n state.promptInput = value;\n ctx.refresh();\n}\n\nexport function closePrompt(ctx: TuiContext, msg: string | null = null): void {\n const { state } = ctx;\n state.prompt = null;\n state.mode = 'browse';\n state.promptInput = '';\n state.message = msg;\n ctx.refresh();\n}\n\nexport function cancelPrompt(ctx: TuiContext): void {\n closePrompt(ctx, 'Cancelled.');\n}\n\nexport function startEdit(ctx: TuiContext): void {\n const { state } = ctx;\n const key = focusedKey(ctx);\n const file = ctx.matrix.files[state.colIdx];\n if (!key || !file) {\n state.message = 'Move onto a variable row to edit.';\n ctx.refresh();\n return;\n }\n // Edit works on missing cells too — on commit we either update the\n // existing entry or append a new one.\n const entry = findKvEntry(file, key);\n openPrompt(ctx, { kind: 'edit', key, file }, entry?.value ?? '');\n}\n\nexport function startAdd(ctx: TuiContext): void {\n const file = ctx.matrix.files[ctx.state.colIdx];\n if (!file) return;\n openPrompt(ctx, { kind: 'add-key', file });\n}\n\nexport function startNewFile(ctx: TuiContext): void {\n openPrompt(ctx, { kind: 'new-file' });\n}\n\nexport function startDelete(ctx: TuiContext): void {\n const { state } = ctx;\n const key = focusedKey(ctx);\n const file = ctx.matrix.files[state.colIdx];\n if (!key || !file) {\n state.message = 'Move onto a variable row to delete.';\n ctx.refresh();\n return;\n }\n const entry = findKvEntry(file, key);\n if (!entry) {\n state.message = `${key} is not present in ${basename(file.path)}.`;\n ctx.refresh();\n return;\n }\n const idx = file.entries.indexOf(entry);\n if (idx >= 0) {\n pushUndo(ctx, { kind: 'delete-kv', file, entry, idx });\n file.entries.splice(idx, 1);\n }\n state.dirty.add(file);\n markModified(ctx, key, file);\n rebuildMatrix(ctx);\n state.message = `Deleted ${key} from ${basename(file.path)}. Ctrl-S to save.`;\n ctx.refresh();\n}\n\nexport function commitPrompt(ctx: TuiContext): void {\n const { state } = ctx;\n if (!state.prompt) return;\n const p = state.prompt;\n const raw = state.promptInput;\n\n if (p.kind === 'edit') {\n const existing = findKvEntry(p.file, p.key);\n if (existing) {\n pushUndo(ctx, {\n kind: 'edit',\n file: p.file,\n entry: existing,\n prevValue: existing.value,\n prevRaw: existing.raw\n });\n existing.value = raw;\n rebuildKvLine(existing);\n state.dirty.add(p.file);\n markModified(ctx, p.key, p.file);\n rebuildMatrix(ctx);\n closePrompt(\n ctx,\n `Edited ${p.key} in ${basename(p.file.path)}. Ctrl-S to save.`\n );\n } else {\n // Missing cell: add the key with the typed value.\n const added = appendKv(p.file, p.key, raw);\n pushUndo(ctx, { kind: 'add-kv', file: p.file, entry: added });\n state.dirty.add(p.file);\n markModified(ctx, p.key, p.file);\n rebuildMatrix(ctx);\n closePrompt(\n ctx,\n `Added ${p.key} to ${basename(p.file.path)}. Ctrl-S to save.`\n );\n }\n return;\n }\n\n if (p.kind === 'add-key') {\n const key = raw.trim();\n if (!KEY_RE.test(key)) {\n state.message = `Invalid key \"${key}\". Must match ${KEY_RE.source}.`;\n ctx.refresh();\n return;\n }\n if (findKvEntry(p.file, key)) {\n state.message = `${key} already exists in ${basename(p.file.path)}. Use edit instead.`;\n ctx.refresh();\n return;\n }\n openPrompt(ctx, { kind: 'add-value', key, file: p.file });\n return;\n }\n\n if (p.kind === 'add-value') {\n const added = appendKv(p.file, p.key, raw);\n pushUndo(ctx, { kind: 'add-kv', file: p.file, entry: added });\n state.dirty.add(p.file);\n markModified(ctx, p.key, p.file);\n rebuildMatrix(ctx);\n focusKey(ctx, p.key);\n state.colIdx = ctx.matrix.files.indexOf(p.file);\n closePrompt(\n ctx,\n `Added ${p.key} to ${basename(p.file.path)}. Ctrl-S to save.`\n );\n return;\n }\n\n if (p.kind === 'new-file') {\n const name = raw.trim();\n if (!isValidEnvFileName(name)) {\n state.message =\n name.length === 0\n ? 'Filename cannot be empty.'\n : !name.startsWith('.env')\n ? `Filename must start with \".env\" (got \"${name}\").`\n : `\"${name}\" is not a valid env filename.`;\n ctx.refresh();\n return;\n }\n const newPath = join(dirname(ctx.currentBase.path), name);\n if (ctx.allFiles.some((f) => f.path === newPath)) {\n state.message = `${name} already exists.`;\n ctx.refresh();\n return;\n }\n const newFile = createEmptyEnvFile(newPath);\n ctx.allFiles.push(newFile);\n state.enabled.add(newFile);\n state.dirty.add(newFile);\n rebuildMatrix(ctx);\n state.colIdx = ctx.matrix.files.indexOf(newFile);\n closePrompt(ctx, `Created ${name}. Ctrl-S to write to disk.`);\n return;\n }\n}\n","import { setBase, syncToAll, toggleEnabled, undo } from '@tui/actions/batch.ts';\nimport { saveDirty } from '@tui/actions/io.ts';\nimport {\n startAdd,\n startDelete,\n startEdit,\n startNewFile\n} from '@tui/actions/prompt.ts';\nimport type { TuiContext } from '@tui/context.ts';\nimport { SECTION_COLLAPSE_KEY, stepRow } from '@tui/grouping.ts';\nimport type { KeyEvent } from '@tui/keys/event.ts';\nimport { recomputeVisibleKeys } from '@tui/state/visible.ts';\n\nexport function handleBrowseKey(\n ctx: TuiContext,\n key: KeyEvent,\n cleanup: () => void\n): void {\n const { state } = ctx;\n const refresh = ctx.refresh;\n\n if (key.ctrl && key.name === 'c') return cleanup();\n if (key.ctrl && key.name === 's') {\n state.confirmQuit = false;\n return void saveDirty(ctx);\n }\n if (key.ctrl && key.name === 'z') {\n state.confirmQuit = false;\n return undo(ctx);\n }\n if (key.ctrl && key.name === 't') {\n state.showSecrets = !state.showSecrets;\n state.message = state.showSecrets\n ? 'Showing secret values in plain text.'\n : 'Masking secret values.';\n return refresh();\n }\n\n const tryQuit = () => {\n if (state.dirty.size > 0 && !state.confirmQuit) {\n state.confirmQuit = true;\n state.message = `${state.dirty.size} unsaved file(s). Press 'q' again to quit without saving, or Ctrl-S to save first.`;\n refresh();\n return;\n }\n cleanup();\n };\n // Any key other than q clears a pending quit confirmation.\n if (state.confirmQuit && key.name !== 'q') {\n state.confirmQuit = false;\n state.message = null;\n }\n\n if (state.pane === 'sidebar') {\n switch (key.name) {\n case 'q':\n return tryQuit();\n case 'tab':\n state.pane = 'matrix';\n return refresh();\n case 'right':\n state.pane = 'matrix';\n return refresh();\n case 'up':\n state.sidebarIdx = Math.max(0, state.sidebarIdx - 1);\n return refresh();\n case 'down':\n state.sidebarIdx = Math.min(\n ctx.allFiles.length - 1,\n state.sidebarIdx + 1\n );\n return refresh();\n case 'space':\n return toggleEnabled(ctx);\n case 'b':\n return setBase(ctx);\n }\n if (key.sequence === ' ') return toggleEnabled(ctx);\n if (key.sequence === '?' || key.sequence === 'ß') {\n state.helpOpen = true;\n return refresh();\n }\n return;\n }\n\n switch (key.name) {\n case 'q':\n return tryQuit();\n case 'tab':\n state.pane = 'sidebar';\n return refresh();\n case 'up':\n state.rowIdx = stepRow(state, -1);\n return refresh();\n case 'down':\n state.rowIdx = stepRow(state, 1);\n return refresh();\n case 'left':\n if (state.colIdx === 0) {\n // Already at the leftmost matrix column — hand focus to the sidebar.\n state.pane = 'sidebar';\n return refresh();\n }\n state.colIdx = Math.max(0, state.colIdx - 1);\n return refresh();\n case 'right':\n state.colIdx = Math.min(ctx.matrix.files.length - 1, state.colIdx + 1);\n return refresh();\n case 'e':\n case 'return':\n return startEdit(ctx);\n case 'a':\n return startAdd(ctx);\n case 'd':\n return startDelete(ctx);\n case 'n':\n return startNewFile(ctx);\n case 'v':\n state.driftOnly = !state.driftOnly;\n state.message = state.driftOnly\n ? 'Drift-only view (only keys with differences).'\n : 'Full view.';\n recomputeVisibleKeys(ctx);\n return refresh();\n case 'c': {\n if (key.shift) {\n // Capital C — expand every collapsed section. Use this when all\n // keys you'd navigate to are hidden behind a fold.\n if (state.collapsed.size === 0) {\n state.message = 'Nothing collapsed.';\n return refresh();\n }\n const count = state.collapsed.size;\n state.collapsed.clear();\n state.message = `Expanded ${count} section(s).`;\n recomputeVisibleKeys(ctx);\n return refresh();\n }\n // Collapse / expand the section of the focused item. Works on\n // both key rows and section dividers; on a divider this is the\n // only way back into a collapsed section.\n const item = state.visibleItems[state.rowIdx];\n if (!item) return;\n const sectionKey =\n item.kind === 'divider'\n ? item.ref\n : (ctx.sectionOf(item.ref) ?? SECTION_COLLAPSE_KEY);\n const label =\n sectionKey === SECTION_COLLAPSE_KEY ? '(other)' : sectionKey;\n if (state.collapsed.has(sectionKey)) {\n state.collapsed.delete(sectionKey);\n state.message = `Expanded \"${label}\".`;\n } else {\n state.collapsed.add(sectionKey);\n state.message = `Collapsed \"${label}\". Press Shift-C to expand all.`;\n }\n recomputeVisibleKeys(ctx);\n return refresh();\n }\n case 'g': {\n // Preserve focus across the rebuild — focusedRef in\n // recomputeVisibleKeys finds the same item by ref.\n state.grouping = state.grouping === 'banner' ? 'prefix' : 'banner';\n recomputeVisibleKeys(ctx);\n state.message =\n state.grouping === 'banner'\n ? 'Group by comment banners.'\n : 'Group by key prefix (first underscore segment).';\n return refresh();\n }\n }\n\n if (key.sequence === '/' || key.name === 'slash') {\n state.mode = 'filter';\n state.message = null;\n refresh();\n return;\n }\n\n // = sync-to-all. opentui's key.name for \"=\" is inconsistent across\n // platforms so we check the sequence directly.\n if (key.sequence === '=') return syncToAll(ctx);\n\n if (key.sequence === '?' || key.sequence === 'ß') {\n state.helpOpen = true;\n refresh();\n }\n}\n","import type { TuiContext } from '@tui/context.ts';\nimport type { KeyEvent } from '@tui/keys/event.ts';\nimport { recomputeVisibleKeys } from '@tui/state/visible.ts';\n\nexport function handleFilterKey(ctx: TuiContext, key: KeyEvent): void {\n const { state } = ctx;\n if (key.name === 'escape') {\n state.filter = '';\n state.mode = 'browse';\n recomputeVisibleKeys(ctx);\n ctx.refresh();\n return;\n }\n if (key.name === 'return') {\n state.mode = 'browse';\n ctx.refresh();\n return;\n }\n if (key.name === 'backspace') {\n if (state.filter.length > 0) {\n state.filter = state.filter.slice(0, -1);\n recomputeVisibleKeys(ctx);\n ctx.refresh();\n }\n return;\n }\n // Append any printable character. opentui's KeyEvent puts the actual\n // char into `sequence` for normal keystrokes.\n const seq = key.sequence ?? '';\n if (seq.length === 1 && seq >= ' ' && seq !== '\\x7f') {\n state.filter += seq;\n recomputeVisibleKeys(ctx);\n ctx.refresh();\n }\n}\n","import type { TuiContext } from '@tui/context.ts';\nimport type { KeyEvent } from '@tui/keys/event.ts';\n\nexport function handleHelpKey(ctx: TuiContext, key: KeyEvent): void {\n if (\n key.name === 'escape' ||\n key.sequence === '?' ||\n key.sequence === 'ß' ||\n key.name === 'q'\n ) {\n ctx.state.helpOpen = false;\n ctx.refresh();\n }\n}\n","import { applyToAllFiles } from '@tui/actions/batch.ts';\nimport {\n cancelPrompt,\n closePrompt,\n commitPrompt\n} from '@tui/actions/prompt.ts';\nimport type { TuiContext } from '@tui/context.ts';\nimport type { KeyEvent } from '@tui/keys/event.ts';\nimport { rebuildMatrix } from '@tui/state/visible.ts';\n\nexport function handlePromptKey(ctx: TuiContext, key: KeyEvent): void {\n const { state } = ctx;\n if (key.name === 'escape') {\n cancelPrompt(ctx);\n return;\n }\n if (key.name === 'return') {\n commitPrompt(ctx);\n return;\n }\n if (key.name === 'backspace') {\n if (state.promptInput.length > 0) {\n state.promptInput = state.promptInput.slice(0, -1);\n ctx.refresh();\n }\n return;\n }\n if (key.ctrl && key.name === 't') {\n state.showSecrets = !state.showSecrets;\n ctx.refresh();\n return;\n }\n if (key.ctrl && key.name === 'a' && state.prompt) {\n const p = state.prompt;\n if (p.kind === 'edit' || p.kind === 'add-value') {\n const touched = applyToAllFiles(ctx, p.key, state.promptInput);\n rebuildMatrix(ctx);\n closePrompt(\n ctx,\n touched > 0\n ? `Set ${p.key} in ${touched} file(s). Ctrl-S to save.`\n : `${p.key} already had that value everywhere.`\n );\n }\n return;\n }\n const seq = key.sequence ?? '';\n if (!key.ctrl && seq.length === 1 && seq >= ' ' && seq !== '\\x7f') {\n state.promptInput += seq;\n ctx.refresh();\n }\n}\n","import type { TuiContext } from '@tui/context.ts';\nimport { handleBrowseKey } from '@tui/keys/browse.ts';\nimport type { KeyEvent } from '@tui/keys/event.ts';\nimport { handleFilterKey } from '@tui/keys/filter.ts';\nimport { handleHelpKey } from '@tui/keys/help.ts';\nimport { handlePromptKey } from '@tui/keys/prompt.ts';\n\n/**\n * Build the global keypress handler. Dispatches by current mode; `cleanup`\n * tears down the renderer and resolves the run promise (used by Ctrl-C and\n * the quit-confirm path in browse mode).\n */\nexport function createOnKey(\n ctx: TuiContext,\n cleanup: () => void\n): (key: KeyEvent) => void {\n return (key: KeyEvent) => {\n if (ctx.state.helpOpen) return handleHelpKey(ctx, key);\n if (ctx.state.mode === 'prompt') return handlePromptKey(ctx, key);\n if (ctx.state.mode === 'filter') return handleFilterKey(ctx, key);\n handleBrowseKey(ctx, key, cleanup);\n };\n}\n","import { RGBA } from '@opentui/core';\n\n// Three semantic colours only. Everything else is grayscale so the eye\n// doesn't get pulled in five directions.\nexport const COLORS = {\n fg: RGBA.fromHex('#cccccc'),\n fgDim: RGBA.fromHex('#666666'),\n fgHeader: RGBA.fromHex('#ffffff'),\n // accent — base file + section names (blue/purple, single accent for\n // navigational anchors).\n fgBase: RGBA.fromHex('#82aaff'),\n fgSection: RGBA.fromHex('#82aaff'),\n // drift/extra/placeholder — same yellow. They describe disagreement with\n // the base file, all one semantic.\n differs: RGBA.fromHex('#ffd866'),\n extra: RGBA.fromHex('#ffd866'),\n placeholder: RGBA.fromHex('#ffd866'),\n // user-made changes (modified cells, dirty files, unsaved counter) — green.\n // Different colour from drift on purpose: \"I just touched this\" is a\n // different signal from \"this disagrees with the base\".\n modified: RGBA.fromHex('#7fce6a'),\n fgDirty: RGBA.fromHex('#7fce6a'),\n // problem — value-is-missing red. Reserved exclusively for missing.\n missing: RGBA.fromHex('#ff6b6b'),\n focusBg: RGBA.fromHex('#3a3f4b')\n};\n\nexport const KEY_COL_WIDTH = 22;\nexport const VALUE_COL_MIN = 18;\nexport const SIDEBAR_WIDTH = 30;\nexport const ROW_GAP = 0;\nexport const CELL_PAD_X = 1;\n","import {\n BoxRenderable,\n type CliRenderer,\n RGBA,\n ScrollBoxRenderable,\n TextRenderable\n} from '@opentui/core';\nimport type { TuiElements } from '@tui/context.ts';\nimport { COLORS, ROW_GAP, SIDEBAR_WIDTH } from '@tui/theme.ts';\n\n/**\n * Build the static element tree once and return stable handles. Content is\n * (re)populated later by the refreshers; this only establishes the layout\n * skeleton and parent/child wiring.\n */\nexport function buildLayout(renderer: CliRenderer): TuiElements {\n const root = new BoxRenderable(renderer, {\n id: 'root',\n flexDirection: 'column',\n width: '100%',\n height: '100%'\n });\n renderer.root.add(root);\n\n const body = new BoxRenderable(renderer, {\n id: 'body',\n flexDirection: 'row',\n flexGrow: 1\n });\n root.add(body);\n\n const sidebar = new BoxRenderable(renderer, {\n id: 'sidebar',\n border: true,\n borderStyle: 'rounded',\n title: '',\n flexDirection: 'column',\n width: SIDEBAR_WIDTH,\n flexShrink: 0,\n paddingX: 1\n });\n body.add(sidebar);\n\n const matrixBox = new BoxRenderable(renderer, {\n id: 'matrix',\n border: true,\n borderStyle: 'rounded',\n title: '',\n flexDirection: 'column',\n flexGrow: 1,\n paddingX: 1,\n // Reserve a row at the bottom so the ScrollBox's horizontal scrollbar\n // does not overlap the matrix box's bottom border.\n paddingBottom: 1\n });\n body.add(matrixBox);\n\n // Header sits above the scrollable region so it doesn't scroll out of view.\n const headerHost = new BoxRenderable(renderer, {\n id: 'header-host',\n flexDirection: 'column',\n flexShrink: 0,\n // Match the ScrollBox viewport padding so the header column boundaries\n // line up with the data rows below.\n paddingRight: 1\n });\n matrixBox.add(headerHost);\n\n const scrollBox = new ScrollBoxRenderable(renderer, {\n id: 'matrix-scroll',\n flexGrow: 1,\n scrollX: true,\n scrollY: true,\n // Reserve a column on the right so the vertical scrollbar doesn't sit on\n // top of cell content, and a row at the bottom for the horizontal one.\n viewportOptions: { paddingRight: 1, paddingBottom: 1 },\n contentOptions: { flexDirection: 'column', rowGap: ROW_GAP }\n });\n matrixBox.add(scrollBox);\n\n const footer = new BoxRenderable(renderer, {\n id: 'footer',\n flexDirection: 'column',\n flexShrink: 0,\n paddingX: 1\n });\n root.add(footer);\n\n const hintA = new BoxRenderable(renderer, {\n id: 'hint-a',\n flexDirection: 'row',\n height: 1,\n flexShrink: 0\n });\n footer.add(hintA);\n\n const hintB = new BoxRenderable(renderer, {\n id: 'hint-b',\n flexDirection: 'row',\n height: 1,\n flexShrink: 0\n });\n footer.add(hintB);\n\n const status = new TextRenderable(renderer, {\n id: 'status',\n content: '',\n fg: COLORS.fgDim,\n wrapMode: 'none',\n height: 1\n });\n footer.add(status);\n\n // Filter has its own popover so we don't fight opentui's input-focus\n // behaviour. state.filter is the source of truth; characters are\n // accumulated by the global keypress handler when state.mode === 'filter'.\n const filterBox = new BoxRenderable(renderer, {\n id: 'filter-box',\n position: 'absolute',\n top: '15%',\n left: '20%',\n right: '20%',\n height: 'auto',\n zIndex: 60,\n border: true,\n borderStyle: 'rounded',\n title: ' Filter keys ',\n paddingX: 2,\n paddingY: 1,\n visible: false,\n backgroundColor: RGBA.fromHex('#1a1a1a'),\n flexDirection: 'column'\n });\n const filterField = new TextRenderable(renderer, {\n id: 'filter-field',\n content: '',\n fg: COLORS.fg,\n height: 1,\n wrapMode: 'none'\n });\n const filterStatus = new TextRenderable(renderer, {\n id: 'filter-status',\n content: '',\n fg: COLORS.fgDim,\n height: 1,\n marginTop: 1,\n wrapMode: 'none'\n });\n const filterHint = new TextRenderable(renderer, {\n id: 'filter-hint',\n content: 'Enter · keep filter Esc · clear & close',\n fg: COLORS.fg,\n height: 1,\n marginTop: 1,\n wrapMode: 'none'\n });\n filterBox.add(filterField);\n filterBox.add(filterStatus);\n filterBox.add(filterHint);\n renderer.root.add(filterBox);\n\n // --- Prompt modal (used for edit / add / new-file) ---\n const promptBox = new BoxRenderable(renderer, {\n id: 'prompt-box',\n position: 'absolute',\n top: '20%',\n left: '15%',\n right: '15%',\n height: 'auto',\n zIndex: 50,\n border: true,\n borderStyle: 'rounded',\n title: '',\n paddingX: 2,\n paddingY: 1,\n visible: false,\n backgroundColor: RGBA.fromHex('#1a1a1a'),\n flexDirection: 'column'\n });\n // Body of the modal is rebuilt on every refresh — either the context table\n // (edit / add-value) or just a single input row (add-key / new-file).\n const promptBody = new BoxRenderable(renderer, {\n id: 'prompt-body',\n flexDirection: 'column',\n flexGrow: 1\n });\n const promptHint = new TextRenderable(renderer, {\n id: 'prompt-hint',\n content: '',\n fg: COLORS.fg,\n height: 1,\n marginTop: 2,\n wrapMode: 'none'\n });\n promptBox.add(promptBody);\n promptBox.add(promptHint);\n renderer.root.add(promptBox);\n\n // Full-screen dim layer drawn below the prompt and help overlays so the\n // matrix fades back when a modal is active. Solid dark backgroundColor at\n // reduced opacity feels like a real dim/scrim.\n const dimOverlay = new BoxRenderable(renderer, {\n id: 'dim-overlay',\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n zIndex: 40,\n backgroundColor: RGBA.fromHex('#000000'),\n opacity: 0.6,\n visible: false\n });\n renderer.root.add(dimOverlay);\n\n // Help overlay — outer Box stays absolute-positioned; the body is rebuilt\n // every time it opens so we can switch between a one-column scrollable\n // layout (small terminals) and a two-column grid (wide terminals).\n const helpBox = new BoxRenderable(renderer, {\n id: 'help-overlay',\n position: 'absolute',\n top: 2,\n bottom: 2,\n left: '8%',\n right: '8%',\n zIndex: 100,\n border: true,\n borderStyle: 'rounded',\n title: ' Keybindings — press ? or Esc to close ',\n paddingX: 2,\n paddingY: 1,\n visible: false,\n backgroundColor: RGBA.fromHex('#1a1a1a'),\n flexDirection: 'column'\n });\n renderer.root.add(helpBox);\n\n return {\n root,\n body,\n sidebar,\n matrixBox,\n headerHost,\n scrollBox,\n footer,\n hintA,\n hintB,\n status,\n filterBox,\n filterField,\n filterStatus,\n promptBox,\n promptBody,\n promptHint,\n helpBox,\n dimOverlay\n };\n}\n","import type { TuiContext } from '@tui/context.ts';\n\nexport function refreshFilter(ctx: TuiContext): void {\n const {\n el: { filterBox, filterField, filterStatus },\n matrix,\n state\n } = ctx;\n const open = state.mode === 'filter';\n filterBox.visible = open;\n if (!open) return;\n // Show the current filter with a fake cursor at the end. visibleKeys is\n // already filtered, so its length is the live match count.\n filterField.content = `▸ ${state.filter}▏`;\n const matches = state.visibleKeys.length;\n const total = matrix.keys.length;\n filterStatus.content =\n state.filter.length === 0\n ? 'Type to filter the keys list.'\n : `Matching ${matches} of ${total} keys.`;\n}\n","import type { BoxRenderable } from '@opentui/core';\n\nexport function removeAllChildren(node: BoxRenderable): void {\n const ids = node.getChildren().map((c) => c.id);\n for (const id of ids) node.remove(id);\n}\n","import {\n type BoxRenderable,\n type CliRenderer,\n type RGBA,\n TextRenderable\n} from '@opentui/core';\nimport type { TuiContext } from '@tui/context.ts';\nimport { removeAllChildren } from '@tui/render/dom.ts';\nimport { COLORS } from '@tui/theme.ts';\n\nexport interface FooterSeg {\n text: string;\n fg: RGBA;\n}\n\nexport function bindings(specs: { key: string; label: string }[]): FooterSeg[] {\n // \"[key] label\" with the brackets dim and the key + label in normal fg.\n // Segments separated by dim \" · \".\n const out: FooterSeg[] = [];\n specs.forEach((spec, i) => {\n if (i > 0) out.push({ text: ' · ', fg: COLORS.fgDim });\n out.push({ text: '[', fg: COLORS.fgDim });\n out.push({ text: spec.key, fg: COLORS.fg });\n out.push({ text: '] ', fg: COLORS.fgDim });\n out.push({ text: spec.label, fg: COLORS.fg });\n });\n return out;\n}\n\nexport function renderHintBox(\n box: BoxRenderable,\n renderer: CliRenderer,\n segs: FooterSeg[]\n): void {\n removeAllChildren(box);\n segs.forEach((seg, i) => {\n box.add(\n new TextRenderable(renderer, {\n id: `${box.id}-seg-${i}`,\n content: seg.text,\n fg: seg.fg,\n height: 1,\n wrapMode: 'none'\n })\n );\n });\n}\n\nexport function refreshFooter(ctx: TuiContext): void {\n const {\n el: { hintA, hintB, status },\n renderer,\n state\n } = ctx;\n const dirty = state.dirty.size;\n const dirtyTail: FooterSeg[] =\n dirty > 0\n ? [\n { text: ' ', fg: COLORS.fgDim },\n { text: '●', fg: COLORS.modified },\n { text: ` ${dirty} unsaved`, fg: COLORS.fg }\n ]\n : [];\n\n if (state.mode === 'filter') {\n renderHintBox(hintA, renderer, [\n ...bindings([\n { key: 'Enter', label: 'keep filter' },\n { key: 'Esc', label: 'clear' }\n ]),\n ...dirtyTail\n ]);\n renderHintBox(hintB, renderer, [{ text: ' Filter:', fg: COLORS.fgDim }]);\n } else if (state.mode === 'prompt') {\n renderHintBox(hintA, renderer, []);\n renderHintBox(hintB, renderer, []);\n } else if (state.pane === 'sidebar') {\n renderHintBox(hintA, renderer, [\n ...bindings([\n { key: '↑↓', label: 'move' },\n { key: 'Space', label: 'toggle' },\n { key: 'b', label: 'set base' },\n { key: 'Tab/→', label: 'matrix' },\n { key: '^S', label: 'save' },\n { key: '?', label: 'help' },\n { key: 'q', label: 'quit' }\n ]),\n ...dirtyTail\n ]);\n renderHintBox(hintB, renderer, [{ text: 'Files pane', fg: COLORS.fgDim }]);\n } else {\n renderHintBox(hintA, renderer, [\n ...bindings([\n { key: '↑↓←→', label: 'move' },\n { key: 'Tab', label: 'files' },\n { key: 'e', label: 'edit' },\n { key: 'a', label: 'add var' },\n { key: 'd', label: 'del var' },\n { key: 'n', label: 'new file' },\n { key: '=', label: 'sync to all' },\n { key: 'c', label: 'collapse' },\n { key: '^T', label: 'secrets' },\n { key: '^Z', label: 'undo' },\n { key: '^S', label: 'save' },\n { key: '/', label: 'filter' },\n { key: '?/ß', label: 'help' },\n { key: 'q', label: 'quit' }\n ]),\n ...dirtyTail\n ]);\n renderHintBox(hintB, renderer, [\n { text: 'view: ', fg: COLORS.fgDim },\n { text: state.driftOnly ? 'drift' : 'all', fg: COLORS.fg },\n { text: ' · group: ', fg: COLORS.fgDim },\n { text: state.grouping, fg: COLORS.fg },\n { text: ' · secrets: ', fg: COLORS.fgDim },\n { text: state.showSecrets ? 'shown' : 'masked', fg: COLORS.fg }\n ]);\n }\n status.content = state.message ?? '';\n}\n","import { RGBA } from '@opentui/core';\nimport type { HelpLine } from '@tui/types.ts';\n\nexport function buildHelpLines(): HelpLine[] {\n return [\n { kind: 'header', text: 'Panes' },\n {\n kind: 'entry',\n text: ' Tab Switch matrix ↔ files sidebar'\n },\n {\n kind: 'entry',\n text: ' ← (leftmost col) Hop from matrix into the sidebar'\n },\n { kind: 'blank' },\n { kind: 'header', text: 'Matrix navigation' },\n { kind: 'entry', text: ' ↑ ↓ ← → Move focused cell' },\n { kind: 'entry', text: ' Mouse wheel Scroll (both axes)' },\n { kind: 'blank' },\n { kind: 'header', text: 'Files sidebar' },\n { kind: 'entry', text: ' ↑ ↓ Move selection' },\n { kind: 'entry', text: ' Space Enable / disable file' },\n { kind: 'entry', text: ' b Make selected file the base' },\n { kind: 'entry', text: ' Tab / → Back to matrix' },\n { kind: 'blank' },\n { kind: 'header', text: 'Editing' },\n { kind: 'entry', text: ' e / Enter Edit focused cell value' },\n { kind: 'entry', text: ' a Add a new variable here' },\n {\n kind: 'entry',\n text: ' d Delete the variable from this file'\n },\n { kind: 'entry', text: ' n Create a new .env* file' },\n {\n kind: 'entry',\n text: ' = Sync focused value to every file'\n },\n {\n kind: 'entry',\n text: ' Ctrl-A (in edit) Apply typed value to every file'\n },\n { kind: 'entry', text: ' Ctrl-Z Undo last edit/add/delete' },\n { kind: 'entry', text: ' Ctrl-S Write all dirty files' },\n {\n kind: 'entry',\n text: ' c Collapse / expand focused section'\n },\n {\n kind: 'entry',\n text: ' Shift-C Expand every collapsed section'\n },\n { kind: 'blank' },\n { kind: 'header', text: 'View' },\n { kind: 'entry', text: ' / Filter keys' },\n { kind: 'entry', text: ' v All keys ↔ drift-only' },\n { kind: 'entry', text: ' g Group by prefix ↔ banner' },\n { kind: 'entry', text: ' Ctrl-T Show / mask secret values' },\n { kind: 'blank' },\n { kind: 'header', text: 'Help & exit' },\n { kind: 'entry', text: ' ? / ß Toggle this overlay' },\n { kind: 'entry', text: ' q Quit (twice if dirty)' },\n { kind: 'entry', text: ' Ctrl-C Force quit' },\n { kind: 'blank' },\n { kind: 'header', text: 'Cell icons' },\n {\n kind: 'legend',\n symbol: '≠ value',\n color: RGBA.fromHex('#ffd866'),\n description: 'value differs from base'\n },\n {\n kind: 'legend',\n symbol: '✗ missing',\n color: RGBA.fromHex('#ff6b6b'),\n description: 'this file has no value for the key'\n },\n {\n kind: 'legend',\n symbol: '★ value',\n color: RGBA.fromHex('#ffd866'),\n description: 'key is not in the base'\n },\n {\n kind: 'legend',\n symbol: '•••• (N)',\n color: RGBA.fromHex('#cccccc'),\n description: 'secret-suspect value masked by length'\n },\n {\n kind: 'legend',\n symbol: '⚠ TODO',\n color: RGBA.fromHex('#ffd866'),\n description: 'placeholder value (TODO, CHANGEME, xxx, …)'\n },\n {\n kind: 'legend',\n symbol: 'value ●',\n color: RGBA.fromHex('#7fce6a'),\n description: 'modified in this session — Ctrl-S to persist'\n }\n ];\n}\n","import {\n BoxRenderable,\n type CliRenderer,\n RGBA,\n TextRenderable\n} from '@opentui/core';\nimport type { CellState } from '@/core/matrix.ts';\nimport { formatValue, isPlaceholderValue, truncate } from '@tui/format.ts';\nimport type { SectionStats } from '@tui/grouping.ts';\nimport { CELL_PAD_X, COLORS } from '@tui/theme.ts';\nimport type { HelpLine } from '@tui/types.ts';\n\nexport interface CellSpec {\n text: string;\n fg: RGBA;\n width: number;\n bg?: RGBA;\n // Optional coloured prefix — rendered in its own Text span so only the\n // icon carries the state colour, the text stays neutral.\n icon?: { char: string; fg: RGBA };\n // Optional coloured marker that sits at the right edge of the cell.\n // Used for the modified-since-load indicator.\n trailing?: { char: string; fg: RGBA };\n}\n\nexport function buildValueCell(\n cell: { state: CellState; value: string | undefined },\n secret: boolean,\n width: number,\n focused: boolean,\n modified: boolean\n): CellSpec {\n const bg = focused ? COLORS.focusBg : undefined;\n const trailing = modified ? { char: '●', fg: COLORS.modified } : undefined;\n if (cell.state === 'missing') {\n return {\n text: 'missing',\n fg: COLORS.fgDim,\n width,\n bg,\n icon: { char: '✗', fg: COLORS.missing },\n trailing\n };\n }\n const value = cell.value ?? '';\n if (value !== '' && isPlaceholderValue(value)) {\n return {\n text: value,\n fg: COLORS.fg,\n width,\n bg,\n icon: { char: '⚠', fg: COLORS.placeholder },\n trailing\n };\n }\n const isEmpty = value === '' && !secret;\n const displayText = isEmpty ? '(empty)' : formatValue(value, secret);\n const displayFg = isEmpty ? COLORS.fgDim : COLORS.fg;\n if (cell.state === 'differs') {\n return {\n text: displayText,\n fg: displayFg,\n width,\n bg,\n icon: { char: '≠', fg: COLORS.differs },\n trailing\n };\n }\n if (cell.state === 'extra') {\n return {\n text: displayText,\n fg: displayFg,\n width,\n bg,\n icon: { char: '★', fg: COLORS.extra },\n trailing\n };\n }\n return { text: displayText, fg: displayFg, width, bg, trailing };\n}\n\nexport function buildHelpRow(\n renderer: CliRenderer,\n id: string,\n line: HelpLine\n): BoxRenderable {\n const row = new BoxRenderable(renderer, {\n id,\n flexDirection: 'row',\n height: 1,\n flexShrink: 0\n });\n if (line.kind === 'header') {\n row.add(\n new TextRenderable(renderer, {\n id: `${id}-t`,\n content: line.text,\n fg: COLORS.fgSection,\n wrapMode: 'none',\n height: 1\n })\n );\n } else if (line.kind === 'entry') {\n row.add(\n new TextRenderable(renderer, {\n id: `${id}-t`,\n content: line.text,\n fg: COLORS.fg,\n wrapMode: 'none',\n height: 1\n })\n );\n } else if (line.kind === 'legend') {\n row.add(\n new TextRenderable(renderer, {\n id: `${id}-sym`,\n content: ` ${line.symbol.padEnd(12)}`,\n fg: line.color,\n wrapMode: 'none',\n height: 1\n })\n );\n row.add(\n new TextRenderable(renderer, {\n id: `${id}-desc`,\n content: line.description,\n fg: COLORS.fgDim,\n wrapMode: 'none',\n height: 1\n })\n );\n }\n return row;\n}\n\nexport function buildSectionDivider(\n renderer: CliRenderer,\n id: string,\n name: string | undefined,\n width: number,\n meta: SectionStats & { collapsed: boolean; focused?: boolean }\n): BoxRenderable {\n // Multi-segment divider so colours can encode meaning:\n // gray ─── blue ▾ Name dim · stats gray ───\n // Drift counts go yellow; if any key in the section is missing in any\n // non-base file we surface that explicitly in red (\"✗ N missing\"), even\n // when the section also has plain differs.\n const baseName = name ?? '(other)';\n const indicator = meta.collapsed ? '▸' : '▾';\n // Icons + numbers carry colour (red for missing, yellow for drift). Name\n // and descriptive words render in the normal foreground; the \"/\" and\n // trailing whitespace stay dim.\n type Seg = { text: string; fg: RGBA };\n const segs: Seg[] = [\n { text: ` ${indicator} `, fg: COLORS.fgDim },\n { text: baseName, fg: COLORS.fg },\n { text: ' ', fg: COLORS.fgDim }\n ];\n if (meta.missing > 0) {\n segs.push({ text: '✗ ', fg: COLORS.missing });\n segs.push({ text: `${meta.missing}`, fg: COLORS.missing });\n segs.push({ text: ' missing ', fg: COLORS.fg });\n }\n if (meta.drift > 0) {\n segs.push({ text: '≠ ', fg: COLORS.differs });\n segs.push({ text: `${meta.drift}`, fg: COLORS.differs });\n segs.push({ text: '/', fg: COLORS.fgDim });\n segs.push({ text: `${meta.total}`, fg: COLORS.differs });\n segs.push({ text: ' drift ', fg: COLORS.fg });\n }\n if (meta.missing === 0 && meta.drift === 0) {\n segs.push({ text: `${meta.total}`, fg: COLORS.fgDim });\n segs.push({ text: ' keys ', fg: COLORS.fg });\n }\n segs.push({ text: ' ', fg: COLORS.fgDim });\n\n const labelLength = segs.reduce((sum, s) => sum + s.text.length, 0);\n const rule = '─';\n const visible = Math.max(0, width - 2);\n const beforeLen = Math.max(2, Math.floor((visible - labelLength) / 2));\n const afterLen = Math.max(0, visible - beforeLen - labelLength);\n\n const box = new BoxRenderable(renderer, {\n id,\n flexDirection: 'row',\n flexShrink: 0,\n height: 1,\n paddingX: 1,\n ...(meta.focused ? { backgroundColor: COLORS.focusBg } : {})\n });\n box.add(\n new TextRenderable(renderer, {\n id: `${id}-lead`,\n content: rule.repeat(beforeLen),\n fg: COLORS.fgDim,\n height: 1,\n wrapMode: 'none'\n })\n );\n segs.forEach((seg, i) => {\n box.add(\n new TextRenderable(renderer, {\n id: `${id}-seg-${i}`,\n content: seg.text,\n fg: seg.fg,\n height: 1,\n wrapMode: 'none'\n })\n );\n });\n box.add(\n new TextRenderable(renderer, {\n id: `${id}-trail`,\n content: rule.repeat(afterLen),\n fg: COLORS.fgDim,\n height: 1,\n wrapMode: 'none'\n })\n );\n return box;\n}\n\nexport function buildRow(\n renderer: CliRenderer,\n idPrefix: string,\n cells: CellSpec[]\n): BoxRenderable {\n const row = new BoxRenderable(renderer, {\n id: idPrefix,\n flexDirection: 'row',\n flexShrink: 0,\n height: 1\n });\n cells.forEach((cell, i) => {\n const cellOpts: ConstructorParameters<typeof BoxRenderable>[1] = {\n id: `${idPrefix}-c${i}`,\n width: cell.width,\n height: 1,\n flexDirection: 'row',\n flexShrink: 0,\n paddingX: CELL_PAD_X\n };\n if (cell.bg) cellOpts.backgroundColor = cell.bg;\n const cellBox = new BoxRenderable(renderer, cellOpts);\n const innerWidth = Math.max(0, cell.width - CELL_PAD_X * 2);\n const iconLen = cell.icon ? cell.icon.char.length + 1 : 0;\n const trailingLen = cell.trailing ? cell.trailing.char.length + 1 : 0;\n const textWidth = Math.max(0, innerWidth - iconLen - trailingLen);\n if (cell.icon) {\n cellBox.add(\n new TextRenderable(renderer, {\n id: `${idPrefix}-c${i}-icon`,\n content: `${cell.icon.char} `,\n fg: cell.icon.fg,\n height: 1,\n wrapMode: 'none'\n })\n );\n }\n cellBox.add(\n new TextRenderable(renderer, {\n id: `${idPrefix}-c${i}-t`,\n content: truncate(cell.text, textWidth),\n fg: cell.fg,\n height: 1,\n flexGrow: 1,\n wrapMode: 'none'\n })\n );\n if (cell.trailing) {\n cellBox.add(\n new TextRenderable(renderer, {\n id: `${idPrefix}-c${i}-trail`,\n content: ` ${cell.trailing.char}`,\n fg: cell.trailing.fg,\n height: 1,\n wrapMode: 'none'\n })\n );\n }\n row.add(cellBox);\n });\n return row;\n}\n","import { BoxRenderable, ScrollBoxRenderable } from '@opentui/core';\nimport type { TuiContext } from '@tui/context.ts';\nimport { buildHelpLines } from '@tui/help.ts';\nimport { buildHelpRow } from '@tui/render/builders.ts';\nimport { removeAllChildren } from '@tui/render/dom.ts';\n\nexport function refreshHelp(ctx: TuiContext): void {\n const {\n el: { helpBox },\n renderer,\n state\n } = ctx;\n helpBox.visible = state.helpOpen;\n if (!state.helpOpen) return;\n removeAllChildren(helpBox);\n const lines = buildHelpLines();\n // Use one scrollable column when the terminal is narrow or short.\n const narrow = renderer.terminalWidth < 100;\n const short = renderer.terminalHeight < 36;\n const oneColumn = narrow || short;\n if (oneColumn) {\n const scroll = new ScrollBoxRenderable(renderer, {\n id: 'help-scroll',\n flexGrow: 1,\n scrollX: false,\n scrollY: true,\n viewportOptions: { paddingRight: 1 },\n contentOptions: { flexDirection: 'column' }\n });\n helpBox.add(scroll);\n lines.forEach((line, i) => {\n scroll.content.add(buildHelpRow(renderer, `help-${i}`, line));\n });\n return;\n }\n // Two-column grid.\n const grid = new BoxRenderable(renderer, {\n id: 'help-grid',\n flexDirection: 'row',\n flexGrow: 1,\n columnGap: 3\n });\n const left = new BoxRenderable(renderer, {\n id: 'help-left',\n flexDirection: 'column',\n flexGrow: 1,\n flexBasis: 0\n });\n const right = new BoxRenderable(renderer, {\n id: 'help-right',\n flexDirection: 'column',\n flexGrow: 1,\n flexBasis: 0\n });\n const half = Math.floor(lines.length / 2);\n let splitIdx = half;\n for (let i = half; i < lines.length; i++) {\n if (lines[i]?.kind === 'blank') {\n splitIdx = i + 1;\n break;\n }\n }\n lines\n .slice(0, splitIdx)\n .forEach((line, i) =>\n left.add(buildHelpRow(renderer, `help-l-${i}`, line))\n );\n lines\n .slice(splitIdx)\n .forEach((line, i) =>\n right.add(buildHelpRow(renderer, `help-r-${i}`, line))\n );\n grid.add(left);\n grid.add(right);\n helpBox.add(grid);\n}\n","import { isSecretKey } from '@/core/mask.ts';\nimport type { Matrix } from '@/core/matrix.ts';\nimport { basename } from 'pathe';\nimport type { TuiContext } from '@tui/context.ts';\nimport { sectionMetadata } from '@tui/grouping.ts';\nimport {\n buildRow,\n buildSectionDivider,\n buildValueCell,\n type CellSpec\n} from '@tui/render/builders.ts';\nimport { removeAllChildren } from '@tui/render/dom.ts';\nimport {\n COLORS,\n KEY_COL_WIDTH,\n SIDEBAR_WIDTH,\n VALUE_COL_MIN\n} from '@tui/theme.ts';\nimport type { State } from '@tui/types.ts';\n\nexport function matrixTitle(matrix: Matrix, state: State): string {\n const visible = state.visibleKeys.length;\n const total = matrix.keys.length;\n const parts: string[] = [`${total} keys`];\n if (state.driftOnly) parts.push(`drift ${visible}/${total}`);\n else if (state.filter && visible !== total) {\n parts.push(`\"${state.filter}\" ${visible}/${total}`);\n }\n return ` Matrix · ${parts.join(' · ')} `;\n}\n\n/**\n * Available width inside the matrix box (subtract sidebar, both borders and the\n * matrix's horizontal padding). If columns would shrink below VALUE_COL_MIN to\n * fit, keep them at the minimum and let the ScrollBox handle the overflow.\n */\nexport function computeValueColWidth(ctx: TuiContext): number {\n const { renderer, matrix } = ctx;\n const available = Math.max(\n 0,\n renderer.terminalWidth - SIDEBAR_WIDTH - 6 - KEY_COL_WIDTH\n );\n const fair = matrix.files.length\n ? Math.floor(available / matrix.files.length)\n : VALUE_COL_MIN;\n return Math.max(VALUE_COL_MIN, fair);\n}\n\nexport function refreshMatrix(ctx: TuiContext): void {\n const {\n el: { matrixBox, headerHost, scrollBox },\n renderer,\n matrix,\n state\n } = ctx;\n const sectionOf = ctx.sectionOf;\n const valueColWidth = computeValueColWidth(ctx);\n matrixBox.title = matrixTitle(matrix, state);\n removeAllChildren(headerHost);\n removeAllChildren(scrollBox.content);\n\n headerHost.add(\n buildRow(renderer, 'header', [\n { text: 'KEY', fg: COLORS.fgHeader, width: KEY_COL_WIDTH },\n ...matrix.files.map((f) => ({\n text: basename(f.path),\n fg: COLORS.fgHeader,\n width: valueColWidth\n }))\n ])\n );\n\n // Walk every key (including those hidden by a collapsed section) so we can\n // render section dividers for collapsed groups too. Within an expanded\n // group we render the cell rows; within a collapsed one we render nothing\n // beyond the header.\n const totalWidth = KEY_COL_WIDTH + valueColWidth * matrix.files.length;\n const sectionStats = sectionMetadata(matrix, sectionOf, state);\n\n for (let r = 0; r < state.visibleItems.length; r++) {\n const item = state.visibleItems[r]!;\n if (item.kind === 'divider') {\n const sectionKey = item.ref;\n const sectionName = sectionKey === '__other__' ? undefined : sectionKey;\n const meta = sectionStats.get(sectionKey) ?? {\n drift: 0,\n missing: 0,\n total: 0\n };\n const focused = state.mode === 'browse' && r === state.rowIdx;\n scrollBox.content.add(\n buildSectionDivider(renderer, `row-${r}`, sectionName, totalWidth, {\n ...meta,\n collapsed: state.collapsed.has(sectionKey),\n focused\n })\n );\n continue;\n }\n const key = item.ref;\n const secret = isSecretKey(key) && !state.showSecrets;\n const cells: CellSpec[] = [\n { text: key, fg: COLORS.fg, width: KEY_COL_WIDTH }\n ];\n for (let c = 0; c < matrix.files.length; c++) {\n const file = matrix.files[c]!;\n const cell = matrix.cell(key, file);\n const focused =\n state.mode === 'browse' && r === state.rowIdx && c === state.colIdx;\n const isModified = state.modified.has(`${key}|${file.path}`);\n cells.push(\n buildValueCell(cell, secret, valueColWidth, focused, isModified)\n );\n }\n scrollBox.content.add(buildRow(renderer, `row-${r}`, cells));\n }\n\n // Keep the focused row in view. Single deferred call so layout has been\n // computed before we ask for a scroll target; doing it twice (sync +\n // deferred) was causing visible \"jumps\" because the two calls landed on\n // slightly different layouts.\n if (state.mode === 'browse' && state.visibleItems.length > 0) {\n const target = `row-${state.rowIdx}`;\n setImmediate(() => {\n try {\n scrollBox.scrollChildIntoView(target);\n } catch {\n /* row not laid out yet — next refresh will retry */\n }\n });\n }\n}\n","import { BoxRenderable, TextRenderable } from '@opentui/core';\nimport { isSecretKey } from '@/core/mask.ts';\nimport { basename } from 'pathe';\nimport type { TuiContext } from '@tui/context.ts';\nimport { findKvEntry, formatValue } from '@tui/format.ts';\nimport { removeAllChildren } from '@tui/render/dom.ts';\nimport { COLORS } from '@tui/theme.ts';\nimport type { Prompt } from '@tui/types.ts';\n\nexport function promptLabelText(p: Prompt): string {\n switch (p.kind) {\n case 'edit':\n return ` Edit ${p.key} in ${basename(p.file.path)}:`;\n case 'add-key':\n return ` Add new key to ${basename(p.file.path)}:`;\n case 'add-value':\n return ` Value for ${p.key} in ${basename(p.file.path)}:`;\n case 'new-file':\n return ' New env file name (e.g. .env.local):';\n }\n}\n\nexport function refreshPrompt(ctx: TuiContext): void {\n const {\n el: { promptBox, promptBody, promptHint },\n renderer,\n matrix,\n state\n } = ctx;\n const open = state.mode === 'prompt' && state.prompt !== null;\n promptBox.visible = open;\n if (!open || !state.prompt) return;\n\n promptBox.title = promptLabelText(state.prompt);\n // Hint depends on which prompt is active — only edit/add-value support\n // the apply-to-all + show-secrets shortcuts.\n const p = state.prompt;\n if (p.kind === 'edit' || p.kind === 'add-value') {\n promptHint.content =\n 'Enter · confirm Ctrl-A · apply to all ' +\n 'Ctrl-T · show/mask secrets Esc · cancel';\n } else {\n promptHint.content = 'Enter · confirm Esc · cancel';\n }\n\n // Body layout: full-width input on top, then a context table of every file\n // and its current value (read-only). For add-key / new-file there's no\n // context to show — just the input.\n removeAllChildren(promptBody);\n // Input is rendered as plain text so we can guarantee char + Esc handling\n // ourselves (opentui's InputRenderable swallows Esc as \"blur\").\n promptBody.add(\n new TextRenderable(renderer, {\n id: 'prompt-input-text',\n content: `▸ ${state.promptInput}▏`,\n fg: COLORS.fg,\n height: 1,\n wrapMode: 'none'\n })\n );\n // Show validation errors inside the modal so they aren't hidden behind\n // the dim overlay. state.message is set by commitPrompt when input is\n // invalid (and cleared on each fresh openPrompt).\n if (state.message) {\n promptBody.add(\n new TextRenderable(renderer, {\n id: 'prompt-error',\n content: `! ${state.message}`,\n fg: COLORS.missing,\n height: 1,\n marginTop: 1,\n wrapMode: 'none'\n })\n );\n }\n\n if (p.kind === 'edit' || p.kind === 'add-value') {\n const secret = isSecretKey(p.key) && !state.showSecrets;\n const nameWidth = Math.min(\n 26,\n Math.max(...matrix.files.map((f) => basename(f.path).length + 2))\n );\n\n promptBody.add(\n new TextRenderable(renderer, {\n id: 'prompt-table-header',\n content: 'Current values',\n fg: COLORS.fgSection,\n wrapMode: 'none',\n height: 1,\n marginTop: 1\n })\n );\n\n for (const file of matrix.files) {\n const isTarget = file === p.file;\n const row = new BoxRenderable(renderer, {\n id: `prompt-row-${file.path}`,\n flexDirection: 'row',\n height: 1,\n flexShrink: 0\n });\n row.add(\n new TextRenderable(renderer, {\n id: `prompt-row-${file.path}-name`,\n content: `${isTarget ? '▸' : ' '} ${basename(file.path)}`.padEnd(\n nameWidth\n ),\n fg: isTarget ? COLORS.fgBase : COLORS.fgDim,\n height: 1,\n wrapMode: 'none'\n })\n );\n const entry = findKvEntry(file, p.key);\n const current = entry ? formatValue(entry.value, secret) : '✗ missing';\n row.add(\n new TextRenderable(renderer, {\n id: `prompt-row-${file.path}-value`,\n content: current,\n fg: !entry ? COLORS.missing : isTarget ? COLORS.fg : COLORS.fgDim,\n height: 1,\n wrapMode: 'none'\n })\n );\n promptBody.add(row);\n }\n }\n}\n","import { BoxRenderable, type RGBA, TextRenderable } from '@opentui/core';\nimport { basename } from 'pathe';\nimport type { TuiContext } from '@tui/context.ts';\nimport { removeAllChildren } from '@tui/render/dom.ts';\nimport { COLORS } from '@tui/theme.ts';\n\nexport function refreshSidebar(ctx: TuiContext): void {\n const {\n el: { sidebar },\n renderer,\n matrix,\n allFiles,\n state\n } = ctx;\n const total = allFiles.length;\n const enabled = state.enabled.size;\n sidebar.title =\n state.pane === 'sidebar'\n ? ` Files ${enabled}/${total} • focused `\n : ` Files ${enabled}/${total} `;\n removeAllChildren(sidebar);\n for (let i = 0; i < allFiles.length; i++) {\n const file = allFiles[i]!;\n const isBase = file === matrix.base;\n const isDirty = state.dirty.has(file);\n const isEnabled = state.enabled.has(file);\n const matrixIdx = matrix.files.indexOf(file);\n const isFocusCol = isEnabled && matrixIdx === state.colIdx;\n const isPaneFocus = state.pane === 'sidebar' && i === state.sidebarIdx;\n const nameFg = !isEnabled\n ? COLORS.fgDim\n : isBase\n ? COLORS.fgBase\n : COLORS.fg;\n\n const row = new BoxRenderable(renderer, {\n id: `file-${file.path}`,\n flexDirection: 'row',\n height: 1,\n flexShrink: 0,\n ...(isPaneFocus ? { backgroundColor: COLORS.focusBg } : {})\n });\n const span = (id: string, text: string, fg: RGBA) =>\n new TextRenderable(renderer, {\n id: `${row.id}-${id}`,\n content: text,\n fg,\n height: 1,\n wrapMode: 'none'\n });\n row.add(span('focus', `${isPaneFocus ? '▶' : ' '} `, COLORS.fg));\n row.add(\n span(\n 'dirty',\n `${isDirty ? '●' : ' '} `,\n isDirty ? COLORS.fgDirty : COLORS.fgDim\n )\n );\n row.add(\n span(\n 'base',\n `${isBase ? '★' : ' '} `,\n isBase ? COLORS.fgBase : COLORS.fgDim\n )\n );\n row.add(span('col', `${isFocusCol ? '▸' : ' '} `, COLORS.fgDim));\n row.add(span('enabled', `${isEnabled ? '✓' : '☐'} `, COLORS.fgDim));\n row.add(span('name', basename(file.path), nameFg));\n sidebar.add(row);\n }\n}\n","import type { TuiContext } from '@tui/context.ts';\nimport { refreshFilter } from '@tui/render/filter.ts';\nimport { refreshFooter } from '@tui/render/footer.ts';\nimport { refreshHelp } from '@tui/render/help.ts';\nimport { refreshMatrix } from '@tui/render/matrix.ts';\nimport { refreshPrompt } from '@tui/render/prompt.ts';\nimport { refreshSidebar } from '@tui/render/sidebar.ts';\n\n/**\n * Full synchronous render of every region. Reads `ctx.matrix` fresh so a\n * `rebuildMatrix` that ran between scheduling and this flush is reflected.\n */\nexport function refreshAll(ctx: TuiContext): void {\n const { state } = ctx;\n refreshSidebar(ctx);\n refreshMatrix(ctx);\n refreshFooter(ctx);\n refreshPrompt(ctx);\n refreshHelp(ctx);\n refreshFilter(ctx);\n ctx.el.dimOverlay.visible =\n state.helpOpen || state.mode === 'prompt' || state.mode === 'filter';\n}\n","import { createCliRenderer } from '@opentui/core';\nimport type { Matrix } from '@/core/matrix.ts';\nimport type { State } from '@tui/types.ts';\nimport { prefixSection } from '@tui/grouping.ts';\nimport type { TuiContext } from '@tui/context.ts';\nimport { createOnKey } from '@tui/keys/onKey.ts';\nimport { buildLayout } from '@tui/render/layout.ts';\nimport { refreshAll } from '@tui/render/index.ts';\nimport { recomputeVisibleKeys } from '@tui/state/visible.ts';\n\nexport async function runMatrixTui(initialMatrix: Matrix): Promise<void> {\n const renderer = await createCliRenderer({ useMouse: true });\n // The full discovered file list never changes; the matrix is rebuilt from\n // the currently *enabled* subset whenever the user toggles a file.\n const allFiles = initialMatrix.files.slice();\n\n // Prefer banner grouping when the base file actually has section banners;\n // otherwise prefix grouping is more useful out of the box.\n const hasBanners = initialMatrix.keys.some(\n (k) => initialMatrix.sectionOf(k) !== undefined\n );\n const state: State = {\n mode: 'browse',\n filter: '',\n rowIdx: 0,\n colIdx: 0,\n prompt: null,\n dirty: new Set(),\n visibleKeys: initialMatrix.keys.slice(),\n visibleItems: [],\n message: null,\n driftOnly: false,\n confirmQuit: false,\n grouping: hasBanners ? 'banner' : 'prefix',\n helpOpen: false,\n undo: [],\n pane: 'matrix',\n sidebarIdx: 0,\n enabled: new Set(allFiles),\n showSecrets: false,\n collapsed: new Set(),\n modified: new Set(),\n promptInput: ''\n };\n\n const el = buildLayout(renderer);\n\n // Coalesce burst-y refreshes (held arrow keys, fast filter typing) into one\n // render per microtask flush. refreshAll rebuilds every matrix row, which is\n // expensive per keystroke; batching keeps held keys in opentui's own redraw\n // loop instead of our re-render.\n let refreshScheduled = false;\n const ctx: TuiContext = {\n renderer,\n state,\n allFiles,\n el,\n matrix: initialMatrix,\n currentBase: initialMatrix.base,\n refresh: () => {\n if (refreshScheduled) return;\n refreshScheduled = true;\n queueMicrotask(() => {\n refreshScheduled = false;\n refreshAll(ctx);\n });\n },\n refreshNow: () => refreshAll(ctx),\n sectionOf: (key: string): string | undefined =>\n state.grouping === 'banner'\n ? ctx.matrix.sectionOf(key)\n : prefixSection(key)\n };\n\n recomputeVisibleKeys(ctx);\n ctx.refreshNow();\n\n // --- Interaction ---\n return new Promise<void>((resolve) => {\n const cleanup = () => {\n renderer._internalKeyInput.offInternal('keypress', onKey);\n renderer.destroy?.();\n resolve();\n };\n\n const onKey = createOnKey(ctx, cleanup);\n\n // Use the internal channel so our handler runs *before* the focused\n // renderable processes the event. That lets us intercept Esc / Enter\n // before opentui's InputRenderable swallows them (Esc would otherwise\n // just blur the input instead of closing the modal).\n renderer._internalKeyInput.onInternal('keypress', onKey);\n renderer.on('resize', ctx.refresh);\n });\n}\n"],"names":["i"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"mask-Bv9W6Ei8.mjs","sources":["../../src/core/serialize.ts","../../src/core/mask.ts"],"sourcesContent":["import type { EnvFile, KvEntry, Quoting } from '@/core/types.ts';\n\n/**\n * Serialize an {@link EnvFile} back to a string. Unchanged entries are emitted\n * from their captured `raw` line so round-trip is byte-exact; modified entries\n * must have their `raw` field re-built via {@link rebuildKvLine} first.\n */\nexport function serializeEnv(file: EnvFile): string {\n const body = file.entries.map((e) => e.raw).join('\\n');\n return file.trailingNewline ? `${body}\\n` : body;\n}\n\n/**\n * Rebuild the `raw` line of a kv entry from its structured fields. Call this\n * after mutating `key`, `value`, `quoting`, or `exportPrefix` so the next\n * serialize emits the new line.\n */\nexport function rebuildKvLine(entry: KvEntry): void {\n const prefix = entry.exportPrefix ? 'export ' : '';\n const encoded = encodeValue(entry.value, entry.quoting);\n // Update rawValue to match the (possibly new) encoded inner form.\n entry.rawValue = encoded.inner;\n entry.raw = `${prefix}${entry.key}=${encoded.full}${entry.inlineComment}`;\n}\n\ninterface EncodedValue {\n /** Inner string without surrounding quotes (for `rawValue`). */\n inner: string;\n /** Fully formed value including surrounding quotes if any. */\n full: string;\n}\n\nfunction encodeValue(value: string, quoting: Quoting): EncodedValue {\n // Auto-promote to double quotes when an unquoted value would be ambiguous\n // (contains whitespace, `#`, or characters that require escaping). Single\n // quotes are preserved as authored but can't represent embedded `'`.\n const effective: Quoting = needsQuoting(value, quoting) ? 'double' : quoting;\n\n switch (effective) {\n case 'none':\n return { inner: value, full: value };\n case 'single': {\n if (value.includes(\"'\")) {\n // Fall back to double-quoting if a single quote sneaks in.\n const escaped = escapeDoubleQuoted(value);\n return { inner: escaped, full: `\"${escaped}\"` };\n }\n return { inner: value, full: `'${value}'` };\n }\n case 'double': {\n const escaped = escapeDoubleQuoted(value);\n return { inner: escaped, full: `\"${escaped}\"` };\n }\n }\n}\n\nfunction needsQuoting(value: string, current: Quoting): boolean {\n if (current !== 'none') return false;\n return /[\\s#\"'\\\\]/.test(value);\n}\n\nfunction escapeDoubleQuoted(value: string): string {\n return value\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t');\n}\n","const SECRET_TOKENS = [\n 'SECRET',\n 'TOKEN',\n 'PASSWORD',\n 'PASSWD',\n 'PWD',\n 'KEY',\n 'PRIVATE',\n 'CREDENTIAL',\n 'AUTH',\n 'DSN'\n] as const;\n\n/**\n * Heuristic: should a value with this key be masked by default? Matches any\n * underscore-separated segment of {@link SECRET_TOKENS} (case-insensitive),\n * with carve-outs for false positives like `*_PUBLIC_KEY`, `PUBLIC_*`, and\n * keys whose final segment is `ID` (e.g. `API_KEY_ID` is an identifier, not\n * a secret).\n */\nexport function isSecretKey(key: string): boolean {\n const upper = key.toUpperCase();\n const segments = upper.split('_').filter(Boolean);\n if (segments.length === 0) return false;\n if (segments[segments.length - 1] === 'ID') return false;\n if (segments[0] === 'PUBLIC') return false;\n if (segments.includes('PUBLIC')) return false;\n\n return segments.some((seg) =>\n SECRET_TOKENS.some((token) => seg === token || seg.endsWith(token))\n );\n}\n\n/**\n * Render a masked placeholder that hints at the original value's length\n * without leaking content. Returns `••••` when the input is empty.\n */\nexport function maskValue(value: string): string {\n if (value.length === 0) return '••••';\n const dots = '•'.repeat(Math.min(value.length, 8));\n return value.length > 8 ? `${dots} (${value.length})` : dots;\n}\n"],"names":[],"mappings":"AAOO,SAAS,aAAa,MAAuB;AAClD,QAAM,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,IAAI;AACrD,SAAO,KAAK,kBAAkB,GAAG,IAAI;AAAA,IAAO;AAC9C;AAOO,SAAS,cAAc,OAAsB;AAClD,QAAM,SAAS,MAAM,eAAe,YAAY;AAChD,QAAM,UAAU,YAAY,MAAM,OAAO,MAAM,OAAO;AAEtD,QAAM,WAAW,QAAQ;AACzB,QAAM,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,QAAQ,IAAI,GAAG,MAAM,aAAa;AACzE;AASA,SAAS,YAAY,OAAe,SAAgC;AAIlE,QAAM,YAAqB,aAAa,OAAO,OAAO,IAAI,WAAW;AAErE,UAAQ,WAAA;AAAA,IACN,KAAK;AACH,aAAO,EAAE,OAAO,OAAO,MAAM,MAAA;AAAA,IAC/B,KAAK,UAAU;AACb,UAAI,MAAM,SAAS,GAAG,GAAG;AAEvB,cAAM,UAAU,mBAAmB,KAAK;AACxC,eAAO,EAAE,OAAO,SAAS,MAAM,IAAI,OAAO,IAAA;AAAA,MAC5C;AACA,aAAO,EAAE,OAAO,OAAO,MAAM,IAAI,KAAK,IAAA;AAAA,IACxC;AAAA,IACA,KAAK,UAAU;AACb,YAAM,UAAU,mBAAmB,KAAK;AACxC,aAAO,EAAE,OAAO,SAAS,MAAM,IAAI,OAAO,IAAA;AAAA,IAC5C;AAAA,EAAA;AAEJ;AAEA,SAAS,aAAa,OAAe,SAA2B;AAC9D,MAAI,YAAY,OAAQ,QAAO;AAC/B,SAAO,YAAY,KAAK,KAAK;AAC/B;AAEA,SAAS,mBAAmB,OAAuB;AACjD,SAAO,MACJ,QAAQ,OAAO,MAAM,EACrB,QAAQ,MAAM,KAAK,EACnB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK,EACpB,QAAQ,OAAO,KAAK;AACzB;ACpEA,MAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AASO,SAAS,YAAY,KAAsB;AAChD,QAAM,QAAQ,IAAI,YAAA;AAClB,QAAM,WAAW,MAAM,MAAM,GAAG,EAAE,OAAO,OAAO;AAChD,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI,SAAS,SAAS,SAAS,CAAC,MAAM,KAAM,QAAO;AACnD,MAAI,SAAS,CAAC,MAAM,SAAU,QAAO;AACrC,MAAI,SAAS,SAAS,QAAQ,EAAG,QAAO;AAExC,SAAO,SAAS;AAAA,IAAK,CAAC,QACpB,cAAc,KAAK,CAAC,UAAU,QAAQ,SAAS,IAAI,SAAS,KAAK,CAAC;AAAA,EAAA;AAEtE;AAMO,SAAS,UAAU,OAAuB;AAC/C,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,OAAO,IAAI,OAAO,KAAK,IAAI,MAAM,QAAQ,CAAC,CAAC;AACjD,SAAO,MAAM,SAAS,IAAI,GAAG,IAAI,KAAK,MAAM,MAAM,MAAM;AAC1D;"}