Skip to content

docmd@0.8.9 πŸ”’ (Plugin Hardening + Semantic 0.1.0)

Latest

Choose a tag to compare

@mgks mgks released this 26 Jun 18:04

docmd logo

docmd 0.8.9 πŸ”’ Plugin + Semantic 0.1.0

Build production-ready documentation from Markdown in seconds.

✨ Highlights

This release is a hardening pass on the plugin and template ecosystem. The biggest change is structural: every official @docmd/* package now carries a docmd namespace in its package.json, and a build-time registry generator reads those namespaces to produce the single source of truth that the runtime loader consumes. The plugin auto-installer is now resilient to packages that ship import-only exports fields, and a new docmd doctor pre-flight command catches configuration drift before a build. Two previously-silent bugs in published tarballs are also fixed: the 404 page now renders with the summer template and translated strings instead of falling back to the default template with raw translation keys.

No public API changes. No breaking config changes. Purely a hardening release.

πŸ”Œ Build-time plugin registry (single source of truth)

A new workspace-level script β€” scripts/build-plugin-registry.mjs β€” walks packages/{plugins,templates,engines}/*, reads each package's package.json#docmd namespace, and emits a generated JSON catalog (packages/api/registry/plugins.generated.json). Wired as the prebuild step of @docmd/api, so the registry is regenerated on every build.

The runtime loader (packages/api/src/hooks.ts getPluginRegistry) now reads from the generated file, with two resolution paths (published layout <pkg>/registry/... and monorepo dev layout <repo>/packages/api/registry/...). The hand-maintained packages/plugins/installer/registry/plugins.json that lived in the installer is no longer consulted at runtime.

πŸ“¦ docmd namespace standardisation

Every official @docmd/* package now carries a docmd namespace in its package.json. The namespace is the contract that the registry generator reads and the loader cross-checks against the JS descriptor at load time. Fields: key (user-facing identifier), kind (plugin / template / engine), displayName, tagline, capabilities (required for plugins and templates, used by the build-time cross-check), and preview (template only).

Engines get the same shape but no capabilities β€” they don't participate in the hook system. The auto-installer checks kind === 'engine' and refuses to install them.

🩺 docmd doctor β€” pre-flight check

A new CLI subcommand for diagnostics. No filesystem writes, no build side-effects β€” purely diagnostic.

npx @docmd/core doctor [options]
Option Description
--config <path> Path to a non-default docmd.config.json (or .ts/.js/.mjs).
--fix Auto-install missing official plugins or templates.
--json Emit the report as machine-readable JSON.

By default, doctor prints a human-readable summary covering the installed @docmd/core version, every configured plugin (with version and βœ“ installed / ⚠ missing status), the active template, the requested engines (js always-on, rust opt-in), and a list of auto-install candidates. With --fix, it shells out to the project's package manager to install the candidates. With --json, the same data is emitted as a single JSON object β€” useful for pre-commit hooks and CI gates.

Also available as pnpm doctor (which routes through the workspace docmd script in the monorepo).

πŸ›  New pnpm scripts in the monorepo

The monorepo's package.json gets a batch of new pnpm scripts, all pointing at the playground via the existing --cwd flag (same pattern as pnpm dev and 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 (separate from `pnpm build` which is the monorepo-wide build)

πŸ› Bug fixes

  • Plugin auto-install: resilient to import-only exports. The auto-installer in @docmd/api previously used require.resolve to resolve installed packages, which throws ERR_PACKAGE_PATH_NOT_EXPORTED for packages that ship only an import condition in exports. The retry path now uses await import(name) directly, which honours the exports field natively. A defense-in-depth registry re-check is performed inside the retry path β€” the set of names that can be auto-installed is unchanged, only the set of exports conditions they can carry is larger.
  • Plugin loader: capability cache and manifest drift check. A per-key capability set cache avoids re-walking the registry on every dev-server rebuild. A new manifest drift check warns if the JS descriptor's capabilities array doesn't match the manifest's capabilities array β€” closing the silent-hook-drop bug where a plugin that implemented a hook but forgot the corresponding capability in its descriptor would have its hook silently skipped.
  • Better "Could not load X after auto-install" errors. The post-install retry's catch block now surfaces err.code (e.g. ERR_PACKAGE_PATH_NOT_EXPORTED, ERR_MODULE_NOT_FOUND) and the first line of err.message. The autoInstallPlugin catch block also surfaces the underlying stderr from the package manager and prints a hint for the most common cases.
  • @docmd/ui: include translations/ in the published tarball. The server-side translation loader reads translation JSON files at __dirname/../translations/. The package.json#files was missing "translations", so npm packed the tarball without the translation files. The 404 page was the most visible symptom: rendered lazily by the deployed site's static-file fallback, the translation cache was empty, and the keys leaked. Now all 7 locale files ship.
  • @docmd/template-summer: include templates/ and assets/ in the published tarball. The summer template's runtime uses new URL('../templates/...', import.meta.url) inside dist/index.js, which resolves to <package-root>/templates/... (not <package-root>/dist/templates/...). The package.json#files was just ["dist"], so the published tarball only contained dist/, and the resolver fell back to the default template. Now the published tarball matches the monorepo dev layout and the summer template renders correctly.
  • Dev server: strip leading slash before safePath(). The CWE-22 fix from 0.8.9 (replacing filePath.startsWith(rootAbs) with safePath(rootAbs, ...)) had a regression in the dev server: URL pathnames always start with /, and path.resolve('/abs/root', '/index.html') returns /index.html β€” which always failed the safePath boundary check and produced 403 Forbidden for every legitimate request. Fix: strip the leading / from the URL pathname before passing to safePath().
  • Live editor, port probe, and Docker docs default to loopback. The live editor's server.listen and the checkPortInUse probe now bind to 127.0.0.1 instead of 0.0.0.0. LAN access is opt-in via DOCMD_HOST=0.0.0.0 or --host 0.0.0.0, with a TUI warning when active. The docker/DOCKER.md examples now use the loopback default.
  • Utils: scriptLiteral and jsonInject harden inline script escaping. Both now properly escape </script, <!--, U+2028, and U+2029 in addition to the existing JSON-safe encoding. The hardening is silent for non-conflicting strings; round-trips through JSON.parse still work because the escape uses JSON-safe sequences.

Changelog

  1. Registry: New scripts/build-plugin-registry.mjs β€” generates packages/api/registry/plugins.generated.json from each official package's docmd namespace.
  2. Loader: getPluginRegistry reads the generated file with two resolution paths; the hand-maintained installer/registry/plugins.json is no longer consulted at runtime.
  3. Namespace: docmd namespace added to all 14 official @docmd/* packages (12 plugins, 2 engines) and updated on @docmd/template-summer with key: "summer".
  4. Loader: Per-key capability set cache + manifest drift check (checkManifestCapabilityDrift).
  5. Auto-install: Retry path uses await import(name) instead of require.resolve + import(file://path), with a defense-in-depth registry re-check.
  6. Auto-install errors: err.code + first line of err.message surfaced; package-manager stderr + hint printed.
  7. CLI: docmd doctor subcommand added with --config, --fix, --json flags. Registered in the CLI dispatcher; help text updated.
  8. CLI: New pnpm scripts in the monorepo β€” doctor, validate, migrate, gen:deploy, mcp, plugin:add, plugin:remove, build:playground.
  9. UI: packages/ui/package.json#files now includes translations/.
  10. Template: packages/templates/summer/package.json#files now includes templates/ and assets/.
  11. Dev server: safePath() in serveStatic strips the leading / from URL pathnames.
  12. Live editor: server.listen and checkPortInUse bind to 127.0.0.1; DOCMD_HOST=0.0.0.0 opt-in.
  13. Docker docs: Three command: dev --host 0.0.0.0 examples replaced with the loopback default; "Network Issues" troubleshooting documents the opt-in path.
  14. Utils: scriptLiteral and jsonInject escape </script, <!--, U+2028, U+2029.
  15. Tests: packages/utils/test/html-escape.test.js updated to assert the new escape behaviour.
  16. Docs: New "ESM Exports β€” the default Condition" section in building-plugins.md and building-templates.md; new "Bundled registry removal in 0.9.0" callout; new docmd doctor entry in reference/cli-commands.md.

Thanks πŸ’–

Thanks to all contributors and community members who reported issues and provided feedback.

Documentation: https://docs.docmd.io/
GitHub: https://github.com/docmd-io/docmd
Semantic Search (docmd-search): https://github.com/docmd-io/docmd-search
Skills: https://github.com/docmd-io/docmd-skills

Full Changelog: 0.8.8...0.8.9