UNPKG

kist

Version:

Lightweight Package Pipeline Processor with Plugin Architecture

142 lines 6.39 kB
import { __awaiter, __rest } from "tslib"; import fs from "fs"; import yaml from "js-yaml"; import path from "path"; import { ArgumentParser } from "../../cli/ArgumentParser.js"; import { AbstractProcess } from "../abstract/AbstractProcess.js"; export class ConfigLoader extends AbstractProcess { constructor() { super(); this.configPath = null; this.defaultFilenames = ["kist.yaml", "kist.yml"]; this.loadedPaths = new Set(); this.logDebug("ConfigLoader initialized."); } initialize() { return __awaiter(this, void 0, void 0, function* () { const parser = new ArgumentParser(); const cliFlags = parser.getAllFlags(); const cliPath = typeof cliFlags.config === "string" ? cliFlags.config : undefined; const searchPaths = cliPath ? [cliPath] : this.defaultFilenames; this.logDebug(`Current working directory: ${process.cwd()}`); this.logDebug(`Searching for config file${cliPath ? ` from --config=${cliPath}` : ""}...`); for (const fileName of searchPaths) { const resolvedPath = path.resolve(process.cwd(), fileName); this.logDebug(`Checking: ${resolvedPath}`); try { yield fs.promises.access(resolvedPath, fs.constants.F_OK | fs.constants.R_OK); this.configPath = resolvedPath; this.logDebug(`Configuration file found: ${resolvedPath}`); return; } catch (_error) { this.logDebug(`File not accessible: ${resolvedPath}`); if (cliPath) { throw new Error(`Configuration file not found or not accessible: ${resolvedPath}`); } } } this.logWarn("No configuration file found. Proceeding with default settings."); }); } loadConfig() { return __awaiter(this, void 0, void 0, function* () { if (!this.configPath) { this.logWarn("No configuration file found. Using default configuration."); return { stages: [] }; } this.loadedPaths.clear(); try { const config = yield this.loadConfigWithInheritance(this.configPath); this.validateConfig(config); this.logDebug(`Successfully loaded configuration from: ${this.configPath}`); return config; } catch (error) { this.logError("Failed to load configuration.", error); throw new Error(`Failed to load configuration: ${error.message}`); } }); } loadConfigWithInheritance(configPath) { return __awaiter(this, void 0, void 0, function* () { const resolvedPath = path.resolve(configPath); if (this.loadedPaths.has(resolvedPath)) { throw new Error(`Circular config inheritance detected: ${resolvedPath}`); } this.loadedPaths.add(resolvedPath); this.logDebug(`Loading configuration from: ${resolvedPath}`); const fileContents = yield fs.promises.readFile(resolvedPath, "utf8"); const config = yaml.load(fileContents); if (config.extends) { const parentPaths = Array.isArray(config.extends) ? config.extends : [config.extends]; const configDir = path.dirname(resolvedPath); let mergedConfig = { stages: [] }; for (const parentPath of parentPaths) { const absoluteParentPath = path.resolve(configDir, parentPath); this.logDebug(`Loading parent config: ${absoluteParentPath}`); const parentConfig = yield this.loadConfigWithInheritance(absoluteParentPath); mergedConfig = this.mergeConfigs(mergedConfig, parentConfig); } const { extends: _extends } = config, configWithoutExtends = __rest(config, ["extends"]); return this.mergeConfigs(mergedConfig, configWithoutExtends); } return config; }); } mergeConfigs(parent, child) { const merged = { metadata: Object.assign(Object.assign({}, (parent.metadata || {})), (child.metadata || {})), options: this.deepMerge(parent.options || {}, child.options || {}), stages: this.mergeStages(parent.stages || [], child.stages || []), }; if (merged.metadata && Object.keys(merged.metadata).length === 0) { delete merged.metadata; } return merged; } mergeStages(parentStages, childStages) { const parentByName = new Map(); for (const stage of parentStages) { parentByName.set(stage.name, stage); } const replacedNames = new Set(); const mergedStages = []; for (const childStage of childStages) { if (parentByName.has(childStage.name)) { replacedNames.add(childStage.name); } mergedStages.push(childStage); } const unreplacedParentStages = parentStages.filter((stage) => !replacedNames.has(stage.name)); return [...unreplacedParentStages, ...mergedStages]; } deepMerge(target, source) { const result = Object.assign({}, target); for (const key of Object.keys(source)) { const sourceValue = source[key]; const targetValue = target[key]; if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) { result[key] = this.deepMerge(targetValue, sourceValue); } else { result[key] = sourceValue; } } return result; } validateConfig(config) { if (!Array.isArray(config.stages)) { throw new Error("Invalid configuration: 'stages' must be an array."); } this.logDebug("Configuration structure validated successfully."); } } //# sourceMappingURL=ConfigLoader.js.map