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
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:
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user