loader.ts

 1import { readFile } from "node:fs/promises";
 2import { resolve } from "node:path";
 3import { defaultConfig } from "./defaults.js";
 4import type { RumiloConfig } from "./schema.js";
 5import { ConfigError } from "../util/errors.js";
 6import toml from "toml";
 7
 8export interface LoadedConfig {
 9  config: RumiloConfig;
10  path?: string;
11}
12
13function mergeConfig(base: RumiloConfig, override: Partial<RumiloConfig>): RumiloConfig {
14  return {
15    defaults: {
16      ...base.defaults,
17      ...override.defaults,
18    },
19    web: {
20      ...base.web,
21      ...override.web,
22    },
23    repo: {
24      ...base.repo,
25      ...override.repo,
26    },
27    custom_models: {
28      ...base.custom_models,
29      ...override.custom_models,
30    },
31  };
32}
33
34function validateConfig(config: RumiloConfig): void {
35  if (!config.defaults.model) {
36    throw new ConfigError("defaults.model is required");
37  }
38  if (typeof config.defaults.cleanup !== "boolean") {
39    throw new ConfigError("defaults.cleanup must be a boolean");
40  }
41}
42
43export async function loadConfig(): Promise<LoadedConfig> {
44  const base = structuredClone(defaultConfig);
45  const configHome = process.env["XDG_CONFIG_HOME"] || `${process.env["HOME"] ?? ""}/.config`;
46  const configPath = resolve(configHome, "rumilo", "config.toml");
47
48  try {
49    const raw = await readFile(configPath, "utf8");
50    const parsed = toml.parse(raw) as Partial<RumiloConfig>;
51    const merged = mergeConfig(base, parsed);
52    validateConfig(merged);
53    return { config: merged, path: configPath };
54  } catch (error: any) {
55    if (error?.code === "ENOENT") {
56      validateConfig(base);
57      return { config: base };
58    }
59
60    if (error instanceof Error) {
61      throw new ConfigError(error.message);
62    }
63
64    throw new ConfigError(String(error));
65  }
66}
67
68export function applyConfigOverrides(
69  config: RumiloConfig,
70  overrides: Partial<RumiloConfig>,
71): RumiloConfig {
72  const merged = mergeConfig(config, overrides);
73  validateConfig(merged);
74  return merged;
75}