kctl-react 0.6.2__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 (102) hide show
  1. kctl_react/__init__.py +3 -0
  2. kctl_react/__main__.py +5 -0
  3. kctl_react/cli.py +201 -0
  4. kctl_react/commands/__init__.py +0 -0
  5. kctl_react/commands/a11y.py +78 -0
  6. kctl_react/commands/affected.py +170 -0
  7. kctl_react/commands/apps.py +353 -0
  8. kctl_react/commands/build.py +376 -0
  9. kctl_react/commands/bundle_cmd.py +217 -0
  10. kctl_react/commands/cap.py +1465 -0
  11. kctl_react/commands/clean.py +76 -0
  12. kctl_react/commands/codegen.py +491 -0
  13. kctl_react/commands/compliance.py +587 -0
  14. kctl_react/commands/config_cmd.py +368 -0
  15. kctl_react/commands/dashboard.py +163 -0
  16. kctl_react/commands/deploy.py +318 -0
  17. kctl_react/commands/deps.py +792 -0
  18. kctl_react/commands/dev.py +96 -0
  19. kctl_react/commands/docker_cmd.py +73 -0
  20. kctl_react/commands/doctor.py +170 -0
  21. kctl_react/commands/e2e.py +343 -0
  22. kctl_react/commands/env.py +155 -0
  23. kctl_react/commands/i18n.py +310 -0
  24. kctl_react/commands/lint.py +306 -0
  25. kctl_react/commands/maintenance.py +308 -0
  26. kctl_react/commands/monitor_cmd.py +50 -0
  27. kctl_react/commands/observe.py +34 -0
  28. kctl_react/commands/packages.py +129 -0
  29. kctl_react/commands/perf.py +762 -0
  30. kctl_react/commands/pipeline.py +289 -0
  31. kctl_react/commands/pwa.py +193 -0
  32. kctl_react/commands/scaffold.py +323 -0
  33. kctl_react/commands/security.py +660 -0
  34. kctl_react/commands/skill_cmd.py +54 -0
  35. kctl_react/commands/state.py +254 -0
  36. kctl_react/commands/test_cmd.py +418 -0
  37. kctl_react/commands/ui_audit.py +889 -0
  38. kctl_react/core/__init__.py +0 -0
  39. kctl_react/core/analyzers.py +200 -0
  40. kctl_react/core/callbacks.py +70 -0
  41. kctl_react/core/compliance/__init__.py +3 -0
  42. kctl_react/core/compliance/api_check/__init__.py +3 -0
  43. kctl_react/core/compliance/api_check/checks/__init__.py +18 -0
  44. kctl_react/core/compliance/api_check/checks/endpoints.py +53 -0
  45. kctl_react/core/compliance/api_check/checks/envelope.py +44 -0
  46. kctl_react/core/compliance/api_check/checks/naming.py +60 -0
  47. kctl_react/core/compliance/api_check/checks/params.py +44 -0
  48. kctl_react/core/compliance/api_check/checks/requests.py +57 -0
  49. kctl_react/core/compliance/api_check/checks/types.py +55 -0
  50. kctl_react/core/compliance/api_check/hooks.py +133 -0
  51. kctl_react/core/compliance/api_check/matcher.py +55 -0
  52. kctl_react/core/compliance/api_check/schema.py +151 -0
  53. kctl_react/core/compliance/api_health/__init__.py +35 -0
  54. kctl_react/core/compliance/api_health/checks/__init__.py +9 -0
  55. kctl_react/core/compliance/api_health/checks/auth.py +72 -0
  56. kctl_react/core/compliance/api_health/checks/reachable.py +44 -0
  57. kctl_react/core/compliance/api_health/checks/response.py +55 -0
  58. kctl_react/core/compliance/api_health/checks/timing.py +38 -0
  59. kctl_react/core/compliance/api_health/client.py +99 -0
  60. kctl_react/core/compliance/api_health/sampler.py +16 -0
  61. kctl_react/core/compliance/checks/__init__.py +47 -0
  62. kctl_react/core/compliance/checks/api.py +101 -0
  63. kctl_react/core/compliance/checks/codegen.py +94 -0
  64. kctl_react/core/compliance/checks/darkmode.py +57 -0
  65. kctl_react/core/compliance/checks/errors.py +68 -0
  66. kctl_react/core/compliance/checks/features.py +66 -0
  67. kctl_react/core/compliance/checks/i18n_check.py +105 -0
  68. kctl_react/core/compliance/checks/imports.py +86 -0
  69. kctl_react/core/compliance/checks/navigation.py +62 -0
  70. kctl_react/core/compliance/checks/practices.py +122 -0
  71. kctl_react/core/compliance/checks/providers.py +85 -0
  72. kctl_react/core/compliance/checks/pwa.py +101 -0
  73. kctl_react/core/compliance/checks/responsive.py +47 -0
  74. kctl_react/core/compliance/checks/scripts.py +85 -0
  75. kctl_react/core/compliance/checks/shadcn.py +51 -0
  76. kctl_react/core/compliance/checks/structure.py +76 -0
  77. kctl_react/core/compliance/checks/testing.py +83 -0
  78. kctl_react/core/compliance/checks/theme.py +92 -0
  79. kctl_react/core/compliance/checks/ui_standard.py +185 -0
  80. kctl_react/core/compliance/checks/vite.py +83 -0
  81. kctl_react/core/compliance/engine.py +87 -0
  82. kctl_react/core/compliance/exceptions_map.py +15 -0
  83. kctl_react/core/compliance/fixes/__init__.py +33 -0
  84. kctl_react/core/compliance/fixes/codegen_fix.py +28 -0
  85. kctl_react/core/compliance/fixes/i18n_fix.py +61 -0
  86. kctl_react/core/compliance/fixes/imports_fix.py +36 -0
  87. kctl_react/core/compliance/fixes/structure_fix.py +20 -0
  88. kctl_react/core/compliance/fixes/theme_fix.py +29 -0
  89. kctl_react/core/compliance/models.py +106 -0
  90. kctl_react/core/config.py +201 -0
  91. kctl_react/core/discovery.py +185 -0
  92. kctl_react/core/exceptions.py +17 -0
  93. kctl_react/core/git.py +146 -0
  94. kctl_react/core/history.py +121 -0
  95. kctl_react/core/output.py +5 -0
  96. kctl_react/core/plugins.py +13 -0
  97. kctl_react/core/runner.py +34 -0
  98. kctl_react/py.typed +0 -0
  99. kctl_react-0.6.2.dist-info/METADATA +17 -0
  100. kctl_react-0.6.2.dist-info/RECORD +102 -0
  101. kctl_react-0.6.2.dist-info/WHEEL +4 -0
  102. kctl_react-0.6.2.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,353 @@
