Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Pages

on:
push:
branches: [master]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure GitHub Pages
uses: actions/configure-pages@v5

- name: Upload site artifact
uses: actions/upload-pages-artifact@v3
with:
path: site

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
143 changes: 117 additions & 26 deletions .github/workflows/release-bundles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,37 @@ permissions:
contents: write

jobs:
build:
name: Build ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
build-macos-public:
name: Build signed macOS release
runs-on: macos-latest
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
steps:
- name: Check required secrets
run: |
set -euo pipefail
missing=0
for name in APPLE_CERTIFICATE APPLE_CERTIFICATE_PASSWORD APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID; do
if [ -z "${!name:-}" ]; then
echo "Missing required secret: $name" >&2
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
exit 1
fi

- name: Checkout
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin,x86_64-apple-darwin

- name: Setup Node
uses: actions/setup-node@v4
Expand All @@ -36,49 +54,122 @@ jobs:
- name: Install Tauri CLI
run: cargo install tauri-cli --locked --force

- name: Build Tauri bundles
run: cargo tauri build --ci
- name: Import Apple Developer ID certificate
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}

- name: Resolve signing identity
id: signing_identity
run: |
set -euo pipefail
identity=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -n 1 | sed -E 's/.*"([^"]+)".*/\1/')
if [ -z "$identity" ]; then
echo "Developer ID Application identity not found in keychain" >&2
security find-identity -v -p codesigning || true
exit 1
fi
echo "identity=$identity" >> "$GITHUB_OUTPUT"

- name: Build universal app and DMG
env:
APPLE_SIGNING_IDENTITY: ${{ steps.signing_identity.outputs.identity }}
run: cargo tauri build --ci --bundles app,dmg --target universal-apple-darwin

- name: Package macOS artifacts
if: matrix.os == 'macos-latest'
- name: Staple and validate notarized artifacts
run: |
app_path="src-tauri/target/release/bundle/macos/Always On Top.app"
set -euo pipefail
app_path="src-tauri/target/universal-apple-darwin/release/bundle/macos/Float.app"
dmg_path=$(find src-tauri/target/universal-apple-darwin/release/bundle/dmg -maxdepth 1 -name '*.dmg' | head -n 1)
if [ ! -d "$app_path" ]; then
echo "App not found at $app_path" >&2
exit 1
fi
ditto -c -k --sequesterRsrc --keepParent "$app_path" "Always-On-Top-macos.zip"
if [ -z "$dmg_path" ]; then
echo "DMG output not found" >&2
exit 1
fi
xcrun stapler staple "$app_path"
xcrun stapler validate "$app_path"
xcrun stapler staple "$dmg_path"
xcrun stapler validate "$dmg_path"
spctl -a -vvv "$app_path"
spctl -a -vvv -t open "$dmg_path"

- name: Create stable public artifacts
run: |
set -euo pipefail
dmg_path=$(find src-tauri/target/universal-apple-darwin/release/bundle/dmg -maxdepth 1 -name '*.dmg' | head -n 1)
cp "$dmg_path" Float-macos-universal.dmg
shasum -a 256 Float-macos-universal.dmg > Float-macos-universal.sha256

- name: Upload macOS release artifact
uses: actions/upload-artifact@v4
with:
name: float-macos-public
path: |
Float-macos-universal.dmg
Float-macos-universal.sha256
if-no-files-found: error
retention-days: 14

- name: Package Windows artifacts
if: matrix.os == 'windows-latest'
build-windows-preview:
name: Build Windows preview artifact
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"

- name: Install JS dependencies
run: npm ci

- name: Install Tauri CLI
run: cargo install tauri-cli --locked --force

- name: Build Windows NSIS installer
run: cargo tauri build --ci --bundles nsis

- name: Collect Windows preview artifact
shell: pwsh
run: |
$installer = Get-ChildItem "src-tauri/target/release/bundle/nsis/*.exe" | Select-Object -First 1
if (-not $installer) { Write-Error "Installer not found"; exit 1 }
Copy-Item $installer.FullName "Always-On-Top-windows.exe" -Force
Copy-Item $installer.FullName "Float-windows-preview.exe" -Force

- name: Upload artifact
- name: Upload Windows preview artifact
uses: actions/upload-artifact@v4
with:
name: always-on-top-${{ matrix.os }}
path: |
Always-On-Top-macos.zip
Always-On-Top-windows.exe
name: float-windows-preview
path: Float-windows-preview.exe
if-no-files-found: error
retention-days: 14

release:
name: Publish release artifacts
needs: build
name: Publish public release assets
needs: build-macos-public
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- name: Download artifacts
- name: Download macOS release artifact
uses: actions/download-artifact@v4
with:
path: dist-artifacts
name: float-macos-public
path: release-artifacts

- name: Publish to GitHub Releases
uses: softprops/action-gh-release@v2
with:
files: dist-artifacts/**/*
files: |
release-artifacts/Float-macos-universal.dmg
release-artifacts/Float-macos-universal.sha256
generate_release_notes: true
2 changes: 1 addition & 1 deletion .github/workflows/release-plz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: release-plz

on:
push:
branches: [main]
branches: [master]
workflow_dispatch:
inputs:
dry_run:
Expand Down
14 changes: 3 additions & 11 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: UI Tests (Playwright + tauri-driver)
name: UI Tests

on:
push:
branches: [ main ]
branches: [ master ]
pull_request:

