mirror of
https://github.com/samiyev/puaros.git
synced 2025-12-28 15:26:53 +05:00
feat(ipuaro): implement v0.1.0 foundation
- Project setup with tsup, vitest, ESM support - Domain entities: Session, Project - Value objects: FileData, FileAST, FileMeta, ChatMessage, ToolCall, ToolResult, UndoEntry - Service interfaces: IStorage, ILLMClient, ITool, IIndexer, IToolRegistry - Shared: Config (zod), IpuaroError, utils (hash, tokens), Result type - CLI with placeholder commands (start, init, index) - 91 unit tests with 100% coverage - Fix package scope @puaros -> @samiyev in CLAUDE.md
This commit is contained in:
2
packages/ipuaro/src/shared/config/index.ts
Normal file
2
packages/ipuaro/src/shared/config/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// Config module exports
|
||||
export * from "./loader.js"
|
||||
89
packages/ipuaro/src/shared/config/loader.ts
Normal file
89
packages/ipuaro/src/shared/config/loader.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { existsSync, readFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { Config, ConfigSchema, DEFAULT_CONFIG } from "../constants/config.js"
|
||||
|
||||
const CONFIG_FILE_NAME = ".ipuaro.json"
|
||||
const DEFAULT_CONFIG_PATH = "config/default.json"
|
||||
|
||||
/**
|
||||
* Load configuration from files.
|
||||
* Priority: .ipuaro.json > config/default.json > defaults
|
||||
*/
|
||||
export function loadConfig(projectRoot: string): Config {
|
||||
const configs: Partial<Config>[] = []
|
||||
|
||||
const defaultConfigPath = join(projectRoot, DEFAULT_CONFIG_PATH)
|
||||
if (existsSync(defaultConfigPath)) {
|
||||
try {
|
||||
const content = readFileSync(defaultConfigPath, "utf-8")
|
||||
configs.push(JSON.parse(content) as Partial<Config>)
|
||||
} catch {
|
||||
// Ignore parse errors for default config
|
||||
}
|
||||
}
|
||||
|
||||
const projectConfigPath = join(projectRoot, CONFIG_FILE_NAME)
|
||||
if (existsSync(projectConfigPath)) {
|
||||
try {
|
||||
const content = readFileSync(projectConfigPath, "utf-8")
|
||||
configs.push(JSON.parse(content) as Partial<Config>)
|
||||
} catch {
|
||||
// Ignore parse errors for project config
|
||||
}
|
||||
}
|
||||
|
||||
if (configs.length === 0) {
|
||||
return DEFAULT_CONFIG
|
||||
}
|
||||
|
||||
const merged = deepMerge(DEFAULT_CONFIG, ...configs)
|
||||
return ConfigSchema.parse(merged)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep merge objects.
|
||||
*/
|
||||
function deepMerge<T extends Record<string, unknown>>(target: T, ...sources: Partial<T>[]): T {
|
||||
const result = { ...target }
|
||||
|
||||
for (const source of sources) {
|
||||
for (const key in source) {
|
||||
const sourceValue = source[key]
|
||||
const targetValue = result[key]
|
||||
|
||||
if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
|
||||
result[key] = deepMerge(
|
||||
targetValue as Record<string, unknown>,
|
||||
sourceValue as Record<string, unknown>,
|
||||
) as T[Extract<keyof T, string>]
|
||||
} else if (sourceValue !== undefined) {
|
||||
result[key] = sourceValue as T[Extract<keyof T, string>]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration.
|
||||
*/
|
||||
export function validateConfig(config: unknown): config is Config {
|
||||
const result = ConfigSchema.safeParse(config)
|
||||
return result.success
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config validation errors.
|
||||
*/
|
||||
export function getConfigErrors(config: unknown): string[] {
|
||||
const result = ConfigSchema.safeParse(config)
|
||||
if (result.success) {
|
||||
return []
|
||||
}
|
||||
return result.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`)
|
||||
}
|
||||
Reference in New Issue
Block a user