dash-devtools 1.0.0__py3-none-any.whl

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 (53) hide show
  1. dash_devtools/__init__.py +8 -0
  2. dash_devtools/__main__.py +11 -0
  3. dash_devtools/ai_engine.py +441 -0
  4. dash_devtools/browser.py +541 -0
  5. dash_devtools/cli.py +1452 -0
  6. dash_devtools/database.py +338 -0
  7. dash_devtools/dbdiagram.py +183 -0
  8. dash_devtools/e2e.py +329 -0
  9. dash_devtools/fixers/__init__.py +57 -0
  10. dash_devtools/fixers/migration_fixer.py +115 -0
  11. dash_devtools/fixers/ux_fixer.py +106 -0
  12. dash_devtools/fixers/version_bumper.py +115 -0
  13. dash_devtools/gas_mes_test.py +1241 -0
  14. dash_devtools/generators/__init__.py +84 -0
  15. dash_devtools/health.py +476 -0
  16. dash_devtools/hooks/__init__.py +250 -0
  17. dash_devtools/hooks/pre_commit.py +161 -0
  18. dash_devtools/hooks/pre_push.py +275 -0
  19. dash_devtools/init_test.py +352 -0
  20. dash_devtools/markdown_report.py +309 -0
  21. dash_devtools/migrators/__init__.py +21 -0
  22. dash_devtools/perf.py +321 -0
  23. dash_devtools/report.py +667 -0
  24. dash_devtools/reporters/__init__.py +11 -0
  25. dash_devtools/spec.py +230 -0
  26. dash_devtools/stats.py +355 -0
  27. dash_devtools/test_suite.py +690 -0
  28. dash_devtools/testing.py +416 -0
  29. dash_devtools/validators/__init__.py +157 -0
  30. dash_devtools/validators/backend/__init__.py +12 -0
  31. dash_devtools/validators/backend/nodejs.py +245 -0
  32. dash_devtools/validators/backend/python.py +439 -0
  33. dash_devtools/validators/code_quality.py +243 -0
  34. dash_devtools/validators/common/__init__.py +11 -0
  35. dash_devtools/validators/common/quality.py +319 -0
  36. dash_devtools/validators/common/security.py +270 -0
  37. dash_devtools/validators/common/spec.py +273 -0
  38. dash_devtools/validators/detector.py +394 -0
  39. dash_devtools/validators/frontend/__init__.py +14 -0
  40. dash_devtools/validators/frontend/angular.py +245 -0
  41. dash_devtools/validators/frontend/gas.py +310 -0
  42. dash_devtools/validators/frontend/vite.py +539 -0
  43. dash_devtools/validators/migration.py +292 -0
  44. dash_devtools/validators/performance.py +167 -0
  45. dash_devtools/validators/security.py +205 -0
  46. dash_devtools/vision/__init__.py +368 -0
  47. dash_devtools/watch.py +266 -0
  48. dash_devtools/word_report.py +690 -0
  49. dash_devtools-1.0.0.dist-info/METADATA +834 -0
  50. dash_devtools-1.0.0.dist-info/RECORD +53 -0
  51. dash_devtools-1.0.0.dist-info/WHEEL +5 -0
  52. dash_devtools-1.0.0.dist-info/entry_points.txt +2 -0
  53. dash_devtools-1.0.0.dist-info/top_level.txt +1 -0
