const fs = require('fs'); const path = require('path'); const YAML = require('yaml'); const repoRoot = process.cwd(); const rootFile = path.join(repoRoot, 'pocket-base', 'spec', 'openapi-wx.yaml'); const splitDir = path.join(repoRoot, 'pocket-base', 'spec', 'openapi-wx'); const filePaths = [ rootFile, ...fs .readdirSync(splitDir) .filter((name) => name.endsWith('.yaml')) .map((name) => path.join(splitDir, name)), ]; const docs = new Map( filePaths.map((filePath) => [filePath, YAML.parse(fs.readFileSync(filePath, 'utf8'))]), ); function decodePointerSegment(segment) { return segment.replace(/~1/g, '/').replace(/~0/g, '~'); } function getByPointer(documentData, pointer) { if (!pointer || pointer === '#' || pointer === '') { return documentData; } const segments = pointer .replace(/^#/, '') .split('/') .filter(Boolean) .map(decodePointerSegment); let current = documentData; for (const segment of segments) { if (current == null) { return undefined; } current = current[segment]; } return current; } function resolveRef(ref, currentFile) { const [filePart, hashPart = ''] = ref.split('#'); const targetFile = filePart ? path.resolve(path.dirname(currentFile), filePart) : currentFile; const targetDoc = docs.get(targetFile); if (!targetDoc) { return { targetFile, targetKey: `${targetFile}#${hashPart}`, schema: undefined }; } const pointer = hashPart ? `#${hashPart}` : '#'; return { targetFile, targetKey: `${targetFile}${pointer}`, schema: getByPointer(targetDoc, pointer), }; } function pickType(typeValue) { if (Array.isArray(typeValue)) { return typeValue.find((item) => item !== 'null') || typeValue[0]; } return typeValue; } function cleanLabelText(text, fallback) { if (!text || typeof text !== 'string') { return fallback; } const normalized = text .split(/\r?\n/) .map((line) => line.trim()) .filter(Boolean) .map((line) => line.replace(/^[\-\d\.\)\s]+/, '').trim()) .find((line) => line && !/^(可选|必填|说明|注意)[。::]?$/u.test(line)); if (!normalized) { return fallback; } return normalized.replace(/[`"'<>]/g, '').trim() || fallback; } function placeholderKey(key) { return typeof key === 'string' && key.includes('|'); } function deepMerge(baseValue, nextValue) { if ( baseValue && nextValue && typeof baseValue === 'object' && typeof nextValue === 'object' && !Array.isArray(baseValue) && !Array.isArray(nextValue) ) { const merged = { ...baseValue }; const nextKeys = Object.keys(nextValue); const hasConcreteNextKeys = nextKeys.some((key) => !placeholderKey(key)); if (hasConcreteNextKeys) { for (const key of Object.keys(merged)) { if (placeholderKey(key)) { delete merged[key]; } } } for (const [key, value] of Object.entries(nextValue)) { if (key in merged) { merged[key] = deepMerge(merged[key], value); } else { merged[key] = value; } } return merged; } return nextValue; } function buildLabel(schemaLike, fallback) { return cleanLabelText(schemaLike?.description || schemaLike?.title, fallback); } function fallbackScalar(label, fieldType) { return `${label}|${fieldType || 'string'}`; } function buildExample(schemaLike, currentFile, fieldName = 'field', seenRefs = new Set()) { if (schemaLike == null) { return fallbackScalar(fieldName, 'string'); } if (typeof schemaLike !== 'object' || Array.isArray(schemaLike)) { return fallbackScalar(fieldName, 'string'); } if (schemaLike.$ref) { const { targetFile, targetKey, schema } = resolveRef(schemaLike.$ref, currentFile); if (!schema) { return fallbackScalar(fieldName, 'string'); } if (seenRefs.has(targetKey)) { return fallbackScalar(fieldName, 'object'); } const nextSeen = new Set(seenRefs); nextSeen.add(targetKey); return buildExample(schema, targetFile, fieldName, nextSeen); } if (schemaLike.allOf) { return schemaLike.allOf.reduce((accumulator, item) => { const nextValue = buildExample(item, currentFile, fieldName, seenRefs); return deepMerge(accumulator, nextValue); }, {}); } if (schemaLike.oneOf && schemaLike.oneOf.length > 0) { return buildExample(schemaLike.oneOf[0], currentFile, fieldName, seenRefs); } if (schemaLike.anyOf && schemaLike.anyOf.length > 0) { return buildExample(schemaLike.anyOf[0], currentFile, fieldName, seenRefs); } const fieldType = pickType(schemaLike.type) || (schemaLike.properties || schemaLike.additionalProperties ? 'object' : undefined) || (schemaLike.items ? 'array' : undefined) || 'string'; const fieldLabel = buildLabel(schemaLike, fieldName); if (fieldType === 'object') { const exampleObject = {}; if (schemaLike.properties && typeof schemaLike.properties === 'object') { for (const [propertyName, propertySchema] of Object.entries(schemaLike.properties)) { exampleObject[propertyName] = buildExample( propertySchema, currentFile, propertyName, seenRefs, ); } } if (Object.keys(exampleObject).length === 0 && schemaLike.additionalProperties) { exampleObject[`${fieldLabel}字段|string`] = `${fieldLabel}值|string`; } if (Object.keys(exampleObject).length === 0) { exampleObject[`${fieldLabel}|string`] = `${fieldLabel}|string`; } return exampleObject; } if (fieldType === 'array') { const itemFieldName = fieldName.endsWith('s') ? fieldName.slice(0, -1) : `${fieldName}_item`; return [buildExample(schemaLike.items || {}, currentFile, itemFieldName, seenRefs)]; } return fallbackScalar(fieldLabel, fieldType); } function httpMethods(pathItem) { return ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace'].filter( (method) => pathItem && typeof pathItem[method] === 'object', ); } for (const filePath of filePaths.filter((file) => file !== rootFile)) { const documentData = docs.get(filePath); if (!documentData || typeof documentData !== 'object') { continue; } if (documentData.components && documentData.components.schemas) { for (const [schemaName, schemaValue] of Object.entries(documentData.components.schemas)) { schemaValue.example = buildExample(schemaValue, filePath, schemaName, new Set()); } } if (documentData.paths) { for (const pathItem of Object.values(documentData.paths)) { for (const method of httpMethods(pathItem)) { const operation = pathItem[method]; const requestContent = operation?.requestBody?.content; if (requestContent && typeof requestContent === 'object') { for (const media of Object.values(requestContent)) { if (media && media.schema) { delete media.examples; media.example = buildExample(media.schema, filePath, 'request', new Set()); } } } const responses = operation?.responses; if (responses && typeof responses === 'object') { for (const response of Object.values(responses)) { const responseContent = response?.content; if (!responseContent || typeof responseContent !== 'object') { continue; } for (const media of Object.values(responseContent)) { if (media && media.schema) { delete media.examples; media.example = buildExample(media.schema, filePath, 'response', new Set()); } } } } } } } fs.writeFileSync(filePath, YAML.stringify(documentData, { lineWidth: 0 }), 'utf8'); } console.log(`Updated examples in ${filePaths.length - 1} split OpenAPI files.`);