srvmd 0.1.0
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/ARCHITECTURE.md +237 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/README_CN.md +214 -0
- package/package.json +68 -0
- package/src/cli/browser.ts +14 -0
- package/src/cli/command.ts +56 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/lifecycle.ts +13 -0
- package/src/cli/listen.ts +27 -0
- package/src/cli/options.ts +52 -0
- package/src/cli/report.ts +40 -0
- package/src/server/assets/mermaid.ts +38 -0
- package/src/server/assets/web.ts +34 -0
- package/src/server/config.ts +99 -0
- package/src/server/constants.ts +33 -0
- package/src/server/context.ts +40 -0
- package/src/server/create-server.ts +69 -0
- package/src/server/file-kinds.ts +15 -0
- package/src/server/http/headers.ts +11 -0
- package/src/server/http/respond.ts +74 -0
- package/src/server/http/validators.ts +37 -0
- package/src/server/markdown/frontmatter.ts +29 -0
- package/src/server/markdown/highlight.ts +100 -0
- package/src/server/markdown/index.ts +8 -0
- package/src/server/markdown/mermaid.ts +30 -0
- package/src/server/markdown/render.ts +119 -0
- package/src/server/markdown/text.ts +30 -0
- package/src/server/markdown/toc.ts +29 -0
- package/src/server/path.ts +9 -0
- package/src/server/resolve/path.ts +43 -0
- package/src/server/resolve/resource.ts +88 -0
- package/src/server/routes/index.ts +91 -0
- package/src/server/routes/page-api.ts +88 -0
- package/src/server/routes/shell.ts +50 -0
- package/src/server/services/directories.ts +41 -0
- package/src/server/services/documents.ts +43 -0
- package/src/server/services/live/client-script.ts +12 -0
- package/src/server/services/live/hub.ts +50 -0
- package/src/server/services/live/index.ts +33 -0
- package/src/server/services/live/watcher.ts +126 -0
- package/src/server/types.ts +20 -0
- package/src/shared/breadcrumbs.ts +14 -0
- package/src/shared/constants.ts +20 -0
- package/src/shared/contracts.ts +49 -0
- package/src/shared/types.ts +33 -0
- package/web/dist/assets/index-BjX1ijWA.js +2 -0
- package/web/dist/assets/index-DRuRRv0z.css +1 -0
- package/web/dist/assets/live-CRaSJLxT.js +1 -0
- package/web/dist/index.html +18 -0
- package/web/dist/live-static.js +1 -0
- package/web/dist/theme-boot.js +16 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# 架构说明
|
|
2
|
+
|
|
3
|
+
## 产品约束
|
|
4
|
+
|
|
5
|
+
- 只在本地和局域网使用,不面向公网。
|
|
6
|
+
- 单进程 Bun 服务,托管任意目录,按需渲染 Markdown。
|
|
7
|
+
- 首屏速度、SEO、无 JavaScript 访问不是硬约束;可维护性优先。
|
|
8
|
+
- Mermaid 运行时体积很大,只在文档包含图表时懒加载。
|
|
9
|
+
|
|
10
|
+
## 架构决定
|
|
11
|
+
|
|
12
|
+
采用 **服务端路由 + 客户端渲染(server-routed CSR)**,不采用 SSR,也不采用带前端 router 的 SPA。
|
|
13
|
+
|
|
14
|
+
- 服务器决定 URL 对应什么资源,并处理 clean URL、目录索引、`?raw`、301、404、静态文件。
|
|
15
|
+
- 浏览器每次导航都是一次完整的页面请求。
|
|
16
|
+
- 对 Markdown 和目录路由,服务器返回同一个 Svelte 壳。
|
|
17
|
+
- Svelte 根据 `location.pathname` 请求 `/__api/page/<path>`,拿到页面数据后渲染。
|
|
18
|
+
- 不做前端 router,不拦截 `<a>`,不维护 history。
|
|
19
|
+
|
|
20
|
+
这样只有一套路由规则,客户端不需要重复实现服务端已经拥有的路径语义。以后需要 SPA 式切换时,可以在同一个 API 上增加 router,服务器仍为深链接返回壳。
|
|
21
|
+
|
|
22
|
+
```mermaid
|
|
23
|
+
flowchart LR
|
|
24
|
+
Browser[Browser] -->|GET /docs/guide| Server[Bun server]
|
|
25
|
+
Server -->|resolve| Resolver[resolve / markdown / directory]
|
|
26
|
+
Server -->|200 shell| Browser
|
|
27
|
+
Browser -->|GET /__api/page/docs/guide| Server
|
|
28
|
+
Server -->|DocumentPayload| Browser
|
|
29
|
+
Browser -->|Svelte render| UI[DocumentPage]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 运行时边界
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
flowchart TB
|
|
36
|
+
CLI[cli<br/>process boundary] --> SERVER[server<br/>Bun runtime]
|
|
37
|
+
SERVER --> SHARED[shared<br/>contracts and types]
|
|
38
|
+
WEB[web<br/>browser runtime] --> SHARED
|
|
39
|
+
SERVER -.->|serves web/dist as files| WEB
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `server` 可以使用 Bun 和 Node API,不导入 `web` 源码,不访问 DOM。
|
|
43
|
+
- `web` 可以使用 DOM 和浏览器 API,不导入 `server` 源码,不读文件系统。
|
|
44
|
+
- `shared` 只包含类型、契约和常量,不依赖任何运行时。
|
|
45
|
+
- `cli` 只负责进程边界:解析参数、启动服务器、输出、信号。
|
|
46
|
+
|
|
47
|
+
## 目录职责
|
|
48
|
+
|
|
49
|
+
```plaintext
|
|
50
|
+
src/
|
|
51
|
+
cli/ process boundary
|
|
52
|
+
server/ Bun runtime
|
|
53
|
+
config.ts options -> ResolvedConfig
|
|
54
|
+
constants.ts server constants
|
|
55
|
+
context.ts long-lived services
|
|
56
|
+
create-server.ts Bun.serve assembly
|
|
57
|
+
types.ts server-only types
|
|
58
|
+
path.ts safe path helpers
|
|
59
|
+
file-kinds.ts extension classification
|
|
60
|
+
http/ response/header/validator primitives
|
|
61
|
+
routes/ URL to handler orchestration
|
|
62
|
+
resolve/ URL to safe filesystem resource
|
|
63
|
+
services/ documents, directories, live
|
|
64
|
+
markdown/ Markdown to HTML + TOC + Mermaid sources
|
|
65
|
+
assets/ web build and Mermaid runtime
|
|
66
|
+
web/ browser runtime, all UI
|
|
67
|
+
src/features/ document, directory, mermaid, theme, live
|
|
68
|
+
src/components/ cross-feature presentational components
|
|
69
|
+
src/lib/ API client, config, formatting
|
|
70
|
+
src/live-static.ts prebuilt static-page live reload
|
|
71
|
+
src/styles/ app, markdown, mermaid CSS
|
|
72
|
+
public/theme-boot.js pre-paint theme bootstrap
|
|
73
|
+
shared/ contracts, types, protocol constants
|
|
74
|
+
test/ integration fixtures and Playwright E2E
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
| 目录 | 只负责 | 不负责 |
|
|
78
|
+
| --- | --- | --- |
|
|
79
|
+
| `cli/` | 参数、启动、输出、信号 | 业务逻辑、UI |
|
|
80
|
+
| `server/http/` | Response、headers、ETag、304 | Markdown、目录、页面结构 |
|
|
81
|
+
| `server/routes/` | URL 到 handler 的编排 | 业务实现 |
|
|
82
|
+
| `server/resolve/` | URL 到安全 Resource | 渲染 |
|
|
83
|
+
| `server/services/` | 文档、目录、live 用例 | 拼页面 HTML |
|
|
84
|
+
| `server/markdown/` | Markdown 到 HTML、TOC、Mermaid 数据 | 页面壳 |
|
|
85
|
+
| `web/features/*` | 一个功能的组件和客户端逻辑 | 读文件、导入 server |
|
|
86
|
+
| `web/components/` | 跨 feature 的展示组件 | 发请求 |
|
|
87
|
+
| `shared/` | 跨端契约、纯类型、常量 | 任何运行时依赖 |
|
|
88
|
+
|
|
89
|
+
## 依赖规则
|
|
90
|
+
|
|
91
|
+
```plaintext
|
|
92
|
+
cli -> server -> shared
|
|
93
|
+
web -> shared
|
|
94
|
+
server -/-> web (only serves web/dist as static files)
|
|
95
|
+
web -/-> server
|
|
96
|
+
shared -> nothing
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
约束由架构测试扫描 `.ts` 和 `.svelte` 的 import 来保证:
|
|
100
|
+
|
|
101
|
+
- `shared` 不能导入 `cli`、`server`、`web`,不能使用任何运行时 API。
|
|
102
|
+
- `server` 只能导入 `src/server/`、`src/shared/` 和外部依赖,不能导入 `web` 或 `cli`。
|
|
103
|
+
- `web` 只能导入 `src/shared/`、自身和浏览器端外部依赖,不能导入 `server`、`cli`、`node:*` 或使用 `Bun`。
|
|
104
|
+
- `cli` 只能导入 `cli`、`server`、`shared`、`package.json` 和外部依赖,不能导入 `web`。
|
|
105
|
+
- `server` 和 `shared` 不能使用浏览器 DOM 全局(如 `window.`、`document.querySelector`)。
|
|
106
|
+
|
|
107
|
+
这些规则由 `test/architecture.test.ts` 扫描 `.ts` 和 `.svelte` 的 import 来保证。
|
|
108
|
+
|
|
109
|
+
## 请求流
|
|
110
|
+
|
|
111
|
+
```mermaid
|
|
112
|
+
sequenceDiagram
|
|
113
|
+
participant B as Browser
|
|
114
|
+
participant S as Bun server
|
|
115
|
+
participant A as Svelte app
|
|
116
|
+
B->>S: GET /docs/guide
|
|
117
|
+
S->>S: resolveResource -> markdown
|
|
118
|
+
S-->>B: 200 web/dist/index.html + config JSON
|
|
119
|
+
B->>A: mount App
|
|
120
|
+
A->>S: GET /__api/page/docs/guide
|
|
121
|
+
S->>S: render Markdown
|
|
122
|
+
S-->>A: DocumentPayload
|
|
123
|
+
A-->>B: render DocumentPage
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 内部路由
|
|
127
|
+
|
|
128
|
+
| 路径 | 用途 |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `/__api/page/<path>` | 返回文档或目录的页面数据 |
|
|
131
|
+
| `/__app/*` | Vite 构建产物,避开用户的 `/assets` |
|
|
132
|
+
| `/__serve_md_live` | WebSocket 实时刷新 |
|
|
133
|
+
| `/__serve_md_mermaid.js` | Mermaid 浏览器运行时 |
|
|
134
|
+
|
|
135
|
+
用户路径保持原样:
|
|
136
|
+
|
|
137
|
+
- Markdown 和目录路由返回 Svelte 壳。
|
|
138
|
+
- 静态文件和静态 HTML 直接返回。
|
|
139
|
+
- Markdown 加 `?raw` 返回源码。
|
|
140
|
+
- 目录缺少结尾 `/` 时返回 301。
|
|
141
|
+
- 不存在的路径返回壳,HTTP 状态为 404,Svelte 渲染错误页。
|
|
142
|
+
- `-s` 回退到 `index.html` 时仍按用户静态 HTML 返回;回退到 `index.md` 时走 Svelte 壳。
|
|
143
|
+
|
|
144
|
+
## 页面数据契约
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
type DocumentPayload = {
|
|
148
|
+
kind: "document";
|
|
149
|
+
title: string;
|
|
150
|
+
html: string;
|
|
151
|
+
toc: TocItem[];
|
|
152
|
+
mermaid: MermaidSource[];
|
|
153
|
+
breadcrumbs: Breadcrumb[];
|
|
154
|
+
rawHref: string;
|
|
155
|
+
warning?: string;
|
|
156
|
+
lang?: string;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
type DirectoryPayload = {
|
|
160
|
+
kind: "directory";
|
|
161
|
+
heading: string;
|
|
162
|
+
parentHref?: string;
|
|
163
|
+
entries: DirEntry[];
|
|
164
|
+
breadcrumbs: Breadcrumb[];
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`html` 是 Markdown 内容,不是页面结构。Mermaid 在 `html` 中只留下 `<div data-mermaid-slot="mermaid-1">` 占位,源码通过 `mermaid` 数组传给 `MermaidDiagram.svelte`。
|
|
169
|
+
|
|
170
|
+
信任边界:
|
|
171
|
+
|
|
172
|
+
- Markdown 正文 HTML 由服务端生成,客户端通过 `{@html}` 插入。
|
|
173
|
+
- Mermaid 返回的 SVG 由 Mermaid 运行时生成,客户端通过 `{@html}` 插入。
|
|
174
|
+
- 除这两处和 config JSON 外,UI 中不再出现字符串 HTML 或 `innerHTML`。
|
|
175
|
+
- 主题启动是外置的阻塞脚本 `web/public/theme-boot.js`,不是内联 JS。
|
|
176
|
+
- 静态 HTML 的 live reload 只注入 `<script type="module" src="/__app/live-static.js">`,脚本本身由 Vite 预构建,不再是内联字符串。
|
|
177
|
+
|
|
178
|
+
## 如何添加功能
|
|
179
|
+
|
|
180
|
+
### 新增页面字段
|
|
181
|
+
|
|
182
|
+
1. 在 `shared/contracts.ts` 增加字段。
|
|
183
|
+
2. 在 `server/page-api.ts` 或对应 service 填充。
|
|
184
|
+
3. 在 `web/lib/api.ts` 的类型中自动获得该字段。
|
|
185
|
+
4. 在对应 Svelte 组件渲染。
|
|
186
|
+
|
|
187
|
+
### 新增交互控件
|
|
188
|
+
|
|
189
|
+
只修改 `web/src/features/<feature>/`。需要服务端数据时,再增加 shared 契约和 API 字段。
|
|
190
|
+
|
|
191
|
+
### 新增 Markdown 语法
|
|
192
|
+
|
|
193
|
+
只修改 `server/markdown/`。如果产生交互占位,再在 shared 中增加数据,并在 `web/features/` 中增加对应组件。
|
|
194
|
+
|
|
195
|
+
### 新增 CLI 参数
|
|
196
|
+
|
|
197
|
+
1. 在 `cli/command.ts` 声明。
|
|
198
|
+
2. 在 `cli/options.ts` 映射到 `ServeMarkdownOptions`。
|
|
199
|
+
3. 在 `server/config.ts` 校验并加入 `ResolvedConfig`。
|
|
200
|
+
4. 在消费该配置的模块使用。
|
|
201
|
+
|
|
202
|
+
### 新增文件类型或路由
|
|
203
|
+
|
|
204
|
+
修改 `server/resolve/` 和 `server/routes/`,不要在 UI 中判断文件类型。
|
|
205
|
+
|
|
206
|
+
## 构建与测试
|
|
207
|
+
|
|
208
|
+
```plaintext
|
|
209
|
+
bun run build # Vite 构建 web/dist
|
|
210
|
+
bun run dev:web # Vite build --watch
|
|
211
|
+
bun run start # 启动服务器
|
|
212
|
+
bun run test:server # Bun test:server、shared 与 API/parity
|
|
213
|
+
bun run test:web # Vitest:Svelte 组件和浏览器端逻辑
|
|
214
|
+
bun run test:e2e # Playwright:真实浏览器 E2E
|
|
215
|
+
bun run typecheck # tsc + svelte-check
|
|
216
|
+
bun pm pack # 验证发布产物包含 web/dist
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
测试分工:
|
|
220
|
+
|
|
221
|
+
- `bun test` 负责 Bun 运行时:API、resolve、Markdown、CLI 纯函数。
|
|
222
|
+
- Vitest 负责浏览器运行时:Svelte 组件、viewport 数学、API 客户端。
|
|
223
|
+
- Playwright 做端到端测试:真实导航、Mermaid 查看器、主题、静态资源和 404。
|
|
224
|
+
|
|
225
|
+
## 当前状态
|
|
226
|
+
|
|
227
|
+
迁移已完成:
|
|
228
|
+
|
|
229
|
+
- 目录只保留 `cli / server / web / shared`,旧 React SSR、旧 Mermaid 客户端、旧 CLI 和 React 依赖已删除。
|
|
230
|
+
- `config / resolve / markdown / directory / live` 全部位于 `src/server/`,新服务器只导入 `server/` 和 `shared/`。
|
|
231
|
+
- 新服务器提供 `/__api/page/*`、Svelte 壳、`/__app/*` 静态资源和 Mermaid 运行时。
|
|
232
|
+
- 新 Svelte 应用支持文档页、目录页、错误页、主题切换、加载态、live reload、Mermaid 图表和查看器。
|
|
233
|
+
- 命令名 `srvmd` 指向 `src/cli/index.ts`。
|
|
234
|
+
- parity 已覆盖 clean URL、`?raw`、目录重定向、静态文件、ETag/304/Range、CORS、隐藏文件、错误状态、`-s`、`--no-live`、`--no-mermaid`、`--no-clean-urls`、`--no-directory-listing`、`--theme`、`--html`、`--highlight`、`--cors`。
|
|
235
|
+
- MermaidDiagram / MermaidViewer 已覆盖渲染、错误、点击/键盘打开、缩放、Fit、关闭和主题重绘。
|
|
236
|
+
- Playwright E2E 的 5 个 smoke 用例已在 WSL2 + Docker 中全部通过;宿主机缺少 Chromium 系统库时,可继续用 Docker 运行。
|
|
237
|
+
- 静态 HTML 的 live reload 使用预构建的 `/__app/live-static.js`;主题启动使用预构建的 `/__app/theme-boot.js`,新页面不包含内联客户端 JS。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 luohuidong
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# srvmd
|
|
2
|
+
|
|
3
|
+
English | [中文](./README_CN.md)
|
|
4
|
+
|
|
5
|
+
A static file server built on Bun: host a directory like `serve`, but `.md` files are rendered as HTML on demand, and the browser reloads automatically when files change.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bunx srvmd ./docs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Static file serving with automatic `Content-Type`, `ETag`, `Last-Modified`, `304 Not Modified`, and Range request support
|
|
14
|
+
- Markdown rendering via built-in `Bun.markdown` (CommonMark + GFM tables, strikethrough, and task lists)
|
|
15
|
+
- YAML frontmatter parsed with built-in `Bun.YAML`, available as page metadata such as `title` and `lang`
|
|
16
|
+
- Automatic heading anchors and a sidebar table of contents (TOC), with rendered output cached by file mtime
|
|
17
|
+
- shiki syntax highlighting with built-in light/dark themes; unknown languages fall back to plain code blocks
|
|
18
|
+
- Mermaid diagrams: the runtime comes from the local mermaid dependency and is loaded on demand; click a diagram to open a fullscreen viewer with zoom and pan
|
|
19
|
+
- File watching + WebSocket live reload, preserving scroll position across reloads
|
|
20
|
+
- Directory listings, clean URLs, SPA single-page fallback, CORS, and dark mode toggle
|
|
21
|
+
- Pages are rendered by Svelte 5 in the browser using server-routed CSR; the React dependency has been removed
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Bun >= 1.3.8 (`Bun.markdown` is available starting from this version)
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Global install
|
|
31
|
+
bun add -g srvmd
|
|
32
|
+
|
|
33
|
+
# Or run directly
|
|
34
|
+
bunx srvmd ./docs
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
srvmd [directory] [options]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Common examples:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
srvmd ./docs # Host ./docs on port 3000
|
|
47
|
+
srvmd . -l 8080 --open # Use a specific port and open the browser
|
|
48
|
+
srvmd ./public -s --cors # SPA mode with CORS enabled
|
|
49
|
+
srvmd ./docs --theme dark # Dark theme by default
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### CLI options
|
|
53
|
+
|
|
54
|
+
| Option | Description |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `-l, --listen <port\|host:port>` | Port or address to listen on, default `3000` |
|
|
57
|
+
| `-o, --open` | Open the default browser after starting |
|
|
58
|
+
| `-s, --single` | Fall back to `index.html` or `index.md` for unmatched paths (SPA mode) |
|
|
59
|
+
| `-c, --cors` | Send permissive CORS headers |
|
|
60
|
+
| `--theme <auto\|light\|dark>` | Default color theme, default `auto` |
|
|
61
|
+
| `--html <filter\|allow\|off>` | How to handle raw HTML in Markdown, default `filter` |
|
|
62
|
+
| `--math` | Enable `$inline$` and `$$display$$` math |
|
|
63
|
+
| `--ignore <name>` | Extra directory names to ignore while watching; repeatable |
|
|
64
|
+
| `--no-live` | Disable WebSocket live reload |
|
|
65
|
+
| `--no-highlight` | Disable syntax highlighting |
|
|
66
|
+
| `--no-mermaid` | Disable Mermaid rendering and keep code blocks |
|
|
67
|
+
| `--no-clean-urls` | Disable resolving `/page` to `page.md` / `page.html` |
|
|
68
|
+
| `--no-directory-listing` | Disable directory listings |
|
|
69
|
+
| `-v, --version` | Print the version |
|
|
70
|
+
| `-h, --help` | Print help |
|
|
71
|
+
|
|
72
|
+
### URL conventions
|
|
73
|
+
|
|
74
|
+
- `/docs/page.md` and `/docs/page` both render `page.md`; relative links and images keep working
|
|
75
|
+
- Append `?raw` to a Markdown request to view the original file
|
|
76
|
+
- A directory request without a trailing `/` receives a `301` redirect
|
|
77
|
+
- Hidden files and directories starting with `.` are not served or listed
|
|
78
|
+
|
|
79
|
+
## Architecture
|
|
80
|
+
|
|
81
|
+
```plaintext
|
|
82
|
+
src/
|
|
83
|
+
cli/ # Commander argument parsing and process lifecycle
|
|
84
|
+
server/ # Bun runtime: HTTP, resolve, Markdown, directories, live
|
|
85
|
+
config.ts
|
|
86
|
+
constants.ts
|
|
87
|
+
context.ts
|
|
88
|
+
create-server.ts
|
|
89
|
+
types.ts
|
|
90
|
+
path.ts
|
|
91
|
+
file-kinds.ts
|
|
92
|
+
http/
|
|
93
|
+
resolve/
|
|
94
|
+
markdown/
|
|
95
|
+
services/
|
|
96
|
+
routes/
|
|
97
|
+
assets/
|
|
98
|
+
shared/ # Cross-runtime API contracts, types, protocol constants
|
|
99
|
+
web/ # Svelte 5 browser application
|
|
100
|
+
src/features/ # document, directory, mermaid, theme, live
|
|
101
|
+
src/components/ # Cross-feature presentational components
|
|
102
|
+
src/lib/ # API client, config, formatting
|
|
103
|
+
src/live-static.ts # Prebuilt live reload for static HTML
|
|
104
|
+
src/styles/ # app, markdown, mermaid CSS
|
|
105
|
+
public/theme-boot.js # Theme bootstrap before first paint
|
|
106
|
+
test/ # Integration fixtures and Playwright E2E
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Dependency direction:
|
|
110
|
+
|
|
111
|
+
```plaintext
|
|
112
|
+
cli -> server -> shared
|
|
113
|
+
web -> shared
|
|
114
|
+
server -/-> web (only serves web/dist as static files)
|
|
115
|
+
web -/-> server
|
|
116
|
+
shared -> nothing
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`src/server/` may only import `src/server/` and `src/shared/`; `web/` may only import `src/shared/` and itself. `test/architecture.test.ts` scans imports in `.ts` and `.svelte` files to enforce these boundaries.
|
|
120
|
+
|
|
121
|
+
The CLI entry point is `src/cli/index.ts`, exposed as the `srvmd` command. See [ARCHITECTURE.md](./ARCHITECTURE.md) (Chinese) for directory responsibilities, the API contract, and where to add features.
|
|
122
|
+
|
|
123
|
+
## How it works
|
|
124
|
+
|
|
125
|
+
```mermaid
|
|
126
|
+
flowchart LR
|
|
127
|
+
Browser[Browser] -->|GET /docs/guide| Server[Bun server]
|
|
128
|
+
Server -->|Svelte shell| Browser
|
|
129
|
+
Browser -->|GET /__api/page/docs/guide| Server
|
|
130
|
+
Server -->|DocumentPayload| Browser
|
|
131
|
+
Watcher[fs.watch] -->|WebSocket reload| Browser
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
After a request reaches `Bun.serve`:
|
|
135
|
+
|
|
136
|
+
1. `resolveRequest` performs path resolution and security checks (path traversal, hidden files, and symlinks that escape the root)
|
|
137
|
+
2. Markdown and directory routes return the same Svelte shell; static files, static HTML, and `?raw` responses are still served directly
|
|
138
|
+
3. Svelte reads `location.pathname` and requests `/__api/page/<path>` to get page data such as `title`, `html`, `toc`, `mermaid`, and `breadcrumbs`
|
|
139
|
+
4. Markdown content, TOC, and shiki highlighting are generated on the server; page structure, theme, directory pages, error pages, and the Mermaid viewer are rendered by Svelte
|
|
140
|
+
5. `fs.watch` watches directories individually (skipping `node_modules`, `.git`, and similar), debounces changes for 80ms, and notifies the browser over WebSocket
|
|
141
|
+
|
|
142
|
+
Every navigation is a full page request; there is no client-side router. The Mermaid runtime is loaded only when a document contains diagrams. Static HTML live reload is provided by the prebuilt `/__app/live-static.js` instead of an inline script.
|
|
143
|
+
|
|
144
|
+
## Differences from vercel `serve`
|
|
145
|
+
|
|
146
|
+
Already covered:
|
|
147
|
+
|
|
148
|
+
- Static files and directory listings
|
|
149
|
+
- Clean URLs (extension omitted)
|
|
150
|
+
- SPA fallback (`-s`)
|
|
151
|
+
- CORS (`-c`)
|
|
152
|
+
- Conditional requests and Range requests
|
|
153
|
+
|
|
154
|
+
Not implemented yet:
|
|
155
|
+
|
|
156
|
+
- `rewrites` / `redirects` / `headers` configuration
|
|
157
|
+
- gzip / brotli compression
|
|
158
|
+
- Custom `404.html` templates
|
|
159
|
+
- HTTPS certificate options
|
|
160
|
+
|
|
161
|
+
## Markdown support
|
|
162
|
+
|
|
163
|
+
- GFM tables, strikethrough, task lists, and autolinks are enabled by default
|
|
164
|
+
- The GFM tag filter removes dangerous tags such as `<script>`, `<style>`, and `<iframe>` by default; normal HTML such as `<details>` is preserved
|
|
165
|
+
- `--html off` disables raw HTML entirely; `--html allow` performs no filtering
|
|
166
|
+
- `mermaid` code fences render as diagrams: click a diagram to open a fullscreen viewer with wheel/pinch zoom, drag-to-pan, `Fit` reset, and keyboard controls. The Mermaid runtime comes from the local dependency and does not require internet access; use `--no-mermaid` to keep source code blocks
|
|
167
|
+
- Math is off by default (so `$HOME` and `$5` in terminal docs are not misdetected); enable it with `--math`
|
|
168
|
+
- Frontmatter uses YAML; non-object or invalid YAML shows a warning at the top of the page instead of crashing the server
|
|
169
|
+
|
|
170
|
+
## Security
|
|
171
|
+
|
|
172
|
+
The server listens on `0.0.0.0` by default and is reachable on the local network. It serves files from the target directory as-is. Do not use it to host a directory containing secrets. Raw HTML in Markdown is only GFM-filtered by default; if you expose the server more broadly, prefer `--html off`.
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
bun install
|
|
178
|
+
bun run build # Build the Svelte web app (web/dist)
|
|
179
|
+
bun run test # Bun + Vitest
|
|
180
|
+
bun run test:server # Bun: server, shared, API/parity
|
|
181
|
+
bun run test:web # Vitest: Svelte components and browser logic
|
|
182
|
+
bun run test:e2e # Playwright: real-browser E2E
|
|
183
|
+
bun run typecheck # tsc + svelte-check
|
|
184
|
+
bun run check # Biome + Prettier
|
|
185
|
+
bun run start example/docs # Start the server (requires a build first)
|
|
186
|
+
bun run dev # Start the server in watch mode
|
|
187
|
+
bun run dev:web # Vite build --watch; run alongside dev for UI work
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Playwright requires browsers to be installed first: `bunx playwright install --with-deps chromium`. On WSL2, headless mode does not need an X server; headed mode requires WSLg. If system dependencies are unavailable, run E2E with Docker:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
docker run --rm --ipc=host \
|
|
194
|
+
-v "$PWD":/work -w /work -v /work/node_modules \
|
|
195
|
+
oven/bun:1.4.3-debian \
|
|
196
|
+
bash -lc 'bun install --frozen-lockfile && bunx playwright install --with-deps chromium && bun run test:e2e'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Keeping the project on the Linux filesystem (for example `/home/...`) provides better file-watching performance.
|
|
200
|
+
|
|
201
|
+
Tests are colocated with code: Markdown rendering lives in `src/server/markdown/render.test.ts`, path security in `src/server/resolve/path.test.ts`, API and parity tests in `src/server/routes/`, and Svelte component tests in `web/src/**/*.test.ts`. `web/` uses Vitest; `bunfig.toml` excludes `web/**` and `test/e2e/**` from `bun test`. Use `bun run test` for the full unit suite and `bun run test:e2e` for E2E.
|
|
202
|
+
|
|
203
|
+
The `example/docs` directory is a ready-to-run sample. Static HTML live reload uses `/__app/live-static.js`, built from `web/src/live-static.ts`; the Svelte app uses `web/src/features/live/live.ts`.
|
|
204
|
+
|
|
205
|
+
## Roadmap
|
|
206
|
+
|
|
207
|
+
- gzip / brotli compression (`Bun.gzipSync`)
|
|
208
|
+
- `serve.json`-style `rewrites` / `redirects` / `headers`
|
|
209
|
+
- More Markdown extensions
|
|
210
|
+
- Built-in full-text search
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# srvmd
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 中文
|
|
4
|
+
|
|
5
|
+
基于 Bun 的静态文件服务器:像 `serve` 一样托管目录,但浏览 `.md` 文件时会实时渲染成 HTML,文件保存后浏览器自动刷新。
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bunx srvmd ./docs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 特性
|
|
12
|
+
|
|
13
|
+
- 静态文件服务:自动 `Content-Type`、`ETag`、`Last-Modified`、`304 Not Modified`、Range 请求
|
|
14
|
+
- Markdown 渲染:基于内置 `Bun.markdown`(CommonMark + GFM 表格 / 删除线 / 任务列表)
|
|
15
|
+
- YAML frontmatter:通过内置 `Bun.YAML` 解析,可用于 `title`、`lang` 等页面元数据
|
|
16
|
+
- 自动标题锚点与侧边目录(TOC),渲染结果按文件 mtime 缓存
|
|
17
|
+
- shiki 代码高亮,内置 light / dark 双主题,未知语言自动降级为普通代码块
|
|
18
|
+
- Mermaid 图表:由本地 mermaid 依赖提供运行时并按需加载,点击图表可打开支持缩放、拖拽的全屏查看器
|
|
19
|
+
- 文件监听 + WebSocket 实时刷新,刷新后保留滚动位置
|
|
20
|
+
- 目录列表、clean URLs、SPA 单页模式、CORS、深色模式切换
|
|
21
|
+
- 页面由 Svelte 5 在浏览器渲染,采用服务端路由 + 客户端渲染(server-routed CSR),React 依赖已移除
|
|
22
|
+
|
|
23
|
+
## 环境要求
|
|
24
|
+
|
|
25
|
+
- Bun >= 1.3.8(`Bun.markdown` 从该版本开始提供)
|
|
26
|
+
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 全局安装
|
|
31
|
+
bun add -g srvmd
|
|
32
|
+
|
|
33
|
+
# 或者直接运行
|
|
34
|
+
bunx srvmd ./docs
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 使用
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
srvmd [directory] [options]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
常用示例:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
srvmd ./docs # 在 3000 端口托管 ./docs
|
|
47
|
+
srvmd . -l 8080 --open # 指定端口并打开浏览器
|
|
48
|
+
srvmd ./public -s --cors # SPA 模式并开启 CORS
|
|
49
|
+
srvmd ./docs --theme dark # 默认深色主题
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### CLI 参数
|
|
53
|
+
|
|
54
|
+
| 参数 | 说明 |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `-l, --listen <port\|host:port>` | 监听端口或地址,默认 `3000` |
|
|
57
|
+
| `-o, --open` | 启动后打开默认浏览器 |
|
|
58
|
+
| `-s, --single` | 未匹配的路径回退到 `index.html` 或 `index.md`(SPA 模式) |
|
|
59
|
+
| `-c, --cors` | 返回宽松的 CORS 响应头 |
|
|
60
|
+
| `--theme <auto\|light\|dark>` | 默认配色主题,默认 `auto` |
|
|
61
|
+
| `--html <filter\|allow\|off>` | Markdown 中原始 HTML 的处理方式,默认 `filter` |
|
|
62
|
+
| `--math` | 启用 `$inline$` 与 `$$display$$` 数学公式 |
|
|
63
|
+
| `--ignore <name>` | 监听时额外忽略的目录名,可重复 |
|
|
64
|
+
| `--no-live` | 关闭 WebSocket 实时刷新 |
|
|
65
|
+
| `--no-highlight` | 关闭代码高亮 |
|
|
66
|
+
| `--no-mermaid` | 关闭 Mermaid 图表渲染,保留代码块 |
|
|
67
|
+
| `--no-clean-urls` | 关闭 `/page` 到 `page.md` / `page.html` 的解析 |
|
|
68
|
+
| `--no-directory-listing` | 关闭目录列表 |
|
|
69
|
+
| `-v, --version` | 输出版本号 |
|
|
70
|
+
| `-h, --help` | 输出帮助 |
|
|
71
|
+
|
|
72
|
+
### URL 约定
|
|
73
|
+
|
|
74
|
+
- `/docs/page.md` 与 `/docs/page` 都会渲染 `page.md`,相对链接与图片照常工作
|
|
75
|
+
- 在 Markdown 请求后追加 `?raw` 可以查看原始文件
|
|
76
|
+
- 访问目录且缺少结尾 `/` 时会收到 `301` 跳转
|
|
77
|
+
- 以 `.` 开头的隐藏文件不会被提供或列入目录列表
|
|
78
|
+
|
|
79
|
+
## 架构
|
|
80
|
+
|
|
81
|
+
```plaintext
|
|
82
|
+
src/
|
|
83
|
+
cli/ # Commander 参数解析与进程生命周期
|
|
84
|
+
server/ # Bun 运行时:HTTP、resolve、Markdown、目录、live
|
|
85
|
+
config.ts
|
|
86
|
+
constants.ts
|
|
87
|
+
context.ts
|
|
88
|
+
create-server.ts
|
|
89
|
+
types.ts
|
|
90
|
+
path.ts
|
|
91
|
+
file-kinds.ts
|
|
92
|
+
http/
|
|
93
|
+
resolve/
|
|
94
|
+
markdown/
|
|
95
|
+
services/
|
|
96
|
+
routes/
|
|
97
|
+
assets/
|
|
98
|
+
shared/ # 跨端 API 契约、类型、协议常量
|
|
99
|
+
web/ # Svelte 5 浏览器应用
|
|
100
|
+
src/features/ # document、directory、mermaid、theme、live
|
|
101
|
+
src/components/ # 跨 feature 的展示组件
|
|
102
|
+
src/lib/ # API client、config、formatting
|
|
103
|
+
src/live-static.ts # 静态 HTML 的预构建 live reload
|
|
104
|
+
src/styles/ # app、markdown、mermaid CSS
|
|
105
|
+
public/theme-boot.js # 首屏前执行的主题初始化脚本
|
|
106
|
+
test/ # 集成 fixture 与 Playwright E2E
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
依赖方向:
|
|
110
|
+
|
|
111
|
+
```plaintext
|
|
112
|
+
cli -> server -> shared
|
|
113
|
+
web -> shared
|
|
114
|
+
server -/-> web (only serves web/dist as static files)
|
|
115
|
+
web -/-> server
|
|
116
|
+
shared -> nothing
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`src/server/` 只能导入 `src/server/` 和 `src/shared/`;`web/` 只能导入 `src/shared/` 和自身。`test/architecture.test.ts` 会扫描 `.ts` 和 `.svelte` 的 import 来校验这些边界。
|
|
120
|
+
|
|
121
|
+
新代码统一由 `src/cli/index.ts` 启动,命令名固定为 `srvmd`。目录职责、API 契约和功能落点见 [ARCHITECTURE.md](./ARCHITECTURE.md)。
|
|
122
|
+
|
|
123
|
+
## 工作原理
|
|
124
|
+
|
|
125
|
+
```mermaid
|
|
126
|
+
flowchart LR
|
|
127
|
+
Browser[浏览器] -->|GET /docs/guide| Server[Bun server]
|
|
128
|
+
Server -->|Svelte 壳| Browser
|
|
129
|
+
Browser -->|GET /__api/page/docs/guide| Server
|
|
130
|
+
Server -->|DocumentPayload| Browser
|
|
131
|
+
Watcher[fs.watch] -->|WebSocket 广播| Browser
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
请求进入 `Bun.serve` 后:
|
|
135
|
+
|
|
136
|
+
1. 用 `resolveRequest` 做路径解析与安全校验(防目录穿越、防隐藏文件、解析符号链接后仍在根目录内)
|
|
137
|
+
2. Markdown 和目录路由返回同一个 Svelte 壳;静态文件、静态 HTML、`?raw` 仍由服务端直接返回
|
|
138
|
+
3. Svelte 根据 `location.pathname` 请求 `/__api/page/<path>`,拿到 `title`、`html`、`toc`、`mermaid`、`breadcrumbs` 等页面数据
|
|
139
|
+
4. Markdown 正文、TOC、shiki 高亮由服务端生成;页面结构、主题、目录页、错误页、Mermaid 查看器由 Svelte 渲染
|
|
140
|
+
5. `fs.watch` 逐个监听目录(跳过 `node_modules`、`.git` 等),变更经 80ms 防抖后通过 WebSocket 通知浏览器刷新
|
|
141
|
+
|
|
142
|
+
每次导航都是一次完整页面请求,不使用前端 router。Mermaid 运行时只在文档包含图表时按需加载;静态 HTML 的 live reload 现在由预构建的 `/__app/live-static.js` 提供,不再注入内联脚本。
|
|
143
|
+
|
|
144
|
+
## 与 vercel `serve` 的差异
|
|
145
|
+
|
|
146
|
+
已经覆盖的能力:
|
|
147
|
+
|
|
148
|
+
- 静态文件与目录列表
|
|
149
|
+
- clean URLs(扩展名省略)
|
|
150
|
+
- SPA 单页回退(`-s`)
|
|
151
|
+
- CORS(`-c`)
|
|
152
|
+
- 文件条件请求与 Range 请求
|
|
153
|
+
|
|
154
|
+
暂未实现的能力:
|
|
155
|
+
|
|
156
|
+
- `rewrites` / `redirects` / `headers` 配置文件
|
|
157
|
+
- gzip / brotli 压缩
|
|
158
|
+
- 自定义 404 模板(`404.html`)
|
|
159
|
+
- HTTPS 证书参数
|
|
160
|
+
|
|
161
|
+
## Markdown 支持情况
|
|
162
|
+
|
|
163
|
+
- 默认开启 GFM 表格、删除线、任务列表、自动链接
|
|
164
|
+
- 默认使用 GFM tag filter 过滤 `<script>`、`<style>`、`<iframe>` 等危险标签;`<details>` 等正常 HTML 会保留
|
|
165
|
+
- `--html off` 会完全禁用原始 HTML,`--html allow` 则不做任何过滤
|
|
166
|
+
- `mermaid` 代码块会渲染为图表:点击图表打开全屏查看器,支持滚轮 / 双指缩放、拖拽平移、`Fit` 重置与键盘操作。Mermaid 运行时来自本地依赖,不依赖外网;不需要图表时可加 `--no-mermaid` 保留源码代码块
|
|
167
|
+
- 数学公式默认关闭(终端文档里的 `$HOME`、`$5` 不会被误判),需要时加 `--math`
|
|
168
|
+
- frontmatter 使用 YAML,非对象或非法 YAML 会在页面顶部显示警告而不会导致服务崩溃
|
|
169
|
+
|
|
170
|
+
## 安全提示
|
|
171
|
+
|
|
172
|
+
默认监听 `0.0.0.0`,局域网内可访问,并且会按原样提供所服务目录中的文件。请勿用它托管包含密钥的目录。Markdown 中的原始 HTML 默认只做 GFM 过滤,如果要对外暴露,建议使用 `--html off`。
|
|
173
|
+
|
|
174
|
+
## 开发
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
bun install
|
|
178
|
+
bun run build # 构建 Svelte Web 应用(web/dist)
|
|
179
|
+
bun run test # Bun + Vitest
|
|
180
|
+
bun run test:server # Bun:server、shared 与 API/parity
|
|
181
|
+
bun run test:web # Vitest:Svelte 组件与浏览器逻辑
|
|
182
|
+
bun run test:e2e # Playwright:真实浏览器 E2E
|
|
183
|
+
bun run typecheck # tsc + svelte-check
|
|
184
|
+
bun run check # Biome + Prettier
|
|
185
|
+
bun run start example/docs # 启动服务(需要先 build)
|
|
186
|
+
bun run dev # 以 watch 模式启动服务
|
|
187
|
+
bun run dev:web # Vite build --watch,开发 UI 时与 dev 同时运行
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Playwright 首次运行前需要安装浏览器:`bunx playwright install --with-deps chromium`。WSL2 下 headless 模式不需要 X server,可以直接运行;headed 模式需要 WSLg。没有系统依赖时也可以用 Docker 运行:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
docker run --rm --ipc=host \
|
|
194
|
+
-v "$PWD":/work -w /work -v /work/node_modules \
|
|
195
|
+
oven/bun:1.4.3-debian \
|
|
196
|
+
bash -lc 'bun install --frozen-lockfile && bunx playwright install --with-deps chromium && bun run test:e2e'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
项目放在 Linux 文件系统(如 `/home/...`)可以获得更好的文件监听性能。
|
|
200
|
+
|
|
201
|
+
测试遵循 colocate 约定:Markdown 渲染在 `src/server/markdown/render.test.ts`,路径安全在 `src/server/resolve/path.test.ts`,API 和 parity 在 `src/server/routes/`,Svelte 组件在 `web/src/**/*.test.ts`。`web/` 使用 Vitest;`bunfig.toml` 已将 `web/**` 和 `test/e2e/**` 从 `bun test` 中排除,完整测试请用 `bun run test`,E2E 用 `bun run test:e2e`。
|
|
202
|
+
|
|
203
|
+
仓库中的 `example/docs` 是一个可直接运行的示例目录。纯静态 HTML 的 live reload 使用 `web/src/live-static.ts` 构建出的 `/__app/live-static.js`;新 Svelte 页面使用 `web/src/features/live/live.ts`。
|
|
204
|
+
|
|
205
|
+
## Roadmap
|
|
206
|
+
|
|
207
|
+
- gzip / brotli 压缩(`Bun.gzipSync`)
|
|
208
|
+
- `serve.json` 风格的 `rewrites` / `redirects` / `headers`
|
|
209
|
+
- 更多 Markdown 扩展
|
|
210
|
+
- 内置全文搜索
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT
|