aja-codeintel 0.1.4__tar.gz → 0.1.5__tar.gz

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 (89) hide show
  1. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/PKG-INFO +1 -1
  2. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/PKG-INFO +1 -1
  3. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/SOURCES.txt +5 -0
  4. aja_codeintel-0.1.5/codeintel_cli/commands/project/__init__.py +35 -0
  5. aja_codeintel-0.1.5/codeintel_cli/commands/project/endpoints_cmd.py +335 -0
  6. aja_codeintel-0.1.5/codeintel_cli/commands/project/frontend_cmd.py +39 -0
  7. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/models_cmd.py +73 -20
  8. aja_codeintel-0.1.5/codeintel_cli/commands/project/overview_cmd.py +394 -0
  9. aja_codeintel-0.1.5/codeintel_cli/commands/project/types_cmd.py +318 -0
  10. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/java_rel.py +73 -0
  11. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/java_spring.py +220 -152
  12. aja_codeintel-0.1.5/codeintel_cli/endpoints/openapi_spec.py +224 -0
  13. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/scan.py +8 -2
  14. aja_codeintel-0.1.5/codeintel_cli/frontend/__init__.py +3 -0
  15. aja_codeintel-0.1.5/codeintel_cli/frontend/server.py +885 -0
  16. aja_codeintel-0.1.5/codeintel_cli/lang/java/models.py +258 -0
  17. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/pyproject.toml +1 -1
  18. aja_codeintel-0.1.4/codeintel_cli/commands/project/__init__.py +0 -29
  19. aja_codeintel-0.1.4/codeintel_cli/commands/project/endpoints_cmd.py +0 -146
  20. aja_codeintel-0.1.4/codeintel_cli/commands/project/types_cmd.py +0 -351
  21. aja_codeintel-0.1.4/codeintel_cli/lang/java/models.py +0 -105
  22. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/LICENSE +0 -0
  23. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/README.md +0 -0
  24. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/dependency_links.txt +0 -0
  25. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/entry_points.txt +0 -0
  26. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/requires.txt +0 -0
  27. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/top_level.txt +0 -0
  28. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/__init__.py +0 -0
  29. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/__main__.py +0 -0
  30. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/cli.py +0 -0
  31. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/__init__.py +0 -0
  32. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/__init__.py +0 -0
  33. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/deps_cmd.py +0 -0
  34. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/related_cmd.py +0 -0
  35. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/relsymbols_cmd.py +0 -0
  36. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/reverse_related_cmd.py +0 -0
  37. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/__init__.py +0 -0
  38. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/copy_cmd.py +0 -0
  39. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/open_cmd.py +0 -0
  40. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/where_cmd.py +0 -0
  41. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/context_cmd.py +0 -0
  42. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/folder_cmd.py +0 -0
  43. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/imports_cmd.py +0 -0
  44. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/modeltree_cmd.py +0 -0
  45. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/new.py +0 -0
  46. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/resolve_cmd.py +0 -0
  47. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/scan_cmd.py +0 -0
  48. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/servicemap_cmd.py +0 -0
  49. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/tree_cmd.py +0 -0
  50. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/version_cmd.py +0 -0
  51. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/java_context.py +0 -0
  52. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/java_service.py +0 -0
  53. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/python_context.py +0 -0
  54. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/python_rel.py +0 -0
  55. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/python_service.py +0 -0
  56. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/fuzzy.py +0 -0
  57. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/opener.py +0 -0
  58. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/project.py +0 -0
  59. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/resolve_folder.py +0 -0
  60. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/resolve_model_target.py +0 -0
  61. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/resolve_target.py +0 -0
  62. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/timing.py +0 -0
  63. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/where.py +0 -0
  64. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/__init__.py +0 -0
  65. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/cache.py +0 -0
  66. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/operations.py +0 -0
  67. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/schema.py +0 -0
  68. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/__init__.py +0 -0
  69. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/models.py +0 -0
  70. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/python_web.py +0 -0
  71. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/errors.py +0 -0
  72. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/__init__.py +0 -0
  73. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/builder.py +0 -0
  74. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/query.py +0 -0
  75. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/traverse.py +0 -0
  76. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/__init__.py +0 -0
  77. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/java/__init__.py +0 -0
  78. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/java/engine.py +0 -0
  79. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/java/resolve.py +0 -0
  80. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/python/__init__.py +0 -0
  81. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/python/engine.py +0 -0
  82. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/python/models.py +0 -0
  83. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/router.py +0 -0
  84. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/parser/imports.py +0 -0
  85. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/parser/resolve.py +0 -0
  86. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/parser/symbols.py +0 -0
  87. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/scanner/__init__.py +0 -0
  88. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/scanner/scanner.py +0 -0
  89. {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aja-codeintel
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: CodeIntel CLI tool
5
5
  Author: Shiva Areti
6
6
  License: MIT License
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aja-codeintel
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: CodeIntel CLI tool
5
5
  Author: Shiva Areti
6
6
  License: MIT License
@@ -25,10 +25,12 @@ codeintel_cli/commands/project/__init__.py
25
25
  codeintel_cli/commands/project/context_cmd.py
26
26
  codeintel_cli/commands/project/endpoints_cmd.py
27
27
  codeintel_cli/commands/project/folder_cmd.py
28
+ codeintel_cli/commands/project/frontend_cmd.py
28
29
  codeintel_cli/commands/project/imports_cmd.py
29
30
  codeintel_cli/commands/project/models_cmd.py
30
31
  codeintel_cli/commands/project/modeltree_cmd.py
31
32
  codeintel_cli/commands/project/new.py
33
+ codeintel_cli/commands/project/overview_cmd.py
32
34
  codeintel_cli/commands/project/resolve_cmd.py
33
35
  codeintel_cli/commands/project/scan_cmd.py
34
36
  codeintel_cli/commands/project/servicemap_cmd.py
@@ -56,8 +58,11 @@ codeintel_cli/db/schema.py
56
58
  codeintel_cli/endpoints/__init__.py
57
59
  codeintel_cli/endpoints/java_spring.py
58
60
  codeintel_cli/endpoints/models.py
61
+ codeintel_cli/endpoints/openapi_spec.py
59
62
  codeintel_cli/endpoints/python_web.py
60
63
  codeintel_cli/endpoints/scan.py
64
+ codeintel_cli/frontend/__init__.py
65
+ codeintel_cli/frontend/server.py
61
66
  codeintel_cli/graph/__init__.py
62
67
  codeintel_cli/graph/builder.py
63
68
  codeintel_cli/graph/query.py
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+
6
+ def register_project_commands(app: typer.Typer) -> None:
7
+ from .overview_cmd import register_overview
8
+ from .models_cmd import register_models
9
+ from .endpoints_cmd import register_endpoints
10
+ from .types_cmd import register_types_command
11
+ from .scan_cmd import register_scan
12
+ from .context_cmd import register_context
13
+ from .tree_cmd import register_tree
14
+ from .imports_cmd import register_imports
15
+ from .folder_cmd import register_folder
16
+ from .resolve_cmd import register_resolve
17
+ from .servicemap_cmd import register_servicemap
18
+ from .modeltree_cmd import register_modeltree
19
+ from .version_cmd import register_version
20
+ from .frontend_cmd import register_frontend
21
+
22
+ register_overview(app)
23
+ register_models(app)
24
+ register_endpoints(app)
25
+ register_types_command(app)
26
+ register_scan(app)
27
+ register_context(app)
28
+ register_tree(app)
29
+ register_imports(app)
30
+ register_folder(app)
31
+ register_resolve(app)
32
+ register_servicemap(app)
33
+ register_modeltree(app)
34
+ register_version(app)
35
+ register_frontend(app)
@@ -0,0 +1,335 @@
1
+ # codeintel_cli/commands/project/endpoints_cmd.py
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import re
6
+ import time
7
+ from collections import defaultdict
8
+ from fnmatch import fnmatch
9
+ from pathlib import Path
10
+
11
+ import typer
12
+
13
+ from ...core.project import find_project_root
14
+ from ...endpoints.java_spring import extract_spring_endpoints
15
+ from ...endpoints.python_web import extract_python_endpoints
16
+ from ...endpoints.scan import EndpointScanOptions, iter_supported_source_files
17
+ from ...endpoints.openapi_spec import extract_openapi_endpoints # NEW
18
+ from ...errors import InvalidPathError
19
+
20
+
21
+ def _validate_folder(path: str) -> Path:
22
+ folder = Path(path).resolve()
23
+ if not folder.exists() or not folder.is_dir():
24
+ raise InvalidPathError(message="Invalid folder", path=folder)
25
+ return folder
26
+
27
+
28
+ def _norm_path(p: str) -> str:
29
+ return p.replace("\\", "/")
30
+
31
+
32
+ _ANYREQ_AUTH_RE = re.compile(r"\.anyRequest\s*\(\s*\)\s*\.authenticated\s*\(\s*\)")
33
+ _LAST_SECURITY_CANDIDATES: list[str] = []
34
+
35
+ # JHipster / modern Spring Security style:
36
+ # .requestMatchers("/api/**").authenticated()
37
+ # .requestMatchers("/api/admin/**").hasAuthority(...)
38
+ _MATCHERS_AUTHZ_RE = re.compile(
39
+ r"\.(requestMatchers|antMatchers|mvcMatchers)\s*\(\s*([^\)]*?)\)\s*\.\s*(authenticated|hasRole|hasAuthority)\s*\(",
40
+ re.DOTALL,
41
+ )
42
+
43
+
44
+ def _extract_strings(text: str) -> list[str]:
45
+ return re.findall(r'"([^"]+)"', text or "")
46
+
47
+
48
+ def _ant_to_fnmatch(p: str) -> str:
49
+ return (p or "").replace("**", "*")
50
+
51
+
52
+ def _matches_any(path: str, patterns: list[str]) -> bool:
53
+ for pat in patterns or []:
54
+ if fnmatch(path, _ant_to_fnmatch(pat)):
55
+ return True
56
+ return False
57
+
58
+
59
+ def _detect_spring_security_policy(files: list[Path]) -> tuple[bool, list[str]]:
60
+ global _LAST_SECURITY_CANDIDATES
61
+ """
62
+ default_auth:
63
+ - detects anyRequest().authenticated()
64
+ - detects requestMatchers(...).authenticated/hasRole/hasAuthority
65
+ - JHipster fallback: if any security config is found, assume API is secured by default
66
+ permit_all:
67
+ - detects requestMatchers(...).permitAll() and extracts only those patterns
68
+ """
69
+ default_auth = False
70
+ permit_all: list[str] = []
71
+ _LAST_SECURITY_CANDIDATES = []
72
+ found_security_config = False
73
+
74
+ # Exact match: .requestMatchers(...).permitAll()
75
+ _MATCHERS_PERMITALL_RE = re.compile(
76
+ r"\.(requestMatchers|antMatchers|mvcMatchers)\s*\(\s*([^\)]*?)\)\s*\.\s*permitAll\s*\(\s*\)",
77
+ re.DOTALL,
78
+ )
79
+
80
+ SEC_KEYWORDS = (
81
+ "SecurityFilterChain",
82
+ "authorizeHttpRequests",
83
+ "EnableWebSecurity",
84
+ "HttpSecurity",
85
+ "org.springframework.security",
86
+ "springSecurityFilterChain",
87
+ "requestMatchers",
88
+ "antMatchers",
89
+ "mvcMatchers",
90
+ "AuthenticationManager",
91
+ "Jwt",
92
+ "Bearer",
93
+ )
94
+
95
+ for f in files:
96
+ if f.suffix.lower() != ".java":
97
+ continue
98
+
99
+ fp = str(f).replace("\\", "/").lower()
100
+ if "/src/test/" in fp or "/target/" in fp or "/build/" in fp:
101
+ continue
102
+
103
+ try:
104
+ src = f.read_text(encoding="utf-8", errors="ignore")
105
+ except Exception:
106
+ continue
107
+
108
+ try:
109
+ parts = [p.lower() for p in f.parts]
110
+ secpath = any("security" in p for p in parts) or any("config" == p for p in parts)
111
+ except Exception:
112
+ secpath = False
113
+
114
+ hits = any(k in src for k in SEC_KEYWORDS)
115
+ if secpath or hits:
116
+ _LAST_SECURITY_CANDIDATES.append(str(f))
117
+
118
+ if not hits and not secpath:
119
+ continue
120
+
121
+ found_security_config = True
122
+
123
+ if _ANYREQ_AUTH_RE.search(src):
124
+ default_auth = True
125
+
126
+ if not default_auth:
127
+ for mm in _MATCHERS_AUTHZ_RE.finditer(src):
128
+ args = mm.group(2) or ""
129
+ paths = _extract_strings(args)
130
+ for s in paths:
131
+ if s.startswith("/api") or s.startswith("/admin") or s.startswith("/management"):
132
+ default_auth = True
133
+ break
134
+ if default_auth:
135
+ break
136
+
137
+ for mm in _MATCHERS_PERMITALL_RE.finditer(src):
138
+ args = mm.group(2) or ""
139
+ for s in _extract_strings(args):
140
+ permit_all.append(s)
141
+
142
+ if found_security_config and not default_auth:
143
+ default_auth = True
144
+
145
+ seen: set[str] = set()
146
+ uniq: list[str] = []
147
+ for pth in permit_all:
148
+ if pth in seen:
149
+ continue
150
+ seen.add(pth)
151
+ uniq.append(pth)
152
+
153
+ return default_auth, uniq
154
+
155
+
156
+ def _is_auth_public_endpoint(path: str) -> bool:
157
+ return (
158
+ path.startswith("/auth")
159
+ or path.startswith("/api/authenticate")
160
+ or path.startswith("/api/auth/") # strict
161
+ or path.startswith("/api/register")
162
+ or path.startswith("/api/activate")
163
+ or path.startswith("/api/account/reset-password")
164
+ )
165
+
166
+
167
+ def _guess_access(path: str, *, default_auth: bool, permit_all: list[str]) -> str:
168
+ if _is_auth_public_endpoint(path):
169
+ return "PUBLIC"
170
+ if _matches_any(path, permit_all):
171
+ return "PUBLIC"
172
+ if path.startswith("/api/admin") or path.startswith("/admin"):
173
+ return "ADMIN"
174
+ if path.startswith("/me"):
175
+ return "CUSTOMER"
176
+ if default_auth:
177
+ return "AUTH"
178
+ return "PUBLIC"
179
+
180
+
181
+ def _display_role_str(path: str, roles: set[str], *, default_auth: bool, permit_all: list[str]) -> str:
182
+ if _is_auth_public_endpoint(path):
183
+ return "{PUBLIC}"
184
+ if roles:
185
+ return "{" + ", ".join(sorted(roles)) + "}"
186
+ return "{" + _guess_access(path, default_auth=default_auth, permit_all=permit_all) + "}"
187
+
188
+
189
+ def register_endpoints(app: typer.Typer) -> None:
190
+ @app.command()
191
+ def endpoints(
192
+ path: str = typer.Argument(".", help="Folder to scan"),
193
+ lang: str = typer.Option("all", "--lang", help="all | py | java"),
194
+ hidden: bool = typer.Option(False, "--hidden", help="Include hidden dotfiles/folders"),
195
+ no_gitignore: bool = typer.Option(False, "--no-gitignore", help="Do not apply .gitignore rules"),
196
+ fmt: str = typer.Option("clean", "--format", help="clean | text | json"),
197
+ only_main: bool = typer.Option(True, "--only-main/--all-sources", help="Only scan src/main (skip tests)"),
198
+ only_api: bool = typer.Option(False, "--only-api", help="Only show paths starting with /api"),
199
+ dedupe: bool = typer.Option(True, "--dedupe/--no-dedupe", help="Remove duplicates (method+path)"),
200
+ debug_security: bool = typer.Option(False, "--debug-security", help="Debug Spring Security detection"),
201
+ ):
202
+ folder = _validate_folder(path)
203
+ project_root = find_project_root(folder)
204
+
205
+ opts = EndpointScanOptions(
206
+ show_hidden=hidden,
207
+ use_gitignore=not no_gitignore,
208
+ )
209
+
210
+ lang_norm = lang.strip().lower()
211
+ if lang_norm not in {"all", "py", "java"}:
212
+ raise typer.BadParameter("Invalid --lang. Use: all | py | java")
213
+
214
+ t0 = time.perf_counter()
215
+ all_eps = []
216
+
217
+ files = list(iter_supported_source_files(project_root, opts))
218
+
219
+ files_for_security = files
220
+ if only_main:
221
+ tmp: list[Path] = []
222
+ for f in files:
223
+ fp = _norm_path(str(f)).lower()
224
+ if "/src/test/" in fp or "/target/" in fp or "/build/" in fp:
225
+ continue
226
+ tmp.append(f)
227
+ files_for_security = tmp
228
+
229
+ default_auth, permit_all = _detect_spring_security_policy(files_for_security)
230
+
231
+ if debug_security:
232
+ typer.echo("")
233
+ typer.echo("DEBUG SECURITY DETECTION")
234
+ typer.echo(" default_auth=" + str(default_auth))
235
+ typer.echo(" permit_all_count=" + str(len(permit_all)))
236
+ typer.echo(" security_candidates_detected=" + str(len(_LAST_SECURITY_CANDIDATES)))
237
+ for i, fp in enumerate(_LAST_SECURITY_CANDIDATES[:25], start=1):
238
+ typer.echo(f" {i:02d}. {fp}")
239
+ typer.echo("")
240
+
241
+ for f in files:
242
+ suf = f.suffix.lower()
243
+ if lang_norm == "py" and suf != ".py":
244
+ continue
245
+ if lang_norm == "java" and suf != ".java":
246
+ continue
247
+
248
+ try:
249
+ src = f.read_text(encoding="utf-8", errors="ignore")
250
+ except Exception:
251
+ continue
252
+
253
+ if suf == ".java":
254
+ all_eps.extend(
255
+ extract_spring_endpoints(
256
+ src,
257
+ str(f),
258
+ default_auth=default_auth,
259
+ permit_all=permit_all,
260
+ )
261
+ )
262
+ elif suf == ".py":
263
+ all_eps.extend(extract_python_endpoints(src, str(f)))
264
+
265
+ # NEW: OpenAPI fallback (spec-first repos like spring-petclinic-rest)
266
+ if lang_norm in {"all", "java"}:
267
+ spring_count = sum(1 for e in all_eps if e.framework == "spring")
268
+ if spring_count == 0:
269
+ all_eps.extend(extract_openapi_endpoints(project_root))
270
+
271
+ if only_main:
272
+ filtered = []
273
+ for e in all_eps:
274
+ fp = _norm_path(e.file)
275
+ if "/src/test/" in fp:
276
+ continue
277
+ if "/src/main/" in fp or "/main/" in fp:
278
+ filtered.append(e)
279
+ all_eps = filtered
280
+
281
+ if only_api:
282
+ all_eps = [e for e in all_eps if e.path.startswith("/api")]
283
+
284
+ if dedupe:
285
+ seen = set()
286
+ uniq = []
287
+ for e in all_eps:
288
+ key = (e.method, e.path)
289
+ if key in seen:
290
+ continue
291
+ seen.add(key)
292
+ uniq.append(e)
293
+ all_eps = uniq
294
+
295
+ all_eps.sort(key=lambda e: (e.path, e.method, e.file, e.line))
296
+ elapsed = time.perf_counter() - t0
297
+
298
+ fmt_norm = fmt.strip().lower()
299
+
300
+ if fmt_norm == "json":
301
+ typer.echo(json.dumps([e.to_dict() for e in all_eps], indent=2))
302
+ return
303
+
304
+ if fmt_norm == "text":
305
+ typer.echo(f"Project: {project_root}")
306
+ typer.echo(f"Found {len(all_eps)} endpoints in {elapsed:.3f}s")
307
+ typer.echo("")
308
+ for e in all_eps:
309
+ roles = set(e.roles or [])
310
+ typer.echo(
311
+ f"{e.method:6} {e.path:40} "
312
+ f"{_display_role_str(e.path, roles, default_auth=default_auth, permit_all=permit_all)} "
313
+ f"{e.file}:{e.line} {e.handler} <{e.framework}>"
314
+ )
315
+ return
316
+
317
+ typer.echo(f"Found {len(all_eps)} endpoints in {elapsed:.3f}s")
318
+ typer.echo("")
319
+
320
+ methods_by_path: dict[str, set[str]] = defaultdict(set)
321
+ roles_by_key: dict[tuple[str, str], set[str]] = defaultdict(set)
322
+
323
+ for e in all_eps:
324
+ methods_by_path[e.path].add(e.method)
325
+ if e.roles:
326
+ roles_by_key[(e.path, e.method)].update(e.roles)
327
+
328
+ paths = sorted(methods_by_path.keys())
329
+ for idx, p in enumerate(paths, start=1):
330
+ methods = sorted(methods_by_path[p])
331
+ parts = []
332
+ for m in methods:
333
+ role_set = roles_by_key.get((p, m), set())
334
+ parts.append(f"{m}{_display_role_str(p, role_set, default_auth=default_auth, permit_all=permit_all)}")
335
+ typer.echo(f"{idx:02d}. {p} [{', '.join(parts)}]")
@@ -0,0 +1,39 @@
1
+ """
2
+ codeintel_cli/commands/project/frontend_cmd.py
3
+ Registers the `aja frontend` command.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ from pathlib import Path
8
+ import typer
9
+
10
+ from ...errors import InvalidPathError
11
+ from ...core.project import find_project_root
12
+ from ...frontend.server import serve
13
+
14
+
15
+ def _validate_folder(path: str) -> Path:
16
+ folder = Path(path).resolve()
17
+ if not folder.exists() or not folder.is_dir():
18
+ raise InvalidPathError(message="Invalid folder", path=folder)
19
+ return folder
20
+
21
+
22
+ def register_frontend(app: typer.Typer) -> None:
23
+ @app.command("frontend", help="Launch a visual UI dashboard for models, endpoints, relationships & DTOs.")
24
+ def frontend(
25
+ path: str = typer.Argument(".", help="Project root folder"),
26
+ port: int = typer.Option(7821, "--port", "-p", help="Port to serve the UI on"),
27
+ ) -> None:
28
+ folder = _validate_folder(path)
29
+ project_root = find_project_root(folder)
30
+
31
+ typer.echo("=" * 60)
32
+ typer.echo(" CodeIntel Frontend")
33
+ typer.echo("=" * 60)
34
+ typer.echo(f" ROOT : {project_root}")
35
+ typer.echo(f" PORT : {port}")
36
+ typer.echo("")
37
+ typer.echo(" Scanning project…")
38
+
39
+ serve(project_root, port=port)
@@ -7,47 +7,104 @@ from ...core.project import find_project_root
7
7
  from ...scanner.scanner import find_all_supported_files
8
8
 
9
9
 
10
+ MODEL_DIRS = {
11
+ "model", "models",
12
+ "entity", "entities",
13
+ "domain", "domains",
14
+ "core",
15
+ "aggregate", "aggregates",
16
+ "persistence",
17
+ "db",
18
+ "data",
19
+ "schema",
20
+ "pojo",
21
+ "bean", "beans",
22
+ "orm",
23
+ "schemas",
24
+ "datamodel", "datamodels",
25
+ "table", "tables",
26
+ "record", "records",
27
+ "document", "documents",
28
+ "dao",
29
+ }
30
+
31
+ SKIP_DIRS = {
32
+ "test", "tests",
33
+ "controller", "controllers",
34
+ "service", "services",
35
+ "repository", "repositories",
36
+ "config", "configuration",
37
+ "util", "utils", "helper", "helpers",
38
+ "exception", "exceptions",
39
+ "security",
40
+ "mapper", "mappers",
41
+ "migration", "migrations",
42
+ "dto",
43
+ }
44
+
45
+ SKIP_SUFFIXES = (
46
+ "Repository",
47
+ "Mapper",
48
+ "Dao",
49
+ "Service",
50
+ "Controller",
51
+ "Config",
52
+ "Configuration",
53
+ "Util",
54
+ "Utils",
55
+ "Helper",
56
+ "Exception",
57
+ "Handler",
58
+ "Interceptor",
59
+ "Filter",
60
+ "Listener",
61
+ "Scheduler",
62
+ "Validator",
63
+ "Converter",
64
+ "Serializer",
65
+ "Deserializer",
66
+ "Factory",
67
+ "Builder",
68
+ "Specification",
69
+ )
70
+
71
+
10
72
  def _import_extractors():
11
73
  py = None
12
74
  jv = None
13
-
14
75
  try:
15
- from ...lang.python.models import extract_python_models as _py # type: ignore
16
-
76
+ from ...lang.python.models import extract_python_models as _py
17
77
  py = _py
18
78
  except Exception:
19
79
  pass
20
-
21
80
  try:
22
- from ...context.python_context import extract_python_models as _py # type: ignore
23
-
81
+ from ...context.python_context import extract_python_models as _py
24
82
  py = _py
25
83
  except Exception:
26
84
  pass
27
-
28
85
  try:
29
- from ...lang.java.models import extract_java_models as _jv # type: ignore
30
-
86
+ from ...lang.java.models import extract_java_models as _jv
31
87
  jv = _jv
32
88
  except Exception:
33
89
  pass
34
-
35
90
  try:
36
- from ...context.java_context import extract_java_models as _jv # type: ignore
37
-
91
+ from ...context.java_context import extract_java_models as _jv
38
92
  jv = _jv
39
93
  except Exception:
40
94
  pass
41
-
42
95
  return py, jv
43
96
 
44
97
 
45
98
  def _is_model_path(rel: Path) -> bool:
46
- keys = {"model", "models", "entity", "entities", "domain", "domains"}
47
99
  parts = [p.lower() for p in rel.parts]
48
100
  if "src" in parts and "test" in parts:
49
101
  return False
50
- return any(part in keys for part in parts)
102
+ if any(part in SKIP_DIRS for part in parts):
103
+ return False
104
+ stem = rel.stem
105
+ if any(stem.endswith(s) for s in SKIP_SUFFIXES):
106
+ return False
107
+ return any(part in MODEL_DIRS for part in parts)
51
108
 
52
109
 
53
110
  def register_models(app: typer.Typer) -> None:
@@ -57,13 +114,12 @@ def register_models(app: typer.Typer) -> None:
57
114
  ):
58
115
  root_path = Path(root).resolve()
59
116
  project_root = find_project_root(root_path)
60
-
61
117
  extract_python_models, extract_java_models = _import_extractors()
62
-
63
118
  files = find_all_supported_files(project_root)
64
119
 
65
120
  candidates: list[Path] = []
66
121
  pr = project_root.resolve()
122
+
67
123
  for f in files:
68
124
  fr = f.resolve()
69
125
  try:
@@ -74,17 +130,14 @@ def register_models(app: typer.Typer) -> None:
74
130
  candidates.append(fr)
75
131
 
76
132
  out: list[str] = []
77
-
78
133
  for f in sorted(candidates, key=lambda x: str(x).lower()):
79
134
  ext = f.suffix.lower()
80
-
81
135
  if ext == ".py" and extract_python_models is not None:
82
136
  defs = extract_python_models(f, project_root)
83
137
  elif ext == ".java" and extract_java_models is not None:
84
138
  defs = extract_java_models(f, project_root)
85
139
  else:
86
140
  continue
87
-
88
141
  for d in defs:
89
142
  out.append(f"{d.name} ({d.kind}) {d.file}")
90
143
  for fld in d.fields: