✨ Highlights
Diese Version ist ein Entwickler-Erfahrungs-Durchlauf für die CLI nach der Härtung in 0.8.8 und der Plugin-Loader-Arbeit in 0.8.9. Die für Anwender sichtbarste Änderung: Fehlermeldungen bei Workspace-Konfigurationen verweisen jetzt mit einem funktionierenden Beispiel auf das eigentliche Problem, statt nur einen einzelnen nackten Satz auszugeben. Mehrere seit Langem offene CLI-Lücken werden ebenfalls geschlossen: Die Flagge deploy --force wird tatsächlich beachtet (keine stillen Überschreibungen mehr), migrate --dry-run ist jetzt eine echte Option, migrate --upgrade deckt die vollständige Legacy-Schlüssel-Map ab, der Dev-Server beendet sich bei docmd stop sauber, und der Plugin-Installer gibt bei einem No-Op-Install keine Erfolgsmeldung mehr aus.
Keine Änderungen an der öffentlichen API. Keine brechenden Konfigurationsänderungen. Reine Entwickler-Erfahrung.
🧪 migrate --dry-run für jeden Migrationspfad
docmd migrate --dry-run ist jetzt eine echte Flagge für jede unterstützte Quelle (Docusaurus, MkDocs, VitePress, Starlight) sowie für den In-Place-Pfad --upgrade. Dry-Run gibt aus, was sich ändern würde, und beendet sich mit Exit-Code 0, ohne etwas zu schreiben:
┌─ Dry run: MkDocs migration
│ Would move 3 files → mkdocs-backup/
│ docs
│ index.md
│ mkdocs.yml
│ Would write docmd.config.js
│ Config {"title":"My Test Site","src":"docs","out":"dist","theme":{"appearance":"system"}}
└──────────────────────────────────────────────────────────
⬢ No changes made. Re-run without --dry-run to apply.
Für --upgrade gibt der Dry-Run die vollständige aktualisierte Konfiguration aus, sodass Sie sie gegen die bestehende Datei diffen können, bevor Sie committen.
📦 migrate --upgrade deckt die vollständige Legacy-Schlüssel-Map ab
Der Pfad --upgrade behandelte bisher nur sechs Legacy-Schlüssel (projects, siteTitle, siteUrl, baseUrl, srcDir, outputDir, defaultLocale). Jetzt behandelt er zusätzlich:
| Legacy-Schlüssel | Wird zu |
|---|---|
source |
src |
outDir |
out |
nav |
navigation |
top-level search (boolean) |
plugins.search |
top-level sidebar |
layout.sidebar |
theme.defaultMode |
theme.appearance |
theme.enableModeToggle |
optionsMenu.components.themeSwitch |
theme.positionMode |
optionsMenu.position |
Jedes Upgrade emittiert einen [ DONE ] Upgraded "X" to "Y"-Schritt, und eine Konfiguration ohne jegliche Legacy-Schlüssel gibt weiterhin Configuration is already up to date with the latest schema. aus.
🩺 Bessere Workspace-Fehlermeldungen
Wenn einer Workspace-Konfiguration das Wurzelprojekt fehlt (der häufigste Erstkonfigurations-Fehler), lautete die Fehlermeldung bisher nur ein einzelner nackter Satz: "Workspace configuration must have a root project with prefix "/"". Das gab dem Anwender weder ein Beispiel noch einen nächsten Schritt an die Hand.
Die Fehlermeldung lautet jetzt:
Workspace configuration must include a root project with prefix "/".
Each workspace needs one project that owns the site root, with the others mounted under their own prefixes.
Example:
{
"workspace": {
"projects": [
{ "name": "main", "src": "./docs", "prefix": "/" },
{ "name": "api", "src": "./api-docs", "prefix": "/api/" }
]
}
}
See https://docs.docmd.io/guides/workspace for the full layout guide.
Dieselbe Form gilt für jeden Workspace-Validierungsfehler: Jeder endet mit der konkreten Konfiguration, die den Validator zufriedenstellen würde.
📦 deploy --force meint jetzt, was es sagt
Die Flagge --force für docmd deploy wurde bisher akzeptiert, vom Deployer aber nie gelesen. Der Aufruf von docmd deploy --docker in einem Projekt, das bereits eine Dockerfile enthielt, überschrieb diese stillschweigend ohne Warnung.
Das neue Verhalten:
- Ohne
--forcewird eine bestehende Konfigurationsdatei erhalten, und die TUI-Zeile zeigt[ SKIP ] Dockerfile (already exists, skipped — use --force to overwrite). - Mit
--forcewird die bestehende Datei wie bisher überschrieben.
Dies gilt für jedes Deploy-Ziel: --docker, --nginx, --caddy, --github-pages, --vercel und --netlify.
🛰️ Netlify-Template macht keinen Soft-404 mehr auf jeder Route
Die generierte netlify.toml enthielt bisher einen [[redirects]]-Block, der jede URL mit HTTP 200 an /index.html weiterleitete:
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Das bedeutet, dass jede fehlende Route die Startseite mit Status 200 zurückgab — ein Soft-404, der Fehler versteckt, SEO schadet und eine URL-Enumeration-Angriffsfläche eröffnet. Der Block wurde entfernt: docmd generiert für jede Route eine echte HTML-Datei, sodass Netlify die richtige Datei ausliefert, wenn sie existiert, und ansonsten auf das gebündelte 404.html (mit 404-Status) zurückfällt.
🔗 Offline-Modus-Links funktionieren in jeder Hosting-Variante
Die Flagge docmd build --offline ließ im gerenderten HTML bisher absolute Pfade wie <a href="/destination/"> für Markdown-Links stehen. Diese funktionieren auf einem HTTP-Server, brechen aber, wenn die Anwender site/index.html aus dem Dateisystem öffnen — file:// kann absolute Pfade ohne Host nicht auflösen.
Der Fix verarbeitet das gerenderte Markdown-HTML im Offline-Modus nach und schreibt jedes interne <a href> und <img src> auf einen relativen .html-Pfad um, wobei die gleiche Logik wie beim Button-Container (:::) verwendet wird. Das Ergebnis:
| Quelle | build (HTTP) |
build --offline (file://) |
|---|---|---|
Button-Container ::: "/destination" |
./destination/ |
./destination/index.html |
Markdown-Link [text](/destination) |
/destination/ |
./destination/index.html |
Markdown-Bild  |
/assets/x.png |
./assets/x.png |
Externes https://example.com |
unverändert | unverändert |
Anker #section |
unverändert | unverändert |
Nicht-Offline-Builds bleiben vollständig unverändert (saubere URLs für SEO bleiben erhalten). Die Ausgabe funktioniert in jeder Hosting-Variante: file://, HTTP-Server, eigene Domains, Dev-Server, Live-Editor. Verschachtelte Seiten erhalten das korrekte ../-Präfix, um aus Unterordnern nach oben zu navigieren.
🔌 docmd-search Peer-Abhängigkeiten werden gemeinsam installiert
Wenn plugins.search.semantic: true gesetzt ist und entweder docmd-search selbst oder eine seiner Peers (@huggingface/transformers, onnxruntime-node) fehlt, tut das Plugin jetzt Folgendes:
- Es schränkt seinen Modul-Auflösungs-Scope auf das Projekt des Anwenders ein (nur
process.cwd()/node_modules) — zuvor konnte es über__dirnamebis zum Monorepo-Root aufwärts wandern und dort eine Kopie finden, was zu verwirrendem Verhalten führte. - Es installiert nur die Peers automatisch nach, wenn nur diese fehlen (zuvor: warnen und aufgeben).
- Es gibt den tatsächlichen Grund in der Fallback-Meldung aus — zuvor lautete die Meldung immer “docmd-search not installed”, selbst wenn nur ein Peer fehlte.
🛰️ Open Knowledge Format ist standardmäßig aktiv
OKF wurde in 0.8.8 ausgeliefert und ist nun vollständig standardmäßig aktiv:
- Wird bei jedem Build automatisch geladen, sofern nicht explizit über
plugins.okf: falsedeaktiviert. plugins.okf.typeFieldist standardmäßigtype(Frontmatter > Pfad > Fallback).- Fehlende
type-Felder erzeugen EINE zusammengefassteTUI-Zeile plus einen Eintrag pro Seite inokf/_meta/lint-report.txt, damit keine Details verloren gehen. - Verwaiste Konzepte und defekte interne Links werden im selben Lint-Bericht aufgeführt.
- Der Graph-Viewer ist Opt-in über
plugins.okf.graph: true(einegraph/index.html,graph.jsundgraph.csswerden unterokf/graph/emittiert). plugins.okf.i18n: trueemittiert OKF-Bundles für jede konfigurierte Locale (andernfalls nur für die Standard-Locale).
Keine Änderungen an der öffentlichen API.
🛑 Sauberer Shutdown bei docmd stop
docmd stop schickte bisher SIGTERM und, falls das fehlschlug, sofort SIGKILL. Der SIGTERM-Handler des Dev-Servers war ein einzeiliger process.exit(0), der Watcher, HTTP-Server, WebSocket-Server und Worker-Pool-Cleanup übersprang. Nach einem Stop konnten Kindprozesse und offene Sockets hängen bleiben.
Der neue Ablauf:
docmd stopsendet SIGTERM.- Der Dev-Server führt denselben sauberen Shutdown aus wie bei Ctrl+C (Watcher schließen → wss schließen → http-Server schließen → Worker-Pool beenden → Exit 0).
docmd stopprüft alle 100 ms bis zu 5 Sekunden lang, ob der Prozess von selbst beendet wird.- Nur wenn der Prozess nach der Gnadenfrist noch lebt, eskaliert
docmd stopzu SIGKILL.
Die TUI-Zeile zeigt Stopped ... PID gracefully bei sauberem Exit oder Killed ... PID (SIGKILL after grace), falls die Eskalation nötig war.
🐛 Fehlerbehebungen
- Workspace-Validator: umsetzbare Fehlermeldung bei fehlendem Wurzelprojekt. Der einzelne Satz wird durch eine mehrzeilige Meldung ersetzt, die die Anforderung erklärt, ein gültiges JSON-Beispiel zeigt und auf den Workspace-Layout-Leitfaden verlinkt.
- Plugin-Installer: korrekte Abschlussmeldung bei bereits installierten Plugins.
docmd add <plugin>für ein bereits konfiguriertes Plugin gab bisher “Plugin successfully installed and activated” aus, obwohl keine Installation stattfand. Die TUI-Abschlusszeile spiegelt jetzt das tatsächliche Ergebnis wider: Neuinstallation zeigt die Erfolgsmeldung; bereits konfiguriert druckt⬢ Plugin was already configured. Nothing changed. - Deployer:
--forceüberschreibt bestehende Konfigurationen nicht mehr stillschweigend. Jedes Deploy-Ziel beachtet jetzt--force: Standardverhalten ist das Überspringen bestehender Dateien;--forceaktiviert das Überschreiben. - Deployer: Netlify-
[[redirects]]-Block zur Behebung des Soft-404 entfernt. Die generiertenetlify.tomlschreibt jede URL nicht mehr mit Status 200 auf/index.htmlum. Fehlende Routen liefern jetzt das gebündelte404.htmlmit 404-Status aus. - Offline-Build: Markdown-Links und -Bilder werden jetzt auf relative
.html-Pfade umgeschrieben. Zuvor war nur der Button-Container Offline-fähig; Markdown-[text](url)undgaben absolute Pfade aus, diefile://nicht auflösen konnte. Sie durchlaufen jetzt dieselbefixHtmlLinks-Umschreibung wie der Button-Container (#167). - Dev-Server: SIGTERM führt denselben sauberen Shutdown wie SIGINT aus. Der Dev-Server schließt jetzt Watcher, WebSocket-Server, HTTP-Server und Worker-Pool bei SIGTERM, bevor er beendet wird.
docmd stopprüft, ob der Prozess beendet wird, und eskaliert erst nach einer Gnadenfrist von 5 Sekunden zu SIGKILL. - docmd-search: Peer-Abhängigkeiten werden bei Fehlen automatisch installiert. Wenn
plugins.search.semantic: truegesetzt ist und die Peer-Pakete (@huggingface/transformers,onnxruntime-node) nicht installiert sind, installiert das Plugin sie, anstatt zu warnen und auf Stichwortsuche zurückzufallen (#163 tieferer Fix). - Versionen:
config.versions.listwird als Alias fürallakzeptiert. Anwender, dielist(eine übliche Form für “Liste von Versionen”) verwendeten, erhielten bisher still 0-Seiten-Builds, weil das Schema nurallerkannte. Die Aliasierunglist→allstellt ihre Konfiguration wieder her, ohne den kanonischen Schlüssel zu ändern (M-6). - Versionen: Fehlendes Verzeichnis der aktuellen Version ist ein harter Fehler. Der Build bricht jetzt mit einem klaren Verweis auf den fehlenden Pfad und die Versions-ID ab. Zuvor erzeugte er still eine 0-Seiten-Site ohne umsetzbare Meldung. Alte (nicht aktuelle) Versionen bleiben Soft-Warnungen (T-Z6).
- llms.txt / llms.json-Titel werden bereinigt. Frontmatter-Titel, die Markdown-Injection-Zeichen (
`,[,], Zeilenumbrüche) enthalten, brechen die Linkform nicht mehr und werden nicht mehr als rohes HTML gerendert; Titel, die mit=,+,-oder@beginnen, erhalten ein vorangestelltes einfaches Anführungszeichen, damit das Öffnen in einer Tabellenkalkulation keine Formel ausführt. CSV-Formel-Neutralisierung (T-Z11) und Markdown-Injection-Verhinderung (T-Z10) in einem Helfer. NO_COLORundDOCMD_NO_BANNERunterdrücken das Build-Banner. Das Setzen vonNO_COLOR=1(oder des docmd-spezifischenDOCMD_NO_BANNER=1) blendet jetzt das ASCII-Art und die Versionszeile am Anfang der Build-Ausgabe aus.NO_COLORist der De-facto-Standard für CLIs;DOCMD_NO_BANNERist eine docmd-spezifische Hintertür für Anwender, die Farbe, aber kein Banner möchten (N-13 + N-16).- Migrations-Feinschliff.
moveFilesToBackupbehält nun Lockfiles undpackage.jsonan Ort und Stelle, sodass eine Wiederherstellung nicht jede Abhängigkeit neu auflösen muss (N-10). Die Docusaurus- und MkDocs-Migratoren erhalten das ursprünglichestaticDir/site_dir(N-22), und MkDocs-nav:-Blöcke werden in docmdsnavigation-Format übersetzt, wobei mehrstufige Abschnitte überchildrenerhalten bleiben (N-9).
Changelog
- Workspace-Validator:
validateProjectsinpackages/core/src/engine/workspace.tslöst jetzt eine mehrzeiligeError-Ausnahme mit einem JSON-Beispiel und einem Docs-Link aus, wenn kein Projektprefix: "/"hat (M-2). - Plugin-Installer:
addPlugininpackages/plugins/installer/src/index.tsverzweigt nach deminjected-Flag, das vom Config-Editor zurückgegeben wird, und emittiertTUI.infofür den bereits konfigurierten Fall, anstatt der irreführendenTUI.success(M-14). - Deployer:
write()inpackages/deployer/src/index.tsnimmt jetzt einforce-Argument und überspringt mit einer[ SKIP ]-TUI-Zeile, wenn die Zieldatei existiert und--forcenicht übergeben wurde (N-2). Alle sechs Zielgeneratoren (docker, nginx, caddy, github-pages, vercel, netlify) reichenopts.forcedurch. - Deployer (Netlify):
generateNetlifyinpackages/deployer/src/providers/netlify.tsemittiert keinen[[redirects]] from = "/*" status = 200-Block mehr. docmd generiert echtes HTML pro Route, sodass der Catch-all sowohl überflüssig als auch die Quelle des Soft-404 war (T-Z9). - Stop:
stopServerinpackages/core/src/commands/stop.tsexportiert einen neuenwaitForExit(pid, timeoutMs)-Helfer und verwendet ihn, um nach SIGTERM zu prüfen, ob der Zielprozess beendet wird. Die Eskalation zu SIGKILL erfolgt erst nach Ablauf der 5-Sekunden-Gnadenfrist (M-11). - Dev-Server:
startDevServerinpackages/core/src/commands/dev.tsextrahiert den SIGINT-Shutdown-Pfad in einegracefulShutdown()-Funktion, die sowohl von denSIGINT- als auch von denSIGTERM-Handlern verwendet wird (M-11). - Migrate:
migrateProjectinpackages/core/src/commands/migrate.tsakzeptiert eine neuedryRun-Option (N-3). Für jeden Quellpfad (Docusaurus, MkDocs, VitePress, Starlight) druckt der Dry-Run die Datei-Verschiebeliste und die neuedocmd.config.js, bevor irgendwelche Seiteneffekte auftreten; für--upgradedruckt er die aktualisierte Konfiguration.packages/core/src/bin/docmd.tsfügt--dry-runzur Migrate-CLI hinzu. - Migrate: der
--upgrade-Pfad inpackages/core/src/commands/migrate.tsbehandelt jetzt acht zusätzliche Legacy-Schlüssel:source,outDir,nav, top-levelsearch, top-levelsidebar,theme.defaultMode,theme.enableModeToggleundtheme.positionMode(N-4). - Tests:
tests/cli-contracts/validate-workspace.test.jsfügt 5 neue M-2-Assertions hinzu, die den Exit-Code und die vier Meldungsinhalt-Invarianten abdecken. - Tests:
tests/cli-contracts/plugin-add-remove.test.jsfügt 3 neue M-14-Assertions hinzu, die die Meldung “already installed”, das Fehlen der falschen Erfolgsmeldung und den Regressionsschutz für Neuinstallationen abdecken. - Tests: neue
tests/cli-contracts/deploy.test.js(5 Assertions, registriert intests/runner.jsalsdeploy) decken die Pfade N-2 Überspringen / Überschreiben / Neu ab. - Tests: neue
tests/cli-contracts/stop.test.js(3 Assertions, registriert alsstop) decken den M-11-HelferwaitForExitgegen laufende Kindprozesse ab. - Tests: neue
tests/cli-contracts/migrate.test.js(33 Assertions, registriert alsmigrate) decken N-3 Dry-Run sowohl für Quell-Migrationen als auch für Upgrade ab, sowie N-4-Abdeckung aller 13 Legacy-Upgrade-Pfade.