From 415fd47df0ad211ee107f2607a76d60366e97486 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Thu, 13 Aug 2026 15:30:01 +0200 Subject: [PATCH 1/6] Modern package manifest --- Package.swift | 6 ++ Sources/ConsoleKit/Terminal/ANSI.swift | 4 +- Sources/ConsoleKit/Terminal/Terminal.swift | 24 ++++---- .../Terminal/readpassphrase_linux.swift | 60 +++++++++---------- .../LoggerFragments/LoggerFragment.swift | 14 ++--- 5 files changed, 57 insertions(+), 51 deletions(-) diff --git a/Package.swift b/Package.swift index 8d29cb5..c120dbb 100644 --- a/Package.swift +++ b/Package.swift @@ -66,6 +66,12 @@ let package = Package( var swiftSettings: [SwiftSetting] { [ + .treatAllWarnings(as: .error), + .strictMemorySafety(), + .enableExperimentalFeature("SuppressedAssociatedTypesWithDefaults"), + .enableExperimentalFeature("LifetimeDependence"), + .enableExperimentalFeature("Lifetimes"), + .enableUpcomingFeature("LifetimeDependence"), .enableUpcomingFeature("ExistentialAny"), .enableUpcomingFeature("InternalImportsByDefault"), .enableUpcomingFeature("MemberImportVisibility"), diff --git a/Sources/ConsoleKit/Terminal/ANSI.swift b/Sources/ConsoleKit/Terminal/ANSI.swift index 44f38b5..f150993 100644 --- a/Sources/ConsoleKit/Terminal/ANSI.swift +++ b/Sources/ConsoleKit/Terminal/ANSI.swift @@ -61,9 +61,9 @@ extension Terminal { // fdopen() on stdout is fast; also the returned file MUST NOT be fclose()d // This avoids concurrency complaints due to accessing global `stdout`. #if os(Windows) - fflush(_fdopen(_fileno(stdout), "w+")) + unsafe fflush(_fdopen(_fileno(stdout), "w+")) #else - fflush(fdopen(STDOUT_FILENO, "w+")) + unsafe fflush(fdopen(STDOUT_FILENO, "w+")) #endif } } diff --git a/Sources/ConsoleKit/Terminal/Terminal.swift b/Sources/ConsoleKit/Terminal/Terminal.swift index 0845d46..3454e94 100644 --- a/Sources/ConsoleKit/Terminal/Terminal.swift +++ b/Sources/ConsoleKit/Terminal/Terminal.swift @@ -66,26 +66,26 @@ public final class Terminal: Console, Sendable { // swift-format-ignore func plat_readpassphrase(into buf: UnsafeMutableBufferPointer) -> Int { #if canImport(Darwin) - let rpp = readpassphrase + let rpp = unsafe readpassphrase #else - let rpp = linux_readpassphrase + let rpp = unsafe linux_readpassphrase let RPP_REQUIRE_TTY = 0 as Int32 #endif - while rpp("", buf.baseAddress!, buf.count, RPP_REQUIRE_TTY) == nil { + while unsafe rpp("", buf.baseAddress!, buf.count, RPP_REQUIRE_TTY) == nil { guard errno == EINTR else { return 0 } } - return strlen(buf.baseAddress!) + return unsafe strlen(buf.baseAddress!) } // swift-format-ignore func readpassphrase_str() -> String { if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) { - return .init(unsafeUninitializedCapacity: 1024) { - $0.withMemoryRebound(to: Int8.self) { plat_readpassphrase(into: $0) } + return unsafe .init(unsafeUninitializedCapacity: 1024) { + unsafe $0.withMemoryRebound(to: Int8.self) { unsafe plat_readpassphrase(into: $0) } } } else { - return .init( - decoding: [Int8](unsafeUninitializedCapacity: 1024) { $1 = plat_readpassphrase(into: $0) }.map(UInt8.init), + return unsafe .init( + decoding: [Int8](unsafeUninitializedCapacity: 1024) { $1 = unsafe plat_readpassphrase(into: $0) }.map(UInt8.init), as: UTF8.self ) } @@ -140,16 +140,16 @@ public final class Terminal: Console, Sendable { output = text.description } Swift.print(output, terminator: newLine ? "\n" : "") - fflush(stdout) + unsafe fflush(stdout) } /// See ``Console`` public func report(error: String, newLine: Bool) { for c in (newLine ? "\(error)\n" : error).utf8 { #if os(Windows) - _putc_nolock(CInt(c), stderr) + unsafe _putc_nolock(CInt(c), stderr) #else - putc_unlocked(CInt(c), stderr) + unsafe putc_unlocked(CInt(c), stderr) #endif } } @@ -162,7 +162,7 @@ public final class Terminal: Console, Sendable { return (Int(csbi.dwSize.X), Int(csbi.dwSize.Y)) #else var w = winsize() - _ = ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w) + _ = unsafe ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w) return (Int(w.ws_col), Int(w.ws_row)) #endif } diff --git a/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift b/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift index 89af717..c45744e 100644 --- a/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift +++ b/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift @@ -36,33 +36,33 @@ internal func linux_readpassphrase( #endif // Open /dev/tty - let fd = open("/dev/tty", O_RDWR) + let fd = unsafe open("/dev/tty", O_RDWR) guard fd >= 0 else { return nil } defer { close(fd) } // Disable echo var oterm = termios() var term = termios() - guard tcgetattr(fd, &oterm) == 0 else { return nil } + guard unsafe tcgetattr(fd, &oterm) == 0 else { return nil } term = oterm if (flags & 0x1 /* RPP_ECHO_ON */) == 0 { term.c_lflag &= tcflag_t(bitPattern: numericCast(~(ECHO | ECHONL))) } - _ = tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) // libbsd ignores it if this calls fails, should we be doing the same? + _ = unsafe tcsetattr(fd, TCSAFLUSH | TCSASOFT, &term) // libbsd ignores it if this calls fails, should we be doing the same? // Reset the signal counts and install a recovery handler onto a whole buncha signals - linux_readpassphrase_signos.reset() + unsafe linux_readpassphrase_signos.reset() var sigrecovery = sigaction() - sigemptyset(&sigrecovery.sa_mask) + unsafe sigemptyset(&sigrecovery.sa_mask) sigrecovery.sa_flags = 0 #if canImport(Darwin) - sigrecovery.__sigaction_u = .init(__sa_handler: { linux_readpassphrase_signos[$0] += 1 }) + sigrecovery.__sigaction_u = .init(__sa_handler: { unsafe linux_readpassphrase_signos[$0] += 1 }) #elseif canImport(Glibc) - sigrecovery.__sigaction_handler = .init(sa_handler: { linux_readpassphrase_signos[$0] += 1 }) + sigrecovery.__sigaction_handler = .init(sa_handler: { unsafe linux_readpassphrase_signos[$0] += 1 }) #elseif canImport(Musl) - sigrecovery.__sa_handler = .init(sa_handler: { linux_readpassphrase_signos[$0] += 1 }) + sigrecovery.__sa_handler = .init(sa_handler: { unsafe linux_readpassphrase_signos[$0] += 1 }) #elseif os(Android) - sigrecovery.sa_handler = { linux_readpassphrase_signos[$0] += 1 } + sigrecovery.sa_handler = { unsafe linux_readpassphrase_signos[$0] += 1 } #endif let sigsaves = linux_readpassphrase_installHandlers(linux_readpassphrase_signals, &sigrecovery) @@ -72,24 +72,24 @@ internal func linux_readpassphrase( var save_errno = 0 as Int32 var c: Int8 = 0 while i < bufsiz - 1 && nr == 1 && c != 0x0a && c != 0x0d { - nr = read(fd, &c, 1) + nr = unsafe read(fd, &c, 1) if nr == 1 { - buf[i] = c + unsafe buf[i] = c i += 1 } } - buf[i] = 0 + unsafe buf[i] = 0 save_errno = errno // save off errno for later restoration after the state restoration stuff below - if (term.c_lflag & tcflag_t(bitPattern: numericCast(ECHO))) == 0 { write(fd, "\n", 1) } + if (term.c_lflag & tcflag_t(bitPattern: numericCast(ECHO))) == 0 { unsafe write(fd, "\n", 1) } // Restore original terminal config - if memcmp(&term, &oterm, MemoryLayout.size) != 0 { + if unsafe memcmp(&term, &oterm, MemoryLayout.size) != 0 { // I don't understand what this sequence accomplishes with respect to ignoring SIGTTOU. - let save_sigttou = linux_readpassphrase_signos[SIGTTOU] - while tcsetattr(fd, TCSAFLUSH | TCSASOFT, &oterm) == -1 && errno == EINTR && linux_readpassphrase_signos[SIGTTOU] == 0 { + let save_sigttou = unsafe linux_readpassphrase_signos[SIGTTOU] + while unsafe tcsetattr(fd, TCSAFLUSH | TCSASOFT, &oterm) == -1 && errno == EINTR && linux_readpassphrase_signos[SIGTTOU] == 0 { continue } - linux_readpassphrase_signos[SIGTTOU] = save_sigttou + unsafe linux_readpassphrase_signos[SIGTTOU] = save_sigttou } // Restore signal handlers @@ -98,7 +98,7 @@ internal func linux_readpassphrase( // libbsd closes the TTY fd here. Since we deferred the fd closure, we just hope the difference doesn't cause problems. // Re-raise any signals we temporarily ignored, now that the old signal handlers are back in place. - for i in 0.. init(capacity: I) { - self.capacity = Int(capacity) - self.baseAddress = .allocate(capacity: self.capacity) + unsafe self.capacity = Int(capacity) + unsafe self.baseAddress = .allocate(capacity: self.capacity) } subscript(_ index: Int) -> sig_atomic_t { - get { self.baseAddress.advanced(by: index).pointee } - nonmutating set { self.baseAddress.advanced(by: index).pointee = newValue } + get { unsafe self.baseAddress.advanced(by: index).pointee } + nonmutating set { unsafe self.baseAddress.advanced(by: index).pointee = newValue } } subscript(_ index: Int32) -> sig_atomic_t { - get { self[Int(index)] } - nonmutating set { self[Int(index)] = newValue } + get { unsafe self[Int(index)] } + nonmutating set { unsafe self[Int(index)] = newValue } } func reset() { - self.baseAddress.update(repeating: 0, count: self.capacity) + unsafe self.baseAddress.update(repeating: 0, count: self.capacity) } } #endif diff --git a/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift b/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift index b8406ae..d461483 100644 --- a/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift +++ b/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift @@ -321,10 +321,10 @@ public struct SystemTimestampSource: TimestampSource { _ = _localtime64_s(&localTime, ×tamp) #else var timestamp = time(nil) - var localTime = tm() - localtime_r(×tamp, &localTime) + var localTime = unsafe tm() + unsafe localtime_r(×tamp, &localTime) #endif - return localTime + return unsafe localTime } } @@ -342,13 +342,13 @@ public struct TimestampFragment: LoggerFragment { } private func timestamp() -> String { - withUnsafeTemporaryAllocation(of: CChar.self, capacity: 255) { - var localTime = self.source.now() + unsafe withUnsafeTemporaryAllocation(of: CChar.self, capacity: 255) { + var localTime = unsafe self.source.now() - guard strftime($0.baseAddress!, $0.count, "%Y-%m-%dT%H:%M:%S%z", &localTime) > 0 else { + guard unsafe strftime($0.baseAddress!, $0.count, "%Y-%m-%dT%H:%M:%S%z", &localTime) > 0 else { return "" } - return String(cString: $0.baseAddress!) + return unsafe String(cString: $0.baseAddress!) } } } From b5d9245ec4c12cd8b810ffc79dd4043b50eaa3ca Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Thu, 13 Aug 2026 15:30:23 +0200 Subject: [PATCH 2/6] CI updates --- .github/workflows/test.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5f9698..c5cdae9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,9 +14,11 @@ jobs: contents: read uses: vapor/ci/.github/workflows/run-unit-tests.yml@main with: + with_release_mode_testing: true with_windows: true with_musl: true with_android: true + # with_wasm: true with_linting: true enable_all_traits: true secrets: inherit @@ -39,6 +41,13 @@ jobs: uses: vapor/ci/.github/workflows/submit-deps.yml@main secrets: inherit + foundation-linking: + uses: vapor/ci/.github/workflows/check-foundation-linking.yml@main + permissions: + contents: read + with: + swift_image: swift:6.3-noble + # integration-check: # runs-on: ubuntu-latest # container: swift:noble From 145dd4db1da38c07a123e0e675a1f88d2980958d Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Thu, 13 Aug 2026 15:33:11 +0200 Subject: [PATCH 3/6] Try WASM CI --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5cdae9..56cf53e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: with_windows: true with_musl: true with_android: true - # with_wasm: true + with_wasm: true with_linting: true enable_all_traits: true secrets: inherit From 86d938f661a4ec935349cda2415be21b40a5e9f2 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Thu, 13 Aug 2026 15:53:46 +0200 Subject: [PATCH 4/6] Fix Windows and `@preconcurrency` --- Sources/ConsoleKit/Terminal/ANSI.swift | 6 ++-- Sources/ConsoleKit/Terminal/Console.swift | 6 ++-- Sources/ConsoleKit/Terminal/Terminal.swift | 6 ++-- .../Terminal/readpassphrase_linux.swift | 6 ++-- Sources/ConsoleLogger/ANSIColor.swift | 8 +++--- .../LoggerFragments/LoggerFragment.swift | 17 +++++++---- .../ConsoleKitTests/ReadPassphraseTests.swift | 12 ++++---- Tests/ConsoleLoggerTests/LoggingTests.swift | 28 +++++++++---------- 8 files changed, 47 insertions(+), 42 deletions(-) diff --git a/Sources/ConsoleKit/Terminal/ANSI.swift b/Sources/ConsoleKit/Terminal/ANSI.swift index f150993..63306aa 100644 --- a/Sources/ConsoleKit/Terminal/ANSI.swift +++ b/Sources/ConsoleKit/Terminal/ANSI.swift @@ -1,11 +1,11 @@ #if canImport(Darwin) import Darwin.C #elseif canImport(Glibc) -@preconcurrency import Glibc +@unsafe @preconcurrency import Glibc #elseif canImport(Musl) -@preconcurrency import Musl +@unsafe @preconcurrency import Musl #elseif canImport(Android) -@preconcurrency import Android +@unsafe @preconcurrency import Android #elseif os(WASI) import WASILibc #elseif os(Windows) diff --git a/Sources/ConsoleKit/Terminal/Console.swift b/Sources/ConsoleKit/Terminal/Console.swift index 1f85d21..f0b3e43 100644 --- a/Sources/ConsoleKit/Terminal/Console.swift +++ b/Sources/ConsoleKit/Terminal/Console.swift @@ -1,11 +1,11 @@ #if canImport(Darwin) import Darwin.C #elseif canImport(Glibc) -@preconcurrency import Glibc +@unsafe @preconcurrency import Glibc #elseif canImport(Musl) -@preconcurrency import Musl +@unsafe @preconcurrency import Musl #elseif canImport(Android) -@preconcurrency import Android +@unsafe @preconcurrency import Android #elseif os(WASI) import WASILibc #elseif os(Windows) diff --git a/Sources/ConsoleKit/Terminal/Terminal.swift b/Sources/ConsoleKit/Terminal/Terminal.swift index 3454e94..c9b92a2 100644 --- a/Sources/ConsoleKit/Terminal/Terminal.swift +++ b/Sources/ConsoleKit/Terminal/Terminal.swift @@ -3,11 +3,11 @@ #if canImport(Darwin) import Darwin.C #elseif canImport(Glibc) -@preconcurrency import Glibc +@unsafe @preconcurrency import Glibc #elseif canImport(Musl) -@preconcurrency import Musl +@unsafe @preconcurrency import Musl #elseif canImport(Android) -@preconcurrency import Android +@unsafe @preconcurrency import Android #elseif os(WASI) import WASILibc #elseif os(Windows) diff --git a/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift b/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift index c45744e..08a1c76 100644 --- a/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift +++ b/Sources/ConsoleKit/Terminal/readpassphrase_linux.swift @@ -4,11 +4,11 @@ #if canImport(Darwin) import Darwin #elseif canImport(Glibc) -@preconcurrency import Glibc +@unsafe @preconcurrency import Glibc #elseif canImport(Android) -@preconcurrency import Android +@unsafe @preconcurrency import Android #elseif canImport(Musl) -@preconcurrency import Musl +@unsafe @preconcurrency import Musl #endif /// This implementation of `readpassphrase()`, used only on Linux where it's extremely difficult to get at the `libbsd` diff --git a/Sources/ConsoleLogger/ANSIColor.swift b/Sources/ConsoleLogger/ANSIColor.swift index 34eaf01..9fc1803 100644 --- a/Sources/ConsoleLogger/ANSIColor.swift +++ b/Sources/ConsoleLogger/ANSIColor.swift @@ -1,11 +1,11 @@ #if canImport(Darwin) import Darwin.C #elseif canImport(Glibc) -@preconcurrency import Glibc +@unsafe @preconcurrency import Glibc #elseif canImport(Musl) -@preconcurrency import Musl +@unsafe @preconcurrency import Musl #elseif canImport(Android) -@preconcurrency import Android +@unsafe @preconcurrency import Android #elseif os(WASI) import WASILibc #elseif os(Windows) @@ -32,7 +32,7 @@ private var supportsANSICommands: Bool { // Xcode output does not support ANSI commands return false #elseif os(Windows) - return _isatty(_fileno(stdout)) > 0 + return unsafe _isatty(_fileno(stdout)) > 0 #else // If STDOUT is not an interactive terminal then omit ANSI commands return isatty(STDOUT_FILENO) > 0 diff --git a/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift b/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift index d461483..52787ff 100644 --- a/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift +++ b/Sources/ConsoleLogger/LoggerFragments/LoggerFragment.swift @@ -3,11 +3,11 @@ public import Logging #if canImport(Darwin) public import Darwin #elseif canImport(Glibc) -@preconcurrency public import Glibc +@unsafe @preconcurrency public import Glibc #elseif canImport(Musl) -@preconcurrency public import Musl +@unsafe @preconcurrency public import Musl #elseif canImport(Android) -@preconcurrency public import Android +@unsafe @preconcurrency public import Android #elseif os(WASI) public import WASILibc #elseif os(Windows) @@ -317,14 +317,15 @@ public struct SystemTimestampSource: TimestampSource { #if os(Windows) var timestamp = __time64_t() var localTime = tm() - _ = _time64(×tamp) - _ = _localtime64_s(&localTime, ×tamp) + _ = unsafe _time64(×tamp) + _ = unsafe _localtime64_s(&localTime, ×tamp) + return localTime #else var timestamp = time(nil) var localTime = unsafe tm() unsafe localtime_r(×tamp, &localTime) - #endif return unsafe localTime + #endif } } @@ -343,7 +344,11 @@ public struct TimestampFragment: LoggerFragment { private func timestamp() -> String { unsafe withUnsafeTemporaryAllocation(of: CChar.self, capacity: 255) { + #if os(Windows) + var localTime = self.source.now() + #else var localTime = unsafe self.source.now() + #endif guard unsafe strftime($0.baseAddress!, $0.count, "%Y-%m-%dT%H:%M:%S%z", &localTime) > 0 else { return "" diff --git a/Tests/ConsoleKitTests/ReadPassphraseTests.swift b/Tests/ConsoleKitTests/ReadPassphraseTests.swift index eab5909..4163507 100644 --- a/Tests/ConsoleKitTests/ReadPassphraseTests.swift +++ b/Tests/ConsoleKitTests/ReadPassphraseTests.swift @@ -17,25 +17,25 @@ import Musl struct ReadPassphraseTests { private func handler(of sa: sigaction) -> UInt { #if canImport(Darwin) - return unsafeBitCast(sa.__sigaction_u.__sa_handler, to: UInt.self) + return unsafe unsafeBitCast(sa.__sigaction_u.__sa_handler, to: UInt.self) #elseif canImport(Glibc) - return unsafeBitCast(sa.__sigaction_handler.sa_handler, to: UInt.self) + return unsafe unsafeBitCast(sa.__sigaction_handler.sa_handler, to: UInt.self) #elseif canImport(Musl) - return unsafeBitCast(sa.__sa_handler.sa_handler, to: UInt.self) + return unsafe unsafeBitCast(sa.__sa_handler.sa_handler, to: UInt.self) #elseif os(Android) - return unsafeBitCast(sa.sa_handler, to: UInt.self) + return unsafe unsafeBitCast(sa.sa_handler, to: UInt.self) #endif } private func currentHandler(_ signo: Int32) -> UInt { var sa = sigaction() - sigaction(signo, nil, &sa) + unsafe sigaction(signo, nil, &sa) return self.handler(of: sa) } private func makeRecoveryHandler() -> sigaction { var sa = sigaction() - sigemptyset(&sa.sa_mask) + unsafe sigemptyset(&sa.sa_mask) sa.sa_flags = 0 #if canImport(Darwin) sa.__sigaction_u = .init(__sa_handler: { _ in }) diff --git a/Tests/ConsoleLoggerTests/LoggingTests.swift b/Tests/ConsoleLoggerTests/LoggingTests.swift index 65f0bd3..2d968df 100644 --- a/Tests/ConsoleLoggerTests/LoggingTests.swift +++ b/Tests/ConsoleLoggerTests/LoggingTests.swift @@ -5,11 +5,11 @@ import Testing #if canImport(Darwin) import Darwin.C #elseif canImport(Glibc) -@preconcurrency import Glibc +@unsafe @preconcurrency import Glibc #elseif canImport(Musl) -@preconcurrency import Musl +@unsafe @preconcurrency import Musl #elseif canImport(Android) -@preconcurrency import Android +@unsafe @preconcurrency import Android #elseif os(WASI) import WASILibc #elseif os(Windows) @@ -132,25 +132,25 @@ struct ConsoleLoggerTests { @Test("Timestamp Fragment") func timestampFragment() { - struct ConstantTimestampSource: TimestampSource, @unchecked Sendable { + @unsafe struct ConstantTimestampSource: TimestampSource, @unchecked Sendable { let time: tm func now() -> tm { - self.time + unsafe self.time } } let printer = TestingConsoleLoggerPrinter() let logger = Logger(label: "codes.vapor.console") { label in - var time = tm() - time.tm_sec = 1 - time.tm_min = 2 - time.tm_hour = 3 - time.tm_mday = 4 - time.tm_mon = 5 - time.tm_year = 100 - - return ConsoleLogger( + var time = unsafe tm() + unsafe time.tm_sec = 1 + unsafe time.tm_min = 2 + unsafe time.tm_hour = 3 + unsafe time.tm_mday = 4 + unsafe time.tm_mon = 5 + unsafe time.tm_year = 100 + + return unsafe ConsoleLogger( fragment: .timestampDefault(timestampSource: ConstantTimestampSource(time: time)), printer: printer, label: label From b77df5613741c9786778a81085b67938c92626cd Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Thu, 13 Aug 2026 16:13:16 +0200 Subject: [PATCH 5/6] Fix Windows and WASM --- Sources/ConsoleKit/Terminal/Console.swift | 2 +- Sources/ConsoleKit/Terminal/Terminal.swift | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Sources/ConsoleKit/Terminal/Console.swift b/Sources/ConsoleKit/Terminal/Console.swift index f0b3e43..e634f5f 100644 --- a/Sources/ConsoleKit/Terminal/Console.swift +++ b/Sources/ConsoleKit/Terminal/Console.swift @@ -111,7 +111,7 @@ extension Console { // Xcode output does not support ANSI commands return false #elseif os(Windows) - return _isatty(_fileno(stdout)) > 0 + return unsafe _isatty(_fileno(stdout)) > 0 #else // If STDOUT is not an interactive terminal then omit ANSI commands return isatty(STDOUT_FILENO) > 0 diff --git a/Sources/ConsoleKit/Terminal/Terminal.swift b/Sources/ConsoleKit/Terminal/Terminal.swift index c9b92a2..76eb6a3 100644 --- a/Sources/ConsoleKit/Terminal/Terminal.swift +++ b/Sources/ConsoleKit/Terminal/Terminal.swift @@ -158,8 +158,11 @@ public final class Terminal: Console, Sendable { public var size: (width: Int, height: Int) { #if os(Windows) var csbi = CONSOLE_SCREEN_BUFFER_INFO() - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) + unsafe GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) return (Int(csbi.dwSize.X), Int(csbi.dwSize.Y)) + #elseif os(WASI) + // WASI has no `ioctl`/`TIOCGWINSZ`/`winsize`, so return `0, 0` as "unknown size". + return (0, 0) #else var w = winsize() _ = unsafe ioctl(STDOUT_FILENO, UInt(TIOCGWINSZ), &w) From 0aa76f8511b29a199d1abd4843ec841e96434cfe Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Thu, 13 Aug 2026 16:22:01 +0200 Subject: [PATCH 6/6] Fix tests on Windows --- Tests/ConsoleLoggerTests/LoggingTests.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/ConsoleLoggerTests/LoggingTests.swift b/Tests/ConsoleLoggerTests/LoggingTests.swift index 2d968df..cb06765 100644 --- a/Tests/ConsoleLoggerTests/LoggingTests.swift +++ b/Tests/ConsoleLoggerTests/LoggingTests.swift @@ -142,6 +142,15 @@ struct ConsoleLoggerTests { let printer = TestingConsoleLoggerPrinter() let logger = Logger(label: "codes.vapor.console") { label in + #if os(Windows) + var time = tm() + time.tm_sec = 1 + time.tm_min = 2 + time.tm_hour = 3 + time.tm_mday = 4 + time.tm_mon = 5 + time.tm_year = 100 + #else var time = unsafe tm() unsafe time.tm_sec = 1 unsafe time.tm_min = 2 @@ -149,6 +158,7 @@ struct ConsoleLoggerTests { unsafe time.tm_mday = 4 unsafe time.tm_mon = 5 unsafe time.tm_year = 100 + #endif return unsafe ConsoleLogger( fragment: .timestampDefault(timestampSource: ConstantTimestampSource(time: time)),