title: “v0.8.9 - Härtung des Plugin-Loaders” description: “Release Notes für docmd v0.8.9 - neues Build-Zeit-Plugin-Registry, docmd-doctor-Befehl, docmd-Namensraum-Standardisierung, 404-Seiten- und Template-Assets-Fixes sowie Sicherheits-Härtung.” date: “2026-06-26”

Eine fokussierte Härtungs-Version. Drei Themen:

  1. Das Plugin- und Template-Ökosystem bekommt eine einzige Quelle der Wahrheit — jedes offizielle @docmd/*-Paket führt jetzt einen docmd-Namensraum in seiner package.json, und ein Build-Zeit-Registry-Generator liest diese Namensräume, um den Katalog zu erzeugen, den der Laufzeit-Loader verwendet. Ein neues offizielles Plugin hinzuzufügen ist jetzt eine einzige package.json-Bearbeitung plus ein Verzeichnis unter packages/{plugins,templates,engines}/.
  2. Der Plugin-Auto-Installer ist jetzt robust gegenüber Paketen, die import-only exports-Felder ausliefern, und ein neuer docmd doctor-Vorabprüfungs-Befehl erkennt Konfigurations-Drift vor einem Build.
  3. Zwei bisher stumme Bugs in veröffentlichten Tarballs sind behoben: @docmd/ui’s translations/ und @docmd/template-summer’s templates/+assets/ sind jetzt in den veröffentlichten Dateien. Die 404-Seite rendert mit dem Summer-Template und übersetzten Zeichenketten; zuvor fiel sie auf das Standard-Template zurück und zeigte rohe Übersetzungs-Schlüssel.

Keine öffentlichen API-Änderungen. Keine brechenden Konfigurations-Änderungen. Reines Härtungs-Release.

✨ Neu

Build-Zeit-Plugin-Registry (einzige Quelle der Wahrheit)

Ein neues Workspace-Skript — scripts/build-plugin-registry.mjs — durchläuft packages/{plugins,templates,engines}/*, liest den package.json#docmd-Namensraum jedes Pakets und erzeugt einen generierten JSON-Katalog (packages/api/registry/plugins.generated.json). Verdrahtet als prebuild-Schritt von @docmd/api, sodass die Registry bei jedem Build neu generiert wird.

Der Laufzeit-Loader (packages/api/src/hooks.ts getPluginRegistry) liest nun aus der generierten Datei, mit zwei Auflösungs-Pfaden (veröffentlichtes Layout <pkg>/registry/... und Monorepo-Dev-Layout <repo>/packages/api/registry/...). Die handgepflegte packages/plugins/installer/registry/plugins.json, die im Installer lebte, wird zur Laufzeit nicht mehr konsultiert. Sie bleibt als veralteter Fallback für Benutzer erhalten, die @docmd/plugin-installer ohne @docmd/api installieren, und wird in 0.9.0 entfernt (siehe Migration unten).

docmd-Namensraum-Standardisierung

Jedes offizielle @docmd/*-Paket führt jetzt einen docmd-Namensraum in seiner package.json. Der Namensraum ist der Vertrag, den der Registry-Generator liest und den der Loader zur Ladezeit gegen den JS-Deskriptor kreuzprüft. Felder:

Feld Erforderlich Beschreibung
key Empfohlen Der benutzerorientierte Bezeichner (config.plugins.<key>). Wird aus dem Paketnamen abgeleitet, falls weggelassen.
kind Empfohlen Eines aus plugin, template, engine. Wird aus der Verzeichnisstruktur abgeleitet, falls weggelassen.
displayName Empfohlen Menschenlesbarer Name in Katalogen und docmd doctor-Ausgabe.
tagline Empfohlen Einzeilige Beschreibung; fällt auf die npm-Beschreibung zurück.
capabilities Erforderlich für Plugins und Templates Die Hook-Fähigkeiten, die der JS-Deskriptor deklariert. Die Build-Zeit-Prüfung warnt bei Abweichung.
preview Optional (Nur Template) Pfad zu einer Vorschau-Asset.

Engines erhalten dieselbe Form, aber keine capabilities — sie nehmen nicht am Hook-System teil, sondern nur am Engine-Loader. Der Auto-Installer prüft kind === 'engine' und weigert sich, sie zu installieren, sodass Engines im Katalog erscheinen, aber niemals automatisch installierbar sind.

docmd doctor — Vorabprüfung

Ein neuer CLI-Unterbefehl für Diagnose. Keine Datei-Schreibvorgänge, keine Build-Nebenwirkungen — rein diagnostisch.

npx @docmd/core doctor [Optionen]
Option Beschreibung
--config <Pfad> Pfad zu einer abweichenden docmd.config.json (oder .ts/.js/.mjs).
--fix Fehlende offizielle Plugins oder Templates automatisch installieren.
--json Den Bericht als maschinenlesbares JSON ausgeben.

Standardmäßig druckt doctor eine menschenlesbare Zusammenfassung mit: installierter @docmd/core-Version, allen konfigurierten Plugins (mit Version und Status ✓ installiert / ⚠ fehlt), dem aktiven Template, den angeforderten Engines (js immer aktiv, rust optional) und einer Liste der Auto-Install-Kandidaten. Mit --fix ruft es den Paket-Manager des Projekts auf (pnpm add, npm install --save, yarn add oder bun add), um die Kandidaten zu installieren, und beendet sich mit Code 0, wenn alles aufgelöst wurde. Mit --json werden dieselben Daten als einzelnes JSON-Objekt ausgegeben — nützlich für Pre-Commit-Hooks und CI-Gates.

Exit-Code 0 bedeutet, das Projekt ist gesund; ungleich Null bedeutet, dass nach einem --fix-Lauf mindestens ein Problem verbleibt.

Auch verfügbar als pnpm doctor (was über das Workspace-docmd-Skript im Monorepo läuft).

Neue pnpm-Skripte im Monorepo

Die package.json des Monorepos erhält eine Reihe neuer pnpm-Skripte, die alle über das bestehende --cwd-Flag auf den Playground zeigen (gleiches Muster wie pnpm dev und pnpm live):

pnpm doctor           # → docmd doctor
pnpm validate         # → docmd validate
pnpm migrate          # → docmd migrate
pnpm gen:deploy       # → docmd deploy
pnpm mcp              # → docmd mcp
pnpm plugin:add foo   # → docmd add foo
pnpm plugin:remove foo # → docmd remove foo
pnpm build:playground # → docmd build (getrennt von `pnpm build`, das den monorepo-weiten Build ist)

🐛 Fehlerbehebungen

Plugin-Auto-Install: robust gegen import-only exports

Der Auto-Installer in @docmd/api löste installierte Pakete zuvor mit Node’s CommonJS require.resolve auf und lud den aufgelösten Pfad anschließend mit await import(file://path). Für Pakete, die im exports-Feld nur eine import-Bedingung ausliefern, wirft require.resolve den Fehler ERR_PACKAGE_PATH_NOT_EXPORTED und der Build gibt aus:

⬢ Could not load @docmd/template-summer after auto-install

obwohl das Paket korrekt installiert ist. Der Retry-Pfad verwendet nun direkt await import(name), was das exports-Feld nativ berücksichtigt und mit jeder Bedingung funktioniert.

Eine Registry-Reprüfung als Defense-in-Depth wird innerhalb des Retry-Pfads durchgeführt. Die Menge der Namen, die automatisch installiert werden können, ist unverändert — nur die Menge der exports-Bedingungen, die sie tragen dürfen, ist nun größer.

Plugin-Loader: Capability-Cache und Manifest-Drift-Prüfung

Zwei Folge-Härtungen für den Loader:

  • Pro-Schlüssel-Capability-Set-Cache. Vermeidet erneutes Durchlaufen der Registry bei jedem Dev-Server-Rebuild. Vernachlässigbar beim ersten Build, spürbar in einer Hot-Reload-Schleife mit einer 15-Einträge-Registry.
  • Manifest/Deskriptor-Capability-Drift-Prüfung. Wenn der Registry-Eintrag Capabilities hat und der JS-Deskriptor Capabilities hat, warnt der Loader, wenn eine Seite einen Hook deklariert, den die andere nicht. Dies schließt den stillen Hook-Drop-Bug, bei dem ein Plugin, das einen Hook (z. B. onPostBuild) implementierte, aber vergaß, die entsprechende Capability im JS-Deskriptor zu deklarieren, seinen Hook stillschweigend fallen ließ.

Bessere Fehlermeldungen „Could not load X after auto-install"

Der Catch-Block des Post-Install-Retry zeigt nun err.code (z. B. ERR_PACKAGE_PATH_NOT_EXPORTED, ERR_MODULE_NOT_FOUND) und die erste Zeile von err.message. Die vorherige Meldung „Could not load X after auto-install" wirkte wie ein docmd-Bug, obwohl die tatsächliche Ursache eine fehlerhafte package.json in der Abhängigkeit war. Der autoInstallPlugin-Catch-Block zeigt auch das zugrunde liegende stderr des Paket-Managers und gibt einen Hinweis für die häufigsten Fälle aus (Template → zu dependencies hinzufügen; Plugin → docmd add <name>).

@docmd/ui: translations/ in veröffentlichten Tarball aufnehmen

Der serverseitige Übersetzungs-Loader in packages/ui/src/index.ts:loadTranslations sucht Übersetzungs-JSON-Dateien unter __dirname/../translations/. Das package.json#files war ["dist", "templates", "assets"] — ohne "translations", sodass npm den Tarball ohne die Übersetzungs-Dateien packte. Das serverseitige readFileSync warf eine Ausnahme, der Catch-Block setzte translationsCache[locale] auf {}, und die t()-Funktion fiel darauf zurück, den Schlüssel literal zurückzugeben.

Der Bug war lokal unsichtbar, weil der --offline-Build des Benutzers (Monorepo-Dev) Zugriff auf den Quellbaum hatte. Er war nur auf der bereitgestellten Doku-Site sichtbar, wo @docmd/ui@0.8.x aus npm installiert wird und der Übersetzungs-Ordner im Tarball fehlt.

Die 404-Seite war das auffälligste Symptom: Sie wird träge durch den Static-File-Fallback der bereitgestellten Site gerendert, wenn ein Benutzer einen fehlenden Pfad aufruft. Die 404-Seite ruft t() für Titel und Body auf, und zu diesem Zeitpunkt ist der Übersetzungs-Cache leer. Die Schlüssel leaken durch.

Fix: Füge "translations" zu packages/ui/package.json#files hinzu. Der veröffentlichte Tarball liefert nun alle 7 Locale-Dateien (de, en, es, fr, hi, ja, zh).

@docmd/template-summer: templates/ und assets/ in veröffentlichten Tarball aufnehmen

Der Summer-Template-Laufzeit verwendet new URL('../templates/...', import.meta.url) innerhalb von dist/index.js, was sich zu <package-root>/templates/... auflöst (nicht zu <package-root>/dist/templates/...). Das package.json#files war nur ["dist"], sodass der veröffentlichte Tarball nur dist/ enthielt. Der Resolver konnte keine der Template-Partials finden und fiel stillschweigend auf das Standard-Template zurück.

Das 404-Seiten-Symptom (zusätzlich zum Übersetzungs-Problem): Auf der bereitgestellten Site sah der Benutzer das Standard-404-Template (immer noch als docmd gebrandmarkt, aber mit dem Standard-Look), nicht das Summer-gestylte 404.

Fix: Füge "templates" und "assets" zu packages/templates/summer/package.json#files hinzu. Der veröffentlichte Tarball entspricht nun dem Monorepo-Dev-Layout und das Summer-Template rendert korrekt.

Dev-Server: führenden Schrägstrich vor safePath() entfernen

Der CWE-22-Fix aus 0.8.9 (Ersetzen von filePath.startsWith(rootAbs) durch safePath(rootAbs, ...)) hatte eine Regression im Dev-Server: URL-Pfadnamen beginnen immer mit / (z. B. /index.html), und path.resolve('/abs/root', '/index.html') gibt /index.html zurück (behandelt das zweite Argument als absolut) — was die safePath-Grenzprüfung immer fehlschlagen ließ und für jede legitime Anfrage 403 Forbidden erzeugte.

Fix: Entferne den führenden / aus dem URL-Pfadnamen, bevor er an safePath() übergeben wird. Der leere Fall ('/''') wird mit || '.' behandelt, sodass er sich zum Root-Verzeichnis selbst auflöst, was safePath über seine resolved !== root-Ausnahme akzeptiert. Verifiziert: Legitime Pfade geben 200 zurück, Pfad-Traversal wird weiterhin blockiert (403), kodiertes ../ löst sich nach Pfad-Normalisierung weiterhin zu 404 auf.

🔒 Sicherheit

Der Cold-E-Mail-Hinweis, der kurz vor dieser Version eingegangen ist, ist vollständig adressiert:

Problem Status Lösung
packages/core/src/utils/dev-utils.ts:118filePath.startsWith(rootAbs) CWE-22 Behoben (0.8.9) Verwende safePath() mit der strengen root + path.sep-Grenze. Die “Forbidden”-Regression aus dieser Änderung ist ebenfalls behoben (siehe oben).
packages/live/src/index.tsserver.listen(port, '0.0.0.0') CWE-668 Behoben (0.8.9) Standardmäßig 127.0.0.1. LAN-Zugriff opt-in über DOCMD_HOST=0.0.0.0 oder --host 0.0.0.0, mit TUI-Warnung wenn aktiv. Der Port-Probe bindet ebenfalls an 127.0.0.1 statt 0.0.0.0.
packages/utils/src/html-escape.tsscriptLiteral/jsonInject sind nacktes JSON.stringify Behoben (0.8.9) Beide escapen jetzt korrekt </script, <!--, U+2028 und U+2029. Die Härtung ist für nicht-konfligierende Zeichenketten still; Round-Trips durch JSON.parse funktionieren weiterhin, da das Escape JSON-sichere Sequenzen verwendet.
docker/DOCKER.md — Beispiele zeigen command: dev --host 0.0.0.0 Behoben (0.8.9) Drei Beispiele verwenden nun den neuen Loopback-Standard. Der Abschnitt “Network Issues” dokumentiert den Opt-in-Pfad mit einem Sicherheitshinweis.
@docmd/plugin-openapi CWE-22 (älterer Hinweis, in 0.8.5) Geschlossen in 0.8.8 safePath(rootDir, asUserPath(specPath)) in renderSpec. Verifiziert.
Dev-Server-WebSocket CWE-1385 (älterer Hinweis, in 0.8.5) Geschlossen in 0.8.8 verifyClient: createOriginVerify() in WebSocketServer. Verifiziert.

Die beiden älteren Hinweise (OpenAPI und WebSocket) können als gelöst geschlossen werden. Sie waren bereits in 0.8.8 behoben — diese Version bestätigt den Fix erneut und fügt eine Manifest-Kreuzprüfung im Loader hinzu, um zukünftige Drift frühzeitig zu erkennen.

📚 Dokumentation

  • Entwicklung → Plugins entwickeln und Entwicklung → Templates entwickeln — neuer Abschnitt “Der docmd-Namensraum”, der den Build-Zeit-Vertrag dokumentiert, den jedes offizielle Plugin erfüllen muss, plus der “Entfernung des gebündelten Registers in default-Bedingung” für Plugins und Templates.
    0.9.0"-Callout. Neuer Unterabschnitt "ESM-Exports — die
  • Plugins → Plugins verwenden — Callout zur robusten Auto-Installation. Der neue import(name)-Retry-Pfad wird als Grund für die Registry-Reprüfung als Defense-in-Depth dokumentiert.
  • Plugins → Search — Callout zur Resolver-Robustheit. Die Fallback-Kette import → default → require → main und der manuelle node_modules-Durchlauf werden erklärt, plus der --foreground-scripts-Hinweis für den docmd-search-Auto-Installer.
  • Referenz → CLI-Befehle — neuer Eintrag für docmd doctor mit allen drei Flags (--config, --fix, --json) dokumentiert.
  • Release Notes → 0.8.9 — diese Datei.

🔌 Kompatibilität

Oberfläche Status
@docmd/plugin-search@>=0.8.5 ✓ Funktioniert. Die gehärtete >=0.1.0-Peer-Dep auf docmd-search löst sauber auf.
docmd-search@>=0.1.0 ✓ Erste Nicht-Alpha-Version. Lässt den ^0.1.0-alpha.1-Bereich fallen, landet auf latest.
@docmd/engine-js@>=0.8.5 ✓ Optionaler Peer, für Chunking/Quantisierung verwendet.
@docmd/engine-rust@>=0.8.5 ✓ Optionaler Peer, beschleunigtes Chunking/Quantisierung, wenn vorhanden.
@huggingface/transformers@^4.2.0 ✓ Optionaler Peer, für das Embedding-Modell erforderlich.
onnxruntime-node@^1.26.0 ✓ Optionaler Peer, für die On-Device-Inferenz-Backend erforderlich.
Node.js >=18 (entspricht dem Rest der @docmd/*-Familie)
Browser (Such-Client) Moderne Browser mit WebAssembly, Atomics, SharedArrayBuffer (Cross-Origin isoliert)

🔄 Migrations-Hinweise

  • Für @docmd/plugin-search@>=0.8.5-Benutzer: Nichts erforderlich. Die gehärtete Peer-Dep >=0.1.0 des Plugins nimmt docmd-search@0.1.0 automatisch auf.
  • Für @docmd/plugin-search@0.8.5-0.8.8-Benutzer: Aktualisieren Sie das Plugin auf >=0.8.9, um die default-Exports-Kompatibilität und die robuste Auto-Installation zu erhalten. Frühere Versionen werden weiterhin mit dem Alpha installiert, aber der Auto-Install-Retry gibt eine Warnung aus.
  • Für direkte docmd-search-Konsumenten: Aktualisieren Sie Ihre package.json von ^0.1.0-alpha.1 auf ^0.1.0. Die beiden Alphas sind veraltet; npm install docmd-search löst zu 0.1.0 auf und landet auf latest.
  • Für Plugin-Autoren (neue oder aktualisierte offizielle Plugins): Fügen Sie einen docmd-Namensraum zu Ihrer package.json hinzu, wie im Plugin-Entwicklungs-Leitfaden dokumentiert. Ohne ihn schlägt der Build-Zeit-Registry-Generator laut fehl.
  • Für nachgelagerte @docmd/plugin-installer-Benutzer ohne @docmd/api: Das gebündelte registry/plugins.json ist in 0.8.9 noch als veralteter Fallback enthalten. Es wird in 0.9.0 entfernt — installieren Sie @docmd/api als Peer-Dep.
  • Für pnpm-Benutzer im Monorepo: Entfernen Sie den pnpm.overrides["docmd-search"]-Workaround (file:../docmd-search), sobald docmd-search@0.1.0 auf npm ist. Die gehärtete >=0.1.0-Peer-Dep löst sich natürlich aus der Registry auf.

🛠 Verifizierung

  • pnpm prep — 0 Fehler, 1 Warnung (vorhandenes Lint-Rauschen, nicht zusammenhängend mit dieser Version). Alle 30 Pakete bauen; 372 Tests bestehen.
  • End-to-End-Smoke-Test in einer sauberen /tmp-Installation: docmd build gelingt; docmd doctor --json meldet korrekt; der Dev-Server gibt 200 für legitime Pfade und 403 für Traversal-Versuche zurück; die 404-Seite rendert mit dem Summer-Template und übersetzten Zeichenketten.

📋 Release-Checkliste (für den Herausgeber)

  1. Bestätigen Sie, dass pnpm prep lokal grün ist.
  2. Taggen Sie das Release: git tag 0.8.9 && git push origin 0.8.9.
  3. Der Publish-Workflow führt den Rust-Matrix-Build aus und veröffentlicht dann alle 30 Pakete in Abhängigkeits-Reihenfolge.
  4. Verifizieren Sie auf npm: npm view @docmd/core dist-tags zeigt { latest: '0.8.9' }.
  5. Entfernen Sie das file:../docmd-search-Override in package.json#pnpm.overrides (die Root-DevDep kann bei ^0.1.0-alpha.1 bleiben oder auf ^0.1.0 wechseln).