mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-29 10:26:23 +02:00
feat: auto-initialize config files on Electron app startup
Ensure models.json, mcp.json, and security.json config files are created when the app starts, before the Settings UI can access them. - Add ensureConfig() method to IModelConfigRepo and IMcpConfigRepo interfaces - Add async ensureSecurityConfig() function for security config initialization - Create initConfigs.ts with centralized initialization that calls all config ensure methods - Call initConfigs() in main.ts before setupIpcHandlers() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7a59b28651
commit
f5cc803340
6 changed files with 56 additions and 15 deletions
20
apps/x/packages/core/src/config/initConfigs.ts
Normal file
20
apps/x/packages/core/src/config/initConfigs.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import container from "../di/container.js";
|
||||
import type { IModelConfigRepo } from "../models/repo.js";
|
||||
import type { IMcpConfigRepo } from "../mcp/repo.js";
|
||||
import { ensureSecurityConfig } from "./security.js";
|
||||
|
||||
/**
|
||||
* Initialize all config files at app startup.
|
||||
* Ensures config files exist before the UI might access them.
|
||||
*/
|
||||
export async function initConfigs(): Promise<void> {
|
||||
// Resolve repos and explicitly call their ensureConfig methods
|
||||
const modelConfigRepo = container.resolve<IModelConfigRepo>("modelConfigRepo");
|
||||
const mcpConfigRepo = container.resolve<IMcpConfigRepo>("mcpConfigRepo");
|
||||
|
||||
await Promise.all([
|
||||
modelConfigRepo.ensureConfig(),
|
||||
mcpConfigRepo.ensureConfig(),
|
||||
ensureSecurityConfig(),
|
||||
]);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import path from "path";
|
||||
import fs from "fs";
|
||||
import fsPromises from "fs/promises";
|
||||
import { WorkDir } from "./config.js";
|
||||
|
||||
export const SECURITY_CONFIG_PATH = path.join(WorkDir, "config", "security.json");
|
||||
|
|
@ -19,7 +20,26 @@ const DEFAULT_ALLOW_LIST = [
|
|||
let cachedAllowList: string[] | null = null;
|
||||
let cachedMtimeMs: number | null = null;
|
||||
|
||||
function ensureSecurityConfig() {
|
||||
/**
|
||||
* Async function to ensure security config file exists.
|
||||
* Called explicitly at app startup via initConfigs().
|
||||
*/
|
||||
export async function ensureSecurityConfig(): Promise<void> {
|
||||
try {
|
||||
await fsPromises.access(SECURITY_CONFIG_PATH);
|
||||
} catch {
|
||||
await fsPromises.writeFile(
|
||||
SECURITY_CONFIG_PATH,
|
||||
JSON.stringify(DEFAULT_ALLOW_LIST, null, 2) + "\n",
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync version for internal use by getSecurityAllowList() and readAllowList().
|
||||
*/
|
||||
function ensureSecurityConfigSync() {
|
||||
if (!fs.existsSync(SECURITY_CONFIG_PATH)) {
|
||||
fs.writeFileSync(
|
||||
SECURITY_CONFIG_PATH,
|
||||
|
|
@ -63,7 +83,7 @@ function parseSecurityPayload(payload: unknown): string[] {
|
|||
}
|
||||
|
||||
function readAllowList(): string[] {
|
||||
ensureSecurityConfig();
|
||||
ensureSecurityConfigSync();
|
||||
|
||||
try {
|
||||
const configContent = fs.readFileSync(SECURITY_CONFIG_PATH, "utf8");
|
||||
|
|
@ -76,7 +96,7 @@ function readAllowList(): string[] {
|
|||
}
|
||||
|
||||
export function getSecurityAllowList(): string[] {
|
||||
ensureSecurityConfig();
|
||||
ensureSecurityConfigSync();
|
||||
try {
|
||||
const stats = fs.statSync(SECURITY_CONFIG_PATH);
|
||||
if (cachedAllowList && cachedMtimeMs === stats.mtimeMs) {
|
||||
|
|
|
|||
|
|
@ -2,4 +2,7 @@
|
|||
export * as workspace from './workspace/workspace.js';
|
||||
|
||||
// Workspace watcher
|
||||
export * as watcher from './workspace/watcher.js';
|
||||
export * as watcher from './workspace/watcher.js';
|
||||
|
||||
// Config initialization
|
||||
export { initConfigs } from './config/initConfigs.js';
|
||||
|
|
@ -5,6 +5,7 @@ import path from "path";
|
|||
import z from "zod";
|
||||
|
||||
export interface IMcpConfigRepo {
|
||||
ensureConfig(): Promise<void>;
|
||||
getConfig(): Promise<z.infer<typeof McpServerConfig>>;
|
||||
upsert(serverName: string, config: z.infer<typeof McpServerDefinition>): Promise<void>;
|
||||
delete(serverName: string): Promise<void>;
|
||||
|
|
@ -13,11 +14,7 @@ export interface IMcpConfigRepo {
|
|||
export class FSMcpConfigRepo implements IMcpConfigRepo {
|
||||
private readonly configPath = path.join(WorkDir, "config", "mcp.json");
|
||||
|
||||
constructor() {
|
||||
this.ensureDefaultConfig();
|
||||
}
|
||||
|
||||
private async ensureDefaultConfig(): Promise<void> {
|
||||
async ensureConfig(): Promise<void> {
|
||||
try {
|
||||
await fs.access(this.configPath);
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import path from "path";
|
|||
import z from "zod";
|
||||
|
||||
export interface IModelConfigRepo {
|
||||
ensureConfig(): Promise<void>;
|
||||
getConfig(): Promise<z.infer<typeof ModelConfig>>;
|
||||
upsert(providerName: string, config: z.infer<typeof Provider>): Promise<void>;
|
||||
delete(providerName: string): Promise<void>;
|
||||
|
|
@ -26,11 +27,7 @@ const defaultConfig: z.infer<typeof ModelConfig> = {
|
|||
export class FSModelConfigRepo implements IModelConfigRepo {
|
||||
private readonly configPath = path.join(WorkDir, "config", "models.json");
|
||||
|
||||
constructor() {
|
||||
this.ensureDefaultConfig();
|
||||
}
|
||||
|
||||
private async ensureDefaultConfig(): Promise<void> {
|
||||
async ensureConfig(): Promise<void> {
|
||||
try {
|
||||
await fs.access(this.configPath);
|
||||
} catch {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue