Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b83bc9e
feat: add Windows CI support with Swift 6.3 and swift-jinja migration
alexey1312 Feb 24, 2026
7425b20
chore: archive add-windows-support openspec change
alexey1312 Feb 24, 2026
a43e3fe
chore: regenerate llms-full.txt after rebase
alexey1312 Mar 25, 2026
4fced7b
style: fix markdown table formatting in CLAUDE.md
alexey1312 Mar 25, 2026
88d9020
fix: suppress function_body_length swiftlint violations
alexey1312 Mar 25, 2026
5270e27
fix: bump swift-resvg to 0.45.1-swift.15 for Windows artifactbundle
alexey1312 Mar 25, 2026
2ec91fa
fix: exclude MCP SDK on Windows (swift-nio incompatible)
alexey1312 Mar 25, 2026
8b11499
perf(ci): add SPM cache for Windows, remove redundant Release build
alexey1312 Mar 25, 2026
a693e8a
docs: document MCP SDK Windows exclusion and resvg version coupling
alexey1312 Mar 25, 2026
97fe6e0
docs: add Windows platform support to README and DocC articles
alexey1312 Mar 25, 2026
7cf5ad1
feat: adopt Swift 6.3, update CI images, and upgrade MCP SDK to 0.12.0
alexey1312 Mar 25, 2026
812051b
fix(ci): install Swift 6.3 via swiftly on macOS, use stable release t…
alexey1312 Mar 25, 2026
38a1c1f
fix(ci): use Swift 6.3 .pkg toolchain instead of swiftly on macOS
alexey1312 Mar 25, 2026
3513e5c
fix: replace deprecated MCP SDK .text() calls and update build instru…
alexey1312 Mar 25, 2026
a1d2268
fix(ci): use correct Swift tag format for compnerd/gha-setup-swift on…
alexey1312 Mar 25, 2026
079d489
fix(ci): use correct Swift tag format in ci.yml for Windows
alexey1312 Mar 25, 2026
b5d43fc
fix: address PR review findings from code review and Gemini
alexey1312 Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude/rules/gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ func withContext<T: Sendable>(operation: @Sendable () async -> T) async -> T
func withContext<T>(operation: () async -> T) async -> T
```

### #if Inside Array Literals (Swift Limitation)

`#if` does NOT work inside array literals in Swift — not just `Package.swift`, but also
`CommandConfiguration(subcommands: [...])` and any other `[T]` literal context.
Use `var` + `.append()` pattern or computed property returning the array.

## SwiftLint Rules

- Use `Data("string".utf8)` not `"string".data(using: .utf8)!`
Expand Down
72 changes: 64 additions & 8 deletions .claude/rules/linux-compat.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Linux Compatibility
# Linux & Windows Compatibility

This rule covers Linux-specific workarounds and differences from macOS.
This rule covers Linux- and Windows-specific workarounds and differences from macOS.

The project builds on Linux (Ubuntu 22.04 LTS / Jammy). Key differences from macOS:
The project builds on Linux (Ubuntu 22.04 LTS / Jammy) and Windows (Swift 6.3). Key differences from macOS:

## Required Import for Networking

