mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-06 19:35:44 +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
|
|
@ -10,6 +10,7 @@ import { init as initFirefliesSync } from "@x/core/dist/knowledge/sync_fireflies
|
|||
import { init as initGranolaSync } from "@x/core/dist/knowledge/granola/sync.js";
|
||||
import { init as initGraphBuilder } from "@x/core/dist/knowledge/build_graph.js";
|
||||
import { init as initPreBuiltRunner } from "@x/core/dist/pre_built/runner.js";
|
||||
import { initConfigs } from "@x/core/dist/config/initConfigs.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
|
@ -96,7 +97,7 @@ function createWindow() {
|
|||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
app.whenReady().then(async () => {
|
||||
// Register custom protocol before creating window (for production builds)
|
||||
if (app.isPackaged) {
|
||||
registerAppProtocol();
|
||||
|
|
@ -113,6 +114,9 @@ app.whenReady().then(() => {
|
|||
});
|
||||
}
|
||||
|
||||
// Initialize all config files before UI can access them
|
||||
await initConfigs();
|
||||
|
||||
setupIpcHandlers();
|
||||
|
||||
createWindow();
|
||||
|
|
|
|||
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