From def1907b90df89d8a2f2f2bed7ae51de26aeff22 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 17 Jul 2026 20:27:27 +0000 Subject: [PATCH] Fix CI and prep 2.1.3 for AppState 3.0 macOS and DocC were failing because runners no longer ship Xcode 16. Switch those workflows to macos-15 + latest-stable (same as AppState). Also polish the #30 fixes for release: unwrap before collection casts on Linux/WASI, import Combine when available, and document 2.1.3 so AppState can drop its Cache revision pin. Co-authored-by: Leif --- .github/workflows/docc.yml | 9 ++---- .github/workflows/macOS.yml | 4 +-- CHANGELOG.md | 32 +++++++++++++++++++ README.md | 3 +- Sources/Cache/Cache/Cache.swift | 4 +++ .../Dictionary/Dictionary+Cacheable.swift | 12 +++---- 6 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 CHANGELOG.md diff --git a/.github/workflows/docc.yml b/.github/workflows/docc.yml index dcc9557..d3a541a 100644 --- a/.github/workflows/docc.yml +++ b/.github/workflows/docc.yml @@ -18,7 +18,7 @@ jobs: environment: name: github-pages url: '${{ steps.deployment.outputs.page_url }}' - runs-on: macos-latest + runs-on: macos-15 steps: - name: Checkout code @@ -27,12 +27,7 @@ jobs: - name: Setup Xcode uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 16.0 - - - name: Set up Swift - uses: swift-actions/setup-swift@v2 - with: - swift-version: '6.1.0' + xcode-version: latest-stable - name: Build and Export DocC run: | diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml index 4868889..5fc3a40 100644 --- a/.github/workflows/macOS.yml +++ b/.github/workflows/macOS.yml @@ -8,11 +8,11 @@ on: jobs: build: - runs-on: macos-latest + runs-on: macos-15 steps: - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: 16.0 + xcode-version: latest-stable - uses: actions/checkout@v4 - name: Build run: swift build -v diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..f13c432 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.1.3] - 2026-07-17 + +Release intended for consumers such as **AppState 3.0**, which can replace a revision pin of Cache#30 with `from: "2.1.3"`. + +### Fixed + +- **WebAssembly / WASI builds** — Guard `@Published` / `ObservableObject` with `#if canImport(Combine)` instead of `#if !os(Linux) && !os(Windows)`, so wasm no longer takes the Combine path ([#30](https://github.com/0xLeif/Cache/pull/30), AppState#149). +- **Linux / WASI collection cast crash** — `Dictionary.get(_:as:)` unwraps the optional value before casting, avoiding `swift_dynamicCastFailure` / `_arrayForceCast` aborts when reading collection-typed values from an `Any` cache ([#30](https://github.com/0xLeif/Cache/pull/30), AppState#151). +- **CI** — macOS and DocC workflows no longer pin Xcode 16 (unavailable on current `macos-latest` images). Use `macos-15` with `latest-stable` Xcode, matching AppState. + +### Changed + +- Explicitly import Combine when available so `@Published` / `ObservableObject` compile reliably across build configurations. + +## [2.1.2] - 2025-09-20 + +### Added + +- DocC plugin dependency and GitHub Pages documentation workflow updates. + +[Unreleased]: https://github.com/0xLeif/Cache/compare/2.1.3...HEAD +[2.1.3]: https://github.com/0xLeif/Cache/compare/2.1.2...2.1.3 +[2.1.2]: https://github.com/0xLeif/Cache/releases/tag/2.1.2 diff --git a/README.md b/README.md index 4755d97..ce09dd5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Cache is a Swift library for caching arbitrary data types in memory. It provides - Flexible caching, allowing for multiple Cache objects with different configurations - Thread-safe implementation - Property Wrappers +- Cross-platform: Apple platforms, Linux, Windows, and WebAssembly (WASI) ## Installation @@ -24,7 +25,7 @@ Add the following line to your `Package.swift` file in the dependencies array: ```swift dependencies: [ - .package(url: "https://github.com/0xLeif/Cache.git", from: "2.0.0") + .package(url: "https://github.com/0xLeif/Cache.git", from: "2.1.3") ] ``` diff --git a/Sources/Cache/Cache/Cache.swift b/Sources/Cache/Cache/Cache.swift index 352d121..40645d9 100644 --- a/Sources/Cache/Cache/Cache.swift +++ b/Sources/Cache/Cache/Cache.swift @@ -1,5 +1,9 @@ import Foundation +#if canImport(Combine) +import Combine +#endif + /** `Cache` class provides a cache functionality that can be used to store key-value pairs. It is thread-safe using a locking mechanism. diff --git a/Sources/Cache/Dictionary/Dictionary+Cacheable.swift b/Sources/Cache/Dictionary/Dictionary+Cacheable.swift index 3505287..fe7000b 100644 --- a/Sources/Cache/Dictionary/Dictionary+Cacheable.swift +++ b/Sources/Cache/Dictionary/Dictionary+Cacheable.swift @@ -15,13 +15,11 @@ extension Dictionary: Cacheable { - Returns: The casted value for the given key, or `nil` if the key doesn't exist or the cast fails. */ public func get(_ key: Key, as: Item.Type = Item.self) -> Item? { - // Route through an explicit `Any` boxing step before the conditional cast. This avoids - // `swift_dynamicCastFailure` / `_arrayForceCast` aborts on Linux and WASI when casting an - // `Optional` that wraps a collection type (e.g. `[SomeStruct]`): without the - // intermediate cast, the runtime takes the `Optional` → `[T]` forced-array-cast path, - // which is not implemented the same way off Darwin. Boxing to `Any` first normalises the - // dynamic type to the concrete `Item` path. - guard let value = (self[key] as Any) as? Item else { + // Unwrap the optional value first. Casting `Optional` (or `Optional`) + // directly to a collection type (e.g. `[T]`) can abort on Linux/WASI with + // `swift_dynamicCastFailure` / `_arrayForceCast`. Unwrapping first takes the + // concrete `Value` → `Item` path instead. + guard let rawValue = self[key], let value = rawValue as? Item else { return nil }