mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* docs(P13a): add Azure Native VM page, team channel policies page, and azure screenshots * docs(P13a): use Note/Important JSX components for consistency with directory siblings * docs(P13a): wire Azure Native VM into Install on Linux sidebar and card grid The new deploy-azure-native-vm.mdx page was orphaned from navigation: the Deployment Guide sidebar's "Install on Linux" grouping and the "Choose an install method" card grid on deploy-linux.mdx are both manually maintained lists, so adding the file alone didn't surface it anywhere. --------- Co-authored-by: Hanzo Dev <[email protected]>
561 lines
21 KiB
JavaScript
561 lines
21 KiB
JavaScript
#!/usr/bin/env node
|
|
// Generate the Documentation sidebar from the migrated content tree under
|
|
// main/. Output: docs-site/sidebars/documentation.generated.json
|
|
//
|
|
// Mirrors gen-developer-sidebar.mjs in structure. Only differences are
|
|
// the source directory and the top-level section list (per PLAN.md 3.1).
|
|
//
|
|
// Usage: node docs-site/scripts/gen-documentation-sidebar.mjs
|
|
|
|
import {readFileSync, writeFileSync, readdirSync, statSync, existsSync} from 'node:fs';
|
|
import {join, resolve, basename, dirname} from 'node:path';
|
|
import {fileURLToPath} from 'node:url';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const SITE_ROOT = resolve(HERE, '..');
|
|
const REPO_ROOT = resolve(SITE_ROOT, '..');
|
|
const SRC = join(REPO_ROOT, 'main');
|
|
const OUT = join(SITE_ROOT, 'sidebars', 'documentation.generated.json');
|
|
|
|
const TOP_LEVEL = [
|
|
{dir: 'product-overview', label: 'Overview'},
|
|
{dir: 'use-case-guide', label: 'Use Case Guide'},
|
|
{dir: 'deployment-guide', label: 'Deployment Guide'},
|
|
{dir: 'administration-guide', label: 'Administration Guide'},
|
|
{dir: 'security-guide', label: 'Security & Compliance'},
|
|
{dir: 'end-user-guide', label: 'End User Guide'},
|
|
{dir: 'integrations-guide', label: 'Integrations Guide'},
|
|
{dir: 'get-help', label: 'Get Help'},
|
|
{dir: 'agents', label: 'Agents'},
|
|
{dir: 'recipes', label: 'Recipes'},
|
|
{dir: 'samples', label: 'Samples'},
|
|
];
|
|
|
|
function humanize(name) {
|
|
return name.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
}
|
|
|
|
function readFm(filePath, key) {
|
|
try {
|
|
const text = readFileSync(filePath, 'utf8').slice(0, 4000);
|
|
const m = text.match(/^---\n([\s\S]*?)\n---/);
|
|
if (!m) return null;
|
|
const r = new RegExp(`^${key}:\\s*"?([^"\\n]+)"?`, 'm');
|
|
const x = m[1].match(r);
|
|
return x ? x[1].trim().replace(/^"|"$/g, '') : null;
|
|
} catch { return null; }
|
|
}
|
|
|
|
function isDraft(filePath) {
|
|
return readFm(filePath, 'draft') === 'true';
|
|
}
|
|
|
|
function pathToDocId(relPath) { return relPath.replace(/\.(md|mdx)$/, ''); }
|
|
|
|
function buildCategory(absDir, docsRelDir) {
|
|
const entries = readdirSync(absDir);
|
|
const indexFile = entries.find((e) => /^index\.(md|mdx)$/.test(e));
|
|
let categoryLink = null;
|
|
if (indexFile && !isDraft(join(absDir, indexFile))) {
|
|
categoryLink = {type: 'doc', id: pathToDocId(join(docsRelDir, indexFile))};
|
|
}
|
|
|
|
const subDirs = [];
|
|
const leafDocs = [];
|
|
for (const name of entries) {
|
|
if (/^index\.(md|mdx)$/.test(name)) continue;
|
|
const abs = join(absDir, name);
|
|
const st = statSync(abs);
|
|
if (st.isDirectory()) subDirs.push(name);
|
|
else if (st.isFile() && /\.(md|mdx)$/.test(name) && !isDraft(abs)) leafDocs.push(name);
|
|
}
|
|
|
|
function key(name, abs) {
|
|
const p = readFm(abs, 'sidebar_position');
|
|
return [p ? Number(p) : 9999, name.toLowerCase()];
|
|
}
|
|
leafDocs.sort((a, b) => {
|
|
const ka = key(a, join(absDir, a));
|
|
const kb = key(b, join(absDir, b));
|
|
return ka[0] - kb[0] || ka[1].localeCompare(kb[1]);
|
|
});
|
|
subDirs.sort((a, b) => {
|
|
const ka = key(a, join(absDir, a, 'index.mdx'));
|
|
const kb = key(b, join(absDir, b, 'index.mdx'));
|
|
return ka[0] - kb[0] || ka[1].localeCompare(kb[1]);
|
|
});
|
|
|
|
const items = [];
|
|
for (const name of leafDocs) {
|
|
const id = pathToDocId(join(docsRelDir, name));
|
|
const filePath = join(absDir, name);
|
|
const label = readFm(filePath, 'sidebar_label') ||
|
|
readFm(filePath, 'title') ||
|
|
humanize(basename(name, /\.(md|mdx)$/.exec(name)[0]));
|
|
items.push({type: 'doc', id, label});
|
|
}
|
|
for (const name of subDirs) {
|
|
const sub = buildCategory(join(absDir, name), join(docsRelDir, name));
|
|
if (sub) items.push(sub);
|
|
}
|
|
|
|
const label =
|
|
readFm(join(absDir, 'index.md'), 'title') ||
|
|
readFm(join(absDir, 'index.mdx'), 'title') ||
|
|
humanize(basename(absDir));
|
|
|
|
if (!categoryLink && items.length === 0) return null;
|
|
|
|
return {type: 'category', label, collapsed: true, ...(categoryLink ? {link: categoryLink} : {}), items};
|
|
}
|
|
|
|
// Files in docs/product-overview/ that are MDX snippet/partial includes,
|
|
// not standalone pages. Excluded from the auto-generated sidebar so they
|
|
// don't appear as orphan entries. They remain importable from other MDX.
|
|
const OVERVIEW_HIDDEN = new Set([
|
|
'common-esr-support',
|
|
'common-esr-support-upgrade',
|
|
'common-esr-support-rst',
|
|
]);
|
|
|
|
// Manual grouping override for the Overview section.
|
|
//
|
|
// The Overview directory is flat (~40 .mdx files at one level) for URL-stability
|
|
// reasons — moving files into sub-directories would break the redirect table.
|
|
// This override applies a logical grouping at sidebar-render time only; the
|
|
// files themselves stay flat on disk and their URLs stay stable.
|
|
//
|
|
// Mirrors the live docs.mattermost.com Overview structure.
|
|
const OVERVIEW_GROUPS = {
|
|
// 'Subscription Overview' — paid subscription model: Self-Hosted, Cloud, Non-Profit.
|
|
subscription: {
|
|
label: 'Subscription Overview',
|
|
landing: 'subscription',
|
|
items: [
|
|
'self-hosted-subscriptions',
|
|
{label: 'Cloud', landing: 'cloud-subscriptions', items: [
|
|
'cloud-dedicated',
|
|
'cloud-shared',
|
|
'cloud-vpc-private-connectivity',
|
|
'cloud-supported-integrations',
|
|
'corporate-directory-integration',
|
|
]},
|
|
'non-profit-subscriptions',
|
|
],
|
|
},
|
|
// 'Releases and Life Cycle' with Server / Desktop / Mobile sub-groups.
|
|
releases: {
|
|
label: 'Releases and Life Cycle',
|
|
landing: 'releases-lifecycle',
|
|
items: [
|
|
'release-policy',
|
|
{label: 'Server', landing: 'server', items: [
|
|
'mattermost-server-releases',
|
|
'mattermost-v11-changelog',
|
|
'mattermost-v10-changelog',
|
|
'unsupported-legacy-releases',
|
|
'version-archive',
|
|
'ui-ada-changelog',
|
|
]},
|
|
{label: 'Desktop', landing: 'desktop', items: [
|
|
'mattermost-desktop-releases',
|
|
'desktop-app-changelog',
|
|
]},
|
|
{label: 'Mobile', landing: 'mobile', items: [
|
|
'mattermost-mobile-releases',
|
|
'mobile-app-changelog',
|
|
]},
|
|
'deprecated-features',
|
|
],
|
|
},
|
|
// 'Frequently Asked Questions'.
|
|
faq: {
|
|
label: 'Frequently Asked Questions',
|
|
landing: 'frequently-asked-questions',
|
|
items: [
|
|
'faq-general',
|
|
'faq-enterprise',
|
|
'faq-federal-procurement',
|
|
'faq-license',
|
|
'faq-mattermost-source-available-license',
|
|
],
|
|
},
|
|
};
|
|
|
|
// Top-level items in the Overview section, in order. Strings are doc basenames;
|
|
// objects are group keys from OVERVIEW_GROUPS above. Mirrors the live site.
|
|
const OVERVIEW_ROOT_ORDER = [
|
|
'editions-and-offerings',
|
|
'plans',
|
|
{group: 'subscription'},
|
|
'certifications-and-compliance',
|
|
'accessibility-compliance-policy',
|
|
{group: 'releases'},
|
|
{group: 'faq'},
|
|
// Locally-added pages not present on live:
|
|
'whats-new-in-v11',
|
|
];
|
|
|
|
function buildOverviewItem(spec, leafLabels) {
|
|
if (typeof spec === 'string') {
|
|
const id = `product-overview/${spec}`;
|
|
return {type: 'doc', id, label: leafLabels[id] || humanize(spec)};
|
|
}
|
|
if (spec.group) {
|
|
const g = OVERVIEW_GROUPS[spec.group];
|
|
if (!g) throw new Error(`unknown overview group: ${spec.group}`);
|
|
return buildOverviewGroup(g, leafLabels);
|
|
}
|
|
return buildOverviewGroup(spec, leafLabels);
|
|
}
|
|
|
|
function buildOverviewGroup(g, leafLabels) {
|
|
const items = g.items.map((it) => buildOverviewItem(it, leafLabels));
|
|
const cat = {type: 'category', label: g.label, collapsed: true, items};
|
|
if (g.landing) {
|
|
cat.link = {type: 'doc', id: `product-overview/${g.landing}`};
|
|
}
|
|
return cat;
|
|
}
|
|
|
|
// Pull every doc label from the auto-generated Overview category so the
|
|
// manual ordering preserves the frontmatter-derived titles.
|
|
function collectLeafLabels(cat, acc = {}) {
|
|
if (!cat || !cat.items) return acc;
|
|
for (const it of cat.items) {
|
|
if (it.type === 'doc' && it.id && it.label) acc[it.id] = it.label;
|
|
else if (it.type === 'category') collectLeafLabels(it, acc);
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Deployment Guide — manual grouping override.
|
|
// ===========================================================================
|
|
//
|
|
// The Deployment Guide directory has loose top-level files mixed with sub-
|
|
// directories (server/, desktop/, mobile/, air-gapped-operations/, reference-
|
|
// architecture/). The auto-generated sidebar ends up as 16 mostly-alphabetical
|
|
// items at the top level, with a 16-item kitchen-sink under Server.
|
|
//
|
|
// This override applies a progression-ordered grouping (Try → Plan → Install
|
|
// → Operate → Security → Troubleshoot) at sidebar-render time only; the
|
|
// files themselves stay flat on disk and their URLs stay stable.
|
|
//
|
|
// Server's internal structure is also restructured into Plan / Install-by-
|
|
// platform / Configure-at-install sub-groups, and the four troubleshooting
|
|
// pages that live under server/ are pulled up into the top-level
|
|
// "Deployment Troubleshooting" category.
|
|
|
|
const DEPLOYMENT_GROUPS = {
|
|
// 'Deployment Scenarios' — promoted from inside Reference Architecture
|
|
// to a top-level group. DISC-relevant patterns (Air-Gapped, DDIL, Mission
|
|
// Partner, OOB, Sovereign-on-Microsoft) deserve prominence, not burial.
|
|
deploymentScenarios: {
|
|
label: 'Deployment Scenarios',
|
|
landing: 'reference-architecture/deployment-scenarios/deployment-scenarios-index',
|
|
items: [
|
|
'reference-architecture/deployment-scenarios/air-gapped-deployment',
|
|
'reference-architecture/deployment-scenarios/deploy-ddil-operations',
|
|
'reference-architecture/deployment-scenarios/deploy-mission-partner',
|
|
'reference-architecture/deployment-scenarios/deploy-oob',
|
|
'reference-architecture/deployment-scenarios/deploy-sovereign-collaboration',
|
|
],
|
|
},
|
|
|
|
// Server — fully restructured. Reference Architecture's remaining pages
|
|
// (Application Architecture + Software & Hardware Requirements) fold in
|
|
// here as the "Plan" sub-group, alongside Preparations + Solution Programs.
|
|
// The standalone Reference Architecture top-level category is eliminated;
|
|
// its index page (reference-architecture-index) is hidden from the sidebar
|
|
// (URL still resolves directly).
|
|
server: {
|
|
label: 'Server',
|
|
landing: 'server/server-deployment-planning',
|
|
items: [
|
|
{label: 'Plan', items: [
|
|
'reference-architecture/application-architecture',
|
|
'software-hardware-requirements',
|
|
'server/preparations',
|
|
'server/orchestration',
|
|
]},
|
|
// Install methods, each with their sub-pages.
|
|
{label: 'Install on Linux', landing: 'server/deploy-linux', items: [
|
|
'server/linux/deploy-ubuntu',
|
|
'server/linux/deploy-rhel',
|
|
'server/linux/deploy-tar',
|
|
'server/linux/deploy-azure-native-vm',
|
|
]},
|
|
{label: 'Install on Kubernetes', landing: 'server/deploy-kubernetes', items: [
|
|
'server/kubernetes/deploy-k8s',
|
|
'server/kubernetes/deploy-k8s-oke',
|
|
]},
|
|
{label: 'Install with Containers', landing: 'server/deploy-containers', items: [
|
|
'server/containers/fips-stig',
|
|
]},
|
|
// Configure at install time — install-blocking decisions like TLS,
|
|
// NGINX reverse proxy, image proxy, MySQL setup, pre-auth secrets.
|
|
{label: 'Configure at install time', items: [
|
|
'server/setup-nginx-proxy',
|
|
'server/setup-tls',
|
|
'server/pre-authentication-secrets',
|
|
'server/image-proxy',
|
|
'server/prepare-mattermost-mysql-database',
|
|
]},
|
|
],
|
|
},
|
|
|
|
// Backup & Disaster Recovery — group the two related pages.
|
|
backupDr: {
|
|
label: 'Backup & Disaster Recovery',
|
|
landing: 'backup-disaster-recovery',
|
|
items: [
|
|
'disaster-recovery-aws',
|
|
],
|
|
},
|
|
|
|
// PostgreSQL Migration — group the three migration pages.
|
|
postgresMig: {
|
|
label: 'PostgreSQL Migration',
|
|
landing: 'postgres-migration',
|
|
items: [
|
|
'postgres-migration-assist-tool',
|
|
'manual-postgres-migration',
|
|
],
|
|
},
|
|
|
|
// Encryption — at-rest + in-transit.
|
|
encryption: {
|
|
label: 'Encryption',
|
|
landing: 'encryption-options',
|
|
items: [
|
|
'transport-encryption',
|
|
],
|
|
},
|
|
|
|
// Deployment Troubleshooting — pulls in the four troubleshooting pages
|
|
// currently scattered inside server/, plus the existing top-level page.
|
|
troubleshooting: {
|
|
label: 'Deployment Troubleshooting',
|
|
landing: 'deployment-troubleshooting',
|
|
items: [
|
|
'server/troubleshooting',
|
|
'server/docker-troubleshooting',
|
|
'server/trouble_mysql',
|
|
'server/trouble-postgres',
|
|
],
|
|
},
|
|
};
|
|
|
|
// Top-level Deployment Guide order — Try → Plan → Install → Operate → Security
|
|
// → Troubleshoot. Strings are paths relative to docs/deployment-guide/; objects
|
|
// reference DEPLOYMENT_GROUPS keys or are inline sub-directories handled
|
|
// by the auto-generator (Desktop, Mobile, Air-Gapped Operations).
|
|
const DEPLOYMENT_ROOT_ORDER = [
|
|
'quick-start-evaluation',
|
|
{group: 'deploymentScenarios'},
|
|
'deployment-architecture',
|
|
{group: 'server'},
|
|
// Desktop, Mobile, Air-Gapped Operations keep their auto-generated trees
|
|
// (each has its own index file + sub-pages). Referenced by the `__auto__`
|
|
// sentinel so we slot them in here, in the order we want.
|
|
{auto: 'desktop'},
|
|
{auto: 'mobile'},
|
|
{auto: 'air-gapped-operations'},
|
|
{group: 'backupDr'},
|
|
{group: 'postgresMig'},
|
|
{group: 'encryption'},
|
|
{group: 'troubleshooting'},
|
|
];
|
|
|
|
// Files re-parented into other groups — exclude from the orphan check so they
|
|
// don't get re-appended at root level.
|
|
const DEPLOYMENT_HIDDEN = new Set([
|
|
'software-hardware-requirements', // → server Plan
|
|
'reference-architecture/application-architecture', // → server Plan
|
|
'reference-architecture/reference-architecture-index', // orphan after RA removal — URL still resolves directly
|
|
'backup-disaster-recovery', // → backupDr (as landing)
|
|
'disaster-recovery-aws', // → backupDr
|
|
'postgres-migration', // → postgresMig (as landing)
|
|
'postgres-migration-assist-tool', // → postgresMig
|
|
'manual-postgres-migration', // → postgresMig
|
|
'encryption-options', // → encryption (as landing)
|
|
'transport-encryption', // → encryption
|
|
'deployment-troubleshooting', // → troubleshooting (as landing)
|
|
'server/troubleshooting', // → troubleshooting
|
|
'server/docker-troubleshooting', // → troubleshooting
|
|
'server/trouble_mysql', // → troubleshooting
|
|
'server/trouble-postgres', // → troubleshooting
|
|
]);
|
|
|
|
function buildDeploymentItem(spec, leafLabels, autoCats) {
|
|
if (typeof spec === 'string') {
|
|
const id = `deployment-guide/${spec}`;
|
|
return {type: 'doc', id, label: leafLabels[id] || humanize(spec.split('/').pop())};
|
|
}
|
|
if (spec.doc) {
|
|
// Explicit doc leaf with an inline label override.
|
|
const id = `deployment-guide/${spec.doc}`;
|
|
return {type: 'doc', id, label: spec.label || leafLabels[id] || humanize(spec.doc.split('/').pop())};
|
|
}
|
|
if (spec.auto) {
|
|
// Reference an auto-generated sub-category (e.g., Desktop, Mobile, Air-Gapped).
|
|
const cat = autoCats.get(spec.auto);
|
|
if (!cat) throw new Error(`auto-category not found: deployment-guide/${spec.auto}`);
|
|
return cat;
|
|
}
|
|
if (spec.group) {
|
|
const g = DEPLOYMENT_GROUPS[spec.group];
|
|
if (!g) throw new Error(`unknown deployment group: ${spec.group}`);
|
|
return buildDeploymentGroup(g, leafLabels, autoCats);
|
|
}
|
|
return buildDeploymentGroup(spec, leafLabels, autoCats);
|
|
}
|
|
|
|
function buildDeploymentGroup(g, leafLabels, autoCats) {
|
|
const items = g.items.map((it) => buildDeploymentItem(it, leafLabels, autoCats));
|
|
const cat = {type: 'category', label: g.label, collapsed: true, items};
|
|
if (g.landing) {
|
|
cat.link = {type: 'doc', id: `deployment-guide/${g.landing}`};
|
|
}
|
|
return cat;
|
|
}
|
|
|
|
function buildDeploymentSidebar(autoCat) {
|
|
// Index the auto-generated sub-categories by directory name so we can
|
|
// hand them off intact to the manual ordering. We look at the category's
|
|
// link target first, and fall back to the first doc child if the dir
|
|
// has no index.{md,mdx} (e.g., desktop/, mobile/).
|
|
const autoCats = new Map();
|
|
function dirNameFromId(id) {
|
|
const parts = id.split('/');
|
|
return parts.length >= 2 ? parts[1] : null;
|
|
}
|
|
for (const it of autoCat.items) {
|
|
if (it.type !== 'category') continue;
|
|
let dirName = null;
|
|
if (it.link && it.link.id) {
|
|
dirName = dirNameFromId(it.link.id);
|
|
}
|
|
if (!dirName && it.items) {
|
|
const firstDoc = it.items.find((c) => c.type === 'doc' && c.id);
|
|
if (firstDoc) dirName = dirNameFromId(firstDoc.id);
|
|
}
|
|
if (dirName) autoCats.set(dirName, it);
|
|
}
|
|
|
|
const leafLabels = collectLeafLabels(autoCat);
|
|
|
|
const items = DEPLOYMENT_ROOT_ORDER.map((spec) => buildDeploymentItem(spec, leafLabels, autoCats));
|
|
|
|
// Orphan detection: surface any leaf doc in the Deployment Guide that we
|
|
// didn't include in the manual order, so new files don't silently disappear.
|
|
// DEPLOYMENT_HIDDEN is files we KNOW are re-parented inside groups — they
|
|
// are referenced (so their labels need to stay in leafLabels) but they
|
|
// must not be re-emitted as orphans.
|
|
const hiddenIds = new Set();
|
|
for (const h of DEPLOYMENT_HIDDEN) hiddenIds.add(`deployment-guide/${h}`);
|
|
const known = new Set();
|
|
(function walk(n) {
|
|
if (Array.isArray(n)) n.forEach(walk);
|
|
else if (n && typeof n === 'object') {
|
|
if (n.type === 'doc' && n.id) known.add(n.id);
|
|
if (n.link && n.link.id) known.add(n.link.id);
|
|
if (n.items) walk(n.items);
|
|
}
|
|
})(items);
|
|
const orphans = [];
|
|
for (const id of Object.keys(leafLabels)) {
|
|
if (!known.has(id) && !hiddenIds.has(id) && id !== 'deployment-guide/deployment-guide-index') {
|
|
orphans.push(id);
|
|
}
|
|
}
|
|
if (orphans.length > 0) {
|
|
console.warn(`[sidebar] WARN: ${orphans.length} Deployment Guide file(s) missing from DEPLOYMENT_ROOT_ORDER — falling through to root:`);
|
|
for (const id of orphans) console.warn(` - ${id}`);
|
|
for (const id of orphans) items.push({type: 'doc', id, label: leafLabels[id]});
|
|
}
|
|
|
|
return {
|
|
type: 'category',
|
|
label: 'Deployment Guide',
|
|
collapsed: true,
|
|
link: {type: 'doc', id: 'deployment-guide/deployment-guide-index'},
|
|
items,
|
|
};
|
|
}
|
|
|
|
function buildOverviewSidebar(autoCat) {
|
|
const leafLabels = collectLeafLabels(autoCat);
|
|
// Drop hidden snippet-include partials from the label map up front.
|
|
for (const hidden of OVERVIEW_HIDDEN) delete leafLabels[`product-overview/${hidden}`];
|
|
|
|
const items = OVERVIEW_ROOT_ORDER.map((spec) => buildOverviewItem(spec, leafLabels));
|
|
|
|
// Surface any flat docs we didn't include in the manual order so a new
|
|
// file dropped into docs/product-overview/ doesn't silently disappear.
|
|
const known = new Set();
|
|
(function walk(n) {
|
|
if (Array.isArray(n)) n.forEach(walk);
|
|
else if (n && typeof n === 'object') {
|
|
if (n.type === 'doc' && n.id) known.add(n.id);
|
|
if (n.link && n.link.id) known.add(n.link.id);
|
|
if (n.items) walk(n.items);
|
|
}
|
|
})(items);
|
|
const orphans = [];
|
|
for (const id of Object.keys(leafLabels)) {
|
|
if (!known.has(id) && id !== 'product-overview/product-overview-index') orphans.push(id);
|
|
}
|
|
if (orphans.length > 0) {
|
|
console.warn(`[sidebar] WARN: ${orphans.length} Overview file(s) missing from OVERVIEW_ROOT_ORDER — falling through to root:`);
|
|
for (const id of orphans) console.warn(` - ${id}`);
|
|
for (const id of orphans) items.push({type: 'doc', id, label: leafLabels[id]});
|
|
}
|
|
|
|
return {
|
|
type: 'category',
|
|
label: 'Overview',
|
|
collapsed: true,
|
|
// Merged with the site landing — clicking "Overview" opens the
|
|
// root doc (docs/index.mdx, slug: /), which is the unified
|
|
// welcome / Overview page.
|
|
link: {type: 'doc', id: 'index'},
|
|
items,
|
|
};
|
|
}
|
|
|
|
function main() {
|
|
if (!existsSync(SRC)) { console.error(`SRC not found at ${SRC}`); process.exit(1); }
|
|
|
|
const sidebar = [];
|
|
for (const {dir, label} of TOP_LEVEL) {
|
|
const abs = join(SRC, dir);
|
|
if (!existsSync(abs)) continue;
|
|
let cat = buildCategory(abs, dir);
|
|
if (!cat) continue;
|
|
cat.label = label;
|
|
cat.collapsed = true;
|
|
if (dir === 'product-overview') {
|
|
cat = buildOverviewSidebar(cat);
|
|
} else if (dir === 'deployment-guide') {
|
|
cat = buildDeploymentSidebar(cat);
|
|
}
|
|
sidebar.push(cat);
|
|
}
|
|
writeFileSync(OUT, JSON.stringify(sidebar, null, 2));
|
|
|
|
let cats = 0, docs = 0;
|
|
(function walk(n) {
|
|
if (Array.isArray(n)) n.forEach(walk);
|
|
else if (n && typeof n === 'object') {
|
|
if (n.type === 'category') cats++;
|
|
if (n.type === 'doc') docs++;
|
|
if (n.items) walk(n.items);
|
|
}
|
|
})(sidebar);
|
|
console.log(`[sidebar] wrote ${OUT}: ${cats} categories, ${docs} docs`);
|
|
}
|
|
|
|
main();
|