✨ Highlights
This release fixes a regression that broke asset loading on GitHub Pages project sites (#175), hardens the plugin/template/engine auto-install pipeline against shell injection (CWE-78), and removes @docmd/plugin-pwa from the default install (it was never a core plugin).
No breaking changes. No new required config. Existing configs, plugins, and CI pipelines work unchanged.
🐛 Fix: GitHub Pages project sites 404 on all assets (#175)
Users deploying to https://username.github.io/repo-name/ saw every CSS, JS, and image file 404 after upgrading from 0.8.9. The cause: 0.8.10 added a <base href="/"> tag to fix a separate workspace issue, which made the browser resolve all relative URLs against the domain root instead of the subpath.
The fix: docmd now derives the asset base path from your url config automatically. If url is https://username.github.io/my-repo, docmd sets base to /my-repo/ internally. No manual base config is needed.
{
"url": "https://username.github.io/my-repo"
}
That’s it. docmd handles the rest.
Users who explicitly set base are unaffected. The derivation is slash-tolerant on both url and base — "my-repo", "/my-repo", and "/my-repo/" all work.
🔒 Security: shell injection in auto-install (CWE-78)
The plugin auto-install path built a shell-string command by interpolating the package name into pnpm add ${pkg}. A package name with shell metacharacters that ever reached this code path would execute arbitrary commands under the user’s account.
The fix: a new shared module (packages/api/src/runtime-deps.ts) replaces the shell-string with spawn(pm, args, { shell: false }) using a fixed arg array. A strict regex validator (/^@docmd\/(plugin|template|engine)-[a-z0-9][a-z0-9.-]*$/) and a registry lookup act as defence-in-depth. The auto-install path can no longer be turned into a generic npm install loader.
🧰 Shared runtime-deps module
The plugin loader (hooks.ts) and the engine loader (engine.ts) each used to carry their own copy of the auto-install pipeline. Both now go through runtime-deps.ts. One install behaviour, applied everywhere. The TUI status reporter is idempotent per-build, so dev-server rebuilds do not spam duplicate [ WAIT ] / [ DONE ] lines.
🛠 Engine auto-install
Requesting engine: "rust" in config used to fall straight through to JS if @docmd/engine-rust was not installed. The new flow tries to install engine-rust first; if the install fails or the native binary is not usable on the platform, it falls back to JS and surfaces the reason in a [ FAIL ] line. The same applies to loadEngine('js') as a last resort.
📦 Corrected core dependency manifest
@docmd/plugin-pwa was listed as a direct dependency of @docmd/core but was never in CORE_PLUGINS (the source-of-truth constant that drives auto-loading). Every npm install @docmd/core pulled it in, even for users who never enabled PWA.
The fix: removed @docmd/plugin-pwa from packages/core/package.json#dependencies. It now installs on demand, the same as math, threads, and every other optional plugin. The dependency list now matches CORE_PLUGINS exactly.
📐 Test coverage
51 new assertions in tests/cli-contracts/runtime-deps.test.js: public surface, validator (12 positive, 13 negative cases including shell metacharacters), registry cache idempotency, install refusal paths, and static source checks proving no execSync shell-string remains in hooks.ts or engine.ts.
Migration
Zero. Existing configs, plugins, and CI pipelines work unchanged.
Full changelog: https://github.com/docmd-io/docmd/compare/0.8.11...0.8.12