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.
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/PKG-INFO +1 -1
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/PKG-INFO +1 -1
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/SOURCES.txt +5 -0
- aja_codeintel-0.1.5/codeintel_cli/commands/project/__init__.py +35 -0
- aja_codeintel-0.1.5/codeintel_cli/commands/project/endpoints_cmd.py +335 -0
- aja_codeintel-0.1.5/codeintel_cli/commands/project/frontend_cmd.py +39 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/models_cmd.py +73 -20
- aja_codeintel-0.1.5/codeintel_cli/commands/project/overview_cmd.py +394 -0
- aja_codeintel-0.1.5/codeintel_cli/commands/project/types_cmd.py +318 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/java_rel.py +73 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/java_spring.py +220 -152
- aja_codeintel-0.1.5/codeintel_cli/endpoints/openapi_spec.py +224 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/scan.py +8 -2
- aja_codeintel-0.1.5/codeintel_cli/frontend/__init__.py +3 -0
- aja_codeintel-0.1.5/codeintel_cli/frontend/server.py +885 -0
- aja_codeintel-0.1.5/codeintel_cli/lang/java/models.py +258 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/pyproject.toml +1 -1
- aja_codeintel-0.1.4/codeintel_cli/commands/project/__init__.py +0 -29
- aja_codeintel-0.1.4/codeintel_cli/commands/project/endpoints_cmd.py +0 -146
- aja_codeintel-0.1.4/codeintel_cli/commands/project/types_cmd.py +0 -351
- aja_codeintel-0.1.4/codeintel_cli/lang/java/models.py +0 -105
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/LICENSE +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/README.md +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/dependency_links.txt +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/entry_points.txt +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/requires.txt +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/aja_codeintel.egg-info/top_level.txt +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/__main__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/cli.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/deps_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/related_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/relsymbols_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/graph/reverse_related_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/copy_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/open_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/nav/where_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/context_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/folder_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/imports_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/modeltree_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/new.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/resolve_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/scan_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/servicemap_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/tree_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/commands/project/version_cmd.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/java_context.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/java_service.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/python_context.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/python_rel.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/context/python_service.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/fuzzy.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/opener.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/project.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/resolve_folder.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/resolve_model_target.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/resolve_target.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/timing.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/core/where.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/cache.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/operations.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/db/schema.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/models.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/endpoints/python_web.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/errors.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/builder.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/query.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/graph/traverse.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/java/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/java/engine.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/java/resolve.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/python/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/python/engine.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/python/models.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/lang/router.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/parser/imports.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/parser/resolve.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/parser/symbols.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/scanner/__init__.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/codeintel_cli/scanner/scanner.py +0 -0
- {aja_codeintel-0.1.4 → aja_codeintel-0.1.5}/setup.cfg +0 -0
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|