@pagenary/publisher 2026.5.0 → 2026.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/scripts/build-tenants.js
CHANGED
|
@@ -7,8 +7,14 @@ import { spawn, execSync } from 'child_process';
|
|
|
7
7
|
import { createHash } from 'crypto';
|
|
8
8
|
import os from 'os';
|
|
9
9
|
import { generateSeoArtifacts } from './lib/seo-generator.js';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
10
11
|
|
|
11
12
|
const root = process.cwd();
|
|
13
|
+
// The package's own directory (this file lives at <pkg>/scripts/build-tenants.js).
|
|
14
|
+
// Bundled scripts/assets (build.js, src/, build.config.json) resolve against this
|
|
15
|
+
// so the `pagenary` bin works from any consumer CWD (#11). Tenant source/target/
|
|
16
|
+
// registry paths stay relative to `root` (the caller's CWD).
|
|
17
|
+
const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
12
18
|
const DEFAULT_TENANTS_DIR = path.join(root, 'tenants');
|
|
13
19
|
const DEFAULT_DIST_DIR = path.join(root, 'dist');
|
|
14
20
|
const DEFAULT_REGISTRY_PATH = path.join(root, 'tenants.json');
|
|
@@ -738,10 +744,16 @@ function escapeAttribute(value) {
|
|
|
738
744
|
|
|
739
745
|
async function runBuild(buildOutput) {
|
|
740
746
|
return new Promise((resolve, reject) => {
|
|
741
|
-
|
|
742
|
-
|
|
747
|
+
// Resolve build.js and run it from the package dir so it reads the package's
|
|
748
|
+
// own src/ + build.config.json (not the caller's CWD). Pass an ABSOLUTE
|
|
749
|
+
// output path (resolved against the caller's CWD) so the bundle still lands
|
|
750
|
+
// in the right dist/ regardless of build.js's CWD (#11).
|
|
751
|
+
const buildScript = path.join(packageRoot, 'scripts', 'build.js');
|
|
752
|
+
const absOutput = path.resolve(root, buildOutput);
|
|
753
|
+
const proc = spawn(process.execPath, [buildScript], {
|
|
754
|
+
cwd: packageRoot,
|
|
743
755
|
stdio: 'inherit',
|
|
744
|
-
env: { ...process.env, BUILD_OUTPUT:
|
|
756
|
+
env: { ...process.env, BUILD_OUTPUT: absOutput }
|
|
745
757
|
});
|
|
746
758
|
proc.on('exit', (code) => {
|
|
747
759
|
if (code === 0) return resolve();
|
|
@@ -2874,7 +2886,7 @@ async function processTenantContent(sourceDir, distDir, tenantId, options = {},
|
|
|
2874
2886
|
|
|
2875
2887
|
if (contentRoot.type === 'none' && !hasManifest) {
|
|
2876
2888
|
console.warn(` ↳ ${tenantId}: no content found`);
|
|
2877
|
-
return;
|
|
2889
|
+
return { success: true };
|
|
2878
2890
|
}
|
|
2879
2891
|
|
|
2880
2892
|
// If nested structure detected, use new processing
|
|
@@ -2892,8 +2904,7 @@ async function processTenantContent(sourceDir, distDir, tenantId, options = {},
|
|
|
2892
2904
|
|
|
2893
2905
|
if (hasDirectoryType || !hasExplicitFiles) {
|
|
2894
2906
|
// Use nested content processing
|
|
2895
|
-
await processNestedContent(sourceDir, distDir, tenantId, contentRoot, options, config);
|
|
2896
|
-
return;
|
|
2907
|
+
return (await processNestedContent(sourceDir, distDir, tenantId, contentRoot, options, config)) ?? { success: true };
|
|
2897
2908
|
}
|
|
2898
2909
|
} catch {
|
|
2899
2910
|
// Fall through to nested processing if manifest is invalid
|
|
@@ -2901,20 +2912,20 @@ async function processTenantContent(sourceDir, distDir, tenantId, options = {},
|
|
|
2901
2912
|
}
|
|
2902
2913
|
|
|
2903
2914
|
// No manifest or manifest doesn't fully define structure - use nested scanning
|
|
2904
|
-
await processNestedContent(sourceDir, distDir, tenantId, contentRoot, options, config);
|
|
2905
|
-
return;
|
|
2915
|
+
return (await processNestedContent(sourceDir, distDir, tenantId, contentRoot, options, config)) ?? { success: true };
|
|
2906
2916
|
}
|
|
2907
2917
|
|
|
2908
2918
|
// Flat content/ structure with manifest - use legacy processing
|
|
2909
2919
|
if (hasManifest && contentRoot.type === 'flat') {
|
|
2910
|
-
await processTenantManifestLegacy(sourceDir, distDir, tenantId, options);
|
|
2911
|
-
return;
|
|
2920
|
+
return (await processTenantManifestLegacy(sourceDir, distDir, tenantId, options)) ?? { success: true };
|
|
2912
2921
|
}
|
|
2913
2922
|
|
|
2914
2923
|
// Fallback: try legacy processing
|
|
2915
2924
|
if (hasManifest) {
|
|
2916
|
-
await processTenantManifestLegacy(sourceDir, distDir, tenantId, options);
|
|
2925
|
+
return (await processTenantManifestLegacy(sourceDir, distDir, tenantId, options)) ?? { success: true };
|
|
2917
2926
|
}
|
|
2927
|
+
|
|
2928
|
+
return { success: true };
|
|
2918
2929
|
}
|
|
2919
2930
|
|
|
2920
2931
|
/**
|
|
@@ -2996,15 +3007,21 @@ async function processTenantManifestLegacy(sourceDir, distDir, tenantId, options
|
|
|
2996
3007
|
return;
|
|
2997
3008
|
}
|
|
2998
3009
|
|
|
2999
|
-
// Print link warnings
|
|
3010
|
+
// Print link warnings, and fail the tenant on broken links under strict mode
|
|
3000
3011
|
if (linkWarnings.length > 0) {
|
|
3001
3012
|
printLinkWarnings(linkWarnings, tenantId, strictLinks);
|
|
3013
|
+
const brokenLinks = linkWarnings.filter(w => w.type === 'broken');
|
|
3014
|
+
if (strictLinks && brokenLinks.length > 0) {
|
|
3015
|
+
console.error(` ↳ [ERROR] ${tenantId}: Build failed due to ${brokenLinks.length} broken link(s). Use strictLinks: false to warn instead.`);
|
|
3016
|
+
return { success: false, brokenLinks: brokenLinks.length };
|
|
3017
|
+
}
|
|
3002
3018
|
}
|
|
3003
3019
|
|
|
3004
3020
|
const defaultSection = manifestData.default || manifestData.defaultSection || context.leafOrder[0];
|
|
3005
3021
|
const manifestModule = buildManifestModuleSource(processedManifest, defaultSection, context.siteConfig);
|
|
3006
3022
|
await fsp.writeFile(path.join(distDir, 'manifest.js'), manifestModule, 'utf8');
|
|
3007
3023
|
console.log(` ↳ applied manifest-driven content for ${tenantId}`);
|
|
3024
|
+
return { success: true };
|
|
3008
3025
|
}
|
|
3009
3026
|
|
|
3010
3027
|
/**
|
|
@@ -3127,6 +3144,7 @@ async function buildTenant(tenant, targetOverride, cacheDir, buildOptions) {
|
|
|
3127
3144
|
followLinks: followLinksSetting || false
|
|
3128
3145
|
};
|
|
3129
3146
|
|
|
3147
|
+
let contentResult = { success: true };
|
|
3130
3148
|
if (isExplicitFileBuild) {
|
|
3131
3149
|
// Explicit file targeting - create synthetic change set
|
|
3132
3150
|
const explicitFiles = {
|
|
@@ -3140,7 +3158,7 @@ async function buildTenant(tenant, targetOverride, cacheDir, buildOptions) {
|
|
|
3140
3158
|
if (!(await pathExists(distDir))) {
|
|
3141
3159
|
console.log(` ↳ no existing build found, performing full build first`);
|
|
3142
3160
|
await runBuild(buildOutput);
|
|
3143
|
-
await processTenantContent(sourceDir, distDir, tenantId, contentOptions, config);
|
|
3161
|
+
contentResult = await processTenantContent(sourceDir, distDir, tenantId, contentOptions, config);
|
|
3144
3162
|
}
|
|
3145
3163
|
|
|
3146
3164
|
// Process only the specified files
|
|
@@ -3161,7 +3179,16 @@ async function buildTenant(tenant, targetOverride, cacheDir, buildOptions) {
|
|
|
3161
3179
|
await runBuild(buildOutput);
|
|
3162
3180
|
|
|
3163
3181
|
// Process full manifest from source directory
|
|
3164
|
-
await processTenantContent(sourceDir, distDir, tenantId, contentOptions, config);
|
|
3182
|
+
contentResult = await processTenantContent(sourceDir, distDir, tenantId, contentOptions, config);
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// Fail the tenant before branding/SEO/deploy if content processing failed
|
|
3186
|
+
// (e.g. broken links under strictLinks). Continuing would deploy a half-built
|
|
3187
|
+
// dist with no tenant manifest.js, yielding a misleading "ready" and the
|
|
3188
|
+
// downstream SEO "sectionEntry is not defined" error (#12, #13).
|
|
3189
|
+
if (contentResult && contentResult.success === false) {
|
|
3190
|
+
console.error(` ↳ ${tenantId}: aborting build — content processing failed`);
|
|
3191
|
+
return { success: false, changes };
|
|
3165
3192
|
}
|
|
3166
3193
|
|
|
3167
3194
|
// Apply file overrides from source FIRST (before branding/theme modifications)
|
|
@@ -3559,6 +3586,13 @@ async function main() {
|
|
|
3559
3586
|
}
|
|
3560
3587
|
}
|
|
3561
3588
|
|
|
3589
|
+
// Exit non-zero when any tenant failed so CI can gate on it — e.g. the
|
|
3590
|
+
// strictLinks broken-link gate (#12). Use exitCode (not exit()) so the
|
|
3591
|
+
// return below still works when this script is imported programmatically.
|
|
3592
|
+
if (failCount > 0) {
|
|
3593
|
+
process.exitCode = 1;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3562
3596
|
// Return results for programmatic use (if this script is imported)
|
|
3563
3597
|
return results;
|
|
3564
3598
|
}
|
package/site/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="description" content="Pagenary developer documentation — building, configuring, deploying, and extending the multi-tenant documentation publisher, published with Pagenary itself." />
|
|
8
8
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
9
9
|
<link rel="stylesheet" href="./styles.css" />
|
|
10
|
-
<meta name="x-build" content="2026-05-
|
|
10
|
+
<meta name="x-build" content="2026-05-26T05:39:33.402Z" />
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<a class="skip-link" href="#app">Skip to content</a>
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
}
|
|
101
101
|
}</code></pre>
|
|
102
102
|
<h3 id="registry-properties">Registry Properties</h3>
|
|
103
|
-
<table><thead><tr><th style="text-align: left">Property</th><th style="text-align: left">Required</th><th style="text-align: left">Description</th></tr></thead><tbody><tr><td style="text-align: left">`source`</td><td style="text-align: left">Yes</td><td style="text-align: left">Path to tenant content directory</td></tr><tr><td style="text-align: left">`domain`</td><td style="text-align: left">No</td><td style="text-align: left">Custom domain for Caddy routing</td></tr></tbody></table>
|
|
103
|
+
<table><thead><tr><th style="text-align: left">Property</th><th style="text-align: left">Required</th><th style="text-align: left">Description</th></tr></thead><tbody><tr><td style="text-align: left">`source`</td><td style="text-align: left">Yes</td><td style="text-align: left">Path to tenant content directory</td></tr><tr><td style="text-align: left">`domain`</td><td style="text-align: left">No</td><td style="text-align: left">Custom domain for Caddy routing</td></tr><tr><td style="text-align: left">`enabled`</td><td style="text-align: left">No</td><td style="text-align: left">Whether to build this tenant (default `true`)</td></tr><tr><td style="text-align: left">`strictLinks`</td><td style="text-align: left">No</td><td style="text-align: left">Broken-link gate (default `true`). When `true`, <strong>broken internal links fail the build</strong> — the tenant is reported `Failed` and the process exits non-zero, so CI can gate on it. Set `false` to log broken links as warnings and continue.</td></tr></tbody></table>
|
|
104
104
|
<h3 id="source-types">Source Types</h3>
|
|
105
105
|
<p><strong>Local Path:</strong></p>
|
|
106
106
|
<pre><code class="language-json">{
|
package/site/robots.txt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export async function load() {
|
|
2
|
-
return { html: "<section class=\"section doc markdown\">\n <div class=\"doc-content\">\n<h1 id=\"tenant-configuration-reference\">Tenant Configuration Reference</h1>\n<p>Complete reference for all tenant configuration options.</p>\n<h2 id=\"tenant-registry-tenantsjson\">Tenant Registry (tenants.json)</h2>\n<p>Located at `apps/publisher/tenants.json`, this file registers all tenants:</p>\n<pre><code class=\"language-json\">{\n "tenant-id": {\n "source": "/path/to/content",\n "domain": "docs.example.com"\n }\n}</code></pre>\n<h3 id=\"registry-properties\">Registry Properties</h3>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Required</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`source`</td><td style=\"text-align: left\">Yes</td><td style=\"text-align: left\">Path to tenant content directory</td></tr><tr><td style=\"text-align: left\">`domain`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Custom domain for Caddy routing</td></tr></tbody></table>\n<h3 id=\"source-types\">Source Types</h3>\n<p><strong>Local Path:</strong></p>\n<pre><code class=\"language-json\">{\n "my-docs": {\n "source": "/home/user/my-docs"\n }\n}</code></pre>\n<p><strong>Git Repository:</strong></p>\n<pre><code class=\"language-json\">{\n "my-docs": {\n "source": "git:https://github.com/org/my-docs.git#main"\n }\n}</code></pre>\n<p>Format: `git:<repo-url>#<branch>`</p>\n<p>Git sources are cloned to a cache directory and updated on each build.</p>\n<h2 id=\"tenant-directory-structure\">Tenant Directory Structure</h2>\n<pre><code>my-tenant/\n├── config.json # Branding and theme (required)\n├── manifest.json # Navigation structure (optional)\n├── content/ # Content files\n│ ├── *.md # Markdown files\n│ ├── *.html # HTML files\n│ ├── *.js # JavaScript modules\n│ └── section/ # Nested directories\n│ └── _manifest.json\n├── .public/ # Static assets (optional)\n│ ├── favicon.ico # Favicons copied to dist root\n│ ├── logo.svg # Assets copied to dist/assets/\n│ └── icons/ # Subdirectories preserved\n└── overrides/ # Post-build replacements (optional)\n └── styles.css # Replace built files</code></pre>\n<h2 id=\"branding-configuration-configjson\">Branding Configuration (config.json)</h2>\n<h3 id=\"complete-example\">Complete Example</h3>\n<pre><code class=\"language-json\">{\n "title": "ACME Documentation",\n "description": "Complete guide to the ACME platform",\n "brandMark": "ACME",\n "brandSub": "Docs",\n "tagline": "Build better, faster",\n "copyright": "ACME Corporation",\n "accentColor": "#6366F1",\n "surfaceColor": "#F7FAFC"\n}</code></pre>\n<h3 id=\"properties-reference\">Properties Reference</h3>\n<h4 id=\"site-metadata\">Site Metadata</h4>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`title`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"Docs Toolkit"</td><td style=\"text-align: left\">Browser tab title and header</td></tr><tr><td style=\"text-align: left\">`description`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">-</td><td style=\"text-align: left\">Meta description for SEO</td></tr></tbody></table>\n<h4 id=\"branding\">Branding</h4>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`brandMark`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"DOCS"</td><td style=\"text-align: left\">Primary brand text (bold, uppercase)</td></tr><tr><td style=\"text-align: left\">`brandSub`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"TOOLKIT"</td><td style=\"text-align: left\">Secondary brand text (light weight)</td></tr><tr><td style=\"text-align: left\">`tagline`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">-</td><td style=\"text-align: left\">Subtitle displayed under brand</td></tr><tr><td style=\"text-align: left\">`copyright`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"Modular Documentation Toolkit"</td><td style=\"text-align: left\">Footer copyright text</td></tr></tbody></table>\n<h4 id=\"theme-colors\">Theme Colors</h4>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`accentColor`</td><td style=\"text-align: left\">hex string</td><td style=\"text-align: left\">"#111111"</td><td style=\"text-align: left\">Links, buttons, active states</td></tr><tr><td style=\"text-align: left\">`surfaceColor`</td><td style=\"text-align: left\">hex string</td><td style=\"text-align: left\">"#ffffff"</td><td style=\"text-align: left\">Page background color</td></tr></tbody></table>\n<p>Color values must be 6-digit hex codes (e.g., `#6366F1`).</p>\n<h2 id=\"navigation-manifest-manifestjson\">Navigation Manifest (manifest.json)</h2>\n<h3 id=\"root-manifest\">Root Manifest</h3>\n<p>Located at tenant root, defines top-level navigation:</p>\n<pre><code class=\"language-json\">[\n {\n "id": "welcome",\n "title": "Welcome",\n "summary": "Introduction to the platform",\n "file": "welcome.md"\n },\n {\n "id": "guides",\n "title": "Guides",\n "summary": "Step-by-step tutorials",\n "subsections": [\n {\n "id": "guides/getting-started",\n "title": "Getting Started",\n "summary": "First steps",\n "file": "guides/getting-started.md"\n },\n {\n "id": "guides/advanced",\n "title": "Advanced",\n "file": "guides/advanced.md"\n }\n ]\n }\n]</code></pre>\n<h3 id=\"section-properties\">Section Properties</h3>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Required</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`id`</td><td style=\"text-align: left\">Yes*</td><td style=\"text-align: left\">Unique section identifier (used in URLs)</td></tr><tr><td style=\"text-align: left\">`title`</td><td style=\"text-align: left\">Yes</td><td style=\"text-align: left\">Display title in navigation</td></tr><tr><td style=\"text-align: left\">`summary`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Description shown in search results</td></tr><tr><td style=\"text-align: left\">`file`</td><td style=\"text-align: left\">No**</td><td style=\"text-align: left\">Path to content file (relative to `content/`)</td></tr><tr><td style=\"text-align: left\">`url`</td><td style=\"text-align: left\">No**</td><td style=\"text-align: left\">External link URL (opens in new tab)</td></tr><tr><td style=\"text-align: left\">`subsections`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Array of child sections</td></tr><tr><td style=\"text-align: left\">`exclude`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Exclude from build (`true` to skip)</td></tr></tbody></table>\n<p>*Not required for external links</p>\n<p>**Use either `file` OR `url`, not both</p>\n<h3 id=\"external-links-in-navigation\">External Links in Navigation</h3>\n<p>Manifest entries can link to external resources using `url` instead of `id`/`file`:</p>\n<pre><code class=\"language-json\">[\n {\n "id": "docs",\n "title": "Documentation",\n "file": "docs.md"\n },\n {\n "title": "GitHub",\n "url": "https://github.com/example/repo"\n },\n {\n "title": "Support Portal",\n "url": "https://support.example.com"\n }\n]</code></pre>\n<p>External links automatically:</p>\n<ul>\n<li>Open in new tab (`target="_blank"`)</li>\n<li>Display subtle arrow icon indicator (↗)</li>\n<li>Skip URL hash routing</li>\n</ul>\n<h3 id=\"section-manifest-manifestjson\">Section Manifest (_manifest.json)</h3>\n<p>Located in content subdirectories for nested navigation:</p>\n<pre><code class=\"language-json\">{\n "title": "API Reference",\n "summary": "Complete API documentation",\n "sections": [\n {\n "id": "overview",\n "title": "Overview",\n "file": "overview.md"\n },\n {\n "id": "endpoints",\n "title": "Endpoints",\n "file": "endpoints.md"\n }\n ]\n}</code></pre>\n<h3 id=\"auto-generated-manifest\">Auto-Generated Manifest</h3>\n<p>If no `manifest.json` exists, the build system auto-generates navigation from the `content/` directory structure:</p>\n<ul>\n<li>Files become sections (filename → title)</li>\n<li>Directories become groups</li>\n<li>`_manifest.json` in directories customizes the group</li>\n</ul>\n<h2 id=\"bottom-navigation\">Bottom Navigation</h2>\n<p>Configure bottom navigation bar behavior in root `_manifest.json` or `manifest.json`:</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "always",\n "bottomNavSections": ["getting-started", "api-reference", "faq"],\n "sections": [\n // ... section definitions\n ]\n}</code></pre>\n<h3 id=\"bottom-navigation-properties\">Bottom Navigation Properties</h3>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`bottomNav`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">`"mobile"`</td><td style=\"text-align: left\">When to show bottom nav: `"mobile"`, `"always"`, or `"never"`</td></tr><tr><td style=\"text-align: left\">`bottomNavSections`</td><td style=\"text-align: left\">string[]</td><td style=\"text-align: left\">`[]`</td><td style=\"text-align: left\">Section IDs to include (empty array = all sections)</td></tr></tbody></table>\n<p><strong>Behavior:</strong></p>\n<ul>\n<li>`"mobile"` - Show only on small screens (default)</li>\n<li>`"always"` - Show on all screen sizes</li>\n<li>`"never"` - Hide bottom navigation completely</li>\n</ul>\n<p><strong>Examples:</strong></p>\n<p>Show all sections on mobile only (default):</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "mobile"\n}</code></pre>\n<p>Show specific sections always:</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "always",\n "bottomNavSections": ["home", "docs", "api"]\n}</code></pre>\n<p>Hide bottom navigation:</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "never"\n}</code></pre>\n<h2 id=\"content-files\">Content Files</h2>\n<h3 id=\"markdown-md\">Markdown (.md)</h3>\n<p>Full CommonMark support plus:</p>\n<ul>\n<li>Fenced code blocks with syntax highlighting</li>\n<li>Mermaid diagrams with interactive controls</li>\n<li>Internal links via `#section-id`</li>\n<li>External links auto-open in new tab</li>\n</ul>\n<pre><code class=\"language-markdown\"># Page Title\n\nRegular markdown content.\n\n## Code Example\n\n\\`\\`\\`javascript\nconst x = 1;\n\\`\\`\\`\n\n## Diagram\n\n\\`\\`\\`mermaid\ngraph LR\n A --> B\n\\`\\`\\`\n\nSee also: [Getting Started](#guides/getting-started)\n\nLearn more: [GitHub](https://github.com/example)</code></pre>\n<h4 id=\"external-links-in-content\">External Links in Content</h4>\n<p>All HTTP/HTTPS links in markdown automatically:</p>\n<ul>\n<li>Open in new tab (`target="_blank"`)</li>\n<li>Display subtle ↗ indicator via CSS</li>\n<li>Include security attributes (`rel="noopener noreferrer"`)</li>\n</ul>\n<p><strong>Standard external link:</strong></p>\n<pre><code class=\"language-markdown\">Visit our [GitHub repository](https://github.com/example/repo).</code></pre>\n<p><strong>Prominent call-to-action link:</strong></p>\n<div class=\"html-block\"><a href=\"https://example.com/signup\" class=\"external-cta\">\n Sign Up Now →\n</a></div>\n<p>The `.external-cta` class provides enhanced styling for important external links.</p>\n<h4 id=\"mermaid-diagrams\">Mermaid Diagrams</h4>\n<p>Mermaid diagrams render with interactive controls:</p>\n<p><strong>Features:</strong></p>\n<ul>\n<li><strong>Zoom controls</strong>: +/− buttons for zoom in/out</li>\n<li><strong>Reset button</strong>: ⊙ restores original view</li>\n<li><strong>Pan</strong>: Click and drag to move diagram</li>\n<li><strong>Pinch zoom</strong>: Touch devices support pinch gestures</li>\n<li><strong>Auto-scroll</strong>: Diagrams larger than viewport are scrollable</li>\n</ul>\n<p><strong>Supported diagram types:</strong></p>\n<ul>\n<li>Flowcharts (`graph`, `flowchart`)</li>\n<li>Sequence diagrams (`sequenceDiagram`)</li>\n<li>Class diagrams (`classDiagram`)</li>\n<li>State diagrams (`stateDiagram`)</li>\n<li>ER diagrams (`erDiagram`)</li>\n<li>User journey (`journey`)</li>\n<li>Gantt charts (`gantt`)</li>\n<li>And more (see Mermaid documentation)</li>\n</ul>\n<p><strong>Example:</strong></p>\n<pre><code class=\"language-markdown\">\\`\\`\\`mermaid\ngraph TD\n A[Start] --> B{Decision}\n B -->|Yes| C[Action 1]\n B -->|No| D[Action 2]\n C --> E[End]\n D --> E\n\\`\\`\\`</code></pre>\n<h3 id=\"html-html\">HTML (.html)</h3>\n<p>Direct HTML with access to built-in CSS classes:</p>\n<div class=\"html-block\"><section class=\"section doc markdown\">\n <div class=\"doc-content\">\n <h1>Custom Section</h1>\n\n <table class=\"spec-table\">\n <thead>\n <tr><th>Feature</th><th>Status</th></tr>\n </thead>\n <tbody>\n <tr><td>Search</td><td>Ready</td></tr>\n </tbody>\n </table>\n\n <div class=\"layer-stack\">\n <div class=\"layer\">\n <div class=\"layer-title\">Layer 1</div>\n <div class=\"layer-desc\">Description</div>\n </div>\n </div>\n </div>\n</section></div>\n<h3 id=\"javascript-js\">JavaScript (.js)</h3>\n<p>Dynamic content modules:</p>\n<pre><code class=\"language-javascript\">export async function load() {\n // Fetch data, compute values, etc.\n const data = await fetch('/api/data.json').then(r => r.json());\n\n return {\n html: `\n <section class="section doc">\n <h1>Dynamic Content</h1>\n <p>Value: ${data.value}</p>\n </section>\n `,\n afterRender(container) {\n // Optional: DOM manipulation after render\n container.querySelector('button')?.addEventListener('click', () => {\n // Handle click\n });\n }\n };\n}</code></pre>\n<h2 id=\"css-classes-reference\">CSS Classes Reference</h2>\n<h3 id=\"layout\">Layout</h3>\n<table><thead><tr><th style=\"text-align: left\">Class</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`.section`</td><td style=\"text-align: left\">Section container</td></tr><tr><td style=\"text-align: left\">`.doc`</td><td style=\"text-align: left\">Document-style section</td></tr><tr><td style=\"text-align: left\">`.markdown`</td><td style=\"text-align: left\">Apply markdown typography</td></tr><tr><td style=\"text-align: left\">`.doc-content`</td><td style=\"text-align: left\">Content wrapper</td></tr></tbody></table>\n<h3 id=\"components\">Components</h3>\n<table><thead><tr><th style=\"text-align: left\">Class</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`.spec-table`</td><td style=\"text-align: left\">Styled data table</td></tr><tr><td style=\"text-align: left\">`.layer-stack`</td><td style=\"text-align: left\">Vertical layer diagram</td></tr><tr><td style=\"text-align: left\">`.layer`</td><td style=\"text-align: left\">Individual layer in stack</td></tr><tr><td style=\"text-align: left\">`.layer-title`</td><td style=\"text-align: left\">Layer heading</td></tr><tr><td style=\"text-align: left\">`.layer-desc`</td><td style=\"text-align: left\">Layer description</td></tr><tr><td style=\"text-align: left\">`.card`</td><td style=\"text-align: left\">Card component</td></tr><tr><td style=\"text-align: left\">`.card-grid`</td><td style=\"text-align: left\">Grid of cards</td></tr><tr><td style=\"text-align: left\">`.content-box`</td><td style=\"text-align: left\">Bordered content box</td></tr><tr><td style=\"text-align: left\">`.box-title`</td><td style=\"text-align: left\">Box heading</td></tr><tr><td style=\"text-align: left\">`.html-block`</td><td style=\"text-align: left\">HTML content wrapper</td></tr><tr><td style=\"text-align: left\">`.external-cta`</td><td style=\"text-align: left\">Prominent external link button</td></tr></tbody></table>\n<h3 id=\"typography\">Typography</h3>\n<table><thead><tr><th style=\"text-align: left\">Class</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`.doc-h1` through `.doc-h4`</td><td style=\"text-align: left\">Heading styles</td></tr><tr><td style=\"text-align: left\">`.doc-list`</td><td style=\"text-align: left\">Styled list</td></tr><tr><td style=\"text-align: left\">`.doc-grid`</td><td style=\"text-align: left\">Two-column grid</td></tr></tbody></table>\n<h2 id=\"overrides-directory\">Overrides Directory</h2>\n<p>Files in `overrides/` replace built files after the build completes:</p>\n<pre><code>my-tenant/\n└── overrides/\n ├── styles.css # Replace default styles\n └── favicon.ico # Custom favicon</code></pre>\n<p>Use for:</p>\n<ul>\n<li>Custom stylesheets</li>\n<li>Custom favicons</li>\n<li>Additional assets</li>\n</ul>\n<h2 id=\"static-assets-public\">Static Assets (.public/)</h2>\n<p>The `.public/` directory stores static assets (images, icons, logos) that should be included in the built tenant bundle.</p>\n<h3 id=\"directory-structure\">Directory Structure</h3>\n<pre><code>my-tenant/\n├── .public/ # Static assets directory\n│ ├── favicon.ico # Copied to dist root\n│ ├── favicon.png # Copied to dist root\n│ ├── logo.svg # Copied to dist/assets/\n│ └── icons/ # Subdirectories preserved\n│ ├── discord.svg\n│ └── github.svg\n├── config.json\n├── content/\n└── ...</code></pre>\n<h3 id=\"build-behavior\">Build Behavior</h3>\n<p>During the build process:</p>\n<p>1. <strong>Assets directory creation</strong>: Contents are copied to `dist/<tenant-id>/assets/`</p>\n<p>2. <strong>Favicon handling</strong>: Files matching `favicon.*` (e.g., `favicon.ico`, `favicon.png`, `favicon.svg`) are copied to the dist root (`dist/<tenant-id>/`) for browser auto-detection</p>\n<p>3. <strong>Subdirectory preservation</strong>: Subdirectory structure within `.public/` is maintained in the output</p>\n<h3 id=\"referencing-assets-in-content\">Referencing Assets in Content</h3>\n<p><strong>In Markdown:</strong></p>\n<pre><code class=\"language-markdown\">\n</code></pre>\n<p><strong>In HTML content:</strong></p>\n<div class=\"html-block\"><img src=\"./assets/logo.svg\" alt=\"Company Logo\">\n<img src=\"./assets/icons/github.svg\" alt=\"GitHub\"></div>\n<p><strong>In CSS (via overrides):</strong></p>\n<pre><code class=\"language-css\">.custom-header {\n background-image: url(./assets/logo.svg);\n}\n\n.icon-discord {\n content: url(./assets/icons/discord.svg);\n}</code></pre>\n<h3 id=\"why-public-instead-of-public\">Why .public Instead of public?</h3>\n<p>The dot-prefix (`.public/`) was chosen to:</p>\n<ul>\n<li><strong>Avoid conflicts</strong>: Prevents naming collisions with user's conventional `public/` directories that might contain user-facing content</li>\n<li><strong>Clear separation</strong>: Distinguishes between tenant assets and potential user content directories</li>\n<li><strong>Build system clarity</strong>: Signals this is a build-time directive, not user-facing content</li>\n</ul>\n<h3 id=\"supported-file-formats\">Supported File Formats</h3>\n<p>The `.public/` directory supports all static file types:</p>\n<p><strong>Images:</strong></p>\n<ul>\n<li>PNG (`.png`)</li>\n<li>JPEG (`.jpg`, `.jpeg`)</li>\n<li>SVG (`.svg`)</li>\n<li>WebP (`.webp`)</li>\n<li>GIF (`.gif`)</li>\n</ul>\n<p><strong>Icons:</strong></p>\n<ul>\n<li>ICO (`.ico`)</li>\n<li>SVG (`.svg`)</li>\n</ul>\n<p><strong>Other static files:</strong></p>\n<ul>\n<li>Any additional static assets needed by your documentation</li>\n</ul>\n<h3 id=\"example-usage\">Example Usage</h3>\n<p><strong>Typical tenant structure with assets:</strong></p>\n<pre><code>acme-docs/\n├── config.json\n├── manifest.json\n├── .public/\n│ ├── favicon.ico # Browser tab icon\n│ ├── favicon.svg # Modern browsers\n│ ├── logo.svg # Company logo\n│ ├── logo-dark.svg # Dark mode variant\n│ ├── screenshots/ # Product screenshots\n│ │ ├── dashboard.png\n│ │ └── settings.png\n│ └── icons/ # Social/external icons\n│ ├── github.svg\n│ ├── discord.svg\n│ └── twitter.svg\n└── content/\n └── welcome.md</code></pre>\n<p><strong>Referenced in welcome.md:</strong></p>\n<pre><code class=\"language-markdown\"># Welcome to ACME Docs\n\n\n\n## Quick Start\n\nCheck out our dashboard:\n\n\n\n## Community\n\nJoin us on:\n-  [GitHub](https://github.com/acme)\n-  [Discord](https://discord.gg/acme)</code></pre>\n<h2 id=\"environment-variables\">Environment Variables</h2>\n<table><thead><tr><th style=\"text-align: left\">Variable</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`BUILD_OUTPUT`</td><td style=\"text-align: left\">`dist/`</td><td style=\"text-align: left\">Output directory</td></tr><tr><td style=\"text-align: left\">`DOCS_TOOLKIT_PORT`</td><td style=\"text-align: left\">`80`</td><td style=\"text-align: left\">Caddy server port</td></tr><tr><td style=\"text-align: left\">`PORT`</td><td style=\"text-align: left\">`5173`</td><td style=\"text-align: left\">Dev server port</td></tr></tbody></table>\n<h2 id=\"build-modes\">Build Modes</h2>\n<h3 id=\"full-build\">Full Build</h3>\n<pre><code class=\"language-bash\">npm run build:tenants my-tenant</code></pre>\n<p>Rebuilds everything from scratch.</p>\n<h3 id=\"incremental-build\">Incremental Build</h3>\n<pre><code class=\"language-bash\">npm run build:incremental my-tenant</code></pre>\n<p>Only rebuilds files changed since last build (git-aware).</p>\n<h3 id=\"all-tenants\">All Tenants</h3>\n<pre><code class=\"language-bash\">npm run build:tenants</code></pre>\n<p>Builds all registered tenants.</p>\n </div>\n</section>" };
|
|
2
|
+
return { html: "<section class=\"section doc markdown\">\n <div class=\"doc-content\">\n<h1 id=\"tenant-configuration-reference\">Tenant Configuration Reference</h1>\n<p>Complete reference for all tenant configuration options.</p>\n<h2 id=\"tenant-registry-tenantsjson\">Tenant Registry (tenants.json)</h2>\n<p>Located at `apps/publisher/tenants.json`, this file registers all tenants:</p>\n<pre><code class=\"language-json\">{\n "tenant-id": {\n "source": "/path/to/content",\n "domain": "docs.example.com"\n }\n}</code></pre>\n<h3 id=\"registry-properties\">Registry Properties</h3>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Required</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`source`</td><td style=\"text-align: left\">Yes</td><td style=\"text-align: left\">Path to tenant content directory</td></tr><tr><td style=\"text-align: left\">`domain`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Custom domain for Caddy routing</td></tr><tr><td style=\"text-align: left\">`enabled`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Whether to build this tenant (default `true`)</td></tr><tr><td style=\"text-align: left\">`strictLinks`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Broken-link gate (default `true`). When `true`, <strong>broken internal links fail the build</strong> — the tenant is reported `Failed` and the process exits non-zero, so CI can gate on it. Set `false` to log broken links as warnings and continue.</td></tr></tbody></table>\n<h3 id=\"source-types\">Source Types</h3>\n<p><strong>Local Path:</strong></p>\n<pre><code class=\"language-json\">{\n "my-docs": {\n "source": "/home/user/my-docs"\n }\n}</code></pre>\n<p><strong>Git Repository:</strong></p>\n<pre><code class=\"language-json\">{\n "my-docs": {\n "source": "git:https://github.com/org/my-docs.git#main"\n }\n}</code></pre>\n<p>Format: `git:<repo-url>#<branch>`</p>\n<p>Git sources are cloned to a cache directory and updated on each build.</p>\n<h2 id=\"tenant-directory-structure\">Tenant Directory Structure</h2>\n<pre><code>my-tenant/\n├── config.json # Branding and theme (required)\n├── manifest.json # Navigation structure (optional)\n├── content/ # Content files\n│ ├── *.md # Markdown files\n│ ├── *.html # HTML files\n│ ├── *.js # JavaScript modules\n│ └── section/ # Nested directories\n│ └── _manifest.json\n├── .public/ # Static assets (optional)\n│ ├── favicon.ico # Favicons copied to dist root\n│ ├── logo.svg # Assets copied to dist/assets/\n│ └── icons/ # Subdirectories preserved\n└── overrides/ # Post-build replacements (optional)\n └── styles.css # Replace built files</code></pre>\n<h2 id=\"branding-configuration-configjson\">Branding Configuration (config.json)</h2>\n<h3 id=\"complete-example\">Complete Example</h3>\n<pre><code class=\"language-json\">{\n "title": "ACME Documentation",\n "description": "Complete guide to the ACME platform",\n "brandMark": "ACME",\n "brandSub": "Docs",\n "tagline": "Build better, faster",\n "copyright": "ACME Corporation",\n "accentColor": "#6366F1",\n "surfaceColor": "#F7FAFC"\n}</code></pre>\n<h3 id=\"properties-reference\">Properties Reference</h3>\n<h4 id=\"site-metadata\">Site Metadata</h4>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`title`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"Docs Toolkit"</td><td style=\"text-align: left\">Browser tab title and header</td></tr><tr><td style=\"text-align: left\">`description`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">-</td><td style=\"text-align: left\">Meta description for SEO</td></tr></tbody></table>\n<h4 id=\"branding\">Branding</h4>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`brandMark`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"DOCS"</td><td style=\"text-align: left\">Primary brand text (bold, uppercase)</td></tr><tr><td style=\"text-align: left\">`brandSub`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"TOOLKIT"</td><td style=\"text-align: left\">Secondary brand text (light weight)</td></tr><tr><td style=\"text-align: left\">`tagline`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">-</td><td style=\"text-align: left\">Subtitle displayed under brand</td></tr><tr><td style=\"text-align: left\">`copyright`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">"Modular Documentation Toolkit"</td><td style=\"text-align: left\">Footer copyright text</td></tr></tbody></table>\n<h4 id=\"theme-colors\">Theme Colors</h4>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`accentColor`</td><td style=\"text-align: left\">hex string</td><td style=\"text-align: left\">"#111111"</td><td style=\"text-align: left\">Links, buttons, active states</td></tr><tr><td style=\"text-align: left\">`surfaceColor`</td><td style=\"text-align: left\">hex string</td><td style=\"text-align: left\">"#ffffff"</td><td style=\"text-align: left\">Page background color</td></tr></tbody></table>\n<p>Color values must be 6-digit hex codes (e.g., `#6366F1`).</p>\n<h2 id=\"navigation-manifest-manifestjson\">Navigation Manifest (manifest.json)</h2>\n<h3 id=\"root-manifest\">Root Manifest</h3>\n<p>Located at tenant root, defines top-level navigation:</p>\n<pre><code class=\"language-json\">[\n {\n "id": "welcome",\n "title": "Welcome",\n "summary": "Introduction to the platform",\n "file": "welcome.md"\n },\n {\n "id": "guides",\n "title": "Guides",\n "summary": "Step-by-step tutorials",\n "subsections": [\n {\n "id": "guides/getting-started",\n "title": "Getting Started",\n "summary": "First steps",\n "file": "guides/getting-started.md"\n },\n {\n "id": "guides/advanced",\n "title": "Advanced",\n "file": "guides/advanced.md"\n }\n ]\n }\n]</code></pre>\n<h3 id=\"section-properties\">Section Properties</h3>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Required</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`id`</td><td style=\"text-align: left\">Yes*</td><td style=\"text-align: left\">Unique section identifier (used in URLs)</td></tr><tr><td style=\"text-align: left\">`title`</td><td style=\"text-align: left\">Yes</td><td style=\"text-align: left\">Display title in navigation</td></tr><tr><td style=\"text-align: left\">`summary`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Description shown in search results</td></tr><tr><td style=\"text-align: left\">`file`</td><td style=\"text-align: left\">No**</td><td style=\"text-align: left\">Path to content file (relative to `content/`)</td></tr><tr><td style=\"text-align: left\">`url`</td><td style=\"text-align: left\">No**</td><td style=\"text-align: left\">External link URL (opens in new tab)</td></tr><tr><td style=\"text-align: left\">`subsections`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Array of child sections</td></tr><tr><td style=\"text-align: left\">`exclude`</td><td style=\"text-align: left\">No</td><td style=\"text-align: left\">Exclude from build (`true` to skip)</td></tr></tbody></table>\n<p>*Not required for external links</p>\n<p>**Use either `file` OR `url`, not both</p>\n<h3 id=\"external-links-in-navigation\">External Links in Navigation</h3>\n<p>Manifest entries can link to external resources using `url` instead of `id`/`file`:</p>\n<pre><code class=\"language-json\">[\n {\n "id": "docs",\n "title": "Documentation",\n "file": "docs.md"\n },\n {\n "title": "GitHub",\n "url": "https://github.com/example/repo"\n },\n {\n "title": "Support Portal",\n "url": "https://support.example.com"\n }\n]</code></pre>\n<p>External links automatically:</p>\n<ul>\n<li>Open in new tab (`target="_blank"`)</li>\n<li>Display subtle arrow icon indicator (↗)</li>\n<li>Skip URL hash routing</li>\n</ul>\n<h3 id=\"section-manifest-manifestjson\">Section Manifest (_manifest.json)</h3>\n<p>Located in content subdirectories for nested navigation:</p>\n<pre><code class=\"language-json\">{\n "title": "API Reference",\n "summary": "Complete API documentation",\n "sections": [\n {\n "id": "overview",\n "title": "Overview",\n "file": "overview.md"\n },\n {\n "id": "endpoints",\n "title": "Endpoints",\n "file": "endpoints.md"\n }\n ]\n}</code></pre>\n<h3 id=\"auto-generated-manifest\">Auto-Generated Manifest</h3>\n<p>If no `manifest.json` exists, the build system auto-generates navigation from the `content/` directory structure:</p>\n<ul>\n<li>Files become sections (filename → title)</li>\n<li>Directories become groups</li>\n<li>`_manifest.json` in directories customizes the group</li>\n</ul>\n<h2 id=\"bottom-navigation\">Bottom Navigation</h2>\n<p>Configure bottom navigation bar behavior in root `_manifest.json` or `manifest.json`:</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "always",\n "bottomNavSections": ["getting-started", "api-reference", "faq"],\n "sections": [\n // ... section definitions\n ]\n}</code></pre>\n<h3 id=\"bottom-navigation-properties\">Bottom Navigation Properties</h3>\n<table><thead><tr><th style=\"text-align: left\">Property</th><th style=\"text-align: left\">Type</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`bottomNav`</td><td style=\"text-align: left\">string</td><td style=\"text-align: left\">`"mobile"`</td><td style=\"text-align: left\">When to show bottom nav: `"mobile"`, `"always"`, or `"never"`</td></tr><tr><td style=\"text-align: left\">`bottomNavSections`</td><td style=\"text-align: left\">string[]</td><td style=\"text-align: left\">`[]`</td><td style=\"text-align: left\">Section IDs to include (empty array = all sections)</td></tr></tbody></table>\n<p><strong>Behavior:</strong></p>\n<ul>\n<li>`"mobile"` - Show only on small screens (default)</li>\n<li>`"always"` - Show on all screen sizes</li>\n<li>`"never"` - Hide bottom navigation completely</li>\n</ul>\n<p><strong>Examples:</strong></p>\n<p>Show all sections on mobile only (default):</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "mobile"\n}</code></pre>\n<p>Show specific sections always:</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "always",\n "bottomNavSections": ["home", "docs", "api"]\n}</code></pre>\n<p>Hide bottom navigation:</p>\n<pre><code class=\"language-json\">{\n "bottomNav": "never"\n}</code></pre>\n<h2 id=\"content-files\">Content Files</h2>\n<h3 id=\"markdown-md\">Markdown (.md)</h3>\n<p>Full CommonMark support plus:</p>\n<ul>\n<li>Fenced code blocks with syntax highlighting</li>\n<li>Mermaid diagrams with interactive controls</li>\n<li>Internal links via `#section-id`</li>\n<li>External links auto-open in new tab</li>\n</ul>\n<pre><code class=\"language-markdown\"># Page Title\n\nRegular markdown content.\n\n## Code Example\n\n\\`\\`\\`javascript\nconst x = 1;\n\\`\\`\\`\n\n## Diagram\n\n\\`\\`\\`mermaid\ngraph LR\n A --> B\n\\`\\`\\`\n\nSee also: [Getting Started](#guides/getting-started)\n\nLearn more: [GitHub](https://github.com/example)</code></pre>\n<h4 id=\"external-links-in-content\">External Links in Content</h4>\n<p>All HTTP/HTTPS links in markdown automatically:</p>\n<ul>\n<li>Open in new tab (`target="_blank"`)</li>\n<li>Display subtle ↗ indicator via CSS</li>\n<li>Include security attributes (`rel="noopener noreferrer"`)</li>\n</ul>\n<p><strong>Standard external link:</strong></p>\n<pre><code class=\"language-markdown\">Visit our [GitHub repository](https://github.com/example/repo).</code></pre>\n<p><strong>Prominent call-to-action link:</strong></p>\n<div class=\"html-block\"><a href=\"https://example.com/signup\" class=\"external-cta\">\n Sign Up Now →\n</a></div>\n<p>The `.external-cta` class provides enhanced styling for important external links.</p>\n<h4 id=\"mermaid-diagrams\">Mermaid Diagrams</h4>\n<p>Mermaid diagrams render with interactive controls:</p>\n<p><strong>Features:</strong></p>\n<ul>\n<li><strong>Zoom controls</strong>: +/− buttons for zoom in/out</li>\n<li><strong>Reset button</strong>: ⊙ restores original view</li>\n<li><strong>Pan</strong>: Click and drag to move diagram</li>\n<li><strong>Pinch zoom</strong>: Touch devices support pinch gestures</li>\n<li><strong>Auto-scroll</strong>: Diagrams larger than viewport are scrollable</li>\n</ul>\n<p><strong>Supported diagram types:</strong></p>\n<ul>\n<li>Flowcharts (`graph`, `flowchart`)</li>\n<li>Sequence diagrams (`sequenceDiagram`)</li>\n<li>Class diagrams (`classDiagram`)</li>\n<li>State diagrams (`stateDiagram`)</li>\n<li>ER diagrams (`erDiagram`)</li>\n<li>User journey (`journey`)</li>\n<li>Gantt charts (`gantt`)</li>\n<li>And more (see Mermaid documentation)</li>\n</ul>\n<p><strong>Example:</strong></p>\n<pre><code class=\"language-markdown\">\\`\\`\\`mermaid\ngraph TD\n A[Start] --> B{Decision}\n B -->|Yes| C[Action 1]\n B -->|No| D[Action 2]\n C --> E[End]\n D --> E\n\\`\\`\\`</code></pre>\n<h3 id=\"html-html\">HTML (.html)</h3>\n<p>Direct HTML with access to built-in CSS classes:</p>\n<div class=\"html-block\"><section class=\"section doc markdown\">\n <div class=\"doc-content\">\n <h1>Custom Section</h1>\n\n <table class=\"spec-table\">\n <thead>\n <tr><th>Feature</th><th>Status</th></tr>\n </thead>\n <tbody>\n <tr><td>Search</td><td>Ready</td></tr>\n </tbody>\n </table>\n\n <div class=\"layer-stack\">\n <div class=\"layer\">\n <div class=\"layer-title\">Layer 1</div>\n <div class=\"layer-desc\">Description</div>\n </div>\n </div>\n </div>\n</section></div>\n<h3 id=\"javascript-js\">JavaScript (.js)</h3>\n<p>Dynamic content modules:</p>\n<pre><code class=\"language-javascript\">export async function load() {\n // Fetch data, compute values, etc.\n const data = await fetch('/api/data.json').then(r => r.json());\n\n return {\n html: `\n <section class="section doc">\n <h1>Dynamic Content</h1>\n <p>Value: ${data.value}</p>\n </section>\n `,\n afterRender(container) {\n // Optional: DOM manipulation after render\n container.querySelector('button')?.addEventListener('click', () => {\n // Handle click\n });\n }\n };\n}</code></pre>\n<h2 id=\"css-classes-reference\">CSS Classes Reference</h2>\n<h3 id=\"layout\">Layout</h3>\n<table><thead><tr><th style=\"text-align: left\">Class</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`.section`</td><td style=\"text-align: left\">Section container</td></tr><tr><td style=\"text-align: left\">`.doc`</td><td style=\"text-align: left\">Document-style section</td></tr><tr><td style=\"text-align: left\">`.markdown`</td><td style=\"text-align: left\">Apply markdown typography</td></tr><tr><td style=\"text-align: left\">`.doc-content`</td><td style=\"text-align: left\">Content wrapper</td></tr></tbody></table>\n<h3 id=\"components\">Components</h3>\n<table><thead><tr><th style=\"text-align: left\">Class</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`.spec-table`</td><td style=\"text-align: left\">Styled data table</td></tr><tr><td style=\"text-align: left\">`.layer-stack`</td><td style=\"text-align: left\">Vertical layer diagram</td></tr><tr><td style=\"text-align: left\">`.layer`</td><td style=\"text-align: left\">Individual layer in stack</td></tr><tr><td style=\"text-align: left\">`.layer-title`</td><td style=\"text-align: left\">Layer heading</td></tr><tr><td style=\"text-align: left\">`.layer-desc`</td><td style=\"text-align: left\">Layer description</td></tr><tr><td style=\"text-align: left\">`.card`</td><td style=\"text-align: left\">Card component</td></tr><tr><td style=\"text-align: left\">`.card-grid`</td><td style=\"text-align: left\">Grid of cards</td></tr><tr><td style=\"text-align: left\">`.content-box`</td><td style=\"text-align: left\">Bordered content box</td></tr><tr><td style=\"text-align: left\">`.box-title`</td><td style=\"text-align: left\">Box heading</td></tr><tr><td style=\"text-align: left\">`.html-block`</td><td style=\"text-align: left\">HTML content wrapper</td></tr><tr><td style=\"text-align: left\">`.external-cta`</td><td style=\"text-align: left\">Prominent external link button</td></tr></tbody></table>\n<h3 id=\"typography\">Typography</h3>\n<table><thead><tr><th style=\"text-align: left\">Class</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`.doc-h1` through `.doc-h4`</td><td style=\"text-align: left\">Heading styles</td></tr><tr><td style=\"text-align: left\">`.doc-list`</td><td style=\"text-align: left\">Styled list</td></tr><tr><td style=\"text-align: left\">`.doc-grid`</td><td style=\"text-align: left\">Two-column grid</td></tr></tbody></table>\n<h2 id=\"overrides-directory\">Overrides Directory</h2>\n<p>Files in `overrides/` replace built files after the build completes:</p>\n<pre><code>my-tenant/\n└── overrides/\n ├── styles.css # Replace default styles\n └── favicon.ico # Custom favicon</code></pre>\n<p>Use for:</p>\n<ul>\n<li>Custom stylesheets</li>\n<li>Custom favicons</li>\n<li>Additional assets</li>\n</ul>\n<h2 id=\"static-assets-public\">Static Assets (.public/)</h2>\n<p>The `.public/` directory stores static assets (images, icons, logos) that should be included in the built tenant bundle.</p>\n<h3 id=\"directory-structure\">Directory Structure</h3>\n<pre><code>my-tenant/\n├── .public/ # Static assets directory\n│ ├── favicon.ico # Copied to dist root\n│ ├── favicon.png # Copied to dist root\n│ ├── logo.svg # Copied to dist/assets/\n│ └── icons/ # Subdirectories preserved\n│ ├── discord.svg\n│ └── github.svg\n├── config.json\n├── content/\n└── ...</code></pre>\n<h3 id=\"build-behavior\">Build Behavior</h3>\n<p>During the build process:</p>\n<p>1. <strong>Assets directory creation</strong>: Contents are copied to `dist/<tenant-id>/assets/`</p>\n<p>2. <strong>Favicon handling</strong>: Files matching `favicon.*` (e.g., `favicon.ico`, `favicon.png`, `favicon.svg`) are copied to the dist root (`dist/<tenant-id>/`) for browser auto-detection</p>\n<p>3. <strong>Subdirectory preservation</strong>: Subdirectory structure within `.public/` is maintained in the output</p>\n<h3 id=\"referencing-assets-in-content\">Referencing Assets in Content</h3>\n<p><strong>In Markdown:</strong></p>\n<pre><code class=\"language-markdown\">\n</code></pre>\n<p><strong>In HTML content:</strong></p>\n<div class=\"html-block\"><img src=\"./assets/logo.svg\" alt=\"Company Logo\">\n<img src=\"./assets/icons/github.svg\" alt=\"GitHub\"></div>\n<p><strong>In CSS (via overrides):</strong></p>\n<pre><code class=\"language-css\">.custom-header {\n background-image: url(./assets/logo.svg);\n}\n\n.icon-discord {\n content: url(./assets/icons/discord.svg);\n}</code></pre>\n<h3 id=\"why-public-instead-of-public\">Why .public Instead of public?</h3>\n<p>The dot-prefix (`.public/`) was chosen to:</p>\n<ul>\n<li><strong>Avoid conflicts</strong>: Prevents naming collisions with user's conventional `public/` directories that might contain user-facing content</li>\n<li><strong>Clear separation</strong>: Distinguishes between tenant assets and potential user content directories</li>\n<li><strong>Build system clarity</strong>: Signals this is a build-time directive, not user-facing content</li>\n</ul>\n<h3 id=\"supported-file-formats\">Supported File Formats</h3>\n<p>The `.public/` directory supports all static file types:</p>\n<p><strong>Images:</strong></p>\n<ul>\n<li>PNG (`.png`)</li>\n<li>JPEG (`.jpg`, `.jpeg`)</li>\n<li>SVG (`.svg`)</li>\n<li>WebP (`.webp`)</li>\n<li>GIF (`.gif`)</li>\n</ul>\n<p><strong>Icons:</strong></p>\n<ul>\n<li>ICO (`.ico`)</li>\n<li>SVG (`.svg`)</li>\n</ul>\n<p><strong>Other static files:</strong></p>\n<ul>\n<li>Any additional static assets needed by your documentation</li>\n</ul>\n<h3 id=\"example-usage\">Example Usage</h3>\n<p><strong>Typical tenant structure with assets:</strong></p>\n<pre><code>acme-docs/\n├── config.json\n├── manifest.json\n├── .public/\n│ ├── favicon.ico # Browser tab icon\n│ ├── favicon.svg # Modern browsers\n│ ├── logo.svg # Company logo\n│ ├── logo-dark.svg # Dark mode variant\n│ ├── screenshots/ # Product screenshots\n│ │ ├── dashboard.png\n│ │ └── settings.png\n│ └── icons/ # Social/external icons\n│ ├── github.svg\n│ ├── discord.svg\n│ └── twitter.svg\n└── content/\n └── welcome.md</code></pre>\n<p><strong>Referenced in welcome.md:</strong></p>\n<pre><code class=\"language-markdown\"># Welcome to ACME Docs\n\n\n\n## Quick Start\n\nCheck out our dashboard:\n\n\n\n## Community\n\nJoin us on:\n-  [GitHub](https://github.com/acme)\n-  [Discord](https://discord.gg/acme)</code></pre>\n<h2 id=\"environment-variables\">Environment Variables</h2>\n<table><thead><tr><th style=\"text-align: left\">Variable</th><th style=\"text-align: left\">Default</th><th style=\"text-align: left\">Description</th></tr></thead><tbody><tr><td style=\"text-align: left\">`BUILD_OUTPUT`</td><td style=\"text-align: left\">`dist/`</td><td style=\"text-align: left\">Output directory</td></tr><tr><td style=\"text-align: left\">`DOCS_TOOLKIT_PORT`</td><td style=\"text-align: left\">`80`</td><td style=\"text-align: left\">Caddy server port</td></tr><tr><td style=\"text-align: left\">`PORT`</td><td style=\"text-align: left\">`5173`</td><td style=\"text-align: left\">Dev server port</td></tr></tbody></table>\n<h2 id=\"build-modes\">Build Modes</h2>\n<h3 id=\"full-build\">Full Build</h3>\n<pre><code class=\"language-bash\">npm run build:tenants my-tenant</code></pre>\n<p>Rebuilds everything from scratch.</p>\n<h3 id=\"incremental-build\">Incremental Build</h3>\n<pre><code class=\"language-bash\">npm run build:incremental my-tenant</code></pre>\n<p>Only rebuilds files changed since last build (git-aware).</p>\n<h3 id=\"all-tenants\">All Tenants</h3>\n<pre><code class=\"language-bash\">npm run build:tenants</code></pre>\n<p>Builds all registered tenants.</p>\n </div>\n</section>" };
|
|
3
3
|
}
|