diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml
index 9341438..49fe6f0 100644
--- a/.github/workflows/release-desktop.yml
+++ b/.github/workflows/release-desktop.yml
@@ -14,8 +14,22 @@ concurrency:
jobs:
release:
- name: Draft Desktop Release
- runs-on: windows-latest
+ name: Build (${{ matrix.target }})
+ runs-on: ${{ matrix.runner }}
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: x86_64-pc-windows-msvc
+ runner: windows-latest
+ rust_target: x86_64-pc-windows-msvc
+ - target: aarch64-apple-darwin
+ runner: macos-latest
+ rust_target: aarch64-apple-darwin
+ - target: x86_64-apple-darwin
+ runner: macos-13
+ rust_target: x86_64-apple-darwin
steps:
- name: Checkout repository
@@ -38,12 +52,13 @@ jobs:
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
- targets: x86_64-pc-windows-msvc
+ targets: ${{ matrix.rust_target }}
- name: Cache Rust
uses: Swatinem/rust-cache@v2
with:
workspaces: desktop/src-tauri -> .codex-cargo-target/desktop-tauri
+ key: ${{ matrix.rust_target }}
- name: Install dependencies
run: pnpm install
@@ -101,27 +116,102 @@ jobs:
RELEASE_TAG: ${{ github.ref_name }}
RELEASE_NOTES_OUTPUT: desktop/.codex-temp/release-assets/RELEASE_NOTES.md
- - name: Create or update draft GitHub release
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: build-${{ matrix.target }}
+ path: |
+ desktop/.codex-temp/release-assets/
+ .codex-cargo-target/desktop-tauri/release/bundle/
+
+ merge-and-publish:
+ name: Merge Manifest & Publish Release
+ needs: release
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: 'pnpm'
+
+ - name: Download all build artifacts
+ uses: actions/download-artifact@v4
+ with:
+ pattern: build-*
+ path: build-artifacts
+ merge-multiple: false
+
+ - name: Collect and merge updater manifests
shell: pwsh
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- $tag = "${{ github.ref_name }}"
- $repo = "${{ github.repository }}"
- $notesPath = (Resolve-Path "desktop/.codex-temp/release-assets/RELEASE_NOTES.md").Path
- $manifestPath = (Resolve-Path "desktop/.codex-temp/release-assets/latest.json").Path
- $bundleRoot = (Resolve-Path ".codex-cargo-target/desktop-tauri/release/bundle").Path
+ $manifestDir = "merged-manifests"
+ New-Item -ItemType Directory -Path $manifestDir -Force
+
+ Get-ChildItem -Path "build-artifacts" -Recurse -Filter "latest.json" |
+ ForEach-Object {
+ Copy-Item $_.FullName -Destination (Join-Path $manifestDir "latest-$($_.Directory.Parent.Name).json") -Force
+ }
+
+ node scripts/merge-updater-manifests.mjs $manifestDir desktop/.codex-temp/release-assets/latest.json
+
+ - name: Collect release notes
+ shell: pwsh
+ run: |
+ $notesFile = Get-ChildItem -Path "build-artifacts" -Recurse -Filter "RELEASE_NOTES.md" |
+ Select-Object -First 1
+ if ($notesFile) {
+ New-Item -ItemType Directory -Path "desktop/.codex-temp/release-assets" -Force
+ Copy-Item $notesFile.FullName -Destination "desktop/.codex-temp/release-assets/RELEASE_NOTES.md" -Force
+ }
+
+ - name: Collect all platform assets
+ id: collect
+ shell: pwsh
+ run: |
+ $assets = @()
- $assets = Get-ChildItem -Path $bundleRoot -Recurse -File |
+ # Windows assets
+ Get-ChildItem -Path "build-artifacts" -Recurse -File |
Where-Object { $_.Extension -in @('.exe', '.msi', '.zip', '.sig') } |
- Select-Object -ExpandProperty FullName
+ ForEach-Object { $assets += $_.FullName }
+ # macOS assets
+ Get-ChildItem -Path "build-artifacts" -Recurse -File |
+ Where-Object { $_.Extension -in @('.dmg', '.app', '.sig') } |
+ ForEach-Object { $assets += $_.FullName }
+
+ # Merged manifest
+ $manifestPath = (Resolve-Path "desktop/.codex-temp/release-assets/latest.json").Path
$assets += $manifestPath
if ($assets.Count -eq 0) {
- throw "No release assets were found under $bundleRoot."
+ throw "No release assets were found."
}
+ # Write asset list to file for next step
+ $assets -join "`n" | Out-File -FilePath "asset-list.txt" -Encoding utf8
+ Write-Output "Asset count: $($assets.Count)"
+
+ - name: Create or update draft GitHub release
+ shell: pwsh
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ $tag = "${{ github.ref_name }}"
+ $repo = "${{ github.repository }}"
+ $notesPath = "desktop/.codex-temp/release-assets/RELEASE_NOTES.md"
+ $assetList = Get-Content "asset-list.txt" | Where-Object { $_.Trim().Length -gt 0 }
+
& gh release view $tag --repo $repo *> $null
$releaseExists = $LASTEXITCODE -eq 0
@@ -132,7 +222,7 @@ jobs:
"--title", $tag,
"--draft",
"--notes-file", $notesPath
- ) + $assets
+ ) + $assetList
& gh @createArgs
exit $LASTEXITCODE
}
@@ -153,6 +243,6 @@ jobs:
"release", "upload", $tag,
"--repo", $repo,
"--clobber"
- ) + $assets
+ ) + $assetList
& gh @uploadArgs
exit $LASTEXITCODE
diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock
index 31b5920..7b67c6d 100644
--- a/desktop/src-tauri/Cargo.lock
+++ b/desktop/src-tauri/Cargo.lock
@@ -2098,6 +2098,16 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "keyring"
+version = "3.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
+dependencies = [
+ "log",
+ "zeroize",
+]
+
[[package]]
name = "kuchikiki"
version = "0.8.8-speedreader"
@@ -3571,9 +3581,10 @@ dependencies = [
[[package]]
name = "rolerover-desktop"
-version = "1.1.0"
+version = "1.1.1"
dependencies = [
"futures-util",
+ "keyring",
"pdf-extract",
"reqwest 0.12.28",
"rusqlite",
diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml
index d290b5f..e07c084 100644
--- a/desktop/src-tauri/Cargo.toml
+++ b/desktop/src-tauri/Cargo.toml
@@ -27,3 +27,6 @@ tauri-plugin-updater = "2.0.0-rc.4"
uuid = { version = "1.18.1", features = ["v4"] }
pdf-extract = "0.7"
+[target.'cfg(target_os = "macos")'.dependencies]
+keyring = "3"
+
diff --git a/desktop/src-tauri/entitlements/macOS.entitlements b/desktop/src-tauri/entitlements/macOS.entitlements
new file mode 100644
index 0000000..f5093df
--- /dev/null
+++ b/desktop/src-tauri/entitlements/macOS.entitlements
@@ -0,0 +1,13 @@
+
+
+
+
+ com.apple.security.network.client
+
+ com.apple.security.files.user-selected.read-only
+
+ com.apple.security.files.user-selected.read-write
+
+
+
diff --git a/desktop/src-tauri/src/settings.rs b/desktop/src-tauri/src/settings.rs
index 515dd6b..d5e0caf 100644
--- a/desktop/src-tauri/src/settings.rs
+++ b/desktop/src-tauri/src/settings.rs
@@ -880,8 +880,12 @@ fn evaluate_runtime_vault_state(
));
}
if warnings.is_empty() && keyring_active_count > 0 {
- warnings
- .push("Active secret descriptors are backed by Windows Credential Manager.".into());
+ #[cfg(target_os = "windows")]
+ warnings.push("Active secret descriptors are backed by Windows Credential Manager.".into());
+ #[cfg(target_os = "macos")]
+ warnings.push("Active secret descriptors are backed by macOS Keychain.".into());
+ #[cfg(not(any(target_os = "windows", target_os = "macos")))]
+ warnings.push("Active secret descriptors are backed by OS keyring.".into());
}
}
@@ -1141,7 +1145,7 @@ fn decode_legacy_entry_to_utf8(
}
fn os_keyring_backend_supported() -> bool {
- cfg!(target_os = "windows")
+ cfg!(target_os = "windows") || cfg!(target_os = "macos")
}
fn secret_keyring_target(secret_key: &str) -> String {
@@ -1154,7 +1158,11 @@ fn read_secret_from_os_keyring(secret_key: &str) -> Result