1
+ """App inventory and health check commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import subprocess
6
+ import time
7
+ from typing import Annotated
8
+
9
+ import httpx
10
+ import typer
11
+
12
+ from kctl_react import __version__
13
+ from kctl_react.commands.clean import run_clean
14
+ from kctl_react.commands.dashboard import _display_dashboard, _fetch_dashboard
15
+ from kctl_react.commands.doctor import run_doctor
16
+ from kctl_react.core.callbacks import AppContext
17
+ from kctl_react.core.discovery import get_app_dir
18
+ from kctl_react.core.config import resolve_active_profile_name
19
+
20
+ app = typer.Typer(help="App inventory, status, and health checks.")
21
+
22
+
23
+ @app.command("list")
24
+ def list_(ctx: typer.Context) -> None:
25
+ """List all apps with ports and package names."""
26
+ actx: AppContext = ctx.obj
27
+ out = actx.output
28
+
29
+ rows: list[list[str]] = []
30
+ json_data: list[dict] = []
31
+
32
+ for app_name, info in actx.apps.items():
33
+ app_dir = actx.get_app_dir(app_name)
34
+ exists = app_dir.is_dir()
35
+ status = "[green]OK[/green]" if exists else "[red]missing[/red]"
36
+
37
+ rows.append(
38
+ [
39
+ app_name,
40
+ info["name"],
41
+ str(info["port"]),
42
+ info["package"],
43
+ status,
44
+ ]
45
+ )
46
+ json_data.append(
47
+ {
48
+ "app": app_name,
49
+ "name": info["name"],
50
+ "port": info["port"],
51
+ "package": info["package"],
52
+ "exists": exists,
53
+ }
54
+ )
55
+
56
+ out.table(
57
+ "Apps",
58
+ [("App", "cyan"), ("Description", ""), ("Port", "green"), ("Package", "dim"), ("Status", "")],
59
+ rows,
60
+ data_for_json=json_data,
61
+ )
62
+
63
+
64
+ @app.command()
65
+ def ports(ctx: typer.Context) -> None:
66
+ """Show port assignments for all apps."""
67
+ actx: AppContext = ctx.obj
68
+ out = actx.output
69
+
70
+ rows: list[list[str]] = []
71
+ for app_name, info in actx.apps.items():
72
+ rows.append([app_name, str(info["port"]), f"http://localhost:{info['port']}"])
73
+
74
+ out.table(
75
+ "Port Assignments",
76
+ [("App", "cyan"), ("Port", "green"), ("URL", "dim")],
77
+ rows,
78
+ )
79
+
80
+
81
+ @app.command()
82
+ def status(
83
+ ctx: typer.Context,
84
+ app_name: Annotated[str | None, typer.Argument(help="App name (optional, checks all if omitted)")] = None,
85
+ ) -> None:
86
+ """Check status of app(s): directory exists, package.json, env file, dist."""
87
+ actx: AppContext = ctx.obj
88
+ out = actx.output
89
+ root = actx.project_root
90
+
91
+ apps_to_check = [app_name] if app_name else actx.app_names
92
+ if app_name:
93
+ actx.validate_app(app_name)
94
+
95
+ rows: list[list[str]] = []
96
+ json_data: list[dict] = []
97
+
98
+ for name in apps_to_check:
99
+ info = actx.apps[name]
100
+ app_dir = get_app_dir(root, name)
101
+
102
+ has_dir = app_dir.is_dir()
103
+ has_pkg = (app_dir / "package.json").exists()
104
+ has_src = (app_dir / "src").is_dir()
105
+ has_dist = (app_dir / "dist").is_dir() or (app_dir / ".next").is_dir()
106
+ has_env = (app_dir / ".env").exists() or (app_dir / ".env.local").exists()
107
+ has_tests = (
108
+ (
109
+ (app_dir / "src" / "__tests__").is_dir()
110
+ or any((app_dir / "src").rglob("*.test.ts"))
111
+ or any((app_dir / "src").rglob("*.test.tsx"))
112
+ )
113
+ if has_src
114
+ else False
115
+ )
116
+
117
+ def icon(ok: bool) -> str:
118
+ return "[green]OK[/green]" if ok else "[red]--[/red]"
119
+
120
+ rows.append(
121
+ [
122
+ name,
123
+ str(info["port"]),
124
+ icon(has_dir),
125
+ icon(has_pkg),
126
+ icon(has_src),
127
+ icon(has_dist),
128
+ icon(has_env),
129
+ icon(has_tests),
130
+ ]
131
+ )
132
+ json_data.append(
133
+ {
134
+ "app": name,
135
+ "port": info["port"],
136
+ "directory": has_dir,
137
+ "package_json": has_pkg,
138
+ "src": has_src,
139
+ "dist": has_dist,
140
+ "env": has_env,
141
+ "tests": has_tests,
142
+ }
143
+ )
144
+
145
+ out.table(
146
+ "App Status",
147
+ [
148
+ ("App", "cyan"),
149
+ ("Port", "green"),
150
+ ("Dir", ""),
151
+ ("Pkg", ""),
152
+ ("Src", ""),
153
+ ("Dist", ""),
154
+ ("Env", ""),
155
+ ("Tests", ""),
156
+ ],
157
+ rows,
158
+ data_for_json=json_data,
159
+ )
160
+
161
+
162
+ def _check_health(actx: AppContext, app_name: str | None) -> tuple[list[list[str]], list[dict], int, int]:
163
+ """Run health checks and return (rows, json_data, healthy, total)."""
164
+ apps_to_check = [app_name] if app_name else actx.app_names
165
+
166
+ rows: list[list[str]] = []
167
+ json_data: list[dict] = []
168
+ healthy = 0
169
+ total = 0
170
+
171
+ for name in apps_to_check:
172
+ info = actx.apps[name]
173
+ port = info["port"]
174
+ url = f"http://localhost:{port}"
175
+ total += 1
176
+
177
+ try:
178
+ r = httpx.get(url, timeout=3, follow_redirects=True)
179
+ is_up = r.status_code < 500
180
+ status = f"[green]{r.status_code}[/green]" if is_up else f"[red]{r.status_code}[/red]"
181
+ if is_up:
182
+ healthy += 1
183
+ except (httpx.HTTPError, Exception):
184
+ is_up = False
185
+ status = "[red]offline[/red]"
186
+
187
+ rows.append([name, str(port), status])
188
+ json_data.append({"app": name, "port": port, "healthy": is_up})
189
+
190
+ return rows, json_data, healthy, total
191
+
192
+
193
+ @app.command()
194
+ def health(
195
+ ctx: typer.Context,
196
+ app_name: Annotated[str | None, typer.Argument(help="App name (optional, checks all if omitted)")] = None,
197
+ watch: Annotated[bool, typer.Option("--watch", "-w", help="Continuously monitor.")] = False,
198
+ interval: Annotated[int, typer.Option("--interval", "-i", help="Refresh interval in seconds.")] = 5,
199
+ ) -> None:
200
+ """Check health of running dev servers."""
201
+ actx: AppContext = ctx.obj
202
+ out = actx.output
203
+
204
+ if app_name:
205
+ actx.validate_app(app_name)
206
+
207
+ if watch:
208
+ try:
209
+ while True:
210
+ rows, json_data, healthy, total = _check_health(actx, app_name)
211
+ out.console.clear()
212
+ out.table(
213
+ "Dev Server Health",
214
+ [("App", "cyan"), ("Port", "green"), ("Status", "")],
215
+ rows,
216
+ data_for_json=json_data,
217
+ )
218
+ out.text(f"\n {healthy}/{total} healthy — refreshing every {interval}s (Ctrl+C to stop)")
219
+ time.sleep(interval)
220
+ except KeyboardInterrupt:
221
+ out.info("Stopped watching.")
222
+ return
223
+
224
+ rows, json_data, healthy, total = _check_health(actx, app_name)
225
+
226
+ out.table(
227
+ "Dev Server Health",
228
+ [("App", "cyan"), ("Port", "green"), ("Status", "")],
229
+ rows,
230
+ data_for_json=json_data,
231
+ )
232
+
233
+ if healthy == total:
234
+ out.success(f"All {total} apps healthy")
235
+ elif healthy > 0:
236
+ out.warn(f"{healthy}/{total} apps healthy")
237
+ else:
238
+ out.info(f"No apps running (checked {total})")
239
+
240
+
241
+ @app.command()
242
+ def info(ctx: typer.Context) -> None:
243
+ """Show quick project info: version, root, profile, node/pnpm versions."""
244
+ actx: AppContext = ctx.obj
245
+ out = actx.output
246
+ root = actx.project_root
247
+
248
+ def _cmd_version(cmd: str) -> str:
249
+ try:
250
+ r = subprocess.run([cmd, "--version"], capture_output=True, text=True, timeout=5)
251
+ return r.stdout.strip().split("\n")[0]
252
+ except Exception:
253
+ return "not found"
254
+
255
+ node_ver = _cmd_version("node")
256
+ pnpm_ver = _cmd_version("pnpm")
257
+
258
+ git_branch = ""
259
+ try:
260
+ r = subprocess.run(
261
+ ["git", "branch", "--show-current"],
262
+ cwd=root,
263
+ capture_output=True,
264
+ text=True,
265
+ timeout=5,
266
+ )
267
+ git_branch = r.stdout.strip()
268
+ except Exception:
269
+ pass
270
+
271
+ profile = resolve_active_profile_name(actx.profile)
272
+ apps_count = sum(1 for a in actx.app_names if (get_app_dir(root, a)).is_dir())
273
+
274
+ sections: list[tuple[str, list[tuple[str, str]]]] = [
275
+ (
276
+ "kctl-react",
277
+ [
278
+ ("Version", __version__),
279
+ ("Profile", profile),
280
+ ("Project root", str(root)),
281
+ ("Git branch", git_branch or "[dim]unknown[/dim]"),
282
+ ("Node", node_ver),
283
+ ("pnpm", pnpm_ver),
284
+ ("Apps", f"{apps_count}/{len(actx.app_names)}"),
285
+ ],
286
+ ),
287
+ ]
288
+
289
+ out.detail(
290
+ "Info",
291
+ sections,
292
+ data_for_json={
293
+ "version": __version__,
294
+ "profile": profile,
295
+ "root": str(root),
296
+ "git_branch": git_branch,
297
+ "node": node_ver,
298
+ "pnpm": pnpm_ver,
299
+ "apps": apps_count,
300
+ },
301
+ )
302
+
303
+
304
+ @app.command()
305
+ def dashboard(
306
+ ctx: typer.Context,
307
+ watch: Annotated[bool, typer.Option("--watch", "-w", help="Continuously refresh.")] = False,
308
+ interval: Annotated[int, typer.Option("--interval", "-i", help="Refresh interval in seconds.")] = 10,
309
+ ) -> None:
310
+ """Show monorepo overview dashboard."""
311
+ actx: AppContext = ctx.obj
312
+
313
+ if watch:
314
+ try:
315
+ while True:
316
+ data = _fetch_dashboard(actx)
317
+ actx.output.console.clear()
318
+ _display_dashboard(actx, data)
319
+ actx.output.text(f"\n[dim]Refreshing every {interval}s. Press Ctrl+C to stop.[/dim]")
320
+ time.sleep(interval)
321
+ except KeyboardInterrupt:
322
+ actx.output.info("Stopped watching.")
323
+ else:
324
+ data = _fetch_dashboard(actx)
325
+ _display_dashboard(actx, data)
326
+
327
+
328
+ @app.command()
329
+ def doctor(ctx: typer.Context) -> None:
330
+ """Run comprehensive monorepo health checks.
331
+
332
+ Checks: node, pnpm, turbo, git, docker, all apps, packages, env files,
333
+ codegen config, and dependency installation.
334
+ """
335
+ actx: AppContext = ctx.obj
336
+ run_doctor(actx)
337
+
338
+
339
+ @app.command()
340
+ def clean(
341
+ ctx: typer.Context,
342
+ app_name: Annotated[str | None, typer.Argument(help="App name (omit for all)")] = None,
343
+ all_: Annotated[bool, typer.Option("--all", "-a", help="Also remove node_modules.")] = False,
344
+ ) -> None:
345
+ """Clean dist, .turbo, and coverage directories.
346
+
347
+ Examples:
348
+ kctl-react apps clean # Clean all apps
349
+ kctl-react apps clean sfa # Clean SFA only
350
+ kctl-react apps clean --all # Clean + remove node_modules
351
+ """
352
+ actx: AppContext = ctx.obj
353
+ run_clean(actx, app_name=app_name, all_=all_)