Files
ittoview/scripts/generate-api.mjs
2026-05-10 12:19:51 +01:00

301 lines
8.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)}`)