diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3d9bfa..1d3d410 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,11 +23,15 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update + - name: Build - run: swift build --build-tests + run: swift build --build-tests --enable-code-coverage - name: Test - run: swift test --enable-code-coverage + timeout-minutes: 15 + run: swift test --skip-build --enable-code-coverage --skip LDAPIntegrationTests - name: Generate Coverage Report run: | @@ -39,19 +43,23 @@ jobs: build-and-test-macos: name: macOS (Swift 6.2) - runs-on: macos-15 + runs-on: macos-26 steps: - name: Checkout uses: actions/checkout@v4 - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.3.app || sudo xcode-select -s /Applications/Xcode_16.2.app || true + run: sudo xcode-select -s /Applications/Xcode_26.2.app || sudo xcode-select -s /Applications/Xcode_26.app || true + + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update - name: Build - run: swift build --build-tests + run: swift build --build-tests --enable-code-coverage - name: Test - run: swift test --enable-code-coverage + timeout-minutes: 30 + run: swift test --skip-build --enable-code-coverage --skip LDAPIntegrationTests lint: name: Swift Lint @@ -62,6 +70,9 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update + - name: Build (strict concurrency check) run: swift build 2>&1 | tee build-output.txt diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 76135a5..04cc54b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,19 +17,22 @@ permissions: security-events: write jobs: - analyze: + analyze-swift: name: Analyze (Swift) - runs-on: macos-15 + runs-on: macos-26 steps: - name: Checkout uses: actions/checkout@v4 - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.3.app || sudo xcode-select -s /Applications/Xcode_16.2.app || true + run: sudo xcode-select -s /Applications/Xcode_26.2.app || sudo xcode-select -s /Applications/Xcode_26.app || true + + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: swift build-mode: manual @@ -38,6 +41,25 @@ jobs: run: swift build - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:swift" + + analyze-actions: + name: Analyze (Actions) + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: actions + build-mode: none + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:actions" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b52a0de..3e3fe76 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,27 +34,35 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update + - name: Build - run: swift build --build-tests + run: swift build --build-tests --enable-code-coverage - name: Test - run: swift test --enable-code-coverage + timeout-minutes: 15 + run: swift test --skip-build --enable-code-coverage --skip LDAPIntegrationTests test-macos: name: Test (macOS) - runs-on: macos-15 + runs-on: macos-26 steps: - name: Checkout uses: actions/checkout@v4 - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.3.app || sudo xcode-select -s /Applications/Xcode_16.2.app || true + run: sudo xcode-select -s /Applications/Xcode_26.2.app || sudo xcode-select -s /Applications/Xcode_26.app || true + + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update - name: Build - run: swift build --build-tests + run: swift build --build-tests --enable-code-coverage - name: Test - run: swift test --enable-code-coverage + timeout-minutes: 30 + run: swift test --skip-build --enable-code-coverage --skip LDAPIntegrationTests # ------------------------------------------------------------------ # 2. Build release binaries @@ -72,6 +80,9 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update + - name: Build release binary run: swift build -c release --static-swift-stdlib @@ -92,13 +103,16 @@ jobs: build-macos: name: Build macOS (arm64) needs: test-macos - runs-on: macos-15 + runs-on: macos-26 steps: - name: Checkout uses: actions/checkout@v4 - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.3.app || sudo xcode-select -s /Applications/Xcode_16.2.app || true + run: sudo xcode-select -s /Applications/Xcode_26.2.app || sudo xcode-select -s /Applications/Xcode_26.app || true + + - name: Reset SPM cache + run: swift package purge-cache && swift package reset && swift package update - name: Build release binary run: swift build -c release diff --git a/Package.swift b/Package.swift index 92aa36f..8b4d651 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 6.2 // SPDX-License-Identifier: (see LICENSE) // Mayam — Swift Package Manager Manifest diff --git a/Sources/MayamCore/Storage/BackupManager.swift b/Sources/MayamCore/Storage/BackupManager.swift index afb648e..83d8087 100644 --- a/Sources/MayamCore/Storage/BackupManager.swift +++ b/Sources/MayamCore/Storage/BackupManager.swift @@ -156,6 +156,21 @@ public actor BackupManager { private func performLocalBackup( to destinationPath: String ) async throws -> (objectCount: Int, sizeBytes: Int64) { + try BackupManager.copyArchive( + from: archivePath, + to: destinationPath + ) + } + + /// Copies `.dcm` files from the archive to a timestamped backup directory. + /// + /// This is a `nonisolated` synchronous helper so that + /// `NSDirectoryEnumerator` iteration (which is unavailable from async + /// contexts in Swift 6.2) can be used directly. + private nonisolated static func copyArchive( + from archivePath: String, + to destinationPath: String + ) throws -> (objectCount: Int, sizeBytes: Int64) { let fm = FileManager.default // Validate destination diff --git a/Sources/MayamCore/Storage/IntegrityScanner.swift b/Sources/MayamCore/Storage/IntegrityScanner.swift index b0b7fa7..6c4f393 100644 --- a/Sources/MayamCore/Storage/IntegrityScanner.swift +++ b/Sources/MayamCore/Storage/IntegrityScanner.swift @@ -170,12 +170,9 @@ public actor IntegrityScanner { logger.info("Integrity scan: Starting full archive scan at '\(archivePath)'") - let fm = FileManager.default - guard let enumerator = fm.enumerator(atPath: archivePath) else { - throw IntegrityScanError.archiveNotAccessible(path: archivePath) - } + let dcmPaths = try IntegrityScanner.collectDCMPaths(at: archivePath) - for case let relativePath as String in enumerator where relativePath.hasSuffix(".dcm") { + for relativePath in dcmPaths { result.scannedCount += 1 let absolutePath = archivePath + "/" + relativePath @@ -187,6 +184,7 @@ public actor IntegrityScanner { } // Read file and compute checksum + let fm = FileManager.default guard let fileData = fm.contents(atPath: absolutePath) else { result.errorCount += 1 result.violations.append(IntegrityViolation( @@ -225,6 +223,23 @@ public actor IntegrityScanner { return result } + /// Collects `.dcm` file paths from the archive directory. + /// + /// This is a `nonisolated` synchronous helper so that + /// `NSDirectoryEnumerator` iteration (which is unavailable from async + /// contexts in Swift 6.2) can be used directly. + private nonisolated static func collectDCMPaths(at archivePath: String) throws -> [String] { + let fm = FileManager.default + guard let enumerator = fm.enumerator(atPath: archivePath) else { + throw IntegrityScanError.archiveNotAccessible(path: archivePath) + } + var paths: [String] = [] + for case let relativePath as String in enumerator where relativePath.hasSuffix(".dcm") { + paths.append(relativePath) + } + return paths + } + /// Returns the history of scan results. public func getScanHistory() -> [ScanResult] { scanHistory diff --git a/Sources/MayamWeb/Admin/Handlers/AdminStorageHandler.swift b/Sources/MayamWeb/Admin/Handlers/AdminStorageHandler.swift index 7d7fc17..68abb4c 100644 --- a/Sources/MayamWeb/Admin/Handlers/AdminStorageHandler.swift +++ b/Sources/MayamWeb/Admin/Handlers/AdminStorageHandler.swift @@ -64,15 +64,7 @@ public actor AdminStorageHandler { /// - Returns: An ``IntegrityCheckResult`` with the count of examined files. public func runIntegrityCheck(archivePath: String) async -> IntegrityCheckResult { let startedAt = Date() - let fileManager = FileManager.default - var checkedCount = 0 - - if let enumerator = fileManager.enumerator(atPath: archivePath) { - for case let filePath as String in enumerator - where filePath.hasSuffix(".dcm") { - checkedCount += 1 - } - } + let checkedCount = AdminStorageHandler.countDCMFiles(at: archivePath) return IntegrityCheckResult( startedAt: startedAt, @@ -83,6 +75,21 @@ public actor AdminStorageHandler { ) } + /// Counts `.dcm` files under the given path. + /// + /// This is a `nonisolated` synchronous helper so that + /// `NSDirectoryEnumerator` iteration (which is unavailable from async + /// contexts in Swift 6.2) can be used directly. + private nonisolated static func countDCMFiles(at archivePath: String) -> Int { + let fm = FileManager.default + guard let enumerator = fm.enumerator(atPath: archivePath) else { return 0 } + var count = 0 + for case let filePath as String in enumerator where filePath.hasSuffix(".dcm") { + count += 1 + } + return count + } + /// Returns the current HSM status including tier statistics. /// /// - Parameter hsmConfig: The HSM configuration.