Skip to content

Commit 157fbe2

Browse files
leogdionclaude
andcommitted
Fix #145: Code migration and environment configuration updates
Implements all 6 child issues grouped under #145: - #76: Add triggerTrackingPermissionsRequest environment property - #106: Migrate SigVerifier.findVerification extension from Bushel - #110: Add NSError extensions for file error checking - #127: Migrate ConsoleOutput with stderr routing from BushelCloud - #131: Enable AllPublicDeclarationsHaveDocumentation rule - #132: Fix VS Code launch.json with standard LLDB fields All new code includes proper documentation and tests pass. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 5907061 commit 157fbe2

7 files changed

Lines changed: 106 additions & 27 deletions

File tree

.swift-format

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"reflowMultilineStringLiterals" : "never",
2525
"respectsExistingLineBreaks" : true,
2626
"rules" : {
27-
"AllPublicDeclarationsHaveDocumentation" : false,
27+
"AllPublicDeclarationsHaveDocumentation" : true,
2828
"AlwaysUseLiteralForEmptyCollectionInit" : false,
2929
"AlwaysUseLowerCamelCase" : true,
3030
"AmbiguousTrailingClosureOverload" : true,

.vscode/launch.json

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@
33
{
44
"type": "swift",
55
"request": "launch",
6-
"args": [],
7-
"cwd": "${workspaceFolder:BushelKit}",
86
"name": "Debug bushel",
7+
"program": "${workspaceFolder}/.build/debug/bushel",
8+
"args": [],
9+
"cwd": "${workspaceFolder}",
910
"preLaunchTask": "swift: Build Debug bushel",
10-
"target": "bushel",
11-
"configuration": "debug"
11+
"stopOnEntry": false,
12+
"terminal": "console"
1213
},
1314
{
1415
"type": "swift",
1516
"request": "launch",
16-
"args": [],
17-
"cwd": "${workspaceFolder:BushelKit}",
1817
"name": "Release bushel",
18+
"program": "${workspaceFolder}/.build/release/bushel",
19+
"args": [],
20+
"cwd": "${workspaceFolder}",
1921
"preLaunchTask": "swift: Build Release bushel",
20-
"target": "bushel",
21-
"configuration": "release"
22+
"stopOnEntry": false,
23+
"terminal": "console"
2224
}
2325
]
2426
}

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4346,7 +4346,7 @@ struct FelinePine: PackageDependency, TargetDependency {
43464346

43474347
struct RadiantKit: PackageDependency, TargetDependency {
43484348
var dependency: Package.Dependency {
4349-
.package(url: "https://github.com/brightdigit/RadiantKit.git", from: "1.0.0-beta.4")
4349+
.package(url: "https://github.com/brightdigit/RadiantKit.git", from: "1.0.0-beta.5")
43504350
}
43514351
}
43524352
//

Sources/BushelFoundation/EnvironmentConfiguration.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,14 @@ public struct EnvironmentConfiguration: CustomReflectable, Sendable {
4444
/// Resets the application.
4545
case resetApplication = "RESET_APPLICATION"
4646

47+
/// Indicates whether this is a release version.
4748
case releaseVersion = "RELEASE_VERSION"
4849

50+
/// The threshold of user engagement to trigger a review request.
4951
case reviewEngagementThreshold = "REVIEW_ENGAGEMENT_THRESHOLD"
52+
53+
/// Indicates whether to trigger tracking permissions request.
54+
case triggerTrackingPermissionsRequest = "TRIGGER_TRACKING_PERMISSIONS_REQUEST"
5055
}
5156

5257
/// The shared instance of `EnvironmentConfiguration`.
@@ -76,6 +81,10 @@ public struct EnvironmentConfiguration: CustomReflectable, Sendable {
7681
@EnvironmentProperty(Key.reviewEngagementThreshold)
7782
public var reviewEngagementThreshold: Int
7883

84+
/// Indicates whether to trigger tracking permissions request.
85+
@EnvironmentProperty(Key.triggerTrackingPermissionsRequest)
86+
public var triggerTrackingPermissionsRequest: Bool
87+
7988
/// Provides a custom mirror for the `EnvironmentConfiguration` instance.
8089
public var customMirror: Mirror {
8190
Mirror(
@@ -87,6 +96,7 @@ public struct EnvironmentConfiguration: CustomReflectable, Sendable {
8796
"resetApplication": self.resetApplication,
8897
"releaseVersion": self.releaseVersion,
8998
"reviewEngagementThreshold": self.reviewEngagementThreshold,
99+
"triggerTrackingPermissionsRequest": self.triggerTrackingPermissionsRequest,
90100
]
91101
)
92102
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// SigVerifier+FindVerification.swift
3+
// BushelKit
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2025 BrightDigit.
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the "Software"), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or
13+
// sell copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
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+
30+
extension SigVerifier {
31+
/// Attempts to find verification for a signature source, returning nil if not found.
32+
///
33+
/// This is a convenience wrapper around `isSignatureSigned(from:)` that treats
34+
/// the `.notFound` error case as a `nil` result instead of throwing.
35+
///
36+
/// - Parameter source: The source of the signature to be verified.
37+
/// - Returns: The signature verification if found, or `nil` if not found.
38+
/// - Throws: A `SigVerificationError` if verification fails for reasons other than not found.
39+
public func findVerification(for source: SignatureSource) async throws(SigVerificationError)
40+
-> SigVerification?
41+
{
42+
do {
43+
return try await self.isSignatureSigned(from: source)
44+
} catch SigVerificationError.notFound {
45+
return nil
46+
}
47+
}
48+
}

Sources/BushelUtilities/ConsoleOutput.swift

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,45 +34,47 @@ import Foundation
3434
/// Provides user-facing console output with verbose mode support.
3535
/// This is separate from logging, which is used for debugging and monitoring.
3636
///
37-
/// ## Thread Safety
38-
/// The `isVerbose` flag uses `nonisolated(unsafe)` because:
39-
/// - It's set exactly once at application startup before any concurrent access
40-
/// - It's only read thereafter (no writes during concurrent execution)
41-
/// - This pattern is safe for CLI tools with single initialization phase
42-
///
43-
/// **Important**: Do not modify `isVerbose` after concurrent operations begin.
37+
/// **Important**: All output goes to stderr to keep stdout clean for structured output (JSON, etc.)
4438
public enum ConsoleOutput {
4539
/// Global verbose mode flag
4640
///
47-
/// Set this flag during application initialization, before any async operations.
48-
/// Once set, it should only be read, never written.
41+
/// Note: This is marked with `nonisolated(unsafe)` because it's set once at startup
42+
/// before any concurrent access and then only read. This pattern is safe for CLI tools.
4943
nonisolated(unsafe) public static var isVerbose = false
5044

45+
/// Print to stderr (keeping stdout clean for structured output)
46+
///
47+
/// This is a drop-in replacement for Swift's `print()` that writes to stderr instead of stdout.
48+
/// Use this throughout the codebase to ensure JSON output on stdout remains clean.
49+
public static func print(_ message: String) {
50+
if let data = (message + "\n").data(using: .utf8) {
51+
FileHandle.standardError.write(data)
52+
}
53+
}
54+
5155
/// Print verbose message only when verbose mode is enabled
5256
public static func verbose(_ message: String) {
53-
guard isVerbose else {
54-
return
55-
}
56-
print(" \(message)")
57+
guard isVerbose else { return }
58+
ConsoleOutput.print(" \(message)")
5759
}
5860

5961
/// Print standard informational message
6062
public static func info(_ message: String) {
61-
print(message)
63+
ConsoleOutput.print(message)
6264
}
6365

6466
/// Print success message
6567
public static func success(_ message: String) {
66-
print("\(message)")
68+
ConsoleOutput.print("\(message)")
6769
}
6870

6971
/// Print warning message
7072
public static func warning(_ message: String) {
71-
print(" ⚠️ \(message)")
73+
ConsoleOutput.print(" ⚠️ \(message)")
7274
}
7375

7476
/// Print error message
7577
public static func error(_ message: String) {
76-
print("\(message)")
78+
ConsoleOutput.print("\(message)")
7779
}
7880
}

Sources/BushelUtilities/Extensions/FileManager+Errors.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,20 @@ extension Error where Self == NSError {
5959
)
6060
}
6161
}
62+
63+
extension NSError {
64+
/// Indicates whether the error is a file not found error.
65+
public var isFileNotFound: Bool {
66+
domain == NSCocoaErrorDomain && code == NSFileNoSuchFileError
67+
}
68+
69+
/// Indicates whether the error is a corrupt file error.
70+
public var isCorruptFile: Bool {
71+
domain == NSCocoaErrorDomain && code == NSFileReadCorruptFileError
72+
}
73+
74+
/// Indicates whether the error is a file not found or corrupt file error.
75+
public var isFileNotFoundOrCorrupt: Bool {
76+
isFileNotFound || isCorruptFile
77+
}
78+
}

0 commit comments

Comments
 (0)