import { app } from 'electron' import { join } from 'path' import { readFileSync, writeFileSync, existsSync } from 'fs' import type { CommandTemplate } from '@shared/ipc' import { isTemplateLike } from './validation' // Plain JSON in userData, same shape as history.ts. const MAX_TEMPLATES = 100 function templatesFile(): string { return join(app.getPath('userData'), 'templates.json') } // A persisted template entry must at least be an object carrying an id (see // isTemplateLike in validation.ts); everything else is coerced by sanitize(). // Drop anything that isn't, so a hand-edited templates.json can't inject // malformed entries. (audit S5) export function listTemplates(): CommandTemplate[] { try { if (!existsSync(templatesFile())) return [] const data = JSON.parse(readFileSync(templatesFile(), 'utf8')) // Validate shape, then normalise each surviving entry through sanitize() so // name/args are always well-formed strings regardless of what was on disk. return Array.isArray(data) ? data.filter(isTemplateLike).map(sanitize) : [] } catch { return [] } } function save(templates: CommandTemplate[]): void { try { writeFileSync(templatesFile(), JSON.stringify(templates.slice(0, MAX_TEMPLATES), null, 2)) } catch { /* best-effort; a read-only data dir just means no persisted templates */ } } function sanitize(t: CommandTemplate): CommandTemplate { const urlPattern = typeof t.urlPattern === 'string' ? t.urlPattern.trim() : '' return { id: String(t.id), name: (t.name ?? '').trim() || 'Untitled command', args: (t.args ?? '').trim(), // Only persist a urlPattern when one was provided, keeping older files clean. ...(urlPattern ? { urlPattern } : {}) } } /** Add a new template, or update an existing one (matched by id). */ export function saveTemplate(template: CommandTemplate): CommandTemplate[] { const clean = sanitize(template) const templates = [clean, ...listTemplates().filter((t) => t.id !== clean.id)] save(templates) return templates } export function removeTemplate(id: string): CommandTemplate[] { const templates = listTemplates().filter((t) => t.id !== id) save(templates) return templates } /** Replace the entire template list (backup restore) rather than merge-by-id. */ export function replaceTemplates(templates: CommandTemplate[]): CommandTemplate[] { const clean = templates.map(sanitize) save(clean) return clean }