// Server-side persistence for search aliases. Two source files: // - global: paths.globalAliasesFile (/search-aliases.json) // - per-site: sites//search-aliases.json // The editor authors both; the export build merges them (per-site shadows // global by id) into each site's shipped /search-aliases.json. When the global // file is absent it falls back to the seeded DEFAULT_ALIASES so a fresh install // ships useful suggestions out of the box; a missing per-site file = no // overrides. See common/lib/searchAliases.ts for the pure model + merge. import { readFileSync } from "node:fs"; import { writeJsonAtomicSync } from "./jsonFile-server"; import type { Paths } from "./paths"; import { siteAliasesFile } from "./site"; import { coerceAliasConfig, mergeAliases, DEFAULT_ALIASES, type AliasConfig, type SearchAlias, } from "./searchAliases"; // Indented, no trailing newline — these files' historical bytes. function writeJsonAtomic(filePath: string, value: unknown): void { writeJsonAtomicSync(filePath, value, { newline: false, mkdir: true }); } // Global dictionary. Absent/unreadable → the seeded defaults (a fresh install // still offers suggestions). An empty-but-present file → no aliases (the // operator deleted the defaults deliberately). export function readGlobalAliases(paths: Paths): AliasConfig { try { return coerceAliasConfig( JSON.parse(readFileSync(paths.globalAliasesFile, "utf8")), ); } catch { return { aliases: [...DEFAULT_ALIASES] }; } } export function writeGlobalAliases(paths: Paths, config: AliasConfig): void { writeJsonAtomic(paths.globalAliasesFile, config); } // Per-site overrides. Absent/unreadable → empty (no overrides). export function readSiteAliases(paths: Paths, siteId: string): AliasConfig { try { return coerceAliasConfig( JSON.parse(readFileSync(siteAliasesFile(paths, siteId), "utf8")), ); } catch { return { aliases: [] }; } } export function writeSiteAliases( paths: Paths, siteId: string, config: AliasConfig, ): void { writeJsonAtomic(siteAliasesFile(paths, siteId), config); } // The list a site actually ships: global merged with its per-site overrides. // Read directly by compose-site.ts at build time (source files are available // there, like duplicates.json), so there's no separate staging step. export function effectiveSiteAliases(paths: Paths, siteId: string): SearchAlias[] { return mergeAliases( readGlobalAliases(paths).aliases, readSiteAliases(paths, siteId).aliases, ); }