Files
Toju/electron/theme-library.ts
2026-04-02 00:08:38 +02:00

92 lines
2.6 KiB
TypeScript

import { app } from 'electron';
import * as fsp from 'fs/promises';
import * as path from 'path';
export interface SavedThemeFileDescriptor {
fileName: string;
modifiedAt: number;
path: string;
}
const SAVED_THEME_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.json$/;
function resolveSavedThemesPath(): string {
return path.join(app.getPath('userData'), 'themes');
}
async function ensureSavedThemesPath(): Promise<string> {
const themesPath = resolveSavedThemesPath();
await fsp.mkdir(themesPath, { recursive: true });
return themesPath;
}
function assertSavedThemeFileName(fileName: string): string {
const normalized = typeof fileName === 'string'
? fileName.trim()
: '';
if (!SAVED_THEME_FILE_NAME_PATTERN.test(normalized) || normalized.includes('..')) {
throw new Error('Invalid saved theme file name.');
}
return normalized;
}
async function resolveSavedThemeFilePath(fileName: string): Promise<string> {
const themesPath = await ensureSavedThemesPath();
return path.join(themesPath, assertSavedThemeFileName(fileName));
}
export async function getSavedThemesPath(): Promise<string> {
return await ensureSavedThemesPath();
}
export async function listSavedThemes(): Promise<SavedThemeFileDescriptor[]> {
const themesPath = await ensureSavedThemesPath();
const entries = await fsp.readdir(themesPath, { withFileTypes: true });
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.json'));
const descriptors = await Promise.all(files.map(async (entry) => {
const filePath = path.join(themesPath, entry.name);
const stats = await fsp.stat(filePath);
return {
fileName: entry.name,
modifiedAt: Math.round(stats.mtimeMs),
path: filePath
} satisfies SavedThemeFileDescriptor;
}));
return descriptors.sort((left, right) => right.modifiedAt - left.modifiedAt || left.fileName.localeCompare(right.fileName));
}
export async function readSavedTheme(fileName: string): Promise<string> {
const filePath = await resolveSavedThemeFilePath(fileName);
return await fsp.readFile(filePath, 'utf8');
}
export async function writeSavedTheme(fileName: string, text: string): Promise<boolean> {
const filePath = await resolveSavedThemeFilePath(fileName);
await fsp.writeFile(filePath, text, 'utf8');
return true;
}
export async function deleteSavedTheme(fileName: string): Promise<boolean> {
const filePath = await resolveSavedThemeFilePath(fileName);
try {
await fsp.unlink(filePath);
return true;
} catch (error) {
if ((error as { code?: string }).code === 'ENOENT') {
return true;
}
throw error;
}
}