├── .gitignore ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Demo ├── Demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── Demo.xcscheme ├── Demo │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-iOS-1024.png │ │ │ ├── Icon-macOS-1024.png │ │ │ ├── Icon-macOS-128.png │ │ │ ├── Icon-macOS-16.png │ │ │ ├── Icon-macOS-256.png │ │ │ ├── Icon-macOS-32.png │ │ │ ├── Icon-macOS-512.png │ │ │ └── Icon-macOS-64.png │ │ └── Contents.json │ ├── DemoApp.swift │ ├── DemoContext.swift │ ├── HomeScreen.swift │ ├── Info.plist │ ├── LicenseKit+Demo.swift │ ├── LicenseScreen.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ └── Views │ │ ├── InfoListItem.swift │ │ ├── LicenseLink.swift │ │ └── StatusListItem.swift └── DemoPackage │ ├── .gitignore │ ├── .swiftpm │ └── xcode │ │ └── package.xcworkspace │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── Package.resolved │ ├── Package.swift │ ├── README.md │ ├── Sources │ └── DemoPackage │ │ ├── Bundle │ │ └── Bundle+DemoPackage.swift │ │ ├── DemoPackage.swift │ │ ├── DemoPackageFeature.swift │ │ ├── LicenseKit+Demo.swift │ │ └── Resources │ │ └── licenses.txt │ └── Tests │ └── DemoPackageTests │ └── DemoPackageTests.swift ├── LICENSE ├── Package.swift ├── README.md ├── RELEASE_NOTES.md ├── Resources ├── Icon.png └── Logo.jpg ├── binary_version.sh ├── package_version.sh └── scripts ├── build.sh ├── chmod.sh ├── docc.sh ├── framework.sh ├── git_default_branch.sh ├── package_docc.sh ├── package_framework.sh ├── package_name.sh ├── package_version.sh ├── sync_from.sh ├── test.sh ├── version.sh ├── version_bump.sh ├── version_number.sh ├── version_validate_git.sh └── version_validate_target.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # SPM defaults 2 | .DS_Store 3 | /.build 4 | /Packages 5 | xcuserdata/ 6 | 7 | Docs/ 8 | 9 | ## Build generated 10 | .build/ 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | # Bundler 37 | .bundle 38 | 39 | # fastlane 40 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 41 | # screenshots whenever they are needed. 42 | # For more information about the recommended setup visit: 43 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 44 | fastlane/report.xml 45 | fastlane/Preview.html 46 | fastlane/screenshots 47 | fastlane/test_output 48 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - cyclomatic_complexity 3 | - function_body_length 4 | - function_parameter_count 5 | - identifier_name 6 | - large_tuple 7 | - statement_position 8 | - trailing_whitespace 9 | - line_length 10 | - todo 11 | - type_name 12 | - vertical_whitespace 13 | 14 | included: 15 | - Sources 16 | - Tests 17 | - KeyboardKitDemo 18 | 19 | identifier_name: 20 | excluded: 21 | - id 22 | - x 23 | - y 24 | - dx 25 | - dy 26 | - vc 27 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 90; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A913F7932D9B53C70096DFA5 /* LicenseKit in Frameworks */ = {isa = PBXBuildFile; productRef = A913F7922D9B53C70096DFA5 /* LicenseKit */; }; 11 | A954FE0327B64722003F7180 /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A954FE0227B64722003F7180 /* DemoApp.swift */; }; 12 | A954FE0727B64722003F7180 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A954FE0627B64722003F7180 /* Assets.xcassets */; }; 13 | A954FE0A27B64722003F7180 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A954FE0927B64722003F7180 /* Preview Assets.xcassets */; }; 14 | A95DFBCC27B6AE9A005580F2 /* DemoPackage in Frameworks */ = {isa = PBXBuildFile; productRef = A95DFBCB27B6AE9A005580F2 /* DemoPackage */; }; 15 | A95DFBCE27B6AEBA005580F2 /* DemoContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95DFBCD27B6AEBA005580F2 /* DemoContext.swift */; }; 16 | A95DFBD727B80FB8005580F2 /* LicenseScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95DFBD627B80FB8005580F2 /* LicenseScreen.swift */; }; 17 | A95DFBDB27B81064005580F2 /* StatusListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95DFBDA27B81064005580F2 /* StatusListItem.swift */; }; 18 | A95DFBDD27B81118005580F2 /* InfoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95DFBDC27B81118005580F2 /* InfoListItem.swift */; }; 19 | A95DFBDF27B8128B005580F2 /* HomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95DFBDE27B8128B005580F2 /* HomeScreen.swift */; }; 20 | A95DFBE327B81474005580F2 /* LicenseLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95DFBE227B81474005580F2 /* LicenseLink.swift */; }; 21 | A98EE6FD2DBA864B005DBED0 /* LicenseKit+Demo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98EE6FC2DBA864B005DBED0 /* LicenseKit+Demo.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | A954FDFF27B64722003F7180 /* Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | A954FE0227B64722003F7180 /* DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoApp.swift; sourceTree = ""; }; 27 | A954FE0627B64722003F7180 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 28 | A954FE0927B64722003F7180 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 29 | A954FE1227B64744003F7180 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 30 | A95DFBCA27B6AE7A005580F2 /* DemoPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = DemoPackage; sourceTree = ""; }; 31 | A95DFBCD27B6AEBA005580F2 /* DemoContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoContext.swift; sourceTree = ""; }; 32 | A95DFBD627B80FB8005580F2 /* LicenseScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseScreen.swift; sourceTree = ""; }; 33 | A95DFBDA27B81064005580F2 /* StatusListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusListItem.swift; sourceTree = ""; }; 34 | A95DFBDC27B81118005580F2 /* InfoListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoListItem.swift; sourceTree = ""; }; 35 | A95DFBDE27B8128B005580F2 /* HomeScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeScreen.swift; sourceTree = ""; }; 36 | A95DFBE227B81474005580F2 /* LicenseLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LicenseLink.swift; sourceTree = ""; }; 37 | A98EE6FC2DBA864B005DBED0 /* LicenseKit+Demo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LicenseKit+Demo.swift"; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | A954FDFC27B64722003F7180 /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | files = ( 44 | A95DFBCC27B6AE9A005580F2 /* DemoPackage in Frameworks */, 45 | A913F7932D9B53C70096DFA5 /* LicenseKit in Frameworks */, 46 | ); 47 | }; 48 | /* End PBXFrameworksBuildPhase section */ 49 | 50 | /* Begin PBXGroup section */ 51 | A954FDF627B64722003F7180 = { 52 | isa = PBXGroup; 53 | children = ( 54 | A954FE1027B6473D003F7180 /* Packages */, 55 | A954FE0127B64722003F7180 /* Demo */, 56 | A954FE0027B64722003F7180 /* Products */, 57 | A954FE1327B6478C003F7180 /* Frameworks */, 58 | ); 59 | sourceTree = ""; 60 | }; 61 | A954FE0027B64722003F7180 /* Products */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | A954FDFF27B64722003F7180 /* Demo.app */, 65 | ); 66 | name = Products; 67 | sourceTree = ""; 68 | }; 69 | A954FE0127B64722003F7180 /* Demo */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | A954FE0627B64722003F7180 /* Assets.xcassets */, 73 | A954FE0827B64722003F7180 /* Preview Content */, 74 | A95DFBD827B8103D005580F2 /* Views */, 75 | A954FE0227B64722003F7180 /* DemoApp.swift */, 76 | A95DFBCD27B6AEBA005580F2 /* DemoContext.swift */, 77 | A95DFBDE27B8128B005580F2 /* HomeScreen.swift */, 78 | A95DFBD627B80FB8005580F2 /* LicenseScreen.swift */, 79 | A98EE6FC2DBA864B005DBED0 /* LicenseKit+Demo.swift */, 80 | A954FE1227B64744003F7180 /* Info.plist */, 81 | ); 82 | path = Demo; 83 | sourceTree = ""; 84 | }; 85 | A954FE0827B64722003F7180 /* Preview Content */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | A954FE0927B64722003F7180 /* Preview Assets.xcassets */, 89 | ); 90 | path = "Preview Content"; 91 | sourceTree = ""; 92 | }; 93 | A954FE1027B6473D003F7180 /* Packages */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | A95DFBCA27B6AE7A005580F2 /* DemoPackage */, 97 | ); 98 | name = Packages; 99 | sourceTree = ""; 100 | }; 101 | A954FE1327B6478C003F7180 /* Frameworks */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | ); 105 | name = Frameworks; 106 | sourceTree = ""; 107 | }; 108 | A95DFBD827B8103D005580F2 /* Views */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | A95DFBDC27B81118005580F2 /* InfoListItem.swift */, 112 | A95DFBE227B81474005580F2 /* LicenseLink.swift */, 113 | A95DFBDA27B81064005580F2 /* StatusListItem.swift */, 114 | ); 115 | path = Views; 116 | sourceTree = ""; 117 | }; 118 | /* End PBXGroup section */ 119 | 120 | /* Begin PBXNativeTarget section */ 121 | A954FDFE27B64722003F7180 /* Demo */ = { 122 | isa = PBXNativeTarget; 123 | buildConfigurationList = A954FE0D27B64722003F7180 /* Build configuration list for PBXNativeTarget "Demo" */; 124 | buildPhases = ( 125 | A954FDFB27B64722003F7180 /* Sources */, 126 | A954FDFC27B64722003F7180 /* Frameworks */, 127 | A954FDFD27B64722003F7180 /* Resources */, 128 | ); 129 | buildRules = ( 130 | ); 131 | name = Demo; 132 | packageProductDependencies = ( 133 | A95DFBCB27B6AE9A005580F2 /* DemoPackage */, 134 | A913F7922D9B53C70096DFA5 /* LicenseKit */, 135 | ); 136 | productName = Demo; 137 | productReference = A954FDFF27B64722003F7180 /* Demo.app */; 138 | productType = "com.apple.product-type.application"; 139 | }; 140 | /* End PBXNativeTarget section */ 141 | 142 | /* Begin PBXProject section */ 143 | A954FDF727B64722003F7180 /* Project object */ = { 144 | isa = PBXProject; 145 | attributes = { 146 | BuildIndependentTargetsInParallel = 1; 147 | LastSwiftUpdateCheck = 1320; 148 | LastUpgradeCheck = 1630; 149 | ORGANIZATIONNAME = "Daniel Saidi"; 150 | TargetAttributes = { 151 | A954FDFE27B64722003F7180 = { 152 | CreatedOnToolsVersion = 13.2.1; 153 | }; 154 | }; 155 | }; 156 | buildConfigurationList = A954FDFA27B64722003F7180 /* Build configuration list for PBXProject "Demo" */; 157 | developmentRegion = en; 158 | hasScannedForEncodings = 0; 159 | knownRegions = ( 160 | en, 161 | Base, 162 | ); 163 | mainGroup = A954FDF627B64722003F7180; 164 | packageReferences = ( 165 | A913F7912D9B53C70096DFA5 /* XCRemoteSwiftPackageReference "LicenseKit" */, 166 | ); 167 | preferredProjectObjectVersion = 90; 168 | productRefGroup = A954FE0027B64722003F7180 /* Products */; 169 | projectDirPath = ""; 170 | projectRoot = ""; 171 | targets = ( 172 | A954FDFE27B64722003F7180 /* Demo */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXResourcesBuildPhase section */ 178 | A954FDFD27B64722003F7180 /* Resources */ = { 179 | isa = PBXResourcesBuildPhase; 180 | files = ( 181 | A954FE0A27B64722003F7180 /* Preview Assets.xcassets in Resources */, 182 | A954FE0727B64722003F7180 /* Assets.xcassets in Resources */, 183 | ); 184 | }; 185 | /* End PBXResourcesBuildPhase section */ 186 | 187 | /* Begin PBXSourcesBuildPhase section */ 188 | A954FDFB27B64722003F7180 /* Sources */ = { 189 | isa = PBXSourcesBuildPhase; 190 | files = ( 191 | A95DFBDD27B81118005580F2 /* InfoListItem.swift in Sources */, 192 | A95DFBDB27B81064005580F2 /* StatusListItem.swift in Sources */, 193 | A95DFBDF27B8128B005580F2 /* HomeScreen.swift in Sources */, 194 | A95DFBE327B81474005580F2 /* LicenseLink.swift in Sources */, 195 | A95DFBCE27B6AEBA005580F2 /* DemoContext.swift in Sources */, 196 | A95DFBD727B80FB8005580F2 /* LicenseScreen.swift in Sources */, 197 | A954FE0327B64722003F7180 /* DemoApp.swift in Sources */, 198 | A98EE6FD2DBA864B005DBED0 /* LicenseKit+Demo.swift in Sources */, 199 | ); 200 | }; 201 | /* End PBXSourcesBuildPhase section */ 202 | 203 | /* Begin XCBuildConfiguration section */ 204 | A954FE0B27B64722003F7180 /* Debug configuration for PBXProject "Demo" */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | CLANG_ANALYZER_NONNULL = YES; 209 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 210 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 211 | CLANG_CXX_LIBRARY = "libc++"; 212 | CLANG_ENABLE_MODULES = YES; 213 | CLANG_ENABLE_OBJC_ARC = YES; 214 | CLANG_ENABLE_OBJC_WEAK = YES; 215 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 216 | CLANG_WARN_BOOL_CONVERSION = YES; 217 | CLANG_WARN_COMMA = YES; 218 | CLANG_WARN_CONSTANT_CONVERSION = YES; 219 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 220 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 221 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 222 | CLANG_WARN_EMPTY_BODY = YES; 223 | CLANG_WARN_ENUM_CONVERSION = YES; 224 | CLANG_WARN_INFINITE_RECURSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 228 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 230 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 232 | CLANG_WARN_STRICT_PROTOTYPES = YES; 233 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | COPY_PHASE_STRIP = NO; 238 | DEBUG_INFORMATION_FORMAT = dwarf; 239 | DEVELOPMENT_TEAM = PMEDFW438U; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | ENABLE_TESTABILITY = YES; 242 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 243 | GCC_C_LANGUAGE_STANDARD = gnu11; 244 | GCC_DYNAMIC_NO_PIC = NO; 245 | GCC_NO_COMMON_BLOCKS = YES; 246 | GCC_OPTIMIZATION_LEVEL = 0; 247 | GCC_PREPROCESSOR_DEFINITIONS = ( 248 | "DEBUG=1", 249 | "$(inherited)", 250 | ); 251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 253 | GCC_WARN_UNDECLARED_SELECTOR = YES; 254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 255 | GCC_WARN_UNUSED_FUNCTION = YES; 256 | GCC_WARN_UNUSED_VARIABLE = YES; 257 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 258 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 259 | MTL_FAST_MATH = YES; 260 | ONLY_ACTIVE_ARCH = YES; 261 | SDKROOT = iphoneos; 262 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 263 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 264 | }; 265 | name = Debug; 266 | }; 267 | A954FE0C27B64722003F7180 /* Release configuration for PBXProject "Demo" */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_ENABLE_OBJC_WEAK = YES; 278 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 279 | CLANG_WARN_BOOL_CONVERSION = YES; 280 | CLANG_WARN_COMMA = YES; 281 | CLANG_WARN_CONSTANT_CONVERSION = YES; 282 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 283 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 284 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INFINITE_RECURSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 291 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 294 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 295 | CLANG_WARN_STRICT_PROTOTYPES = YES; 296 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 297 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 298 | CLANG_WARN_UNREACHABLE_CODE = YES; 299 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 300 | COPY_PHASE_STRIP = NO; 301 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 302 | DEVELOPMENT_TEAM = PMEDFW438U; 303 | ENABLE_NS_ASSERTIONS = NO; 304 | ENABLE_STRICT_OBJC_MSGSEND = YES; 305 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu11; 307 | GCC_NO_COMMON_BLOCKS = YES; 308 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 309 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 310 | GCC_WARN_UNDECLARED_SELECTOR = YES; 311 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 312 | GCC_WARN_UNUSED_FUNCTION = YES; 313 | GCC_WARN_UNUSED_VARIABLE = YES; 314 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 315 | MTL_ENABLE_DEBUG_INFO = NO; 316 | MTL_FAST_MATH = YES; 317 | SDKROOT = iphoneos; 318 | SWIFT_COMPILATION_MODE = wholemodule; 319 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 320 | VALIDATE_PRODUCT = YES; 321 | }; 322 | name = Release; 323 | }; 324 | A954FE0E27B64722003F7180 /* Debug configuration for PBXNativeTarget "Demo" */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 328 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 329 | CODE_SIGN_STYLE = Automatic; 330 | CURRENT_PROJECT_VERSION = 1; 331 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; 332 | ENABLE_PREVIEWS = YES; 333 | GENERATE_INFOPLIST_FILE = YES; 334 | INFOPLIST_FILE = Demo/Info.plist; 335 | INFOPLIST_KEY_CFBundleDisplayName = LicenseKit; 336 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 337 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 338 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 339 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 340 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 341 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 342 | LD_RUNPATH_SEARCH_PATHS = ( 343 | "$(inherited)", 344 | "@executable_path/Frameworks", 345 | ); 346 | MARKETING_VERSION = 1.0; 347 | PRODUCT_BUNDLE_IDENTIFIER = com.licensekit.demo; 348 | PRODUCT_NAME = "$(TARGET_NAME)"; 349 | SWIFT_EMIT_LOC_STRINGS = YES; 350 | SWIFT_VERSION = 6.0; 351 | TARGETED_DEVICE_FAMILY = "1,2"; 352 | }; 353 | name = Debug; 354 | }; 355 | A954FE0F27B64722003F7180 /* Release configuration for PBXNativeTarget "Demo" */ = { 356 | isa = XCBuildConfiguration; 357 | buildSettings = { 358 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 359 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 360 | CODE_SIGN_STYLE = Automatic; 361 | CURRENT_PROJECT_VERSION = 1; 362 | DEVELOPMENT_ASSET_PATHS = "\"Demo/Preview Content\""; 363 | ENABLE_PREVIEWS = YES; 364 | GENERATE_INFOPLIST_FILE = YES; 365 | INFOPLIST_FILE = Demo/Info.plist; 366 | INFOPLIST_KEY_CFBundleDisplayName = LicenseKit; 367 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 368 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 369 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 370 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 371 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 372 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 373 | LD_RUNPATH_SEARCH_PATHS = ( 374 | "$(inherited)", 375 | "@executable_path/Frameworks", 376 | ); 377 | MARKETING_VERSION = 1.0; 378 | PRODUCT_BUNDLE_IDENTIFIER = com.licensekit.demo; 379 | PRODUCT_NAME = "$(TARGET_NAME)"; 380 | SWIFT_EMIT_LOC_STRINGS = YES; 381 | SWIFT_VERSION = 6.0; 382 | TARGETED_DEVICE_FAMILY = "1,2"; 383 | }; 384 | name = Release; 385 | }; 386 | /* End XCBuildConfiguration section */ 387 | 388 | /* Begin XCConfigurationList section */ 389 | A954FDFA27B64722003F7180 /* Build configuration list for PBXProject "Demo" */ = { 390 | isa = XCConfigurationList; 391 | buildConfigurations = ( 392 | A954FE0B27B64722003F7180 /* Debug configuration for PBXProject "Demo" */, 393 | A954FE0C27B64722003F7180 /* Release configuration for PBXProject "Demo" */, 394 | ); 395 | defaultConfigurationName = Release; 396 | }; 397 | A954FE0D27B64722003F7180 /* Build configuration list for PBXNativeTarget "Demo" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | A954FE0E27B64722003F7180 /* Debug configuration for PBXNativeTarget "Demo" */, 401 | A954FE0F27B64722003F7180 /* Release configuration for PBXNativeTarget "Demo" */, 402 | ); 403 | defaultConfigurationName = Release; 404 | }; 405 | /* End XCConfigurationList section */ 406 | 407 | /* Begin XCRemoteSwiftPackageReference section */ 408 | A913F7912D9B53C70096DFA5 /* XCRemoteSwiftPackageReference "LicenseKit" */ = { 409 | isa = XCRemoteSwiftPackageReference; 410 | repositoryURL = "https://github.com/LicenseKit/LicenseKit"; 411 | requirement = { 412 | kind = upToNextMajorVersion; 413 | minimumVersion = 1.2.4; 414 | }; 415 | }; 416 | /* End XCRemoteSwiftPackageReference section */ 417 | 418 | /* Begin XCSwiftPackageProductDependency section */ 419 | A913F7922D9B53C70096DFA5 /* LicenseKit */ = { 420 | isa = XCSwiftPackageProductDependency; 421 | package = A913F7912D9B53C70096DFA5 /* XCRemoteSwiftPackageReference "LicenseKit" */; 422 | productName = LicenseKit; 423 | }; 424 | A95DFBCB27B6AE9A005580F2 /* DemoPackage */ = { 425 | isa = XCSwiftPackageProductDependency; 426 | productName = DemoPackage; 427 | }; 428 | /* End XCSwiftPackageProductDependency section */ 429 | }; 430 | rootObject = A954FDF727B64722003F7180 /* Project object */; 431 | } 432 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "06cca5813ecd8c77cf31a4136e7705c2ec0c503f5e867e12a58e2b800b39a2c2", 3 | "pins" : [ 4 | { 5 | "identity" : "licensekit", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/LicenseKit/LicenseKit", 8 | "state" : { 9 | "revision" : "3cb10784c958a0730b35ba0506dcb1004f86e2bb", 10 | "version" : "1.2.4" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /Demo/Demo.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-iOS-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "Icon-macOS-16.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Icon-macOS-32.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "Icon-macOS-32.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Icon-macOS-64.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "Icon-macOS-128.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Icon-macOS-256.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "Icon-macOS-256.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Icon-macOS-512.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "Icon-macOS-512.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "Icon-macOS-1024.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-iOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-iOS-1024.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-1024.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-128.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-16.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-256.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-32.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-512.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Icon-macOS-64.png -------------------------------------------------------------------------------- /Demo/Demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo/DemoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoApp.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2022-02-11. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import DemoPackage 11 | import LicenseKit 12 | 13 | @main 14 | struct DemoApp: App { 15 | 16 | @StateObject 17 | private var context = DemoContext() 18 | 19 | // This sets how the DemoPackage should get app licenses. 20 | private let packageLicenseSource = License.Source.binary 21 | 22 | var body: some Scene { 23 | WindowGroup { 24 | HomeScreen() 25 | .environmentObject(context) 26 | .task(setupDemoPackage) 27 | } 28 | } 29 | } 30 | 31 | extension DemoApp { 32 | 33 | // This is an app-specific license key that's defined by 34 | // the app itself, that can be used by the demo app user 35 | // to unlock features in the app. 36 | static let appLicenseKey = "4B142177-214B-447F-9E57-8E906DE6FCFC" 37 | 38 | // This is an app-specific license key that's defined in 39 | // the demo package and can only be used by the demo app, 40 | // to unlock features in the package. 41 | static let packageLicenseKey = "6A34BED3-5A7F-44B9-A3C6-3415463C4D0B" 42 | 43 | // This is a product-specific license key that's defined 44 | // in LicenseKit and can be used by both the package and 45 | // the app to unlock features in LicenseKit. 46 | static let productLicenseKey = "299B33C6-061C-4285-8189-90525BCAF098" 47 | } 48 | 49 | private extension DemoApp { 50 | 51 | /// Try to set up the DemoPackage with a license key, so 52 | /// that this app can use features from the package. 53 | func setupDemoPackage() async { 54 | do { 55 | let license = try await DemoPackage.setup( 56 | withLicenseKey: DemoApp.packageLicenseKey, 57 | source: packageLicenseSource 58 | ) 59 | context.packageLicense = license 60 | } catch { 61 | context.packageLicenseError = error as? License.ValidationError 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Demo/Demo/DemoContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoContext.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2022-02-11. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import LicenseKit 11 | 12 | /// This observable class is used to manage observable state 13 | /// for the app. 14 | /// 15 | /// The `packageLicense` is registered when the app launches, 16 | /// and is used by the app to access features in the package. 17 | /// 18 | /// The `appLicense` is registered when a user taps a button, 19 | /// and is used to let a user access features within the app. 20 | class DemoContext: ObservableObject { 21 | 22 | init() {} 23 | 24 | @Published var packageLicense: License? 25 | @Published var packageLicenseError: License.ValidationError? 26 | 27 | @Published var appLicense: License? 28 | @Published var appLicenseError: License.ValidationError? 29 | } 30 | 31 | extension DemoContext { 32 | 33 | var hasAppLicense: Bool { appLicense != nil } 34 | var hasPackageLicense: Bool { packageLicense != nil } 35 | } 36 | -------------------------------------------------------------------------------- /Demo/Demo/HomeScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeScreen.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2022-02-11. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import DemoPackage 11 | import LicenseKit 12 | 13 | struct HomeScreen: View { 14 | 15 | init() { 16 | do { 17 | /// Try to resolve a demo package feature, which 18 | /// requires that the app has a valid license. 19 | packageFeature = try DemoPackageFeature() 20 | } catch { 21 | packageFeature = nil 22 | print("The package feature threw an error: \(error).") 23 | } 24 | } 25 | 26 | private let packageFeature: DemoPackageFeature? 27 | 28 | @State 29 | private var appLicenseKey = DemoApp.appLicenseKey 30 | 31 | @EnvironmentObject 32 | private var context: DemoContext 33 | 34 | var body: some View { 35 | NavigationView { 36 | List { 37 | packageLicenseSection 38 | appLicenseSection 39 | } 40 | .font(.callout) 41 | .navigationTitle("LicenseKit demo") 42 | } 43 | } 44 | } 45 | 46 | private extension HomeScreen { 47 | 48 | var packageLicenseSection: some View { 49 | Section(header: Text("Demo Package License"), footer: Text("This license is defined in the demo package. The app registers it on launch.")) { 50 | StatusListItem(status: context.hasPackageLicense, title: "Has valid package license") 51 | StatusListItem(status: packageFeature != nil, title: "Can access package feature") 52 | if let license = context.packageLicense { 53 | LicenseLink(license: license) 54 | } 55 | text(for: context.packageLicenseError) 56 | } 57 | } 58 | 59 | var appLicenseSection: some View { 60 | Group { 61 | Section(header: Text("App License"), footer: Text("This license is defined in the app. Register it by tapping the button below.")) { 62 | TextField("Enter app license key", text: $appLicenseKey) 63 | StatusListItem(status: context.hasAppLicense, title: "Has valid app license") 64 | if let license = context.appLicense { 65 | LicenseLink(license: license) 66 | } 67 | } 68 | 69 | Section(footer: text(for: context.appLicenseError)) { 70 | appLicenseRegisterButton 71 | } 72 | } 73 | } 74 | } 75 | 76 | private extension HomeScreen { 77 | 78 | var appLicenseRegisterButton: some View { 79 | Button(action: registerAppLicenseKey) { 80 | Text("Register app license key") 81 | .frame(maxWidth: .infinity) 82 | } 83 | .controlSize(.large) 84 | .buttonStyle(.borderedProminent) 85 | .listRowInsets(EdgeInsets()) 86 | .listRowBackground(Color.clear) 87 | } 88 | 89 | @ViewBuilder 90 | func text(for error: License.ValidationError?) -> some View { 91 | if let error = error { 92 | Text("ERROR: \(error.displayName)") 93 | .frame(maxWidth: .infinity) 94 | .padding() 95 | } 96 | } 97 | } 98 | 99 | private extension HomeScreen { 100 | 101 | func registerAppLicenseKey() { 102 | Task { 103 | do { 104 | context.appLicense = nil 105 | context.appLicenseError = nil 106 | let license = try await LicenseEngine.getAppLicense(withLicenseKey: appLicenseKey) 107 | context.appLicense = license 108 | } catch { 109 | context.appLicenseError = error as? License.ValidationError 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Demo/Demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Demo/Demo/LicenseKit+Demo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicenseKit+Demo.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2025-04-24. 6 | // Copyright © 2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import LicenseKit 10 | 11 | extension LicenseEngine { 12 | 13 | /// Try to get an app-specific license for a license key 14 | /// that is defined within LicenseKit, and that can only 15 | /// be used by the demo app and the demo package. 16 | static func getAppLicense( 17 | withLicenseKey licenseKey: String 18 | ) async throws -> License { 19 | let engine = try await createAppSpecificEngine() 20 | let license = try await engine.getLicense(withKey: licenseKey) 21 | return license 22 | } 23 | 24 | /// Try to create an app-specific license engine. 25 | /// 26 | /// The reason for adding the `sending` keyword, is that 27 | /// it makes it possible to use the function from within 28 | /// a SwiftUI view. 29 | static func createAppSpecificEngine() async throws -> sending LicenseEngine { 30 | let licenseKey = await DemoApp.appLicenseKey 31 | return try await LicenseEngine(licenseKey: DemoApp.productLicenseKey) { 32 | .binary(licenses: [ 33 | .init( 34 | licenseKey: licenseKey, 35 | customer: .init(name: "Demo app user"), 36 | tier: .gold, 37 | expirationDate: Date().addingTimeInterval(3600), 38 | allowsExpiration: false, 39 | platforms: [.iOS], 40 | additionalInfo: ["source" : License.Source.binary.id] 41 | ) 42 | ]) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Demo/Demo/LicenseScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicenseScreen.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2022-02-12. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import LicenseKit 10 | import SwiftUI 11 | 12 | /// This screen is used to display licenses for the demo app. 13 | struct LicenseScreen: View { 14 | 15 | let license: License 16 | 17 | var body: some View { 18 | List { 19 | Section(header: Text("License")) { 20 | InfoListItem(title: "License key", text: license.licenseKey) 21 | InfoListItem(title: "License tier", text: "\(license.tier.name) (level \(license.tier.level))") 22 | InfoListItem(title: "Expiration date", text: license.expirationDate?.formatted(date: .long, time: .shortened) ?? "-") 23 | InfoListItem(title: "Platforms", text: license.platforms.map { $0.id }.joined(separator: ", ")) 24 | InfoListItem(title: "Bundle IDs", text: bundleIdsText) 25 | InfoListItem(title: "Registration method", text: license.additionalInfo["registration-method"] ?? "") 26 | } 27 | Section(header: Text("Customer")) { 28 | InfoListItem(title: "Name", text: license.customer?.name ?? "-") 29 | InfoListItem(title: "Contact", text: license.customer?.contact ?? "-") 30 | InfoListItem(title: "Address", text: license.customer?.address ?? "-") 31 | InfoListItem(title: "Email", text: license.customer?.email ?? "-") 32 | InfoListItem(title: "Phone", text: license.customer?.phone ?? "-") 33 | InfoListItem(title: "Website", text: license.customer?.website ?? "-") 34 | } 35 | } 36 | .font(.callout) 37 | .navigationTitle("License information") 38 | } 39 | } 40 | 41 | private extension LicenseScreen { 42 | 43 | var bundleIdsText: String { 44 | license.bundleIds.isEmpty ? "-" : license.bundleIds.joined(separator: ", ") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Demo/Demo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Demo/Demo/Views/InfoListItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoListItem.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2022-02-12. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | /** 12 | This list item can be used to show any kind of informatoin. 13 | */ 14 | struct InfoListItem: View { 15 | 16 | let title: String 17 | let text: String 18 | 19 | var body: some View { 20 | VStack(alignment: .leading) { 21 | Text(title).font(.footnote) 22 | Text(text) 23 | } 24 | .lineLimit(1) 25 | .padding(.vertical, 5) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Demo/Demo/Views/LicenseLink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicenseLink.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2022-02-12. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import LicenseKit 10 | import SwiftUI 11 | 12 | struct LicenseLink: View { 13 | 14 | let license: License 15 | 16 | var body: some View { 17 | NavigationLink { 18 | LicenseScreen(license: license) 19 | } label: { 20 | Label { 21 | Text("License information") 22 | } icon: { 23 | Image(systemName: "info.circle") 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Demo/Demo/Views/StatusListItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StatusListItem.swift 3 | // Demo 4 | // 5 | // Created by Daniel Saidi on 2022-02-12. 6 | // Copyright © 2022 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | /** 12 | This list item can be used to show a bool status in lists. 13 | */ 14 | struct StatusListItem: View { 15 | 16 | let status: Bool 17 | let title: String 18 | 19 | var body: some View { 20 | HStack { 21 | Label { 22 | Text(title) 23 | } icon: { 24 | Circle() 25 | .padding(6) 26 | .foregroundColor(status ? .green : .red) 27 | } 28 | Spacer() 29 | Text(status ? "Yes" : "No") 30 | .font(.footnote) 31 | .foregroundColor(.secondary) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Demo/DemoPackage/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /Demo/DemoPackage/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "licensekit", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/LicenseKit/LicenseKit", 7 | "state" : { 8 | "revision" : "14cd1d185507c61006d50e3e52fc06486b05b562", 9 | "version" : "1.2.2" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "DemoPackage", 7 | platforms: [ 8 | .iOS(.v15), 9 | .macOS(.v13), 10 | .tvOS(.v15), 11 | .watchOS(.v8) 12 | ], 13 | products: [ 14 | .library( 15 | name: "DemoPackage", 16 | targets: ["DemoPackage"]), 17 | ], 18 | dependencies: [ 19 | //.package(name: "LicenseKit", path: "../../"), 20 | //.package(name: "LicenseKit", path: "../../licensekitsource"), 21 | .package(url: "https://github.com/LicenseKit/LicenseKit", .upToNextMajor(from: "1.2.4")) 22 | ], 23 | targets: [ 24 | .target( 25 | name: "DemoPackage", 26 | dependencies: ["LicenseKit"], 27 | resources: [.process("Resources")] 28 | ), 29 | .testTarget( 30 | name: "DemoPackageTests", 31 | dependencies: ["DemoPackage"]), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /Demo/DemoPackage/README.md: -------------------------------------------------------------------------------- 1 | # DemoPackage 2 | 3 | This package demonstrates how to add LicenseKit to a package 4 | and use it to protect features with a license key. 5 | 6 | The package has a `DemoPackageLicense` class that takes care 7 | of registering the package's LicenseKit license as well as a 8 | license for the demo app, which lets the app use the package. 9 | 10 | The demo app imports both LicenseKit and DemoPackage and has 11 | code that registers the demo app's DemoPackage license using 12 | the `DemoPackageLicense` class. This way, this paclage keeps 13 | its internal license informaton to itself. 14 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Sources/DemoPackage/Bundle/Bundle+DemoPackage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle+DemoPackage.swift 3 | // DemoPackage 4 | // 5 | // Created by Daniel Saidi on 2022-08-21. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Bundle { 12 | 13 | /// The name of the package bundle. This may change in a 14 | /// future Xcode update. 15 | /// 16 | /// If the Xcode name convention changes, just print the 17 | /// new path and look for the bundle name in the text: 18 | /// 19 | /// ``` 20 | /// Bundle(for: BundleFinder.self).resourceURL?.deletingLastPathComponent().deletingLastPathComponent() 21 | /// ``` 22 | static let demoPackageBundleName = "DemoPackage_DemoPackage" 23 | 24 | /** 25 | This bundle lets us use resources from DemoPackage. 26 | 27 | Hopefully, Apple will fix this bundle bug to remove the 28 | need for this workaround. 29 | 30 | Inspiration from here: 31 | https://developer.apple.com/forums/thread/664295 32 | https://dev.jeremygale.com/swiftui-how-to-use-custom-fonts-and-images-in-a-swift-package-cl0k9bv52013h6bnvhw76alid 33 | */ 34 | public static let demoPackage: Bundle = { 35 | let candidates = [ 36 | // Bundle should be present here when the package is linked into an App. 37 | Bundle.main.resourceURL, 38 | // Bundle should be present here when the package is linked into a framework. 39 | Bundle(for: BundleFinder.self).resourceURL, 40 | // For command-line tools. 41 | Bundle.main.bundleURL, 42 | // Bundle should be present here when running previews from a different package 43 | // (this is the path to "…/Debug-iphonesimulator/"). 44 | Bundle(for: BundleFinder.self) 45 | .resourceURL? 46 | .deletingLastPathComponent() 47 | .deletingLastPathComponent() 48 | .deletingLastPathComponent(), 49 | Bundle(for: BundleFinder.self) 50 | .resourceURL? 51 | .deletingLastPathComponent() 52 | .deletingLastPathComponent() 53 | ] 54 | 55 | for candidate in candidates { 56 | let bundlePath = candidate?.appendingPathComponent(demoPackageBundleName + ".bundle") 57 | if let bundle = bundlePath.flatMap(Bundle.init(url:)) { 58 | return bundle 59 | } 60 | } 61 | fatalError("Can't find custom bundle. See Bundle+DemoPackage.swift") 62 | }() 63 | } 64 | 65 | private extension Bundle { 66 | 67 | class BundleFinder {} 68 | } 69 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Sources/DemoPackage/DemoPackage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoPackage.swift 3 | // DemoPackage 4 | // 5 | // Created by Daniel Saidi on 2022-02-11. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LicenseKit 11 | 12 | /// This is the central class within the demo library. It is 13 | /// used to set up the library with any customer license key 14 | /// that unlocks protected features in the library. 15 | public final class DemoPackage { 16 | 17 | /// This function must be called by the demo app, to set 18 | /// up the library and unlock its features. 19 | public static func setup( 20 | withLicenseKey customerLicenseKey: String, 21 | source: License.Source 22 | ) async throws -> License { 23 | 24 | // Try to create a license engine with a license key 25 | // and a license store, using a source-based service. 26 | let licenseEngine = try await LicenseEngine( 27 | licenseKey: DemoPackage.productLicenseKey, 28 | licenseStore: LicenseStore.demo 29 | ) { 30 | licenseService(for: source) 31 | } 32 | 33 | // Try to get a license for the provided license key. 34 | let license = try await licenseEngine.getLicense( 35 | withKey: customerLicenseKey 36 | ) 37 | 38 | // Return the customer license. This license is also 39 | // auto-stored in the license store by the engine. 40 | return license 41 | } 42 | } 43 | 44 | extension DemoPackage { 45 | 46 | // This is an app-specific license key that's defined in 47 | // the demo package and can only be used by the demo app, 48 | // to unlock features in the package. 49 | static let packageLicenseKey = "6A34BED3-5A7F-44B9-A3C6-3415463C4D0B" 50 | 51 | // This is a product-specific license key that's defined 52 | // in LicenseKit and can be used by both the package and 53 | // the app to unlock features in LicenseKit. 54 | static let productLicenseKey = "299B33C6-061C-4285-8189-90525BCAF098" 55 | } 56 | 57 | private extension DemoPackage { 58 | 59 | /// Create a license service for the provided source. 60 | static func licenseService( 61 | for source: License.Source 62 | ) -> LicenseServiceType { 63 | switch source { 64 | 65 | /// Binary licenses are defined in `License+Demo`. 66 | case .binary: 67 | return .binary( 68 | licenses: [.packageLicense(method: "local")] 69 | ) 70 | 71 | /// File licenses are set in `Resources/licenses.txt`. 72 | case .file: 73 | return .fileName( 74 | fileName: "licenses", 75 | fileExtension: "txt", 76 | bundle: .demoPackage, 77 | licenseMapping: { row in 78 | License( 79 | licenseKey: row[0], 80 | customer: .init(name: row[1]), 81 | additionalInfo: ["registration-method": "csv"] 82 | ) 83 | } 84 | ) 85 | default: 86 | fatalError("Unhandled source") 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Sources/DemoPackage/DemoPackageFeature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DemoPackageFeature.swift 3 | // DemoPackage 4 | // 5 | // Created by Daniel Saidi on 2022-02-11. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LicenseKit 11 | 12 | /// This class represents a license protected feature, which 13 | /// requires that the app has a valid license. 14 | public class DemoPackageFeature { 15 | 16 | /// Create an instance of the demo feature. 17 | /// 18 | /// This initializer validates that this app has a valid 19 | /// license. If not, the initializer will throw an error. 20 | public init() throws { 21 | try License.validate(.current) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Sources/DemoPackage/LicenseKit+Demo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LicenseKit+Demo.swift 3 | // DemoPackage 4 | // 5 | // Created by Daniel Saidi on 2022-02-11. 6 | // Copyright © 2022-2025 Daniel Saidi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import LicenseKit 11 | 12 | extension License { 13 | 14 | /// The currently registered license, if any. 15 | static var current: License? { LicenseStore.demo.license } 16 | } 17 | 18 | extension LicenseStore { 19 | 20 | // This license store is used to store a current license. 21 | static let demo = LicenseStore() 22 | } 23 | 24 | extension License { 25 | 26 | /// This license can be used by the demo app, to make it 27 | /// possible to use license-protected package features. 28 | static func packageLicense(method: String) -> License { 29 | License( 30 | licenseKey: DemoPackage.packageLicenseKey, 31 | customer: .packageLicenseCustomer, 32 | expirationDate: Date().addingTimeInterval(3600), 33 | allowsExpiration: false, 34 | bundleIds: ["com.licensekit.demo"], 35 | platforms: .all, 36 | additionalInfo: ["registration-method" : method] 37 | ) 38 | } 39 | } 40 | 41 | extension License.Customer { 42 | 43 | /// This license customer is added to the demo license. 44 | static let packageLicenseCustomer = Self( 45 | name: "Kankoda", 46 | contact: "Daniel Saidi", 47 | address: "Stockholm, Sweden", 48 | email: "daniel.saidi@gmail.com", 49 | website: "https://kankoda.com") 50 | } 51 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Sources/DemoPackage/Resources/licenses.txt: -------------------------------------------------------------------------------- 1 | 6A34BED3-5A7F-44B9-A3C6-3415463C4D0B, Kankoda Sweden AB 2 | -------------------------------------------------------------------------------- /Demo/DemoPackage/Tests/DemoPackageTests/DemoPackageTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import DemoPackage 3 | 4 | final class DemoPackageTests: XCTestCase { 5 | func testExample() throws {} 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Closed Source License. 2 | 3 | This software and any associated software, documentation and 4 | resources (hereby referred to as "the Software") must only be 5 | used with a valid license key and a prior mutual and written 6 | license agreement between the licenser and the licensee. 7 | 8 | The Software must only be used in the application(s) that are 9 | included in the license agreement and must not be distributed 10 | to or used by developers, teams, companies or other parties 11 | that are not covered by the license agreement. 12 | 13 | Any attempts to bypass or work around these limitations or any 14 | other restrictions or security mechanisms in the Software, will 15 | be seen as an attempt to violate the license agreement and may 16 | render the licenser liable to compensate the licensee for any 17 | damages, loss of IP or any other economic losses caused by the 18 | infringement. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | Copyright © 2021-2025 Kankoda Sweden AB 30 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "LicenseKit", 7 | platforms: [ 8 | .iOS(.v15), 9 | .macOS(.v13), 10 | .tvOS(.v15), 11 | .watchOS(.v8) 12 | ], 13 | products: [ 14 | .library( 15 | name: "LicenseKit", 16 | targets: ["LicenseKit"] 17 | ) 18 | ], 19 | targets: [ 20 | .binaryTarget( 21 | name: "LicenseKit", 22 | url: "https://github.com/LicenseKit/LicenseKit/releases/download/1.2.4_binary/LicenseKit.zip", 23 | checksum: "389a58fc8148215a8f8fed06960aa24ddaba3a5b88e73093f60256ddf947cc1d" 24 | ) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Project Icon 3 |

