301 lines
8.6 KiB
JavaScript
301 lines
8.6 KiB
JavaScript
import fs from 'node:fs'
|
||
import path from 'node:path'
|
||
import vm from 'node:vm'
|
||
import { fileURLToPath } from 'node:url'
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||
const rootDir = path.resolve(__dirname, '..')
|
||
const dataDir = path.join(rootDir, 'src', 'data')
|
||
const apiDir = path.join(rootDir, 'public', 'api')
|
||
const apiDocPath = path.join(rootDir, 'public', 'apidoc')
|
||
|
||
function readJson(relativePath) {
|
||
return JSON.parse(fs.readFileSync(path.join(rootDir, relativePath), 'utf8'))
|
||
}
|
||
|
||
function ensureDir(dir) {
|
||
fs.mkdirSync(dir, { recursive: true })
|
||
}
|
||
|
||
function writeJson(relativePath, data) {
|
||
const target = path.join(apiDir, relativePath)
|
||
ensureDir(path.dirname(target))
|
||
fs.writeFileSync(target, `${JSON.stringify(data, null, 2)}\n`, 'utf8')
|
||
}
|
||
|
||
function cleanApiDir() {
|
||
fs.rmSync(apiDir, { recursive: true, force: true })
|
||
fs.rmSync(apiDocPath, { force: true })
|
||
ensureDir(apiDir)
|
||
}
|
||
|
||
function writeTextFile(target, content) {
|
||
ensureDir(path.dirname(target))
|
||
fs.writeFileSync(target, content, 'utf8')
|
||
}
|
||
|
||
function copyApiDoc() {
|
||
const source = path.join(rootDir, 'docs', '知识库API接口说明.md')
|
||
const content = fs.readFileSync(source, 'utf8')
|
||
// /apidoc 是无扩展名文本接口;写入 UTF-8 BOM,避免部分静态服务器或浏览器按非 UTF-8 猜测导致中文乱码。
|
||
writeTextFile(apiDocPath, `\uFEFF${content}`)
|
||
}
|
||
|
||
function extractConstArrayFromTs(filePath, constName) {
|
||
const text = fs.readFileSync(filePath, 'utf8')
|
||
const marker = `export const ${constName}`
|
||
const markerIndex = text.indexOf(marker)
|
||
if (markerIndex === -1) {
|
||
throw new Error(`未找到 ${constName}`)
|
||
}
|
||
|
||
const assignmentIndex = text.indexOf('=', markerIndex)
|
||
if (assignmentIndex === -1) {
|
||
throw new Error(`未找到 ${constName} 赋值符号`)
|
||
}
|
||
|
||
const arrayStart = text.indexOf('[', assignmentIndex)
|
||
if (arrayStart === -1) {
|
||
throw new Error(`未找到 ${constName} 数组起点`)
|
||
}
|
||
|
||
let depth = 0
|
||
let quote = null
|
||
let escaped = false
|
||
let lineComment = false
|
||
let blockComment = false
|
||
|
||
for (let index = arrayStart; index < text.length; index += 1) {
|
||
const char = text[index]
|
||
const next = text[index + 1]
|
||
|
||
if (lineComment) {
|
||
if (char === '\n') lineComment = false
|
||
continue
|
||
}
|
||
|
||
if (blockComment) {
|
||
if (char === '*' && next === '/') {
|
||
blockComment = false
|
||
index += 1
|
||
}
|
||
continue
|
||
}
|
||
|
||
if (quote) {
|
||
if (escaped) {
|
||
escaped = false
|
||
} else if (char === '\\') {
|
||
escaped = true
|
||
} else if (char === quote) {
|
||
quote = null
|
||
}
|
||
continue
|
||
}
|
||
|
||
if (char === '/' && next === '/') {
|
||
lineComment = true
|
||
index += 1
|
||
continue
|
||
}
|
||
|
||
if (char === '/' && next === '*') {
|
||
blockComment = true
|
||
index += 1
|
||
continue
|
||
}
|
||
|
||
if (char === '\'' || char === '"' || char === '`') {
|
||
quote = char
|
||
continue
|
||
}
|
||
|
||
if (char === '[') depth += 1
|
||
if (char === ']') {
|
||
depth -= 1
|
||
if (depth === 0) {
|
||
const source = text.slice(arrayStart, index + 1)
|
||
return vm.runInNewContext(`(${source})`, {}, { timeout: 1000 })
|
||
}
|
||
}
|
||
}
|
||
|
||
throw new Error(`未能完整解析 ${constName}`)
|
||
}
|
||
|
||
const knowledgeAreas = readJson('src/data/knowledge-areas.json').knowledgeAreas
|
||
const processGroups = readJson('src/data/process-groups.json').processGroups
|
||
const processes = readJson('src/data/processes.json').processes
|
||
const artifacts = readJson('src/data/artifacts.json').artifacts
|
||
const tools = readJson('src/data/tools.json').tools
|
||
const performanceDomains = extractConstArrayFromTs(
|
||
path.join(dataDir, 'performance-domains.ts'),
|
||
'performanceDomains'
|
||
)
|
||
|
||
const knowledgeAreaMap = new Map(knowledgeAreas.map((item) => [item.id, item]))
|
||
const processGroupMap = new Map(processGroups.map((item) => [item.id, item]))
|
||
const artifactMap = new Map(artifacts.map((item) => [item.id, item]))
|
||
const toolMap = new Map(tools.map((item) => [item.id, item]))
|
||
|
||
function normalizeRef(ref) {
|
||
if (typeof ref === 'string') return { id: ref, details: [], note: undefined }
|
||
return {
|
||
id: ref.id,
|
||
details: Array.isArray(ref.detail) ? ref.detail.map((item) => ({ label: item.label })) : [],
|
||
note: ref.note,
|
||
}
|
||
}
|
||
|
||
function processSummary(process) {
|
||
const knowledgeArea = knowledgeAreaMap.get(process.knowledgeAreaId)
|
||
const processGroup = processGroupMap.get(process.processGroupId)
|
||
return {
|
||
id: process.id,
|
||
name: process.name,
|
||
knowledgeAreaId: process.knowledgeAreaId,
|
||
knowledgeAreaName: knowledgeArea?.name ?? '',
|
||
processGroupId: process.processGroupId,
|
||
processGroupName: processGroup?.name ?? '',
|
||
purpose: process.purpose ?? '',
|
||
}
|
||
}
|
||
|
||
function attachRefMeta(ref, entityMap) {
|
||
const normalized = normalizeRef(ref)
|
||
const entity = entityMap.get(normalized.id)
|
||
return {
|
||
id: normalized.id,
|
||
name: entity?.name ?? normalized.id,
|
||
details: normalized.details,
|
||
...(normalized.note ? { note: normalized.note } : {}),
|
||
}
|
||
}
|
||
|
||
function processItto(process) {
|
||
return {
|
||
id: process.id,
|
||
name: process.name,
|
||
inputs: process.inputs.map((ref) => attachRefMeta(ref, artifactMap)),
|
||
tools: process.tools.map((ref) => attachRefMeta(ref, toolMap)),
|
||
outputs: process.outputs.map((ref) => attachRefMeta(ref, artifactMap)),
|
||
}
|
||
}
|
||
|
||
function includesRef(refs, targetId) {
|
||
return refs.some((ref) => normalizeRef(ref).id === targetId)
|
||
}
|
||
|
||
function artifactUsage(artifact) {
|
||
return {
|
||
id: artifact.id,
|
||
name: artifact.name,
|
||
asInput: processes.filter((process) => includesRef(process.inputs, artifact.id)).map(processSummary),
|
||
asOutput: processes.filter((process) => includesRef(process.outputs, artifact.id)).map(processSummary),
|
||
}
|
||
}
|
||
|
||
function toolUsage(tool) {
|
||
return {
|
||
id: tool.id,
|
||
name: tool.name,
|
||
usedIn: processes.filter((process) => includesRef(process.tools, tool.id)).map(processSummary),
|
||
}
|
||
}
|
||
|
||
cleanApiDir()
|
||
|
||
writeJson('process-groups.json', processGroups.map((item) => ({ id: item.id, name: item.name })))
|
||
|
||
writeJson(
|
||
'knowledge-areas.json',
|
||
knowledgeAreas.map((item) => ({
|
||
id: item.id,
|
||
name: item.name,
|
||
tailoringFactors: (item.tailoringFactors ?? []).map((factor) => ({
|
||
title: factor.title,
|
||
description: factor.description,
|
||
})),
|
||
}))
|
||
)
|
||
|
||
for (const knowledgeArea of knowledgeAreas) {
|
||
writeJson(
|
||
`knowledge-areas/${knowledgeArea.id}/tailoring-factors.json`,
|
||
(knowledgeArea.tailoringFactors ?? []).map((factor) => ({
|
||
title: factor.title,
|
||
description: factor.description,
|
||
}))
|
||
)
|
||
|
||
writeJson(
|
||
`knowledge-areas/${knowledgeArea.id}/processes.json`,
|
||
processes
|
||
.filter((process) => process.knowledgeAreaId === knowledgeArea.id)
|
||
.map((process) => {
|
||
const summary = processSummary(process)
|
||
return {
|
||
id: summary.id,
|
||
name: summary.name,
|
||
processGroupId: summary.processGroupId,
|
||
processGroupName: summary.processGroupName,
|
||
purpose: summary.purpose,
|
||
}
|
||
})
|
||
)
|
||
}
|
||
|
||
for (const processGroup of processGroups) {
|
||
writeJson(
|
||
`process-groups/${processGroup.id}/processes.json`,
|
||
processes
|
||
.filter((process) => process.processGroupId === processGroup.id)
|
||
.map((process) => {
|
||
const summary = processSummary(process)
|
||
return {
|
||
id: summary.id,
|
||
name: summary.name,
|
||
knowledgeAreaId: summary.knowledgeAreaId,
|
||
knowledgeAreaName: summary.knowledgeAreaName,
|
||
purpose: summary.purpose,
|
||
}
|
||
})
|
||
)
|
||
}
|
||
|
||
writeJson('processes.json', processes.map(processSummary))
|
||
|
||
for (const process of processes) {
|
||
writeJson(`processes/${process.id}.json`, processSummary(process))
|
||
writeJson(`processes/${process.id}/itto.json`, processItto(process))
|
||
}
|
||
|
||
writeJson('performance-domains.json', performanceDomains.map((item) => ({ id: item.id, name: item.name })))
|
||
|
||
for (const domain of performanceDomains) {
|
||
writeJson(`performance-domains/${domain.id}.json`, {
|
||
id: domain.id,
|
||
name: domain.name,
|
||
expectedGoals: domain.detail?.expectedGoals ?? [],
|
||
keyPoints: domain.detail?.keyPoints ?? [],
|
||
interactions: domain.detail?.interactions ?? [],
|
||
checks: domain.detail?.checks ?? [],
|
||
})
|
||
}
|
||
|
||
writeJson('artifacts.json', artifacts.map((item) => ({ id: item.id, name: item.name })))
|
||
writeJson('tools.json', tools.map((item) => ({ id: item.id, name: item.name })))
|
||
|
||
for (const artifact of artifacts) {
|
||
writeJson(`artifacts/${artifact.id}/usage.json`, artifactUsage(artifact))
|
||
}
|
||
|
||
for (const tool of tools) {
|
||
writeJson(`tools/${tool.id}/usage.json`, toolUsage(tool))
|
||
}
|
||
|
||
copyApiDoc()
|
||
|
||
console.log(`已生成静态 API 文件:${path.relative(rootDir, apiDir)}`)
|
||
console.log(`已生成 Markdown 接口文档:${path.relative(rootDir, apiDocPath)}`)
|