kist
Version:
Lightweight Package Pipeline Processor with Plugin Architecture
142 lines • 6.39 kB
JavaScript
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