accounts/dashboard-fresh/scripts/scan-md.ts
Haitao Pan 9c877ceb3c feat(dashboard-fresh): migrate and integrate dashboard source
- Add fresh app structure (auth, tenant, mail, insight, docs, panel)
- Include CMS content, API routes, scripts, and config
- Migrate UI components, themes, and extensions to fresh runtime
2025-11-04 18:06:21 +08:00

123 lines
3.1 KiB
TypeScript

#!/usr/bin/env -S deno run --allow-read --allow-write
import { join } from '$std/path/mod.ts'
import { ensureDir, walk } from '$std/fs/mod.ts'
const projectRoot = new URL('..', import.meta.url).pathname
const CONTENT_DIR = join(projectRoot, 'cms/content')
const OUTPUT_DIR = join(projectRoot, 'static/_build')
const OUTPUT_FILE = join(OUTPUT_DIR, 'docs_index.json')
type DocEntry = {
slug: string
title: string
description: string
updatedAt: string
pathSegments: string[]
}
/**
* Extract title from markdown content
*/
function extractTitle(lines: string[], fallback: string): string {
const heading = lines.find((line) => /^#\s+/.test(line))
if (!heading) return fallback
return heading.replace(/^#\s+/, '').trim() || fallback
}
/**
* Extract description from markdown content
*/
function extractDescription(lines: string[], title: string): string {
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed || /^#\s+/.test(trimmed)) {
continue
}
return trimmed
}
return `${title}.`
}
/**
* Read and parse a markdown file
*/
async function readMarkdownFile(filePath: string, relativePath: string): Promise<DocEntry | null> {
if (!filePath.endsWith('.md')) return null
const slug = relativePath.replace(/\.md$/, '').replace(/\\/g, '/')
try {
const [content, fileInfo] = await Promise.all([
Deno.readTextFile(filePath),
Deno.stat(filePath),
])
const lines = content.split(/\r?\n/)
const title = extractTitle(lines, slug)
const description = extractDescription(lines, title)
return {
slug,
title,
description,
updatedAt: fileInfo.mtime?.toISOString() || new Date().toISOString(),
pathSegments: slug.split('/').filter(Boolean),
}
} catch (error) {
console.warn(`[scan-md] Failed to read ${relativePath}:`, error.message)
return null
}
}
/**
* Collect all documentation entries
*/
async function collectDocs(): Promise<DocEntry[]> {
console.log(`[scan-md] 📂 Scanning ${CONTENT_DIR}...`)
try {
const entries: DocEntry[] = []
for await (const entry of walk(CONTENT_DIR, {
exts: ['.md'],
skip: [/node_modules/, /_build/],
})) {
if (entry.isFile) {
const relativePath = entry.path.replace(CONTENT_DIR + '/', '')
const doc = await readMarkdownFile(entry.path, relativePath)
if (doc) {
entries.push(doc)
}
}
}
return entries.sort((a, b) => a.slug.localeCompare(b.slug))
} catch (error) {
console.warn('[scan-md] Unable to scan markdown directory:', error.message)
return []
}
}
async function main() {
console.log('[scan-md] 🔨 Scanning markdown files...')
await ensureDir(OUTPUT_DIR)
const docs = await collectDocs()
await Deno.writeTextFile(OUTPUT_FILE, JSON.stringify(docs, null, 2))
console.log(`[scan-md] ✓ Scanned ${docs.length} markdown files`)
console.log(`[scan-md] ✓ Output: ${OUTPUT_FILE}`)
}
if (import.meta.main) {
try {
await main()
} catch (error) {
console.error('[scan-md] ✗ Failed:', error)
Deno.exit(1)
}
}