diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..915d104 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,261 @@ +name: Build + +on: + push: + branches: + - "**" + tags: + - "v*" + paths: + - "src/**" + - "src-tauri/**" + - "package.json" + - "vite.config.ts" + - ".github/workflows/build.yml" + pull_request: + branches: + - "**" + paths: + - "src/**" + - "src-tauri/**" + - "package.json" + - "vite.config.ts" + - ".github/workflows/build.yml" + workflow_dispatch: + +permissions: + contents: write + +jobs: + meta: + name: Meta + runs-on: ubuntu-latest + outputs: + version: ${{ steps.meta.outputs.version }} + tag: ${{ steps.meta.outputs.tag }} + is-release: ${{ steps.meta.outputs.is-release }} + steps: + - name: Resolve version/tag/release flag + id: meta + shell: bash + run: | + REF="${GITHUB_REF}" + if [[ "$REF" == refs/tags/v* ]]; then + TAG="${REF#refs/tags/}" + VERSION="${TAG#v}" + IS_RELEASE="true" + else + TAG="dev" + VERSION="0.0.0-dev" + IS_RELEASE="false" + fi + + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "is-release=$IS_RELEASE" >> "$GITHUB_OUTPUT" + + build: + name: Build ${{ matrix.job }} + needs: meta + strategy: + fail-fast: true + matrix: + include: + - job: "windows-amd64" + platform: "windows-latest" + os_name: "windows" + arch: "x86_64" + rust_target: "x86_64-pc-windows-msvc" + - job: "macos-arm64" + platform: "macos-latest" + os_name: "macos" + arch: "aarch64" + rust_target: "aarch64-apple-darwin" + + runs-on: ${{ matrix.platform }} + + env: + VERSION: ${{ needs.meta.outputs.version }} + APP_NAME: RustSyncCV + BIN_NAME: rust-sync-cv-client + + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.rust_target }} + + - name: Setup sccache + uses: mozilla-actions/sccache-action@v0.0.5 + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + workspaces: "src-tauri -> target" + + - name: Update version + shell: bash + run: | + VERSION="${{ needs.meta.outputs.version }}" + sed -i.bak 's/\"version\": \"[^\"]*\"/\"version\": \"'"$VERSION"'\"/g' package.json + sed -i.bak 's/\"version\": \"[^\"]*\"/\"version\": \"'"$VERSION"'\"/g' src-tauri/tauri.conf.json + sed -i.bak 's/^version = \"[^\"]*\"/version = \"'"$VERSION"'\"/g' src-tauri/Cargo.toml + rm -f package.json.bak src-tauri/tauri.conf.json.bak src-tauri/Cargo.toml.bak + echo "Updated version to $VERSION" + + - name: Install frontend dependencies + run: pnpm install --frozen-lockfile + + - name: Generate icons if source exists + shell: bash + run: | + SOURCE_IMG="src-tauri/icons/icon.png" + if [ -f "$SOURCE_IMG" ]; then + pnpm tauri icon "$SOURCE_IMG" + fi + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SCCACHE_GHA_ENABLED: "true" + RUSTC_WRAPPER: sccache + with: + args: --target ${{ matrix.rust_target }} + + - name: Collect install payload + shell: bash + run: | + set -euo pipefail + + OUT_DIR="install/${APP_NAME}-${{ matrix.os_name }}-${{ matrix.arch }}-v${VERSION}" + mkdir -p "$OUT_DIR" + + cp LICENSE* "$OUT_DIR/" 2>/dev/null || true + cp README* "$OUT_DIR/" 2>/dev/null || true + + if [[ "${{ matrix.os_name }}" == "windows" ]]; then + BIN="src-tauri/target/${{ matrix.rust_target }}/release/${BIN_NAME}.exe" + PDB="src-tauri/target/${{ matrix.rust_target }}/release/${BIN_NAME}.pdb" + if [[ ! -f "$BIN" ]]; then + echo "Binary not found: $BIN" + exit 1 + fi + cp "$BIN" "$OUT_DIR/" + [[ -f "$PDB" ]] && cp "$PDB" "$OUT_DIR/" || true + else + BIN="src-tauri/target/${{ matrix.rust_target }}/release/${BIN_NAME}" + if [[ ! -f "$BIN" ]]; then + echo "Binary not found: $BIN" + exit 1 + fi + chmod +x "$BIN" + cp "$BIN" "$OUT_DIR/" + fi + + - name: Package artifact + shell: bash + run: | + set -euo pipefail + mkdir -p dist + + DIR_NAME="${APP_NAME}-${{ matrix.os_name }}-${{ matrix.arch }}-v${VERSION}" + SRC_DIR="install/${DIR_NAME}" + + if [[ "${{ matrix.os_name }}" == "windows" ]]; then + cp -r "$SRC_DIR" "dist/" + else + tar -czf "dist/${DIR_NAME}.tar.gz" -C install "${DIR_NAME}" + fi + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.job }} + path: dist/* + + changelog: + name: Changelog + needs: meta + runs-on: ubuntu-latest + outputs: + content: ${{ steps.git-cliff.outputs.content }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Generate changelog + id: git-cliff + uses: orhun/git-cliff-action@v4 + with: + config: .github/cliff.toml + args: ${{ needs.meta.outputs.is-release == 'true' && '--latest --strip header' || '--unreleased --strip header' }} + env: + GITHUB_REPO: ${{ github.repository }} + + release: + name: Release + needs: [meta, build, changelog] + runs-on: ubuntu-latest + if: needs.meta.outputs.is-release == 'true' + steps: + - uses: actions/checkout@v4 + + - name: Download all build artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: build-* + merge-multiple: false + + - name: Repackage and collect release files + shell: bash + run: | + set -euo pipefail + mkdir -p release_files + + cd artifacts + for root in */; do + [ -d "$root" ] || continue + cd "$root" + + for item in *; do + [ -e "$item" ] || continue + + if [[ -d "$item" && "$item" == *"-windows-"* ]]; then + zip -r "../../release_files/${item}.zip" "$item" + fi + + if [[ -f "$item" && "$item" == *.tar.gz ]]; then + mv "$item" "../../release_files/" + fi + done + + cd .. + done + + - name: Publish GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.meta.outputs.tag }} + name: RustSyncCV ${{ needs.meta.outputs.tag }} + body: ${{ needs.changelog.outputs.content }} + files: release_files/* + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 35fbdc6..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,215 +0,0 @@ -name: Release -on: - push: - tags: - - "v*" - workflow_dispatch: - -permissions: - contents: write - -jobs: - buildapp: - name: Build ${{ matrix.job }} - strategy: - fail-fast: true - matrix: - include: - - job: "win-amd64" - platform: "windows-latest" - os_name: "windows" - arch: "amd64" - args: "" - - job: "mac-arm64" - platform: "macos-latest" - os_name: "macos" - arch: "arm64" - args: "--target aarch64-apple-darwin" - - runs-on: ${{ matrix.platform }} - steps: - - uses: actions/checkout@v4 - - - name: Set and Update Version - shell: bash - run: | - if [[ "${{ github.ref }}" == refs/tags/v* ]]; then - VERSION="${GITHUB_REF#refs/tags/v}" - TAG="${GITHUB_REF#refs/tags/}" - else - VERSION="0.0.0-dev" - TAG="dev" - fi - sed -i.bak 's/"version": "[^"]*"/"version": "'$VERSION'"/g' package.json - sed -i.bak 's/"version": "[^"]*"/"version": "'$VERSION'"/g' src-tauri/tauri.conf.json - sed -i.bak 's/^version = "[^"]*"/version = "'$VERSION'"/g' src-tauri/Cargo.toml - rm -f package.json.bak src-tauri/tauri.conf.json.bak src-tauri/Cargo.toml.bak - - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - version: 9 - - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: "pnpm" - - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin' || '' }} - - - name: Rust cache - uses: swatinem/rust-cache@v2 - with: - workspaces: "src-tauri -> target" - - - name: Install Frontend Dependencies - run: pnpm install - - - name: Fix and Generate Icons - shell: bash - run: | - SOURCE_IMG="src-tauri/icons/icon.png" - if [ -f "$SOURCE_IMG" ]; then - pnpm tauri icon $SOURCE_IMG - fi - - - name: Build Tauri App - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - args: ${{ matrix.args }} - tagName: "" - - - name: Rename and Package Artifacts - shell: bash - run: | - VERSION=$(jq -r .version package.json) - mkdir -p ./final_artifacts - BASE_NAME="RustSyncCV-v${VERSION}-${{ matrix.os_name }}-${{ matrix.arch }}" - - if [[ "${{ matrix.os_name }}" == "windows" ]]; then - EXE_FILE="src-tauri/target/release/rust-sync-cv-client.exe" - 7z a -t7z -mx=9 "./final_artifacts/${BASE_NAME}.7z" "./${EXE_FILE}" - - elif [[ "${{ matrix.os_name }}" == "macos" ]]; then - brew install create-dmg - if [ -f "src-tauri/icons/icon.icns" ]; then - cp src-tauri/icons/icon.icns ./app.icns - else - mkdir -p app.iconset - sips -z 512 512 src-tauri/icons/icon.png --out app.iconset/icon_512x512.png - iconutil -c icns app.iconset -o app.icns - fi - - APP_NAME="RustSyncCV.app" - mkdir -p "$APP_NAME/Contents/MacOS" - mkdir -p "$APP_NAME/Contents/Resources" - BINARY_SOURCE="src-tauri/target/aarch64-apple-darwin/release/rust-sync-cv-client" - if [ ! -f "$BINARY_SOURCE" ]; then - BINARY_SOURCE=$(ls src-tauri/target/release/rust-sync-cv-client 2>/dev/null || ls src-tauri/target/*/release/rust-sync-cv-client | head -n 1) - fi - cp "$BINARY_SOURCE" "$APP_NAME/Contents/MacOS/RustSyncCV" - cp app.icns "$APP_NAME/Contents/Resources/" - - cat > "$APP_NAME/Contents/Info.plist" < - - - - CFBundleExecutable - RustSyncCV - CFBundleIconFile - app.icns - CFBundleIdentifier - com.rustsynccv.app - CFBundleName - RustSyncCV - CFBundlePackageType - APPL - CFBundleShortVersionString - $VERSION - CFBundleVersion - $VERSION - LSMinimumSystemVersion - 10.13 - - - EOF - - chmod +x "$APP_NAME/Contents/MacOS/RustSyncCV" - codesign --force --deep --sign - "$APP_NAME" - - create-dmg \ - --volname "RustSyncCV Installer" \ - --volicon "app.icns" \ - --window-pos 200 120 \ - --window-size 800 400 \ - --icon-size 100 \ - --icon "$APP_NAME" 200 190 \ - --hide-extension "$APP_NAME" \ - --app-drop-link 600 185 \ - "./final_artifacts/${BASE_NAME}.dmg" \ - "$APP_NAME" - fi - - - name: Upload Artifacts - uses: actions/upload-artifact@v4 - with: - name: tauri-build-${{ matrix.job }} - path: ./final_artifacts/* - - publish-release: - needs: buildapp - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get version - id: version - run: | - if [[ "${{ github.ref }}" == refs/tags/v* ]]; then - VERSION="${GITHUB_REF#refs/tags/v}" - else - VERSION="0.0.0-dev" - fi - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Generate Changelog - uses: orhun/git-cliff-action@v4 - id: git-cliff - with: - config: .github/cliff.toml - args: > - ${{ github.event_name == 'workflow_dispatch' - && '--unreleased --strip header' - || '--latest --strip header' }} - env: - GITHUB_REPO: ${{ github.repository }} - - - name: Download All Artifacts - uses: actions/download-artifact@v4 - with: - path: ./artifacts - pattern: tauri-build-* - merge-multiple: true - - - name: Create Final Release - uses: softprops/action-gh-release@v2 - with: - tag_name: v${{ steps.version.outputs.version }} - name: RustSyncCV v${{ steps.version.outputs.version }} - body: ${{ steps.git-cliff.outputs.content }} - files: | - ./artifacts/**/* - draft: false - prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/src-tauri/src/events.rs b/src-tauri/src/events.rs index 6ea5f26..403d647 100644 --- a/src-tauri/src/events.rs +++ b/src-tauri/src/events.rs @@ -5,11 +5,12 @@ use tauri::{AppHandle, Emitter, Manager}; pub fn listen_events( app_handle: AppHandle, state: tauri::State<'_, AppState>, - mut event_rx: tokio::sync::mpsc::Receiver, + event_rx: tokio::sync::mpsc::Receiver, ) { let runtime_clone = state.runtime.clone(); runtime_clone.spawn(async move { + let mut event_rx = event_rx; while let Some(event) = event_rx.recv().await { match &event { RuntimeEvent::Status(text) => { diff --git a/src-tauri/src/tray.rs b/src-tauri/src/tray.rs index 0d4acb0..661c4ee 100644 --- a/src-tauri/src/tray.rs +++ b/src-tauri/src/tray.rs @@ -7,9 +7,12 @@ pub fn setup_tray(app: &AppHandle) -> tauri::Result<()> { let show_i = MenuItem::with_id(app, "show", "Show", true, None::<&str>)?; let menu = Menu::with_items(app, &[&show_i, &quit_i])?; - let _tray = TrayIconBuilder::new() - .menu(&menu) - .icon(app.default_window_icon().unwrap().clone()) + let mut builder = TrayIconBuilder::new().menu(&menu); + if let Some(icon) = app.default_window_icon() { + builder = builder.icon(icon.clone()); + } + + let _tray = builder .on_menu_event(|app: &AppHandle, event| match event.id().as_ref() { "quit" => app.exit(0), "show" => { diff --git a/src/App.tsx b/src/App.tsx index 139c882..5460d77 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -55,7 +55,7 @@ export default function App() { invoke("frontend_log", { level: "error", message: `Failed to load initial state: ${err}`, - }), + }).catch(() => {}), ); const unlistenStatus = listen("status-update", (event) => { @@ -112,7 +112,9 @@ export default function App() { try { await invoke("toggle_pause"); } catch (e) { - invoke("frontend_log", { level: "error", message: String(e) }); + invoke("frontend_log", { level: "error", message: String(e) }).catch( + () => {}, + ); setStatusText(`Error: ${e}`); } }; @@ -121,7 +123,9 @@ export default function App() { try { await invoke("open_log_folder"); } catch (e) { - invoke("frontend_log", { level: "error", message: String(e) }); + invoke("frontend_log", { level: "error", message: String(e) }).catch( + () => {}, + ); } }; @@ -148,14 +152,14 @@ export default function App() { invoke("frontend_log", { level: "error", message: `Failed to create settings window: ${e}`, - }); + }).catch(() => {}); setStatusText(`Failed to create settings window: ${e}`); }); } catch (e) { invoke("frontend_log", { level: "error", message: `Error opening settings window: ${e}`, - }); + }).catch(() => {}); setStatusText(`Error opening settings: ${e}`); } };