WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
// 校验文档之间的相对链接。拆分章节时最容易留下指向已移动内容的死链,
// 而 vitepress build 不会因为死链失败。
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
import { join, relative, dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
function walk(dir, out = []) {
for (const name of readdirSync(dir)) {
if (name === 'node_modules' || name.startsWith('.')) continue
const path = join(dir, name)
if (statSync(path).isDirectory()) walk(path, out)
else if (name.endsWith('.md')) out.push(path)
}
return out
}
const files = walk(root)
const failures = []
let checked = 0
for (const file of files) {
const lines = readFileSync(file, 'utf-8').split('\n')
lines.forEach((line, index) => {
for (const match of line.matchAll(/\[[^\]]*\]\(([^)\s]+)\)/g)) {
const target = match[1]
if (/^(https?:|#|mailto:)/.test(target)) continue
const [path] = target.split('#')
if (!path || !path.endsWith('.md')) continue
checked++
if (!existsSync(resolve(dirname(file), path))) {
failures.push({ file: relative(root, file), line: index + 1, target })
}
}
})
}
console.log(`检查 ${files.length} 篇文档的 ${checked} 个内部链接,失效 ${failures.length} 个`)
for (const failure of failures) {
console.log(` ${failure.file}:${failure.line} -> ${failure.target}`)
}
process.exit(failures.length === 0 ? 0 : 1)
+77
View File
@@ -0,0 +1,77 @@
// 用 mermaid 自带的解析器校验文档里所有 ```mermaid 代码块。
// 站点构建不会检查图表语法——语法错的图只在浏览器里报错,
// 所以这里在 CI/本地提前拦住。
import { readFileSync, readdirSync, statSync } from 'node:fs'
import { join, relative, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { JSDOM } from 'jsdom'
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
// mermaid 是浏览器库,parse() 需要 DOM 才能初始化。
const dom = new JSDOM('<!DOCTYPE html><body></body>', { pretendToBeVisual: true })
globalThis.window = dom.window
globalThis.document = dom.window.document
// Node 22 的 globalThis.navigator 只有 getter,只能用 defineProperty 覆盖。
Object.defineProperty(globalThis, 'navigator', {
value: dom.window.navigator,
configurable: true,
})
const { default: mermaid } = await import('mermaid')
mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' })
function walk(dir, out = []) {
for (const name of readdirSync(dir)) {
if (name === 'node_modules' || name.startsWith('.')) continue
const path = join(dir, name)
if (statSync(path).isDirectory()) walk(path, out)
else if (name.endsWith('.md')) out.push(path)
}
return out
}
/** 取出每个 mermaid 块及其起始行号,行号用于定位报错。 */
function blocksOf(text) {
const lines = text.split('\n')
const blocks = []
let start = -1
let buffer = []
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
if (start === -1 && /^\s*```mermaid\s*$/.test(line)) {
start = i + 1
buffer = []
} else if (start !== -1 && /^\s*```\s*$/.test(line)) {
blocks.push({ line: start + 1, code: buffer.join('\n') })
start = -1
} else if (start !== -1) {
buffer.push(line)
}
}
return blocks
}
let total = 0
const failures = []
for (const file of walk(root)) {
for (const block of blocksOf(readFileSync(file, 'utf-8'))) {
total++
try {
await mermaid.parse(block.code)
} catch (error) {
failures.push({
file: relative(root, file),
line: block.line,
message: String(error?.message ?? error).split('\n').slice(0, 6).join('\n'),
})
}
}
}
console.log(`检查 ${total} 个 mermaid 图,失败 ${failures.length} 个`)
for (const failure of failures) {
console.log(`\n--- ${failure.file}:${failure.line}\n${failure.message}`)
}
process.exit(failures.length === 0 ? 0 : 1)
+49
View File
@@ -0,0 +1,49 @@
import { chromium } from 'playwright-core'
const b = await chromium.launch({ executablePath: process.env.HOME + '/Library/Caches/ms-playwright/chromium-1161/chrome-mac/Chromium.app/Contents/MacOS/Chromium' })
const q = await b.newPage()
const geom = (page) => page.evaluate(() => {
const g = (s) => { const e = document.querySelector(s); if (!e) return null; const r = e.getBoundingClientRect(); return [Math.round(r.left), Math.round(r.right)] }
return { logo: g('.VPNavBarTitle'), search: g('.VPNavBarSearch button, .VPNavBarSearch .DocSearch-Button'), menu: g('.VPNavBarMenu'), sidebarItem: g('.VPSidebarItem .link .text') }
})
const scanLine = async (page, label) => {
const buf = await page.screenshot({ clip: { x: 0, y: 58, width: 1680, height: 20 } })
const url = 'data:image/png;base64,' + buf.toString('base64')
const hits = await q.evaluate(async (u) => {
const img = new Image(); img.src = u; await img.decode()
const c = document.createElement('canvas'); c.width = img.width; c.height = img.height
const ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0)
const res = []
for (const x of [60, 200, 400, 800, 1200, 1560, 1660]) {
for (let y = 0; y < img.height; y++) {
const d = ctx.getImageData(x, y, 1, 1).data
if (d[0] < 248) res.push(`x${x} y${y + 58} ${d[0]},${d[1]},${d[2]}`)
}
}
return res
}, url)
console.log(' line@' + label, hits.length ? hits.join(' | ') : 'none')
}
for (const w of [1440, 1680, 1920]) {
for (const [name, path] of [['landing', '/'], ['doc ', '/03-features/07-agent']]) {
const p = await b.newPage({ viewport: { width: w, height: 900 }, deviceScaleFactor: 1 })
await p.goto('http://localhost:3011' + path, { waitUntil: 'networkidle' })
await p.waitForTimeout(800)
console.log(w, name, JSON.stringify(await geom(p)))
await p.evaluate(() => window.scrollTo({ top: 1200, behavior: 'instant' }))
await p.waitForTimeout(600)
if (w === 1680) await scanLine(p, name.trim())
await p.close()
}
}
const s = await b.newPage({ viewport: { width: 1680, height: 900 }, deviceScaleFactor: 2 })
for (const [name, path] of [['landing', '/'], ['doc', '/03-features/07-agent']]) {
await s.goto('http://localhost:3011' + path, { waitUntil: 'networkidle' })
await s.waitForTimeout(800)
await s.evaluate(() => window.scrollTo({ top: 900, behavior: 'instant' }))
await s.waitForTimeout(600)
await s.screenshot({ path: `.shots/nav-${name}.png`, clip: { x: 0, y: 0, width: 1680, height: 120 } })
}
await b.close()