├── .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 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
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 |
--------------------------------------------------------------------------------