import { existsSync, readdirSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; declare const __PROJECT_ROOT__: string; declare const __WEB_PACKAGE_ROOT__: string; const PROJECT_GUIDE_DIR = join(__PROJECT_ROOT__, 'guide'); const DEFAULT_GUIDE_DIR = join(__WEB_PACKAGE_ROOT__, 'src', 'lib', 'server', 'default-guide'); const SOURCE_DIRS = [PROJECT_GUIDE_DIR, DEFAULT_GUIDE_DIR]; export interface DocEntry { slug: string; title: Record; } function extractTitle(content: string): string { const match = content.match(/^#\s+(.+)$/m); return match ? match[1].trim() : ''; } function fileExists(path: string): boolean { return existsSync(path); } function resolveIndexPath(locale: string): string | null { const localizedNames = locale === 'en' ? ['index.md'] : [`index_${locale}.md`, 'index.md']; for (const dir of SOURCE_DIRS) { for (const name of localizedNames) { const candidate = join(dir, name); if (fileExists(candidate)) return candidate; } } return null; } function findFileWithPrefix(dir: string, slug: string, suffix: string): string | null { const direct = join(dir, `${slug}${suffix}`); if (fileExists(direct)) return direct; if (!fileExists(dir)) return null; const pattern = new RegExp(`^\\d+-${slug.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}${suffix.replace('.', '\\.')}$`); for (const file of readdirSync(dir)) { if (pattern.test(file)) return join(dir, file); } return null; } function resolveGuidePath(slug: string, locale: string): string | null { if (slug === '') return resolveIndexPath(locale); for (const dir of SOURCE_DIRS) { if (locale !== 'en') { const localized = findFileWithPrefix(dir, slug, `_${locale}.md`); if (localized) return localized; } const fallback = findFileWithPrefix(dir, slug, '.md'); if (fallback) return fallback; } return null; } function stripPreamble(content: string): string { content = content.replace(/^]*>.*?<\/p>\s*\n*/s, ''); return content.replace(/^#\s+.+\n+/, ''); } function rewriteGuideLinks(content: string): string { return content .replace(/\((?:\.\/)?docs\/(?:\d+-)?([^)\/]+?)(?:_[a-z]{2})?\.md\)/g, '(/guide/$1)') .replace(/\(guide\/(?:\d+-)?([^)]+?)(?:_[a-z]{2})?\.md\)/g, '(/guide/$1)') .replace(/\((?:\.\/)?README(?:_[a-z]{2})?\.md\)/g, '(/guide)'); } function buildEntry(slug: string): DocEntry | null { const enPath = resolveGuidePath(slug, 'en'); if (!enPath) return null; const enTitle = extractTitle(readFileSync(enPath, 'utf-8')) || slug || 'Guide'; const jaPath = resolveGuidePath(slug, 'ja'); const jaTitle = jaPath ? extractTitle(readFileSync(jaPath, 'utf-8')) || enTitle : enTitle; return { slug, title: { en: enTitle, ja: jaTitle } }; } function stripOrderPrefix(name: string): string { return name.replace(/^\d+-/, ''); } function listGuideSlugs(): string[] { const fileNames = new Set(); for (const dir of SOURCE_DIRS) { if (!fileExists(dir)) continue; for (const file of readdirSync(dir)) { if (!file.endsWith('.md') || file.startsWith('.')) continue; if (file === 'index.md' || /^index_[a-z]{2}\.md$/.test(file)) continue; if (file === 'README.md' || /^README_[a-z]{2}\.md$/.test(file)) continue; const baseName = file.replace(/(?:_[a-z]{2})?\.md$/, ''); fileNames.add(baseName); } } return [...fileNames].sort().map(stripOrderPrefix); } export function listDocs(): DocEntry[] { const entries: DocEntry[] = []; const rootEntry = buildEntry(''); if (rootEntry) entries.push(rootEntry); for (const slug of listGuideSlugs()) { const entry = buildEntry(slug); if (entry) entries.push(entry); } return entries; } export function loadDocContent(slug: string, locale: string): string | null { const filePath = resolveGuidePath(slug, locale); if (!filePath) return null; let content = readFileSync(filePath, 'utf-8'); content = stripPreamble(content); content = rewriteGuideLinks(content); return content; }