feat(analyzer): add iOS/Swift project detection (#389)
- Add Swift/iOS detection via Package.swift or .xcodeproj - Detect SwiftUI, UIKit, AppKit frameworks from imports - Identify Apple frameworks (Combine, MapKit, WidgetKit, etc.) - Parse SPM dependencies from xcodeproj or Package.swift - Add mobile/desktop project types with icons and colors - Display Apple Frameworks and SPM Dependencies in Context UI This enables Auto-Claude to provide rich context for iOS/macOS projects, feeding framework and dependency info into Ideation and Roadmap features. Signed-off-by: Black Circle Sentinel <mludlow000@icloud.com>
This commit is contained in:
@@ -82,6 +82,15 @@ class FrameworkAnalyzer(BaseAnalyzer):
|
||||
content = self._read_file("Gemfile")
|
||||
self._detect_ruby_framework(content)
|
||||
|
||||
# Swift/iOS detection
|
||||
elif self._exists("Package.swift") or any(self.path.glob("*.xcodeproj")):
|
||||
self.analysis["language"] = "Swift"
|
||||
if self._exists("Package.swift"):
|
||||
self.analysis["package_manager"] = "Swift Package Manager"
|
||||
else:
|
||||
self.analysis["package_manager"] = "Xcode"
|
||||
self._detect_swift_framework()
|
||||
|
||||
def _detect_python_framework(self, content: str) -> None:
|
||||
"""Detect Python framework."""
|
||||
from .port_detector import PortDetector
|
||||
@@ -290,6 +299,109 @@ class FrameworkAnalyzer(BaseAnalyzer):
|
||||
if "sidekiq" in content.lower():
|
||||
self.analysis["task_queue"] = "Sidekiq"
|
||||
|
||||
def _detect_swift_framework(self) -> None:
|
||||
"""Detect Swift/iOS framework and dependencies."""
|
||||
try:
|
||||
# Scan Swift files for imports, excluding hidden/vendor dirs
|
||||
swift_files = []
|
||||
for swift_file in self.path.rglob("*.swift"):
|
||||
# Skip hidden directories, node_modules, .worktrees, etc.
|
||||
if any(
|
||||
part.startswith(".") or part in ("node_modules", "Pods", "Carthage")
|
||||
for part in swift_file.parts
|
||||
):
|
||||
continue
|
||||
swift_files.append(swift_file)
|
||||
if len(swift_files) >= 50: # Limit for performance
|
||||
break
|
||||
|
||||
imports = set()
|
||||
for swift_file in swift_files:
|
||||
try:
|
||||
content = swift_file.read_text(encoding="utf-8", errors="ignore")
|
||||
for line in content.split("\n"):
|
||||
line = line.strip()
|
||||
if line.startswith("import "):
|
||||
module = line.replace("import ", "").split()[0]
|
||||
imports.add(module)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Detect UI framework
|
||||
if "SwiftUI" in imports:
|
||||
self.analysis["framework"] = "SwiftUI"
|
||||
self.analysis["type"] = "mobile"
|
||||
elif "UIKit" in imports:
|
||||
self.analysis["framework"] = "UIKit"
|
||||
self.analysis["type"] = "mobile"
|
||||
elif "AppKit" in imports:
|
||||
self.analysis["framework"] = "AppKit"
|
||||
self.analysis["type"] = "desktop"
|
||||
|
||||
# Detect iOS/Apple frameworks
|
||||
apple_frameworks = []
|
||||
framework_map = {
|
||||
"Combine": "Combine",
|
||||
"CoreData": "CoreData",
|
||||
"MapKit": "MapKit",
|
||||
"WidgetKit": "WidgetKit",
|
||||
"CoreLocation": "CoreLocation",
|
||||
"StoreKit": "StoreKit",
|
||||
"CloudKit": "CloudKit",
|
||||
"ActivityKit": "ActivityKit",
|
||||
"UserNotifications": "UserNotifications",
|
||||
}
|
||||
for key, name in framework_map.items():
|
||||
if key in imports:
|
||||
apple_frameworks.append(name)
|
||||
|
||||
if apple_frameworks:
|
||||
self.analysis["apple_frameworks"] = apple_frameworks
|
||||
|
||||
# Detect SPM dependencies from Package.swift or xcodeproj
|
||||
dependencies = self._detect_spm_dependencies()
|
||||
if dependencies:
|
||||
self.analysis["spm_dependencies"] = dependencies
|
||||
except Exception:
|
||||
# Silently fail if Swift detection has issues
|
||||
pass
|
||||
|
||||
def _detect_spm_dependencies(self) -> list[str]:
|
||||
"""Detect Swift Package Manager dependencies."""
|
||||
dependencies = []
|
||||
|
||||
# Try Package.swift first
|
||||
if self._exists("Package.swift"):
|
||||
content = self._read_file("Package.swift")
|
||||
# Look for .package(url: "...", patterns
|
||||
import re
|
||||
|
||||
urls = re.findall(r'\.package\s*\([^)]*url:\s*"([^"]+)"', content)
|
||||
for url in urls:
|
||||
# Extract package name from URL
|
||||
name = url.rstrip("/").split("/")[-1].replace(".git", "")
|
||||
if name:
|
||||
dependencies.append(name)
|
||||
|
||||
# Also check xcodeproj for XCRemoteSwiftPackageReference
|
||||
for xcodeproj in self.path.glob("*.xcodeproj"):
|
||||
pbxproj = xcodeproj / "project.pbxproj"
|
||||
if pbxproj.exists():
|
||||
try:
|
||||
content = pbxproj.read_text(encoding="utf-8", errors="ignore")
|
||||
import re
|
||||
|
||||
# Match repositoryURL patterns
|
||||
urls = re.findall(r'repositoryURL\s*=\s*"([^"]+)"', content)
|
||||
for url in urls:
|
||||
name = url.rstrip("/").split("/")[-1].replace(".git", "")
|
||||
if name and name not in dependencies:
|
||||
dependencies.append(name)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return dependencies
|
||||
|
||||
def _detect_node_package_manager(self) -> str:
|
||||
"""Detect Node.js package manager."""
|
||||
if self._exists("pnpm-lock.yaml"):
|
||||
|
||||
@@ -106,6 +106,34 @@ export function ServiceCard({ name, service }: ServiceCardProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Apple Frameworks (iOS/Swift) */}
|
||||
{service.apple_frameworks && service.apple_frameworks.length > 0 && (
|
||||
<div className="pt-2 border-t border-border">
|
||||
<p className="text-xs text-muted-foreground mb-1.5">Apple Frameworks</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{service.apple_frameworks.map((fw) => (
|
||||
<Badge key={fw} variant="secondary" className="text-xs">
|
||||
{fw}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* SPM Dependencies (iOS/Swift) */}
|
||||
{service.spm_dependencies && service.spm_dependencies.length > 0 && (
|
||||
<div className="pt-2 border-t border-border">
|
||||
<p className="text-xs text-muted-foreground mb-1.5">SPM Dependencies</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{service.spm_dependencies.map((dep) => (
|
||||
<Badge key={dep} variant="outline" className="text-xs font-mono">
|
||||
{dep}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Collapsible Sections */}
|
||||
<EnvironmentSection environment={service.environment} />
|
||||
<APIRoutesSection api={service.api} />
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
FileCode,
|
||||
Lightbulb,
|
||||
FolderTree,
|
||||
AlertTriangle
|
||||
AlertTriangle,
|
||||
Smartphone,
|
||||
Monitor
|
||||
} from 'lucide-react';
|
||||
|
||||
// Service type icon mapping
|
||||
@@ -19,6 +21,8 @@ export const serviceTypeIcons: Record<string, React.ElementType> = {
|
||||
scraper: Code,
|
||||
library: Package,
|
||||
proxy: GitBranch,
|
||||
mobile: Smartphone,
|
||||
desktop: Monitor,
|
||||
unknown: FileCode
|
||||
};
|
||||
|
||||
@@ -30,6 +34,8 @@ export const serviceTypeColors: Record<string, string> = {
|
||||
scraper: 'bg-green-500/10 text-green-400 border-green-500/30',
|
||||
library: 'bg-gray-500/10 text-gray-400 border-gray-500/30',
|
||||
proxy: 'bg-cyan-500/10 text-cyan-400 border-cyan-500/30',
|
||||
mobile: 'bg-orange-500/10 text-orange-400 border-orange-500/30',
|
||||
desktop: 'bg-indigo-500/10 text-indigo-400 border-indigo-500/30',
|
||||
unknown: 'bg-muted text-muted-foreground border-muted'
|
||||
};
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export interface ServiceInfo {
|
||||
path: string;
|
||||
language?: string;
|
||||
framework?: string;
|
||||
type?: 'backend' | 'frontend' | 'worker' | 'scraper' | 'library' | 'proxy' | 'unknown';
|
||||
type?: 'backend' | 'frontend' | 'worker' | 'scraper' | 'library' | 'proxy' | 'mobile' | 'desktop' | 'unknown';
|
||||
package_manager?: string;
|
||||
default_port?: number;
|
||||
entry_point?: string;
|
||||
@@ -65,6 +65,9 @@ export interface ServiceInfo {
|
||||
styling?: string;
|
||||
state_management?: string;
|
||||
build_tool?: string;
|
||||
// iOS/Swift specific
|
||||
apple_frameworks?: string[];
|
||||
spm_dependencies?: string[];
|
||||
dockerfile?: string;
|
||||
consumes?: string[];
|
||||
environment?: {
|
||||
|
||||
Reference in New Issue
Block a user