Files
vendoo/app/core/modules/module-state-store.mjs
T
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

88 lines
2.8 KiB
JavaScript

import { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from 'fs';
import { dirname } from 'path';
import { PlatformError } from '../../kernel/errors.mjs';
const SCHEMA_VERSION = 1;
function normalizeState(input = {}) {
const modules = {};
const source = input && typeof input === 'object' && !Array.isArray(input) ? input.modules : null;
if (source && typeof source === 'object' && !Array.isArray(source)) {
for (const [id, record] of Object.entries(source)) {
if (!/^vendoo\.[a-z][a-z0-9-]*$/.test(id)) continue;
if (!record || typeof record !== 'object' || Array.isArray(record)) continue;
modules[id] = {
enabled: record.enabled !== false,
source: String(record.source || 'admin'),
updatedAt: String(record.updatedAt || new Date(0).toISOString()),
};
}
}
return { schemaVersion: SCHEMA_VERSION, modules };
}
export class ModuleStateStore {
#path;
#state;
constructor(pathname) {
this.#path = pathname;
this.#state = this.#read();
}
#read() {
try {
if (!existsSync(this.#path)) return normalizeState();
const parsed = JSON.parse(readFileSync(this.#path, 'utf8'));
if (Number(parsed?.schemaVersion || 0) !== SCHEMA_VERSION) {
throw new Error(`Nicht unterstützte Modulstatus-Version: ${parsed?.schemaVersion}`);
}
return normalizeState(parsed);
} catch (error) {
throw new PlatformError('Der persistente Modulstatus konnte nicht gelesen werden.', {
code: 'MODULE_STATE_READ_FAILED',
cause: error,
details: { path: this.#path },
});
}
}
#persist() {
mkdirSync(dirname(this.#path), { recursive: true });
const temporary = `${this.#path}.${process.pid}.tmp`;
writeFileSync(temporary, `${JSON.stringify(this.#state, null, 2)}\n`, { mode: 0o600 });
renameSync(temporary, this.#path);
}
has(id) { return Boolean(this.#state.modules[id]); }
isEnabled(id, fallback = true) {
const record = this.#state.modules[id];
return record ? record.enabled !== false : Boolean(fallback);
}
setEnabled(id, enabled, { source = 'admin' } = {}) {
if (!/^vendoo\.[a-z][a-z0-9-]*$/.test(String(id || ''))) {
throw new PlatformError('Ungültige Modul-ID für den Modulstatus.', { code: 'MODULE_ID_INVALID', status: 400, expose: true });
}
this.#state.modules[id] = {
enabled: Boolean(enabled),
source: String(source || 'admin').slice(0, 80),
updatedAt: new Date().toISOString(),
};
this.#persist();
return { id, ...this.#state.modules[id] };
}
remove(id) {
if (!this.#state.modules[id]) return false;
delete this.#state.modules[id];
this.#persist();
return true;
}
snapshot() {
return JSON.parse(JSON.stringify(this.#state));
}
}