Files
vendoo/app/core/modules/module-package-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

113 lines
4.1 KiB
JavaScript

import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'fs';
import { dirname, join, resolve, sep } from 'path';
import { PlatformError } from '../../kernel/errors.mjs';
import { validateModulePackage } from './module-package-contract.mjs';
function ensureInside(root, pathname) {
const base = resolve(root);
const target = resolve(pathname);
if (target !== base && !target.startsWith(`${base}${sep}`)) {
throw new PlatformError('Moduldatei liegt außerhalb des Modulspeichers.', { code: 'MODULE_STORAGE_ESCAPE', status: 400, expose: true });
}
return target;
}
export class ModulePackageStore {
#root;
#trustKeys;
constructor(root, { trustKeys = {} } = {}) {
this.#root = resolve(root);
this.#trustKeys = { ...trustKeys };
mkdirSync(this.#root, { recursive: true });
}
#moduleRoot(id) {
return ensureInside(this.#root, join(this.#root, id));
}
#packagePath(id) {
return join(this.#moduleRoot(id), 'package.vmod');
}
list() {
if (!existsSync(this.#root)) return [];
const result = [];
for (const entry of readdirSync(this.#root, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
try {
const packagePath = this.#packagePath(entry.name);
if (!existsSync(packagePath)) continue;
const parsed = JSON.parse(readFileSync(packagePath, 'utf8'));
const pkg = validateModulePackage(parsed, { trustKeys: this.#trustKeys });
result.push({
id: pkg.manifest.id,
manifest: pkg.manifest,
runtime: pkg.runtime,
trust: pkg.trust,
installedAt: String(parsed.installedAt || parsed.createdAt || ''),
activatable: pkg.runtime.mode === 'declarative' || (pkg.runtime.mode === 'isolated' && pkg.trust.trusted),
packagePath,
});
} catch (error) {
result.push({ id: entry.name, invalid: true, error: error?.message || String(error), activatable: false });
}
}
return result.sort((a, b) => String(a.id).localeCompare(String(b.id)));
}
get(id) {
const packagePath = this.#packagePath(id);
if (!existsSync(packagePath)) return null;
const parsed = JSON.parse(readFileSync(packagePath, 'utf8'));
return validateModulePackage(parsed, { trustKeys: this.#trustKeys });
}
install(input) {
const pkg = validateModulePackage(input, { trustKeys: this.#trustKeys });
const root = this.#moduleRoot(pkg.manifest.id);
if (existsSync(root)) throw new PlatformError('Dieses Fremdmodul ist bereits installiert.', { code: 'MODULE_ALREADY_INSTALLED', status: 409, expose: true });
const temporary = `${root}.${process.pid}.tmp`;
mkdirSync(temporary, { recursive: true });
try {
for (const file of pkg.files) {
const destination = ensureInside(temporary, join(temporary, file.path));
mkdirSync(dirname(destination), { recursive: true });
writeFileSync(destination, Buffer.from(file.content, 'base64'), { mode: 0o600 });
}
const persisted = { ...pkg, trust: undefined, installedAt: new Date().toISOString() };
writeFileSync(join(temporary, 'package.vmod'), `${JSON.stringify(persisted, null, 2)}\n`, { mode: 0o600 });
renameSync(temporary, root);
} catch (error) {
rmSync(temporary, { recursive: true, force: true });
throw error;
}
return this.describe(pkg.manifest.id);
}
remove(id) {
const root = this.#moduleRoot(id);
if (!existsSync(root)) return false;
rmSync(root, { recursive: true, force: true });
return true;
}
export(id) {
const packagePath = this.#packagePath(id);
if (!existsSync(packagePath)) throw new PlatformError('Installiertes Modulpaket nicht gefunden.', { code: 'MODULE_NOT_FOUND', status: 404, expose: true });
return readFileSync(packagePath);
}
describe(id) {
const pkg = this.get(id);
if (!pkg) return null;
return {
id: pkg.manifest.id,
manifest: pkg.manifest,
runtime: pkg.runtime,
trust: pkg.trust,
activatable: pkg.runtime.mode === 'declarative' || (pkg.runtime.mode === 'isolated' && pkg.trust.trusted),
};
}
}