Files
optimus/index.js
T
2026-09-04 22:54:50 -05:00

192 lines
7.0 KiB
JavaScript

const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
function isPlainObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function overlaySettingsValue(base, overlay) {
if (!isPlainObject(base) || !isPlainObject(overlay)) return overlay;
const result = { ...base };
for (const [key, value] of Object.entries(overlay)) {
result[key] = key in result ? overlaySettingsValue(result[key], value) : value;
}
return result;
}
function applySettingOverlay(settings, projectRoot, overlayPath) {
const resolvedPath = path.isAbsolute(overlayPath) ? overlayPath : path.resolve(projectRoot, overlayPath);
const overlay = JSON.parse(fs.readFileSync(resolvedPath, "utf8"));
if (!isPlainObject(overlay)) throw new Error(`Setting overlay must contain an object: ${overlayPath}`);
for (const [name, value] of Object.entries(overlay)) {
settings.set(name, settings.has(name) ? overlaySettingsValue(settings.get(name), value) : value);
}
}
function loadSettings(projectRoot, settingOverlays = []) {
const settingsRoot = path.join(projectRoot, "settings");
if (!fs.existsSync(settingsRoot)) throw new Error(`No settings folder found in ${projectRoot}`);
const settings = new Map();
for (const fileName of fs.readdirSync(settingsRoot)) {
if (path.extname(fileName).toLowerCase() !== ".json") continue;
const name = path.basename(fileName, path.extname(fileName));
const filePath = path.join(settingsRoot, fileName);
settings.set(name, JSON.parse(fs.readFileSync(filePath, "utf8")));
}
for (const overlayPath of settingOverlays) applySettingOverlay(settings, projectRoot, overlayPath);
return settings;
}
function hashPayload(value) {
const json = JSON.stringify(value);
return crypto.createHash("sha256").update(json === undefined ? "undefined" : json).digest("hex");
}
function getSourceHash(sources) {
const sourceHash = {};
for (const [name, source] of Object.entries(sources)) {
if (!source || typeof source !== "object" || !("hash" in source)) {
throw new Error(`Source "${name}" must be a node with a hash`);
}
sourceHash[name] = source.hash;
}
return sourceHash;
}
function buildNode(payload, sources = {}) {
return {
sourceHash: getSourceHash(sources),
hash: hashPayload(payload),
payload,
};
}
function buildBinaryNode(filePath, sources = {}) {
const bytes = fs.readFileSync(filePath);
return {
sourceHash: getSourceHash(sources),
hash: crypto.createHash("sha256").update(bytes).digest("hex"),
payloadBin: filePath,
};
}
function initialize(projectRoot = process.cwd(), options = {}) {
const settingOverlays = options.settingOverlays || [];
if (!Array.isArray(settingOverlays)) throw new Error("options.settingOverlays must be an array");
const settings = loadSettings(projectRoot, settingOverlays);
const registry = new Map();
let artifactOutcome;
const utils = {
loadModule(name) {
const entry = registry.get(name);
if (!entry) throw new Error(`Unknown module: ${name}`);
if (entry.module) return entry.module;
if (entry.initializing) throw new Error(`Circular module initialization involving: ${name}`);
entry.initializing = true;
try {
const initialized = entry.init(utils);
if (!initialized || (typeof initialized !== "object" && typeof initialized !== "function")) {
throw new Error(`Module "${name}" initializer must return a module object or function`);
}
entry.module = initialized;
return entry.module;
} finally {
entry.initializing = false;
}
},
getSettings(name) {
if (!settings.has(name)) throw new Error(`Unknown settings: ${name}`);
return settings.get(name);
},
hashPayload,
buildNode,
buildBinaryNode,
setArtifactOutcome(value) {
artifactOutcome = `${value ?? ""}`.trim() || undefined;
},
};
const modules = utils.getSettings("modules");
if (!modules || typeof modules !== "object" || Array.isArray(modules)) throw new Error("settings/modules.json must contain an object");
for (const [name, modulePath] of Object.entries(modules)) {
const resolvedPath = path.resolve(projectRoot, modulePath);
const init = require(resolvedPath);
if (typeof init !== "function") throw new Error(`Module "${name}" must export an initializer function: ${modulePath}`);
registry.set(name, { init, module: null, initializing: false });
}
for (const name of registry.keys()) utils.loadModule(name);
function buildArtifact(name) {
const artifacts = utils.getSettings("artifacts");
const artifact = artifacts[name];
if (!artifact) throw new Error(`Unknown artifact: ${name}`);
if (!artifact.module) throw new Error(`Artifact "${name}" is missing module`);
if (!artifact.builder) throw new Error(`Artifact "${name}" is missing builder`);
const module = utils.loadModule(artifact.module);
const builder = module[artifact.builder];
if (typeof builder !== "function") {
throw new Error(`Artifact "${name}" builder not found: ${artifact.module}.${artifact.builder}`);
}
const params = artifact.params === undefined ? [] : artifact.params;
if (!Array.isArray(params)) throw new Error(`Artifact "${name}" params must be an array`);
artifactOutcome = undefined;
console.log(`Ensuring artifact: ${name}`);
try {
const result = builder(...params);
if (result && typeof result.then === "function") {
return result.then(value => {
console.log(` ${artifactOutcome ?? "completed — success"}`);
artifactOutcome = undefined;
return value;
}, error => {
console.error(" failed");
artifactOutcome = undefined;
throw error;
});
}
console.log(` ${artifactOutcome ?? "completed — success"}`);
artifactOutcome = undefined;
return result;
}
catch (error) {
console.error(" failed");
artifactOutcome = undefined;
throw error;
}
}
async function buildAll() {
const artifacts = utils.getSettings("artifacts");
for (const name of Object.keys(artifacts)) await buildArtifact(name);
}
return {
utils,
loadModule: utils.loadModule,
getSettings: utils.getSettings,
hashPayload,
buildNode,
buildBinaryNode,
buildArtifact,
buildAll,
modules: [...registry.keys()],
};
}
module.exports = { initialize, hashPayload, buildNode, buildBinaryNode };