/** * -------------------------------------------------------------------- * docmd : the zero-config documentation engine. * * Slice C.1 + C.2 β Plugin-contract and public-API fixes * * Covers: * D-H1 buildSite(configPath) honours the configPath (and an * explicit opts.cwd) instead of always using process.cwd(). * D-H2 @docmd/core re-exports buildWorkspace, detectWorkspace, * and isWorkspace so the documented API actually imports. * D-H3 generateScripts gets a `target: 'head'|'body'` third arg * so plugins can render different content per slot. * D-S4 generateMetaTags returns must be a string; objects produce * a warning and are skipped instead of being stringified to * "[object Object]" and injected into every page's
. * D-S5 generateScripts plain-string returns are treated as the * head slot; previously they were silently dropped on body. * D-M1 translations returns must be a plain stringβstring map; * strings used to spread into garbage numeric keys. * D-H7 onBeforeParse chain continues when a plugin throws. * D-S2 buildContextualUrl strips the `external:` prefix. * D-M2 PostBuildContext.config + pages are typed. * D-M6 URL utility re-export types are aligned. * T-Z3 Unknown top-level config keys produce a warning. * * Run: `node tests/runner.js --only=plugin-contract` * -------------------------------------------------------------------- */ import { DOCMD, TEST_ROOT, setup, writeFile, build, runTestFile } from '../shared.js'; import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'path'; let passed = 0; let failed = 0; const failures = []; function assert(condition, message) { if (!condition) { failed++; failures.push(message); console.log(` β ${message}`); } else { passed++; console.log(` β ${message}`); } } export const test = runTestFile({ name: 'Plugin contract + public API fixes (Slice C.1 + C.2)', emoji: 'π', run: async () => { // D-H2 β re-exports. Direct import to confirm the symbols exist // on the public package surface. We don't need to run a full // build β the import-time check is the test. { const apiPkg = path.resolve(import.meta.dirname, '..', '..', 'packages', 'core', 'dist', 'index.js'); const source = fs.readFileSync(apiPkg, 'utf8'); assert(/export\s*\{\s*[^}]*buildWorkspace[^}]*\}\s*from/.test(source), 'D-H2: buildWorkspace re-exported from @docmd/core'); assert(/export\s*\{\s*[^}]*detectWorkspace[^}]*\}\s*from/.test(source), 'D-H2: detectWorkspace re-exported from @docmd/core'); assert(/export\s*\{\s*[^}]*isWorkspace[^}]*\}\s*from/.test(source), 'D-H2: isWorkspace re-exported from @docmd/core'); } // D-H1 β buildSite honours the configPath. We build the site from // a directory OTHER than cwd, and verify it actually picks up the // docs from that directory (not from cwd/docs). { const proj = setup('plugin-contract-c1-dh1-configpath'); writeFile(proj, 'docs/index.md', '# Hi\n'); writeFile(proj, 'docmd.config.json', JSON.stringify({ title: 'D-H1', src: './docs', out: './site' }, null, 2) + '\n'); // Also plant a decoy docs/ in cwd so a wrong cwd would be detected. const decoyDir = path.join(TEST_ROOT, '__cwd-decoy'); fs.mkdirSync(path.join(decoyDir, 'docs'), { recursive: true }); writeFile(decoyDir, 'docs/index.md', '# DECOY\n'); const configPath = path.join(proj, 'docmd.config.json'); const cmd = `node ${DOCMD} build --config "${configPath}"`; const result = (() => { try { return { ok: true, output: execSync(cmd, { cwd: decoyDir, stdio: 'pipe', encoding: 'utf8' }) }; } catch (e) { return { ok: false, output: (e.stderr || '') + (e.stdout || '') }; } })(); assert(result.ok, 'D-H1: build with --config