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
@@ -0,0 +1,310 @@
1
+ """
2
+ Google Apps Script (GAS) 專案驗證器 v1.1
3
+
4
+ 支援 UI 框架:
5
+ - DaisyUI + Vue 3 CDN(主要)
6
+ - Shoelace(向下相容)
7
+
8
+ 檢查項目:
9
+ 1. appsscript.json 設定
10
+ 2. Code.js 版本號管理
11
+ 3. HTML 模板品質(v-for :key、標籤閉合)
12
+ 4. DaisyUI 主題設定
13
+ 5. Shoelace 綁定語法(僅限 Shoelace 專案)
14
+ """
15
+
16
+ import re
17
+ import json
18
+ from pathlib import Path
19
+
20
+
21
+ class GasValidator:
22
+ """Google Apps Script 專案驗證器"""
23
+
24
+ name = 'gas'
25
+
26
+ def __init__(self, project_path):
27
+ self.project_path = Path(project_path)
28
+ self.project_name = self.project_path.name
29
+ self.result = {
30
+ 'name': self.name,
31
+ 'passed': True,
32
+ 'errors': [],
33
+ 'warnings': [],
34
+ 'checks': {}
35
+ }
36
+ # 偵測 UI 框架
37
+ self.ui_framework = self._detect_ui_framework()
38
+ self.has_vue = self._detect_vue()
39
+
40
+ def _detect_ui_framework(self) -> str | None:
41
+ """偵測 UI 框架(從 HTML 檔案判斷)"""
42
+ for html_file in self.project_path.glob('*.html'):
43
+ try:
44
+ content = html_file.read_text(encoding='utf-8')
45
+ # DaisyUI 優先檢測(更常見)
46
+ if 'daisyui' in content.lower():
47
+ return 'daisyui'
48
+ elif 'shoelace' in content.lower() or 'sl-' in content:
49
+ return 'shoelace'
50
+ except Exception:
51
+ pass
52
+ return None
53
+
54
+ def _detect_vue(self) -> bool:
55
+ """偵測是否使用 Vue"""
56
+ for html_file in self.project_path.glob('*.html'):
57
+ try:
58
+ content = html_file.read_text(encoding='utf-8')
59
+ if 'vue' in content.lower() and ('v-if' in content or 'v-for' in content or ':' in content):
60
+ return True
61
+ except Exception:
62
+ pass
63
+ return False
64
+
65
+ def run(self):
66
+ """執行所有驗證"""
67
+ if not self.project_path.exists():
68
+ self.result['passed'] = False
69
+ self.result['errors'].append(f'專案路徑不存在: {self.project_path}')
70
+ return self.result
71
+
72
+ # 檢查 appsscript.json
73
+ self.check_appsscript_config()
74
+
75
+ # UI 框架特定檢查
76
+ if self.ui_framework == 'daisyui':
77
+ self.check_daisyui_setup()
78
+ elif self.ui_framework == 'shoelace':
79
+ self.check_shoelace_binding()
80
+ if self.has_vue:
81
+ self.check_vue_custom_element()
82
+
83
+ # Code.js 版本檢查
84
+ self.check_version_management()
85
+
86
+ # HTML 品質檢查
87
+ self.check_html_quality()
88
+
89
+ return self.result
90
+
91
+ def check_appsscript_config(self):
92
+ """檢查 appsscript.json 設定"""
93
+ config_file = self.project_path / 'appsscript.json'
94
+
95
+ if not config_file.exists():
96
+ self.result['passed'] = False
97
+ self.result['errors'].append('缺少 appsscript.json')
98
+ return
99
+
100
+ try:
101
+ config = json.loads(config_file.read_text(encoding='utf-8'))
102
+
103
+ runtime = config.get('runtimeVersion', 'DEPRECATED_ES5')
104
+ webapp = config.get('webapp', {})
105
+
106
+ self.result['checks']['appsscript_config'] = {
107
+ 'runtime': runtime,
108
+ 'has_webapp': bool(webapp),
109
+ 'access': webapp.get('access', 'unknown')
110
+ }
111
+
112
+ if runtime == 'DEPRECATED_ES5':
113
+ self.result['warnings'].append('建議使用 V8 runtime')
114
+
115
+ except json.JSONDecodeError as e:
116
+ self.result['passed'] = False
117
+ self.result['errors'].append(f'appsscript.json 格式錯誤: {e}')
118
+
119
+ def check_daisyui_setup(self):
120
+ """檢查 DaisyUI 設定"""
121
+ has_theme = False
122
+ theme_value = None
123
+
124
+ # 檢查 index.html 的 data-theme 設定
125
+ index_html = self.project_path / 'index.html'
126
+ if index_html.exists():
127
+ try:
128
+ content = index_html.read_text(encoding='utf-8')
129
+ theme_match = re.search(r'data-theme\s*=\s*["\']([^"\']+)["\']', content)
130
+ if theme_match:
131
+ has_theme = True
132
+ theme_value = theme_match.group(1)
133
+ except Exception:
134
+ pass
135
+
136
+ self.result['checks']['daisyui_setup'] = {
137
+ 'has_theme': has_theme,
138
+ 'theme': theme_value,
139
+ 'ui_framework': 'daisyui'
140
+ }
141
+
142
+ if not has_theme:
143
+ self.result['warnings'].append(
144
+ 'DaisyUI 未設定 data-theme,建議在 <html> 加入:\n'
145
+ ' <html data-theme="light">'
146
+ )
147
+
148
+ def check_shoelace_binding(self):
149
+ """檢查 Shoelace 元件綁定語法(僅限 Shoelace 專案)"""
150
+ issues = []
151
+
152
+ # 錯誤的 v-model 使用模式
153
+ wrong_patterns = [
154
+ (r'<sl-input[^>]*v-model\s*=\s*["\'][^"\']+["\']', 'sl-input'),
155
+ (r'<sl-select[^>]*v-model\s*=\s*["\'][^"\']+["\']', 'sl-select'),
156
+ (r'<sl-checkbox[^>]*v-model\s*=\s*["\'][^"\']+["\']', 'sl-checkbox'),
157
+ (r'<sl-textarea[^>]*v-model\s*=\s*["\'][^"\']+["\']', 'sl-textarea'),
158
+ (r'<sl-radio-group[^>]*v-model\s*=\s*["\'][^"\']+["\']', 'sl-radio-group'),
159
+ ]
160
+
161
+ for html_file in self.project_path.glob('*.html'):
162
+ try:
163
+ content = html_file.read_text(encoding='utf-8')
164
+ rel_path = html_file.name
165
+
166
+ for pattern, component in wrong_patterns:
167
+ matches = re.findall(pattern, content, re.IGNORECASE)
168
+ if matches:
169
+ issues.append({
170
+ 'file': rel_path,
171
+ 'component': component,
172
+ 'count': len(matches)
173
+ })
174
+
175
+ except Exception:
176
+ pass
177
+
178
+ self.result['checks']['shoelace_binding'] = {
179
+ 'issues_count': len(issues),
180
+ 'issues': issues
181
+ }
182
+
183
+ if issues:
184
+ self.result['passed'] = False
185
+ for issue in issues:
186
+ self.result['errors'].append(
187
+ f"Shoelace 綁定錯誤: {issue['file']} - {issue['component']} 使用 v-model(應改用 :value + @sl-input)"
188
+ )
189
+
190
+ self.result['warnings'].append(
191
+ '正確寫法範例:\n'
192
+ ' <sl-input :value="formData.name" @sl-input="e => formData.name = e.target.value"></sl-input>\n'
193
+ ' <sl-select :value="formData.type" @sl-change="e => formData.type = e.target.value"></sl-select>'
194
+ )
195
+
196
+ def check_vue_custom_element(self):
197
+ """檢查 Vue isCustomElement 設定(僅限 Shoelace 專案)"""
198
+ has_config = False
199
+ config_location = None
200
+
201
+ for html_file in self.project_path.glob('*.html'):
202
+ try:
203
+ content = html_file.read_text(encoding='utf-8')
204
+
205
+ if 'isCustomElement' in content:
206
+ has_config = True
207
+ config_location = html_file.name
208
+
209
+ if "tag.startsWith('sl-')" in content or "tag.startsWith(\"sl-\")" in content:
210
+ break
211
+
212
+ except Exception:
213
+ pass
214
+
215
+ self.result['checks']['vue_custom_element'] = {
216
+ 'has_config': has_config,
217
+ 'config_location': config_location
218
+ }
219
+
220
+ if not has_config:
221
+ self.result['warnings'].append(
222
+ 'Vue 未設定 isCustomElement,可能導致 Shoelace 元件警告\n'
223
+ ' 建議在 Vue 初始化時加入:\n'
224
+ " app.config.compilerOptions.isCustomElement = tag => tag.startsWith('sl-');"
225
+ )
226
+
227
+ def check_version_management(self):
228
+ """檢查 Code.js 版本號管理"""
229
+ code_js = self.project_path / 'Code.js'
230
+
231
+ if not code_js.exists():
232
+ self.result['checks']['version_management'] = {'skipped': '無 Code.js'}
233
+ return
234
+
235
+ try:
236
+ content = code_js.read_text(encoding='utf-8')
237
+
238
+ # 尋找版本號
239
+ version_match = re.search(r"case\s+['\"]getVersion['\"]:\s*\n?\s*return\s*{\s*success:\s*true,\s*data:\s*['\"]([^'\"]+)['\"]", content)
240
+
241
+ if version_match:
242
+ version = version_match.group(1)
243
+ self.result['checks']['version_management'] = {
244
+ 'version': version,
245
+ 'has_version_api': True
246
+ }
247
+ else:
248
+ self.result['checks']['version_management'] = {
249
+ 'has_version_api': False
250
+ }
251
+ self.result['warnings'].append(
252
+ 'Code.js 未找到 getVersion API,建議加入版本管理:\n'
253
+ " case 'getVersion':\n"
254
+ " return { success: true, data: '1.0.0' };"
255
+ )
256
+
257
+ except Exception as e:
258
+ self.result['checks']['version_management'] = {'error': str(e)}
259
+
260
+ def check_html_quality(self):
261
+ """檢查 HTML 品質"""
262
+ html_files = list(self.project_path.glob('*.html'))
263
+ issues = []
264
+
265
+ for html_file in html_files:
266
+ try:
267
+ content = html_file.read_text(encoding='utf-8')
268
+ rel_path = html_file.name
269
+ file_issues = []
270
+
271
+ # 檢查 v-for 是否有 :key
272
+ v_for_without_key = re.findall(r'v-for="[^"]*"(?![^>]*:key)', content)
273
+ if v_for_without_key:
274
+ file_issues.append(f'v-for 缺少 :key ({len(v_for_without_key)} 處)')
275
+
276
+ # 檢查空的事件處理器
277
+ empty_handlers = re.findall(r'@\w+\s*=\s*["\']["\']', content)
278
+ if empty_handlers:
279
+ file_issues.append(f'空的事件處理器 ({len(empty_handlers)} 處)')
280
+
281
+ # 檢查未閉合的 HTML 標籤(簡單檢查)
282
+ tags_to_check = ['div', 'span', 'table', 'ul', 'ol', 'select']
283
+ for tag in tags_to_check:
284
+ open_count = len(re.findall(rf'<{tag}[^>]*(?<!/)>', content))
285
+ close_count = len(re.findall(rf'</{tag}>', content))
286
+ self_closing = len(re.findall(rf'<{tag}[^>]*/>', content))
287
+ open_count -= self_closing
288
+
289
+ if open_count > close_count + 2:
290
+ file_issues.append(f'<{tag}> 標籤可能未正確閉合')
291
+
292
+ if file_issues:
293
+ issues.append({
294
+ 'file': rel_path,
295
+ 'issues': file_issues
296
+ })
297
+
298
+ except Exception:
299
+ pass
300
+
301
+ self.result['checks']['html_quality'] = {
302
+ 'total_files': len(html_files),
303
+ 'files_with_issues': len(issues),
304
+ 'issues': issues
305
+ }
306
+
307
+ for issue in issues:
308
+ for problem in issue['issues']:
309
+ if '缺少' in problem or '未正確閉合' in problem:
310
+ self.result['warnings'].append(f"HTML: {issue['file']} - {problem}")