const fs = require('fs'); const path = require('path'); const YAML = require('yaml'); const repoRoot = path.resolve(process.cwd(), '..'); const specRoot = path.join(repoRoot, 'pocket-base', 'spec'); const folders = ['openapi-wx', 'openapi-manage']; const allFiles = []; for (const folder of folders) { const dir = path.join(specRoot, folder); for (const name of fs.readdirSync(dir)) { if (name.endsWith('.yaml')) allFiles.push(path.join(dir, name)); } } const docCache = new Map(); function loadDoc(filePath) { if (!docCache.has(filePath)) { const text = fs.readFileSync(filePath, 'utf8'); docCache.set(filePath, YAML.parse(text)); } return docCache.get(filePath); } function deepClone(v) { return v == null ? v : JSON.parse(JSON.stringify(v)); } function decodePointer(seg) { return seg.replace(/~1/g, '/').replace(/~0/g, '~'); } function getByPointer(doc, pointer) { if (!pointer || pointer === '#' || pointer === '') return doc; const parts = pointer.replace(/^#/, '').split('/').filter(Boolean).map(decodePointer); let cur = doc; for (const p of parts) { if (cur == null) return undefined; cur = cur[p]; } return cur; } function mergeObjects(base, extra) { if (base && typeof base === 'object' && !Array.isArray(base) && extra && typeof extra === 'object' && !Array.isArray(extra)) { return { ...base, ...extra }; } return base; } function isExternalRef(ref) { if (typeof ref !== 'string') return false; if (ref.startsWith('#')) return false; if (/^https?:\/\//i.test(ref)) return false; const [filePart] = ref.split('#'); return !!filePart; } function resolveExternalRef(ref, baseFile) { const [filePart, hashPart = ''] = ref.split('#'); const targetFile = path.resolve(path.dirname(baseFile), filePart); const targetDoc = loadDoc(targetFile); const pointer = hashPart ? `#${hashPart}` : '#'; const targetNode = getByPointer(targetDoc, pointer); return { targetFile, targetNode: deepClone(targetNode) }; } function derefExternals(node, baseFile, seen = new Set()) { if (Array.isArray(node)) { return node.map((item) => derefExternals(item, baseFile, seen)); } if (!node || typeof node !== 'object') { return node; } if (typeof node.$ref === 'string' && isExternalRef(node.$ref)) { const key = `${baseFile}::${node.$ref}`; if (seen.has(key)) { return node; } seen.add(key); const { targetFile, targetNode } = resolveExternalRef(node.$ref, baseFile); let inlined = derefExternals(targetNode, targetFile, seen); const siblings = { ...node }; delete siblings.$ref; if (Object.keys(siblings).length > 0) { inlined = mergeObjects(inlined, derefExternals(siblings, baseFile, seen)); } return inlined; } const out = {}; for (const [k, v] of Object.entries(node)) { out[k] = derefExternals(v, baseFile, seen); } return out; } function ensureTopLevel(doc, filePath) { if (!doc.openapi) doc.openapi = '3.1.0'; if (!doc.info || typeof doc.info !== 'object') { const base = path.basename(filePath, '.yaml'); const group = filePath.includes('openapi-wx') ? 'WX Native' : 'Manage Hooks'; doc.info = { title: `BAI ${group} API - ${base}`, version: '1.0.0' }; } else { if (!doc.info.title) doc.info.title = `BAI API - ${path.basename(filePath, '.yaml')}`; if (!doc.info.version) doc.info.version = '1.0.0'; } if (!Array.isArray(doc.servers)) { doc.servers = [ { url: 'https://bai-api.blv-oa.com', description: '生产环境' }, { url: 'http://localhost:8090', description: 'PocketBase 本地环境' } ]; } } for (const filePath of allFiles) { const original = loadDoc(filePath); const transformed = derefExternals(deepClone(original), filePath); ensureTopLevel(transformed, filePath); const outText = YAML.stringify(transformed, { lineWidth: 0 }); fs.writeFileSync(filePath, outText, 'utf8'); docCache.set(filePath, transformed); } console.log(`Processed ${allFiles.length} yaml files under openapi-wx/openapi-manage.`);