dash_devtools/perf.py ADDED
@@ -0,0 +1,321 @@
1
+ """
2
+ 效能測試模組
3
+ 使用 Lighthouse 進行網站效能分析
4
+ """
5
+
6
+ import subprocess
7
+ import json
8
+ import tempfile
9
+ from pathlib import Path
10
+ from typing import Dict, List, Optional
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+ from rich.panel import Panel
14
+ from rich.progress import Progress, SpinnerColumn, TextColumn
15
+
16
+ console = Console()
17
+
18
+ # Lighthouse Node.js 腳本
19
+ LIGHTHOUSE_SCRIPT = '''
20
+ const { execSync } = require("child_process");
21
+
22
+ const url = process.argv[2];
23
+ const categories = process.argv[3] || "performance,accessibility,best-practices,seo";
24
+
25
+ try {
26
+ // 使用 lighthouse CLI
27
+ const result = execSync(
28
+ `npx lighthouse "${url}" --output=json --quiet --chrome-flags="--headless --no-sandbox" --only-categories=${categories}`,
29
+ {
30
+ encoding: "utf-8",
31
+ timeout: 120000,
32
+ maxBuffer: 10 * 1024 * 1024
33
+ }
34
+ );
35
+
36
+ const report = JSON.parse(result);
37
+
38
+ // 擷取關鍵數據
39
+ const output = {
40
+ url: url,
41
+ success: true,
42
+ scores: {
43
+ performance: Math.round((report.categories.performance?.score || 0) * 100),
44
+ accessibility: Math.round((report.categories.accessibility?.score || 0) * 100),
45
+ bestPractices: Math.round((report.categories["best-practices"]?.score || 0) * 100),
46
+ seo: Math.round((report.categories.seo?.score || 0) * 100)
47
+ },
48
+ metrics: {
49
+ fcp: report.audits["first-contentful-paint"]?.numericValue || 0,
50
+ lcp: report.audits["largest-contentful-paint"]?.numericValue || 0,
51
+ tbt: report.audits["total-blocking-time"]?.numericValue || 0,
52
+ cls: report.audits["cumulative-layout-shift"]?.numericValue || 0,
53
+ si: report.audits["speed-index"]?.numericValue || 0,
54
+ tti: report.audits["interactive"]?.numericValue || 0
55
+ },
56
+ opportunities: [],
57
+ diagnostics: []
58
+ };
59
+
60
+ // 擷取改善建議 (有節省時間的項目)
61
+ for (const [id, audit] of Object.entries(report.audits)) {
62
+ if (audit.details?.overallSavingsMs > 100) {
63
+ output.opportunities.push({
64
+ id: id,
65
+ title: audit.title,
66
+ savings: Math.round(audit.details.overallSavingsMs),
67
+ description: audit.description?.substring(0, 150)
68
+ });
69
+ }
70
+ }
71
+
72
+ // 排序: 節省時間最多的優先
73
+ output.opportunities.sort((a, b) => b.savings - a.savings);
74
+ output.opportunities = output.opportunities.slice(0, 5);
75
+
76
+ // 擷取診斷資訊
77
+ const diagnosticIds = [
78
+ "dom-size",
79
+ "bootup-time",
80
+ "mainthread-work-breakdown",
81
+ "font-display",
82
+ "uses-responsive-images"
83
+ ];
84
+
85
+ for (const id of diagnosticIds) {
86
+ const audit = report.audits[id];
87
+ if (audit && audit.score !== null && audit.score < 1) {
88
+ output.diagnostics.push({
89
+ id: id,
90
+ title: audit.title,
91
+ score: Math.round(audit.score * 100),
92
+ displayValue: audit.displayValue || ""
93
+ });
94
+ }
95
+ }
96
+
97
+ console.log(JSON.stringify(output));
98
+
99
+ } catch (err) {
100
+ console.log(JSON.stringify({
101
+ url: url,
102
+ success: false,
103
+ error: err.message,
104
+ scores: { performance: 0, accessibility: 0, bestPractices: 0, seo: 0 },
105
+ metrics: {},
106
+ opportunities: [],
107
+ diagnostics: []
108
+ }));
109
+ }
110
+ '''
111
+
112
+
113
+ def run_perf_test(
114
+ url: str,
115
+ categories: str = "performance,accessibility,best-practices,seo",
116
+ timeout: int = 120000
117
+ ) -> Dict:
118
+ """
119
+ 執行 Lighthouse 效能測試
120
+
121
+ Args:
122
+ url: 要測試的網址
123
+ categories: 要測試的類別
124
+ timeout: 超時時間 (毫秒)
125
+
126
+ Returns:
127
+ 測試結果字典
128
+ """
129
+ # 建立臨時腳本檔案
130
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False) as f:
131
+ f.write(LIGHTHOUSE_SCRIPT)
132
+ script_path = f.name
133
+
134
+ try:
135
+ # 執行 Node.js 腳本
136
+ result = subprocess.run(
137
+ ['node', script_path, url, categories],
138
+ capture_output=True,
139
+ text=True,
140
+ timeout=timeout / 1000 + 30,
141
+ cwd=get_node_cwd()
142
+ )
143
+
144
+ if result.returncode != 0 and not result.stdout:
145
+ return {
146
+ 'url': url,
147
+ 'success': False,
148
+ 'error': f"Script error: {result.stderr}",
149
+ 'scores': {'performance': 0, 'accessibility': 0, 'bestPractices': 0, 'seo': 0},
150
+ 'metrics': {},
151
+ 'opportunities': [],
152
+ 'diagnostics': []
153
+ }
154
+
155
+ # 解析 JSON 輸出
156
+ try:
157
+ return json.loads(result.stdout.strip())
158
+ except json.JSONDecodeError:
159
+ return {
160
+ 'url': url,
161
+ 'success': False,
162
+ 'error': f"Invalid JSON: {result.stdout[:200]}",
163
+ 'scores': {'performance': 0, 'accessibility': 0, 'bestPractices': 0, 'seo': 0},
164
+ 'metrics': {},
165
+ 'opportunities': [],
166
+ 'diagnostics': []
167
+ }
168
+
169
+ except subprocess.TimeoutExpired:
170
+ return {
171
+ 'url': url,
172
+ 'success': False,
173
+ 'error': 'Timeout exceeded',
174
+ 'scores': {'performance': 0, 'accessibility': 0, 'bestPractices': 0, 'seo': 0},
175
+ 'metrics': {},
176
+ 'opportunities': [],
177
+ 'diagnostics': []
178
+ }
179
+ except FileNotFoundError:
180
+ return {
181
+ 'url': url,
182
+ 'success': False,
183
+ 'error': 'Node.js not found',
184
+ 'scores': {'performance': 0, 'accessibility': 0, 'bestPractices': 0, 'seo': 0},
185
+ 'metrics': {},
186
+ 'opportunities': [],
187
+ 'diagnostics': []
188
+ }
189
+ finally:
190
+ Path(script_path).unlink(missing_ok=True)
191
+
192
+
193
+ def get_node_cwd() -> str:
194
+ """取得有 Node.js 的工作目錄"""
195
+ return '/Users/dash/Documents/github/smai-process-vision'
196
+
197
+
198
+ def get_score_color(score: int) -> str:
199
+ """根據分數取得顏色"""
200
+ if score >= 90:
201
+ return "green"
202
+ elif score >= 50:
203
+ return "yellow"
204
+ else:
205
+ return "red"
206
+
207
+
208
+ def get_score_emoji(score: int) -> str:
209
+ """根據分數取得狀態符號"""
210
+ if score >= 90:
211
+ return "[green]OK[/green]"
212
+ elif score >= 50:
213
+ return "[yellow]!![/yellow]"
214
+ else:
215
+ return "[red]XX[/red]"
216
+
217
+
218
+ def format_time(ms: float) -> str:
219
+ """格式化時間"""
220
+ if ms >= 1000:
221
+ return f"{ms/1000:.1f}s"
222
+ return f"{ms:.0f}ms"
223
+
224
+
225
+ def print_perf_report(result: Dict, verbose: bool = False):
226
+ """印出效能報告"""
227
+
228
+ if not result.get('success', False):
229
+ console.print(f"[red]測試失敗: {result.get('error', 'Unknown error')}[/red]")
230
+ return
231
+
232
+ scores = result.get('scores', {})
233
+ metrics = result.get('metrics', {})
234
+
235
+ # 分數表格
236
+ score_table = Table(title="Lighthouse 效能評分", show_header=True)
237
+ score_table.add_column("類別", style="cyan")
238
+ score_table.add_column("分數", justify="right")
239
+ score_table.add_column("狀態", justify="center")
240
+
241
+ categories = [
242
+ ("Performance", scores.get('performance', 0)),
243
+ ("Accessibility", scores.get('accessibility', 0)),
244
+ ("Best Practices", scores.get('bestPractices', 0)),
245
+ ("SEO", scores.get('seo', 0))
246
+ ]
247
+
248
+ for name, score in categories:
249
+ color = get_score_color(score)
250
+ status = get_score_emoji(score)
251
+ score_table.add_row(name, f"[{color}]{score}[/{color}]", status)
252
+
253
+ console.print(score_table)
254
+ console.print()
255
+
256
+ # Core Web Vitals
257
+ if metrics:
258
+ vitals_table = Table(title="Core Web Vitals", show_header=True)
259
+ vitals_table.add_column("指標", style="cyan")
260
+ vitals_table.add_column("數值", justify="right")
261
+ vitals_table.add_column("說明")
262
+
263
+ vitals = [
264
+ ("FCP", metrics.get('fcp', 0), "First Contentful Paint"),
265
+ ("LCP", metrics.get('lcp', 0), "Largest Contentful Paint"),
266
+ ("TBT", metrics.get('tbt', 0), "Total Blocking Time"),
267
+ ("CLS", metrics.get('cls', 0), "Cumulative Layout Shift"),
268
+ ("SI", metrics.get('si', 0), "Speed Index"),
269
+ ("TTI", metrics.get('tti', 0), "Time to Interactive")
270
+ ]
271
+
272
+ for abbr, value, desc in vitals:
273
+ if abbr == "CLS":
274
+ formatted = f"{value:.3f}"
275
+ else:
276
+ formatted = format_time(value)
277
+ vitals_table.add_row(abbr, formatted, desc)
278
+
279
+ console.print(vitals_table)
280
+ console.print()
281
+
282
+ # 改善建議
283
+ opportunities = result.get('opportunities', [])
284
+ if opportunities:
285
+ console.print("[bold yellow]改善建議 (可節省時間):[/bold yellow]")
286
+ for i, opp in enumerate(opportunities, 1):
287
+ savings = format_time(opp.get('savings', 0))
288
+ console.print(f" {i}. {opp.get('title')} [dim](-{savings})[/dim]")
289
+ console.print()
290
+
291
+ # 診斷資訊
292
+ diagnostics = result.get('diagnostics', [])
293
+ if diagnostics and verbose:
294
+ console.print("[bold cyan]診斷資訊:[/bold cyan]")
295
+ for diag in diagnostics:
296
+ display = diag.get('displayValue', '')
297
+ console.print(f" - {diag.get('title')}: {display}")
298
+ console.print()
299
+
300
+ # 總結
301
+ perf_score = scores.get('performance', 0)
302
+ if perf_score >= 90:
303
+ console.print("[green]效能優秀! 繼續保持[/green]")
304
+ elif perf_score >= 50:
305
+ console.print("[yellow]效能尚可,建議參考上述改善建議[/yellow]")
306
+ else:
307
+ console.print("[red]效能需要改善,請優先處理上述建議[/red]")
308
+
309
+
310
+ def check_lighthouse_installed() -> bool:
311
+ """檢查 Lighthouse 是否可用"""
312
+ try:
313
+ result = subprocess.run(
314
+ ['npx', 'lighthouse', '--version'],
315
+ capture_output=True,
316
+ timeout=30,
317
+ cwd=get_node_cwd()
318
+ )
319
+ return result.returncode == 0
320
+ except:
321
+ return False