vibes-plug 1.0.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.
Files changed (141) hide show
  1. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  2. package/BLUEPRINT.md +125 -0
  3. package/CHANGELOG.md +195 -0
  4. package/CONTRIBUTING.md +199 -0
  5. package/LICENSE +21 -0
  6. package/README.md +263 -0
  7. package/SECURITY.md +21 -0
  8. package/banner.png +0 -0
  9. package/package.json +25 -0
  10. package/plugin.json +8 -0
  11. package/scripts/update_skills.js +75 -0
  12. package/skills/ai-llm-integration-expert/SKILL.md +162 -0
  13. package/skills/api-design-expert/SKILL.md +310 -0
  14. package/skills/app-analyzer-optimizer/SKILL.md +189 -0
  15. package/skills/asisten_ramah/SKILL.md +41 -0
  16. package/skills/authentication-identity-expert/SKILL.md +45 -0
  17. package/skills/auto-doc-updater/SKILL.md +204 -0
  18. package/skills/bootstrap-to-modern/SKILL.md +87 -0
  19. package/skills/brainstorming/SKILL.md +353 -0
  20. package/skills/bun-runtime-expert/SKILL.md +211 -0
  21. package/skills/ci-cd-devops-architect/SKILL.md +45 -0
  22. package/skills/cloud-hosting-expert/SKILL.md +244 -0
  23. package/skills/coderabbit/SKILL.md +192 -0
  24. package/skills/data-telemetry-expert/SKILL.md +213 -0
  25. package/skills/database-orm-expert/SKILL.md +294 -0
  26. package/skills/design-system-architect/SKILL.md +243 -0
  27. package/skills/e2e-testing-expert/SKILL.md +315 -0
  28. package/skills/event-driven-architect/SKILL.md +81 -0
  29. package/skills/firebase-security-expert/SKILL.md +195 -0
  30. package/skills/fullstack-expert/SKILL.md +202 -0
  31. package/skills/fullstack-expert/references/api_design_guide.md +466 -0
  32. package/skills/fullstack-expert/references/devops_infrastructure.md +477 -0
  33. package/skills/fullstack-expert/references/multi_language_backend.md +528 -0
  34. package/skills/fullstack-expert/references/system_design_patterns.md +358 -0
  35. package/skills/fullstack-expert/scripts/api_contract_validator.py +253 -0
  36. package/skills/fullstack-expert/scripts/architecture_analyzer.py +326 -0
  37. package/skills/gemini-agent-booster/SKILL.md +135 -0
  38. package/skills/global-a11y-i18n-expert/SKILL.md +81 -0
  39. package/skills/go-programming-expert/SKILL.md +295 -0
  40. package/skills/hig/SKILL.md +188 -0
  41. package/skills/js-backend-expert/SKILL.md +192 -0
  42. package/skills/mcp-server-architect/SKILL.md +194 -0
  43. package/skills/mobile-expo-expert/SKILL.md +186 -0
  44. package/skills/monday-design-aesthetic/SKILL.md +67 -0
  45. package/skills/monorepo-architect/SKILL.md +227 -0
  46. package/skills/mpa-orchestrator/SKILL.md +101 -0
  47. package/skills/multi-agent-orchestration/SKILL.md +234 -0
  48. package/skills/multiple-entry-points/SKILL.md +55 -0
  49. package/skills/mvc-expert/SKILL.md +231 -0
  50. package/skills/payment-gateway-expert/SKILL.md +45 -0
  51. package/skills/performance-web-vitals/SKILL.md +332 -0
  52. package/skills/prd-architect/SKILL.md +191 -0
  53. package/skills/production-ready-hardener/SKILL.md +469 -0
  54. package/skills/production-ready-hardener/references/performance_optimization.md +441 -0
  55. package/skills/production-ready-hardener/references/production_checklist.md +161 -0
  56. package/skills/production-ready-hardener/references/security_hardening_guide.md +379 -0
  57. package/skills/production-ready-hardener/scripts/production_readiness_scanner.py +875 -0
  58. package/skills/python-programming-expert/SKILL.md +271 -0
  59. package/skills/realtime-collaboration-expert/SKILL.md +45 -0
  60. package/skills/rust-programming-expert/SKILL.md +235 -0
  61. package/skills/saas-billing/SKILL.md +377 -0
  62. package/skills/saas-multi-tenant/SKILL.md +237 -0
  63. package/skills/saas-mvp-launcher/SKILL.md +231 -0
  64. package/skills/saas-transformer/SKILL.md +446 -0
  65. package/skills/saas-transformer/references/billing_integration_guide.md +401 -0
  66. package/skills/saas-transformer/references/feature_gating_patterns.md +137 -0
  67. package/skills/saas-transformer/references/saas_transformation_checklist.md +121 -0
  68. package/skills/saas-transformer/scripts/saas_transformation_scanner.py +254 -0
  69. package/skills/scalability-clean-code/SKILL.md +229 -0
  70. package/skills/secure-fuzz-testing/SKILL.md +201 -0
  71. package/skills/senior-frontend/SKILL.md +161 -0
  72. package/skills/senior-frontend/references/frontend_best_practices.md +806 -0
  73. package/skills/senior-frontend/references/nextjs_optimization_guide.md +724 -0
  74. package/skills/senior-frontend/references/react_patterns.md +746 -0
  75. package/skills/senior-frontend/scripts/bundle_analyzer.py +407 -0
  76. package/skills/senior-frontend/scripts/component_generator.py +329 -0
  77. package/skills/senior-frontend/scripts/frontend_scaffolder.py +1005 -0
  78. package/skills/senior-fullstack/SKILL.md +167 -0
  79. package/skills/senior-fullstack/references/architecture_patterns.md +160 -0
  80. package/skills/senior-fullstack/references/development_workflows.md +222 -0
  81. package/skills/senior-fullstack/references/tech_stack_guide.md +190 -0
  82. package/skills/senior-fullstack/scripts/code_quality_analyzer.py +114 -0
  83. package/skills/senior-fullstack/scripts/fullstack_scaffolder.py +114 -0
  84. package/skills/senior-fullstack/scripts/project_scaffolder.py +114 -0
  85. package/skills/seo/SKILL.md +225 -0
  86. package/skills/seo/references/cwv-thresholds.md +108 -0
  87. package/skills/seo/references/eeat-framework.md +214 -0
  88. package/skills/seo/references/quality-gates.md +155 -0
  89. package/skills/seo/references/schema-types.md +118 -0
  90. package/skills/seo-aeo-landing-page-writer/SKILL.md +97 -0
  91. package/skills/seo-geo/SKILL.md +188 -0
  92. package/skills/session-handoff-resume/SKILL.md +158 -0
  93. package/skills/skill_baru/SKILL.md +147 -0
  94. package/skills/spa-orchestrator/SKILL.md +288 -0
  95. package/skills/state-management-expert/SKILL.md +272 -0
  96. package/skills/supabase-migration/SKILL.md +45 -0
  97. package/skills/supabase-security-expert/SKILL.md +243 -0
  98. package/skills/tailwind-expert/SKILL.md +188 -0
  99. package/skills/tanstack-query-expert/SKILL.md +199 -0
  100. package/skills/tauri-expert/SKILL.md +97 -0
  101. package/skills/token-saver/SKILL.md +111 -0
  102. package/skills/typescript-expert/SKILL.md +279 -0
  103. package/skills/ui-components-expert/SKILL.md +63 -0
  104. package/skills/ui-ux-pro-max/SKILL.md +201 -0
  105. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  106. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  107. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  108. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  109. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  110. package/skills/ui-ux-pro-max/data/prompts.csv +24 -0
  111. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  112. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  113. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  114. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  115. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  116. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  117. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  118. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  119. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  120. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  121. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  122. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  123. package/skills/ui-ux-pro-max/data/styles.csv +59 -0
  124. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  125. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  126. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  127. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  128. package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-310.pyc +0 -0
  129. package/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
  130. package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-310.pyc +0 -0
  131. package/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
  132. package/skills/ui-ux-pro-max/scripts/core.py +257 -0
  133. package/skills/ui-ux-pro-max/scripts/design_system.py +493 -0
  134. package/skills/ui-ux-pro-max/scripts/search.py +81 -0
  135. package/skills/ui_ux_expert/SKILL.md +114 -0
  136. package/skills/vibe-code-gardener/SKILL.md +173 -0
  137. package/skills/web-scraper/SKILL.md +205 -0
  138. package/skills/web-scraper/references/data-transforms.md +397 -0
  139. package/skills/web-scraper/references/extraction-patterns.md +475 -0
  140. package/skills/web-scraper/references/output-templates.md +481 -0
  141. package/skills/zero-to-prod-orchestrator/SKILL.md +180 -0
