import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; import { parseCanonicalKeymap } from "./canonical-parser.js"; import type { ReadlineKeymap } from "./keymap.js"; import { parseReadlineVariables, type ReadlineVariables } from "./variables.js"; export type ReadlineProfileMetadata = { profileName: string; bash: { executable: string; version: string; }; readline: { version: string; }; }; export type ResolvedReadlineProfile = { name: string; metadata: ReadlineProfileMetadata; viCommand: ReadlineKeymap; viInsert: ReadlineKeymap; variables: ReadlineVariables; }; function isProfileMetadata(value: unknown): value is ReadlineProfileMetadata { if (typeof value !== "object" || value === null) return false; const metadata = value as Record; const bash = metadata.bash; const readline = metadata.readline; return ( typeof metadata.profileName === "string" && typeof bash === "object" && bash !== null && typeof (bash as Record).executable === "string" && typeof (bash as Record).version === "string" && typeof readline === "object" && readline !== null && typeof (readline as Record).version === "string" ); } function assertSafeProfileName(name: string): void { if (!/^[a-z0-9-]+$/.test(name)) { throw new RangeError(`Invalid Readline profile name: ${name}`); } } /** Load checked-in default bindings for one major-version Bash/Readline profile. */ export async function loadBundledReadlineProfile( name: string, ): Promise { assertSafeProfileName(name); const directory = resolve(import.meta.dirname, "../../profiles", name); try { const [metadataSource, viCommandSource, viInsertSource, variablesSource] = await Promise.all([ readFile(resolve(directory, "metadata.json"), "utf8"), readFile(resolve(directory, "vi-command.inputrc"), "utf8"), readFile(resolve(directory, "vi-insert.inputrc"), "utf8"), readFile(resolve(directory, "variables.inputrc"), "utf8"), ]); const metadata: unknown = JSON.parse(metadataSource); if (!isProfileMetadata(metadata)) { throw new Error("metadata.json has an unsupported schema"); } if (metadata.profileName !== name) { throw new Error( `metadata.json declares ${metadata.profileName}, expected ${name}`, ); } return { name, metadata, viCommand: parseCanonicalKeymap("vi-command", viCommandSource), viInsert: parseCanonicalKeymap("vi-insert", viInsertSource), variables: parseReadlineVariables(variablesSource), }; } catch (error) { const reason = error instanceof Error ? error.message : String(error); throw new Error(`Could not load Readline profile ${name}: ${reason}`, { cause: error, }); } }