4 | 5 |

6 | Version 7 | Swift 6.0 8 | Documentation 9 | Documentation 10 |

11 | 12 | 13 | 14 | # LicenseKit 15 | 16 | LicenseKit lets you protect your software with commercial licenses on major Apple platforms (iOS, macOS, tvOS, watchOS & visionOS). You can use it with boths apps and libraries, to require a license to use your software. 17 | 18 | LicenseKit lets you define licenses with code, read licenses from plain and encrypted files, validate licenses from an API, and integrate with external services like Gumroad, etc. 19 | 20 | LicenseKit can validate expiration date, platform, bundle ID, tier, environment, features, and much more. It lets you cache licenses to handle temporary connectivity loss, and combine multiple data sources for flexible validation. 21 | 22 | 23 | 24 | ## Pricing 25 | 26 | LicenseKit requires a commercial license to be used. It's free to start using, using the limited "FREE" license key, and affordable to scale. You can purchase a license or try out a free, unlimited trial from the [LicenseKit website][Website]. 27 | 28 | 29 | 30 | ## Installation 31 | 32 | LicenseKit can be installed with the Swift Package Manager: 33 | 34 | ``` 35 | https://github.com/Kankoda/LicenseKit.git 36 | ``` 37 | 38 | LicenseKit only has to be linked to the main target. If you use LicenseKit with a Swift package, make sure to set up your package to fetch both your library and LicenseKit. 39 | 40 | 41 | 42 | ## Features 43 | 44 | LicenseKit provides a bunch of license-specific features: 45 | 46 | * ✅ [License Validation][Licenses] - LicenseKit can validate licenses in many ways. 47 | * ⌨️ [Binary Licenses][Services] - LicenseKit lets you define licenses with source code. 48 | * 📄 [File-Based Licenses][Services] - LicenseKit lets you define licenses with plain text files. 49 | * 🌩️ [API/Cloud-Based Licenses][Services] - LicenseKit can validate licenses with web requests. 50 | * 💰 [Gumroad][Services] - LicenseKit can integrate directly with Gumroad. 51 | * 📦 [License Caching][Services] - LicenseKit can cache successful license validations. 52 | * ➡️ [Service Proxying][Services] - LicenseKit can chain multiple services together. 53 | 54 | 55 | 56 | ## Getting started 57 | 58 | With LicenseKit, your app/library should create a ``LicenseEngine`` with the license key you obtain when signing up for LicenseKit, and define which ``LicenseServiceType`` you want to use to use to fetch customer licenses. 59 | 60 | For instance, this would create a license engine with two licenses that are defined with source code and that will be validated on-device: 61 | 62 | ```swift 63 | let licenseEngine = try await LicenseEngine( 64 | licenseKey: "your-license-key", 65 | licenseStore: .myInternalLicenseStore // optional 66 | licenseService: { .binary( 67 | licenses: [ 68 | License(licenseKey: "license-key-1", ...), 69 | License(licenseKey: "license-key-2", ...) 70 | ] 71 | ) 72 | } 73 | ) 74 | ``` 75 | 76 | There are many service types to choose from, as described in the [license services article][Services]. You can define licenses with source code, read licenses from file, fetch licenses from an API, integrate with services like Gumroad, etc. 77 | 78 | Once you have a license engine, you can use it to resolve and validate licenses for your product, by letting users enter *their* license key. 79 | 80 | See the [getting-started guide][Getting-Started] for more information, and for how to set up license activation for apps and libraries. 81 | 82 | 83 | 84 | ## Documentation 85 | 86 | The [online documentation][Documentation] has articles, code examples etc. that let you overview the various parts of the library. 87 | 88 | 89 | 90 | ## Demo Application 91 | 92 | The demo app lets you try out the library on iOS and macOS. Just open and run the `Demo` project. 93 | 94 | 95 | 96 | ## Contact 97 | 98 | LicenseKit is handled by Kankoda: 99 | 100 | * [E-mail][Email] 101 | * [Website][Website] 102 | * [Bluesky][Bluesky] 103 | * [Mastodon][Mastodon] 104 | 105 | Reach out if you have any questions or need help any way. 106 | 107 | 108 | 109 | ## License 110 | 111 | LicenseKit is closed source. See the [LICENSE][License] file for more info. 112 | 113 | 114 | 115 | [Email]: mailto:info@kankoda.com 116 | [Website]: https://kankoda.com/licensekit 117 | [GitHub]: https://github.com/kankoda 118 | 119 | [Bluesky]: https://bsky.app/profile/kankoda.bsky.social 120 | [Twitter]: https://twitter.com/kankodahq 121 | [Mastodon]: https://mastodon.social/@kankoda 122 | 123 | [Documentation]: https://kankoda.github.io/LicenseKit/documentation/licensekit 124 | [Getting-Started]: https://kankoda.github.io/LicenseKit/documentation/licensekit/getting-started-article 125 | [License]: https://github.com/Kankoda/LicenseKit/blob/main/LICENSE 126 | 127 | [Licenses]: https://kankoda.github.io/LicenseKit/documentation/licensekit/understanding-licenses 128 | [Services]: https://kankoda.github.io/LicenseKit/documentation/licensekit/understanding-services 129 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | 3 | LicenseKit honors semantic versioning, with the following strategy: 4 | 5 | * Deprecations can happen at any time. 6 | * Deprecations are removed in `major` updates. 7 | * Breaking changes should only occur in `major` updates. 8 | * Breaking changes *can* occur in `minor` and `patch` updates, if the alternative is worse. 9 | 10 | These release notes cover the current major version. See older versions for older release notes. 11 | 12 | 13 | 14 | ## 1.2.4 15 | 16 | This version replaces 1.2.1 - 1.2.3 due to git and SPM problems. 17 | 18 | This version removes `LicenseStore.shared` and adds a public `LicenseStore` initializer instead. 19 | 20 | This version is also released as a multiplatform build. 21 | 22 | 23 | 24 | ## 1.2 25 | 26 | ### ✨ New Features 27 | 28 | * `License` has a new, static validication function. 29 | * `LicenseStore` is a new way to store a single license. 30 | * `LicenseEngine` can now be created with a license store. 31 | 32 | ### 💡 Adjustments 33 | 34 | * `License.ValidationError` has renamed multiple error types. 35 | 36 | 37 | 38 | ## 1.1 39 | 40 | ### ✨ New Features 41 | 42 | * `License` has a new `renewalDate` property. 43 | * `License.Customer` has a new `fullName` property. 44 | 45 | ### 💡 Adjustments 46 | 47 | * `License.allowsExpiration` is now `false` by default. 48 | 49 | 50 | 51 | ## 1.0.2 52 | 53 | ### 💡 Adjustments 54 | 55 | * `License.allowsExpiration` is now `false` by default. 56 | 57 | 58 | 59 | ## 1.0.1 60 | 61 | ### ✨ New Features 62 | 63 | * `License` has new matching and sorting functionality. 64 | 65 | 66 | 67 | ## 1.0 68 | 69 | This version bumps to Swift 6 and removes all previously deprecated code. 70 | 71 | ### ✨ New Features 72 | 73 | * `License` has new expiration properties. 74 | * `License` and all subtypes now implement `Hashable` and `Sendable`. 75 | -------------------------------------------------------------------------------- /Resources/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Resources/Icon.png -------------------------------------------------------------------------------- /Resources/Logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kankoda/LicenseKit/da17bee9f84c3e57c095ca81ab2dde9891bac1e9/Resources/Logo.jpg -------------------------------------------------------------------------------- /binary_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new package version binary tag. 5 | 6 | SCRIPT="scripts/version_bump.sh --no-semver" 7 | chmod +x $SCRIPT & bash $SCRIPT 8 | -------------------------------------------------------------------------------- /package_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new package version tag. 5 | 6 | SCRIPT="scripts/version_bump.sh" 7 | chmod +x $SCRIPT & bash $SCRIPT 8 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds a for all provided . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # build.sh [ default:iOS macOS tvOS watchOS xrOS] 10 | # e.g. `bash scripts/build.sh MyTarget iOS macOS` 11 | 12 | # Exit immediately if a command exits with a non-zero status 13 | set -e 14 | 15 | # Verify that all required arguments are provided 16 | if [ $# -eq 0 ]; then 17 | echo "Error: This script requires at least one argument" 18 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 19 | echo "For instance: $0 MyTarget iOS macOS" 20 | exit 1 21 | fi 22 | 23 | # Define argument variables 24 | TARGET=$1 25 | 26 | # Remove TARGET from arguments list 27 | shift 28 | 29 | # Define platforms variable 30 | if [ $# -eq 0 ]; then 31 | set -- iOS macOS tvOS watchOS xrOS 32 | fi 33 | PLATFORMS=$@ 34 | 35 | # A function that builds $TARGET for a specific platform 36 | build_platform() { 37 | 38 | # Define a local $PLATFORM variable 39 | local PLATFORM=$1 40 | 41 | # Build $TARGET for the $PLATFORM 42 | echo "Building $TARGET for $PLATFORM..." 43 | if ! xcodebuild -scheme $TARGET -derivedDataPath .build -destination generic/platform=$PLATFORM; then 44 | echo "Failed to build $TARGET for $PLATFORM" 45 | return 1 46 | fi 47 | 48 | # Complete successfully 49 | echo "Successfully built $TARGET for $PLATFORM" 50 | } 51 | 52 | # Start script 53 | echo "" 54 | echo "Building $TARGET for [$PLATFORMS]..." 55 | echo "" 56 | 57 | # Loop through all platforms and call the build function 58 | for PLATFORM in $PLATFORMS; do 59 | if ! build_platform "$PLATFORM"; then 60 | exit 1 61 | fi 62 | done 63 | 64 | # Complete successfully 65 | echo "" 66 | echo "Building $TARGET completed successfully!" 67 | echo "" 68 | -------------------------------------------------------------------------------- /scripts/chmod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script makes all scripts in this folder executable. 5 | 6 | # Usage: 7 | # scripts_chmod.sh 8 | # e.g. `bash scripts/chmod.sh` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Use the script folder to refer to other scripts. 14 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 15 | 16 | # Find all .sh files in the FOLDER except chmod.sh 17 | find "$FOLDER" -name "*.sh" ! -name "chmod.sh" -type f | while read -r script; do 18 | chmod +x "$script" 19 | done 20 | -------------------------------------------------------------------------------- /scripts/docc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC for a and certain . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | # The documentation ends up in to .build/docs-. 8 | 9 | # Usage: 10 | # docc.sh [ default:iOS macOS tvOS watchOS xrOS] 11 | # e.g. `bash scripts/docc.sh MyTarget iOS macOS` 12 | 13 | # Exit immediately if a command exits with a non-zero status 14 | set -e 15 | 16 | # Fail if any command in a pipeline fails 17 | set -o pipefail 18 | 19 | # Verify that all required arguments are provided 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires at least one argument" 22 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 23 | echo "For instance: $0 MyTarget iOS macOS" 24 | exit 1 25 | fi 26 | 27 | # Define argument variables 28 | TARGET=$1 29 | TARGET_LOWERCASED=$(echo "$1" | tr '[:upper:]' '[:lower:]') 30 | 31 | # Remove TARGET from arguments list 32 | shift 33 | 34 | # Define platforms variable 35 | if [ $# -eq 0 ]; then 36 | set -- iOS macOS tvOS watchOS xrOS 37 | fi 38 | PLATFORMS=$@ 39 | 40 | # Prepare the package for DocC 41 | swift package resolve; 42 | 43 | # A function that builds $TARGET for a specific platform 44 | build_platform() { 45 | 46 | # Define a local $PLATFORM variable and set an exit code 47 | local PLATFORM=$1 48 | local EXIT_CODE=0 49 | 50 | # Define the build folder name, based on the $PLATFORM 51 | case $PLATFORM in 52 | "iOS") 53 | DEBUG_PATH="Debug-iphoneos" 54 | ;; 55 | "macOS") 56 | DEBUG_PATH="Debug" 57 | ;; 58 | "tvOS") 59 | DEBUG_PATH="Debug-appletvos" 60 | ;; 61 | "watchOS") 62 | DEBUG_PATH="Debug-watchos" 63 | ;; 64 | "xrOS") 65 | DEBUG_PATH="Debug-xros" 66 | ;; 67 | *) 68 | echo "Error: Unsupported platform '$PLATFORM'" 69 | exit 1 70 | ;; 71 | esac 72 | 73 | # Build $TARGET docs for the $PLATFORM 74 | echo "Building $TARGET docs for $PLATFORM..." 75 | if ! xcodebuild docbuild -scheme $TARGET -derivedDataPath .build/docbuild -destination "generic/platform=$PLATFORM"; then 76 | echo "Error: Failed to build documentation for $PLATFORM" >&2 77 | return 1 78 | fi 79 | 80 | # Transform docs for static hosting 81 | if ! $(xcrun --find docc) process-archive \ 82 | transform-for-static-hosting .build/docbuild/Build/Products/$DEBUG_PATH/$TARGET.doccarchive \ 83 | --output-path .build/docs-$PLATFORM \ 84 | --hosting-base-path "$TARGET"; then 85 | echo "Error: Failed to transform documentation for $PLATFORM" >&2 86 | return 1 87 | fi 88 | 89 | # Inject a root redirect script on the root page 90 | echo "" > .build/docs-$PLATFORM/index.html; 91 | 92 | # Complete successfully 93 | echo "Successfully built $TARGET docs for $PLATFORM" 94 | return 0 95 | } 96 | 97 | # Start script 98 | echo "" 99 | echo "Building $TARGET docs for [$PLATFORMS]..." 100 | echo "" 101 | 102 | # Loop through all platforms and call the build function 103 | for PLATFORM in $PLATFORMS; do 104 | if ! build_platform "$PLATFORM"; then 105 | exit 1 106 | fi 107 | done 108 | 109 | # Complete successfully 110 | echo "" 111 | echo "Building $TARGET docs completed successfully!" 112 | echo "" 113 | -------------------------------------------------------------------------------- /scripts/framework.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC for a and certain . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Important: 9 | # This script doesn't work on packages, only on .xcproj projects that generate a framework. 10 | 11 | # Usage: 12 | # framework.sh [ default:iOS macOS tvOS watchOS xrOS] 13 | # e.g. `bash scripts/framework.sh MyTarget iOS macOS` 14 | 15 | # Exit immediately if a command exits with a non-zero status 16 | set -e 17 | 18 | # Verify that all required arguments are provided 19 | if [ $# -eq 0 ]; then 20 | echo "Error: This script requires exactly one argument" 21 | echo "Usage: $0 " 22 | exit 1 23 | fi 24 | 25 | # Define argument variables 26 | TARGET=$1 27 | 28 | # Remove TARGET from arguments list 29 | shift 30 | 31 | # Define platforms variable 32 | if [ $# -eq 0 ]; then 33 | set -- iOS macOS tvOS watchOS xrOS 34 | fi 35 | PLATFORMS=$@ 36 | 37 | # Define local variables 38 | BUILD_FOLDER=.build 39 | BUILD_FOLDER_ARCHIVES=.build/framework_archives 40 | BUILD_FILE=$BUILD_FOLDER/$TARGET.xcframework 41 | BUILD_ZIP=$BUILD_FOLDER/$TARGET.zip 42 | 43 | # Start script 44 | echo "" 45 | echo "Building $TARGET XCFramework for [$PLATFORMS]..." 46 | echo "" 47 | 48 | # Delete old builds 49 | echo "Cleaning old builds..." 50 | rm -rf $BUILD_ZIP 51 | rm -rf $BUILD_FILE 52 | rm -rf $BUILD_FOLDER_ARCHIVES 53 | 54 | 55 | # Generate XCArchive files for all platforms 56 | echo "Generating XCArchives..." 57 | 58 | # Initialize the xcframework command 59 | XCFRAMEWORK_CMD="xcodebuild -create-xcframework" 60 | 61 | # Build iOS archives and append to the xcframework command 62 | if [[ " ${PLATFORMS[@]} " =~ " iOS " ]]; then 63 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=iOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-iOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 64 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=iOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-iOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 65 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-iOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 66 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-iOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 67 | fi 68 | 69 | # Build iOS archive and append to the xcframework command 70 | if [[ " ${PLATFORMS[@]} " =~ " macOS " ]]; then 71 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=macOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-macOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 72 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-macOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 73 | fi 74 | 75 | # Build tvOS archives and append to the xcframework command 76 | if [[ " ${PLATFORMS[@]} " =~ " tvOS " ]]; then 77 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=tvOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 78 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=tvOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 79 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 80 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-tvOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 81 | fi 82 | 83 | # Build watchOS archives and append to the xcframework command 84 | if [[ " ${PLATFORMS[@]} " =~ " watchOS " ]]; then 85 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=watchOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 86 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=watchOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 87 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 88 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-watchOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 89 | fi 90 | 91 | # Build xrOS archives and append to the xcframework command 92 | if [[ " ${PLATFORMS[@]} " =~ " xrOS " ]]; then 93 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=xrOS" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 94 | xcodebuild archive -scheme $TARGET -configuration Release -destination "generic/platform=xrOS Simulator" -archivePath $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS-Sim SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES 95 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS.xcarchive/Products/Library/Frameworks/$TARGET.framework" 96 | XCFRAMEWORK_CMD+=" -framework $BUILD_FOLDER_ARCHIVES/$TARGET-xrOS-Sim.xcarchive/Products/Library/Frameworks/$TARGET.framework" 97 | fi 98 | 99 | # Genererate XCFramework 100 | echo "Generating XCFramework..." 101 | XCFRAMEWORK_CMD+=" -output $BUILD_FILE" 102 | eval "$XCFRAMEWORK_CMD" 103 | 104 | # Genererate iOS XCFramework zip 105 | echo "Generating XCFramework zip..." 106 | zip -r $BUILD_ZIP $BUILD_FILE 107 | echo "" 108 | echo "***** CHECKSUM *****" 109 | swift package compute-checksum $BUILD_ZIP 110 | echo "********************" 111 | echo "" 112 | 113 | # Complete successfully 114 | echo "" 115 | echo "$TARGET XCFramework created successfully!" 116 | echo "" 117 | -------------------------------------------------------------------------------- /scripts/git_default_branch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script echos the default git branch name. 5 | 6 | # Usage: 7 | # git_default_branch.sh 8 | # e.g. `bash scripts/git_default_branch.sh` 9 | 10 | BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@') 11 | echo $BRANCH 12 | -------------------------------------------------------------------------------- /scripts/package_docc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script builds DocC documentation for `Package.swift`. 5 | # This script targets iOS by default, but you can pass in custom . 6 | 7 | # Usage: 8 | # package_docc.sh [ default:iOS] 9 | # e.g. `bash scripts/package_docc.sh iOS macOS` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 17 | SCRIPT_DOCC="$FOLDER/docc.sh" 18 | 19 | # Define platforms variable 20 | if [ $# -eq 0 ]; then 21 | set -- iOS 22 | fi 23 | PLATFORMS=$@ 24 | 25 | # Get package name 26 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 27 | 28 | # Build package documentation 29 | bash $SCRIPT_DOCC $PACKAGE_NAME $PLATFORMS || { echo "DocC script failed"; exit 1; } 30 | -------------------------------------------------------------------------------- /scripts/package_framework.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script generates an XCFramework for `Package.swift`. 5 | # This script targets iOS by default, but you can pass in custom . 6 | 7 | # Usage: 8 | # package_framework.sh [ default:iOS] 9 | # e.g. `bash scripts/package_framework.sh iOS macOS` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 17 | SCRIPT_FRAMEWORK="$FOLDER/framework.sh" 18 | 19 | # Define platforms variable 20 | if [ $# -eq 0 ]; then 21 | set -- iOS 22 | fi 23 | PLATFORMS=$@ 24 | 25 | # Get package name 26 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 27 | 28 | # Build package framework 29 | bash $SCRIPT_FRAMEWORK $PACKAGE_NAME $PLATFORMS 30 | -------------------------------------------------------------------------------- /scripts/package_name.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script finds the main target name in `Package.swift`. 5 | 6 | # Usage: 7 | # package_name.sh 8 | # e.g. `bash scripts/package_name.sh` 9 | 10 | # Exit immediately if a command exits with non-zero status 11 | set -e 12 | 13 | # Check that a Package.swift file exists 14 | if [ ! -f "Package.swift" ]; then 15 | echo "Error: Package.swift not found in current directory" 16 | exit 1 17 | fi 18 | 19 | # Using grep and sed to extract the package name 20 | # 1. grep finds the line containing "name:" 21 | # 2. sed extracts the text between quotes 22 | package_name=$(grep -m 1 'name:.*"' Package.swift | sed -n 's/.*name:[[:space:]]*"\([^"]*\)".*/\1/p') 23 | 24 | if [ -z "$package_name" ]; then 25 | echo "Error: Could not find package name in Package.swift" 26 | exit 1 27 | else 28 | echo "$package_name" 29 | fi 30 | -------------------------------------------------------------------------------- /scripts/package_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new version for `Package.swift`. 5 | # You can pass in a to validate any non-main branch. 6 | 7 | # Usage: 8 | # package_version.sh 9 | # e.g. `bash scripts/package_version.sh master` 10 | 11 | # Exit immediately if a command exits with non-zero status 12 | set -e 13 | 14 | # Use the script folder to refer to other scripts. 15 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 16 | SCRIPT_BRANCH_NAME="$FOLDER/git_default_branch.sh" 17 | SCRIPT_PACKAGE_NAME="$FOLDER/package_name.sh" 18 | SCRIPT_VERSION="$FOLDER/version.sh" 19 | 20 | # Get branch name 21 | DEFAULT_BRANCH=$("$SCRIPT_BRANCH_NAME") || { echo "Failed to get branch name"; exit 1; } 22 | BRANCH_NAME=${1:-$DEFAULT_BRANCH} 23 | 24 | # Get package name 25 | PACKAGE_NAME=$("$SCRIPT_PACKAGE_NAME") || { echo "Failed to get package name"; exit 1; } 26 | 27 | # Build package version 28 | bash $SCRIPT_VERSION $PACKAGE_NAME $BRANCH_NAME 29 | -------------------------------------------------------------------------------- /scripts/sync_from.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script syncs Swift Package Scripts from a . 5 | # This script will overwrite the existing "scripts" folder. 6 | # Only pass in the full path to a Swift Package Scripts root. 7 | 8 | # Usage: 9 | # package_name.sh 10 | # e.g. `bash sync_from.sh ../SwiftPackageScripts` 11 | 12 | # Define argument variables 13 | SOURCE=$1 14 | 15 | # Define variables 16 | FOLDER="scripts/" 17 | SOURCE_FOLDER="$SOURCE/$FOLDER" 18 | 19 | # Start script 20 | echo "" 21 | echo "Syncing scripts from $SOURCE_FOLDER..." 22 | echo "" 23 | 24 | # Remove existing folder 25 | rm -rf $FOLDER 26 | 27 | # Copy folder 28 | cp -r "$SOURCE_FOLDER/" "$FOLDER/" 29 | 30 | # Complete successfully 31 | echo "" 32 | echo "Script syncing from $SOURCE_FOLDER completed successfully!" 33 | echo "" 34 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script tests a for all provided . 5 | 6 | # Usage: 7 | # test.sh [ default:iOS macOS tvOS watchOS xrOS] 8 | # e.g. `bash scripts/test.sh MyTarget iOS macOS` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Verify that all required arguments are provided 14 | if [ $# -eq 0 ]; then 15 | echo "Error: This script requires at least one argument" 16 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 17 | echo "For instance: $0 MyTarget iOS macOS" 18 | exit 1 19 | fi 20 | 21 | # Define argument variables 22 | TARGET=$1 23 | 24 | # Remove TARGET from arguments list 25 | shift 26 | 27 | # Define platforms variable 28 | if [ $# -eq 0 ]; then 29 | set -- iOS macOS tvOS watchOS xrOS 30 | fi 31 | PLATFORMS=$@ 32 | 33 | # Start script 34 | echo "" 35 | echo "Testing $TARGET for [$PLATFORMS]..." 36 | echo "" 37 | 38 | # A function that gets the latest simulator for a certain OS. 39 | get_latest_simulator() { 40 | local PLATFORM=$1 41 | local SIMULATOR_TYPE 42 | 43 | case $PLATFORM in 44 | "iOS") 45 | SIMULATOR_TYPE="iPhone" 46 | ;; 47 | "tvOS") 48 | SIMULATOR_TYPE="Apple TV" 49 | ;; 50 | "watchOS") 51 | SIMULATOR_TYPE="Apple Watch" 52 | ;; 53 | "xrOS") 54 | SIMULATOR_TYPE="Apple Vision" 55 | ;; 56 | *) 57 | echo "Error: Unsupported platform for simulator '$PLATFORM'" 58 | return 1 59 | ;; 60 | esac 61 | 62 | # Get the latest simulator for the platform 63 | xcrun simctl list devices available | grep "$SIMULATOR_TYPE" | tail -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/' 64 | } 65 | 66 | # A function that tests $TARGET for a specific platform 67 | test_platform() { 68 | 69 | # Define a local $PLATFORM variable 70 | local PLATFORM="${1//_/ }" 71 | 72 | # Define the destination, based on the $PLATFORM 73 | case $PLATFORM in 74 | "iOS"|"tvOS"|"watchOS"|"xrOS") 75 | local SIMULATOR_UDID=$(get_latest_simulator "$PLATFORM") 76 | if [ -z "$SIMULATOR_UDID" ]; then 77 | echo "Error: No simulator found for $PLATFORM" 78 | return 1 79 | fi 80 | DESTINATION="id=$SIMULATOR_UDID" 81 | ;; 82 | "macOS") 83 | DESTINATION="platform=macOS" 84 | ;; 85 | *) 86 | echo "Error: Unsupported platform '$PLATFORM'" 87 | return 1 88 | ;; 89 | esac 90 | 91 | # Test $TARGET for the $DESTINATION 92 | echo "Testing $TARGET for $PLATFORM..." 93 | xcodebuild test -scheme $TARGET -derivedDataPath .build -destination "$DESTINATION" -enableCodeCoverage YES 94 | local TEST_RESULT=$? 95 | 96 | if [[ $TEST_RESULT -ne 0 ]]; then 97 | return $TEST_RESULT 98 | fi 99 | 100 | # Complete successfully 101 | echo "Successfully tested $TARGET for $PLATFORM" 102 | return 0 103 | } 104 | 105 | # Loop through all platforms and call the test function 106 | for PLATFORM in $PLATFORMS; do 107 | if ! test_platform "$PLATFORM"; then 108 | exit 1 109 | fi 110 | done 111 | 112 | # Complete successfully 113 | echo "" 114 | echo "Testing $TARGET completed successfully!" 115 | echo "" 116 | -------------------------------------------------------------------------------- /scripts/version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script creates a new version for the provided and . 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # version.sh [ default:iOS macOS tvOS watchOS xrOS]" 10 | # e.g. `scripts/version.sh MyTarget master iOS macOS` 11 | 12 | # This script will: 13 | # * Call version_validate_git.sh to validate the git repo. 14 | # * Call version_validate_target to run tests, swiftlint, etc. 15 | # * Call version_bump.sh if all validation steps above passed. 16 | 17 | # Exit immediately if a command exits with a non-zero status 18 | set -e 19 | 20 | # Verify that all required arguments are provided 21 | if [ $# -lt 2 ]; then 22 | echo "Error: This script requires at least two arguments" 23 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 24 | echo "For instance: $0 MyTarget master iOS macOS" 25 | exit 1 26 | fi 27 | 28 | # Define argument variables 29 | TARGET=$1 30 | BRANCH=${2:-main} 31 | 32 | # Remove TARGET and BRANCH from arguments list 33 | shift 34 | shift 35 | 36 | # Read platform arguments or use default value 37 | if [ $# -eq 0 ]; then 38 | set -- iOS macOS tvOS watchOS xrOS 39 | fi 40 | 41 | # Use the script folder to refer to other scripts. 42 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 43 | SCRIPT_VALIDATE_GIT="$FOLDER/version_validate_git.sh" 44 | SCRIPT_VALIDATE_TARGET="$FOLDER/version_validate_target.sh" 45 | SCRIPT_VERSION_BUMP="$FOLDER/version_bump.sh" 46 | 47 | # A function that run a certain script and checks for errors 48 | run_script() { 49 | local script="$1" 50 | shift # Remove the first argument (the script path) 51 | 52 | if [ ! -f "$script" ]; then 53 | echo "Error: Script not found: $script" 54 | exit 1 55 | fi 56 | 57 | chmod +x "$script" 58 | if ! "$script" "$@"; then 59 | echo "Error: Script $script failed" 60 | exit 1 61 | fi 62 | } 63 | 64 | # Start script 65 | echo "" 66 | echo "Creating a new version for $TARGET on the $BRANCH branch..." 67 | echo "" 68 | 69 | # Validate git and project 70 | echo "Validating..." 71 | run_script "$SCRIPT_VALIDATE_GIT" "$BRANCH" 72 | run_script "$SCRIPT_VALIDATE_TARGET" "$TARGET" 73 | 74 | # Bump version 75 | echo "Bumping version..." 76 | run_script "$SCRIPT_VERSION_BUMP" 77 | 78 | # Complete successfully 79 | echo "" 80 | echo "Version created successfully!" 81 | echo "" 82 | -------------------------------------------------------------------------------- /scripts/version_bump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script bumps the project version number. 5 | # You can append --no-semver to disable semantic version validation. 6 | 7 | # Usage: 8 | # version_bump.sh [--no-semver] 9 | # e.g. `bash scripts/version_bump.sh` 10 | # e.g. `bash scripts/version_bump.sh --no-semver` 11 | 12 | # Exit immediately if a command exits with a non-zero status 13 | set -e 14 | 15 | # Use the script folder to refer to other scripts. 16 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 17 | SCRIPT_VERSION_NUMBER="$FOLDER/version_number.sh" 18 | 19 | 20 | # Parse --no-semver argument 21 | VALIDATE_SEMVER=true 22 | for arg in "$@"; do 23 | case $arg in 24 | --no-semver) 25 | VALIDATE_SEMVER=false 26 | shift # Remove --no-semver from processing 27 | ;; 28 | esac 29 | done 30 | 31 | # Start script 32 | echo "" 33 | echo "Bumping version number..." 34 | echo "" 35 | 36 | # Get the latest version 37 | VERSION=$($SCRIPT_VERSION_NUMBER) 38 | if [ $? -ne 0 ]; then 39 | echo "Failed to get the latest version" 40 | exit 1 41 | fi 42 | 43 | # Print the current version 44 | echo "The current version is: $VERSION" 45 | 46 | # Function to validate semver format, including optional -rc. suffix 47 | validate_semver() { 48 | if [ "$VALIDATE_SEMVER" = false ]; then 49 | return 0 50 | fi 51 | 52 | if [[ $1 =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then 53 | return 0 54 | else 55 | return 1 56 | fi 57 | } 58 | 59 | # Prompt user for new version 60 | while true; do 61 | read -p "Enter the new version number: " NEW_VERSION 62 | 63 | # Validate the version number to ensure that it's a semver version 64 | if validate_semver "$NEW_VERSION"; then 65 | break 66 | else 67 | echo "Invalid version format. Please use semver format (e.g., 1.2.3, v1.2.3, 1.2.3-rc.1, etc.)." 68 | exit 1 69 | fi 70 | done 71 | 72 | # Push the new tag 73 | git push -u origin HEAD 74 | git tag $NEW_VERSION 75 | git push --tags 76 | 77 | # Complete successfully 78 | echo "" 79 | echo "Version tag pushed successfully!" 80 | echo "" 81 | -------------------------------------------------------------------------------- /scripts/version_number.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script returns the latest project version. 5 | 6 | # Usage: 7 | # version_number.sh 8 | # e.g. `bash scripts/version_number.sh` 9 | 10 | # Exit immediately if a command exits with a non-zero status 11 | set -e 12 | 13 | # Check if the current directory is a Git repository 14 | if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then 15 | echo "Error: Not a Git repository" 16 | exit 1 17 | fi 18 | 19 | # Fetch all tags 20 | git fetch --tags > /dev/null 2>&1 21 | 22 | # Get the latest semver tag 23 | latest_version=$(git tag -l --sort=-v:refname | grep -E '^v?[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) 24 | 25 | # Check if we found a version tag 26 | if [ -z "$latest_version" ]; then 27 | echo "Error: No semver tags found in this repository" >&2 28 | exit 1 29 | fi 30 | 31 | # Print the latest version 32 | echo "$latest_version" 33 | -------------------------------------------------------------------------------- /scripts/version_validate_git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script validates the Git repository for release. 5 | # You can pass in a to validate any non-main branch. 6 | 7 | # Usage: 8 | # version_validate_git.sh " 9 | # e.g. `bash scripts/version_validate_git.sh master` 10 | 11 | # This script will: 12 | # * Validate that the script is run within a git repository. 13 | # * Validate that the git repository doesn't have any uncommitted changes. 14 | # * Validate that the current git branch matches the provided one. 15 | 16 | # Exit immediately if a command exits with a non-zero status 17 | set -e 18 | 19 | # Verify that all required arguments are provided 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires exactly one argument" 22 | echo "Usage: $0 " 23 | exit 1 24 | fi 25 | 26 | # Create local argument variables. 27 | BRANCH=$1 28 | 29 | # Start script 30 | echo "" 31 | echo "Validating git repository..." 32 | echo "" 33 | 34 | # Check if the current directory is a Git repository 35 | if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then 36 | echo "Error: Not a Git repository" 37 | exit 1 38 | fi 39 | 40 | # Check for uncommitted changes 41 | if [ -n "$(git status --porcelain)" ]; then 42 | echo "Error: Git repository is dirty. There are uncommitted changes." 43 | exit 1 44 | fi 45 | 46 | # Verify that we're on the correct branch 47 | current_branch=$(git rev-parse --abbrev-ref HEAD) 48 | if [ "$current_branch" != "$BRANCH" ]; then 49 | echo "Error: Not on the specified branch. Current branch is $current_branch, expected $1." 50 | exit 1 51 | fi 52 | 53 | # The Git repository validation succeeded. 54 | echo "" 55 | echo "Git repository validated successfully!" 56 | echo "" 57 | -------------------------------------------------------------------------------- /scripts/version_validate_target.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Documentation: 4 | # This script validates a for release. 5 | # This script targets iOS, macOS, tvOS, watchOS, and xrOS by default. 6 | # You can pass in a list of if you want to customize the build. 7 | 8 | # Usage: 9 | # version_validate_target.sh [ default:iOS macOS tvOS watchOS xrOS]" 10 | # e.g. `bash scripts/version_validate_target.sh iOS macOS` 11 | 12 | # This script will: 13 | # * Validate that swiftlint passes. 14 | # * Validate that all unit tests passes for all . 15 | 16 | # Exit immediately if a command exits with a non-zero status 17 | set -e 18 | 19 | # Verify that all requires at least one argument" 20 | if [ $# -eq 0 ]; then 21 | echo "Error: This script requires at least one argument" 22 | echo "Usage: $0 [ default:iOS macOS tvOS watchOS xrOS]" 23 | exit 1 24 | fi 25 | 26 | # Create local argument variables. 27 | TARGET=$1 28 | 29 | # Remove TARGET from arguments list 30 | shift 31 | 32 | # Define platforms variable 33 | if [ $# -eq 0 ]; then 34 | set -- iOS macOS tvOS watchOS xrOS 35 | fi 36 | PLATFORMS=$@ 37 | 38 | # Use the script folder to refer to other scripts. 39 | FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" 40 | SCRIPT_TEST="$FOLDER/test.sh" 41 | 42 | # A function that run a certain script and checks for errors 43 | run_script() { 44 | local script="$1" 45 | shift # Remove the first argument (script path) from the argument list 46 | 47 | if [ ! -f "$script" ]; then 48 | echo "Error: Script not found: $script" 49 | exit 1 50 | fi 51 | 52 | chmod +x "$script" 53 | if ! "$script" "$@"; then 54 | echo "Error: Script $script failed" 55 | exit 1 56 | fi 57 | } 58 | 59 | # Start script 60 | echo "" 61 | echo "Validating project..." 62 | echo "" 63 | 64 | # Run SwiftLint 65 | echo "Running SwiftLint" 66 | if ! swiftlint --strict; then 67 | echo "Error: SwiftLint failed" 68 | exit 1 69 | fi 70 | 71 | # Run unit tests 72 | echo "Testing..." 73 | run_script "$SCRIPT_TEST" "$TARGET" "$PLATFORMS" 74 | 75 | # Complete successfully 76 | echo "" 77 | echo "Project successfully validated!" 78 | echo "" 79 | --------------------------------------------------------------------------------