Expand Down Expand Up @@ -44,8 +44,64 @@ func testSomePngOperation() throws {

## Platform-Specific Features

| Feature | macOS | Linux |
| ------------ | ------------ | ------------------------ |
| HEIC encoding| ImageIO | Falls back to PNG |
| libpng tests | Full support | Build tests first |
| Foundation | Full | Some APIs missing/broken |
| Feature | macOS | Linux | Windows |
| ------------ | ------------ | ------------------------ | ------------------------ |
| HEIC encoding| ImageIO | Falls back to PNG | Falls back to PNG |
| libpng tests | Full support | Build tests first | Not tested |
| Foundation | Full | Some APIs missing/broken | Some APIs missing/broken |
| XcodeProj | Full | Full | Not available |
| Swift version| 6.3+ | 6.3+ | 6.3+ |

## Windows Support

### Swift Version

Windows requires Swift 6.3 (stable release) due to `swift-resvg` artifactbundle compatibility.
CI uses `compnerd/gha-setup-swift@v0.3.0` with `branch: swift-6.3-release` and `tag: 6.3-RELEASE` (action prepends `swift-` to the tag).

### Conditional Dependencies (Package.swift)

`#if` inside array literals does NOT work in SPM Package.swift. Use variable + `#if` append pattern:

```swift
var packageDependencies: [Package.Dependency] = [...]
#if !os(Windows)
packageDependencies.append(.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.27.0"))
#endif
```

### XcodeProj Exclusion

XcodeProj is Apple-only (depends on PathKit/AEXML). On Windows:
- Dependency excluded via `#if !os(Windows)` in Package.swift
- `XcodeProjectWriter` wrapped in `#if canImport(XcodeProj)` (6 files in Export/)
- Xcode project manipulation silently skipped on Windows

### FoundationNetworking / FoundationXML

Use `#if canImport()` instead of `#if os(Linux)` — covers both Linux and Windows:

```swift
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

#if canImport(FoundationXML)
import FoundationXML
#endif
```

### MCP SDK Exclusion

MCP swift-sdk depends on swift-nio (no Windows support). On Windows:
- Dependency excluded via `#if !os(Windows)` in Package.swift (same pattern as XcodeProj)
- All `Sources/ExFigCLI/MCP/*.swift` and `Subcommands/MCPServe.swift` wrapped in `#if canImport(MCP)`
- `ExFigCommand.allSubcommands` computed var (not array literal) for conditional subcommand registration
- `MCPToolHandlers.swift` excluded in `.swiftlint.yml` (file_length with `#if` wrapper)

### SPM Artifactbundle on Windows

SPM on Windows has library naming differences:
- Unix linkers auto-prepend `lib` prefix (`-lresvg` finds `libresvg.a`)
- Windows `lld-link` does NOT prepend prefix (`resvg.lib` must exist as-is)
- Swift 6.3 allows `.lib` files without `lib` prefix in artifactbundle info.json
49 changes: 43 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,27 @@ jobs:
runs-on: macos-15
needs: lint
env:
DEVELOPER_DIR: "/Applications/Xcode_26.1.1.app/Contents/Developer"
DEVELOPER_DIR: "/Applications/Xcode_26.2.app/Contents/Developer"

steps:
- uses: actions/checkout@v6
with:
lfs: true

- name: Install Swift 6.3 toolchain
run: |
curl -fLo swift-6.3.pkg "https://download.swift.org/swift-6.3-release/xcode/swift-6.3-RELEASE/swift-6.3-RELEASE-osx.pkg"
sudo installer -pkg swift-6.3.pkg -target /
TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-6.3-RELEASE.xctoolchain/Info.plist)
echo "TOOLCHAINS=$TOOLCHAINS" >> "$GITHUB_ENV"

- name: Cache SPM dependencies
uses: actions/cache@v5
with:
path: .build
key: ${{ runner.os }}-xcode-26.1.1-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}
key: ${{ runner.os }}-swift-6.3-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}
restore-keys: |
${{ runner.os }}-xcode-26.1.1-spm-
${{ runner.os }}-swift-6.3-spm-

- uses: jdx/mise-action@v4
with:
Expand All @@ -81,7 +88,7 @@ jobs:
runs-on: ubuntu-latest
needs: lint
container:
image: swift:6.2.3-jammy
image: swift:6.3-jammy
steps:
- name: Install dependencies
run: |
Expand All @@ -97,9 +104,9 @@ jobs:
uses: actions/cache@v5
with:
path: .build
key: ${{ runner.os }}-swift-6.2.3-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}
key: ${{ runner.os }}-swift-6.3-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}
restore-keys: |
${{ runner.os }}-swift-6.2.3-spm-
${{ runner.os }}-swift-6.3-spm-

- uses: jdx/mise-action@v4
with:
Expand All @@ -124,3 +131,33 @@ jobs:
- name: Run tests
# Build tests separately to avoid hangs from concurrent build + test execution
run: swift test --skip-build --parallel 2>&1 | xcsift

build-windows:
name: Build (Windows)
runs-on: windows-latest
needs: lint
steps:
- uses: actions/checkout@v6

- name: Install Swift
uses: compnerd/gha-setup-swift@v0.3.0
with:
branch: swift-6.3-release
tag: 6.3-RELEASE

- name: Cache SPM dependencies
uses: actions/cache@v5
with:
path: .build
key: ${{ runner.os }}-swift-6.3-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}
restore-keys: |
${{ runner.os }}-swift-6.3-spm-

- name: Swift version
run: swift --version

- name: Resolve dependencies
run: swift package resolve

- name: Build (Debug)
run: swift build
62 changes: 56 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ jobs:
platform: linux-x64
build-path: x86_64-unknown-linux-gnu/release
archive-name: exfig-linux-x64
container: swift:6.2.3-jammy
container: swift:6.3-jammy
extra-flags: --static-swift-stdlib -Xlinker -lcurl -Xlinker -lxml2 -Xlinker -lssl -Xlinker -lcrypto -Xlinker -lz
- os: windows-latest
platform: windows-x64
build-path: x86_64-unknown-windows-msvc/release
archive-name: exfig-windows-x64


runs-on: ${{ matrix.os }}
Expand All @@ -45,16 +49,28 @@ jobs:
with:
lfs: true

- name: Select Xcode 26.1 (macOS)
- name: Install Swift (Windows)
if: matrix.platform == 'windows-x64'
uses: compnerd/gha-setup-swift@v0.3.0
with:
branch: swift-6.3-release
tag: 6.3-RELEASE

