@every-env/cli
Version:
Multi-agent orchestrator for AI-powered development workflows
116 lines • 4.62 kB
JavaScript
import { readFile } from "fs/promises";
import { resolve } from "path";
import { ConfigSchema } from "../types/config.js";
import { logger } from "../utils/logger.js";
export class ConfigLoader {
configCache = new Map();
async load(configPath) {
const resolvedPath = await this.resolveConfigPath(configPath);
if (this.configCache.has(resolvedPath)) {
return this.configCache.get(resolvedPath);
}
const rawConfig = await this.readConfigFile(resolvedPath);
const config = this.validateConfig(rawConfig);
const mergedConfig = this.mergeWithDefaults(config);
// Always return the docs-level configuration object for consumer simplicity
const resultDocs = (mergedConfig.docs ?? mergedConfig);
this.configCache.set(resolvedPath, resultDocs);
return resultDocs;
}
async resolveConfigPath(configPath) {
if (configPath) {
return resolve(configPath);
}
// Look for config in standard locations
const locations = [
".every-env/config.json",
"every-env.json",
".every-env.json",
".docs-config.json",
"docs.config.json",
];
for (const location of locations) {
try {
await readFile(location);
return resolve(location);
}
catch {
// Continue to next location
}
}
// Check package.json
try {
const pkg = JSON.parse(await readFile("package.json", "utf-8"));
if (pkg["every-env"]) {
return resolve("package.json");
}
}
catch {
// No package.json or no config
}
throw new Error("No configuration file found");
}
async readConfigFile(path) {
const content = await readFile(path, "utf-8");
if (path.endsWith("package.json")) {
const pkg = JSON.parse(content);
// Package.json stores config under "every-env", which may include either full config or docs namespace
return (pkg["every-env"]?.["docs"] ??
pkg["every-env"] ??
undefined);
}
const data = JSON.parse(content);
// Support both old format (direct config) and new format (under 'docs' namespace)
if ((path.includes("every-env.json") || path.includes(".every-env/config.json")) && data["docs"]) {
// Return full data (with version + docs) when present
return data;
}
return data;
}
validateConfig(rawConfig) {
// Normalize legacy shapes to current schema before validation
const normalize = (input) => {
if (input && typeof input === "object" && "docs" in input) {
const cfg = input;
const docs = { ...(cfg.docs ?? {}) };
if (!docs.defaultCommand) {
docs.defaultCommand = process.env.DOCS_DEFAULT_COMMAND ?? "claude";
}
if (!("defaultFlags" in docs)) {
docs.defaultFlags = [];
}
return { version: cfg.version ?? "1.0", docs };
}
if (input && typeof input === "object" && "patterns" in input) {
// Legacy top-level docs format without "docs" wrapper
const legacy = input;
const docs = {
defaultCommand: legacy.defaultCommand ?? process.env.DOCS_DEFAULT_COMMAND ?? "claude",
defaultFlags: legacy.defaultFlags ?? [],
parallelism: legacy.parallelism,
patterns: legacy.patterns,
variables: legacy.variables,
};
return { version: "1.0", docs };
}
return input;
};
try {
const normalized = normalize(rawConfig);
return ConfigSchema.parse(normalized);
}
catch (error) {
logger.error("Invalid configuration:", error);
throw new Error("Configuration validation failed");
}
}
mergeWithDefaults(config) {
const docs = { ...config.docs };
// Environment variable override takes precedence
const envDefault = process.env.DOCS_DEFAULT_COMMAND;
docs.defaultCommand = envDefault ?? docs.defaultCommand ?? "claude";
docs.defaultFlags = docs.defaultFlags ?? [];
return { ...config, docs };
}
}
//# sourceMappingURL=config-loader.js.map