mirror of
https://github.com/Nanaloveyuki/BitLogger.git
synced 2026-07-23 16:32:19 +00:00
📝 add VitePress site and deployment workflow
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
name: Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: docs-pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build docs site
|
||||
env:
|
||||
DOCS_BASE: /
|
||||
run: npm run docs:build
|
||||
|
||||
- name: Configure Pages
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload Pages artifact
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/.vitepress/dist
|
||||
|
||||
deploy:
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
+7
-1
@@ -16,9 +16,15 @@ desktop.ini
|
||||
*.log
|
||||
*.tmp
|
||||
*.bak
|
||||
node_modules/
|
||||
|
||||
# Dev Documentations
|
||||
docs/dev/*
|
||||
|
||||
# Docs site build artifacts
|
||||
docs/.vitepress/cache/
|
||||
docs/.vitepress/dist/
|
||||
docs/.vitepress/generated/
|
||||
|
||||
# vibecoding
|
||||
AGENTS/
|
||||
AGENTS/
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { basename } from 'node:path'
|
||||
import { createMoonbitLanguageRegistration } from 'moonbit-syntax-highlighter'
|
||||
import { defineConfig, type DefaultTheme } from 'vitepress'
|
||||
|
||||
import { docsData } from './generated/docs-data.mjs'
|
||||
|
||||
type ApiDocEntry = {
|
||||
file: string
|
||||
text: string
|
||||
group: string
|
||||
category: string
|
||||
}
|
||||
|
||||
const repoSlug = process.env.GITHUB_REPOSITORY?.split('/')[1] ?? 'BitLogger'
|
||||
const docsBase = process.env.DOCS_BASE ?? (process.env.GITHUB_ACTIONS ? `/${repoSlug}/` : '/')
|
||||
const repository = 'https://github.com/Nanaloveyuki/BitLogger'
|
||||
|
||||
function splitWords(input: string): string[] {
|
||||
return input
|
||||
.split(/[^A-Za-z0-9]+/)
|
||||
.map(part => part.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function formatWord(word: string): string {
|
||||
const lower = word.toLowerCase()
|
||||
if (lower === 'api') return 'API'
|
||||
if (lower === 'json') return 'JSON'
|
||||
if (lower === 'js') return 'JS'
|
||||
if (lower === 'wasm') return 'WASM'
|
||||
if (lower === 'llvm') return 'LLVM'
|
||||
return lower.charAt(0).toUpperCase() + lower.slice(1)
|
||||
}
|
||||
|
||||
function humanize(input: string): string {
|
||||
const words = splitWords(input)
|
||||
if (words.length === 0) return input
|
||||
return words.map(formatWord).join(' ')
|
||||
}
|
||||
|
||||
function readApiDocs(): ApiDocEntry[] {
|
||||
return docsData.api.entries.map(entry => ({
|
||||
file: entry.file,
|
||||
text: entry.title,
|
||||
group: entry.group,
|
||||
category: entry.category,
|
||||
}))
|
||||
}
|
||||
|
||||
function buildApiSidebar(): DefaultTheme.SidebarItem[] {
|
||||
const entries = readApiDocs()
|
||||
const sidebar: DefaultTheme.SidebarItem[] = [{ text: 'Overview', link: '/api/' }]
|
||||
const groups = new Map<string, Map<string, DefaultTheme.SidebarItem[]>>()
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.file === 'index.md') continue
|
||||
const group = groups.get(entry.group) ?? new Map<string, DefaultTheme.SidebarItem[]>()
|
||||
const categoryItems = group.get(entry.category) ?? []
|
||||
categoryItems.push({
|
||||
text: entry.text,
|
||||
link: `/api/${basename(entry.file, '.md')}`,
|
||||
})
|
||||
group.set(entry.category, categoryItems)
|
||||
groups.set(entry.group, group)
|
||||
}
|
||||
|
||||
if (groups.size === 1 && groups.has('api')) {
|
||||
const categories = groups.get('api')!
|
||||
for (const category of [...categories.keys()].sort((a, b) => humanize(a).localeCompare(humanize(b)))) {
|
||||
sidebar.push({
|
||||
text: humanize(category),
|
||||
collapsed: true,
|
||||
items: categories.get(category)!,
|
||||
})
|
||||
}
|
||||
return sidebar
|
||||
}
|
||||
|
||||
for (const group of [...groups.keys()].sort((a, b) => humanize(a).localeCompare(humanize(b)))) {
|
||||
const categories = groups.get(group)!
|
||||
sidebar.push({
|
||||
text: humanize(group),
|
||||
collapsed: true,
|
||||
items: [...categories.keys()]
|
||||
.sort((a, b) => humanize(a).localeCompare(humanize(b)))
|
||||
.map(category => ({
|
||||
text: humanize(category),
|
||||
collapsed: true,
|
||||
items: categories.get(category)!,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
return sidebar
|
||||
}
|
||||
|
||||
function buildChangesSidebar(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{ text: 'Overview', link: '/changes/' },
|
||||
{
|
||||
text: 'Versions',
|
||||
items: docsData.changes.map(item => ({ text: item.version, link: item.link })),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
title: 'BitLogger',
|
||||
description: 'Structured logging library docs for MoonBit.',
|
||||
base: docsBase,
|
||||
cleanUrls: true,
|
||||
srcExclude: ['dev/**'],
|
||||
lastUpdated: true,
|
||||
markdown: {
|
||||
languages: [createMoonbitLanguageRegistration()],
|
||||
},
|
||||
themeConfig: {
|
||||
siteTitle: 'BitLogger',
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'API', link: '/api/' },
|
||||
{ text: 'Changes', link: '/changes/' },
|
||||
{ text: 'Mooncake', link: 'https://mooncakes.io/docs/Nanaloveyuki/BitLogger' },
|
||||
],
|
||||
search: {
|
||||
provider: 'local',
|
||||
},
|
||||
socialLinks: [{ icon: 'github', link: repository }],
|
||||
editLink: {
|
||||
pattern: `${repository}/edit/main/docs/:path`,
|
||||
text: 'Edit this page on GitHub',
|
||||
},
|
||||
sidebar: {
|
||||
'/api/': buildApiSidebar(),
|
||||
'/changes/': buildChangesSidebar(),
|
||||
},
|
||||
footer: {
|
||||
message: 'Published from the repository docs folder with VitePress.',
|
||||
copyright: 'MIT',
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,219 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, shallowRef } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
import { docsData } from '../../generated/docs-data.mjs'
|
||||
|
||||
const selectedGroup = shallowRef('all')
|
||||
const query = shallowRef('')
|
||||
|
||||
const groups = computed(() => docsData.api.groups)
|
||||
|
||||
const filteredGroups = computed(() => {
|
||||
const keyword = query.value.trim().toLowerCase()
|
||||
return groups.value
|
||||
.filter(group => selectedGroup.value === 'all' || group.id === selectedGroup.value)
|
||||
.map(group => ({
|
||||
...group,
|
||||
categories: group.categories
|
||||
.map(category => ({
|
||||
...category,
|
||||
entries: category.entries.filter(entry => {
|
||||
if (!keyword) return true
|
||||
const haystack = [entry.title, entry.description, entry.name, ...entry.keywords]
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
return haystack.includes(keyword)
|
||||
}),
|
||||
}))
|
||||
.filter(category => category.entries.length > 0),
|
||||
}))
|
||||
.filter(group => group.categories.length > 0)
|
||||
})
|
||||
|
||||
const resultCount = computed(() =>
|
||||
filteredGroups.value.reduce(
|
||||
(total, group) => total + group.categories.reduce((sum, category) => sum + category.entries.length, 0),
|
||||
0,
|
||||
),
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="api-shell">
|
||||
<div class="api-toolbar">
|
||||
<label class="api-search">
|
||||
<span>Find API</span>
|
||||
<input v-model="query" type="search" placeholder="Search logger, sink, config, async...">
|
||||
</label>
|
||||
|
||||
<label class="api-filter">
|
||||
<span>Doc Group</span>
|
||||
<select v-model="selectedGroup">
|
||||
<option value="all">All groups</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="group.id">
|
||||
{{ group.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="api-summary">
|
||||
<strong>{{ resultCount }}</strong>
|
||||
<span>matching API pages across {{ filteredGroups.length }} group views</span>
|
||||
</div>
|
||||
|
||||
<div class="api-groups">
|
||||
<article v-for="group in filteredGroups" :key="group.id" class="api-group-card">
|
||||
<header class="api-group-header">
|
||||
<div>
|
||||
<p class="api-group-kicker">{{ group.label }}</p>
|
||||
<h3>{{ group.entryCount }} APIs in this group</h3>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="api-category-grid">
|
||||
<section v-for="category in group.categories" :key="category.id" class="api-category-card">
|
||||
<h4>{{ category.label }}</h4>
|
||||
<p>{{ category.entries.length }} entries</p>
|
||||
<ul>
|
||||
<li v-for="entry in category.entries.slice(0, 8)" :key="entry.slug">
|
||||
<a :href="withBase(entry.link)">{{ entry.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<a v-if="category.entries.length > 8" class="api-more" :href="withBase('/api/')">
|
||||
Browse {{ category.entries.length - 8 }} more in sidebar
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.api-shell {
|
||||
margin: 1.5rem 0 2rem;
|
||||
}
|
||||
|
||||
.api-toolbar {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.api-search,
|
||||
.api-filter {
|
||||
display: grid;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.api-search span,
|
||||
.api-filter span {
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #9b4d28;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.api-search input,
|
||||
.api-filter select {
|
||||
width: 100%;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 0.85rem 0.95rem;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.api-summary {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.65rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.api-summary strong {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.api-summary span {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.api-groups {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.api-group-card {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 28px;
|
||||
padding: 1rem;
|
||||
background: linear-gradient(180deg, rgba(255, 251, 247, 0.96), rgba(246, 242, 236, 0.96));
|
||||
}
|
||||
|
||||
.api-group-header h3,
|
||||
.api-category-card h4,
|
||||
.api-category-card p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.api-group-kicker {
|
||||
margin: 0 0 0.25rem;
|
||||
color: #9b4d28;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.api-category-grid {
|
||||
display: grid;
|
||||
gap: 0.85rem;
|
||||
margin-top: 0.9rem;
|
||||
}
|
||||
|
||||
.api-category-card {
|
||||
border-radius: 20px;
|
||||
background: rgba(255, 255, 255, 0.88);
|
||||
border: 1px solid rgba(155, 77, 40, 0.12);
|
||||
padding: 0.95rem;
|
||||
}
|
||||
|
||||
.api-category-card p {
|
||||
margin-top: 0.25rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.api-category-card ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.85rem 0 0;
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.api-category-card a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.api-more {
|
||||
display: inline-block;
|
||||
margin-top: 0.8rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (min-width: 820px) {
|
||||
.api-toolbar {
|
||||
grid-template-columns: minmax(0, 2fr) minmax(220px, 0.8fr);
|
||||
}
|
||||
|
||||
.api-category-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
import { docsData } from '../../generated/docs-data.mjs'
|
||||
|
||||
const topCategories = computed(() => docsData.api.categories.slice(0, 6))
|
||||
const recentChanges = computed(() => docsData.changes.slice(0, 4))
|
||||
const apiCount = computed(() => docsData.api.entries.filter(entry => entry.slug !== 'index').length)
|
||||
const groupCount = computed(() => docsData.api.groups.length)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="hub-grid">
|
||||
<article class="hub-panel hub-panel-accent">
|
||||
<p class="hub-eyebrow">Docs Hub</p>
|
||||
<h2 class="hub-title">Start from use case, not from filenames.</h2>
|
||||
<p class="hub-copy">
|
||||
Browse builders, runtime helpers, presets, sync logging, and async logging from one place.
|
||||
</p>
|
||||
<div class="hub-stats">
|
||||
<div>
|
||||
<strong>{{ apiCount }}</strong>
|
||||
<span>API pages</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ groupCount }}</strong>
|
||||
<span>doc groups</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ docsData.changes.length }}</strong>
|
||||
<span>release notes</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="hub-panel">
|
||||
<p class="hub-eyebrow">Popular Areas</p>
|
||||
<ul class="hub-chip-list">
|
||||
<li v-for="category in topCategories" :key="category.id">
|
||||
<a class="hub-chip" :href="withBase('/api/')">
|
||||
<span>{{ category.label }}</span>
|
||||
<small>{{ category.entryCount }}</small>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="hub-panel">
|
||||
<p class="hub-eyebrow">Release Trail</p>
|
||||
<ul class="hub-link-list">
|
||||
<li v-for="item in recentChanges" :key="item.version">
|
||||
<a :href="withBase(item.link)">Version {{ item.version }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hub-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
margin: 1.5rem 0 2rem;
|
||||
}
|
||||
|
||||
.hub-panel {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 24px;
|
||||
padding: 1.25rem;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(247, 245, 239, 0.95));
|
||||
box-shadow: 0 16px 40px rgba(62, 46, 31, 0.08);
|
||||
}
|
||||
|
||||
.hub-panel-accent {
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(191, 68, 32, 0.16), transparent 34%),
|
||||
linear-gradient(180deg, rgba(255, 250, 244, 0.98), rgba(247, 241, 232, 0.98));
|
||||
}
|
||||
|
||||
.hub-eyebrow {
|
||||
margin: 0 0 0.5rem;
|
||||
color: #9b4d28;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hub-title {
|
||||
margin: 0;
|
||||
font-size: 1.55rem;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.hub-copy {
|
||||
margin: 0.85rem 0 0;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.hub-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.15rem;
|
||||
}
|
||||
|
||||
.hub-stats strong,
|
||||
.hub-stats span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hub-stats strong {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.hub-stats span {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.hub-chip-list,
|
||||
.hub-link-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hub-chip-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.hub-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
min-height: 56px;
|
||||
gap: 0.8rem;
|
||||
padding: 0.7rem 0.9rem;
|
||||
border-radius: 999px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 1px solid rgba(155, 77, 40, 0.14);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hub-chip span {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.hub-chip small {
|
||||
flex: 0 0 auto;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hub-chip-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.hub-link-list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.hub-link-list a {
|
||||
color: var(--vp-c-brand-1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (min-width: 860px) {
|
||||
.hub-grid {
|
||||
grid-template-columns: 1.35fr 1fr;
|
||||
}
|
||||
|
||||
.hub-panel-accent {
|
||||
grid-row: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hub-stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,37 @@
|
||||
:root {
|
||||
--vp-c-brand-1: #b34a24;
|
||||
--vp-c-brand-2: #92381a;
|
||||
--vp-c-brand-3: #d06d39;
|
||||
--vp-c-brand-soft: rgba(176, 74, 36, 0.14);
|
||||
--vp-home-hero-name-color: #6b2f17;
|
||||
--vp-home-hero-text-color: #27170f;
|
||||
--vp-home-hero-tagline-color: #5f5249;
|
||||
--vp-font-family-base: "Segoe UI", "PingFang SC", "Hiragino Sans GB", sans-serif;
|
||||
--vp-font-family-mono: "Cascadia Mono", "JetBrains Mono", monospace;
|
||||
}
|
||||
|
||||
.VPContent {
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(208, 109, 57, 0.08), transparent 28%),
|
||||
radial-gradient(circle at left 20%, rgba(179, 74, 36, 0.06), transparent 22%);
|
||||
}
|
||||
|
||||
.VPHomeHero .name,
|
||||
.VPDoc h1,
|
||||
.VPDoc h2,
|
||||
.VPDoc h3 {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.VPHomeHero .image-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vp-doc a {
|
||||
text-underline-offset: 0.18em;
|
||||
}
|
||||
|
||||
.vp-doc .custom-block,
|
||||
.vp-doc div[class*='language-'] {
|
||||
border-radius: 18px;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import type { Theme } from 'vitepress'
|
||||
|
||||
import ApiOverview from './components/ApiOverview.vue'
|
||||
import HomeDocHub from './components/HomeDocHub.vue'
|
||||
|
||||
import './custom.css'
|
||||
|
||||
const theme: Theme = {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp({ app }) {
|
||||
app.component('ApiOverview', ApiOverview)
|
||||
app.component('HomeDocHub', HomeDocHub)
|
||||
},
|
||||
}
|
||||
|
||||
export default theme
|
||||
@@ -0,0 +1,25 @@
|
||||
## BitLogger Update Changes
|
||||
|
||||
version 0.5.3
|
||||
|
||||
### Docs
|
||||
|
||||
- docs: expand `docs/api` coverage for sync and async facade aliases, builders, config models, state models, and runtime/file helper surfaces
|
||||
- docs: clarify facade layering for `ConfiguredLogger`, `ApplicationLogger`, `LibraryLogger`, `ApplicationAsyncLogger`, `ApplicationTextAsyncLogger`, and the library async/text facades
|
||||
- docs: refresh sync and async target-verification notes so published support claims stay aligned with the current validation matrix
|
||||
|
||||
### Test
|
||||
|
||||
- test: cover sync facade composition and parse/build contracts for configured, application, and library logger paths
|
||||
- test: cover async facade helper-surface and unwrap contracts for application, library, parsed, and text-console builder paths
|
||||
- test: keep sync and async package lines validated across `native`, `js`, and `wasm-gc` after the facade contract sweep
|
||||
|
||||
### Runtime
|
||||
|
||||
- fix: correct closed async backlog accounting so pending and dropped counts remain consistent after close and shutdown paths
|
||||
- fix: hide internal file backend wrapper details from the public surface while preserving runtime file-helper behavior
|
||||
|
||||
### Notes
|
||||
|
||||
- this release is a contract-locking follow-up focused on making the published API reference match the live facade behavior more directly
|
||||
- facade-oriented sync and async entry paths now have stronger direct tests for alias-preserved helpers, narrowing boundaries, and unwrap recovery behavior
|
||||
@@ -0,0 +1,12 @@
|
||||
## Release Notes
|
||||
|
||||
Versioned BitLogger change summaries.
|
||||
|
||||
- [0.5.3](./0.5.3.md)
|
||||
- [0.5.2](./0.5.2.md)
|
||||
- [0.5.1](./0.5.1.md)
|
||||
- [0.5.0](./0.5.0.md)
|
||||
- [0.4.1](./0.4.1.md)
|
||||
- [0.4.0](./0.4.0.md)
|
||||
- [0.3.0](./0.3.0.md)
|
||||
- [0.2.0](./0.2.0.md)
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: BitLogger
|
||||
text: Structured logging for MoonBit
|
||||
tagline: API reference, examples, and release notes for BitLogger.
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Open API Reference
|
||||
link: /api/
|
||||
- theme: alt
|
||||
text: Read Release Notes
|
||||
link: /changes/
|
||||
- theme: alt
|
||||
text: Mooncake Package
|
||||
link: https://mooncakes.io/docs/Nanaloveyuki/BitLogger
|
||||
|
||||
features:
|
||||
- title: Structured logging
|
||||
details: Levels, targets, message fields, and configurable text or JSON output.
|
||||
- title: Sync and async paths
|
||||
details: API reference covers both the main package and the async package surface.
|
||||
- title: Config and runtime helpers
|
||||
details: Browse builders, presets, file helpers, queue helpers, and verification notes quickly.
|
||||
---
|
||||
|
||||
## Start Here
|
||||
|
||||
<HomeDocHub />
|
||||
|
||||
- Use [API Reference](./api/index.md) when you want the canonical one-file-per-public-API docs.
|
||||
- Use [Release Notes](./changes/index.md) when you want version-scoped changes and publish-facing summaries.
|
||||
- Use [Chinese README](https://github.com/Nanaloveyuki/BitLogger/blob/main/README.md) or [English README](./README-en.md) when you want a shorter project overview first.
|
||||
Generated
+2837
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "bitlogger-docs-site",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"prepare": "node ./scripts/generate-docs-data.mjs",
|
||||
"dev": "node ./scripts/generate-docs-data.mjs && node ./node_modules/vitepress/dist/node/cli.js dev docs",
|
||||
"build": "node ./scripts/generate-docs-data.mjs && node ./node_modules/vitepress/dist/node/cli.js build docs",
|
||||
"preview": "node ./node_modules/vitepress/dist/node/cli.js preview docs",
|
||||
"docs:prepare": "node ./scripts/generate-docs-data.mjs",
|
||||
"docs:dev": "node ./scripts/generate-docs-data.mjs && node ./node_modules/vitepress/dist/node/cli.js dev docs",
|
||||
"docs:build": "node ./scripts/generate-docs-data.mjs && node ./node_modules/vitepress/dist/node/cli.js build docs",
|
||||
"docs:preview": "node ./node_modules/vitepress/dist/node/cli.js preview docs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gray-matter": "^4.0.3",
|
||||
"moonbit-syntax-highlighter": "^0.1.1",
|
||||
"vitepress": "^1.6.4"
|
||||
}
|
||||
}
|
||||
Generated
+1803
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
allowBuilds:
|
||||
esbuild: true
|
||||
minimumReleaseAgeExclude:
|
||||
- moonbit-syntax-highlighter@0.1.1
|
||||
@@ -0,0 +1,120 @@
|
||||
import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { basename, dirname, extname, resolve } from 'node:path'
|
||||
|
||||
import matter from 'gray-matter'
|
||||
|
||||
const root = process.cwd()
|
||||
const docsDir = resolve(root, 'docs')
|
||||
const apiDir = resolve(docsDir, 'api')
|
||||
const changesDir = resolve(docsDir, 'changes')
|
||||
const outFile = resolve(docsDir, '.vitepress', 'generated', 'docs-data.mjs')
|
||||
|
||||
function splitWords(input) {
|
||||
return input
|
||||
.split(/[^A-Za-z0-9]+/)
|
||||
.map(part => part.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function formatWord(word) {
|
||||
const lower = word.toLowerCase()
|
||||
if (lower === 'api') return 'API'
|
||||
if (lower === 'json') return 'JSON'
|
||||
if (lower === 'js') return 'JS'
|
||||
if (lower === 'wasm') return 'WASM'
|
||||
if (lower === 'llvm') return 'LLVM'
|
||||
return lower.charAt(0).toUpperCase() + lower.slice(1)
|
||||
}
|
||||
|
||||
function humanize(input) {
|
||||
const words = splitWords(input)
|
||||
if (words.length === 0) return input
|
||||
return words.map(formatWord).join(' ')
|
||||
}
|
||||
|
||||
function extractTitle(source, fallback) {
|
||||
const heading = source.match(/^##\s+(.+)$/m)?.[1]?.trim()
|
||||
return humanize(heading ?? fallback)
|
||||
}
|
||||
|
||||
function readApiDocs() {
|
||||
const entries = readdirSync(apiDir, { withFileTypes: true })
|
||||
.filter(entry => entry.isFile())
|
||||
.filter(entry => extname(entry.name) === '.md')
|
||||
.filter(entry => !entry.name.startsWith('.'))
|
||||
.map(entry => {
|
||||
const file = entry.name
|
||||
const slug = basename(file, '.md')
|
||||
const source = readFileSync(resolve(apiDir, file), 'utf8')
|
||||
const parsed = matter(source)
|
||||
const data = parsed.data
|
||||
const keywords = Array.isArray(data['key-word']) ? data['key-word'].map(String) : []
|
||||
return {
|
||||
file,
|
||||
slug,
|
||||
title: extractTitle(parsed.content, data.name ?? slug),
|
||||
name: String(data.name ?? slug),
|
||||
group: String(data.group ?? 'misc'),
|
||||
category: String(data.category ?? 'misc'),
|
||||
description: String(data.description ?? ''),
|
||||
updateTime: data['update-time'] == null ? '' : String(data['update-time']),
|
||||
keywords,
|
||||
link: `/api/${slug}`,
|
||||
}
|
||||
})
|
||||
.sort((left, right) => left.title.localeCompare(right.title))
|
||||
|
||||
const grouped = [...new Set(entries.map(entry => entry.group))]
|
||||
.sort((left, right) => humanize(left).localeCompare(humanize(right)))
|
||||
.map(group => {
|
||||
const items = entries.filter(entry => entry.group === group)
|
||||
const categories = [...new Set(items.map(entry => entry.category))]
|
||||
.sort((left, right) => humanize(left).localeCompare(humanize(right)))
|
||||
.map(category => ({
|
||||
id: category,
|
||||
label: humanize(category),
|
||||
entries: items.filter(entry => entry.category === category),
|
||||
}))
|
||||
|
||||
return {
|
||||
id: group,
|
||||
label: humanize(group),
|
||||
entryCount: items.length,
|
||||
categories,
|
||||
}
|
||||
})
|
||||
|
||||
const categorySummaries = [...new Set(entries.map(entry => entry.category))]
|
||||
.sort((left, right) => humanize(left).localeCompare(humanize(right)))
|
||||
.map(category => ({
|
||||
id: category,
|
||||
label: humanize(category),
|
||||
entryCount: entries.filter(entry => entry.category === category).length,
|
||||
}))
|
||||
|
||||
return {
|
||||
entries,
|
||||
groups: grouped,
|
||||
categories: categorySummaries,
|
||||
}
|
||||
}
|
||||
|
||||
function readChanges() {
|
||||
return readdirSync(changesDir, { withFileTypes: true })
|
||||
.filter(entry => entry.isFile())
|
||||
.filter(entry => extname(entry.name) === '.md')
|
||||
.filter(entry => entry.name !== 'index.md')
|
||||
.map(entry => basename(entry.name, '.md'))
|
||||
.sort((left, right) => right.localeCompare(left, undefined, { numeric: true }))
|
||||
.map(version => ({
|
||||
version,
|
||||
link: `/changes/${version}`,
|
||||
}))
|
||||
}
|
||||
|
||||
const api = readApiDocs()
|
||||
const changes = readChanges()
|
||||
|
||||
const output = `export const docsData = ${JSON.stringify({ api, changes }, null, 2)}\n`
|
||||
mkdirSync(dirname(outFile), { recursive: true })
|
||||
writeFileSync(outFile, output)
|
||||
Reference in New Issue
Block a user