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:
- Das Plugin- und Template-Ökosystem bekommt eine einzige Quelle der Wahrheit — jedes offizielle
@docmd/*-Paket führt jetzt einendocmd-Namensraum in seinerpackage.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 einzigepackage.json-Bearbeitung plus ein Verzeichnis unterpackages/{plugins,templates,engines}/. - Der Plugin-Auto-Installer ist jetzt robust gegenüber Paketen, die
import-onlyexports-Felder ausliefern, und ein neuerdocmd doctor-Vorabprüfungs-Befehl erkennt Konfigurations-Drift vor einem Build. - Zwei bisher stumme Bugs in veröffentlichten Tarballs sind behoben:
@docmd/ui’stranslations/und@docmd/template-summer’stemplates/+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:118 — filePath.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.ts — server.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.ts — scriptLiteral/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 indefault-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 → mainund der manuellenode_modules-Durchlauf werden erklärt, plus der--foreground-scripts-Hinweis für dendocmd-search-Auto-Installer. - Referenz → CLI-Befehle — neuer Eintrag für
docmd doctormit 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.0des Plugins nimmtdocmd-search@0.1.0automatisch auf. - Für
@docmd/plugin-search@0.8.5-0.8.8-Benutzer: Aktualisieren Sie das Plugin auf>=0.8.9, um diedefault-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 Ihrepackage.jsonvon^0.1.0-alpha.1auf^0.1.0. Die beiden Alphas sind veraltet;npm install docmd-searchlöst zu0.1.0auf und landet auflatest. - Für Plugin-Autoren (neue oder aktualisierte offizielle Plugins): Fügen Sie einen
docmd-Namensraum zu Ihrerpackage.jsonhinzu, 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ündelteregistry/plugins.jsonist in 0.8.9 noch als veralteter Fallback enthalten. Es wird in 0.9.0 entfernt — installieren Sie@docmd/apials Peer-Dep. - Für
pnpm-Benutzer im Monorepo: Entfernen Sie denpnpm.overrides["docmd-search"]-Workaround (file:../docmd-search), sobalddocmd-search@0.1.0auf 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 buildgelingt;docmd doctor --jsonmeldet korrekt; der Dev-Server gibt200für legitime Pfade und403für Traversal-Versuche zurück; die 404-Seite rendert mit dem Summer-Template und übersetzten Zeichenketten.
📋 Release-Checkliste (für den Herausgeber)
- Bestätigen Sie, dass
pnpm preplokal grün ist. - Taggen Sie das Release:
git tag 0.8.9 && git push origin 0.8.9. - Der Publish-Workflow führt den Rust-Matrix-Build aus und veröffentlicht dann alle 30 Pakete in Abhängigkeits-Reihenfolge.
- Verifizieren Sie auf npm:
npm view @docmd/core dist-tagszeigt{ latest: '0.8.9' }. - Entfernen Sie das
file:../docmd-search-Override inpackage.json#pnpm.overrides(die Root-DevDep kann bei^0.1.0-alpha.1bleiben oder auf^0.1.0wechseln).