@@ -0,0 +1,875 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Production Readiness Scanner
4
+ Automated scanner that runs all 7 phases of the Production-Ready Hardener
5
+ checklist against a target project.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import json
11
+ import argparse
12
+ import re
13
+ import subprocess
14
+ from pathlib import Path
15
+ from typing import Dict, List, Tuple
16
+
17
+
18
+ class ProductionReadinessScanner:
19
+ """Scans a project for production readiness across 7 phases."""
20
+
21
+ PHASE_WEIGHTS = {
22
+ 'architecture': 0.10,
23
+ 'frontend': 0.15,
24
+ 'backend': 0.15,
25
+ 'security': 0.25,
26
+ 'testing': 0.15,
27
+ 'performance': 0.10,
28
+ 'devops': 0.10,
29
+ }
30
+
31
+ def __init__(self, target_path: str, verbose: bool = False,
32
+ run_tsc: bool = False, run_lint: bool = False,
33
+ run_tests: bool = False, run_build: bool = False):
34
+ self.target_path = Path(target_path).resolve()
35
+ self.verbose = verbose
36
+ self.run_tsc_cmd = run_tsc
37
+ self.run_lint_cmd = run_lint
38
+ self.run_tests_cmd = run_tests
39
+ self.run_build_cmd = run_build
40
+
41
+ self.phases: Dict[str, Dict] = {}
42
+ self.overall_score = 0
43
+ self.stack = {}
44
+
45
+ # Parsed log results
46
+ self.parsed_tsc_errors = []
47
+ self.parsed_eslint_errors = []
48
+ self.build_errors = []
49
+
50
+ # Execution logs
51
+ self.execution_results = {}
52
+
53
+ def strip_ansi(self, text: str) -> str:
54
+ """Strip ANSI escape sequences from text."""
55
+ ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
56
+ return ansi_escape.sub('', text)
57
+
58
+ def _exec_command(self, cmd: str) -> Tuple[int, str]:
59
+ """Execute a shell command within the target path."""
60
+ try:
61
+ res = subprocess.run(
62
+ cmd,
63
+ shell=True,
64
+ cwd=str(self.target_path),
65
+ stdout=subprocess.PIPE,
66
+ stderr=subprocess.STDOUT,
67
+ text=True,
68
+ timeout=240 # 4 minutes timeout
69
+ )
70
+ return res.returncode, res.stdout
71
+ except subprocess.TimeoutExpired as e:
72
+ return -1, f"Command timed out. Output so far:\n{e.output}"
73
+ except Exception as e:
74
+ return -1, f"Execution failed: {str(e)}"
75
+
76
+ def _detect_stack(self):
77
+ """Analyze project files and package.json to identify tech stack."""
78
+ self.stack = {
79
+ 'framework': 'Unknown',
80
+ 'react': False,
81
+ 'react_version': None,
82
+ 'typescript': False,
83
+ 'vite': False,
84
+ 'next': False,
85
+ 'supabase': False,
86
+ 'firebase': False,
87
+ 'vitest': False,
88
+ 'eslint': False,
89
+ 'playwright': False,
90
+ 'pwa': False,
91
+ 'analytics': False,
92
+ }
93
+
94
+ # Check package.json
95
+ pkg_path = self.target_path / 'package.json'
96
+ if pkg_path.exists():
97
+ try:
98
+ pkg = json.loads(pkg_path.read_text(encoding='utf-8', errors='ignore'))
99
+ deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
100
+
101
+ self.stack['typescript'] = 'typescript' in deps
102
+ self.stack['vite'] = 'vite' in deps
103
+ self.stack['next'] = 'next' in deps
104
+ self.stack['supabase'] = '@supabase/supabase-js' in deps
105
+ self.stack['firebase'] = 'firebase' in deps or 'firebase-admin' in deps
106
+ self.stack['vitest'] = 'vitest' in deps
107
+ self.stack['eslint'] = 'eslint' in deps
108
+ self.stack['playwright'] = '@playwright/test' in deps
109
+ self.stack['pwa'] = 'vite-plugin-pwa' in deps
110
+
111
+ self.stack['analytics'] = any(
112
+ k in deps for k in ['@vercel/analytics', '@vercel/speed-insights', 'react-ga', 'mixpanel-browser']
113
+ )
114
+
115
+ if 'react' in deps:
116
+ self.stack['react'] = True
117
+ self.stack['react_version'] = deps['react']
118
+ if self.stack['next']:
119
+ self.stack['framework'] = 'Next.js'
120
+ elif self.stack['vite']:
121
+ self.stack['framework'] = 'Vite + React'
122
+ else:
123
+ self.stack['framework'] = 'React SPA'
124
+ except Exception:
125
+ pass
126
+
127
+ # Supplemental folder structure detection
128
+ if (self.target_path / 'supabase').is_dir():
129
+ self.stack['supabase'] = True
130
+ if (self.target_path / 'firestore.rules').exists():
131
+ self.stack['firebase'] = True
132
+
133
+ def _parse_logs(self):
134
+ """Parse pre-existing or dynamic diagnostic logs to find actual code problems."""
135
+ # 1. Parse TypeScript Compiler (tsc) errors
136
+ tsc_log_paths = ['tsc_errors.txt', 'tsc_errors.log', 'full_errors.txt']
137
+ for p in tsc_log_paths:
138
+ full_p = self.target_path / p
139
+ if full_p.exists():
140
+ try:
141
+ content = full_p.read_text(encoding='utf-8', errors='ignore')
142
+ self.parsed_tsc_errors = self._parse_tsc_content(content)
143
+ if self.parsed_tsc_errors:
144
+ break
145
+ except Exception:
146
+ pass
147
+
148
+ # 2. Parse ESLint errors
149
+ eslint_log_paths = ['eslint_output.txt', 'lint_output.txt', 'lint_warnings.txt']
150
+ for p in eslint_log_paths:
151
+ full_p = self.target_path / p
152
+ if full_p.exists():
153
+ try:
154
+ content = full_p.read_text(encoding='utf-8', errors='ignore')
155
+ self.parsed_eslint_errors = self._parse_eslint_content(content)
156
+ if self.parsed_eslint_errors:
157
+ break
158
+ except Exception:
159
+ pass
160
+
161
+ # 3. Parse build error logs
162
+ build_log_paths = ['build_err.txt', 'build_err2.txt', 'build_errors.txt', 'build_full_log.txt']
163
+ for p in build_log_paths:
164
+ full_p = self.target_path / p
165
+ if full_p.exists():
166
+ try:
167
+ content = full_p.read_text(encoding='utf-8', errors='ignore')
168
+ if "error" in content.lower() or "failed" in content.lower() or "exception" in content.lower():
169
+ self.build_errors.append({
170
+ 'source_file': p,
171
+ 'content': content[:800] # First 800 chars
172
+ })
173
+ except Exception:
174
+ pass
175
+
176
+ def _parse_tsc_content(self, content: str) -> List[Dict]:
177
+ """Convert tsc error console dump into structured lists."""
178
+ content = self.strip_ansi(content)
179
+ errors = []
180
+ lines = content.splitlines()
181
+ for line in lines:
182
+ line_str = line.strip()
183
+ # Match formats like: src/pages/Inventory.tsx(42,11): error TS6133: 'containerVariants'...
184
+ match = re.match(r'^([^(]+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)', line_str)
185
+ if match:
186
+ filepath = match.group(1).strip()
187
+ line_num = int(match.group(2))
188
+ col_num = int(match.group(3))
189
+ code = match.group(4)
190
+ message = match.group(5).strip()
191
+ errors.append({
192
+ 'file': filepath,
193
+ 'line': line_num,
194
+ 'col': col_num,
195
+ 'code': code,
196
+ 'message': message
197
+ })
198
+ else:
199
+ # Append sub-details to last error if applicable
200
+ if errors and (line_str.startswith("Property") or line_str.startswith("Type") or line_str.startswith("Types")):
201
+ errors[-1]['message'] += f"\n {line_str}"
202
+ return errors
203
+
204
+ def _parse_eslint_content(self, content: str) -> List[Dict]:
205
+ """Convert eslint console dump into structured lists."""
206
+ content = self.strip_ansi(content)
207
+ errors = []
208
+ current_file = None
209
+ lines = content.splitlines()
210
+ for line in lines:
211
+ line_str = line.strip()
212
+ if not line_str:
213
+ continue
214
+
215
+ # Check if this line is a filepath indicator (e.g. C:\kafealagi\src\components\core\Sidebar.tsx)
216
+ file_match = re.search(r'([a-zA-Z]:\\[^\s]+|[a-zA-Z]:/[^\s]+|[\w\-_\./\\]+\.(?:tsx?|jsx?|ts|js))', line_str)
217
+ if file_match and not any(kw in line_str for kw in ['problem', 'warning', 'error', 'eslint', 'package.json']):
218
+ path_cand = file_match.group(1)
219
+ try:
220
+ p = Path(path_cand)
221
+ if p.is_absolute():
222
+ current_file = str(p.relative_to(self.target_path))
223
+ else:
224
+ current_file = str(p)
225
+ except Exception:
226
+ current_file = path_cand
227
+ continue
228
+
229
+ # Match error details: "131:9 error React Hook ... react-hooks/rules-of-hooks"
230
+ error_match = re.search(r'^(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}([a-zA-Z0-9\-/]+)$', line_str)
231
+ if error_match:
232
+ line_num = int(error_match.group(1))
233
+ col_num = int(error_match.group(2))
234
+ sev = error_match.group(3)
235
+ msg = error_match.group(4).strip()
236
+ rule = error_match.group(5)
237
+ errors.append({
238
+ 'file': current_file or 'Unknown File',
239
+ 'line': line_num,
240
+ 'col': col_num,
241
+ 'severity': sev,
242
+ 'message': msg,
243
+ 'rule': rule
244
+ })
245
+ return errors
246
+
247
+ def _run_diagnostics(self):
248
+ """Optionally execute direct compiler, linter, test and build steps."""
249
+ if self.run_tsc_cmd:
250
+ print("⏳ Running TypeScript typecheck (tsc --noEmit)...")
251
+ ret, out = self._exec_command("npx tsc --noEmit")
252
+ self.execution_results['tsc'] = {'code': ret, 'output': out}
253
+ # Also parse these outputs
254
+ dyn_tsc = self._parse_tsc_content(out)
255
+ if dyn_tsc:
256
+ self.parsed_tsc_errors.extend(dyn_tsc)
257
+
258
+ if self.run_lint_cmd:
259
+ print("⏳ Running ESLint check...")
260
+ ret, out = self._exec_command("npx eslint . --format compact")
261
+ self.execution_results['lint'] = {'code': ret, 'output': out}
262
+ # Try to parse
263
+ dyn_eslint = []
264
+ for line in out.splitlines():
265
+ # eslint compact format: filename: line line_num, col col_num, Error - message (rule_id)
266
+ m = re.match(r'^([^:]+):\s*line\s*(\d+),\s*col\s*(\d+),\s*(Error|Warning)\s*-\s*(.+?)\s*\((.+)\)$', line.strip())
267
+ if m:
268
+ dyn_eslint.append({
269
+ 'file': m.group(1).strip(),
270
+ 'line': int(m.group(2)),
271
+ 'col': int(m.group(3)),
272
+ 'severity': m.group(4).lower(),
273
+ 'message': m.group(5).strip(),
274
+ 'rule': m.group(6)
275
+ })
276
+ if dyn_eslint:
277
+ self.parsed_eslint_errors.extend(dyn_eslint)
278
+
279
+ if self.run_tests_cmd:
280
+ print("⏳ Running unit tests...")
281
+ # Detect vitest
282
+ cmd = "npx vitest run" if self.stack['vitest'] else "npm test -- --watchAll=false"
283
+ ret, out = self._exec_command(cmd)
284
+ self.execution_results['tests'] = {'code': ret, 'output': out}
285
+
286
+ if self.run_build_cmd:
287
+ print("⏳ Testing application bundle build...")
288
+ ret, out = self._exec_command("npm run build")
289
+ self.execution_results['build'] = {'code': ret, 'output': out}
290
+ if ret != 0:
291
+ self.build_errors.append({
292
+ 'source_file': 'Dynamic Build Execution',
293
+ 'content': out[:1200]
294
+ })
295
+
296
+ def run(self) -> Dict:
297
+ """Execute the full production readiness scan."""
298
+ print("🛡️ Production Readiness Scanner")
299
+ print(f"📁 Target: {self.target_path}")
300
+ print("=" * 60)
301
+
302
+ if not self.target_path.exists():
303
+ print(f"❌ Target path does not exist: {self.target_path}")
304
+ sys.exit(1)
305
+
306
+ # Detect project stacks and load existing logs
307
+ self._detect_stack()
308
+ self._parse_logs()
309
+
310
+ # Run direct diagnostic executions if requested
311
+ self._run_diagnostics()
312
+
313
+ # Execute check phases
314
+ self._phase_1_architecture()
315
+ self._phase_2_frontend()
316
+ self._phase_3_backend()
317
+ self._phase_4_security()
318
+ self._phase_5_testing()
319
+ self._phase_6_performance()
320
+ self._phase_7_devops()
321
+
322
+ # Calculate overall score
323
+ self._calculate_overall_score()
324
+ self._generate_report()
325
+ self._write_markdown_report()
326
+
327
+ return {
328
+ 'target': str(self.target_path),
329
+ 'overall_score': self.overall_score,
330
+ 'stack': self.stack,
331
+ 'phases': self.phases,
332
+ }
333
+
334
+ def _init_phase(self, name: str, display_name: str) -> Dict:
335
+ """Initialize a phase result structure."""
336
+ phase = {
337
+ 'name': display_name,
338
+ 'score': 100,
339
+ 'checks': [],
340
+ 'critical': 0,
341
+ 'warnings': 0,
342
+ 'passed': 0,
343
+ }
344
+ self.phases[name] = phase
345
+ if self.verbose:
346
+ print(f"\n{'─' * 40}")
347
+ print(f"📋 Phase: {display_name}")
348
+ print(f"{'─' * 40}")
349
+ return phase
350
+
351
+ def _check(self, phase: Dict, name: str, passed: bool, severity: str = 'warning', penalty: int = 5):
352
+ """Record a check result."""
353
+ phase['checks'].append({
354
+ 'name': name,
355
+ 'passed': passed,
356
+ 'severity': severity,
357
+ })
358
+ if passed:
359
+ phase['passed'] += 1
360
+ if self.verbose:
361
+ print(f" ✅ {name}")
362
+ else:
363
+ if severity == 'critical':
364
+ phase['critical'] += 1
365
+ phase['score'] -= max(penalty, 15)
366
+ else:
367
+ phase['warnings'] += 1
368
+ phase['score'] -= penalty
369
+ phase['score'] = max(0, phase['score'])
370
+ icon = '🔴' if severity == 'critical' else '🟡'
371
+ if self.verbose:
372
+ print(f" {icon} {name}")
373
+
374
+ def _file_exists(self, *paths: str) -> bool:
375
+ """Check if any of the given file paths exist."""
376
+ return any((self.target_path / p).exists() for p in paths)
377
+
378
+ def _dir_exists(self, *paths: str) -> bool:
379
+ """Check if any of the given directory paths exist."""
380
+ return any((self.target_path / p).is_dir() for p in paths)
381
+
382
+ def _file_contains(self, filepath: str, pattern: str) -> bool:
383
+ """Check if a file contains a pattern."""
384
+ fp = self.target_path / filepath
385
+ if not fp.exists():
386
+ return False
387
+ try:
388
+ return pattern in fp.read_text(errors='ignore')
389
+ except (IOError, OSError):
390
+ return False
391
+
392
+ def _grep_project(self, pattern: str, extensions: List[str] = None) -> List[str]:
393
+ """Search for a pattern in project files."""
394
+ matches = []
395
+ exts = extensions or ['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs']
396
+ for ext in exts:
397
+ for f in self.target_path.rglob(f'*{ext}'):
398
+ if any(x in str(f) for x in ['node_modules', '.next', 'dist', '.git', 'playwright-report', 'test-results']):
399
+ continue
400
+ try:
401
+ content = f.read_text(errors='ignore')
402
+ if re.search(pattern, content):
403
+ matches.append(str(f.relative_to(self.target_path)))
404
+ except (IOError, OSError):
405
+ continue
406
+ return matches
407
+
408
+ # ─── PHASE 1: Architecture & Code Quality ───
409
+
410
+ def _phase_1_architecture(self):
411
+ phase = self._init_phase('architecture', 'Architecture & Code Quality')
412
+
413
+ # Structure
414
+ self._check(phase, 'Has organized source directory (src/ or app/)',
415
+ self._dir_exists('src', 'app'))
416
+
417
+ # TS Setup
418
+ has_tsconfig = self._file_exists('tsconfig.json')
419
+ strict_ts = self._file_contains('tsconfig.json', '"strict": true') or self._file_contains('tsconfig.json', '"strict":true')
420
+
421
+ # Also check tsconfig.app.json etc
422
+ if not strict_ts and self._file_exists('tsconfig.app.json'):
423
+ strict_ts = self._file_contains('tsconfig.app.json', '"strict": true') or self._file_contains('tsconfig.app.json', '"strict":true')
424
+
425
+ self._check(phase, 'TypeScript strict mode enabled', strict_ts or not self.stack['typescript'])
426
+ self._check(phase, 'package.json exists', self._file_exists('package.json'))
427
+
428
+ # Any types
429
+ any_matches = self._grep_project(r': any\b|as any\b', ['.ts', '.tsx'])
430
+ self._check(phase, 'No TypeScript `any` types found',
431
+ len(any_matches) == 0, penalty=3)
432
+
433
+ self._check(phase, '.env.example/env.example documents required config variables',
434
+ self._file_exists('.env.example', 'env.example', '.env.template'))
435
+
436
+ # Input validation
437
+ has_val = self.stack['react'] and self._file_exists('package.json')
438
+ if has_val:
439
+ try:
440
+ pkg = json.loads((self.target_path / 'package.json').read_text(encoding='utf-8', errors='ignore'))
441
+ deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
442
+ has_val = any(v in deps for v in ['zod', 'joi', 'yup', 'class-validator'])
443
+ except Exception:
444
+ has_val = False
445
+ self._check(phase, 'Input validation library used (Zod/Joi/Yup)', has_val)
446
+
447
+ # TypeScript Compiler Check
448
+ tsc_ok = len(self.parsed_tsc_errors) == 0
449
+ penalty_val = min(5 * len(self.parsed_tsc_errors), 45)
450
+ self._check(phase, f'TypeScript compiles without errors ({len(self.parsed_tsc_errors)} errors)',
451
+ tsc_ok, severity='warning', penalty=penalty_val)
452
+
453
+ # ─── PHASE 2: Frontend Hardening ───
454
+
455
+ def _phase_2_frontend(self):
456
+ phase = self._init_phase('frontend', 'Frontend Hardening')
457
+
458
+ has_fe = self.stack['react'] or self._file_exists('vite.config.ts', 'next.config.js')
459
+ self._check(phase, 'Frontend framework detected', has_fe)
460
+
461
+ if not has_fe:
462
+ return
463
+
464
+ # Error Boundary
465
+ error_files = self._grep_project(r'error\.(tsx?|jsx?)$|ErrorBoundary|react-error-boundary', ['.ts', '.tsx', '.js', '.jsx'])
466
+ self._check(phase, 'Error boundaries implemented', len(error_files) > 0)
467
+
468
+ # Suspense / Loading
469
+ loading_files = self._grep_project(r'loading\.(tsx?|jsx?)$|Suspense|Skeleton', ['.ts', '.tsx', '.js', '.jsx'])
470
+ self._check(phase, 'Loading skeletons or Suspense implemented', len(loading_files) > 0)
471
+
472
+ # React 19 rules / ESLint Problems
473
+ eslint_ok = len(self.parsed_eslint_errors) == 0
474
+ penalty_val = min(4 * len(self.parsed_eslint_errors), 30)
475
+ self._check(phase, f'ESLint passes with zero errors ({len(self.parsed_eslint_errors)} warnings/errors)',
476
+ eslint_ok, severity='warning', penalty=penalty_val)
477
+
478
+ # Check React Hook calls (hook violations)
479
+ hook_violations = [e for e in self.parsed_eslint_errors if 'rules-of-hooks' in e.get('rule', '')]
480
+ self._check(phase, 'No React Hook rule violations detected',
481
+ len(hook_violations) == 0, severity='critical', penalty=15)
482
+
483
+ # ─── PHASE 3: Backend Hardening ───
484
+
485
+ def _phase_3_backend(self):
486
+ phase = self._init_phase('backend', 'Backend Hardening')
487
+
488
+ # Database migrations
489
+ has_mig = self._dir_exists('supabase/migrations', 'migrations', 'prisma/migrations', 'drizzle')
490
+ self._check(phase, 'Database migrations directory setup', has_mig)
491
+
492
+ # Health endpoint / check
493
+ health_check = self._file_exists('public/robots.txt') # placeholder or real
494
+ self._check(phase, 'Public routing endpoints ready', health_check)
495
+
496
+ # Supabase specific checks
497
+ if self.stack['supabase']:
498
+ has_sb_types = self._file_exists('src/types/supabase.ts') or self._file_exists('src/lib/supabase.ts')
499
+ self._check(phase, 'Supabase TypeScript schema types generated (supabase.ts)', has_sb_types)
500
+
501
+ # Check local supabase config
502
+ has_sb_config = self._file_exists('supabase/config.toml')
503
+ self._check(phase, 'Supabase config.toml set up', has_sb_config, penalty=3)
504
+
505
+ # ─── PHASE 4: Security Hardening ───
506
+
507
+ def _phase_4_security(self):
508
+ phase = self._init_phase('security', 'Security Hardening')
509
+
510
+ # .gitignore env
511
+ gi = self.target_path / '.gitignore'
512
+ env_ig = False
513
+ if gi.exists():
514
+ content = gi.read_text(encoding='utf-8', errors='ignore')
515
+ env_ig = '.env' in content
516
+ self._check(phase, '.env included in .gitignore', env_ig, severity='critical', penalty=20)
517
+
518
+ # Check raw env files for secrets
519
+ secrets_exposed = False
520
+ env_files = ['.env', '.env.local', '.env.production']
521
+ for ef in env_files:
522
+ ef_path = self.target_path / ef
523
+ if ef_path.exists():
524
+ # Just flag if .env is tracked in git (which gitignore audit checks)
525
+ pass
526
+
527
+ # Check for hardcoded private keys/secrets in files
528
+ matches = self._grep_project(r'sk_live_[a-zA-Z0-9]{20,}|sk_test_[a-zA-Z0-9]{20,}|service_role.*["\'][a-zA-Z0-9_]{30,}', ['.ts', '.tsx', '.js', '.py'])
529
+ self._check(phase, 'No hardcoded private API keys/secrets in project files', len(matches) == 0, severity='critical', penalty=25)
530
+
531
+ # SQL Injection / parameterized queries check
532
+ raw_sqli = self._grep_project(r'\.query\(\s*`[^\$]*\$\{', ['.ts', '.js'])
533
+ self._check(phase, 'No vulnerable string-interpolated SQL queries found', len(raw_sqli) == 0, severity='critical', penalty=20)
534
+
535
+ # CORS Check
536
+ cors_wildcard = self._grep_project(r'cors:\s*["\']\*["\']|Access-Control-Allow-Origin.*\*', ['.ts', '.js'])
537
+ self._check(phase, 'No unrestricted CORS wildcard access allowed in source', len(cors_wildcard) == 0, severity='warning', penalty=10)
538
+
539
+ # ─── PHASE 5: Testing & QA ───
540
+
541
+ def _phase_5_testing(self):
542
+ phase = self._init_phase('testing', 'Testing & Quality Assurance')
543
+
544
+ # Unit tests
545
+ has_ut = self._file_exists('vitest.config.ts', 'jest.config.js') or self._dir_exists('tests', '__tests__')
546
+ self._check(phase, 'Unit test suite configured (Vitest/Jest)', has_ut)
547
+
548
+ # E2E test
549
+ has_e2e = self._file_exists('playwright.config.ts') or self._dir_exists('e2e')
550
+ self._check(phase, 'E2E test suite configured (Playwright)', has_e2e)
551
+
552
+ # Eslint config file
553
+ has_eslint_cfg = self._file_exists('eslint.config.js', '.eslintrc.js', '.eslintrc.json')
554
+ self._check(phase, 'Linter configuration exists (eslint.config.js)', has_eslint_cfg)
555
+
556
+ # Pre-commit config
557
+ has_hooks = self._file_exists('.husky/pre-commit', '.pre-commit-config.yaml')
558
+ self._check(phase, 'Git hooks config present', has_hooks, penalty=3)
559
+
560
+ # ─── PHASE 6: Performance & SEO ───
561
+
562
+ def _phase_6_performance(self):
563
+ phase = self._init_phase('performance', 'Performance & SEO')
564
+
565
+ # Sitemap
566
+ has_sitemap = self._file_exists('public/sitemap.xml') or self._file_exists('src/sitemap.ts')
567
+ self._check(phase, 'Sitemap exists or generated', has_sitemap, penalty=3)
568
+
569
+ # robots.txt
570
+ self._check(phase, 'robots.txt exists in public directory', self._file_exists('public/robots.txt'))
571
+
572
+ # Analytics
573
+ self._check(phase, 'User speed & web vitals tracking integrated', self.stack['analytics'], penalty=3)
574
+
575
+ # PWA
576
+ self._check(phase, 'Progressive Web App (PWA) configuration present', self.stack['pwa'], penalty=2)
577
+
578
+ # ─── PHASE 7: DevOps & Deployment ───
579
+
580
+ def _phase_7_devops(self):
581
+ phase = self._init_phase('devops', 'DevOps & Deployment')
582
+
583
+ # GitHub Workflows
584
+ has_ci = self._dir_exists('.github/workflows')
585
+ self._check(phase, 'GitHub Actions CI/CD workflows setup', has_ci)
586
+
587
+ # Vercel or hosting configuration
588
+ has_deploy_config = self._file_exists('vercel.json', 'netlify.toml', 'Dockerfile')
589
+ self._check(phase, 'Deployment configuration file exists', has_deploy_config)
590
+
591
+ # Documentation completeness
592
+ self._check(phase, 'README.md contains instructions', self._file_exists('README.md'))
593
+ self._check(phase, 'CHANGELOG.md updated for release', self._file_exists('CHANGELOG.md'), penalty=2)
594
+
595
+ # Build errors penalty
596
+ has_build_errs = len(self.build_errors) > 0
597
+ self._check(phase, 'Vite/Compiler build executes successfully (No build_errors.txt)',
598
+ not has_build_errs, severity='critical', penalty=50)
599
+
600
+ # ─── Scoring & Reporting ───
601
+
602
+ def _calculate_overall_score(self):
603
+ """Calculate weighted overall score."""
604
+ total = 0
605
+ for phase_key, weight in self.PHASE_WEIGHTS.items():
606
+ if phase_key in self.phases:
607
+ total += self.phases[phase_key]['score'] * weight
608
+ self.overall_score = round(total)
609
+
610
+ def _get_grade(self, score: int) -> str:
611
+ """Convert score to letter grade."""
612
+ if score >= 95: return 'A+'
613
+ elif score >= 90: return 'A'
614
+ elif score >= 85: return 'B+'
615
+ elif score >= 80: return 'B'
616
+ elif score >= 70: return 'C'
617
+ elif score >= 60: return 'D'
618
+ else: return 'F'
619
+
620
+ def _generate_report(self):
621
+ """Print the final production readiness report to console."""
622
+ grade = self._get_grade(self.overall_score)
623
+ total_critical = sum(p['critical'] for p in self.phases.values())
624
+ total_warnings = sum(p['warnings'] for p in self.phases.values())
625
+ total_passed = sum(p['passed'] for p in self.phases.values())
626
+
627
+ print(f"\n{'=' * 60}")
628
+ print("🛡️ PRODUCTION READINESS REPORT")
629
+ print(f"{'=' * 60}")
630
+ print(f"\n Overall Score: {self.overall_score}/100 ({grade})")
631
+ print(f" Passed: {total_passed} | Warnings: {total_warnings} | Critical: {total_critical}")
632
+
633
+ if self.overall_score >= 95:
634
+ print(" Status: ✅ PRODUCTION READY")
635
+ elif self.overall_score >= 80:
636
+ print(" Status: ⚠️ NEEDS MINOR FIXES")
637
+ elif self.overall_score >= 60:
638
+ print(" Status: 🟡 SIGNIFICANT ISSUES")
639
+ else:
640
+ print(" Status: 🔴 NOT PRODUCTION READY")
641
+
642
+ print(f"\n{'─' * 60}")
643
+ print(" Phase Breakdown:")
644
+ print(f"{'─' * 60}")
645
+ print(f" {'Phase':<35} {'Score':>6} {'Critical':>9} {'Warnings':>9}")
646
+ print(f" {'─' * 59}")
647
+
648
+ for phase_key in self.PHASE_WEIGHTS:
649
+ if phase_key in self.phases:
650
+ p = self.phases[phase_key]
651
+ icon = '✅' if p['score'] >= 90 else ('🟡' if p['score'] >= 70 else '🔴')
652
+ print(f" {icon} {p['name']:<32} {p['score']:>4}/100 {p['critical']:>6} {p['warnings']:>6}")
653
+
654
+ # List critical issues
655
+ critical_issues = []
656
+ for phase_key, phase in self.phases.items():
657
+ for check in phase['checks']:
658
+ if not check['passed'] and check['severity'] == 'critical':
659
+ critical_issues.append((phase['name'], check['name']))
660
+
661
+ if critical_issues:
662
+ print(f"\n{'─' * 60}")
663
+ print(" 🔴 CRITICAL ISSUES (Must Fix Before Production):")
664
+ print(f"{'─' * 60}")
665
+ for phase_name, issue in critical_issues:
666
+ print(f" [{phase_name}] {issue}")
667
+
668
+ print(f"\n{'=' * 60}\n")
669
+
670
+ def _write_markdown_report(self):
671
+ """Generate and save PRODUCTION_READINESS_REPORT.md in target project."""
672
+ report_path = self.target_path / 'PRODUCTION_READINESS_REPORT.md'
673
+ grade = self._get_grade(self.overall_score)
674
+
675
+ status_text = "✅ PRODUCTION READY" if self.overall_score >= 95 else \
676
+ ("⚠️ NEEDS MINOR FIXES" if self.overall_score >= 80 else \
677
+ ("🟡 SIGNIFICANT ISSUES" if self.overall_score >= 60 else "🔴 NOT PRODUCTION READY"))
678
+
679
+ md = []
680
+ md.append(f"# Production Readiness Report")
681
+ md.append(f"**Target:** `{self.target_path}` ")
682
+ md.append(f"**Date Check:** {os.environ.get('DATE_STRING', 'Current Date')} ")
683
+ md.append(f"**Overall Score:** `{self.overall_score}/100` (`{grade}`) ")
684
+ md.append(f"**Status:** {status_text}\n")
685
+
686
+ md.append("## Executive Summary")
687
+ md.append("This report lists potential security vulnerabilities, code architecture errors, test coverage status, and build checks before release. Critical errors must be addressed immediately.")
688
+
689
+ # Phase score table
690
+ md.append("## Phase Breakdown\n")
691
+ md.append("| Phase | Score | Critical | Warnings |")
692
+ md.append("|:---|:---:|:---:|:---:|")
693
+ for phase_key in self.PHASE_WEIGHTS:
694
+ if phase_key in self.phases:
695
+ p = self.phases[phase_key]
696
+ md.append(f"| {p['name']} | `{p['score']}/100` | {p['critical']} | {p['warnings']} |")
697
+ md.append("")
698
+
699
+ # Helper to format clickable links
700
+ def make_link(filepath: str, line: int = None) -> str:
701
+ # Normalize path
702
+ fp_norm = filepath.replace('\\', '/')
703
+ full_abs = (self.target_path / fp_norm).resolve().as_posix()
704
+ link_text = f"{filepath}:{line}" if line else filepath
705
+ href = f"file:///{full_abs}"
706
+ if line:
707
+ href += f"#L{line}"
708
+ return f"[{link_text}]({href})"
709
+
710
+ # Dynamic Log results
711
+ if self.parsed_tsc_errors:
712
+ md.append("## 🔴 TypeScript Compiler Errors")
713
+ md.append(f"The following **{len(self.parsed_tsc_errors)} TypeScript errors** were parsed from logs or dynamic execution. Resolving these is highly recommended to prevent production runtime errors.\n")
714
+ md.append("| File & Line | Code | Message |")
715
+ md.append("|:---|:---:|:---|")
716
+ for err in self.parsed_tsc_errors[:30]: # Limit top 30
717
+ md.append(f"| {make_link(err['file'], err['line'])} | `TS{err['code']}` | `{err['message'].replace('|', 'I')}` |")
718
+ if len(self.parsed_tsc_errors) > 30:
719
+ md.append(f"| *and {len(self.parsed_tsc_errors) - 30} more errors...* | | |")
720
+ md.append("\n")
721
+
722
+ if self.parsed_eslint_errors:
723
+ md.append("## 🟡 ESLint Warnings & Errors")
724
+ md.append(f"Detected **{len(self.parsed_eslint_errors)} problems** in coding rules and hook order requirements.\n")
725
+ md.append("| File & Line | Severity | Message | Rule |")
726
+ md.append("|:---|:---:|:---|:---:|")
727
+ for err in self.parsed_eslint_errors[:30]:
728
+ sev_icon = "🔴 error" if err['severity'].lower() == 'error' else "🟡 warning"
729
+ md.append(f"| {make_link(err['file'], err['line'])} | {sev_icon} | `{err['message'].replace('|', 'I')}` | `{err['rule']}` |")
730
+ if len(self.parsed_eslint_errors) > 30:
731
+ md.append(f"| *and {len(self.parsed_eslint_errors) - 30} more warnings...* | | | |")
732
+ md.append("\n")
733
+
734
+ if self.build_errors:
735
+ md.append("## 🔴 Vite / Compiler Build Failures")
736
+ md.append("Application bundle builds have failed. You must repair these prior to release.\n")
737
+ for err in self.build_errors:
738
+ md.append(f"### Log Source: {err['source_file']}")
739
+ md.append("```text")
740
+ md.append(err['content'])
741
+ md.append("```\n")
742
+
743
+ # Specific checklist results
744
+ md.append("## Detailed Checklist Items\n")
745
+ for phase_key, p in self.phases.items():
746
+ md.append(f"### {p['name']}")
747
+ for check in p['checks']:
748
+ status = "✅ Passed" if check['passed'] else f"❌ Failed ({check['severity'].upper()})"
749
+ md.append(f"- **{check['name']}**: {status}")
750
+ md.append("")
751
+
752
+ # Remediation section
753
+ md.append("## 🚀 Quick Remediation Checklist")
754
+ md.append("Below are instructions to address the most urgent failures detected:\n")
755
+
756
+ remediation_items = 0
757
+
758
+ # Check hooks called conditionally
759
+ hook_errs = [e for e in self.parsed_eslint_errors if 'rules-of-hooks' in e.get('rule', '')]
760
+ if hook_errs:
761
+ remediation_items += 1
762
+ md.append(f"### 1. Fix React Hook Rules in {make_link(hook_errs[0]['file'], hook_errs[0]['line'])}")
763
+ md.append("React Hooks cannot be called conditionally. Ensure `usePWAInstall` or `useState` calls are moved to the top level of component rendering.")
764
+ md.append("```typescript")
765
+ md.append("// Move inside component but BEFORE any conditional returns or if-blocks:")
766
+ md.append("const installState = usePWAInstall();")
767
+ md.append("```\n")
768
+
769
+ # Check motion variants assignment
770
+ motion_errs = [e for e in self.parsed_tsc_errors if 'Variants' in e['message'] and 'BusinessAnalysisTab' in e['file']]
771
+ if motion_errs:
772
+ remediation_items += 1
773
+ md.append(f"### 2. Fix Framer Motion Types in {make_link(motion_errs[0]['file'], motion_errs[0]['line'])}")
774
+ md.append("In Framer Motion variants, setting `type` as generic string causes type assignment failures on type `Variants`. Set it as a string literal or cast it:")
775
+ md.append("```typescript")
776
+ md.append("// Change from:")
777
+ md.append("transition: { type: 'spring', ... }")
778
+ md.append("// To literal type casting or let variants behave automatically:")
779
+ md.append("transition: { type: 'spring' as const, ... }")
780
+ md.append("```\n")
781
+
782
+ if self.stack['supabase'] and not self._file_exists('src/types/supabase.ts'):
783
+ remediation_items += 1
784
+ md.append("### 3. Generate Supabase Schema Types")
785
+ md.append("Execute the supabase schema type exporter script:")
786
+ md.append("```bash")
787
+ md.append("npm run supabase:types")
788
+ md.append("```\n")
789
+
790
+ if not remediation_items:
791
+ md.append("- No immediate high-priority remediation templates needed. Fix individual warnings listed above.")
792
+
793
+ # Save MD
794
+ try:
795
+ report_path.write_text("\n".join(md), encoding='utf-8')
796
+ print(f"📝 Markdown report generated and saved: {report_path}")
797
+ except Exception as e:
798
+ print(f"❌ Failed to write markdown report: {str(e)}")
799
+
800
+
801
+ def main():
802
+ """Main entry point."""
803
+ if sys.platform.startswith('win'):
804
+ try:
805
+ sys.stdout.reconfigure(encoding='utf-8')
806
+ sys.stderr.reconfigure(encoding='utf-8')
807
+ except Exception:
808
+ pass
809
+ parser = argparse.ArgumentParser(
810
+ description="Production Readiness Scanner — comprehensive pre-production audit"
811
+ )
812
+ parser.add_argument(
813
+ 'target',
814
+ help='Target project path to scan'
815
+ )
816
+ parser.add_argument(
817
+ '--verbose', '-v',
818
+ action='store_true',
819
+ help='Enable verbose output (show all checks)'
820
+ )
821
+ parser.add_argument(
822
+ '--json',
823
+ action='store_true',
824
+ help='Output results as JSON'
825
+ )
826
+ parser.add_argument(
827
+ '--output', '-o',
828
+ help='Output file path'
829
+ )
830
+ parser.add_argument(
831
+ '--run-tsc',
832
+ action='store_true',
833
+ help='Execute typescript compiler typechecking'
834
+ )
835
+ parser.add_argument(
836
+ '--run-lint',
837
+ action='store_true',
838
+ help='Execute eslint check'
839
+ )
840
+ parser.add_argument(
841
+ '--run-tests',
842
+ action='store_true',
843
+ help='Execute unit tests'
844
+ )
845
+ parser.add_argument(
846
+ '--run-build',
847
+ action='store_true',
848
+ help='Execute vite build compilation test'
849
+ )
850
+
851
+ args = parser.parse_args()
852
+
853
+ scanner = ProductionReadinessScanner(
854
+ args.target,
855
+ verbose=args.verbose,
856
+ run_tsc=args.run_tsc,
857
+ run_lint=args.run_lint,
858
+ run_tests=args.run_tests,
859
+ run_build=args.run_build
860
+ )
861
+
862
+ results = scanner.run()
863
+
864
+ if args.json:
865
+ output = json.dumps(results, indent=2, default=str)
866
+ if args.output:
867
+ with open(args.output, 'w', encoding='utf-8') as f:
868
+ f.write(output)
869
+ print(f"Results written to {args.output}")
870
+ else:
871
+ print(output)
872
+
873
+
874
+ if __name__ == '__main__':
875
+ main()