Skip to content

Commit c43c371

Browse files
committed
Add React Native project detection and support
Recognize React Native projects across the scanner and services. Updates: - ProjectInfo: add language abbreviation mapping for "React Native". - DependenciesService: include "React Native" in JS project routing so dependencies are parsed with JS tooling. - ProjectScanner: implement isReactNativeProject and hasReactNativeDependency to detect RN projects via marker files, ios/android folders, package.json dependencies, scripts, keywords, and project name. When detected, tag projects as "React Native" and "Mobile", set projectType accordingly, remove generic JS/Node tags, add RN to mobileTypes, and provide an icon color for React Native. These changes improve identification and handling of React Native repositories for dependency parsing, tagging, and UI presentation.
1 parent c4c2b37 commit c43c371

3 files changed

Lines changed: 97 additions & 2 deletions

File tree

DevAtlasMac/Models/ProjectInfo.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct ProjectInfo: Identifiable, Codable, Hashable {
4242
private func languageAbbreviation(for type: String) -> String {
4343
let mapping: [String: String] = [
4444
"Node.js": "JS",
45+
"React Native": "RN",
4546
"Next.js": "JS",
4647
"Vite": "JS",
4748
"Vue": "JS",

DevAtlasMac/Services/DependenciesService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ actor DependenciesService {
1212

1313
// Route to appropriate parser based on project type
1414
switch projectType {
15-
case "Node.js", "React", "Next.js", "Vue", "Angular", "Vite":
15+
case "Node.js", "React", "React Native", "Next.js", "Vue", "Angular", "Vite":
1616
return parseJavaScriptProject(at: projectPath, type: projectType)
1717

1818
case "Flutter":

DevAtlasMac/Services/ProjectScanner.swift

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ actor ProjectScanner {
247247
private func detectProject(at path: String, files: [String]) -> ProjectInfo? {
248248
var projectType: String?
249249
var tags: Set<String> = []
250+
let isReactNative = isReactNativeProject(at: path, files: files)
251+
252+
if isReactNative {
253+
tags.insert("React Native")
254+
tags.insert("Mobile")
255+
}
250256

251257
for file in files {
252258
let ext = (file as NSString).pathExtension
@@ -268,6 +274,12 @@ actor ProjectScanner {
268274
}
269275
}
270276

277+
if isReactNative {
278+
projectType = "React Native"
279+
tags.remove("JavaScript")
280+
tags.remove("Node.js")
281+
}
282+
271283
guard let type = projectType else { return nil }
272284

273285
let name = (path as NSString).lastPathComponent
@@ -303,7 +315,7 @@ actor ProjectScanner {
303315
"React", "Next.js", "Vue", "Angular", "Vite", "Node.js", "PHP", "Ruby", "Go"
304316
]
305317
let desktopTypes: Set<String> = [".NET", ".NET Solution", "F#", "VB.NET"]
306-
let mobileTypes: Set<String> = ["iOS", "Flutter", "Xcode", "Xcode Workspace"]
318+
let mobileTypes: Set<String> = ["iOS", "Flutter", "Xcode", "Xcode Workspace", "React Native"]
307319
let cloudTypes: Set<String> = ["Docker"]
308320

309321
if mobileTypes.contains(type) { return .mobile }
@@ -361,6 +373,7 @@ actor ProjectScanner {
361373
private func generateIconColor(type: String) -> String {
362374
let typeColors: [String: String] = [
363375
"Node.js": "68A063",
376+
"React Native": "61DAFB",
364377
"React": "61DAFB",
365378
"Next.js": "000000",
366379
"Vue": "42B883",
@@ -386,4 +399,85 @@ actor ProjectScanner {
386399
]
387400
return typeColors[type] ?? "6B7280"
388401
}
402+
403+
// MARK: - React Native Detection
404+
405+
private func isReactNativeProject(at path: String, files: [String]) -> Bool {
406+
let normalizedFiles = Set(files.map { $0.lowercased() })
407+
guard normalizedFiles.contains("package.json") else { return false }
408+
409+
let reactNativeMarkerFiles: Set<String> = [
410+
"app.json",
411+
"app.config.js",
412+
"app.config.ts",
413+
"react-native.config.js",
414+
"metro.config.js",
415+
"metro.config.cjs",
416+
"eas.json"
417+
]
418+
419+
if !reactNativeMarkerFiles.isDisjoint(with: normalizedFiles) {
420+
return true
421+
}
422+
423+
let hasPlatformFolders = normalizedFiles.contains("ios") && normalizedFiles.contains("android")
424+
425+
let packagePath = (path as NSString).appendingPathComponent("package.json")
426+
guard let data = try? Data(contentsOf: URL(fileURLWithPath: packagePath)),
427+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
428+
return hasPlatformFolders
429+
}
430+
431+
if hasReactNativeDependency(in: json) {
432+
return true
433+
}
434+
435+
if let scripts = json["scripts"] as? [String: Any] {
436+
let scriptKeys = scripts.keys.map { $0.lowercased() }
437+
let scriptValues = scripts.values
438+
.compactMap { $0 as? String }
439+
.map { $0.lowercased() }
440+
441+
if scriptKeys.contains(where: { $0.contains("react-native") || $0.contains("expo") }) {
442+
return true
443+
}
444+
445+
if scriptValues.contains(where: { $0.contains("react-native") || $0.contains("expo") }) {
446+
return true
447+
}
448+
}
449+
450+
if let keywords = json["keywords"] as? [String] {
451+
let loweredKeywords = keywords.map { $0.lowercased() }
452+
if loweredKeywords.contains(where: { $0.contains("react-native") || $0 == "expo" }) {
453+
return true
454+
}
455+
}
456+
457+
if let name = (json["name"] as? String)?.lowercased(), name.contains("react-native") {
458+
return true
459+
}
460+
461+
return hasPlatformFolders
462+
}
463+
464+
private func hasReactNativeDependency(in packageJson: [String: Any]) -> Bool {
465+
let dependencyKeys = ["dependencies", "devDependencies", "peerDependencies"]
466+
for key in dependencyKeys {
467+
guard let deps = packageJson[key] as? [String: Any] else { continue }
468+
let depNames = deps.keys.map { $0.lowercased() }
469+
470+
if depNames.contains(where: {
471+
$0 == "react-native" ||
472+
$0.hasPrefix("@react-native/") ||
473+
$0.hasPrefix("react-native-") ||
474+
$0 == "expo" ||
475+
$0.hasPrefix("@expo/") ||
476+
$0.hasPrefix("expo-")
477+
}) {
478+
return true
479+
}
480+
}
481+
return false
482+
}
389483
}

0 commit comments

Comments
 (0)