prime
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user