vibes-plug 1.0.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/vibes-plug-core.md +32 -0
- package/.cursor/rules/vibes-plug-core.mdc +51 -0
- package/.cursorrules +42 -0
- package/AGENTS.md +96 -0
- package/BLUEPRINT.md +309 -125
- package/CHANGELOG.md +183 -1
- package/CLAUDE.md +70 -0
- package/LICENSE +1 -1
- package/README.md +641 -263
- package/index.js +19 -0
- package/package.json +61 -25
- package/plugin.json +24 -7
- package/scripts/generate_swarm_gif.py +295 -0
- package/scripts/install.js +201 -0
- package/skills/accessibility-testing-expert/SKILL.md +116 -0
- package/skills/ai-cost-token-optimizer/SKILL.md +82 -0
- package/skills/ai-evals-benchmark-expert/SKILL.md +188 -0
- package/skills/ai-llm-integration-expert/SKILL.md +147 -122
- package/skills/ai-media-generation-expert/SKILL.md +172 -0
- package/skills/ai-prompt-engineering-expert/SKILL.md +84 -0
- package/skills/angular-expert/SKILL.md +148 -0
- package/skills/api-design-expert/SKILL.md +316 -309
- package/skills/api-gateway-proxy-expert/SKILL.md +81 -0
- package/skills/app-analyzer-optimizer/SKILL.md +195 -188
- package/skills/apple-ecosystem-expert/SKILL.md +145 -0
- package/skills/{asisten_ramah → asisten-ramah}/SKILL.md +7 -1
- package/skills/astro-framework-expert/SKILL.md +200 -0
- package/skills/async-queue-temporal-expert/SKILL.md +240 -0
- package/skills/authentication-identity-expert/SKILL.md +279 -45
- package/skills/auto-doc-updater/SKILL.md +219 -203
- package/skills/autonomous-chaos-monkey/SKILL.md +63 -0
- package/skills/autonomous-red-teamer/SKILL.md +203 -0
- package/skills/autonomous-tdd-debugger/SKILL.md +71 -0
- package/skills/background-jobs-queue-expert/SKILL.md +235 -0
- package/skills/biome-linter-formatter-expert/SKILL.md +89 -0
- package/skills/blockchain-web3-expert/SKILL.md +115 -0
- package/skills/bootstrap-to-modern/SKILL.md +93 -86
- package/skills/brainstorming/SKILL.md +381 -353
- package/skills/browser-automation-expert/SKILL.md +222 -0
- package/skills/bun-runtime-expert/SKILL.md +7 -1
- package/skills/chatbot-messaging-expert/SKILL.md +114 -0
- package/skills/ci-cd-devops-architect/SKILL.md +81 -45
- package/skills/cloud-hosting-expert/SKILL.md +249 -243
- package/skills/coderabbit/SKILL.md +197 -191
- package/skills/compliance-gdpr-privacy-expert/SKILL.md +85 -0
- package/skills/cron-scheduler-expert/SKILL.md +304 -0
- package/skills/data-pipeline-etl-expert/SKILL.md +84 -0
- package/skills/data-telemetry-expert/SKILL.md +218 -212
- package/skills/data-visualization-expert/SKILL.md +154 -0
- package/skills/database-migration-versioning-expert/SKILL.md +90 -0
- package/skills/database-orm-expert/SKILL.md +303 -293
- package/skills/dependency-upgrade-migrator/SKILL.md +301 -0
- package/skills/design-system-architect/SKILL.md +278 -242
- package/skills/desktop-electron-expert/SKILL.md +128 -0
- package/skills/documentation-site-expert/SKILL.md +59 -0
- package/skills/doku-mcp-server/SKILL.md +257 -0
- package/skills/doku-payment-gateway/SKILL.md +233 -0
- package/skills/domain-driven-design-expert/SKILL.md +82 -0
- package/skills/e2e-testing-expert/SKILL.md +320 -314
- package/skills/ecommerce-expert/SKILL.md +87 -0
- package/skills/edge-serverless-db-expert/SKILL.md +99 -0
- package/skills/email-notification-expert/SKILL.md +368 -0
- package/skills/error-resilience-expert/SKILL.md +486 -0
- package/skills/event-driven-architect/SKILL.md +86 -80
- package/skills/feature-flag-analytics-expert/SKILL.md +66 -0
- package/skills/file-upload-media-expert/SKILL.md +437 -0
- package/skills/firebase-security-expert/SKILL.md +7 -1
- package/skills/form-validation-expert/SKILL.md +407 -0
- package/skills/fullstack-expert/SKILL.md +260 -201
- package/skills/fullstack-expert/references/api_design_guide.md +466 -466
- package/skills/fullstack-expert/references/multi_language_backend.md +528 -528
- package/skills/fullstack-expert/scripts/api_contract_validator.py +253 -253
- package/skills/fullstack-expert/scripts/architecture_analyzer.py +326 -326
- package/skills/gemini-agent-booster/SKILL.md +142 -104
- package/skills/geospatial-maps-expert/SKILL.md +80 -0
- package/skills/global-a11y-i18n-expert/SKILL.md +86 -80
- package/skills/glsl-shader-expert/SKILL.md +107 -0
- package/skills/go-programming-expert/SKILL.md +300 -294
- package/skills/graph-rag-knowledge-expert/SKILL.md +159 -0
- package/skills/graphql-apollo-expert/SKILL.md +114 -0
- package/skills/headless-cms-expert/SKILL.md +181 -0
- package/skills/hig/SKILL.md +193 -187
- package/skills/js-backend-expert/SKILL.md +218 -191
- package/skills/legacy-code-translator/SKILL.md +71 -0
- package/skills/local-slm-edge-ai-expert/SKILL.md +167 -0
- package/skills/logging-error-tracking-expert/SKILL.md +344 -0
- package/skills/mcp-client-orchestrator/SKILL.md +76 -0
- package/skills/mcp-server-architect/SKILL.md +226 -126
- package/skills/micro-frontend-architect/SKILL.md +112 -0
- package/skills/mobile-expo-expert/SKILL.md +191 -185
- package/skills/mobile-push-notification-expert/SKILL.md +71 -0
- package/skills/modern-css-native-expert/SKILL.md +189 -0
- package/skills/monday-design-aesthetic/SKILL.md +72 -66
- package/skills/monorepo-architect/SKILL.md +232 -226
- package/skills/mpa-orchestrator/SKILL.md +120 -101
- package/skills/multi-agent-orchestration/SKILL.md +173 -153
- package/skills/multiple-entry-points/SKILL.md +91 -55
- package/skills/mvc-expert/SKILL.md +237 -231
- package/skills/n8n-automation-expert/SKILL.md +89 -0
- package/skills/nextjs-app-router-expert/SKILL.md +148 -0
- package/skills/openapi-swagger-codegen-expert/SKILL.md +67 -0
- package/skills/payment-gateway-expert/SKILL.md +129 -45
- package/skills/pdf-document-generation-expert/SKILL.md +91 -0
- package/skills/performance-web-vitals/SKILL.md +337 -331
- package/skills/post-quantum-crypto-migrator/SKILL.md +57 -0
- package/skills/prd-architect/SKILL.md +206 -190
- package/skills/proactive-background-watcher/SKILL.md +68 -0
- package/skills/production-ready-hardener/PRODUCTION_READINESS_REPORT.md +67 -0
- package/skills/production-ready-hardener/SKILL.md +461 -468
- package/skills/production-ready-hardener/references/production_checklist.md +161 -161
- package/skills/production-ready-hardener/scripts/production_readiness_scanner.py +881 -875
- package/skills/project-context-mapper/SKILL.md +85 -0
- package/skills/pwa-offline-first-expert/SKILL.md +185 -0
- package/skills/python-programming-expert/SKILL.md +407 -270
- package/skills/rate-limit-abuse-prevention/SKILL.md +377 -0
- package/skills/realtime-collaboration-expert/SKILL.md +99 -45
- package/skills/rich-text-editor-expert/SKILL.md +177 -0
- package/skills/rust-programming-expert/SKILL.md +240 -234
- package/skills/saas-billing/SKILL.md +382 -376
- package/skills/saas-multi-tenant/SKILL.md +256 -236
- package/skills/saas-mvp-launcher/SKILL.md +30 -1
- package/skills/saas-transformer/SKILL.md +499 -445
- package/skills/saas-transformer/references/billing_integration_guide.md +401 -401
- package/skills/saas-transformer/references/feature_gating_patterns.md +137 -137
- package/skills/saas-transformer/references/saas_transformation_checklist.md +121 -121
- package/skills/saas-transformer/scripts/saas_transformation_scanner.py +39 -29
- package/skills/scalability-clean-code/SKILL.md +234 -228
- package/skills/search-engine-expert/SKILL.md +89 -0
- package/skills/secure-fuzz-testing/SKILL.md +7 -1
- package/skills/self-evolving-memory-graph/SKILL.md +91 -0
- package/skills/self-healing-cloud-orchestrator/SKILL.md +57 -0
- package/skills/senior-frontend/SKILL.md +85 -105
- package/skills/seo/SKILL.md +258 -224
- package/skills/session-context-loader/SKILL.md +83 -0
- package/skills/session-handoff-resume/SKILL.md +163 -157
- package/skills/{skill_baru → skill-baru}/SKILL.md +177 -146
- package/skills/solidjs-expert/SKILL.md +80 -0
- package/skills/spa-orchestrator/SKILL.md +306 -287
- package/skills/sse-websocket-streaming-expert/SKILL.md +93 -0
- package/skills/state-management-expert/SKILL.md +277 -271
- package/skills/supabase-migration/SKILL.md +47 -1
- package/skills/supabase-security-expert/SKILL.md +248 -242
- package/skills/svelte-sveltekit-expert/SKILL.md +91 -0
- package/skills/svg-animation-motion-expert/SKILL.md +115 -0
- package/skills/tailwind-expert/SKILL.md +139 -187
- package/skills/tanstack-query-expert/SKILL.md +204 -198
- package/skills/tauri-expert/SKILL.md +7 -1
- package/skills/token-saver/SKILL.md +118 -110
- package/skills/typescript-expert/SKILL.md +329 -278
- package/skills/ui-components-expert/SKILL.md +166 -63
- package/skills/ui-ux-pro-max/SKILL.md +221 -200
- package/skills/vector-db-rag-expert/SKILL.md +208 -0
- package/skills/vibe-code-gardener/SKILL.md +180 -172
- package/skills/visual-qa-vision-agent/SKILL.md +71 -0
- package/skills/voice-ai-realtime-agent/SKILL.md +202 -0
- package/skills/vue-frontend-expert/SKILL.md +132 -0
- package/skills/wasm-edge-computing-expert/SKILL.md +97 -0
- package/skills/web-3d-graphics-expert/SKILL.md +137 -0
- package/skills/web-game-engine-expert/SKILL.md +102 -0
- package/skills/web-scraper/SKILL.md +98 -146
- package/skills/website-design-cloner/SKILL.md +180 -0
- package/skills/webxr-ar-vr-expert/SKILL.md +123 -0
- package/skills/wordpress-headless-expert/SKILL.md +144 -0
- package/skills/zero-to-prod-orchestrator/SKILL.md +231 -180
- package/skills/zero-trust-secret-vault/SKILL.md +88 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/CONTRIBUTING.md +0 -199
- package/SECURITY.md +0 -21
- package/banner.png +0 -0
- package/skills/senior-fullstack/SKILL.md +0 -167
- package/skills/senior-fullstack/references/architecture_patterns.md +0 -160
- package/skills/senior-fullstack/references/development_workflows.md +0 -222
- package/skills/senior-fullstack/references/tech_stack_guide.md +0 -190
- package/skills/senior-fullstack/scripts/code_quality_analyzer.py +0 -114
- package/skills/senior-fullstack/scripts/fullstack_scaffolder.py +0 -114
- package/skills/senior-fullstack/scripts/project_scaffolder.py +0 -114
- package/skills/seo-aeo-landing-page-writer/SKILL.md +0 -97
- package/skills/seo-geo/SKILL.md +0 -188
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-310.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-310.pyc +0 -0
- package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/skills/ui_ux_expert/SKILL.md +0 -114
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
API Contract Validator
|
|
4
|
-
Validates API contracts (OpenAPI specs) and checks for best practices.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import os
|
|
8
|
-
import sys
|
|
9
|
-
import json
|
|
10
|
-
import argparse
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Dict, List, Optional
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ApiContractValidator:
|
|
16
|
-
"""Validates OpenAPI specs and API route files for best practices."""
|
|
17
|
-
|
|
18
|
-
def __init__(self, target_path: str, verbose: bool = False):
|
|
19
|
-
self.target_path = Path(target_path)
|
|
20
|
-
self.verbose = verbose
|
|
21
|
-
self.results: Dict = {
|
|
22
|
-
'status': 'pending',
|
|
23
|
-
'target': str(self.target_path),
|
|
24
|
-
'findings': [],
|
|
25
|
-
'api_routes': [],
|
|
26
|
-
'score': 100,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
def run(self) -> Dict:
|
|
30
|
-
"""Execute the API contract validation."""
|
|
31
|
-
print(f"📋 Running API Contract Validator...")
|
|
32
|
-
print(f"📁 Target: {self.target_path}")
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
self.validate_target()
|
|
36
|
-
self.scan_api_routes()
|
|
37
|
-
self.check_openapi_spec()
|
|
38
|
-
self.check_validation_schemas()
|
|
39
|
-
self.check_error_handling()
|
|
40
|
-
self.check_authentication()
|
|
41
|
-
self.check_rate_limiting()
|
|
42
|
-
self.generate_report()
|
|
43
|
-
|
|
44
|
-
self.results['status'] = 'success'
|
|
45
|
-
print("✅ Validation completed!")
|
|
46
|
-
return self.results
|
|
47
|
-
|
|
48
|
-
except Exception as e:
|
|
49
|
-
print(f"❌ Error: {e}")
|
|
50
|
-
sys.exit(1)
|
|
51
|
-
|
|
52
|
-
def validate_target(self):
|
|
53
|
-
"""Validate the target path exists."""
|
|
54
|
-
if not self.target_path.exists():
|
|
55
|
-
raise ValueError(f"Target path does not exist: {self.target_path}")
|
|
56
|
-
|
|
57
|
-
def scan_api_routes(self):
|
|
58
|
-
"""Scan for API route definitions."""
|
|
59
|
-
api_dirs = [
|
|
60
|
-
'app/api', 'src/routes', 'src/api', 'routes',
|
|
61
|
-
'internal/handler', 'src/handlers',
|
|
62
|
-
]
|
|
63
|
-
route_files = []
|
|
64
|
-
|
|
65
|
-
for api_dir in api_dirs:
|
|
66
|
-
dir_path = self.target_path / api_dir
|
|
67
|
-
if dir_path.is_dir():
|
|
68
|
-
for f in dir_path.rglob('*'):
|
|
69
|
-
if f.is_file() and f.suffix in {'.ts', '.tsx', '.js', '.py', '.go', '.rs'}:
|
|
70
|
-
route_files.append(str(f.relative_to(self.target_path)))
|
|
71
|
-
|
|
72
|
-
self.results['api_routes'] = route_files
|
|
73
|
-
if self.verbose:
|
|
74
|
-
print(f"✓ Found {len(route_files)} API route files")
|
|
75
|
-
|
|
76
|
-
if not route_files:
|
|
77
|
-
self._add_finding('info', 'No API routes found in standard directories')
|
|
78
|
-
|
|
79
|
-
def check_openapi_spec(self):
|
|
80
|
-
"""Check for OpenAPI specification file."""
|
|
81
|
-
spec_files = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.json']
|
|
82
|
-
has_spec = any((self.target_path / f).exists() for f in spec_files)
|
|
83
|
-
|
|
84
|
-
# Also check in docs/ or api/ directories
|
|
85
|
-
for subdir in ['docs', 'api', 'spec']:
|
|
86
|
-
if (self.target_path / subdir).is_dir():
|
|
87
|
-
has_spec = has_spec or any(
|
|
88
|
-
(self.target_path / subdir / f).exists() for f in spec_files
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
if not has_spec and self.results['api_routes']:
|
|
92
|
-
self._add_finding('warning', 'No OpenAPI spec found — consider spec-first API design')
|
|
93
|
-
self.results['score'] -= 10
|
|
94
|
-
|
|
95
|
-
def check_validation_schemas(self):
|
|
96
|
-
"""Check for input validation schemas (Zod, Pydantic, etc.)."""
|
|
97
|
-
validation_patterns = ['zod', 'pydantic', 'joi', 'yup', 'class-validator']
|
|
98
|
-
has_validation = False
|
|
99
|
-
|
|
100
|
-
pkg_json = self.target_path / 'package.json'
|
|
101
|
-
if pkg_json.exists():
|
|
102
|
-
try:
|
|
103
|
-
with open(pkg_json) as f:
|
|
104
|
-
pkg = json.load(f)
|
|
105
|
-
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
106
|
-
has_validation = any(v in deps for v in validation_patterns)
|
|
107
|
-
except (json.JSONDecodeError, IOError):
|
|
108
|
-
pass
|
|
109
|
-
|
|
110
|
-
# Check Python
|
|
111
|
-
pyproject = self.target_path / 'pyproject.toml'
|
|
112
|
-
if pyproject.exists():
|
|
113
|
-
try:
|
|
114
|
-
content = pyproject.read_text()
|
|
115
|
-
has_validation = has_validation or 'pydantic' in content
|
|
116
|
-
except IOError:
|
|
117
|
-
pass
|
|
118
|
-
|
|
119
|
-
if not has_validation and self.results['api_routes']:
|
|
120
|
-
self._add_finding('warning', 'No input validation library detected (Zod, Pydantic, etc.)')
|
|
121
|
-
self.results['score'] -= 15
|
|
122
|
-
|
|
123
|
-
def check_error_handling(self):
|
|
124
|
-
"""Check for structured error handling patterns."""
|
|
125
|
-
error_files = []
|
|
126
|
-
for pattern in ['**/error*.ts', '**/error*.py', '**/error*.go', '**/error*.rs']:
|
|
127
|
-
error_files.extend(self.target_path.glob(pattern))
|
|
128
|
-
|
|
129
|
-
if not error_files and self.results['api_routes']:
|
|
130
|
-
self._add_finding('info', 'No dedicated error handling module found — consider structured error responses (RFC 7807)')
|
|
131
|
-
|
|
132
|
-
def check_authentication(self):
|
|
133
|
-
"""Check for authentication middleware setup."""
|
|
134
|
-
auth_indicators = ['auth', 'jwt', 'clerk', 'nextauth', 'lucia', 'passport']
|
|
135
|
-
has_auth = False
|
|
136
|
-
|
|
137
|
-
pkg_json = self.target_path / 'package.json'
|
|
138
|
-
if pkg_json.exists():
|
|
139
|
-
try:
|
|
140
|
-
with open(pkg_json) as f:
|
|
141
|
-
pkg = json.load(f)
|
|
142
|
-
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
143
|
-
has_auth = any(
|
|
144
|
-
any(indicator in dep for indicator in auth_indicators)
|
|
145
|
-
for dep in deps
|
|
146
|
-
)
|
|
147
|
-
except (json.JSONDecodeError, IOError):
|
|
148
|
-
pass
|
|
149
|
-
|
|
150
|
-
if not has_auth and self.results['api_routes']:
|
|
151
|
-
self._add_finding('info', 'No authentication library detected — ensure API endpoints are protected')
|
|
152
|
-
|
|
153
|
-
def check_rate_limiting(self):
|
|
154
|
-
"""Check for rate limiting setup."""
|
|
155
|
-
rate_limit_deps = ['rate-limit', 'ratelimit', 'limiter', 'throttle']
|
|
156
|
-
has_rate_limit = False
|
|
157
|
-
|
|
158
|
-
pkg_json = self.target_path / 'package.json'
|
|
159
|
-
if pkg_json.exists():
|
|
160
|
-
try:
|
|
161
|
-
with open(pkg_json) as f:
|
|
162
|
-
pkg = json.load(f)
|
|
163
|
-
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
164
|
-
has_rate_limit = any(
|
|
165
|
-
any(rl in dep for rl in rate_limit_deps)
|
|
166
|
-
for dep in deps
|
|
167
|
-
)
|
|
168
|
-
except (json.JSONDecodeError, IOError):
|
|
169
|
-
pass
|
|
170
|
-
|
|
171
|
-
if not has_rate_limit and self.results['api_routes']:
|
|
172
|
-
self._add_finding('warning', 'No rate limiting detected — protect API endpoints from abuse')
|
|
173
|
-
self.results['score'] -= 5
|
|
174
|
-
|
|
175
|
-
def _add_finding(self, severity: str, message: str):
|
|
176
|
-
"""Add a finding to the results."""
|
|
177
|
-
self.results['findings'].append({
|
|
178
|
-
'severity': severity,
|
|
179
|
-
'message': message,
|
|
180
|
-
})
|
|
181
|
-
if self.verbose:
|
|
182
|
-
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(severity, '⚪')
|
|
183
|
-
print(f" {icon} [{severity.upper()}] {message}")
|
|
184
|
-
|
|
185
|
-
def generate_report(self):
|
|
186
|
-
"""Generate and display the validation report."""
|
|
187
|
-
findings = self.results['findings']
|
|
188
|
-
critical = len([f for f in findings if f['severity'] == 'critical'])
|
|
189
|
-
warnings = len([f for f in findings if f['severity'] == 'warning'])
|
|
190
|
-
info = len([f for f in findings if f['severity'] == 'info'])
|
|
191
|
-
|
|
192
|
-
score = max(0, self.results['score'])
|
|
193
|
-
self.results['score'] = score
|
|
194
|
-
|
|
195
|
-
print(f"\n{'='*60}")
|
|
196
|
-
print("📋 API CONTRACT VALIDATION REPORT")
|
|
197
|
-
print(f"{'='*60}")
|
|
198
|
-
print(f"Target: {self.results['target']}")
|
|
199
|
-
print(f"Score: {score}/100")
|
|
200
|
-
print(f"API Routes: {len(self.results['api_routes'])} files found")
|
|
201
|
-
print(f"Findings: {critical} critical, {warnings} warnings, {info} info")
|
|
202
|
-
print(f"\nFindings:")
|
|
203
|
-
for f in findings:
|
|
204
|
-
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(f['severity'], '⚪')
|
|
205
|
-
print(f" {icon} [{f['severity'].upper()}] {f['message']}")
|
|
206
|
-
print(f"{'='*60}\n")
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def main():
|
|
210
|
-
"""Main entry point."""
|
|
211
|
-
parser = argparse.ArgumentParser(
|
|
212
|
-
description="API Contract Validator — validates API design best practices"
|
|
213
|
-
)
|
|
214
|
-
parser.add_argument(
|
|
215
|
-
'target',
|
|
216
|
-
help='Target project path to validate'
|
|
217
|
-
)
|
|
218
|
-
parser.add_argument(
|
|
219
|
-
'--verbose', '-v',
|
|
220
|
-
action='store_true',
|
|
221
|
-
help='Enable verbose output'
|
|
222
|
-
)
|
|
223
|
-
parser.add_argument(
|
|
224
|
-
'--json',
|
|
225
|
-
action='store_true',
|
|
226
|
-
help='Output results as JSON'
|
|
227
|
-
)
|
|
228
|
-
parser.add_argument(
|
|
229
|
-
'--output', '-o',
|
|
230
|
-
help='Output file path'
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
args = parser.parse_args()
|
|
234
|
-
|
|
235
|
-
validator = ApiContractValidator(
|
|
236
|
-
args.target,
|
|
237
|
-
verbose=args.verbose
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
results = validator.run()
|
|
241
|
-
|
|
242
|
-
if args.json:
|
|
243
|
-
output = json.dumps(results, indent=2)
|
|
244
|
-
if args.output:
|
|
245
|
-
with open(args.output, 'w') as f:
|
|
246
|
-
f.write(output)
|
|
247
|
-
print(f"Results written to {args.output}")
|
|
248
|
-
else:
|
|
249
|
-
print(output)
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if __name__ == '__main__':
|
|
253
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
API Contract Validator
|
|
4
|
+
Validates API contracts (OpenAPI specs) and checks for best practices.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import json
|
|
10
|
+
import argparse
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ApiContractValidator:
|
|
16
|
+
"""Validates OpenAPI specs and API route files for best practices."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, target_path: str, verbose: bool = False):
|
|
19
|
+
self.target_path = Path(target_path)
|
|
20
|
+
self.verbose = verbose
|
|
21
|
+
self.results: Dict = {
|
|
22
|
+
'status': 'pending',
|
|
23
|
+
'target': str(self.target_path),
|
|
24
|
+
'findings': [],
|
|
25
|
+
'api_routes': [],
|
|
26
|
+
'score': 100,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
def run(self) -> Dict:
|
|
30
|
+
"""Execute the API contract validation."""
|
|
31
|
+
print(f"📋 Running API Contract Validator...")
|
|
32
|
+
print(f"📁 Target: {self.target_path}")
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
self.validate_target()
|
|
36
|
+
self.scan_api_routes()
|
|
37
|
+
self.check_openapi_spec()
|
|
38
|
+
self.check_validation_schemas()
|
|
39
|
+
self.check_error_handling()
|
|
40
|
+
self.check_authentication()
|
|
41
|
+
self.check_rate_limiting()
|
|
42
|
+
self.generate_report()
|
|
43
|
+
|
|
44
|
+
self.results['status'] = 'success'
|
|
45
|
+
print("✅ Validation completed!")
|
|
46
|
+
return self.results
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print(f"❌ Error: {e}")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
def validate_target(self):
|
|
53
|
+
"""Validate the target path exists."""
|
|
54
|
+
if not self.target_path.exists():
|
|
55
|
+
raise ValueError(f"Target path does not exist: {self.target_path}")
|
|
56
|
+
|
|
57
|
+
def scan_api_routes(self):
|
|
58
|
+
"""Scan for API route definitions."""
|
|
59
|
+
api_dirs = [
|
|
60
|
+
'app/api', 'src/routes', 'src/api', 'routes',
|
|
61
|
+
'internal/handler', 'src/handlers',
|
|
62
|
+
]
|
|
63
|
+
route_files = []
|
|
64
|
+
|
|
65
|
+
for api_dir in api_dirs:
|
|
66
|
+
dir_path = self.target_path / api_dir
|
|
67
|
+
if dir_path.is_dir():
|
|
68
|
+
for f in dir_path.rglob('*'):
|
|
69
|
+
if f.is_file() and f.suffix in {'.ts', '.tsx', '.js', '.py', '.go', '.rs'}:
|
|
70
|
+
route_files.append(str(f.relative_to(self.target_path)))
|
|
71
|
+
|
|
72
|
+
self.results['api_routes'] = route_files
|
|
73
|
+
if self.verbose:
|
|
74
|
+
print(f"✓ Found {len(route_files)} API route files")
|
|
75
|
+
|
|
76
|
+
if not route_files:
|
|
77
|
+
self._add_finding('info', 'No API routes found in standard directories')
|
|
78
|
+
|
|
79
|
+
def check_openapi_spec(self):
|
|
80
|
+
"""Check for OpenAPI specification file."""
|
|
81
|
+
spec_files = ['openapi.yaml', 'openapi.yml', 'openapi.json', 'swagger.yaml', 'swagger.json']
|
|
82
|
+
has_spec = any((self.target_path / f).exists() for f in spec_files)
|
|
83
|
+
|
|
84
|
+
# Also check in docs/ or api/ directories
|
|
85
|
+
for subdir in ['docs', 'api', 'spec']:
|
|
86
|
+
if (self.target_path / subdir).is_dir():
|
|
87
|
+
has_spec = has_spec or any(
|
|
88
|
+
(self.target_path / subdir / f).exists() for f in spec_files
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if not has_spec and self.results['api_routes']:
|
|
92
|
+
self._add_finding('warning', 'No OpenAPI spec found — consider spec-first API design')
|
|
93
|
+
self.results['score'] -= 10
|
|
94
|
+
|
|
95
|
+
def check_validation_schemas(self):
|
|
96
|
+
"""Check for input validation schemas (Zod, Pydantic, etc.)."""
|
|
97
|
+
validation_patterns = ['zod', 'pydantic', 'joi', 'yup', 'class-validator']
|
|
98
|
+
has_validation = False
|
|
99
|
+
|
|
100
|
+
pkg_json = self.target_path / 'package.json'
|
|
101
|
+
if pkg_json.exists():
|
|
102
|
+
try:
|
|
103
|
+
with open(pkg_json) as f:
|
|
104
|
+
pkg = json.load(f)
|
|
105
|
+
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
106
|
+
has_validation = any(v in deps for v in validation_patterns)
|
|
107
|
+
except (json.JSONDecodeError, IOError):
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
# Check Python
|
|
111
|
+
pyproject = self.target_path / 'pyproject.toml'
|
|
112
|
+
if pyproject.exists():
|
|
113
|
+
try:
|
|
114
|
+
content = pyproject.read_text()
|
|
115
|
+
has_validation = has_validation or 'pydantic' in content
|
|
116
|
+
except IOError:
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
if not has_validation and self.results['api_routes']:
|
|
120
|
+
self._add_finding('warning', 'No input validation library detected (Zod, Pydantic, etc.)')
|
|
121
|
+
self.results['score'] -= 15
|
|
122
|
+
|
|
123
|
+
def check_error_handling(self):
|
|
124
|
+
"""Check for structured error handling patterns."""
|
|
125
|
+
error_files = []
|
|
126
|
+
for pattern in ['**/error*.ts', '**/error*.py', '**/error*.go', '**/error*.rs']:
|
|
127
|
+
error_files.extend(self.target_path.glob(pattern))
|
|
128
|
+
|
|
129
|
+
if not error_files and self.results['api_routes']:
|
|
130
|
+
self._add_finding('info', 'No dedicated error handling module found — consider structured error responses (RFC 7807)')
|
|
131
|
+
|
|
132
|
+
def check_authentication(self):
|
|
133
|
+
"""Check for authentication middleware setup."""
|
|
134
|
+
auth_indicators = ['auth', 'jwt', 'clerk', 'nextauth', 'lucia', 'passport']
|
|
135
|
+
has_auth = False
|
|
136
|
+
|
|
137
|
+
pkg_json = self.target_path / 'package.json'
|
|
138
|
+
if pkg_json.exists():
|
|
139
|
+
try:
|
|
140
|
+
with open(pkg_json) as f:
|
|
141
|
+
pkg = json.load(f)
|
|
142
|
+
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
143
|
+
has_auth = any(
|
|
144
|
+
any(indicator in dep for indicator in auth_indicators)
|
|
145
|
+
for dep in deps
|
|
146
|
+
)
|
|
147
|
+
except (json.JSONDecodeError, IOError):
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
if not has_auth and self.results['api_routes']:
|
|
151
|
+
self._add_finding('info', 'No authentication library detected — ensure API endpoints are protected')
|
|
152
|
+
|
|
153
|
+
def check_rate_limiting(self):
|
|
154
|
+
"""Check for rate limiting setup."""
|
|
155
|
+
rate_limit_deps = ['rate-limit', 'ratelimit', 'limiter', 'throttle']
|
|
156
|
+
has_rate_limit = False
|
|
157
|
+
|
|
158
|
+
pkg_json = self.target_path / 'package.json'
|
|
159
|
+
if pkg_json.exists():
|
|
160
|
+
try:
|
|
161
|
+
with open(pkg_json) as f:
|
|
162
|
+
pkg = json.load(f)
|
|
163
|
+
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
|
164
|
+
has_rate_limit = any(
|
|
165
|
+
any(rl in dep for rl in rate_limit_deps)
|
|
166
|
+
for dep in deps
|
|
167
|
+
)
|
|
168
|
+
except (json.JSONDecodeError, IOError):
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
if not has_rate_limit and self.results['api_routes']:
|
|
172
|
+
self._add_finding('warning', 'No rate limiting detected — protect API endpoints from abuse')
|
|
173
|
+
self.results['score'] -= 5
|
|
174
|
+
|
|
175
|
+
def _add_finding(self, severity: str, message: str):
|
|
176
|
+
"""Add a finding to the results."""
|
|
177
|
+
self.results['findings'].append({
|
|
178
|
+
'severity': severity,
|
|
179
|
+
'message': message,
|
|
180
|
+
})
|
|
181
|
+
if self.verbose:
|
|
182
|
+
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(severity, '⚪')
|
|
183
|
+
print(f" {icon} [{severity.upper()}] {message}")
|
|
184
|
+
|
|
185
|
+
def generate_report(self):
|
|
186
|
+
"""Generate and display the validation report."""
|
|
187
|
+
findings = self.results['findings']
|
|
188
|
+
critical = len([f for f in findings if f['severity'] == 'critical'])
|
|
189
|
+
warnings = len([f for f in findings if f['severity'] == 'warning'])
|
|
190
|
+
info = len([f for f in findings if f['severity'] == 'info'])
|
|
191
|
+
|
|
192
|
+
score = max(0, self.results['score'])
|
|
193
|
+
self.results['score'] = score
|
|
194
|
+
|
|
195
|
+
print(f"\n{'='*60}")
|
|
196
|
+
print("📋 API CONTRACT VALIDATION REPORT")
|
|
197
|
+
print(f"{'='*60}")
|
|
198
|
+
print(f"Target: {self.results['target']}")
|
|
199
|
+
print(f"Score: {score}/100")
|
|
200
|
+
print(f"API Routes: {len(self.results['api_routes'])} files found")
|
|
201
|
+
print(f"Findings: {critical} critical, {warnings} warnings, {info} info")
|
|
202
|
+
print(f"\nFindings:")
|
|
203
|
+
for f in findings:
|
|
204
|
+
icon = {'critical': '🔴', 'warning': '🟡', 'info': '🔵'}.get(f['severity'], '⚪')
|
|
205
|
+
print(f" {icon} [{f['severity'].upper()}] {f['message']}")
|
|
206
|
+
print(f"{'='*60}\n")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def main():
|
|
210
|
+
"""Main entry point."""
|
|
211
|
+
parser = argparse.ArgumentParser(
|
|
212
|
+
description="API Contract Validator — validates API design best practices"
|
|
213
|
+
)
|
|
214
|
+
parser.add_argument(
|
|
215
|
+
'target',
|
|
216
|
+
help='Target project path to validate'
|
|
217
|
+
)
|
|
218
|
+
parser.add_argument(
|
|
219
|
+
'--verbose', '-v',
|
|
220
|
+
action='store_true',
|
|
221
|
+
help='Enable verbose output'
|
|
222
|
+
)
|
|
223
|
+
parser.add_argument(
|
|
224
|
+
'--json',
|
|
225
|
+
action='store_true',
|
|
226
|
+
help='Output results as JSON'
|
|
227
|
+
)
|
|
228
|
+
parser.add_argument(
|
|
229
|
+
'--output', '-o',
|
|
230
|
+
help='Output file path'
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
args = parser.parse_args()
|
|
234
|
+
|
|
235
|
+
validator = ApiContractValidator(
|
|
236
|
+
args.target,
|
|
237
|
+
verbose=args.verbose
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
results = validator.run()
|
|
241
|
+
|
|
242
|
+
if args.json:
|
|
243
|
+
output = json.dumps(results, indent=2)
|
|
244
|
+
if args.output:
|
|
245
|
+
with open(args.output, 'w') as f:
|
|
246
|
+
f.write(output)
|
|
247
|
+
print(f"Results written to {args.output}")
|
|
248
|
+
else:
|
|
249
|
+
print(output)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
if __name__ == '__main__':
|
|
253
|
+
main()
|