init helder

This commit is contained in:
2026-06-15 22:33:46 +02:00
parent 3d77fdfeff
commit 3f5078841d
38 changed files with 6349 additions and 2083 deletions

93
src/main/config.ts Normal file
View File

@@ -0,0 +1,93 @@
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
/**
* Project-scoped settings, living in `.helder/` in the opened project's root.
* - config.default.json full built-in defaults, REGENERATED on every launch
* (live documentation; the app never reads user edits here)
* - config.json sparse — only user-overridden values
* - theme.css custom CSS over the built-in dark theme; the CODE FONT
* and FONT SIZE live here (as CSS vars), not in the JSON
* Effective value = config.json over config.default.json, merged key by key.
*/
export interface HelderConfig {
ai: { command: string; autoLaunch: boolean }
editor: { autoSave: boolean; tabSize: number }
git: { confirmDiscard: boolean }
terminal: { shell: string | null }
}
export const DEFAULTS: HelderConfig = {
ai: { command: 'claude', autoLaunch: true },
editor: { autoSave: false, tabSize: 4 },
git: { confirmDiscard: true },
terminal: { shell: null },
}
const THEME_TEMPLATE = `/* Helder theme — custom CSS applied OVER the built-in dark theme.
* This file is created once and never overwritten; edit it freely.
* The code font and font size live here (not in config.json). Uncomment and
* tweak any variable below; you can also override any --token from the built-in
* theme (see the design tokens in the app's styles). */
:root {
/* Code surfaces (editor + terminals) */
/* --code-font: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; */
/* --code-size: 13px; */ /* editor font size */
/* --term-size: 12.5px; */ /* terminal font size */
/* Example accent override: */
/* --accent: #4d8dff; */
}
`
let current: HelderConfig = DEFAULTS
let themeCss = ''
function isPlainObject(v: unknown): v is Record<string, unknown> {
return !!v && typeof v === 'object' && !Array.isArray(v)
}
function deepMerge<T>(base: T, over: unknown): T {
if (!isPlainObject(base) || !isPlainObject(over)) return base
const out: Record<string, unknown> = { ...base }
for (const key of Object.keys(over)) {
const b = (base as Record<string, unknown>)[key]
const o = over[key]
if (isPlainObject(b) && isPlainObject(o)) out[key] = deepMerge(b, o)
else if (o !== undefined) out[key] = o
}
return out as T
}
/** (Re)resolve config + theme for a project root, regenerating the defaults file. */
export async function resolveConfig(root: string): Promise<void> {
const dir = join(root, '.helder')
try {
await mkdir(dir, { recursive: true })
// Always regenerate the defaults file — it documents every setting.
await writeFile(join(dir, 'config.default.json'), JSON.stringify(DEFAULTS, null, 2) + '\n')
let override: unknown = {}
try { override = JSON.parse(await readFile(join(dir, 'config.json'), 'utf8')) } catch { /* none / invalid */ }
current = deepMerge(DEFAULTS, override)
try {
themeCss = await readFile(join(dir, 'theme.css'), 'utf8')
} catch {
themeCss = THEME_TEMPLATE
await writeFile(join(dir, 'theme.css'), THEME_TEMPLATE)
}
} catch {
// Read-only / inaccessible root: fall back to built-in defaults.
current = DEFAULTS
themeCss = ''
}
}
export function getConfig(): HelderConfig {
return current
}
export function getThemeCss(): string {
return themeCss
}