jobs:
Expand All @@ -16,19 +16,11 @@ jobs:
with:
node-version: '22'

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable

- name: Install JS deps
run: npm install --no-fund

- name: Install Playwright browsers
run: npx playwright install --with-deps

- name: Install tauri-driver
run: cargo install tauri-driver --locked

- name: Run Playwright tests
env:
PATH: "$HOME/.cargo/bin:${PATH}"
- name: Run Playwright smoke tests
run: npm run test:ui
54 changes: 39 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ Relevant specs: `specs/always-on-top/`, `specs/file-selection/`, `specs/fit-wind
- Linux: system dependencies per Tauri docs; only dev run covered here.
- Optional: `just` for common tasks (install via `cargo install just`).

## Quick Start (Tauri shell)
## Local Development
```sh
# Clone and enter repo
just tauri-dev # Runs Tauri in dev mode
```
- The window launches always-on-top; use File → Open… to pick an image.
Expand All @@ -36,11 +35,11 @@ just tauri-dev # Runs Tauri in dev mode
- Install the Tauri WebDriver once via `cargo install tauri-driver --locked` so the `tauri-driver` binary is on your `PATH` (or export `TAURI_DRIVER_PATH` pointing to it).
- Execute `npm run test:ui` to run the Playwright spec in `tests/`.

### Build Bundles (release artifacts)
## Internal Packaging
```sh
just tauri-build # macOS .app + Windows NSIS installer
```
Artifacts:
Outputs:
- macOS app bundle: `src-tauri/target/release/bundle/macos/Float.app`
- Windows NSIS installer: `src-tauri/target/release/bundle/nsis/Float_*.exe`

Expand All @@ -49,7 +48,7 @@ To open the built macOS app locally:
just tauri-open
```

### Cross-build Windows executable (macOS host)
### Windows cross-build from macOS
```sh
just tauri-build-windows
```
Expand All @@ -62,17 +61,40 @@ just build-run # cargo run
just bundle-run # cargo bundle --release (macOS .app)
```

## Downloads
- GitHub Actions (workflow: `release-bundles`) builds macOS and Windows artifacts and uploads them to the Releases page when a tag (`v*`) is pushed or the workflow is dispatched manually.
- If no release is published yet, build locally using the commands above.
- Linux packages are not produced; run locally on Linux if needed.
## Public Release

## Release Pipeline (manual + CI)
1) Ensure `cargo fmt`, `cargo clippy --all-targets -- -D warnings`, and `cargo check` pass.
2) Release automation: `.github/workflows/release-plz.yml` (runs on `main` pushes or manual dispatch) uses `release-plz` to update `CHANGELOG.md`, bump versions, tag with `v*`, and create the GitHub Release (no crates.io publish).
3) Bundles on tags: `.github/workflows/release-bundles.yml` builds on `v*` tags (or manual dispatch), uploads build artifacts as workflow artifacts, and publishes/updates the GitHub Release with the macOS zip + Windows installer.
4) Local build sanity (optional): `just tauri-build`; collect artifacts from `src-tauri/target/release/bundle/macos/Float.app` and `src-tauri/target/release/bundle/nsis/Float_*.exe`.
5) Draft release notes summarizing changes and link relevant OpenSpec change IDs (release-plz populates the changelog automatically).
The public distribution channel is:

- GitHub Pages for the landing page
- GitHub Releases for the notarized macOS download

Public macOS assets are published with stable names:

- `Float-macos-universal.dmg`
- `Float-macos-universal.sha256`

The landing page lives in `site/` and links to:

- `https://github.com/Zacaria/float/releases/latest/download/Float-macos-universal.dmg`
- `https://github.com/Zacaria/float/releases/latest/download/Float-macos-universal.sha256`

### CI release flow

1. `release-plz` updates `CHANGELOG.md`, versions, tags, and the GitHub Release from `.github/workflows/release-plz.yml`.
2. `.github/workflows/release-bundles.yml` builds a universal macOS bundle on `v*` tags, signs it with a `Developer ID Application` certificate, staples and validates the notarized app and DMG, generates `Float-macos-universal.sha256`, and publishes the macOS assets to the GitHub Release.
3. `.github/workflows/pages.yml` deploys the static landing page from `site/` to GitHub Pages on `master`.

### CI secret contract

The macOS public release workflow requires these repository secrets:

- `APPLE_CERTIFICATE`
- `APPLE_CERTIFICATE_PASSWORD`
- `APPLE_ID`
- `APPLE_PASSWORD`
- `APPLE_TEAM_ID`

See [`docs/releasing.md`](docs/releasing.md) for the exact contract, fallback commands, and the release checklist.

## Troubleshooting
- **Tauri missing deps**: install platform prereqs (Xcode CLT on macOS; MSVC + WebView2 on Windows).
Expand All @@ -82,3 +104,5 @@ just bundle-run # cargo bundle --release (macOS .app)
## Contributing
- Specs live under `openspec/specs/`; proposed changes go in `openspec/changes/`.
- Prefer `just tauri-dev` for local runs; keep changes small and update specs when behavior changes.
- Commit subjects must use Conventional Commits such as `feat: ...`, `fix(menu): ...`, or `chore!: ...`.
- Run `just install-git-hooks` once to enable the local `commit-msg` guard, or validate a range manually with `just check-commits origin/main..HEAD`.
Loading
Loading