arc-cloud 0.1.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.
- arc_cloud/__init__.py +4 -0
- arc_cloud/blueprint/__init__.py +35 -0
- arc_cloud/blueprint/generator.py +89 -0
- arc_cloud/blueprint/models.py +108 -0
- arc_cloud/commands/__init__.py +5 -0
- arc_cloud/commands/scan.py +177 -0
- arc_cloud/main.py +48 -0
- arc_cloud/scanner/__init__.py +41 -0
- arc_cloud/scanner/config_detector.py +54 -0
- arc_cloud/scanner/dependencies.py +246 -0
- arc_cloud/scanner/engine.py +252 -0
- arc_cloud/scanner/frameworks.py +341 -0
- arc_cloud/scanner/languages.py +82 -0
- arc_cloud/scanner/platforms.py +64 -0
- arc_cloud/scanner/structure.py +54 -0
- arc_cloud/utils/__init__.py +16 -0
- arc_cloud/utils/filesystem.py +143 -0
- arc_cloud/utils/security.py +121 -0
- arc_cloud-0.1.0.dist-info/METADATA +303 -0
- arc_cloud-0.1.0.dist-info/RECORD +23 -0
- arc_cloud-0.1.0.dist-info/WHEEL +4 -0
- arc_cloud-0.1.0.dist-info/entry_points.txt +2 -0
- arc_cloud-0.1.0.dist-info/licenses/LICENSE +21 -0
arc_cloud/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Software Blueprint module for ARC CLOUD."""
|
|
2
|
+
|
|
3
|
+
from arc_cloud.blueprint.generator import BlueprintGenerator
|
|
4
|
+
from arc_cloud.blueprint.models import (
|
|
5
|
+
ArchitectureSummary,
|
|
6
|
+
Blueprint,
|
|
7
|
+
ConfigFileInfo,
|
|
8
|
+
DependencyInfo,
|
|
9
|
+
FrameworkMetric,
|
|
10
|
+
LanguageMetric,
|
|
11
|
+
PlatformInfo,
|
|
12
|
+
PlatformType,
|
|
13
|
+
ProjectInfo,
|
|
14
|
+
ProjectType,
|
|
15
|
+
ScannerMetadata,
|
|
16
|
+
StructureArea,
|
|
17
|
+
StructureInfo,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ArchitectureSummary",
|
|
22
|
+
"Blueprint",
|
|
23
|
+
"BlueprintGenerator",
|
|
24
|
+
"ConfigFileInfo",
|
|
25
|
+
"DependencyInfo",
|
|
26
|
+
"FrameworkMetric",
|
|
27
|
+
"LanguageMetric",
|
|
28
|
+
"PlatformInfo",
|
|
29
|
+
"PlatformType",
|
|
30
|
+
"ProjectInfo",
|
|
31
|
+
"ProjectType",
|
|
32
|
+
"ScannerMetadata",
|
|
33
|
+
"StructureArea",
|
|
34
|
+
"StructureInfo",
|
|
35
|
+
]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Blueprint generator and serializer for ARC CLOUD CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from arc_cloud.blueprint.models import (
|
|
10
|
+
ArchitectureSummary,
|
|
11
|
+
Blueprint,
|
|
12
|
+
ConfigFileInfo,
|
|
13
|
+
DependencyInfo,
|
|
14
|
+
FrameworkMetric,
|
|
15
|
+
LanguageMetric,
|
|
16
|
+
PlatformInfo,
|
|
17
|
+
ProjectInfo,
|
|
18
|
+
ProjectType,
|
|
19
|
+
ScannerMetadata,
|
|
20
|
+
StructureInfo,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BlueprintGenerator:
|
|
25
|
+
"""Generates a standardized Software Blueprint v1.0."""
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def create(
|
|
29
|
+
project_name: str,
|
|
30
|
+
project_type: ProjectType,
|
|
31
|
+
root_path: str,
|
|
32
|
+
languages: List[LanguageMetric],
|
|
33
|
+
frameworks: List[FrameworkMetric],
|
|
34
|
+
platforms: List[PlatformInfo],
|
|
35
|
+
dependencies: List[DependencyInfo],
|
|
36
|
+
configuration_files: List[ConfigFileInfo],
|
|
37
|
+
structure: StructureInfo,
|
|
38
|
+
architecture: ArchitectureSummary,
|
|
39
|
+
warnings: List[str],
|
|
40
|
+
duration_seconds: float,
|
|
41
|
+
files_scanned: int,
|
|
42
|
+
scanner_version: str = "0.1.0",
|
|
43
|
+
description: Optional[str] = None,
|
|
44
|
+
) -> Blueprint:
|
|
45
|
+
scanner = ScannerMetadata(
|
|
46
|
+
name="ARC CLOUD CLI",
|
|
47
|
+
version=scanner_version,
|
|
48
|
+
duration_seconds=round(duration_seconds, 3),
|
|
49
|
+
files_scanned=files_scanned,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
project = ProjectInfo(
|
|
53
|
+
name=project_name,
|
|
54
|
+
type=project_type,
|
|
55
|
+
root_path=root_path,
|
|
56
|
+
description=description,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return Blueprint(
|
|
60
|
+
schema_version="1.0",
|
|
61
|
+
scanner=scanner,
|
|
62
|
+
project=project,
|
|
63
|
+
languages=languages,
|
|
64
|
+
frameworks=frameworks,
|
|
65
|
+
platforms=platforms,
|
|
66
|
+
dependencies=dependencies,
|
|
67
|
+
configuration_files=configuration_files,
|
|
68
|
+
structure=structure,
|
|
69
|
+
architecture=architecture,
|
|
70
|
+
warnings=warnings,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def to_json(blueprint: Blueprint, indent: int = 2) -> str:
|
|
75
|
+
"""Serialize the blueprint model to a formatted JSON string."""
|
|
76
|
+
return blueprint.model_dump_json(indent=indent)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def to_dict(blueprint: Blueprint) -> Dict[str, Any]:
|
|
80
|
+
"""Serialize the blueprint model to a Python dictionary."""
|
|
81
|
+
return blueprint.model_dump(mode="json")
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def save_to_file(blueprint: Blueprint, output_path: str | Path) -> Path:
|
|
85
|
+
"""Write the blueprint JSON to a specified file path."""
|
|
86
|
+
path = Path(output_path).resolve()
|
|
87
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
path.write_text(BlueprintGenerator.to_json(blueprint), encoding="utf-8")
|
|
89
|
+
return path
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Pydantic data models for ARC CLOUD Software Blueprint schema v1.0."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProjectType(str, Enum):
|
|
12
|
+
MOBILE = "Mobile Application"
|
|
13
|
+
WEB = "Web Application"
|
|
14
|
+
BACKEND = "Backend"
|
|
15
|
+
FULL_STACK = "Full Stack"
|
|
16
|
+
DESKTOP = "Desktop Application"
|
|
17
|
+
LIBRARY = "Library"
|
|
18
|
+
CLI = "CLI Application"
|
|
19
|
+
UNKNOWN = "Unknown"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlatformType(str, Enum):
|
|
23
|
+
ANDROID = "Android"
|
|
24
|
+
IOS = "iOS"
|
|
25
|
+
WEB = "Web"
|
|
26
|
+
WINDOWS = "Windows"
|
|
27
|
+
MACOS = "macOS"
|
|
28
|
+
LINUX = "Linux"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ProjectInfo(BaseModel):
|
|
32
|
+
name: str = Field(description="Name of the analyzed project")
|
|
33
|
+
type: ProjectType = Field(default=ProjectType.UNKNOWN, description="Classified project type")
|
|
34
|
+
root_path: Optional[str] = Field(default=None, description="Absolute or relative root path of the project")
|
|
35
|
+
description: Optional[str] = Field(default=None, description="Optional project description from manifest")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LanguageMetric(BaseModel):
|
|
39
|
+
name: str = Field(description="Programming language name")
|
|
40
|
+
files: int = Field(ge=0, description="Total number of files identified for this language")
|
|
41
|
+
percentage: float = Field(ge=0.0, le=100.0, description="Percentage of total code files")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FrameworkMetric(BaseModel):
|
|
45
|
+
name: str = Field(description="Framework name (e.g. Flutter, React, FastAPI)")
|
|
46
|
+
version: Optional[str] = Field(default=None, description="Detected framework version, if available")
|
|
47
|
+
category: Optional[str] = Field(default=None, description="Frontend, Backend, Mobile, Fullstack, etc.")
|
|
48
|
+
confidence: float = Field(default=1.0, ge=0.0, le=1.0, description="Detection confidence score")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class PlatformInfo(BaseModel):
|
|
52
|
+
name: PlatformType = Field(description="Target platform name")
|
|
53
|
+
source: Optional[str] = Field(default=None, description="Directory or configuration that confirmed the platform")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class DependencyInfo(BaseModel):
|
|
57
|
+
name: str = Field(description="Dependency package name")
|
|
58
|
+
version: Optional[str] = Field(default=None, description="Version specifier or pinned version")
|
|
59
|
+
source: str = Field(description="Manifest file source (e.g. package.json, requirements.txt)")
|
|
60
|
+
type: Optional[str] = Field(default="runtime", description="Dependency type: runtime, dev, peer, test")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ConfigFileInfo(BaseModel):
|
|
64
|
+
path: str = Field(description="Relative path to configuration file")
|
|
65
|
+
name: str = Field(description="Filename of the configuration")
|
|
66
|
+
type: str = Field(description="Category of config, e.g. package, docker, build, git, documentation")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class StructureArea(BaseModel):
|
|
70
|
+
name: str = Field(description="Functional area, e.g. source, tests, assets, platform, docs, config")
|
|
71
|
+
paths: List[str] = Field(default_factory=list, description="Relative paths belonging to this area")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class StructureInfo(BaseModel):
|
|
75
|
+
areas: List[StructureArea] = Field(default_factory=list, description="Classified functional directory areas")
|
|
76
|
+
total_files: int = Field(default=0, ge=0, description="Total scanned files considered")
|
|
77
|
+
total_directories: int = Field(default=0, ge=0, description="Total directories visited")
|
|
78
|
+
ignored_directories: List[str] = Field(default_factory=list, description="Ignored directory names detected")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ArchitectureSummary(BaseModel):
|
|
82
|
+
patterns: List[str] = Field(default_factory=list, description="Detected architectural patterns or indicators")
|
|
83
|
+
details: Dict[str, Any] = Field(default_factory=dict, description="Structural indicators and metadata")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ScannerMetadata(BaseModel):
|
|
87
|
+
name: str = Field(default="ARC CLOUD CLI", description="Name of the scanner engine")
|
|
88
|
+
version: str = Field(default="0.1.0", description="Scanner CLI version")
|
|
89
|
+
timestamp: datetime = Field(
|
|
90
|
+
default_factory=lambda: datetime.now(timezone.utc),
|
|
91
|
+
description="Scan completion timestamp in UTC"
|
|
92
|
+
)
|
|
93
|
+
duration_seconds: float = Field(default=0.0, ge=0.0, description="Total scan execution duration in seconds")
|
|
94
|
+
files_scanned: int = Field(default=0, ge=0, description="Count of files analyzed")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Blueprint(BaseModel):
|
|
98
|
+
schema_version: str = Field(default="1.0", description="Software Blueprint schema version")
|
|
99
|
+
scanner: ScannerMetadata = Field(default_factory=ScannerMetadata)
|
|
100
|
+
project: ProjectInfo
|
|
101
|
+
languages: List[LanguageMetric] = Field(default_factory=list)
|
|
102
|
+
frameworks: List[FrameworkMetric] = Field(default_factory=list)
|
|
103
|
+
platforms: List[PlatformInfo] = Field(default_factory=list)
|
|
104
|
+
dependencies: List[DependencyInfo] = Field(default_factory=list)
|
|
105
|
+
configuration_files: List[ConfigFileInfo] = Field(default_factory=list)
|
|
106
|
+
structure: StructureInfo = Field(default_factory=StructureInfo)
|
|
107
|
+
architecture: ArchitectureSummary = Field(default_factory=ArchitectureSummary)
|
|
108
|
+
warnings: List[str] = Field(default_factory=list)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""The 'scan' command for ARC CLOUD CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
from arc_cloud.blueprint.generator import BlueprintGenerator
|
|
15
|
+
from arc_cloud.blueprint.models import Blueprint
|
|
16
|
+
from arc_cloud.scanner.engine import ScannerEngine
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
err_console = Console(stderr=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def scan_command(
|
|
23
|
+
path: Optional[str] = typer.Argument(
|
|
24
|
+
None,
|
|
25
|
+
help="Path to the software project to analyze (defaults to current working directory).",
|
|
26
|
+
show_default=False,
|
|
27
|
+
),
|
|
28
|
+
json_output: bool = typer.Option(
|
|
29
|
+
False,
|
|
30
|
+
"--json",
|
|
31
|
+
"-j",
|
|
32
|
+
help="Output the normalized Software Blueprint as raw JSON to stdout.",
|
|
33
|
+
),
|
|
34
|
+
output_file: Optional[str] = typer.Option(
|
|
35
|
+
None,
|
|
36
|
+
"--output",
|
|
37
|
+
"-o",
|
|
38
|
+
help="Save the normalized Software Blueprint JSON to a specified file.",
|
|
39
|
+
),
|
|
40
|
+
max_files: int = typer.Option(
|
|
41
|
+
20_000,
|
|
42
|
+
"--max-files",
|
|
43
|
+
help="Maximum number of files to inspect before stopping.",
|
|
44
|
+
),
|
|
45
|
+
max_depth: int = typer.Option(
|
|
46
|
+
20,
|
|
47
|
+
"--max-depth",
|
|
48
|
+
help="Maximum directory recursion depth to traverse.",
|
|
49
|
+
),
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Analyze a software project statically and generate a Software Blueprint."""
|
|
52
|
+
target_path = Path(path).resolve() if path else Path.cwd()
|
|
53
|
+
|
|
54
|
+
# Pre-flight path validations
|
|
55
|
+
if not target_path.exists():
|
|
56
|
+
err_console.print(f"[bold red]✗ Error:[/bold red] Project path does not exist: '{target_path}'")
|
|
57
|
+
raise typer.Exit(code=1)
|
|
58
|
+
|
|
59
|
+
if not target_path.is_dir():
|
|
60
|
+
err_console.print(f"[bold red]✗ Error:[/bold red] Project path is not a directory: '{target_path}'")
|
|
61
|
+
raise typer.Exit(code=1)
|
|
62
|
+
|
|
63
|
+
engine = ScannerEngine(max_files=max_files, max_depth=max_depth)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
blueprint = engine.scan(target_path)
|
|
67
|
+
except PermissionError as exc:
|
|
68
|
+
err_console.print(f"[bold red]✗ Permission Error:[/bold red] Unable to read project directory: {exc}")
|
|
69
|
+
raise typer.Exit(code=1)
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
err_console.print(f"[bold red]✗ Scan Failed:[/bold red] An unexpected error occurred: {exc}")
|
|
72
|
+
raise typer.Exit(code=1)
|
|
73
|
+
|
|
74
|
+
# Save to file if requested
|
|
75
|
+
if output_file:
|
|
76
|
+
try:
|
|
77
|
+
saved_path = BlueprintGenerator.save_to_file(blueprint, output_file)
|
|
78
|
+
if not json_output:
|
|
79
|
+
console.print(f"[bold green]✓[/bold green] Blueprint written to [cyan]{output_file}[/cyan]\n")
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
err_console.print(f"[bold red]✗ Error saving blueprint:[/bold red] {exc}")
|
|
82
|
+
raise typer.Exit(code=1)
|
|
83
|
+
|
|
84
|
+
# Handle raw JSON output
|
|
85
|
+
if json_output:
|
|
86
|
+
print(BlueprintGenerator.to_json(blueprint))
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
# Render professional Rich Terminal UI
|
|
90
|
+
_render_rich_report(blueprint)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _render_rich_report(blueprint: Blueprint) -> None:
|
|
94
|
+
"""Renders the Software X-Ray report using Rich."""
|
|
95
|
+
console.print()
|
|
96
|
+
console.rule("[bold cyan]ARC CLOUD SOFTWARE X-RAY[/bold cyan]", style="cyan")
|
|
97
|
+
console.print()
|
|
98
|
+
|
|
99
|
+
# 1. Project Summary
|
|
100
|
+
summary_table = Table(box=None, show_header=False, padding=(0, 2))
|
|
101
|
+
summary_table.add_column("Key", style="bold white", width=12)
|
|
102
|
+
summary_table.add_column("Value", style="cyan")
|
|
103
|
+
summary_table.add_row("Name:", f"[bold white]{blueprint.project.name}[/bold white]")
|
|
104
|
+
summary_table.add_row("Type:", f"[bold green]{blueprint.project.type.value}[/bold green]")
|
|
105
|
+
summary_table.add_row("Files:", f"{blueprint.scanner.files_scanned:,}")
|
|
106
|
+
if blueprint.scanner.duration_seconds > 0:
|
|
107
|
+
summary_table.add_row("Duration:", f"{blueprint.scanner.duration_seconds:.2f}s")
|
|
108
|
+
console.print(Panel(summary_table, title="[bold]Project Summary[/bold]", border_style="dim"))
|
|
109
|
+
|
|
110
|
+
# 2. Languages & Frameworks Side-by-Side or Sequential
|
|
111
|
+
lang_table = Table(title="Languages", title_style="bold magenta", box=None, padding=(0, 2))
|
|
112
|
+
lang_table.add_column("Language", style="bold")
|
|
113
|
+
lang_table.add_column("Files", justify="right", style="dim")
|
|
114
|
+
lang_table.add_column("Share", justify="right", style="magenta")
|
|
115
|
+
|
|
116
|
+
if blueprint.languages:
|
|
117
|
+
for lang in blueprint.languages[:7]: # Top 7 languages
|
|
118
|
+
lang_table.add_row(lang.name, str(lang.files), f"{lang.percentage}%")
|
|
119
|
+
else:
|
|
120
|
+
lang_table.add_row("[dim]None detected[/dim]", "-", "-")
|
|
121
|
+
|
|
122
|
+
console.print(lang_table)
|
|
123
|
+
console.print()
|
|
124
|
+
|
|
125
|
+
# 3. Frameworks & Platforms
|
|
126
|
+
fw_plat_table = Table(box=None, show_header=False, padding=(0, 2))
|
|
127
|
+
fw_plat_table.add_column("Category", style="bold yellow", width=14)
|
|
128
|
+
fw_plat_table.add_column("Details", style="white")
|
|
129
|
+
|
|
130
|
+
if blueprint.frameworks:
|
|
131
|
+
fw_list = ", ".join(f"{f.name}" + (f" ({f.version})" if f.version else "") for f in blueprint.frameworks)
|
|
132
|
+
fw_plat_table.add_row("Frameworks:", fw_list)
|
|
133
|
+
else:
|
|
134
|
+
fw_plat_table.add_row("Frameworks:", "[dim]None detected[/dim]")
|
|
135
|
+
|
|
136
|
+
if blueprint.platforms:
|
|
137
|
+
plat_list = ", ".join(p.name.value for p in blueprint.platforms)
|
|
138
|
+
fw_plat_table.add_row("Platforms:", plat_list)
|
|
139
|
+
else:
|
|
140
|
+
fw_plat_table.add_row("Platforms:", "[dim]Generic / Agnostic[/dim]")
|
|
141
|
+
|
|
142
|
+
dep_count = len(blueprint.dependencies)
|
|
143
|
+
fw_plat_table.add_row("Dependencies:", f"[bold]{dep_count}[/bold] identified")
|
|
144
|
+
|
|
145
|
+
console.print(Panel(fw_plat_table, title="[bold]Ecosystem & Stack[/bold]", border_style="dim"))
|
|
146
|
+
|
|
147
|
+
# 4. Structure Areas
|
|
148
|
+
if blueprint.structure.areas:
|
|
149
|
+
struct_table = Table(title="Structure Classification", title_style="bold blue", box=None, padding=(0, 2))
|
|
150
|
+
struct_table.add_column("Area", style="bold")
|
|
151
|
+
struct_table.add_column("Directories", style="dim")
|
|
152
|
+
for area in blueprint.structure.areas:
|
|
153
|
+
display_paths = ", ".join(area.paths[:4])
|
|
154
|
+
if len(area.paths) > 4:
|
|
155
|
+
display_paths += f" (+{len(area.paths) - 4} more)"
|
|
156
|
+
struct_table.add_row(area.name.capitalize(), display_paths)
|
|
157
|
+
console.print(struct_table)
|
|
158
|
+
console.print()
|
|
159
|
+
|
|
160
|
+
# 5. Architecture Summary
|
|
161
|
+
if blueprint.architecture.patterns:
|
|
162
|
+
arch_text = Text(", ".join(blueprint.architecture.patterns), style="green")
|
|
163
|
+
console.print(f"[bold]Architecture Patterns:[/bold] {arch_text}\n")
|
|
164
|
+
|
|
165
|
+
# 6. Warnings
|
|
166
|
+
if blueprint.warnings:
|
|
167
|
+
console.print("[bold yellow]Warnings:[/bold yellow]")
|
|
168
|
+
for warn in blueprint.warnings:
|
|
169
|
+
console.print(f" [yellow]{warn}[/yellow]")
|
|
170
|
+
console.print()
|
|
171
|
+
|
|
172
|
+
# Footer rule
|
|
173
|
+
console.rule(
|
|
174
|
+
f"[bold green]✓ Scan completed in {blueprint.scanner.duration_seconds:.2f} seconds[/bold green]",
|
|
175
|
+
style="green",
|
|
176
|
+
)
|
|
177
|
+
console.print()
|
arc_cloud/main.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Main entry point for ARC CLOUD CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from arc_cloud import __app_name__, __version__
|
|
10
|
+
from arc_cloud.commands.scan import scan_command
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(
|
|
15
|
+
name=__app_name__,
|
|
16
|
+
help="ARC CLOUD CLI — Local, deterministic Software X-Ray scanner and Software Blueprint generator.",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
add_completion=False,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Register primary command
|
|
22
|
+
app.command(name="scan", help="Analyze a software project statically and generate a Software Blueprint.")(scan_command)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _version_callback(value: bool) -> None:
|
|
26
|
+
if value:
|
|
27
|
+
console.print(f"[bold cyan]{__app_name__}[/bold cyan] version [green]{__version__}[/green]")
|
|
28
|
+
raise typer.Exit()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.callback()
|
|
33
|
+
def main(
|
|
34
|
+
version: Optional[bool] = typer.Option(
|
|
35
|
+
None,
|
|
36
|
+
"--version",
|
|
37
|
+
"-v",
|
|
38
|
+
help="Show ARC CLOUD CLI version and exit.",
|
|
39
|
+
callback=_version_callback,
|
|
40
|
+
is_eager=True,
|
|
41
|
+
)
|
|
42
|
+
) -> None:
|
|
43
|
+
"""ARC CLOUD — Software X-Ray platform."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
app()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Scanner engine and detector modules for ARC CLOUD CLI."""
|
|
2
|
+
|
|
3
|
+
from arc_cloud.scanner.config_detector import ConfigDetector
|
|
4
|
+
from arc_cloud.scanner.dependencies import DependencyDetector
|
|
5
|
+
from arc_cloud.scanner.engine import ScannerEngine
|
|
6
|
+
from arc_cloud.scanner.frameworks import (
|
|
7
|
+
BaseFrameworkDetector,
|
|
8
|
+
DjangoDetector,
|
|
9
|
+
DotNetDetector,
|
|
10
|
+
FastAPIDetector,
|
|
11
|
+
FlutterDetector,
|
|
12
|
+
FrameworkRegistry,
|
|
13
|
+
NextJsDetector,
|
|
14
|
+
NodeJsDetector,
|
|
15
|
+
ReactDetector,
|
|
16
|
+
ScanContext,
|
|
17
|
+
SpringBootDetector,
|
|
18
|
+
)
|
|
19
|
+
from arc_cloud.scanner.languages import LanguageDetector
|
|
20
|
+
from arc_cloud.scanner.platforms import PlatformDetector
|
|
21
|
+
from arc_cloud.scanner.structure import StructureAnalyzer
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"BaseFrameworkDetector",
|
|
25
|
+
"ConfigDetector",
|
|
26
|
+
"DependencyDetector",
|
|
27
|
+
"DjangoDetector",
|
|
28
|
+
"DotNetDetector",
|
|
29
|
+
"FastAPIDetector",
|
|
30
|
+
"FlutterDetector",
|
|
31
|
+
"FrameworkRegistry",
|
|
32
|
+
"LanguageDetector",
|
|
33
|
+
"NextJsDetector",
|
|
34
|
+
"NodeJsDetector",
|
|
35
|
+
"PlatformDetector",
|
|
36
|
+
"ReactDetector",
|
|
37
|
+
"ScanContext",
|
|
38
|
+
"ScannerEngine",
|
|
39
|
+
"SpringBootDetector",
|
|
40
|
+
"StructureAnalyzer",
|
|
41
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Configuration file detector for ARC CLOUD CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from arc_cloud.blueprint.models import ConfigFileInfo
|
|
10
|
+
|
|
11
|
+
# Recognized configuration patterns and categories
|
|
12
|
+
KNOWN_CONFIG_PATTERNS = [
|
|
13
|
+
(r"^pubspec\.ya?ml$", "package", "Dart/Flutter Package Spec"),
|
|
14
|
+
(r"^package\.json$", "package", "Node.js Package Manifest"),
|
|
15
|
+
(r"^tsconfig.*\.json$", "build", "TypeScript Configuration"),
|
|
16
|
+
(r"^next\.config\.[mc]?[jt]s$", "framework", "Next.js Configuration"),
|
|
17
|
+
(r"^vite\.config\.[mc]?[jt]s$", "build", "Vite Configuration"),
|
|
18
|
+
(r"^requirements.*\.txt$", "package", "Python Requirements"),
|
|
19
|
+
(r"^pyproject\.toml$", "package", "Python Project Configuration"),
|
|
20
|
+
(r"^Pipfile(\.lock)?$", "package", "Pipenv Manifest"),
|
|
21
|
+
(r"^pom\.xml$", "build", "Maven Build Descriptor"),
|
|
22
|
+
(r"^(build|settings)\.gradle(\.kts)?$", "build", "Gradle Build Script"),
|
|
23
|
+
(r"^.*\.csproj$", "build", "C# Project Configuration"),
|
|
24
|
+
(r"^.*\.sln$", "build", ".NET Solution File"),
|
|
25
|
+
(r"^(Dockerfile.*|docker-compose.*\.ya?ml)$", "container", "Docker Configuration"),
|
|
26
|
+
(r"^\.gitignore$", "git", "Git Ignore Rules"),
|
|
27
|
+
(r"^README(\.md|\.rst|\.txt)?$", "documentation", "Project Documentation"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ConfigDetector:
|
|
32
|
+
"""Detects important configuration and manifest files in the project."""
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def detect(relative_files: List[str]) -> List[ConfigFileInfo]:
|
|
36
|
+
detected: List[ConfigFileInfo] = []
|
|
37
|
+
seen_paths = set()
|
|
38
|
+
|
|
39
|
+
for rel_file in relative_files:
|
|
40
|
+
file_name = Path(rel_file).name
|
|
41
|
+
for pattern, cat, _desc in KNOWN_CONFIG_PATTERNS:
|
|
42
|
+
if re.match(pattern, file_name, re.IGNORECASE):
|
|
43
|
+
if rel_file not in seen_paths:
|
|
44
|
+
seen_paths.add(rel_file)
|
|
45
|
+
detected.append(
|
|
46
|
+
ConfigFileInfo(
|
|
47
|
+
path=rel_file,
|
|
48
|
+
name=file_name,
|
|
49
|
+
type=cat,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
return sorted(detected, key=lambda c: (c.type, c.path))
|