From 21359acdd0cadb2c68f44b048b09ec6861ba8c5f Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 16:32:32 +0200 Subject: [PATCH 1/9] Migrate to Swift 6.0 with Swift 6 language mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated swift-tools-version from 5.7 to 6.0 - Added swiftLanguageModes: [.v6] to Package.swift - Changed all static var configuration to static let configuration for Swift 6 concurrency safety - Added @preconcurrency import for ArgumentParser to handle non-Sendable CommandConfiguration - All tests pass (19 tests) - Build successful with Swift 6 language mode Modified files: - Package.swift: Swift 6.0 tools version and language mode - CompareCommand.swift: static let + @preconcurrency import - DependantCommand.swift: static let + @preconcurrency import - GraphCommand.swift: static let + @preconcurrency import - HistoryCommand.swift: static let + @preconcurrency import - Main.swift: static let + @preconcurrency import - ModulesCommand.swift: static let + @preconcurrency import 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Package.swift | 5 +++-- Sources/jungle/Commands/CompareCommand.swift | 4 ++-- Sources/jungle/Commands/DependantCommand.swift | 4 ++-- Sources/jungle/Commands/GraphCommand.swift | 4 ++-- Sources/jungle/Commands/HistoryCommand.swift | 8 ++++---- Sources/jungle/Commands/Main.swift | 4 ++-- Sources/jungle/Commands/ModulesCommand.swift | 4 ++-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/Package.swift b/Package.swift index 86a7f6e..b5bc714 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -79,5 +79,6 @@ let package = Package( .target( name: "Shell" ) - ] + ], + swiftLanguageModes: [.v6] ) diff --git a/Sources/jungle/Commands/CompareCommand.swift b/Sources/jungle/Commands/CompareCommand.swift index b0b1db4..90b16de 100644 --- a/Sources/jungle/Commands/CompareCommand.swift +++ b/Sources/jungle/Commands/CompareCommand.swift @@ -1,4 +1,4 @@ -import ArgumentParser +@preconcurrency import ArgumentParser import Foundation import DependencyGraph import PodExtractor @@ -20,7 +20,7 @@ extension CompareError: CustomStringConvertible { } struct CompareCommand: ParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "compare", abstract: "Compares the current complexity of the dependency graph to others versions in git" ) diff --git a/Sources/jungle/Commands/DependantCommand.swift b/Sources/jungle/Commands/DependantCommand.swift index 9cf84a6..c379495 100644 --- a/Sources/jungle/Commands/DependantCommand.swift +++ b/Sources/jungle/Commands/DependantCommand.swift @@ -1,12 +1,12 @@ import Foundation -import ArgumentParser +@preconcurrency import ArgumentParser import Shell import SPMExtractor import DependencyModule import PodExtractor struct DependantCommand: ParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "dependant", abstract: "Outputs a sorted list of targets that depends on the specified one in target" ) diff --git a/Sources/jungle/Commands/GraphCommand.swift b/Sources/jungle/Commands/GraphCommand.swift index 68c973f..18fdbb7 100644 --- a/Sources/jungle/Commands/GraphCommand.swift +++ b/Sources/jungle/Commands/GraphCommand.swift @@ -1,4 +1,4 @@ -import ArgumentParser +@preconcurrency import ArgumentParser import Foundation import DependencyGraph import PodExtractor @@ -7,7 +7,7 @@ import Shell import SPMExtractor struct GraphCommand: ParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "graph", abstract: "Outputs the dependency graph in DOT format" ) diff --git a/Sources/jungle/Commands/HistoryCommand.swift b/Sources/jungle/Commands/HistoryCommand.swift index 1c1fc9b..d54989a 100644 --- a/Sources/jungle/Commands/HistoryCommand.swift +++ b/Sources/jungle/Commands/HistoryCommand.swift @@ -1,4 +1,4 @@ -import ArgumentParser +@preconcurrency import ArgumentParser import Foundation import DependencyGraph import PodExtractor @@ -7,13 +7,13 @@ import DependencyModule import Shell struct HistoryCommand: AsyncParsableCommand { - + enum OutputFormat: String, ExpressibleByArgument { case json case csv } - - static var configuration = CommandConfiguration( + + static let configuration = CommandConfiguration( commandName: "history", abstract: "Displays historic complexity of the dependency graph" ) diff --git a/Sources/jungle/Commands/Main.swift b/Sources/jungle/Commands/Main.swift index 38b68b9..e00d55e 100644 --- a/Sources/jungle/Commands/Main.swift +++ b/Sources/jungle/Commands/Main.swift @@ -1,8 +1,8 @@ -import ArgumentParser +@preconcurrency import ArgumentParser @main struct Jungle: AsyncParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "jungle", abstract: "SwiftPM and Cocoapods based projects complexity analyzer.", version: "2.3.2", diff --git a/Sources/jungle/Commands/ModulesCommand.swift b/Sources/jungle/Commands/ModulesCommand.swift index 0cb6be5..4dba073 100644 --- a/Sources/jungle/Commands/ModulesCommand.swift +++ b/Sources/jungle/Commands/ModulesCommand.swift @@ -1,5 +1,5 @@ import Foundation -import ArgumentParser +@preconcurrency import ArgumentParser import Shell import PodExtractor import DependencyGraph @@ -8,7 +8,7 @@ import DependencyModule struct ModulesCommand: ParsableCommand { - static var configuration = CommandConfiguration( + static let configuration = CommandConfiguration( commandName: "modules", abstract: "Outputs a sorted list of modules dependencies count of your project" ) From 7364c642eb169766470e6fe0cb8985ac8dd2be4c Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 16:34:20 +0200 Subject: [PATCH 2/9] Add .vscode and .claude to .gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ignore editor and IDE specific configuration folders. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index cd00854..7b32bc1 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,7 @@ fastlane/test_output # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ + +# Editor and IDE specific +.vscode/ +.claude/ From 2dbb679390c83510ccdc3cd2e8dbb608918de9e6 Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 16:51:04 +0200 Subject: [PATCH 3/9] Fix yamlParsingFailed error when Podfile.lock is not in git MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed ModulesCommand to read Podfile.lock directly from the filesystem instead of requiring it to be committed to git. This fixes issue #24 where users encountered yamlParsingFailed errors when running jungle modules on new projects that haven't committed Podfile.lock to git yet. Also added demo_project/ to .gitignore to exclude test projects. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 3 +++ Sources/jungle/Commands/ModulesCommand.swift | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7b32bc1..3f83ea6 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,6 @@ iOSInjectionProject/ # Editor and IDE specific .vscode/ .claude/ + +# Demo project +demo_project/ diff --git a/Sources/jungle/Commands/ModulesCommand.swift b/Sources/jungle/Commands/ModulesCommand.swift index 4dba073..be7895f 100644 --- a/Sources/jungle/Commands/ModulesCommand.swift +++ b/Sources/jungle/Commands/ModulesCommand.swift @@ -41,13 +41,14 @@ struct ModulesCommand: ParsableCommand { private func processPodfile(at directoryURL: URL) throws { // Choose the target to analyze let podfileJSON = try shell("pod ipc podfile-json Podfile --silent", at: directoryURL) - + guard let currentTargetDependencies = try moduleFromJSONPodfile(podfileJSON, onTarget: target) else { throw CompareError.targetNotFound(target: target) } let targetDependencies = currentTargetDependencies.dependencies - - let podfileLock = try shell("git show HEAD:Podfile.lock", at: directoryURL) + + let podfileLockURL = directoryURL.appendingPathComponent("Podfile.lock") + let podfileLock = try String(contentsOf: podfileLockURL, encoding: .utf8) let allPodfileModules = try extractModulesFromPodfileLock(podfileLock) let realTargetDependencies = allPodfileModules.filter { targetDependencies.contains($0.name) } From 970458e087b7a9ed53bb9d34983735038354948c Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 17:45:52 +0200 Subject: [PATCH 4/9] Add sample projects for SPM and CocoaPods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created Samples directory with two reference projects: - SPMSample: Swift Package Manager project with Alamofire, RealmSwift, SwiftyJSON, Kingfisher - CocoaPodsSample: iOS project with Moya, RxSwift, SnapKit, and other popular pods Updated root README.md with sample project information and usage instructions. Added individual README files for each sample explaining dependencies and usage. These samples serve as reference implementations for testing Jungle's analysis capabilities with different dependency management systems. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 26 ++ Samples/CocoaPodsSample/.gitignore | 6 + .../CocoaPodsSample.xcodeproj/project.pbxproj | 359 ++++++++++++++++++ .../CocoaPodsSample/AppDelegate.swift | 14 + Samples/CocoaPodsSample/Podfile | 21 + Samples/CocoaPodsSample/Podfile.lock | 49 +++ Samples/CocoaPodsSample/README.md | 38 ++ Samples/CocoaPodsSample/project.yml | 13 + Samples/README.md | 37 ++ Samples/SPMSample/Package.swift | 34 ++ Samples/SPMSample/README.md | 31 ++ .../Sources/SPMSample/SPMSample.swift | 13 + .../Tests/SPMSampleTests/SPMSampleTests.swift | 9 + 13 files changed, 650 insertions(+) create mode 100644 Samples/CocoaPodsSample/.gitignore create mode 100644 Samples/CocoaPodsSample/CocoaPodsSample.xcodeproj/project.pbxproj create mode 100644 Samples/CocoaPodsSample/CocoaPodsSample/AppDelegate.swift create mode 100644 Samples/CocoaPodsSample/Podfile create mode 100644 Samples/CocoaPodsSample/Podfile.lock create mode 100644 Samples/CocoaPodsSample/README.md create mode 100644 Samples/CocoaPodsSample/project.yml create mode 100644 Samples/README.md create mode 100644 Samples/SPMSample/Package.swift create mode 100644 Samples/SPMSample/README.md create mode 100644 Samples/SPMSample/Sources/SPMSample/SPMSample.swift create mode 100644 Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift diff --git a/README.md b/README.md index a39440b..17d4f75 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ You can read more information about dependency complexity in our Technical artic - [Installation](#installation) * [Mint](#mint) * [Manual](#manual) +- [Sample Projects](#sample-projects) - [Usage](#usage) * [Fetch Historic Complexities](#fetch-historic-complexities) * [Compare Complexity Graphs](#compare-complexity-graphs) @@ -44,6 +45,31 @@ swift build -c release .build/release/jungle help ``` +## Sample Projects + +The `Samples/` directory contains example projects to help you get started with Jungle: + +### SPMSample +A Swift Package Manager project with popular dependencies (Alamofire, RealmSwift, SwiftyJSON, Kingfisher). + +**Try it:** +```bash +cd Samples/SPMSample +jungle modules --target SPMSample +``` + +### CocoaPodsSample +An iOS project using CocoaPods with dependencies like Moya, RxSwift, and SnapKit. + +**Setup and try:** +```bash +cd Samples/CocoaPodsSample +pod install +jungle modules --target CocoaPodsSample +``` + +See [Samples/README.md](Samples/README.md) for more details about each sample project. + ## Usage ### Fetch Historic Complexities diff --git a/Samples/CocoaPodsSample/.gitignore b/Samples/CocoaPodsSample/.gitignore new file mode 100644 index 0000000..d419aba --- /dev/null +++ b/Samples/CocoaPodsSample/.gitignore @@ -0,0 +1,6 @@ +# CocoaPods +Pods/ +*.xcworkspace + +# Xcode +xcuserdata/ diff --git a/Samples/CocoaPodsSample/CocoaPodsSample.xcodeproj/project.pbxproj b/Samples/CocoaPodsSample/CocoaPodsSample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..98ece19 --- /dev/null +++ b/Samples/CocoaPodsSample/CocoaPodsSample.xcodeproj/project.pbxproj @@ -0,0 +1,359 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 54C280AE07D87589A2F580BC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B61101AF65E260F9BADA139 /* AppDelegate.swift */; }; + 74C66065555FB629F727483C /* Pods_CocoaPodsSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2D0DA4CCB77970154FBCC36 /* Pods_CocoaPodsSample.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 5653DF33309A954D3FF03627 /* Pods-CocoaPodsSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CocoaPodsSample.debug.xcconfig"; path = "Target Support Files/Pods-CocoaPodsSample/Pods-CocoaPodsSample.debug.xcconfig"; sourceTree = ""; }; + 6B61101AF65E260F9BADA139 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7CC13D77AAA1D7BA7FB000AB /* CocoaPodsSample.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = CocoaPodsSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7D30B2B1191E353231DBB12B /* Pods-CocoaPodsSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CocoaPodsSample.release.xcconfig"; path = "Target Support Files/Pods-CocoaPodsSample/Pods-CocoaPodsSample.release.xcconfig"; sourceTree = ""; }; + B2D0DA4CCB77970154FBCC36 /* Pods_CocoaPodsSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CocoaPodsSample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + EEF661CC971D388A48920883 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 74C66065555FB629F727483C /* Pods_CocoaPodsSample.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3EB6D18563ECF0A7B6A0ABA3 /* Products */ = { + isa = PBXGroup; + children = ( + 7CC13D77AAA1D7BA7FB000AB /* CocoaPodsSample.app */, + ); + name = Products; + sourceTree = ""; + }; + 50F499507ACC7496DA7B1219 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B2D0DA4CCB77970154FBCC36 /* Pods_CocoaPodsSample.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 536692CDDAC281C567A166AB /* CocoaPodsSample */ = { + isa = PBXGroup; + children = ( + 6B61101AF65E260F9BADA139 /* AppDelegate.swift */, + ); + path = CocoaPodsSample; + sourceTree = ""; + }; + 83B14D9353013C7B4AE9FAC1 /* Pods */ = { + isa = PBXGroup; + children = ( + 5653DF33309A954D3FF03627 /* Pods-CocoaPodsSample.debug.xcconfig */, + 7D30B2B1191E353231DBB12B /* Pods-CocoaPodsSample.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + AF7E0FF6A308B3F7B1A6A479 = { + isa = PBXGroup; + children = ( + 536692CDDAC281C567A166AB /* CocoaPodsSample */, + 3EB6D18563ECF0A7B6A0ABA3 /* Products */, + 83B14D9353013C7B4AE9FAC1 /* Pods */, + 50F499507ACC7496DA7B1219 /* Frameworks */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 245CAD9F3964737E6C773D82 /* CocoaPodsSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4E41CC94FA177DFCA91ADFA3 /* Build configuration list for PBXNativeTarget "CocoaPodsSample" */; + buildPhases = ( + 2890D6350275F10090EBE1BE /* [CP] Check Pods Manifest.lock */, + 89B091C4E4FA74E879CA3ECA /* Sources */, + EEF661CC971D388A48920883 /* Frameworks */, + B11A05C7F6D5883C8E8B1969 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CocoaPodsSample; + productName = CocoaPodsSample; + productReference = 7CC13D77AAA1D7BA7FB000AB /* CocoaPodsSample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A3D1E7084E80EDB2CB2D78DA /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + }; + }; + buildConfigurationList = 436D44B293380B95B868A659 /* Build configuration list for PBXProject "CocoaPodsSample" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = AF7E0FF6A308B3F7B1A6A479; + productRefGroup = 3EB6D18563ECF0A7B6A0ABA3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 245CAD9F3964737E6C773D82 /* CocoaPodsSample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2890D6350275F10090EBE1BE /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-CocoaPodsSample-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B11A05C7F6D5883C8E8B1969 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-CocoaPodsSample/Pods-CocoaPodsSample-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-CocoaPodsSample/Pods-CocoaPodsSample-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CocoaPodsSample/Pods-CocoaPodsSample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 89B091C4E4FA74E879CA3ECA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 54C280AE07D87589A2F580BC /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 22C0550B4A6DE7E315271D75 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7D30B2B1191E353231DBB12B /* Pods-CocoaPodsSample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.sample.CocoaPodsSample; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + A64178699855E49810B57046 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + F6F2FF85804301B1932C21CF /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5653DF33309A954D3FF03627 /* Pods-CocoaPodsSample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.sample.CocoaPodsSample; + SDKROOT = iphoneos; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + F86C4E901F8C6E5A2BABAC02 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 436D44B293380B95B868A659 /* Build configuration list for PBXProject "CocoaPodsSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A64178699855E49810B57046 /* Debug */, + F86C4E901F8C6E5A2BABAC02 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 4E41CC94FA177DFCA91ADFA3 /* Build configuration list for PBXNativeTarget "CocoaPodsSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F6F2FF85804301B1932C21CF /* Debug */, + 22C0550B4A6DE7E315271D75 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = A3D1E7084E80EDB2CB2D78DA /* Project object */; +} diff --git a/Samples/CocoaPodsSample/CocoaPodsSample/AppDelegate.swift b/Samples/CocoaPodsSample/CocoaPodsSample/AppDelegate.swift new file mode 100644 index 0000000..896a8b6 --- /dev/null +++ b/Samples/CocoaPodsSample/CocoaPodsSample/AppDelegate.swift @@ -0,0 +1,14 @@ +import UIKit +import Alamofire +import RxSwift +import Kingfisher + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + print("CocoaPods Sample App with popular dependencies") + return true + } +} diff --git a/Samples/CocoaPodsSample/Podfile b/Samples/CocoaPodsSample/Podfile new file mode 100644 index 0000000..6f465c5 --- /dev/null +++ b/Samples/CocoaPodsSample/Podfile @@ -0,0 +1,21 @@ +platform :ios, '13.0' +use_frameworks! + +target 'CocoaPodsSample' do + # Networking + pod 'Alamofire', '~> 5.8' + pod 'Moya', '~> 15.0' + + # JSON Parsing + pod 'SwiftyJSON', '~> 5.0' + + # Image Loading + pod 'Kingfisher', '~> 7.10' + + # Reactive Programming + pod 'RxSwift', '~> 6.6' + pod 'RxCocoa', '~> 6.6' + + # UI + pod 'SnapKit', '~> 5.6' +end diff --git a/Samples/CocoaPodsSample/Podfile.lock b/Samples/CocoaPodsSample/Podfile.lock new file mode 100644 index 0000000..fb8cfa9 --- /dev/null +++ b/Samples/CocoaPodsSample/Podfile.lock @@ -0,0 +1,49 @@ +PODS: + - Alamofire (5.10.2) + - Kingfisher (7.12.0) + - Moya (15.0.0): + - Moya/Core (= 15.0.0) + - Moya/Core (15.0.0): + - Alamofire (~> 5.0) + - RxCocoa (6.9.0): + - RxRelay (= 6.9.0) + - RxSwift (= 6.9.0) + - RxRelay (6.9.0): + - RxSwift (= 6.9.0) + - RxSwift (6.9.0) + - SnapKit (5.7.1) + - SwiftyJSON (5.0.2) + +DEPENDENCIES: + - Alamofire (~> 5.8) + - Kingfisher (~> 7.10) + - Moya (~> 15.0) + - RxCocoa (~> 6.6) + - RxSwift (~> 6.6) + - SnapKit (~> 5.6) + - SwiftyJSON (~> 5.0) + +SPEC REPOS: + trunk: + - Alamofire + - Kingfisher + - Moya + - RxCocoa + - RxRelay + - RxSwift + - SnapKit + - SwiftyJSON + +SPEC CHECKSUMS: + Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496 + Kingfisher: 53a10ea35051a436b5fb626ca2dd8f3144d755e9 + Moya: 138f0573e53411fb3dc17016add0b748dfbd78ee + RxCocoa: ac16414696ae706516be3e1ab00fcce5bdc9be8a + RxRelay: 6b0c930e5cef57d5fe2032571e5e65b78e3cbdb1 + RxSwift: 31649ace6aceeb422e16ff71c60804f9c3281ed9 + SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a + SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a + +PODFILE CHECKSUM: 138a12e14797ce3c5cc5a496d9d472ec428bfc16 + +COCOAPODS: 1.16.2 diff --git a/Samples/CocoaPodsSample/README.md b/Samples/CocoaPodsSample/README.md new file mode 100644 index 0000000..6adf131 --- /dev/null +++ b/Samples/CocoaPodsSample/README.md @@ -0,0 +1,38 @@ +# CocoaPodsSample + +A CocoaPods sample project demonstrating Jungle's analysis capabilities with iOS projects. + +## Dependencies + +This project includes popular CocoaPods: +- **Alamofire** (~> 5.8) - HTTP networking library +- **Moya** (~> 15.0) - Network abstraction layer +- **SwiftyJSON** (~> 5.0) - JSON parsing library +- **Kingfisher** (~> 7.10) - Image downloading and caching +- **RxSwift** (~> 6.6) - Reactive programming framework +- **RxCocoa** (~> 6.6) - Reactive extensions for Cocoa +- **SnapKit** (~> 5.6) - Auto Layout DSL + +## Setup + +1. Install dependencies: +```bash +pod install +``` + +2. Open the workspace: +```bash +open CocoaPodsSample.xcworkspace +``` + +## Usage + +Run Jungle to analyze module dependencies: + +```bash +jungle modules --target CocoaPodsSample +``` + +## Note + +This sample project has a git repository initialized with Podfile.lock committed, which allows Jungle to analyze the dependency graph. diff --git a/Samples/CocoaPodsSample/project.yml b/Samples/CocoaPodsSample/project.yml new file mode 100644 index 0000000..26dade3 --- /dev/null +++ b/Samples/CocoaPodsSample/project.yml @@ -0,0 +1,13 @@ +name: CocoaPodsSample +options: + bundleIdPrefix: com.sample +targets: + CocoaPodsSample: + type: application + platform: iOS + deploymentTarget: "13.0" + sources: + - CocoaPodsSample + settings: + PRODUCT_BUNDLE_IDENTIFIER: com.sample.CocoaPodsSample + SWIFT_VERSION: "5.0" diff --git a/Samples/README.md b/Samples/README.md new file mode 100644 index 0000000..4f10ce8 --- /dev/null +++ b/Samples/README.md @@ -0,0 +1,37 @@ +# Jungle Sample Projects + +This directory contains sample projects demonstrating how to use Jungle with different dependency management systems. + +## Available Samples + +### SPMSample +A Swift Package Manager project with popular dependencies including: +- Alamofire (networking) +- RealmSwift (database) +- SwiftyJSON (JSON parsing) +- Kingfisher (image loading) + +### CocoaPodsSample +A CocoaPods-based iOS project with popular dependencies including: +- Alamofire (networking) +- Moya (network abstraction) +- SwiftyJSON (JSON parsing) +- Kingfisher (image loading) +- RxSwift & RxCocoa (reactive programming) +- SnapKit (Auto Layout) + +## Usage + +Navigate to any sample directory and run Jungle commands to analyze dependencies. + +### For SPMSample: +```bash +cd SPMSample +jungle modules --target SPMSample +``` + +### For CocoaPodsSample: +```bash +cd CocoaPodsSample +jungle modules --target CocoaPodsSample +``` diff --git a/Samples/SPMSample/Package.swift b/Samples/SPMSample/Package.swift new file mode 100644 index 0000000..4fb06f7 --- /dev/null +++ b/Samples/SPMSample/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version: 5.9 +import PackageDescription + +let package = Package( + name: "SPMSample", + platforms: [ + .iOS(.v13), + .macOS(.v10_15) + ], + products: [ + .library( + name: "SPMSample", + targets: ["SPMSample"]), + ], + dependencies: [ + .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"), + .package(url: "https://github.com/realm/realm-swift.git", from: "10.45.0"), + .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"), + .package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.10.0"), + ], + targets: [ + .target( + name: "SPMSample", + dependencies: [ + "Alamofire", + .product(name: "RealmSwift", package: "realm-swift"), + "SwiftyJSON", + "Kingfisher", + ]), + .testTarget( + name: "SPMSampleTests", + dependencies: ["SPMSample"]), + ] +) diff --git a/Samples/SPMSample/README.md b/Samples/SPMSample/README.md new file mode 100644 index 0000000..927389e --- /dev/null +++ b/Samples/SPMSample/README.md @@ -0,0 +1,31 @@ +# SPMSample + +A Swift Package Manager sample project demonstrating Jungle's analysis capabilities. + +## Dependencies + +This project includes popular Swift packages: +- **Alamofire** (5.8.0+) - HTTP networking library +- **RealmSwift** (10.45.0+) - Mobile database +- **SwiftyJSON** (5.0.0+) - JSON parsing library +- **Kingfisher** (7.10.0+) - Image downloading and caching + +## Usage + +Run Jungle to analyze module dependencies: + +```bash +jungle modules --target SPMSample +``` + +## Building + +```bash +swift build +``` + +## Testing + +```bash +swift test +``` diff --git a/Samples/SPMSample/Sources/SPMSample/SPMSample.swift b/Samples/SPMSample/Sources/SPMSample/SPMSample.swift new file mode 100644 index 0000000..cbf7e2c --- /dev/null +++ b/Samples/SPMSample/Sources/SPMSample/SPMSample.swift @@ -0,0 +1,13 @@ +import Foundation +import Alamofire +import RealmSwift +import SwiftyJSON +import Kingfisher + +public struct SPMSample { + public init() {} + + public func example() { + print("SPM Sample with popular dependencies") + } +} diff --git a/Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift b/Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift new file mode 100644 index 0000000..d531c20 --- /dev/null +++ b/Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift @@ -0,0 +1,9 @@ +import XCTest +@testable import SPMSample + +final class SPMSampleTests: XCTestCase { + func testExample() throws { + let sample = SPMSample() + sample.example() + } +} From 8de88e971ffd35764f6b8c71185d2b42fb235deb Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 17:52:12 +0200 Subject: [PATCH 5/9] Add Makefile for easy build and installation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created a comprehensive Makefile with the following targets: - make build: Build the executable in release mode - make install: Build and install to /usr/local/bin (customizable with PREFIX) - make uninstall: Remove installed executable - make clean: Clean build artifacts - make test: Run tests - make help: Show available commands Updated README.md to document Makefile usage as the recommended installation method, with examples of custom installation paths. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Makefile | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 20 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0c83456 --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +# Makefile for Jungle + +PREFIX ?= /usr/local +INSTALL_PATH = $(PREFIX)/bin +BUILD_PATH = .build/release +EXECUTABLE_NAME = jungle + +.PHONY: all build install uninstall clean test help + +# Default target +all: build + +# Build the executable in release mode +build: + @echo "Building $(EXECUTABLE_NAME) in release mode..." + swift build -c release --disable-sandbox + +# Install the executable to the system +install: build + @echo "Installing $(EXECUTABLE_NAME) to $(INSTALL_PATH)..." + @mkdir -p $(INSTALL_PATH) + @install -m 755 $(BUILD_PATH)/$(EXECUTABLE_NAME) $(INSTALL_PATH)/$(EXECUTABLE_NAME) + @echo "✓ $(EXECUTABLE_NAME) installed successfully at $(INSTALL_PATH)/$(EXECUTABLE_NAME)" + +# Uninstall the executable from the system +uninstall: + @echo "Uninstalling $(EXECUTABLE_NAME) from $(INSTALL_PATH)..." + @rm -f $(INSTALL_PATH)/$(EXECUTABLE_NAME) + @echo "✓ $(EXECUTABLE_NAME) uninstalled successfully" + +# Clean build artifacts +clean: + @echo "Cleaning build artifacts..." + @rm -rf .build + @echo "✓ Build artifacts cleaned" + +# Run tests +test: + @echo "Running tests..." + swift test + +# Show help +help: + @echo "Jungle Makefile Commands:" + @echo "" + @echo " make build - Build the executable in release mode" + @echo " make install - Build and install to $(INSTALL_PATH) (default: /usr/local/bin)" + @echo " make uninstall - Remove the executable from $(INSTALL_PATH)" + @echo " make clean - Remove build artifacts" + @echo " make test - Run tests" + @echo " make help - Show this help message" + @echo "" + @echo "Custom Installation Path:" + @echo " make install PREFIX=/custom/path" + @echo "" + @echo "Example:" + @echo " make install PREFIX=~/.local" + @echo " This will install to ~/.local/bin/jungle" diff --git a/README.md b/README.md index 17d4f75..f4d42a7 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,26 @@ mint install xing/jungle mint run jungle help ``` +### Makefile (Recommended) + +```bash +git clone https://github.com/xing/jungle +cd jungle +make install +``` + +This will build and install jungle to `/usr/local/bin`. You can customize the installation path: + +```bash +make install PREFIX=~/.local +``` + +Other available commands: +- `make build` - Build the executable +- `make clean` - Clean build artifacts +- `make uninstall` - Remove the installed executable +- `make help` - Show all available commands + ### Manual ```bash From 4faa356777e2a2c035bdd36b811fd9e01b62d4dc Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 17:54:22 +0200 Subject: [PATCH 6/9] Fix Makefile install permission issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated the install and uninstall targets to automatically use sudo when the target directory requires elevated permissions. The Makefile now: - Checks if the install path is writable - Automatically uses sudo for system directories like /usr/local/bin - Works without sudo for user directories like ~/.local/bin Updated README.md to clarify installation options: - System-wide installation (may require password) - User installation (no sudo required) This fixes the "Permission denied" error when running make install. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Makefile | 26 +++++++++++++++++++------- README.md | 8 +++++++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 0c83456..6983c1d 100644 --- a/Makefile +++ b/Makefile @@ -18,14 +18,24 @@ build: # Install the executable to the system install: build @echo "Installing $(EXECUTABLE_NAME) to $(INSTALL_PATH)..." - @mkdir -p $(INSTALL_PATH) - @install -m 755 $(BUILD_PATH)/$(EXECUTABLE_NAME) $(INSTALL_PATH)/$(EXECUTABLE_NAME) + @if [ -w $(INSTALL_PATH) ]; then \ + mkdir -p $(INSTALL_PATH); \ + install -m 755 $(BUILD_PATH)/$(EXECUTABLE_NAME) $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ + else \ + echo "Note: $(INSTALL_PATH) requires elevated permissions"; \ + sudo mkdir -p $(INSTALL_PATH); \ + sudo install -m 755 $(BUILD_PATH)/$(EXECUTABLE_NAME) $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ + fi @echo "✓ $(EXECUTABLE_NAME) installed successfully at $(INSTALL_PATH)/$(EXECUTABLE_NAME)" # Uninstall the executable from the system uninstall: @echo "Uninstalling $(EXECUTABLE_NAME) from $(INSTALL_PATH)..." - @rm -f $(INSTALL_PATH)/$(EXECUTABLE_NAME) + @if [ -w $(INSTALL_PATH)/$(EXECUTABLE_NAME) ]; then \ + rm -f $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ + else \ + sudo rm -f $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ + fi @echo "✓ $(EXECUTABLE_NAME) uninstalled successfully" # Clean build artifacts @@ -45,14 +55,16 @@ help: @echo "" @echo " make build - Build the executable in release mode" @echo " make install - Build and install to $(INSTALL_PATH) (default: /usr/local/bin)" + @echo " Note: May require sudo for system directories" @echo " make uninstall - Remove the executable from $(INSTALL_PATH)" @echo " make clean - Remove build artifacts" @echo " make test - Run tests" @echo " make help - Show this help message" @echo "" - @echo "Custom Installation Path:" - @echo " make install PREFIX=/custom/path" - @echo "" - @echo "Example:" + @echo "Custom Installation Path (no sudo required):" @echo " make install PREFIX=~/.local" @echo " This will install to ~/.local/bin/jungle" + @echo "" + @echo "System-wide Installation:" + @echo " make install" + @echo " This will install to /usr/local/bin/jungle (may prompt for password)" diff --git a/README.md b/README.md index f4d42a7..467bcd3 100644 --- a/README.md +++ b/README.md @@ -38,18 +38,24 @@ mint run jungle help ### Makefile (Recommended) +**System-wide installation** (installs to `/usr/local/bin`, may require password): + ```bash git clone https://github.com/xing/jungle cd jungle make install ``` -This will build and install jungle to `/usr/local/bin`. You can customize the installation path: +**User installation** (no sudo required): ```bash +git clone https://github.com/xing/jungle +cd jungle make install PREFIX=~/.local ``` +Make sure `~/.local/bin` is in your `PATH`. + Other available commands: - `make build` - Build the executable - `make clean` - Clean build artifacts From b185cc1903e5b4eab33c590fb85617136707fb15 Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 17:57:23 +0200 Subject: [PATCH 7/9] Change default installation to user directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated Makefile to install to ~/.local/bin by default instead of /usr/local/bin, eliminating the need for sudo permissions. Changes: - Changed PREFIX default from /usr/local to $HOME/.local - Removed sudo logic from install/uninstall targets - Added PATH check that warns users if ~/.local/bin is not in PATH - Simplified installation process with helpful PATH setup instructions Updated README.md to reflect the simpler installation process: - Single installation method (no system vs user choice) - Clear PATH setup instructions - Removed sudo/permission references This makes installation easier and safer for users without requiring elevated permissions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Makefile | 42 ++++++++++++++++++------------------------ README.md | 11 +++-------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 6983c1d..174e9b0 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Makefile for Jungle -PREFIX ?= /usr/local +PREFIX ?= $(HOME)/.local INSTALL_PATH = $(PREFIX)/bin BUILD_PATH = .build/release EXECUTABLE_NAME = jungle @@ -15,27 +15,23 @@ build: @echo "Building $(EXECUTABLE_NAME) in release mode..." swift build -c release --disable-sandbox -# Install the executable to the system +# Install the executable to user directory install: build @echo "Installing $(EXECUTABLE_NAME) to $(INSTALL_PATH)..." - @if [ -w $(INSTALL_PATH) ]; then \ - mkdir -p $(INSTALL_PATH); \ - install -m 755 $(BUILD_PATH)/$(EXECUTABLE_NAME) $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ - else \ - echo "Note: $(INSTALL_PATH) requires elevated permissions"; \ - sudo mkdir -p $(INSTALL_PATH); \ - sudo install -m 755 $(BUILD_PATH)/$(EXECUTABLE_NAME) $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ - fi + @mkdir -p $(INSTALL_PATH) + @install -m 755 $(BUILD_PATH)/$(EXECUTABLE_NAME) $(INSTALL_PATH)/$(EXECUTABLE_NAME) @echo "✓ $(EXECUTABLE_NAME) installed successfully at $(INSTALL_PATH)/$(EXECUTABLE_NAME)" + @if ! echo "$$PATH" | grep -q "$(INSTALL_PATH)"; then \ + echo ""; \ + echo "⚠️ Note: $(INSTALL_PATH) is not in your PATH"; \ + echo " Add this to your shell profile (~/.zshrc or ~/.bashrc):"; \ + echo " export PATH=\"$(INSTALL_PATH):\$$PATH\""; \ + fi -# Uninstall the executable from the system +# Uninstall the executable from user directory uninstall: @echo "Uninstalling $(EXECUTABLE_NAME) from $(INSTALL_PATH)..." - @if [ -w $(INSTALL_PATH)/$(EXECUTABLE_NAME) ]; then \ - rm -f $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ - else \ - sudo rm -f $(INSTALL_PATH)/$(EXECUTABLE_NAME); \ - fi + @rm -f $(INSTALL_PATH)/$(EXECUTABLE_NAME) @echo "✓ $(EXECUTABLE_NAME) uninstalled successfully" # Clean build artifacts @@ -54,17 +50,15 @@ help: @echo "Jungle Makefile Commands:" @echo "" @echo " make build - Build the executable in release mode" - @echo " make install - Build and install to $(INSTALL_PATH) (default: /usr/local/bin)" - @echo " Note: May require sudo for system directories" + @echo " make install - Build and install to $(INSTALL_PATH) (default: ~/.local/bin)" @echo " make uninstall - Remove the executable from $(INSTALL_PATH)" @echo " make clean - Remove build artifacts" @echo " make test - Run tests" @echo " make help - Show this help message" @echo "" - @echo "Custom Installation Path (no sudo required):" - @echo " make install PREFIX=~/.local" - @echo " This will install to ~/.local/bin/jungle" + @echo "Custom Installation Path:" + @echo " make install PREFIX=/custom/path" + @echo " This will install to /custom/path/bin/jungle" @echo "" - @echo "System-wide Installation:" - @echo " make install" - @echo " This will install to /usr/local/bin/jungle (may prompt for password)" + @echo "Note: The default installation uses ~/.local/bin" + @echo " Make sure this directory is in your PATH" diff --git a/README.md b/README.md index 467bcd3..a4e9e37 100644 --- a/README.md +++ b/README.md @@ -38,24 +38,19 @@ mint run jungle help ### Makefile (Recommended) -**System-wide installation** (installs to `/usr/local/bin`, may require password): - ```bash git clone https://github.com/xing/jungle cd jungle make install ``` -**User installation** (no sudo required): +This will install jungle to `~/.local/bin`. Make sure this directory is in your `PATH`: ```bash -git clone https://github.com/xing/jungle -cd jungle -make install PREFIX=~/.local +# Add to your ~/.zshrc or ~/.bashrc +export PATH="$HOME/.local/bin:$PATH" ``` -Make sure `~/.local/bin` is in your `PATH`. - Other available commands: - `make build` - Build the executable - `make clean` - Clean build artifacts From 1e45089fd92897830923eaf3f6e57f37127892f5 Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 18:12:57 +0200 Subject: [PATCH 8/9] Fix yamlParsingFailed error in dependant command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated DependantCommand to read Podfile.lock directly from the filesystem instead of requiring it to be in git, matching the fix previously applied to ModulesCommand. Changed line 42-43 in DependantCommand.swift: - Before: let podfileLock = try shell("git show HEAD:Podfile.lock", at: directoryURL) - After: Read from filesystem using String(contentsOf:encoding:) This fixes issue #24 for the dependant command, allowing it to work on projects without git or with uncommitted Podfile.lock files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Sources/jungle/Commands/DependantCommand.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/jungle/Commands/DependantCommand.swift b/Sources/jungle/Commands/DependantCommand.swift index c379495..18ab5f2 100644 --- a/Sources/jungle/Commands/DependantCommand.swift +++ b/Sources/jungle/Commands/DependantCommand.swift @@ -39,7 +39,8 @@ struct DependantCommand: ParsableCommand { } private func processPodfile(at directoryURL: URL) throws { - let podfileLock = try shell("git show HEAD:Podfile.lock", at: directoryURL) + let podfileLockURL = directoryURL.appendingPathComponent("Podfile.lock") + let podfileLock = try String(contentsOf: podfileLockURL, encoding: .utf8) let allPodfileModules = try extractModulesFromPodfileLock(podfileLock, excludeTests: false) let targets = try extractDependantTargets(from: allPodfileModules, for: target) processOutput(for: targets) From 49f72aff733f6338db7a20a64799e3284d9ad909 Mon Sep 17 00:00:00 2001 From: Oswaldo Rubio Date: Wed, 22 Oct 2025 18:17:22 +0200 Subject: [PATCH 9/9] Update SPMSample to complex multi-module architecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructured SPMSample from a simple single-module package to a complex multi-module architecture demonstrating internal dependencies and realistic project structure. New Architecture: - Core: Base utilities module (no dependencies) - Networking: HTTP client layer (depends on Core, Alamofire, SwiftyJSON) - DataLayer: Data persistence (depends on Core, RealmSwift) - APIClient: API integration (depends on Networking, DataLayer) - ImageService: Image loading (depends on Core, Kingfisher) - Feature1: User profile feature (depends on Core, APIClient, ImageService) - Feature2: Data sync feature (depends on Core, Networking, DataLayer) - SPMSample: Main module depending on all internal modules Benefits: - Demonstrates internal module dependencies for Jungle analysis - Shows realistic multi-layer architecture - Provides complex dependency graph for testing - Includes multiple test targets Updated README with: - Complete architecture diagram - Dependency graph visualization - Jungle command examples for different scenarios Tested with Jungle: - jungle modules --target SPMSample ✓ - jungle dependant --target Core ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Samples/SPMSample/Package.swift | 78 ++++++++++++++++++- Samples/SPMSample/README.md | 68 ++++++++++++++-- .../Sources/APIClient/APIClient.swift | 16 ++++ .../SPMSample/Sources/Core/Extensions.swift | 13 ++++ Samples/SPMSample/Sources/Core/Logger.swift | 13 ++++ .../Sources/DataLayer/DatabaseManager.swift | 19 +++++ .../SPMSample/Sources/Feature1/Feature1.swift | 23 ++++++ .../SPMSample/Sources/Feature2/Feature2.swift | 23 ++++++ .../Sources/ImageService/ImageLoader.swift | 14 ++++ .../Sources/Networking/NetworkClient.swift | 16 ++++ .../Sources/SPMSample/SPMSample.swift | 32 ++++++-- .../Tests/CoreTests/LoggerTests.swift | 10 +++ .../NetworkingTests/NetworkClientTests.swift | 10 +++ .../Tests/SPMSampleTests/SPMSampleTests.swift | 4 +- 14 files changed, 323 insertions(+), 16 deletions(-) create mode 100644 Samples/SPMSample/Sources/APIClient/APIClient.swift create mode 100644 Samples/SPMSample/Sources/Core/Extensions.swift create mode 100644 Samples/SPMSample/Sources/Core/Logger.swift create mode 100644 Samples/SPMSample/Sources/DataLayer/DatabaseManager.swift create mode 100644 Samples/SPMSample/Sources/Feature1/Feature1.swift create mode 100644 Samples/SPMSample/Sources/Feature2/Feature2.swift create mode 100644 Samples/SPMSample/Sources/ImageService/ImageLoader.swift create mode 100644 Samples/SPMSample/Sources/Networking/NetworkClient.swift create mode 100644 Samples/SPMSample/Tests/CoreTests/LoggerTests.swift create mode 100644 Samples/SPMSample/Tests/NetworkingTests/NetworkClientTests.swift diff --git a/Samples/SPMSample/Package.swift b/Samples/SPMSample/Package.swift index 4fb06f7..3ab73b1 100644 --- a/Samples/SPMSample/Package.swift +++ b/Samples/SPMSample/Package.swift @@ -11,6 +11,12 @@ let package = Package( .library( name: "SPMSample", targets: ["SPMSample"]), + .library( + name: "Networking", + targets: ["Networking"]), + .library( + name: "DataLayer", + targets: ["DataLayer"]), ], dependencies: [ .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.8.0"), @@ -19,14 +25,82 @@ let package = Package( .package(url: "https://github.com/onevcat/Kingfisher.git", from: "7.10.0"), ], targets: [ + // Core utilities module (no dependencies) .target( - name: "SPMSample", + name: "Core", + dependencies: []), + + // Networking layer (depends on Core and Alamofire) + .target( + name: "Networking", dependencies: [ + "Core", "Alamofire", - .product(name: "RealmSwift", package: "realm-swift"), "SwiftyJSON", + ]), + + // Data persistence layer (depends on Core and RealmSwift) + .target( + name: "DataLayer", + dependencies: [ + "Core", + .product(name: "RealmSwift", package: "realm-swift"), + ]), + + // API client (depends on Networking and DataLayer) + .target( + name: "APIClient", + dependencies: [ + "Networking", + "DataLayer", + ]), + + // Image loading service (depends on Core and Kingfisher) + .target( + name: "ImageService", + dependencies: [ + "Core", "Kingfisher", ]), + + // Feature 1 (depends on APIClient and ImageService) + .target( + name: "Feature1", + dependencies: [ + "Core", + "APIClient", + "ImageService", + ]), + + // Feature 2 (depends on Networking and DataLayer) + .target( + name: "Feature2", + dependencies: [ + "Core", + "Networking", + "DataLayer", + ]), + + // Main app module (depends on everything) + .target( + name: "SPMSample", + dependencies: [ + "Core", + "Networking", + "DataLayer", + "APIClient", + "ImageService", + "Feature1", + "Feature2", + ]), + + // Tests + .testTarget( + name: "CoreTests", + dependencies: ["Core"]), + .testTarget( + name: "NetworkingTests", + dependencies: ["Networking"]), .testTarget( name: "SPMSampleTests", dependencies: ["SPMSample"]), diff --git a/Samples/SPMSample/README.md b/Samples/SPMSample/README.md index 927389e..913c5ba 100644 --- a/Samples/SPMSample/README.md +++ b/Samples/SPMSample/README.md @@ -1,23 +1,79 @@ # SPMSample -A Swift Package Manager sample project demonstrating Jungle's analysis capabilities. +A complex Swift Package Manager sample project demonstrating Jungle's analysis capabilities with multiple internal modules and dependencies. -## Dependencies +## Architecture + +This project demonstrates a multi-module architecture with internal dependencies: + +### Internal Modules + +- **Core** - Base utilities (no dependencies) +- **Networking** - HTTP client layer (depends on Core, Alamofire, SwiftyJSON) +- **DataLayer** - Data persistence (depends on Core, RealmSwift) +- **APIClient** - API integration (depends on Networking, DataLayer) +- **ImageService** - Image loading (depends on Core, Kingfisher) +- **Feature1** - User profile feature (depends on Core, APIClient, ImageService) +- **Feature2** - Data sync feature (depends on Core, Networking, DataLayer) +- **SPMSample** - Main module (depends on all modules) + +### Dependency Graph + +``` +SPMSample +├── Core +├── Networking +│ ├── Core +│ ├── Alamofire +│ └── SwiftyJSON +├── DataLayer +│ ├── Core +│ └── RealmSwift +├── APIClient +│ ├── Networking +│ └── DataLayer +├── ImageService +│ ├── Core +│ └── Kingfisher +├── Feature1 +│ ├── Core +│ ├── APIClient +│ └── ImageService +└── Feature2 + ├── Core + ├── Networking + └── DataLayer +``` + +## External Dependencies -This project includes popular Swift packages: - **Alamofire** (5.8.0+) - HTTP networking library - **RealmSwift** (10.45.0+) - Mobile database - **SwiftyJSON** (5.0.0+) - JSON parsing library - **Kingfisher** (7.10.0+) - Image downloading and caching -## Usage - -Run Jungle to analyze module dependencies: +## Usage with Jungle +Analyze the main module dependencies: ```bash jungle modules --target SPMSample ``` +Analyze a specific internal module: +```bash +jungle modules --target Networking +``` + +Find dependants of a module: +```bash +jungle dependant --target Core +``` + +Generate dependency graph: +```bash +jungle graph --target SPMSample +``` + ## Building ```bash diff --git a/Samples/SPMSample/Sources/APIClient/APIClient.swift b/Samples/SPMSample/Sources/APIClient/APIClient.swift new file mode 100644 index 0000000..0bb57fd --- /dev/null +++ b/Samples/SPMSample/Sources/APIClient/APIClient.swift @@ -0,0 +1,16 @@ +import Foundation +import Networking +import DataLayer + +public class APIClient { + private let networkClient = NetworkClient() + private let databaseManager = DatabaseManager() + + public init() {} + + public func fetchAndStore(endpoint: String) async throws { + let data = try await networkClient.request(endpoint) + // Process and store data + print("Fetched and stored data from \(endpoint)") + } +} diff --git a/Samples/SPMSample/Sources/Core/Extensions.swift b/Samples/SPMSample/Sources/Core/Extensions.swift new file mode 100644 index 0000000..8ade9d5 --- /dev/null +++ b/Samples/SPMSample/Sources/Core/Extensions.swift @@ -0,0 +1,13 @@ +import Foundation + +public extension String { + var trimmed: String { + trimmingCharacters(in: .whitespacesAndNewlines) + } +} + +public extension Date { + var timestamp: Int64 { + Int64(timeIntervalSince1970) + } +} diff --git a/Samples/SPMSample/Sources/Core/Logger.swift b/Samples/SPMSample/Sources/Core/Logger.swift new file mode 100644 index 0000000..87f5f23 --- /dev/null +++ b/Samples/SPMSample/Sources/Core/Logger.swift @@ -0,0 +1,13 @@ +import Foundation + +public struct Logger { + public init() {} + + public func log(_ message: String) { + print("[LOG] \(message)") + } + + public func error(_ message: String) { + print("[ERROR] \(message)") + } +} diff --git a/Samples/SPMSample/Sources/DataLayer/DatabaseManager.swift b/Samples/SPMSample/Sources/DataLayer/DatabaseManager.swift new file mode 100644 index 0000000..4b6e712 --- /dev/null +++ b/Samples/SPMSample/Sources/DataLayer/DatabaseManager.swift @@ -0,0 +1,19 @@ +import Foundation +import RealmSwift +import Core + +public class DatabaseManager { + private let logger = Logger() + + public init() {} + + public func save(_ object: T) throws { + logger.log("Saving object to database") + // Simplified example + } + + public func fetch(_ type: T.Type) -> [T] { + logger.log("Fetching objects from database") + return [] + } +} diff --git a/Samples/SPMSample/Sources/Feature1/Feature1.swift b/Samples/SPMSample/Sources/Feature1/Feature1.swift new file mode 100644 index 0000000..c5dfa0b --- /dev/null +++ b/Samples/SPMSample/Sources/Feature1/Feature1.swift @@ -0,0 +1,23 @@ +import Foundation +import Core +import APIClient +import ImageService + +public class Feature1 { + private let apiClient = APIClient() + private let imageLoader = ImageLoader() + private let logger = Logger() + + public init() {} + + public func execute() async { + logger.log("Feature1: Executing user profile feature") + do { + try await apiClient.fetchAndStore(endpoint: "/api/user/profile") + imageLoader.loadImage(from: URL(string: "https://example.com/avatar.png")!) + logger.log("Feature1: User profile loaded successfully") + } catch { + logger.error("Feature1: Error - \(error)") + } + } +} diff --git a/Samples/SPMSample/Sources/Feature2/Feature2.swift b/Samples/SPMSample/Sources/Feature2/Feature2.swift new file mode 100644 index 0000000..277359f --- /dev/null +++ b/Samples/SPMSample/Sources/Feature2/Feature2.swift @@ -0,0 +1,23 @@ +import Foundation +import Core +import Networking +import DataLayer + +public class Feature2 { + private let networkClient = NetworkClient() + private let databaseManager = DatabaseManager() + private let logger = Logger() + + public init() {} + + public func execute() async { + logger.log("Feature2: Executing data sync feature") + do { + let data = try await networkClient.request("/api/sync") + // Process and store sync data + logger.log("Feature2: Data synced successfully") + } catch { + logger.error("Feature2: Error - \(error)") + } + } +} diff --git a/Samples/SPMSample/Sources/ImageService/ImageLoader.swift b/Samples/SPMSample/Sources/ImageService/ImageLoader.swift new file mode 100644 index 0000000..8828129 --- /dev/null +++ b/Samples/SPMSample/Sources/ImageService/ImageLoader.swift @@ -0,0 +1,14 @@ +import Foundation +import Kingfisher +import Core + +public class ImageLoader { + private let logger = Logger() + + public init() {} + + public func loadImage(from url: URL) { + logger.log("Loading image from: \(url)") + // Simplified example using Kingfisher + } +} diff --git a/Samples/SPMSample/Sources/Networking/NetworkClient.swift b/Samples/SPMSample/Sources/Networking/NetworkClient.swift new file mode 100644 index 0000000..7f7023c --- /dev/null +++ b/Samples/SPMSample/Sources/Networking/NetworkClient.swift @@ -0,0 +1,16 @@ +import Foundation +import Alamofire +import SwiftyJSON +import Core + +public class NetworkClient { + private let logger = Logger() + + public init() {} + + public func request(_ endpoint: String) async throws -> JSON { + logger.log("Making request to: \(endpoint)") + // Simplified example + return JSON([:]) + } +} diff --git a/Samples/SPMSample/Sources/SPMSample/SPMSample.swift b/Samples/SPMSample/Sources/SPMSample/SPMSample.swift index cbf7e2c..280fe45 100644 --- a/Samples/SPMSample/Sources/SPMSample/SPMSample.swift +++ b/Samples/SPMSample/Sources/SPMSample/SPMSample.swift @@ -1,13 +1,33 @@ import Foundation -import Alamofire -import RealmSwift -import SwiftyJSON -import Kingfisher +import Core +import Networking +import DataLayer +import APIClient +import ImageService +import Feature1 +import Feature2 public struct SPMSample { + private let apiClient = APIClient() + private let imageLoader = ImageLoader() + private let feature1 = Feature1() + private let feature2 = Feature2() + private let logger = Logger() + public init() {} - public func example() { - print("SPM Sample with popular dependencies") + public func run() async { + logger.log("SPM Sample - Complex multi-module architecture") + do { + try await apiClient.fetchAndStore(endpoint: "/api/data") + imageLoader.loadImage(from: URL(string: "https://example.com/image.png")!) + + await feature1.execute() + await feature2.execute() + + logger.log("Application running successfully") + } catch { + logger.error("Error: \(error)") + } } } diff --git a/Samples/SPMSample/Tests/CoreTests/LoggerTests.swift b/Samples/SPMSample/Tests/CoreTests/LoggerTests.swift new file mode 100644 index 0000000..1f6fd36 --- /dev/null +++ b/Samples/SPMSample/Tests/CoreTests/LoggerTests.swift @@ -0,0 +1,10 @@ +import XCTest +@testable import Core + +final class LoggerTests: XCTestCase { + func testLogger() { + let logger = Logger() + logger.log("Test message") + logger.error("Test error") + } +} diff --git a/Samples/SPMSample/Tests/NetworkingTests/NetworkClientTests.swift b/Samples/SPMSample/Tests/NetworkingTests/NetworkClientTests.swift new file mode 100644 index 0000000..effd755 --- /dev/null +++ b/Samples/SPMSample/Tests/NetworkingTests/NetworkClientTests.swift @@ -0,0 +1,10 @@ +import XCTest +@testable import Networking + +final class NetworkClientTests: XCTestCase { + func testNetworkClient() async throws { + let client = NetworkClient() + let result = try await client.request("/test") + XCTAssertNotNil(result) + } +} diff --git a/Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift b/Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift index d531c20..8d453cb 100644 --- a/Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift +++ b/Samples/SPMSample/Tests/SPMSampleTests/SPMSampleTests.swift @@ -2,8 +2,8 @@ import XCTest @testable import SPMSample final class SPMSampleTests: XCTestCase { - func testExample() throws { + func testSample() async throws { let sample = SPMSample() - sample.example() + await sample.run() } }