mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
- Improved the reading of the discovery index by adding a fallback mechanism to scan directories for discovery folders if the index.json is invalid or missing. - Added sorting of discoveries by creation time in descending order. - Enhanced the `appendToIssuesJsonl` function to include deduplication logic for issues based on ID and source finding ID. - Updated the discovery route handler to reflect the number of issues added and skipped during export. - Introduced UI elements for selecting and deselecting findings in the dashboard. - Added CSS styles for exported findings and action buttons. - Implemented search functionality for filtering findings based on title, file, and description. - Added internationalization support for new UI elements. - Created scripts for automated API extraction from various project types, including FastAPI and TypeScript. - Documented the API extraction process and library bundling instructions.
271 lines
8.3 KiB
Python
271 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
API 文档提取脚本
|
|
支持 FastAPI、TypeScript、Python 模块
|
|
"""
|
|
|
|
import subprocess
|
|
import sys
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional
|
|
|
|
# 项目配置
|
|
PROJECTS = {
|
|
'backend': {
|
|
'path': Path('D:/dongdiankaifa9/backend'),
|
|
'type': 'fastapi',
|
|
'entry': 'app.main:app',
|
|
'output': 'api-docs/backend'
|
|
},
|
|
'frontend': {
|
|
'path': Path('D:/dongdiankaifa9/frontend'),
|
|
'type': 'typescript',
|
|
'entries': ['lib', 'hooks', 'components'],
|
|
'output': 'api-docs/frontend'
|
|
},
|
|
'hydro_generator_module': {
|
|
'path': Path('D:/dongdiankaifa9/hydro_generator_module'),
|
|
'type': 'python',
|
|
'output': 'api-docs/hydro_generator'
|
|
},
|
|
'multiphysics_network': {
|
|
'path': Path('D:/dongdiankaifa9/multiphysics_network'),
|
|
'type': 'python',
|
|
'output': 'api-docs/multiphysics'
|
|
}
|
|
}
|
|
|
|
|
|
def extract_fastapi(name: str, config: Dict[str, Any], output_base: Path) -> bool:
|
|
"""提取 FastAPI OpenAPI 文档"""
|
|
path = config['path']
|
|
output_dir = output_base / config['output']
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# 添加路径到 sys.path
|
|
if str(path) not in sys.path:
|
|
sys.path.insert(0, str(path))
|
|
|
|
try:
|
|
# 动态导入 app
|
|
from app.main import app
|
|
|
|
# 获取 OpenAPI schema
|
|
openapi_schema = app.openapi()
|
|
|
|
# 保存 JSON
|
|
json_path = output_dir / 'openapi.json'
|
|
with open(json_path, 'w', encoding='utf-8') as f:
|
|
json.dump(openapi_schema, f, indent=2, ensure_ascii=False)
|
|
|
|
# 生成 Markdown 摘要
|
|
md_path = output_dir / 'API_SUMMARY.md'
|
|
generate_api_markdown(openapi_schema, md_path)
|
|
|
|
endpoints = len(openapi_schema.get('paths', {}))
|
|
print(f" ✓ Extracted {endpoints} endpoints → {output_dir}")
|
|
return True
|
|
|
|
except ImportError as e:
|
|
print(f" ✗ Import error: {e}")
|
|
return False
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
return False
|
|
|
|
|
|
def generate_api_markdown(schema: Dict, output_path: Path):
|
|
"""从 OpenAPI schema 生成 Markdown"""
|
|
lines = [
|
|
f"# {schema.get('info', {}).get('title', 'API Reference')}",
|
|
"",
|
|
f"Version: {schema.get('info', {}).get('version', '1.0.0')}",
|
|
"",
|
|
"## Endpoints",
|
|
"",
|
|
"| Method | Path | Summary |",
|
|
"|--------|------|---------|"
|
|
]
|
|
|
|
for path, methods in schema.get('paths', {}).items():
|
|
for method, details in methods.items():
|
|
if method in ('get', 'post', 'put', 'delete', 'patch'):
|
|
summary = details.get('summary', details.get('operationId', '-'))
|
|
lines.append(f"| `{method.upper()}` | `{path}` | {summary} |")
|
|
|
|
lines.extend([
|
|
"",
|
|
"## Schemas",
|
|
""
|
|
])
|
|
|
|
for name, schema_def in schema.get('components', {}).get('schemas', {}).items():
|
|
lines.append(f"### {name}")
|
|
lines.append("")
|
|
if 'properties' in schema_def:
|
|
lines.append("| Property | Type | Required |")
|
|
lines.append("|----------|------|----------|")
|
|
required = schema_def.get('required', [])
|
|
for prop, prop_def in schema_def['properties'].items():
|
|
prop_type = prop_def.get('type', prop_def.get('$ref', 'any'))
|
|
is_required = '✓' if prop in required else ''
|
|
lines.append(f"| `{prop}` | {prop_type} | {is_required} |")
|
|
lines.append("")
|
|
|
|
with open(output_path, 'w', encoding='utf-8') as f:
|
|
f.write('\n'.join(lines))
|
|
|
|
|
|
def extract_typescript(name: str, config: Dict[str, Any], output_base: Path) -> bool:
|
|
"""提取 TypeScript 文档 (TypeDoc)"""
|
|
path = config['path']
|
|
output_dir = output_base / config['output']
|
|
|
|
# 检查 TypeDoc 是否已安装
|
|
try:
|
|
result = subprocess.run(
|
|
['npx', 'typedoc', '--version'],
|
|
cwd=path,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
if result.returncode != 0:
|
|
print(f" ⚠ TypeDoc not installed, installing...")
|
|
subprocess.run(
|
|
['npm', 'install', '--save-dev', 'typedoc', 'typedoc-plugin-markdown'],
|
|
cwd=path,
|
|
check=True
|
|
)
|
|
except FileNotFoundError:
|
|
print(f" ✗ npm/npx not found")
|
|
return False
|
|
|
|
# 运行 TypeDoc
|
|
try:
|
|
entries = config.get('entries', ['lib'])
|
|
cmd = [
|
|
'npx', 'typedoc',
|
|
'--plugin', 'typedoc-plugin-markdown',
|
|
'--out', str(output_dir),
|
|
'--entryPointStrategy', 'expand',
|
|
'--exclude', '**/node_modules/**',
|
|
'--exclude', '**/*.test.*',
|
|
'--readme', 'none'
|
|
]
|
|
for entry in entries:
|
|
entry_path = path / entry
|
|
if entry_path.exists():
|
|
cmd.extend(['--entryPoints', str(entry_path)])
|
|
|
|
result = subprocess.run(cmd, cwd=path, capture_output=True, text=True)
|
|
|
|
if result.returncode == 0:
|
|
print(f" ✓ TypeDoc generated → {output_dir}")
|
|
return True
|
|
else:
|
|
print(f" ✗ TypeDoc error: {result.stderr[:200]}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
return False
|
|
|
|
|
|
def extract_python_module(name: str, config: Dict[str, Any], output_base: Path) -> bool:
|
|
"""提取 Python 模块文档 (pdoc)"""
|
|
path = config['path']
|
|
output_dir = output_base / config['output']
|
|
module_name = path.name
|
|
|
|
# 检查 pdoc
|
|
try:
|
|
subprocess.run(['pdoc', '--version'], capture_output=True, check=True)
|
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
print(f" ⚠ pdoc not installed, installing...")
|
|
subprocess.run([sys.executable, '-m', 'pip', 'install', 'pdoc'], check=True)
|
|
|
|
# 运行 pdoc
|
|
try:
|
|
result = subprocess.run(
|
|
[
|
|
'pdoc', module_name,
|
|
'--output-dir', str(output_dir),
|
|
'--format', 'markdown'
|
|
],
|
|
cwd=path.parent,
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
# 统计生成的文件
|
|
md_files = list(output_dir.glob('**/*.md'))
|
|
print(f" ✓ pdoc generated {len(md_files)} files → {output_dir}")
|
|
return True
|
|
else:
|
|
print(f" ✗ pdoc error: {result.stderr[:200]}")
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f" ✗ Error: {e}")
|
|
return False
|
|
|
|
|
|
EXTRACTORS = {
|
|
'fastapi': extract_fastapi,
|
|
'typescript': extract_typescript,
|
|
'python': extract_python_module
|
|
}
|
|
|
|
|
|
def main(output_base: Optional[str] = None, projects: Optional[list] = None):
|
|
"""主入口"""
|
|
base = Path(output_base) if output_base else Path.cwd()
|
|
|
|
print("=" * 50)
|
|
print("API Documentation Extraction")
|
|
print("=" * 50)
|
|
|
|
results = {}
|
|
|
|
for name, config in PROJECTS.items():
|
|
if projects and name not in projects:
|
|
continue
|
|
|
|
print(f"\n[{name}] ({config['type']})")
|
|
|
|
if not config['path'].exists():
|
|
print(f" ✗ Path not found: {config['path']}")
|
|
results[name] = False
|
|
continue
|
|
|
|
extractor = EXTRACTORS.get(config['type'])
|
|
if extractor:
|
|
results[name] = extractor(name, config, base)
|
|
else:
|
|
print(f" ✗ Unknown type: {config['type']}")
|
|
results[name] = False
|
|
|
|
# 汇总
|
|
print("\n" + "=" * 50)
|
|
print("Summary")
|
|
print("=" * 50)
|
|
success = sum(1 for v in results.values() if v)
|
|
print(f"Success: {success}/{len(results)}")
|
|
|
|
return all(results.values())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='Extract API documentation')
|
|
parser.add_argument('--output', '-o', default='.', help='Output base directory')
|
|
parser.add_argument('--projects', '-p', nargs='+', help='Specific projects to extract')
|
|
|
|
args = parser.parse_args()
|
|
|
|
success = main(args.output, args.projects)
|
|
sys.exit(0 if success else 1)
|