- name: Install Swift 6.3 toolchain (macOS)
if: matrix.platform == 'macos'
run: sudo xcode-select -s /Applications/Xcode_26.1.1.app/Contents/Developer
run: |
curl -fLo swift-6.3.pkg "https://download.swift.org/swift-6.3-release/xcode/swift-6.3-RELEASE/swift-6.3-RELEASE-osx.pkg"
sudo installer -pkg swift-6.3.pkg -target /
TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-6.3-RELEASE.xctoolchain/Info.plist)
echo "TOOLCHAINS=$TOOLCHAINS" >> "$GITHUB_ENV"

- name: Install system dependencies (Linux)
if: matrix.platform == 'linux-x64'
run: |
apt-get install -y libcurl4-openssl-dev libxml2-dev libssl-dev

- name: Set version from tag
- name: Set version from tag (Unix)
if: matrix.platform != 'windows-x64'
run: |
VERSION="${GITHUB_REF#refs/tags/}"
FILE="Sources/ExFigCLI/ExFigCommand.swift"
Expand All @@ -64,6 +80,14 @@ jobs:
sed -i "s/static let version = \".*\"/static let version = \"$VERSION\"/" "$FILE"
fi

- name: Set version from tag (Windows)
if: matrix.platform == 'windows-x64'
shell: pwsh
run: |
$version = $env:GITHUB_REF -replace '^refs/tags/', ''
$file = "Sources/ExFigCLI/ExFigCommand.swift"
(Get-Content $file) -replace 'static let version = ".*"', "static let version = `"$version`"" | Set-Content $file

- name: Build release binary (macOS)
if: matrix.platform == 'macos'
run: |
Expand All @@ -83,6 +107,10 @@ jobs:
if: matrix.platform == 'linux-x64'
run: swift build -c release ${{ matrix.extra-flags }}

- name: Build release binary (Windows)
if: matrix.platform == 'windows-x64'
run: swift build -c release

- name: Create release archive (macOS)
if: matrix.platform == 'macos'
run: |
Expand All @@ -109,6 +137,21 @@ jobs:
cp LICENSE dist/
cd dist && tar -czf ../${{ matrix.archive-name }}.tar.gz -- *

- name: Create release archive (Windows)
if: matrix.platform == 'windows-x64'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist
Copy-Item ".build/${{ matrix.build-path }}/exfig.exe" "dist/ExFig.exe"
# Resource bundles are optional — not all modules produce them on every build config
Copy-Item ".build/${{ matrix.build-path }}/exfig_AndroidExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
Copy-Item ".build/${{ matrix.build-path }}/exfig_XcodeExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
Copy-Item ".build/${{ matrix.build-path }}/exfig_FlutterExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
Copy-Item ".build/${{ matrix.build-path }}/exfig_WebExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
Copy-Item ".build/${{ matrix.build-path }}/exfig_ExFigCLI.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
Copy-Item "LICENSE" "dist/"
Compress-Archive -Path "dist/*" -DestinationPath "${{ matrix.archive-name }}.zip"

- name: Upload artifact
uses: actions/upload-artifact@v7
with:
Expand Down Expand Up @@ -218,6 +261,7 @@ jobs:
files: |
artifacts/exfig-macos/exfig-macos.zip
artifacts/exfig-linux-x64/exfig-linux-x64.tar.gz
artifacts/exfig-windows-x64/exfig-windows-x64.zip
.pkl-out/exfig@*/*
completions/exfig.bash
completions/_exfig
Expand Down Expand Up @@ -292,18 +336,24 @@ jobs:
concurrency:
group: "pages"
cancel-in-progress: true
env:
DEVELOPER_DIR: "/Applications/Xcode_26.1.1.app/Contents/Developer"
steps:
- uses: actions/checkout@v6

- name: Install Swift 6.3 toolchain
run: |
curl -fLo swift-6.3.pkg "https://download.swift.org/swift-6.3-release/xcode/swift-6.3-RELEASE/swift-6.3-RELEASE-osx.pkg"
sudo installer -pkg swift-6.3.pkg -target /
TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-6.3-RELEASE.xctoolchain/Info.plist)
echo "TOOLCHAINS=$TOOLCHAINS" >> "$GITHUB_ENV"

- name: Build DocC
run: |
swift package --allow-writing-to-directory docs \
generate-documentation --target ExFigCLI \
--disable-indexing \
--transform-for-static-hosting \
--hosting-base-path exfig \
--enable-experimental-code-block-annotations \
--output-path docs
echo '<script>window.location.href += "/documentation/exfigcli"</script>' > docs/index.html

Expand Down
36 changes: 0 additions & 36 deletions .github/workflows/windows-test.yml

This file was deleted.

1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.3.0
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ excluded:
- Sources/ExFigCLI/Resources/flutterConfig.swift
- Sources/ExFigCLI/Resources/webConfig.swift
- Tests/XcodeExportTests/XcodeIconsExporterTests.swift
- Sources/ExFigCLI/MCP/MCPToolHandlers.swift

disabled_rules:
- trailing_comma
Expand Down
Loading
Loading