Fix section 404 by rewriting README pages to index.html
Generate VitePress rewrites for subdirectory README.md and simplify Express page resolution. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import path from 'node:path'
|
|||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import { defineConfig } from 'vitepress'
|
import { defineConfig } from 'vitepress'
|
||||||
import { missingAssetsPlugin } from './missing-assets-plugin.mjs'
|
import { missingAssetsPlugin } from './missing-assets-plugin.mjs'
|
||||||
|
import { generateRewrites } from './rewrites.mts'
|
||||||
import { generateSidebar } from './sidebar.mts'
|
import { generateSidebar } from './sidebar.mts'
|
||||||
|
|
||||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||||
@@ -11,6 +12,7 @@ export default defineConfig({
|
|||||||
description: '传统文化典籍资料库',
|
description: '传统文化典籍资料库',
|
||||||
lang: 'zh-CN',
|
lang: 'zh-CN',
|
||||||
srcDir: '.',
|
srcDir: '.',
|
||||||
|
rewrites: generateRewrites(),
|
||||||
srcExclude: [
|
srcExclude: [
|
||||||
'**/node_modules/**',
|
'**/node_modules/**',
|
||||||
'**/.vitepress/**',
|
'**/.vitepress/**',
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||||
|
|
||||||
|
const EXCLUDE_DIRS = new Set([
|
||||||
|
'node_modules',
|
||||||
|
'.vitepress',
|
||||||
|
'server',
|
||||||
|
'scripts',
|
||||||
|
'assets',
|
||||||
|
'images',
|
||||||
|
'.git',
|
||||||
|
'金瓶梅',
|
||||||
|
'黄帝内经',
|
||||||
|
'健康学习到150岁 - 人体系统调优不完全指南',
|
||||||
|
'梅花',
|
||||||
|
])
|
||||||
|
|
||||||
|
export function generateRewrites(): Record<string, string> {
|
||||||
|
const rewrites: Record<string, string> = {}
|
||||||
|
|
||||||
|
function walk(dir: string, relativeDir: string) {
|
||||||
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
if (EXCLUDE_DIRS.has(entry.name)) continue
|
||||||
|
walk(path.join(dir, entry.name), path.join(relativeDir, entry.name))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/readme\.md$/i.test(entry.name)) continue
|
||||||
|
|
||||||
|
const from = path.join(relativeDir, entry.name).replace(/\\/g, '/')
|
||||||
|
const to = from.replace(/README\.md$/i, 'index.md')
|
||||||
|
rewrites[from] = to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
||||||
|
if (!entry.isDirectory() || EXCLUDE_DIRS.has(entry.name)) continue
|
||||||
|
walk(path.join(root, entry.name), entry.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rewrites
|
||||||
|
}
|
||||||
@@ -330,14 +330,26 @@ npm run build
|
|||||||
|
|
||||||
部分章节引用的图片若仓库中不存在,页面可正常访问,仅图片为空。
|
部分章节引用的图片若仓库中不存在,页面可正常访问,仅图片为空。
|
||||||
|
|
||||||
### Q4:端口被占用
|
### Q4:导航栏「五行 / 周易 / 中医」打开是 404
|
||||||
|
|
||||||
|
**必须重新构建**,`.vitepress/dist` 不会随 `git pull` 自动更新:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull
|
||||||
|
npm run build # 关键步骤,不可省略
|
||||||
|
npm run start # 或 systemctl restart dao-de-jing
|
||||||
|
```
|
||||||
|
|
||||||
|
构建完成后,目录下应存在例如 ` .vitepress/dist/周易/index.html`(不是只有 `README.html`)。
|
||||||
|
|
||||||
|
### Q5:端口被占用
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ss -tlnp | grep 12100
|
ss -tlnp | grep 12100
|
||||||
# 结束占用进程或修改 server/index.js 中的 PORT 常量
|
# 结束占用进程或修改 server/index.js 中的 PORT 常量
|
||||||
```
|
```
|
||||||
|
|
||||||
### Q5:内网穿透后外网无法访问
|
### Q6:内网穿透后外网无法访问
|
||||||
|
|
||||||
1. 确认 Ubuntu 上 `npm run start` 正常运行
|
1. 确认 Ubuntu 上 `npm run start` 正常运行
|
||||||
2. 确认 NPS 客户端在线,TCP 隧道状态正常
|
2. 确认 NPS 客户端在线,TCP 隧道状态正常
|
||||||
|
|||||||
+36
-20
@@ -34,6 +34,36 @@ function loadAuthConfig() {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeRoutePath(urlPath) {
|
||||||
|
let route = decodeURIComponent(urlPath)
|
||||||
|
if (!route.startsWith('/')) route = `/${route}`
|
||||||
|
if (route.length > 1 && route.endsWith('/')) route = route.slice(0, -1)
|
||||||
|
return route === '' ? '/' : route
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveHtmlPath(urlPath) {
|
||||||
|
const route = normalizeRoutePath(urlPath)
|
||||||
|
const rel = route === '/' ? 'index' : route.replace(/^\//, '')
|
||||||
|
|
||||||
|
const candidates = [
|
||||||
|
path.join(distPath, `${rel}.html`),
|
||||||
|
path.join(distPath, rel, 'index.html'),
|
||||||
|
path.join(distPath, rel, 'README.html'),
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
if (fs.existsSync(candidate)) {
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAssetRequest(urlPath) {
|
||||||
|
return /\.[a-zA-Z0-9]+$/.test(urlPath) && !urlPath.endsWith('.html')
|
||||||
|
}
|
||||||
|
|
||||||
const authConfig = loadAuthConfig()
|
const authConfig = loadAuthConfig()
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
@@ -117,31 +147,17 @@ if (!fs.existsSync(distPath)) {
|
|||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
express.static(distPath, {
|
express.static(distPath, {
|
||||||
index: ['index.html', 'README.html'],
|
index: false,
|
||||||
extensions: ['html'],
|
fallthrough: true,
|
||||||
redirect: true,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
function resolveHtmlPath(urlPath) {
|
app.use((req, res, next) => {
|
||||||
const rel = decodeURIComponent(urlPath).replace(/^\//, '').replace(/\/$/, '') || 'index'
|
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
||||||
const candidates = [
|
return next()
|
||||||
path.join(distPath, `${rel}.html`),
|
|
||||||
path.join(distPath, rel, 'index.html'),
|
|
||||||
path.join(distPath, rel, 'README.html'),
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const candidate of candidates) {
|
|
||||||
if (fs.existsSync(candidate)) {
|
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
if (isAssetRequest(req.path)) {
|
||||||
}
|
|
||||||
|
|
||||||
app.get('*', (req, res, next) => {
|
|
||||||
if (req.path.includes('.')) {
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user