✨ Highlights

Diese Version ist ein Härtungsdurchlauf für das Plugin- und Template-Ökosystem. Die größte Änderung ist strukturell: 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 die einzige Quelle der Wahrheit zu erzeugen, die der Laufzeit-Loader verwendet. Der Plugin-Auto-Installer ist jetzt robust gegenüber Paketen mit import-only exports-Feldern, und ein neuer docmd doctor-Vorabprüfungs-Befehl erkennt Konfigurations-Drift vor einem Build. Zwei bisher stille Fehler in veröffentlichten Tarballs sind ebenfalls behoben: Die 404-Seite rendert jetzt mit dem Summer-Template und übersetzten Zeichenketten, anstatt auf das Standard-Template mit rohen Übersetzungs-Schlüsseln zurückzufallen.

Keine öffentlichen API-Änderungen. Keine brechenden Konfigurations-Änderungen. Reine Härtung.

🔌 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.

📦 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: key (benutzerorientierter Bezeichner), kind (plugin / template / engine), displayName, tagline, capabilities (für Plugins und Templates erforderlich, von der Build-Zeit-Prüfung verwendet) und preview (nur Template).

Engines erhalten dieselbe Form, aber keine capabilities — sie nehmen nicht am Hook-System teil. Der Auto-Installer prüft kind === 'engine' und weigert sich, sie zu installieren.

🩺 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, um die Kandidaten zu installieren. Mit --json werden dieselben Daten als einzelnes JSON-Objekt ausgegeben — nützlich für Pre-Commit-Hooks und CI-Gates.

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 verwendete zuvor require.resolve, um installierte Pakete aufzulösen, was ERR_PACKAGE_PATH_NOT_EXPORTED für Pakete wirft, die nur eine import-Bedingung in exports ausliefern. Der Retry-Pfad verwendet nun direkt await import(name), was das exports-Feld nativ berücksichtigt. 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. Ein Pro-Schlüssel-Capability-Set-Cache vermeidet erneutes Durchlaufen der Registry bei jedem Dev-Server-Rebuild. Eine neue Manifest-Drift-Prüfung warnt, wenn das capabilities-Array des JS-Deskriptors nicht zum capabilities-Array des Manifests passt — dies schließt den stillen Hook-Drop-Bug, bei dem ein Plugin, das einen Hook implementierte, aber vergaß, die entsprechende Capability im 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. 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.
  • @docmd/ui: translations/ in veröffentlichten Tarball aufnehmen. Der serverseitige Übersetzungs-Loader liest Übersetzungs-JSON-Dateien unter __dirname/../translations/. Das package.json#files enthielt "translations" nicht, sodass npm den Tarball ohne die Übersetzungs-Dateien packte. Die 404-Seite war das auffälligste Symptom: träge durch den Static-File-Fallback der bereitgestellten Site gerendert, war der Übersetzungs-Cache leer und die Schlüssel leakten. Jetzt werden alle 7 Locale-Dateien ausgeliefert.
  • @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, und der Resolver fiel auf das Standard-Template zurück. Jetzt entspricht der veröffentlichte Tarball 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 /, und path.resolve('/abs/root', '/index.html') gibt /index.html zurück — 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.
  • Live-Editor, Port-Probe und Docker-Docs: Standardmäßig Loopback. Der server.listen des Live-Editors und der checkPortInUse-Probe binden nun an 127.0.0.1 statt 0.0.0.0. LAN-Zugriff ist opt-in über DOCMD_HOST=0.0.0.0 oder --host 0.0.0.0, mit TUI-Warnung wenn aktiv. Die docker/DOCKER.md-Beispiele verwenden nun den Loopback-Standard.
  • Utils: scriptLiteral und jsonInject härten Inline-Script-Escaping. Beide escapen jetzt korrekt </script, <!--, U+2028 und U+2029 zusätzlich zur bestehenden JSON-sicheren Codierung. Die Härtung ist für nicht-konfligierende Zeichenketten still; Round-Trips durch JSON.parse funktionieren weiterhin, da das Escape JSON-sichere Sequenzen verwendet.

Changelog

  1. Registry: Neues scripts/build-plugin-registry.mjs — erzeugt packages/api/registry/plugins.generated.json aus dem docmd-Namensraum jedes offiziellen Pakets.
  2. Loader: getPluginRegistry liest die generierte Datei mit zwei Auflösungs-Pfaden; die handgepflegte installer/registry/plugins.json wird zur Laufzeit nicht mehr konsultiert.
  3. Namensraum: docmd-Namensraum zu allen 14 offiziellen @docmd/*-Paketen (12 Plugins, 2 Engines) hinzugefügt und auf @docmd/template-summer mit key: "summer" aktualisiert.
  4. Loader: Pro-Schlüssel-Capability-Set-Cache + Manifest-Drift-Prüfung (checkManifestCapabilityDrift).
  5. Auto-Install: Retry-Pfad verwendet await import(name) anstelle von require.resolve + import(file://path), mit Defense-in-Depth-Registry-Reprüfung.
  6. Auto-Install-Fehler: err.code + erste Zeile von err.message werden angezeigt; stderr des Paket-Managers + Hinweis werden gedruckt.
  7. CLI: docmd doctor-Unterbefehl mit --config, --fix, --json-Flags hinzugefügt. Im CLI-Dispatcher registriert; Hilfetext aktualisiert.
  8. CLI: Neue pnpm-Skripte im Monorepo — doctor, validate, migrate, gen:deploy, mcp, plugin:add, plugin:remove, build:playground.
  9. UI: packages/ui/package.json#files enthält jetzt translations/.
  10. Template: packages/templates/summer/package.json#files enthält jetzt templates/ und assets/.
  11. Dev-Server: safePath() in serveStatic entfernt den führenden / aus URL-Pfadnamen.
  12. Live-Editor: server.listen und checkPortInUse binden an 127.0.0.1; DOCMD_HOST=0.0.0.0 opt-in.
  13. Docker-Docs: Drei command: dev --host 0.0.0.0-Beispiele durch den Loopback-Standard ersetzt; “Network Issues”-Fehlerbehebung dokumentiert den Opt-in-Pfad.
  14. Utils: scriptLiteral und jsonInject escapen </script, <!--, U+2028, U+2029.
  15. Tests: packages/utils/test/html-escape.test.js aktualisiert, um das neue Escape-Verhalten zu prüfen.
  16. Dokumentation: Neuer Abschnitt “ESM Exports — die default-Bedingung” in building-plugins.md und building-templates.md; neuer “Entfernung des gebündelten Registers in 0.9.0”-Callout; neuer docmd doctor-Eintrag in reference/cli-commands.md.