The docmd 0.7.1 release introduces a major architectural improvement — the plugin system has been extracted into a dedicated @docmd/api package, bringing plugin descriptors, crash isolation, and capability enforcement to the ecosystem.

✨ Highlights

📦 @docmd/api — Dedicated Plugin Package

The plugin API surface — hook registration, WebSocket RPC dispatch, and source editing tools — now lives in its own dedicated package: @docmd/api.

npm install @docmd/api

This decouples the plugin ecosystem from the build engine, allowing plugin authors to depend on a lightweight API contract without pulling in the entire @docmd/core package.

Backward Compatible: All exports from @docmd/api are re-exported from @docmd/core. Existing code using @docmd/core imports continues to work without changes.

🛡️ Plugin Descriptors

Plugins can now export a plugin descriptor declaring their identity and capabilities:

export default {
  plugin: {
    name: 'my-analytics',
    version: '1.0.0',
    capabilities: ['head', 'body', 'post-build']
  },

  generateScripts: (config, opts) => { ... },
  onPostBuild: async (ctx) => { ... }
};

The engine validates descriptors at load time — invalid names, versions, or unknown capabilities are rejected immediately for official plugins, and emit warnings for third-party packages.

Migration Note: Descriptors are optional in 0.7.x. A soft deprecation warning is emitted for plugins without one. This will become a hard requirement in 0.8.0.

🔒 Plugin Isolation

Every hook invocation is now wrapped in a try/catch boundary. A broken plugin cannot crash the build or interfere with other plugins. Errors are logged and collected into a summary displayed at the end of the build:

⚠️  2 plugin error(s) occurred (build completed)

🔑 Capability Enforcement

Plugins that declare capabilities can only register for hooks matching those declarations. If a plugin exports a hook it didn’t declare, the engine skips it with a warning:

Plugin "analytics" exports markdownSetup but didn't declare "markdown" capability — skipped
Capability Allowed Hooks Phase
init onConfigResolved Init
markdown markdownSetup Setup
head generateMetaTags, generateScripts (head) Render
body generateScripts (body) Render
build onBeforeParse, onAfterParse, onPageReady Build
post-build onPostBuild Post-Build
dev onDevServerReady Dev Server
assets getAssets Output
actions actions Interactive
events events Interactive
translations translations i18n

Legacy plugins without a descriptor continue to have full access to all hooks.

📝 Complete Changelog

📦 Architecture

  • @docmd/api package: Extracted loadPlugins, createActionDispatcher, createSourceTools, and all plugin/RPC types into packages/api/.
  • @docmd/core re-exports: All moved API symbols are re-exported from @docmd/core/src/index.ts for seamless backward compatibility.
  • Dead code removal: Deleted the original plugin-loader.ts, action-dispatcher.ts, source-tools.ts, and types.ts from @docmd/core after migration.

🔌 Plugin System

  • Plugin Descriptor (plugin export): Name, version, and capability declaration.
  • Validation (§1): Strict enforcement for @docmd/plugin-* packages; soft warnings for third-party plugins.
  • Isolation (§2): safeCall() wrappers around all synchronous hooks; async try/catch for onPostBuild.
  • Capability Enforcement (§3): Hook registration gated by declared capabilities.
  • Expanded Lifecycle Hooks (§4): Introduced onConfigResolved, onDevServerReady, onBeforeParse, onAfterParse, and onPageReady to handle complex site integration.
  • Error Summary: Plugin errors are collected and displayed as a count at the end of the build, without halting the process.

🐛 Bug Fixes

  • 404 page raw key display: Fixed an issue where the 404 page could display errorCode404 as literal text when translations failed to load. The error code is now hardcoded as 404.
  • i18n stringMode script leak: Fixed no-style.ejs injecting the docmd-i18n-strings.js runtime when stringMode is active. The client-side locale runtime is now correctly suppressed in string mode.
  • Plugin resolution in pnpm workspaces: Fixed loadPlugins failing to locate @docmd/plugin-* packages when called from @docmd/api — the function now accepts resolvePaths from the caller to support pnpm’s strict node_modules layout.

⚠️ Breaking Changes

The canonical home for the plugin API is now @docmd/api. While @docmd/core re-exports everything for backward compatibility, plugin authors are encouraged to update their imports:

-import { createActionDispatcher, createSourceTools } from '@docmd/core';
+import { createActionDispatcher, createSourceTools } from '@docmd/api';
-import type { PluginModule, ActionContext, SourceTools } from '@docmd/core';
+import type { PluginModule, ActionContext, SourceTools, PluginDescriptor, Capability } from '@docmd/api';

No action required for end users. This only affects plugin developers who directly import API utilities. The @docmd/core re-exports will remain available indefinitely.

Threads Plugin Peer Dependency

The @docmd/plugin-threads package now peer-depends on @docmd/api instead of @docmd/core. If you install Threads manually (rather than via docmd add threads), ensure @docmd/api is available in your dependency tree.

Migration Guide

For end users: No changes required. npm install docmd@latest is sufficient.

For plugin authors:

  1. Add a plugin descriptor to your default export (optional now, required in 0.8.0):
    export default {
      plugin: { name: 'my-plugin', version: '1.0.0', capabilities: ['head', 'post-build'] },
      // ... hooks
    };
    
  2. Update imports from @docmd/core to @docmd/api for createActionDispatcher, createSourceTools, loadPlugins, and all type exports.
  3. Update peer dependencies from @docmd/core to @docmd/api in your package.json if your plugin uses the RPC or source editing APIs.

See Building Plugins for the full updated API reference.