diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 668f8b17b..42140a421 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,34 +1,19 @@ # Whenever an open PR is updated, the workflow will be triggered # # This can get expensive, so we do a lot of caching and checks to prevent unnecessary runs +# Language linting (typos, link-check) runs on every push; Rust jobs are gated behind path checks -name: Rust CI +name: CI on: push: branches: - main - paths: - - packages/** - - examples/** - - docs/guide/** - - src/** - - .github/** - - lib.rs - - Cargo.toml - - Makefile.toml pull_request: types: [opened, synchronize, reopened, ready_for_review] branches: - main - paths: - - packages/** - - examples/** - - src/** - - .github/** - - lib.rs - - Cargo.toml # workflow_dispatch: concurrency: @@ -38,110 +23,113 @@ concurrency: env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: 0 # todo(jon): cargo-cache wipes incremental artifacts, but we eventually want to cache them + # CARGO_PROFILE_DEV_PACKAGE_STAR_STRIP: debuginfo # strip debug info from deps to shrink cache RUST_BACKTRACE: 1 rust_nightly: nightly-2025-10-05 jobs: - check-msrv: - if: github.event.pull_request.draft == false - name: Check MSRV - runs-on: blacksmith-4vcpu-ubuntu-2404 + # --------------------------------------------------------------------------- + # Path gate — determines whether Rust jobs should run + # --------------------------------------------------------------------------- + check-paths: + runs-on: warp-ubuntu-latest-x64-4x + outputs: + rust: ${{ steps.filter.outputs.rust }} steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@1.88.0 - - uses: Swatinem/rust-cache@v2 + - uses: dorny/paths-filter@v3 + id: filter with: - cache-all-crates: "true" - # https://github.com/foresterre/cargo-msrv/blob/4345edfe3f4fc91cc8ae6c7d6804c0748fae92ae/.github/workflows/msrv.yml - - name: install_cargo_msrv - run: cargo install cargo-msrv --all-features --version 0.16.3 --locked - - name: version_of_cargo_msrv - run: cargo msrv --version - - name: run_cargo_msrv - run: cargo msrv --output-format json verify -- cargo check - - name: run_cargo_msrv_on_verify_failure - if: ${{ failure() }} - run: cargo msrv --output-format json -- cargo check + filters: | + rust: + - 'packages/**' + - 'examples/**' + - 'docs/guide/**' + - 'src/**' + - '.github/**' + - 'lib.rs' + - 'Cargo.toml' + - 'Makefile.toml' - test: + # --------------------------------------------------------------------------- + # Language linting — runs on every push + # --------------------------------------------------------------------------- + typos: if: github.event.pull_request.draft == false - name: Test Suite - runs-on: blacksmith-4vcpu-ubuntu-2404 + name: "Lint | Typos" + runs-on: warp-ubuntu-latest-x64-4x + steps: + - uses: actions/checkout@v5 + - name: Check for typos + uses: crate-ci/typos@master + + link-check: + if: github.event.pull_request.draft == false + name: "Lint | Links" + runs-on: warp-ubuntu-latest-x64-4x + steps: + - uses: actions/checkout@v5 + - name: Restore lychee cache + uses: actions/cache@v5 + with: + path: .lycheecache + key: cache-lychee-${{ github.sha }} + restore-keys: cache-lychee- + - name: Run lychee + uses: lycheeverse/lychee-action@v2 + with: + args: --config ./lychee.toml './**/*.md' + fail: true + + # --------------------------------------------------------------------------- + # Rust jobs — only run when relevant paths change + # --------------------------------------------------------------------------- + lint: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Lint | Clippy, fmt, schema, msrv" + runs-on: warp-ubuntu-latest-x64-4x steps: - uses: actions/checkout@v5 - - name: Free Disk Space - uses: ./.github/actions/free-disk-space - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev version: 1.0 - - uses: dtolnay/rust-toolchain@1.88.0 + - uses: dtolnay/rust-toolchain@1.94.0 with: components: rustfmt, clippy + - uses: cargo-bins/cargo-binstall@main + - name: Install cargo-msrv + run: cargo binstall cargo-msrv -y --force - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" - - uses: browser-actions/setup-firefox@latest - - run: cargo test --lib --bins --tests --examples --workspace --exclude dioxus-desktop - - release-test: - if: github.event.pull_request.draft == false - name: Test Suite with Optimizations - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v5 - - name: Free Disk Space - uses: ./.github/actions/free-disk-space - - uses: awalsh128/cache-apt-pkgs-action@latest - with: - packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev - version: 1.0 - - uses: dtolnay/rust-toolchain@1.88.0 - with: - components: rustfmt, clippy - - uses: Swatinem/rust-cache@v2 - with: - cache-all-crates: "true" - - uses: browser-actions/setup-firefox@latest - - run: cargo test --lib --bins --tests --examples --workspace --exclude dioxus-desktop --profile release-unoptimized - - fmt: - if: github.event.pull_request.draft == false - name: Rustfmt - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@1.88.0 - with: - components: rustfmt - - uses: Swatinem/rust-cache@v2 - with: - cache-all-crates: "true" - - run: cargo fmt --all -- --check - - schema: - if: github.event.pull_request.draft == false - name: Check Schema - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@1.88.0 - - uses: Swatinem/rust-cache@v2 - with: - cache-all-crates: "true" - - name: Generate schema + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} + - name: Rustfmt + run: cargo fmt --all -- --check + - name: Clippy + run: cargo clippy --workspace --examples --tests --all-features --all-targets -- -D warnings + - name: Check schema run: cargo run -p dioxus-cli -- config schema --out packages/cli/schema.json - - name: Check for uncommitted changes + - name: Verify schema is up to date run: | if ! git diff --exit-code packages/cli/schema.json; then echo "::error::Schema is out of date. Run 'dx config schema --out packages/cli/schema.json' and commit the changes." exit 1 fi + - name: Verify MSRV + run: cargo msrv --output-format json verify -- cargo check + - name: Find MSRV on verify failure + if: ${{ failure() }} + run: cargo msrv --output-format json -- cargo check docs: - if: github.event.pull_request.draft == false - name: Docs - runs-on: blacksmith-4vcpu-ubuntu-2404 + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Docs | Build" + runs-on: warp-ubuntu-latest-x64-4x steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest @@ -155,6 +143,9 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} - name: "doc --lib --all-features" run: | cargo doc --workspace --no-deps --all-features --document-private-items @@ -162,9 +153,10 @@ jobs: RUSTDOCFLAGS: -Dwarnings --document-private-items test-docs: - if: github.event.pull_request.draft == false - name: Test Docs - runs-on: blacksmith-4vcpu-ubuntu-2404 + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Docs | Test" + runs-on: warp-ubuntu-latest-x64-4x steps: - uses: actions/checkout@v5 - uses: awalsh128/cache-apt-pkgs-action@latest @@ -178,83 +170,213 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} - name: "doc --lib --all-features" run: | cargo test --doc --workspace --all-features - check: - if: github.event.pull_request.draft == false - name: Check - runs-on: blacksmith-4vcpu-ubuntu-2404 + bundle-linux-smoke: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Bundle | Linux" + runs-on: warp-ubuntu-latest-x64-4x + timeout-minutes: 45 steps: - uses: actions/checkout@v5 + - name: Free Disk Space + uses: ./.github/actions/free-disk-space - uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev + packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev patchelf desktop-file-utils librsvg2-dev version: 1.0 - uses: dtolnay/rust-toolchain@1.88.0 - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" - - run: cargo check --workspace --all-features --all-targets + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} + - name: Bundle hotdog as Linux packages + working-directory: ./examples/01-app-demos/hotdog + run: cargo run --manifest-path ../../../Cargo.toml -p dioxus-cli -- bundle --platform desktop --package-types appimage --package-types deb --package-types rpm + - name: Verify Linux bundle outputs + run: | + find target -name '*.AppImage' -print + find target -name '*.deb' -print + find target -name '*.rpm' -print + test -n "$(find target -name '*.AppImage' -print -quit)" + test -n "$(find target -name '*.deb' -print -quit)" + test -n "$(find target -name '*.rpm' -print -quit)" + - uses: actions/upload-artifact@v6 + if: always() + with: + name: hotdog-linux-bundles + path: | + target/**/*.AppImage + target/**/*.deb + target/**/*.rpm + if-no-files-found: ignore - clippy: - if: github.event.pull_request.draft == false - name: Clippy - runs-on: blacksmith-4vcpu-ubuntu-2404 + bundle-macos-smoke: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Bundle | macOS" + runs-on: warp-macos-latest-arm64-6x + timeout-minutes: 45 steps: - uses: actions/checkout@v5 - - uses: awalsh128/cache-apt-pkgs-action@latest + - uses: dtolnay/rust-toolchain@1.88.0 + - uses: Swatinem/rust-cache@v2 with: - packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev - version: 1.0 - - uses: dtolnay/rust-toolchain@1.90.0 + cache-all-crates: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} + - name: Bundle hotdog as macOS packages + working-directory: ./examples/01-app-demos/hotdog + run: cargo run --manifest-path ../../../Cargo.toml -p dioxus-cli -- bundle --platform desktop --package-types macos --package-types dmg + - name: Verify macOS bundle outputs + run: | + find target -type d -name '*.app' -print + find target -type f -name '*.dmg' -print + test -n "$(find target -type d -name '*.app' -print -quit)" + test -n "$(find target -type f -name '*.dmg' -print -quit)" + - uses: actions/upload-artifact@v6 + if: always() + with: + name: hotdog-macos-bundles + path: | + target/**/*.app + target/**/*.dmg + if-no-files-found: ignore + + windows-bundle-smoke: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Bundle | Windows" + runs-on: warp-windows-2025-x64-8x + timeout-minutes: 45 + steps: + - uses: actions/checkout@v5 + - name: Setup Dev Drive + run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1 + - name: Copy Repo to Dev Drive + run: Copy-Item -Path "${{ github.workspace }}" -Destination "$env:DIOXUS_WORKSPACE" -Recurse + - name: Install Rust + uses: dtolnay/rust-toolchain@1.88.0 with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 with: + key: "windows-bundle-${{ runner.os }}" cache-all-crates: "true" - - run: cargo clippy --workspace --examples --tests --all-features --all-targets -- -D warnings + cache-workspace-crates: "true" + cache-provider: "warpbuild" + workspaces: ${{ env.DIOXUS_WORKSPACE }} + - name: Bundle hotdog as Windows packages + working-directory: ${{ env.DIOXUS_WORKSPACE }}/examples/01-app-demos/hotdog + run: cargo run --release --manifest-path ..\..\..\Cargo.toml -p dioxus-cli -- bundle --platform desktop --package-types msi --package-types nsis + - name: Verify Windows bundle outputs + working-directory: ${{ env.DIOXUS_WORKSPACE }} + shell: pwsh + run: | + $msi = Get-ChildItem -Path .\target\dx -Recurse -File -Include "*.msi" + $nsis = Get-ChildItem -Path .\target\dx -Recurse -File -Include "*-setup.exe" + $msi | ForEach-Object { $_.FullName } + $nsis | ForEach-Object { $_.FullName } + if (-not $msi) { throw "Expected MSI artifact was not produced." } + if (-not $nsis) { throw "Expected NSIS artifact was not produced." } + - name: Target dir size by extension + shell: pwsh + run: | + Get-ChildItem -Path "$env:DIOXUS_WORKSPACE\target" -Recurse -File | + Group-Object Extension | + Sort-Object { ($_.Group | Measure-Object Length -Sum).Sum } -Descending | + Select-Object -First 20 @{N='Ext';E={$_.Name}}, Count, @{N='SizeMB';E={[math]::Round(($_.Group | Measure-Object Length -Sum).Sum / 1MB, 1)}} | + Format-Table -AutoSize + # - name: Clean Windows artifacts before cache save + # shell: pwsh + # run: | + # Get-ChildItem -Path "$env:DIOXUS_WORKSPACE\target" -Recurse -Include "*.pdb","*.exe","*.dll" | Remove-Item -Force + - uses: actions/upload-artifact@v6 + if: always() + with: + name: hotdog-windows-bundles-smoke + path: | + ${{ env.DIOXUS_WORKSPACE }}/target/**/*.msi + ${{ env.DIOXUS_WORKSPACE }}/target/**/*-setup.exe + if-no-files-found: ignore - nix: - if: github.event.pull_request.draft == false - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [blacksmith-4vcpu-ubuntu-2404, macos-latest] + windows: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Playwright | Windows" + runs-on: warp-windows-2025-x64-8x + timeout-minutes: 60 steps: - uses: actions/checkout@v5 - - uses: nixbuild/nix-quick-install-action@master - - uses: nix-community/cache-nix-action@main + - name: Setup Dev Drive + run: ${{ github.workspace }}/.github/workflows/setup-dev-drive.ps1 + - name: Copy Repo to Dev Drive + run: Copy-Item -Path "${{ github.workspace }}" -Destination "$env:DIOXUS_WORKSPACE" -Recurse + - uses: actions/setup-node@v4 with: - primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} - restore-prefixes-first-match: nix-${{ runner.os }}-${{ runner.arch }} - - name: Install omnix - run: nix --accept-flake-config profile install "github:juspay/omnix" - - name: Build all flake outputs - run: om ci - - name: Ensure devShell has all build deps - run: nix develop -c cargo build -p dioxus-cli --features no-downloads + node-version: 24 + - name: Install Rust + uses: dtolnay/rust-toolchain@1.88.0 + with: + targets: wasm32-unknown-unknown + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + workspaces: ${{ env.DIOXUS_WORKSPACE }} + # save-if: ${{ github.ref == 'refs/heads/main' }} + - name: Playwright + working-directory: ${{ env.DIOXUS_WORKSPACE }}/packages/playwright-tests + env: + CARGO_INCREMENTAL: 1 + run: | + npm ci + npm install -D @playwright/test + npx playwright install + npx playwright test + # - name: Clean Windows artifacts before cache save + # if: always() + # shell: pwsh + # run: | + # Get-ChildItem -Path "$env:DIOXUS_WORKSPACE\target" -Recurse -Include "*.pdb","*.exe","*.dll" | Remove-Item -Force + - uses: actions/upload-artifact@v6 + if: always() + with: + name: playwright-report-windows + path: ${{ env.DIOXUS_WORKSPACE }}/packages/playwright-tests/playwright-report/ + retention-days: 30 + playwright: - if: github.event.pull_request.draft == false - name: Playwright Tests + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Playwright | ${{ matrix.name }}" runs-on: ${{ matrix.os }} timeout-minutes: 45 strategy: matrix: - os: [blacksmith-4vcpu-windows-2025, blacksmith-8vcpu-ubuntu-2404] - platform: - - { toolchain: beta } - # - { toolchain: 1.86.0 } + include: + - { os: warp-ubuntu-latest-x64-8x, name: "Linux (stable)", toolchain: "1.88.0" } + - { os: warp-ubuntu-latest-x64-8x, name: "Linux (beta)", toolchain: beta } steps: # Do our best to cache the toolchain and node install steps - uses: actions/checkout@v5 - name: Free Disk Space - if: ${{ matrix.os == 'ubuntu-24.04' || matrix.os == 'ubuntu-24.04-arm' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404-arm' || matrix.os == 'blacksmith-8vcpu-ubuntu-2404' }} + if: runner.os == 'Linux' uses: ./.github/actions/free-disk-space - uses: awalsh128/cache-apt-pkgs-action@latest - if: ${{ matrix.os == 'ubuntu-24.04' || matrix.os == 'ubuntu-24.04-arm' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404-arm' || matrix.os == 'blacksmith-8vcpu-ubuntu-2404' }} + if: runner.os == 'Linux' with: packages: glib-networking glib-networking-common glib-networking-services libaa1 libabsl20220623t64 libass9 libasyncns0 libavc1394-0 libavcodec60 libavfilter9 libavformat60 libavtp0 libavutil58 libblas3 libbluray2 libbs2b0 libcaca0 libcairo-script-interpreter2 libcdparanoia0 libchromaprint1 libcjson1 libcodec2-1.2 libdav1d7 libdc1394-25 libdca0 libdecor-0-0 libdirectfb-1.7-7t64 libdv4t64 libdvdnav4 libdvdread8t64 libfaad2 libflac12t64 libfluidsynth3 libfreeaptx0 libgav1-1 libgme0 libgraphene-1.0-0 libgsm1 libgssdp-1.6-0 libgstreamer-plugins-good1.0-0 libgtk-4-common libgupnp-1.6-0 libgupnp-igd-1.6-0 libhwy1t64 libiec61883-0 libimath-3-1-29t64 libinstpatch-1.0-2 libjack-jackd2-0 libjxl0.7 liblapack3 liblc3-1 libldacbt-enc2 liblilv-0-0 liblrdf0 libltc11 libmbedcrypto7t64 libmfx1 libmjpegutils-2.1-0t64 libmodplug1 libmp3lame0 libmpcdec6 libmpeg2encpp-2.1-0t64 libmpg123-0t64 libmplex2-2.1-0t64 libmysofa1 libneon27t64 libnice10 libogg0 libopenal-data libopenal1 libopenexr-3-1-30 libopenh264-7 libopenmpt0t64 libopenni2-0 liborc-0.4-0t64 libpipewire-0.3-0t64 libplacebo338 libpocketsphinx3 libpostproc57 libproxy1v5 libpulse0 libqrencode4 libraptor2-0 librav1e0 libraw1394-11 librist4 librubberband2 libsamplerate0 libsbc1 libsdl2-2.0-0 libsecret-common libserd-0-0 libshine3 libshout3 libsndfile1 libsndio7.0 libsord-0-0 libsoundtouch1 libsoup-3.0-0 libsoup-3.0-common libsoxr0 libspa-0.2-modules libspandsp2t64 libspeex1 libsphinxbase3t64 libsratom-0-0 libsrt1.5-gnutls libsrtp2-1 libssh-gcrypt-4 libsvtav1enc1d1 libswresample4 libswscale7 libtag1v5 libtag1v5-vanilla libtheora0 libtwolame0 libudfread0 libunibreak5 libv4l-0t64 libv4lconvert0t64 libva-drm2 libva-x11-2 libva2 libvdpau1 libvidstab1.1 libvisual-0.4-0 libvo-aacenc0 libvo-amrwbenc0 libvorbis0a libvorbisenc2 libvorbisfile3 libvpl2 libwavpack1 libwebrtc-audio-processing1 libwildmidi2 libx265-199 libxcb-xkb1 libxkbcommon-x11-0 libcups2t64 libxml2 libxml2-dev libxvidcore4 libyuv0 libzbar0t64 libzimg2 libzix-0-0 libzvbi-common libzvbi0t64 libzxing3 ocl-icd-libopencl1 timgm6mb-soundfont xfonts-encodings xfonts-utils binutils lld binutils-devel binutils-gold fonts-freefont-ttf fonts-ipafont-gothic fonts-tlwg-loma-otf fonts-unifont fonts-wqy-zenhei gstreamer1.0-libav gstreamer1.0-plugins-bad gstreamer1.0-plugins-good libavif16 libevent-2.1-7t64 libgstreamer-plugins-bad1.0-0 libharfbuzz-icu0 libhyphen0 libmanette-0.2-0 libsecret-1-0 libwoff1 xfonts-cyrillic xfonts-scalable fonts-ipafont-mincho fonts-tlwg-loma gstreamer1.0-x version: 1.0 @@ -264,15 +386,17 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@master with: - toolchain: ${{ matrix.platform.toolchain }} + toolchain: ${{ matrix.toolchain }} targets: x86_64-unknown-linux-gnu,wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 with: - key: "playwright-${{ matrix.platform.toolchain }}-${{ runner.os }}" + key: "playwright-${{ matrix.toolchain }}-${{ runner.os }}" cache-all-crates: "true" - cache-on-failure: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} - name: Wipe dx cache - if: ${{ matrix.os == 'ubuntu-24.04' || matrix.os == 'ubuntu-24.04-arm' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.os == 'blacksmith-4vcpu-ubuntu-2404-arm' || matrix.os == 'blacksmith-8vcpu-ubuntu-2404' }} + if: runner.os == 'Linux' run: | rm -rf ./target/dx - name: Playwright @@ -288,13 +412,67 @@ jobs: - uses: actions/upload-artifact@v6 if: always() with: - name: playwright-report-${{ matrix.platform.toolchain }}-${{ runner.os }} + name: playwright-report-${{ matrix.toolchain }}-${{ runner.os }} path: ./packages/playwright-tests/playwright-report/ retention-days: 30 + + + test: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Test | Linux" + runs-on: warp-ubuntu-latest-x64-8x + steps: + - uses: actions/checkout@v5 + - name: Free Disk Space + uses: ./.github/actions/free-disk-space + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev + version: 1.0 + - uses: dtolnay/rust-toolchain@1.88.0 + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} + - uses: browser-actions/setup-firefox@latest + - run: cargo test --lib --bins --tests --examples --workspace --exclude dioxus-desktop + + release-test: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Test | Linux (Release)" + runs-on: warp-ubuntu-latest-x64-8x + steps: + - uses: actions/checkout@v5 + - name: Free Disk Space + uses: ./.github/actions/free-disk-space + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev + version: 1.0 + - uses: dtolnay/rust-toolchain@1.88.0 + with: + components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + with: + cache-all-crates: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} + - uses: browser-actions/setup-firefox@latest + - run: cargo test --lib --bins --tests --examples --workspace --exclude dioxus-desktop --profile release-unoptimized + matrix_test: + needs: check-paths runs-on: ${{ matrix.platform.os }} - if: github.event.pull_request.draft == false + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Cross | ${{ matrix.platform.target }}" env: RUST_CARGO_COMMAND: ${{ matrix.platform.cross == true && 'cross' || 'cargo' }} strategy: @@ -302,7 +480,7 @@ jobs: platform: - { target: aarch64-apple-darwin, - os: macos-latest, + os: warp-macos-latest-arm64-6x, toolchain: "1.88.0", cross: false, command: "test", @@ -311,7 +489,7 @@ jobs: } - { target: aarch64-apple-ios, - os: macos-latest, + os: warp-macos-latest-arm64-6x, toolchain: "1.88.0", cross: false, command: "build", @@ -320,7 +498,7 @@ jobs: } - { target: aarch64-unknown-linux-gnu, - os: ubuntu-24.04-arm, + os: warp-ubuntu-latest-arm64-4x, toolchain: "1.88.0", cross: false, command: "build", @@ -329,14 +507,14 @@ jobs: } - { target: aarch64-linux-android, - os: blacksmith-4vcpu-ubuntu-2404, + os: warp-ubuntu-latest-x64-4x, toolchain: "1.88.0", cross: true, command: "build", args: "--package dioxus-desktop", platform: "android", } - # commented out because it's having issues with space on the device, but we already test it above + # commented out because it's having issues with space on the device, but we already test it above # - { # target: x86_64-unknown-linux-gnu, # os: ubuntu-24.04, @@ -350,15 +528,31 @@ jobs: steps: - uses: actions/checkout@v5 - name: Free Disk Space - if: ${{ matrix.platform.os == 'ubuntu-24.04' || matrix.platform.os == 'ubuntu-24.04-arm' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404-arm' }} + if: runner.os == 'Linux' uses: ./.github/actions/free-disk-space - uses: awalsh128/cache-apt-pkgs-action@latest - if: ${{ matrix.platform.os == 'ubuntu-24.04' || matrix.platform.os == 'ubuntu-24.04-arm' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404' || matrix.platform.os == 'blacksmith-4vcpu-ubuntu-2404-arm' }} + if: ${{ runner.os == 'Linux' && matrix.platform.target != 'aarch64-unknown-linux-gnu' }} with: - packages: libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev + packages: pkg-config libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev version: ${{ matrix.platform.target }}-${{ matrix.platform.os }} # disambiguate since we're in a matrix and this caching action doesn't factor in these variables + - name: Install Linux desktop dependencies for ARM + if: ${{ matrix.platform.target == 'aarch64-unknown-linux-gnu' }} + run: | + sudo apt-get update + sudo apt-get install -y pkg-config libwebkit2gtk-4.1-dev libgtk-3-dev libasound2-dev libudev-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev + + - name: Verify GLib pkg-config metadata on ARM Linux + if: ${{ matrix.platform.target == 'aarch64-unknown-linux-gnu' }} + run: pkg-config --modversion glib-2.0 + + - name: Configure pkg-config for ARM Linux desktop deps + if: ${{ matrix.platform.target == 'aarch64-unknown-linux-gnu' }} + run: | + echo "PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV" + echo "PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig" >> "$GITHUB_ENV" + - name: install stable uses: dtolnay/rust-toolchain@master with: @@ -366,7 +560,7 @@ jobs: targets: ${{ matrix.platform.target }} components: rustfmt - - name: Install nasm for windows (tls) + - name: Install nasm for windows (tls)1 if: ${{ matrix.platform.target == 'x86_64-pc-windows-msvc' }} uses: ilammy/setup-nasm@v1 @@ -378,33 +572,45 @@ jobs: with: key: "matrix-${{ matrix.platform.target }}" cache-all-crates: "true" + cache-workspace-crates: "true" + cache-provider: "warpbuild" + # save-if: ${{ github.ref == 'refs/heads/main' }} - name: test run: | ${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }} - # borrowed from uv - # https://raw.githubusercontent.com/astral-sh/uv/refs/heads/main/.github/workflows/ci.yml - cargo-test-windows: - if: github.event.pull_request.draft == false - runs-on: - labels: "blacksmith-4vcpu-windows-2025" - name: "cargo test | windows" + nix: + needs: check-paths + if: github.event.pull_request.draft == false && needs.check-paths.outputs.rust == 'true' + name: "Nix | ${{ matrix.name }}" + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - { os: warp-ubuntu-latest-x64-8x, name: Linux } + - { os: warp-macos-latest-arm64-6x, name: macOS } steps: - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@1.88.0 + - uses: nixbuild/nix-quick-install-action@master + - uses: jkelleyrtp/cache-nix-action@jk/use-warpbuild with: - components: rustfmt, clippy - - uses: Swatinem/rust-cache@v2 - with: - workspaces: ${{ env.UV_WORKSPACE }} - cache-all-crates: "true" - - - name: "Install Rust toolchain" - working-directory: ${{ env.UV_WORKSPACE }} - run: rustup show - - - name: "Cargo test" - working-directory: ${{ env.UV_WORKSPACE }} + primary-key: nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('flake.nix', 'flake.lock') }}-${{ hashFiles('Cargo.lock') }} + restore-prefixes-first-match: | + nix-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('flake.nix', 'flake.lock') }}- + nix-${{ runner.os }}-${{ runner.arch }}- + backend: warpbuild + - name: Build dioxus-cli from the flake + run: nix build .#dioxus-cli + - name: Smoke test devShell + shell: bash run: | - cargo test --workspace --tests + nix develop .#default -c bash -lc ' + cargo --version + rustc --version + pkg-config --modversion openssl + if [[ "${{ runner.os }}" == "Linux" ]]; then + pkg-config --modversion gtk+-3.0 + pkg-config --modversion webkit2gtk-4.1 + fi + ' diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index d89b3c2d9..7d0fcabb4 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -23,7 +23,7 @@ jobs: # Attempt to backport a merged pull request to the latest stable release backport: name: Backport pull request - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: warp-ubuntu-latest-x64-4x # Don't run on closed unmerged pull requests, or pull requests with the "breaking" label if: github.event.pull_request.merged && !contains(github.event.pull_request.labels.*.name, 'breaking') @@ -34,7 +34,7 @@ jobs: # Upload nightly docs to the website docs: - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: warp-ubuntu-latest-x64-4x permissions: contents: write steps: @@ -47,6 +47,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-all-crates: "true" + cache-provider: "warpbuild" save-if: ${{ github.ref == 'refs/heads/main' }} - name: cargo doc diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index 627c11e5a..dcf36c794 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -22,7 +22,7 @@ permissions: jobs: promote: - runs-on: blacksmith-4vcpu-ubuntu-2404 + runs-on: warp-ubuntu-latest-x64-4x steps: - uses: actions/checkout@v5 - name: Publish the next pre-release diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1dc98bd0d..951c9d523 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -84,11 +84,21 @@ jobs: uses: ./.github/actions/free-disk-space - uses: awalsh128/cache-apt-pkgs-action@latest - if: ${{ matrix.platform.os == 'ubuntu-24.04' || matrix.platform.os == 'ubuntu-24.04-arm' }} + if: ${{ matrix.platform.os == 'ubuntu-24.04' }} with: - packages: libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev musl-tools + packages: pkg-config libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev musl-tools version: 1.0 + - name: Install Linux desktop dependencies for ARM + if: ${{ matrix.platform.os == 'ubuntu-24.04-arm' }} + run: | + sudo apt-get update + sudo apt-get install -y pkg-config libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev libxdo-dev libglib2.0-dev musl-tools + + - name: Verify GLib pkg-config metadata on ARM Linux + if: ${{ matrix.platform.os == 'ubuntu-24.04-arm' }} + run: pkg-config --modversion glib-2.0 + - name: Install stable uses: dtolnay/rust-toolchain@master with: diff --git a/.github/workflows/setup-dev-drive.ps1 b/.github/workflows/setup-dev-drive.ps1 index a4fc3c3a6..0bfc849da 100644 --- a/.github/workflows/setup-dev-drive.ps1 +++ b/.github/workflows/setup-dev-drive.ps1 @@ -1,26 +1,74 @@ -# This creates a 20GB dev drive, and exports all required environment -# variables so that rustup, uv and others all use the dev drive as much -# as possible. -$Volume = New-VHD -Path C:/uv_dev_drive.vhdx -SizeBytes 20GB | - Mount-VHD -Passthru | - Initialize-Disk -Passthru | - New-Partition -AssignDriveLetter -UseMaximumSize | - Format-Volume -FileSystem ReFS -Confirm:$false -Force +# Configures a dev drive for Windows CI to speed up Rust builds. +# +# The main performance win is disabling Windows Defender antivirus scanning +# on the build drive — AV scanning every .o, .rlib, .rmeta, and .exe during +# a Rust build is extremely expensive. +# +# Three code paths: +# 1. D: drive exists (some runners) — use it directly +# 2. Hyper-V available — create a Dev Drive VHD (best perf, supports fsutil devdrv) +# 3. No Hyper-V (Warp/EC2 runners) — use diskpart + ReFS format -Write-Output $Volume +if (Test-Path "D:\") { + Write-Output "Using existing drive at D:" + $Drive = "D:" +} elseif (Get-Command New-VHD -ErrorAction SilentlyContinue) { + # Hyper-V is available — create a proper Dev Drive + $Volume = New-VHD -Path C:/dev_drive.vhdx -SizeBytes 25GB | + Mount-VHD -Passthru | + Initialize-Disk -Passthru | + New-Partition -AssignDriveLetter -UseMaximumSize | + Format-Volume -DevDrive -Confirm:$false -Force -$Drive = "$($Volume.DriveLetter):" -$Tmp = "$($Drive)/uv-tmp" + $Drive = "$($Volume.DriveLetter):" -# Create the directory ahead of time in an attempt to avoid race-conditions -New-Item $Tmp -ItemType Directory + # Mark as trusted and disable antivirus filtering + fsutil devdrv trust $Drive + fsutil devdrv enable /disallowAv + + # Remount so the changes take effect + Dismount-VHD -Path C:/dev_drive.vhdx + Mount-VHD -Path C:/dev_drive.vhdx + + Write-Output $Volume + fsutil devdrv query $Drive + Write-Output "Created Dev Drive at $Drive" +} else { + # No Hyper-V — fall back to diskpart + ReFS + Write-Output "No Hyper-V detected, creating ReFS drive via diskpart..." + + $vhdPath = "C:\dev_drive.vhdx" + @" +create vdisk file="$vhdPath" maximum=25600 type=expandable +attach vdisk +create partition primary +active +assign letter=V +"@ | diskpart + + format V: /fs:ReFS /q /y + $Drive = "V:" + + Write-Output "Created ReFS drive at $Drive" +} + +$Tmp = "$($Drive)\tmp" + +# Create directories ahead of time to avoid race conditions +New-Item $Tmp -ItemType Directory -Force + +# Move Cargo to the dev drive so registry/crate downloads also benefit +New-Item -Path "$($Drive)/.cargo/bin" -ItemType Directory -Force +if (Test-Path "C:/Users/runneradmin/.cargo") { + Copy-Item -Path "C:/Users/runneradmin/.cargo/*" -Destination "$($Drive)/.cargo/" -Recurse -Force +} Write-Output ` - "DEV_DRIVE=$($Drive)" ` - "TMP=$($Tmp)" ` - "TEMP=$($Tmp)" ` - "RUSTUP_HOME=$($Drive)/.rustup" ` - "CARGO_HOME=$($Drive)/.cargo" ` - "UV_WORKSPACE=$($Drive)/uv" ` - "PATH=$($Drive)/.cargo/bin;$env:PATH" ` - >> $env:GITHUB_ENV \ No newline at end of file + "DEV_DRIVE=$($Drive)" ` + "TMP=$($Tmp)" ` + "TEMP=$($Tmp)" ` + "RUSTUP_HOME=$($Drive)/.rustup" ` + "CARGO_HOME=$($Drive)/.cargo" ` + "DIOXUS_WORKSPACE=$($Drive)/dioxus" ` + "PATH=$($Drive)/.cargo/bin;$env:PATH" ` + >> $env:GITHUB_ENV diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml deleted file mode 100644 index 2c6585fa7..000000000 --- a/.github/workflows/typos.yml +++ /dev/null @@ -1,42 +0,0 @@ -# Whenever an open PR is updated, the workflow will be triggered - -name: Language Linting - -on: - push: - branches: - - main - - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: - - main - -jobs: - typos: - if: github.event.pull_request.draft == false - name: Check for typos - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v5 - - name: Check for typos - uses: crate-ci/typos@master - - # Check for invalid links in the repository - link-check: - if: github.event.pull_request.draft == false - name: Check For Invalid Links - runs-on: blacksmith-4vcpu-ubuntu-2404 - steps: - - uses: actions/checkout@v5 - - name: Restore lychee cache - uses: actions/cache@v5 - with: - path: .lycheecache - key: cache-lychee-${{ github.sha }} - restore-keys: cache-lychee- - - name: Run lychee - uses: lycheeverse/lychee-action@v2 - with: - args: --config ./lychee.toml './**/*.md' - fail: true diff --git a/Cargo.lock b/Cargo.lock index 8546e58cb..6f864435c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -217,15 +217,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "aes-kw" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" -dependencies = [ - "aes", -] - [[package]] name = "ahash" version = "0.7.8" @@ -313,7 +304,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "libc", ] @@ -335,11 +326,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.9.4", + "bitflags 2.11.0", "cc", "cesu8", "jni 0.21.1", - "jni-sys", + "jni-sys 0.3.0", "libc", "log", "ndk 0.9.0", @@ -406,7 +397,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -416,9 +422,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -429,6 +435,15 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.5" @@ -451,9 +466,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "anymap2" @@ -527,142 +542,6 @@ dependencies = [ "serde", ] -[[package]] -name = "apple-bundles" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb7c27ee2ca7826adfdc84228cd4c5a84ab57b0a11d269d1d7cd0615238e5a2" -dependencies = [ - "anyhow", - "plist", - "simple-file-manifest", - "walkdir", -] - -[[package]] -name = "apple-codesign" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329820aac7259ca0529d3cc21dd3b4c11651225dfce9e0ce25b121b23f923164" -dependencies = [ - "anyhow", - "apple-bundles", - "apple-flat-package", - "apple-xar", - "base64 0.21.7", - "bcder", - "bitflags 2.9.4", - "bytes", - "chrono", - "clap", - "cryptographic-message-syntax", - "der", - "dialoguer", - "difference", - "digest", - "dirs 5.0.1", - "elliptic-curve", - "env_logger 0.10.2", - "figment", - "filetime", - "glob", - "goblin", - "hex", - "log", - "md-5", - "minicbor", - "num-traits", - "object 0.32.2", - "oid-registry", - "once_cell", - "p12", - "p256", - "pem", - "pkcs1", - "pkcs8", - "plist", - "rand 0.8.5", - "rasn", - "rayon", - "regex", - "reqwest 0.11.27", - "ring", - "rsa", - "scroll 0.12.0", - "security-framework 2.11.1", - "security-framework-sys", - "semver", - "serde", - "serde_json", - "serde_yaml", - "sha2", - "signature", - "simple-file-manifest", - "spake2", - "spki", - "subtle", - "tempfile", - "thiserror 1.0.69", - "tokio", - "tungstenite 0.21.0", - "uuid", - "walkdir", - "widestring", - "windows-sys 0.52.0", - "x509", - "x509-certificate", - "xml-rs", - "yasna", - "zeroize", - "zip 0.6.6", - "zip_structs", -] - -[[package]] -name = "apple-flat-package" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6adc520e05304de5ec383487786fa20e9c636fe972e59719cdd93621a2db6f1" -dependencies = [ - "apple-xar", - "cpio-archive", - "flate2", - "scroll 0.12.0", - "serde", - "serde-xml-rs 0.6.0", - "thiserror 1.0.69", -] - -[[package]] -name = "apple-xar" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "844e00dc1e665b3cf0bba745aa9c6464292ca512db0c11384511586701eb0335" -dependencies = [ - "base64 0.21.7", - "bcder", - "bzip2", - "chrono", - "cryptographic-message-syntax", - "digest", - "flate2", - "log", - "md-5", - "rand 0.8.5", - "reqwest 0.11.27", - "scroll 0.12.0", - "serde", - "serde-xml-rs 0.6.0", - "sha1", - "sha2", - "signature", - "thiserror 1.0.69", - "url", - "x509-certificate", - "xml-rs", - "xz2", -] - [[package]] name = "approx" version = "0.5.1" @@ -678,15 +557,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" -[[package]] -name = "ar_archive_writer" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" -dependencies = [ - "object 0.37.3", -] - [[package]] name = "arbitrary" version = "1.4.2" @@ -704,7 +574,7 @@ checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" dependencies = [ "clipboard-win", "log", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "parking_lot", @@ -715,9 +585,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" dependencies = [ "rustversion", ] @@ -730,20 +600,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", -] - -[[package]] -name = "argon2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", - "zeroize", + "syn 2.0.117", ] [[package]] @@ -773,12 +630,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - [[package]] name = "ash" version = "0.37.3+1.3.251" @@ -803,44 +654,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3df27b8d5ddb458c5fb1bbc1ce172d4a38c614a97d550b0ac89003897fb01de4" -[[package]] -name = "asn1-rs" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom 7.1.3", - "num-traits", - "rusticata-macros", - "thiserror 1.0.69", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure 0.12.6", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "assert_type_match" version = "0.1.1" @@ -849,18 +662,7 @@ checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", -] - -[[package]] -name = "ast_node" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a184645bcc6f52d69d8e7639720699c6a99efb711f886e251ed1d16db8dd90e" -dependencies = [ - "quote", - "swc_macros_common", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -900,9 +702,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82da0ea54ea533ec09d949717c6386a1c34f2d9b51c1fcc7eef8b9ce0b690a3e" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ "compression-codecs", "compression-core", @@ -913,9 +715,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -964,7 +766,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 1.1.3", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -1001,7 +803,7 @@ dependencies = [ "cfg-if", "event-listener 5.4.1", "futures-lite", - "rustix 1.1.3", + "rustix 1.1.4", ] [[package]] @@ -1012,7 +814,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1027,7 +829,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 1.1.3", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -1078,7 +880,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1098,7 +900,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1149,15 +951,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" -dependencies = [ - "bytemuck", -] - [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -1249,22 +1042,11 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4888bf91cce63baf1670512d0f12b5d636179a4abbad6504812ac8ab124b3efe" dependencies = [ - "dirs 6.0.0", + "dirs", "git2", "terminal-prompt", ] -[[package]] -name = "auto_impl" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "autocfg" version = "1.5.0" @@ -1287,35 +1069,13 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" +checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" dependencies = [ "arrayvec", ] -[[package]] -name = "aws-lc-rs" -version = "1.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - [[package]] name = "axum" version = "0.7.9" @@ -1326,8 +1086,8 @@ dependencies = [ "axum-core 0.4.5", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "itoa", "matchit 0.7.3", @@ -1337,7 +1097,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower 0.5.3", "tower-layer", "tower-service", @@ -1355,10 +1115,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "itoa", "matchit 0.8.4", @@ -1372,9 +1132,9 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", - "tokio-tungstenite 0.28.0", + "tokio-tungstenite", "tower 0.5.3", "tower-layer", "tower-service", @@ -1390,13 +1150,13 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", ] @@ -1409,12 +1169,12 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -1431,8 +1191,8 @@ dependencies = [ "bytes", "futures-util", "headers", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -1451,7 +1211,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1463,16 +1223,16 @@ dependencies = [ "arc-swap", "bytes", "fs-err", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http", + "http-body", + "hyper", "hyper-util", "pin-project-lite", - "rustls 0.23.36", - "rustls-pemfile 2.2.0", + "rustls", + "rustls-pemfile", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", ] @@ -1493,8 +1253,8 @@ dependencies = [ "forwarded-header-value", "futures", "hmac", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "rand 0.8.5", "serde", "serde_json", @@ -1522,8 +1282,8 @@ dependencies = [ "chrono", "dashmap 6.1.0", "futures", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "serde", "tokio", "tower-layer", @@ -1553,7 +1313,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object 0.37.3", + "object", "rustc-demangle", "windows-link 0.2.1", ] @@ -1571,12 +1331,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d27c3610c36aee21ce8ac510e6224498de4228ad772a171ed65643a24693a5a8" -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - [[package]] name = "base64" version = "0.21.7" @@ -1598,41 +1352,12 @@ dependencies = [ "simd-abstraction", ] -[[package]] -name = "base64-simd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" -dependencies = [ - "outref 0.5.2", - "vsimd", -] - [[package]] name = "base64ct" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bcder" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7c42c9913f68cf9390a225e81ad56a5c515347287eb98baa710090ca1de86d" -dependencies = [ - "bytes", - "smallvec", -] - -[[package]] -name = "better_scoped_tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609" -dependencies = [ - "scoped-tls", -] - [[package]] name = "bevy" version = "0.17.3" @@ -1716,7 +1441,7 @@ checksum = "d577eae7246a1cda461df1b63188619fc6a3c619adba2a8e5a79e9aa51f64671" dependencies = [ "bevy_macro_utils", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1782,7 +1507,7 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", - "bitflags 2.9.4", + "bitflags 2.11.0", "blake3", "crossbeam-channel", "derive_more 2.1.1", @@ -1813,7 +1538,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1897,7 +1622,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.9.4", + "bitflags 2.11.0", "nonmax", "radsort", "smallvec", @@ -1913,7 +1638,7 @@ checksum = "f9396b256b366a43d7f61d1f230cdab0a512fb4712cbf7d688f3d6fce4c5ea8a" dependencies = [ "bevy_macro_utils", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1947,7 +1672,7 @@ dependencies = [ "bevy_reflect", "bevy_tasks", "bevy_utils", - "bitflags 2.9.4", + "bitflags 2.11.0", "bumpalo", "concurrent-queue", "derive_more 2.1.1", @@ -1971,7 +1696,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2037,7 +1762,7 @@ checksum = "92c4b3c3aac86f0db85d4f708883ebdc735c3f88ac5b84c033874fcdd3540a9d" dependencies = [ "bevy_macro_utils", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2089,7 +1814,7 @@ dependencies = [ "bevy_platform", "bevy_reflect", "bevy_utils", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "futures-lite", "guillotiere", @@ -2097,7 +1822,7 @@ dependencies = [ "image", "ktx2", "rectangle-pack", - "ruzstd 0.8.2", + "ruzstd", "serde", "thiserror 2.0.18", "tracing", @@ -2238,8 +1963,8 @@ dependencies = [ "parking_lot", "proc-macro2", "quote", - "syn 2.0.114", - "toml_edit 0.23.5", + "syn 2.0.117", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -2278,7 +2003,7 @@ dependencies = [ "bevy_platform", "bevy_reflect", "bevy_transform", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "derive_more 2.1.1", "hexasphere", @@ -2317,7 +2042,7 @@ dependencies = [ "bevy_shader", "bevy_transform", "bevy_utils", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "derive_more 2.1.1", "fixedbitset 0.5.7", @@ -2396,7 +2121,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.9.4", + "bitflags 2.11.0", "nonmax", "radsort", "smallvec", @@ -2448,7 +2173,7 @@ dependencies = [ "indexmap 2.13.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "uuid", ] @@ -2479,7 +2204,7 @@ dependencies = [ "bevy_transform", "bevy_utils", "bevy_window", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "derive_more 2.1.1", "downcast-rs 2.0.2", @@ -2510,7 +2235,7 @@ dependencies = [ "bevy_macro_utils", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2600,7 +2325,7 @@ dependencies = [ "bevy_text", "bevy_transform", "bevy_utils", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "derive_more 2.1.1", "fixedbitset 0.5.7", @@ -2632,7 +2357,7 @@ checksum = "bcda45913b1d6470c6b751656e72fb3f25ca6b5b7b2ee055b294aaed1eb7e5ba" dependencies = [ "bevy_macro_utils", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2860,7 +2585,7 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -2869,7 +2594,7 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2908,12 +2633,6 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" -[[package]] -name = "bitfield" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" - [[package]] name = "bitflags" version = "1.3.2" @@ -2922,24 +2641,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "bytemuck", - "serde", -] - -[[package]] -name = "bitness" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57792b99d555ebf109c83169228076f7d997e2b37ba1a653850ccd703ac7bab0" -dependencies = [ - "sysctl", - "thiserror 1.0.69", - "uname", - "winapi", + "serde_core", ] [[package]] @@ -2960,25 +2667,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "bitvec-nom2" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988fcc40055ceaa85edc55875a08f8abd29018582647fd82ad6128dba14a5f0" -dependencies = [ - "bitvec", - "nom 7.1.3", -] - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - [[package]] name = "blake3" version = "1.8.3" @@ -3002,7 +2690,7 @@ dependencies = [ "accesskit 0.17.1", "app_units", "atomic_refcell", - "bitflags 2.9.4", + "bitflags 2.11.0", "blitz-traits", "color", "cssparser 0.35.0", @@ -3015,7 +2703,7 @@ dependencies = [ "keyboard-types", "linebender_resource_handle", "markup5ever 0.35.0", - "objc2 0.6.3", + "objc2 0.6.4", "parley", "percent-encoding", "rayon", @@ -3055,7 +2743,7 @@ checksum = "0583c80a179a95a08adeb30c6c25dfeaa9d6e9314072f3c81a7976dee8d7a562" dependencies = [ "blitz-traits", "data-url 0.3.2", - "reqwest 0.12.28", + "reqwest", "tokio", ] @@ -3106,10 +2794,10 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cd87a5b7bc1bc3d546aeb28d0867ff3191e9c4e85452e1808931200a4bc49d4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytes", "cursor-icon", - "http 1.4.0", + "http", "keyboard-types", "serde", "smol_str", @@ -3131,15 +2819,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "block2" version = "0.5.1" @@ -3155,7 +2834,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" dependencies = [ - "objc2 0.6.3", + "objc2 0.6.4", ] [[package]] @@ -3171,16 +2850,6 @@ dependencies = [ "piper", ] -[[package]] -name = "blowfish" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "bluetooth-scanner" version = "0.1.1" @@ -3198,7 +2867,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84ae4213cc2a8dc663acecac67bbdad05142be4d8ef372b6903abf878b0c690a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bluez-generated", "dbus", "dbus-tokio", @@ -3206,7 +2875,7 @@ dependencies = [ "itertools 0.14.0", "log", "serde", - "serde-xml-rs 0.8.2", + "serde-xml-rs", "thiserror 2.0.18", "tokio", "uuid", @@ -3238,10 +2907,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" dependencies = [ "once_cell", - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3275,23 +2944,6 @@ dependencies = [ "chrono", ] -[[package]] -name = "browserslist-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf24e007a83ff1f58d2441b459fa26124aa1a7367da88948e9940f14e723d06" -dependencies = [ - "ahash 0.8.12", - "chrono", - "either", - "indexmap 2.13.0", - "itertools 0.13.0", - "nom 7.1.3", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "browserslist-rs" version = "0.19.0" @@ -3327,7 +2979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9a11621cb2c8c024e444734292482b1ad86fb50ded066cf46252e46643c8748" dependencies = [ "async-trait", - "bitflags 2.9.4", + "bitflags 2.11.0", "bluez-async", "dashmap 6.1.0", "dbus", @@ -3348,38 +3000,17 @@ dependencies = [ "windows-future 0.2.1", ] -[[package]] -name = "buffer-redux" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "431a9cc8d7efa49bc326729264537f5e60affce816c66edf434350778c9f4f54" -dependencies = [ - "memchr", -] - [[package]] name = "built" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" -[[package]] -name = "built" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" -dependencies = [ - "git2", -] - [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" -dependencies = [ - "allocator-api2", -] +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecheck" @@ -3423,7 +3054,7 @@ checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3443,7 +3074,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3467,49 +3098,13 @@ dependencies = [ "serde", ] -[[package]] -name = "bytes-str" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3" -dependencies = [ - "bytes", - "serde", -] - -[[package]] -name = "bytesize" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.13+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "cairo-rs" version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cairo-sys-rs", "glib", "libc", @@ -3534,7 +3129,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "log", "polling", "rustix 0.38.44", @@ -3554,16 +3149,6 @@ dependencies = [ "wayland-client", ] -[[package]] -name = "camellia" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "camino" version = "1.2.2" @@ -3575,13 +3160,13 @@ dependencies = [ [[package]] name = "cargo-config2" -version = "0.1.39" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3795d3a48839a46854805f56c8fe9c558f10804bcf57df53925ca843d87c788f" +checksum = "1f7dacdd4a7586d602c2543e0217304bebc6502bde82154209cd8f09f24a7718" dependencies = [ "serde", "serde_derive", - "toml 0.9.6", + "toml 1.0.6+spec-1.1.0", "windows-sys 0.61.2", ] @@ -3598,7 +3183,7 @@ dependencies = [ "clap", "console 0.15.11", "dialoguer", - "env_logger 0.11.8", + "env_logger", "fs-err", "git2", "gix-config", @@ -3673,7 +3258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ "serde", - "toml 0.9.6", + "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -3688,15 +3273,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cast5" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" -dependencies = [ - "cipher", -] - [[package]] name = "castaway" version = "0.2.4" @@ -3706,20 +3282,11 @@ dependencies = [ "rustversion", ] -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -3753,15 +3320,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "cfb-mode" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" -dependencies = [ - "cipher", -] - [[package]] name = "cfg-expr" version = "0.15.8" @@ -3811,9 +3369,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -3873,9 +3431,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "52fa72306bb30daf11bc97773431628e5b4916e97aaa74b7d3f625d4d495da02" dependencies = [ "clap_builder", "clap_derive", @@ -3883,11 +3441,11 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "2071365c5c56eae7d77414029dde2f4f4ba151cf68d5a3261c9a40de428ace93" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim", @@ -3896,21 +3454,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.5.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "dec5be1eea072311774b7b84ded287adbd9f293f9d23456817605c6042f4f5e0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clipboard-win" @@ -3921,26 +3479,6 @@ dependencies = [ "error-code", ] -[[package]] -name = "cmac" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" -dependencies = [ - "cipher", - "dbl", - "digest", -] - -[[package]] -name = "cmake" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" -dependencies = [ - "cc", -] - [[package]] name = "cobs" version = "0.3.0" @@ -3956,7 +3494,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad36507aeb7e16159dfe68db81ccc27571c3ccd4b76fb2fb72fc59e7a4b1b64c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "cocoa-foundation", "core-foundation 0.10.1", @@ -3972,7 +3510,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81411967c50ee9a1fc11365f8c585f863a22a9697c89239c452292c40ba79b0d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "core-foundation 0.10.1", "core-graphics-types 0.2.0", @@ -4023,9 +3561,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "com" @@ -4068,19 +3606,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "compact_str" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" -dependencies = [ - "castaway", - "cfg-if", - "itoa", - "ryu", - "static_assertions", -] - [[package]] name = "compact_str" version = "0.8.1" @@ -4097,9 +3622,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ "brotli", "compression-core", @@ -4140,13 +3665,12 @@ dependencies = [ [[package]] name = "console" -version = "0.16.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" dependencies = [ "encode_unicode", "libc", - "once_cell", "unicode-width 0.2.0", "windows-sys 0.61.2", ] @@ -4260,7 +3784,7 @@ checksum = "4f160aad86b4343e8d4e261fee9965c3005b2fd6bc117d172ab65948779e4acf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4269,7 +3793,7 @@ version = "0.8.0-alpha.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4405,20 +3929,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "cookie-factory" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" -dependencies = [ - "futures", -] - [[package]] name = "cookie_store" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" +checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206" dependencies = [ "cookie", "document-features", @@ -4477,7 +3992,20 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.11.0", "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -4501,7 +4029,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "core-foundation 0.10.1", "libc", ] @@ -4541,7 +4069,7 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "fontdb 0.16.2", "log", "rangemap", @@ -4581,24 +4109,6 @@ dependencies = [ "windows 0.54.0", ] -[[package]] -name = "cpio" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938e716cb1ade5d6c8f959c13a7248b889c07491fc7e41167c3afe20f8f0de1e" - -[[package]] -name = "cpio-archive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d5133d716d3d82da8c76367ddb0ab1733e2629f1462e4f39947e13b8b4b741" -dependencies = [ - "chrono", - "is_executable", - "simple-file-manifest", - "thiserror 1.0.69", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -4608,42 +4118,21 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" -dependencies = [ - "crc-catalog 1.1.1", -] - [[package]] name = "crc" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ - "crc-catalog 2.4.0", + "crc-catalog", ] -[[package]] -name = "crc-catalog" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" - [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" -[[package]] -name = "crc24" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" - [[package]] name = "crc32fast" version = "1.5.0" @@ -4763,30 +4252,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "crossterm_winapi", - "mio", - "parking_lot", - "rustix 0.38.44", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" -dependencies = [ - "bitflags 2.9.4", - "crossterm_winapi", - "derive_more 2.1.1", - "document-features", "futures-core", "mio", "parking_lot", - "rustix 1.1.3", + "rustix 0.38.44", "signal-hook", "signal-hook-mio", "winapi", @@ -4807,18 +4278,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-common" version = "0.1.7" @@ -4830,23 +4289,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cryptographic-message-syntax" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c324ba1028cef7e3a71a00cbf585637bb0215dec2f6a2b566d094190a1309b" -dependencies = [ - "bcder", - "bytes", - "chrono", - "hex", - "pem", - "reqwest 0.11.27", - "ring", - "signature", - "x509-certificate", -] - [[package]] name = "cssparser" version = "0.29.6" @@ -4907,17 +4349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.114", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4931,12 +4363,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.5.1" +version = "3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73736a89c4aff73035ba2ed2e565061954da00d4970fc9ac25dcc85a2a20d790" +checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" dependencies = [ "dispatch2", - "nix 0.30.1", + "nix 0.31.2", "windows-sys 0.61.2", ] @@ -4956,8 +4388,7 @@ dependencies = [ "cpufeatures", "curve25519-dalek-derive", "digest", - "fiat-crypto 0.2.9", - "rand_core 0.6.4", + "fiat-crypto", "rustc_version", "subtle", "zeroize", @@ -4971,7 +4402,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4989,7 +4420,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "libloading 0.8.9", "winapi", ] @@ -5035,7 +4466,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5048,8 +4479,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5062,7 +4492,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5073,7 +4503,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5084,7 +4514,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5095,7 +4525,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5152,15 +4582,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" -[[package]] -name = "dbl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "dbus" version = "0.9.10" @@ -5221,12 +4642,11 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde_core", ] [[package]] @@ -5237,7 +4657,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5258,7 +4678,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5268,7 +4688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5281,16 +4701,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.114", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", + "syn 2.0.117", ] [[package]] @@ -5299,19 +4710,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ - "derive_more-impl 2.1.1", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", - "unicode-xid", + "derive_more-impl", ] [[package]] @@ -5324,19 +4723,10 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.114", + "syn 2.0.117", "unicode-xid", ] -[[package]] -name = "des" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" -dependencies = [ - "cipher", -] - [[package]] name = "dialoguer" version = "0.11.0" @@ -5356,12 +4746,6 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - [[package]] name = "digest" version = "0.10.7" @@ -5403,7 +4787,7 @@ dependencies = [ "dioxus-ssr", "dioxus-stores", "dioxus-web", - "env_logger 0.11.8", + "env_logger", "futures-util", "manganis", "rand 0.9.2", @@ -5422,7 +4806,7 @@ version = "0.7.3" dependencies = [ "dioxus", "dioxus-cli-config", - "http 1.4.0", + "http", "infer", "jni 0.21.1", "js-sys", @@ -5448,19 +4832,7 @@ dependencies = [ "quote", "regex", "serde", - "syn 2.0.114", -] - -[[package]] -name = "dioxus-check" -version = "0.7.3" -dependencies = [ - "indoc", - "owo-colors", - "pretty_assertions", - "proc-macro2", - "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -5476,7 +4848,6 @@ dependencies = [ "axum-server", "backtrace", "brotli", - "built 0.8.0", "bytes", "cargo-config2", "cargo-generate", @@ -5484,72 +4855,76 @@ dependencies = [ "cargo_toml", "chrono", "clap", - "console 0.16.2", + "codemap", + "console 0.16.3", "console-subscriber", "const-serialize 0.7.2", "const-serialize 0.8.0-alpha.0", "convert_case 0.8.0", - "crossterm 0.29.0", + "crossterm", "ctrlc", "depinfo", "dioxus-autofmt", - "dioxus-check", "dioxus-cli-config", - "dioxus-cli-opt", "dioxus-cli-telemetry", "dioxus-component-manifest", "dioxus-core", "dioxus-core-types", "dioxus-devtools-types", "dioxus-dx-wire-format", - "dioxus-fullstack", "dioxus-html", "dioxus-rsx", "dioxus-rsx-hotreload", "dioxus-rsx-rosetta", "dircpy", - "dirs 6.0.0", + "dirs", "dunce", - "env_logger 0.11.8", "escargot", "flate2", "fs2", "futures-channel", "futures-util", "git2", + "grass", "handlebars", - "headers", "home", "html_parser", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-util", + "icns", "ignore", + "image", + "imagequant", "include_dir", + "indoc", "itertools 0.14.0", "krates", - "local-ip-address", + "lightningcss", "log", "manganis", "manganis-core", "manganis-core-07", - "memmap", - "memoize", + "md5", + "mozjpeg", "notify", "nucleo", - "object 0.37.3", + "object", "open", + "owo-colors", "path-absolutize", "pdb", "plist", - "posthog-rs", + "png 0.17.16", + "pretty_assertions", "prettyplease", "proc-macro2", "ratatui", "rayon", "regex", - "reqwest 0.12.28", - "rustls 0.23.36", + "reqwest", + "rpm", + "rustls", "schemars 0.8.22", "self-replace", "self_update", @@ -5557,21 +4932,19 @@ dependencies = [ "serde", "serde_json", "serde_json5", + "sha1", + "sha2", "shell-words", - "strum 0.27.2", "subsecond-types", - "syn 2.0.114", + "syn 2.0.117", "tar", - "target-lexicon 0.13.4", - "tauri-bundler", - "tauri-macos-sign", - "tauri-utils", + "target-lexicon 0.13.5", "tempfile", "thiserror 2.0.18", "throbber-widgets-tui", "tokio", "tokio-stream", - "tokio-tungstenite 0.27.0", + "tokio-tungstenite", "tokio-util", "toml 0.8.23", "toml_edit 0.22.27", @@ -5583,11 +4956,10 @@ dependencies = [ "uuid", "walkdir", "walrus", - "wasm-bindgen-externref-xform", - "wasm-encoder 0.235.0", "wasm-split-cli", "wasmparser 0.235.0", - "which 8.0.0", + "which", + "zip 4.6.1", ] [[package]] @@ -5597,54 +4969,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "dioxus-cli-opt" -version = "0.7.3" -dependencies = [ - "anyhow", - "browserslist-rs 0.19.0", - "built 0.8.0", - "codemap", - "const-serialize 0.8.0-alpha.0", - "grass", - "image", - "imagequant", - "lightningcss", - "manganis", - "manganis-core", - "mozjpeg", - "object 0.37.3", - "png", - "rayon", - "serde", - "serde_json", - "swc_allocator", - "swc_atoms", - "swc_bundler", - "swc_common", - "swc_config", - "swc_config_macro", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_codegen_macros", - "swc_ecma_lexer", - "swc_ecma_loader", - "swc_ecma_minifier", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_optimization", - "swc_ecma_usage_analyzer", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_eq_ignore_macros", - "swc_graph_analyzer", - "swc_macros_common", - "swc_sourcemap", - "swc_timer", - "swc_visit", - "tracing", -] - [[package]] name = "dioxus-cli-optimization-test" version = "0.0.1" @@ -5659,7 +4983,7 @@ name = "dioxus-cli-telemetry" version = "0.7.3" dependencies = [ "chrono", - "dirs 6.0.0", + "dirs", "rand 0.9.2", "serde", "serde_json", @@ -5703,7 +5027,7 @@ dependencies = [ "longest-increasing-subsequence", "pretty_assertions", "rand 0.9.2", - "reqwest 0.12.28", + "reqwest", "rustc-hash 2.1.1", "rustversion", "serde", @@ -5729,7 +5053,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.114", + "syn 2.0.117", "tokio", "trybuild", ] @@ -5778,7 +5102,7 @@ dependencies = [ "objc_id", "percent-encoding", "rand 0.9.2", - "reqwest 0.12.28", + "reqwest", "rfd", "rustc-hash 2.1.1", "separator", @@ -5792,7 +5116,7 @@ dependencies = [ "tokio", "tracing", "tray-icon", - "tungstenite 0.27.0", + "tungstenite 0.28.0", "webbrowser", "wry", ] @@ -5813,7 +5137,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tracing", - "tungstenite 0.27.0", + "tungstenite 0.28.0", ] [[package]] @@ -5876,7 +5200,7 @@ dependencies = [ "ouroboros", "pollster 0.4.0", "rand 0.9.2", - "reqwest 0.12.28", + "reqwest", "separator", "serde", "serde_json", @@ -5899,7 +5223,7 @@ dependencies = [ "dioxus-autofmt", "dioxus-rsx-rosetta", "html_parser", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen", ] @@ -5936,15 +5260,15 @@ dependencies = [ "futures-util", "gloo-net", "headers", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "inventory", "js-sys", "mime", "pin-project", "postcard", - "reqwest 0.12.28", + "reqwest", "rmp-serde", "rustversion", "send_wrapper", @@ -5955,7 +5279,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tokio-stream", - "tokio-tungstenite 0.27.0", + "tokio-tungstenite", "tokio-util", "tower 0.5.3", "tower-http", @@ -5988,7 +5312,7 @@ dependencies = [ "futures-channel", "futures-util", "generational-box", - "http 1.4.0", + "http", "inventory", "parking_lot", "serde", @@ -6009,7 +5333,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.114", + "syn 2.0.117", "tower-http", "xxhash-rust", ] @@ -6033,7 +5357,7 @@ dependencies = [ "futures-channel", "futures-util", "generational-box", - "reqwest 0.12.28", + "reqwest", "rustversion", "slab", "tokio", @@ -6079,7 +5403,7 @@ dependencies = [ "convert_case 0.8.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "trybuild", ] @@ -6339,7 +5663,7 @@ dependencies = [ "quote", "sha2", "slab", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6352,7 +5676,7 @@ dependencies = [ "proc-macro2-diagnostics", "quote", "rustversion", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6366,7 +5690,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.114", + "syn 2.0.117", "tracing", ] @@ -6383,7 +5707,7 @@ dependencies = [ "pretty_assertions", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6418,26 +5742,26 @@ dependencies = [ "futures-channel", "futures-util", "generational-box", - "http 1.4.0", + "http", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-util", "inventory", "lru 0.16.3", "multer", "parking_lot", "pin-project", - "rkyv 0.8.14", + "rkyv 0.8.15", "rustc-hash 2.1.1", - "rustls 0.23.36", + "rustls", "serde", "serde_json", "serde_qs", "subsecond", "thiserror 2.0.18", "tokio", - "tokio-tungstenite 0.27.0", + "tokio-tungstenite", "tokio-util", "tower 0.5.3", "tower-http", @@ -6459,7 +5783,7 @@ dependencies = [ "generational-box", "parking_lot", "rand 0.9.2", - "reqwest 0.12.28", + "reqwest", "rustc-hash 2.1.1", "serde", "tokio", @@ -6499,7 +5823,7 @@ dependencies = [ "dioxus-stores", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6560,34 +5884,13 @@ dependencies = [ "walkdir", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.6", - "windows-sys 0.48.0", + "dirs-sys", ] [[package]] @@ -6598,7 +5901,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.2", + "redox_users", "windows-sys 0.61.2", ] @@ -6610,14 +5913,14 @@ checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" [[package]] name = "dispatch2" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.6.2", "libc", - "objc2 0.6.3", + "objc2 0.6.4", ] [[package]] @@ -6628,7 +5931,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6645,9 +5948,9 @@ checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" [[package]] name = "dlib" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" dependencies = [ "libloading 0.8.9", ] @@ -6672,7 +5975,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6714,22 +6017,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -[[package]] -name = "dsa" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" -dependencies = [ - "digest", - "num-bigint-dig", - "num-traits", - "pkcs8", - "rfc6979", - "sha2", - "signature", - "zeroize", -] - [[package]] name = "dtoa" version = "1.0.11" @@ -6757,40 +6044,13 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "eax" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" -dependencies = [ - "aead", - "cipher", - "cmac", - "ctr", - "subtle", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - [[package]] name = "ecommerce-site" version = "0.1.1" dependencies = [ "chrono", "dioxus", - "reqwest 0.12.28", + "reqwest", "serde", ] @@ -6819,17 +6079,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed448-goldilocks" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" -dependencies = [ - "fiat-crypto 0.1.20", - "hex", - "subtle", -] - [[package]] name = "either" version = "1.15.0" @@ -6839,27 +6088,6 @@ dependencies = [ "serde", ] -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array 0.14.7", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - [[package]] name = "embedded-io" version = "0.4.0" @@ -6901,7 +6129,7 @@ checksum = "1796db3d892515842ca2dfb11124c4bb4a9e58d9f2c5c1072e5bca1b2334507b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6944,7 +6172,7 @@ checksum = "ba7795da175654fe16979af73f81f26a8ea27638d8d9823d317016888a63dc4c" dependencies = [ "num-traits", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6965,7 +6193,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -6986,45 +6214,26 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", "regex", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "env_logger" -version = "0.10.2" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", + "anstream 0.6.21", "anstyle", "env_filter", "jiff", @@ -7048,7 +6257,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -7059,9 +6268,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" dependencies = [ "serde", "serde_core", @@ -7171,7 +6380,7 @@ checksum = "57dd896da3fbb77138059b015c013459d96063c66bcdd3b9094ff2e9d3f19a47" dependencies = [ "execute-command-tokens", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -7262,22 +6471,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - [[package]] name = "fiat-crypto" version = "0.2.9" @@ -7294,20 +6487,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "figment" -version = "0.10.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" -dependencies = [ - "atomic", - "pear", - "serde", - "toml 0.8.23", - "uncased", - "version_check", -] - [[package]] name = "file-explorer" version = "0.1.0" @@ -7454,7 +6633,7 @@ dependencies = [ "icu_locale_core", "linebender_resource_handle", "memmap2", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-core-foundation", "objc2-core-text", "objc2-foundation 0.3.2", @@ -7493,7 +6672,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -7527,16 +6706,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "from_variant" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308530a56b099da144ebc5d8e179f343ad928fa2b3558d1eb3db9af18d6eff43" -dependencies = [ - "swc_macros_common", - "syn 2.0.114", -] - [[package]] name = "fs-err" version = "3.1.3" @@ -7571,12 +6740,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "fsevent-sys" version = "4.1.0" @@ -7599,7 +6762,7 @@ dependencies = [ "dioxus", "dioxus-web", "execute", - "http 1.4.0", + "http", "serde", "sqlx", "tokio", @@ -7621,7 +6784,7 @@ version = "0.1.0" dependencies = [ "chrono", "dioxus", - "reqwest 0.12.28", + "reqwest", "serde", ] @@ -7631,7 +6794,7 @@ version = "0.1.0" dependencies = [ "anyhow", "dioxus", - "reqwest 0.12.28", + "reqwest", "serde", "serde_json", "thiserror 2.0.18", @@ -7665,9 +6828,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -7680,9 +6843,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -7690,15 +6853,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -7718,9 +6881,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -7737,32 +6900,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -7772,7 +6935,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -7888,7 +7050,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -7918,7 +7079,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", "windows-link 0.2.1", ] @@ -7955,21 +7116,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] -name = "getset" -version = "0.1.6" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.114", + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", ] [[package]] @@ -8081,7 +7243,7 @@ version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "libc", "libgit2-sys", "log", @@ -8101,7 +7263,7 @@ dependencies = [ "gix-utils 0.2.0", "itoa", "thiserror 2.0.18", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -8122,7 +7284,7 @@ dependencies = [ "smallvec", "thiserror 2.0.18", "unicode-bom", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -8131,7 +7293,7 @@ version = "0.14.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dc2c844c4cf141884678cabef736fd91dd73068b9146e6f004ba1a0457944b6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bstr", "gix-path", "libc", @@ -8210,7 +7372,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20972499c03473e773a2099e5fd0c695b9b72465837797a51a43391a1635a030" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bstr", "gix-features 0.41.1", "gix-path", @@ -8280,7 +7442,7 @@ dependencies = [ "itoa", "smallvec", "thiserror 2.0.18", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -8313,7 +7475,7 @@ dependencies = [ "gix-validate 0.9.4", "memmap2", "thiserror 2.0.18", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -8322,7 +7484,7 @@ version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47aeb0f13de9ef2f3033f5ff218de30f44db827ac9f1286f9ef050aacddd5888" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "gix-path", "libc", "windows-sys 0.52.0", @@ -8343,9 +7505,9 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e42a4c2583357721ba2d887916e78df504980f22f1182df06997ce197b89504" +checksum = "f69a13643b8437d4ca6845e08143e847a36ca82903eed13303475d0ae8b162e0" [[package]] name = "gix-utils" @@ -8416,7 +7578,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "futures-channel", "futures-core", "futures-executor", @@ -8444,7 +7606,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8471,7 +7633,7 @@ checksum = "b9247516746aa8e53411a0db9b62b0e24efbcf6a76e0ba73e5a91b512ddabed7" dependencies = [ "crossbeam-channel", "keyboard-types", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "once_cell", "thiserror 2.0.18", @@ -8513,7 +7675,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http 1.4.0", + "http", "js-sys", "pin-project", "serde", @@ -8594,7 +7756,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8638,24 +7800,13 @@ dependencies = [ "system-deps", ] -[[package]] -name = "goblin" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47" -dependencies = [ - "log", - "plain", - "scroll 0.12.0", -] - [[package]] name = "gpu-alloc" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "gpu-alloc-types", ] @@ -8665,7 +7816,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -8699,7 +7850,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "gpu-descriptor-types 0.1.2", "hashbrown 0.14.5", ] @@ -8710,7 +7861,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "gpu-descriptor-types 0.2.0", "hashbrown 0.15.5", ] @@ -8721,7 +7872,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -8730,7 +7881,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -8770,17 +7921,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9e2d4c0a8296178d8802098410ca05d86b17a10bb5ab559b3fb404c1f948220" -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "gtk" version = "0.18.2" @@ -8830,7 +7970,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -8843,25 +7983,6 @@ dependencies = [ "svg_fmt", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.13.0", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.13" @@ -8873,7 +7994,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap 2.13.0", "slab", "tokio", @@ -8915,7 +8036,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "core_maths", "read-fonts", @@ -9083,15 +8204,6 @@ dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.12", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -9152,7 +8264,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "com", "libc", "libloading 0.8.9", @@ -9161,6 +8273,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "hayro-jpeg2000" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a74cfc18c0093ef8009a0d6c1ba3024df0cce228503a14c1372e1e23eed43e" +dependencies = [ + "fearless_simd", +] + [[package]] name = "hdrhistogram" version = "7.5.4" @@ -9183,7 +8304,7 @@ dependencies = [ "base64 0.22.1", "bytes", "headers-core", - "http 1.4.0", + "http", "httpdate", "mime", "sha1", @@ -9195,7 +8316,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.4.0", + "http", ] [[package]] @@ -9297,25 +8418,12 @@ version = "0.1.0" dependencies = [ "anyhow", "dioxus", - "reqwest 0.12.28", + "reqwest", "rusqlite", "serde", "serde_json", ] -[[package]] -name = "hstr" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85186bc48d3c611ead052cc3f907748e40b63d73a99e4ed34d18063e2baaf1b" -dependencies = [ - "hashbrown 0.14.5", - "new_debug_unreachable", - "once_cell", - "rustc-hash 2.1.1", - "triomphe", -] - [[package]] name = "html-escape" version = "0.2.13" @@ -9374,17 +8482,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -9395,17 +8492,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -9413,7 +8499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -9424,8 +8510,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -9459,30 +8545,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.8.1" @@ -9493,9 +8555,9 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -9506,35 +8568,21 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.32", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", - "hyper 1.8.1", + "http", + "hyper", "hyper-util", "log", - "rustls 0.23.36", - "rustls-native-certs 0.8.3", + "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", "webpki-roots 1.0.6", ] @@ -9545,7 +8593,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.8.1", + "hyper", "hyper-util", "pin-project-lite", "tokio", @@ -9560,7 +8608,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -9578,20 +8626,20 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http", + "http-body", + "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", - "system-configuration 0.7.0", + "socket2 0.6.3", + "system-configuration", "tokio", "tower-layer", "tower-service", "tracing", - "windows-registry 0.6.1", + "windows-registry", ] [[package]] @@ -9618,6 +8666,17 @@ dependencies = [ "cc", ] +[[package]] +name = "icns" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467efeb2c1e570cebb9863f9173447f8fe92303d66adc97cb89ac824b60e7a0f" +dependencies = [ + "byteorder", + "hayro-jpeg2000", + "png 0.18.1", +] + [[package]] name = "icu_collections" version = "2.1.1" @@ -9754,15 +8813,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "idea" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" -dependencies = [ - "cipher", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -9790,12 +8840,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "if_chain" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb" - [[package]] name = "ignore" version = "0.4.25" @@ -9825,7 +8869,7 @@ dependencies = [ "gif", "image-webp", "num-traits", - "png", + "png 0.17.16", "qoi", "ravif", "rayon", @@ -9906,7 +8950,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", ] [[package]] @@ -9958,19 +9001,13 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - [[package]] name = "inotify" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -9990,7 +9027,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding", "generic-array 0.14.7", ] @@ -10004,7 +9040,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -10033,23 +9069,23 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "inventory" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +checksum = "009ae045c87e7082cb72dab0ccd01ae075dd00141ddc108f43a0ea150a9e7227" dependencies = [ "rustversion", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "ipnetwork" @@ -10079,18 +9115,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-macro" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "is-terminal" version = "0.4.17" @@ -10118,27 +9142,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" -[[package]] -name = "is_executable" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" -dependencies = [ - "windows-sys 0.60.2", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "iter-read" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294" - [[package]] name = "itertools" version = "0.10.5" @@ -10206,9 +9215,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.19" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -10221,20 +9230,20 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.19" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "jiff-tzdb" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68971ebff725b9e2ca27a601c5eb38a4c5d64422c4cbab0c535f248087eda5c2" +checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076" [[package]] name = "jiff-tzdb-platform" @@ -10253,7 +9262,7 @@ checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" dependencies = [ "cesu8", "combine", - "jni-sys", + "jni-sys 0.3.0", "log", "thiserror 1.0.69", "walkdir", @@ -10268,19 +9277,68 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.0", "log", "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295dc9997acda1562fdf8d299f56063c936443b60f078e63a5d8d3c34ef2642b" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c3d1da60c95c98847b26b9d45f4360fee718b31de746df016d9cd6de916a7ef" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "jni-utils" version = "0.1.1" @@ -10314,36 +9372,14 @@ checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", ] -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "jwalk" version = "0.8.1" @@ -10354,31 +9390,11 @@ dependencies = [ "rayon", ] -[[package]] -name = "jzon" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ab85f84ca42c5ec520e6f3c9966ba1fd62909ce260f8837e248857d2560509" - -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2", - "signature", -] - [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] @@ -10389,7 +9405,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "serde", "unicode-segmentation", ] @@ -10411,26 +9427,6 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" -[[package]] -name = "konst" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4381b9b00c55f251f2ebe9473aef7c117e96828def1a7cb3bd3f0f903c6894e9" -dependencies = [ - "const_panic", - "konst_kernel", - "typewit", -] - -[[package]] -name = "konst_kernel" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b1eb7788f3824c629b1116a7a9060d6e898c358ebff59070093d51103dcc3c" -dependencies = [ - "typewit", -] - [[package]] name = "kqueue" version = "1.1.1" @@ -10481,7 +9477,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff7f53bdf698e7aa7ec916411bbdc8078135da11b66db5182675b2227f6c0d07" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -10604,9 +9600,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libdbus-sys" @@ -10619,9 +9615,9 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" dependencies = [ "arbitrary", "cc", @@ -10669,13 +9665,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "libc", - "redox_syscall 0.7.0", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -10734,9 +9731,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "d52f4c29e2a68ac30c9087e1b772dc9f44a2b66ed44edf2266cf2be9b03dafc1" dependencies = [ "cc", "libc", @@ -10746,29 +9743,30 @@ dependencies = [ [[package]] name = "lightningcss" -version = "1.0.0-alpha.67" +version = "1.0.0-alpha.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798fba4e1205eed356b8ed7754cc3f7f04914e27855ca641409f4a532e992149" +checksum = "cb6314c2f0590ac93c86099b98bb7ba8abcf759bfd89604ffca906472bb54937" dependencies = [ "ahash 0.8.12", - "bitflags 2.9.4", - "browserslist-rs 0.18.2", + "bitflags 2.11.0", + "browserslist-rs", "const-str 0.3.2", "cssparser 0.33.0", "cssparser-color", "dashmap 5.5.3", "data-encoding", - "getrandom 0.2.17", + "getrandom 0.3.4", "indexmap 2.13.0", "itertools 0.10.5", "lazy_static", "lightningcss-derive", "parcel_selectors", "parcel_sourcemap", - "paste", + "pastey", "pathdiff", "rayon", "serde", + "serde-content", "smallvec", "static-self", ] @@ -10799,9 +9797,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "liquid" @@ -10840,7 +9838,7 @@ checksum = "de66c928222984aea59fcaed8ba627f388aaac3c1f57dcb05cc25495ef8faefe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -10869,17 +9867,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" -[[package]] -name = "local-ip-address" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ef8c257c92ade496781a32a581d43e3d512cf8ce714ecf04ea80f93ed0ff4a" -dependencies = [ - "libc", - "neli", - "windows-sys 0.61.2", -] - [[package]] name = "lock_api" version = "0.4.14" @@ -10913,24 +9900,6 @@ dependencies = [ "imgref", ] -[[package]] -name = "lru" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" -dependencies = [ - "hashbrown 0.12.3", -] - -[[package]] -name = "lru" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" -dependencies = [ - "hashbrown 0.13.2", -] - [[package]] name = "lru" version = "0.12.5" @@ -10955,17 +9924,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "mac" version = "0.1.1" @@ -10999,7 +9957,7 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11018,8 +9976,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f44db74bde26fdf427af23f1d146c211aed857c59e3be750cf2617f6b0b05c94" dependencies = [ "proc-macro2", - "syn 2.0.114", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -11032,7 +9990,7 @@ dependencies = [ "manganis-core", "manganis-macro", "ndk-context", - "objc2 0.6.3", + "objc2 0.6.4", "thiserror 2.0.18", ] @@ -11046,7 +10004,7 @@ dependencies = [ "dioxus-core-types", "manganis", "serde", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -11067,7 +10025,7 @@ dependencies = [ "manganis-core", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11103,7 +10061,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11114,7 +10072,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11166,15 +10124,15 @@ dependencies = [ [[package]] name = "md5" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memfd" @@ -11182,24 +10140,14 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" dependencies = [ - "rustix 1.1.3", -] - -[[package]] -name = "memmap" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -dependencies = [ - "libc", - "winapi", + "rustix 1.1.4", ] [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -11213,36 +10161,13 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoize" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d1d5792299bab3f8b5d88d1b7a7cb50ad7ef039a8c4d45a6b84880a6526276" -dependencies = [ - "lazy_static", - "lru 0.7.8", - "memoize-inner", -] - -[[package]] -name = "memoize-inner" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd8f89255d8ff313afabed9a3c83ef0993cc056679dfd001f5111a026f876f7" -dependencies = [ - "lazy_static", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "metal" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -11257,7 +10182,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "core-graphics-types 0.2.0", "foreign-types 0.5.0", @@ -11282,26 +10207,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minicbor" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d15f4203d71fdf90903c2696e55426ac97a363c67b218488a73b534ce7aca10" -dependencies = [ - "minicbor-derive", -] - -[[package]] -name = "minicbor-derive" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "minicov" version = "0.3.8" @@ -11376,12 +10281,12 @@ dependencies = [ "gtk", "keyboard-types", "libxdo", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", "once_cell", - "png", + "png 0.17.16", "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -11395,7 +10300,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.0", + "http", "httparse", "memchr", "mime", @@ -11420,7 +10325,7 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11430,7 +10335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ "bit-set 0.5.3", - "bitflags 2.9.4", + "bitflags 2.11.0", "codespan-reporting 0.11.1", "hexf-parse", "indexmap 2.13.0", @@ -11451,7 +10356,7 @@ checksum = "916cbc7cb27db60be930a4e2da243cf4bc39569195f22fd8ee419cd31d5b662c" dependencies = [ "arrayvec", "bit-set 0.8.0", - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "cfg_aliases 0.2.1", "codespan-reporting 0.12.0", @@ -11552,17 +10457,17 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe 0.2.1", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -11573,8 +10478,8 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.9.4", - "jni-sys", + "bitflags 2.11.0", + "jni-sys 0.3.0", "log", "ndk-sys 0.5.0+25.2.9519653", "num_enum", @@ -11587,8 +10492,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.4", - "jni-sys", + "bitflags 2.11.0", + "jni-sys 0.3.0", "log", "ndk-sys 0.6.0+11769913", "num_enum", @@ -11608,7 +10513,7 @@ version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ - "jni-sys", + "jni-sys 0.3.0", ] [[package]] @@ -11617,36 +10522,7 @@ version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" dependencies = [ - "jni-sys", -] - -[[package]] -name = "neli" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" -dependencies = [ - "bitflags 2.9.4", - "byteorder", - "derive_builder", - "getset", - "libc", - "log", - "neli-proc-macros", - "parking_lot", -] - -[[package]] -name = "neli-proc-macros" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d8d08c6e98f20a62417478ebf7be8e1425ec9acecc6f63e22da633f6b71609" -dependencies = [ - "either", - "proc-macro2", - "quote", - "serde", - "syn 2.0.114", + "jni-sys 0.3.0", ] [[package]] @@ -11670,7 +10546,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -11683,7 +10559,19 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +dependencies = [ + "bitflags 2.11.0", "cfg-if", "cfg_aliases 0.2.1", "libc", @@ -11732,15 +10620,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" -[[package]] -name = "normpath" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a9da8c9922c35a1033d76f7272dfc2e7ee20392083d75aeea6ced23c6266578" -dependencies = [ - "winapi", -] - [[package]] name = "normpath" version = "1.5.0" @@ -11756,7 +10635,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "fsevent-sys", "inotify", "kqueue", @@ -11774,15 +10653,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "serde", ] [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -11839,7 +10718,6 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", - "serde", ] [[package]] @@ -11854,7 +10732,6 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.5", - "serde", "smallvec", "zeroize", ] @@ -11882,7 +10759,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -11967,10 +10844,10 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12007,9 +10884,9 @@ dependencies = [ [[package]] name = "objc2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" dependencies = [ "objc2-encode", "objc2-exception-helper", @@ -12021,7 +10898,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -12037,9 +10914,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.6.2", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", @@ -12051,7 +10928,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -12075,7 +10952,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a644b62ffb826a5277f536cf0f701493de420b13d40e700c452c36567771111" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -12086,7 +10963,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -12098,9 +10975,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "dispatch2", - "objc2 0.6.3", + "objc2 0.6.4", ] [[package]] @@ -12109,9 +10986,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "dispatch2", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-core-foundation", "objc2-io-surface", ] @@ -12146,7 +11023,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "objc2-core-foundation", ] @@ -12171,7 +11048,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "dispatch", "libc", @@ -12184,9 +11061,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.6.2", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-core-foundation", ] @@ -12196,7 +11073,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "libc", "objc2-core-foundation", ] @@ -12207,8 +11084,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", + "bitflags 2.11.0", + "objc2 0.6.4", "objc2-core-foundation", ] @@ -12230,7 +11107,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -12242,7 +11119,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -12265,7 +11142,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", @@ -12286,8 +11163,8 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.9.4", - "objc2 0.6.3", + "bitflags 2.11.0", + "objc2 0.6.4", "objc2-core-foundation", "objc2-foundation 0.3.2", ] @@ -12309,7 +11186,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", @@ -12322,9 +11199,9 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.6.2", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -12348,20 +11225,6 @@ dependencies = [ "objc", ] -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "crc32fast", - "flate2", - "hashbrown 0.14.5", - "indexmap 2.13.0", - "memchr", - "ruzstd 0.5.0", -] - [[package]] name = "object" version = "0.37.3" @@ -12373,7 +11236,7 @@ dependencies = [ "hashbrown 0.15.5", "indexmap 2.13.0", "memchr", - "ruzstd 0.8.2", + "ruzstd", "wasmparser 0.236.1", ] @@ -12400,18 +11263,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ocb3" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" -dependencies = [ - "aead", - "cipher", - "ctr", - "subtle", -] - [[package]] name = "offset-allocator" version = "0.2.0" @@ -12431,30 +11282,11 @@ dependencies = [ "byteorder", ] -[[package]] -name = "oid-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once-cell-regex" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de7e389a5043420c8f2b95ed03f3f104ad6f4c41f7d7e27298f033abc253e8" -dependencies = [ - "once_cell", - "regex", -] - [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "portable-atomic", ] @@ -12490,11 +11322,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "foreign-types 0.3.2", "libc", @@ -12511,7 +11343,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12528,9 +11360,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -12546,9 +11378,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "orbclient" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ad2c6bae700b7aa5d1cc30c59bdd3a1c180b09dbaea51e2ae2b8e1cf211fdd" +checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" dependencies = [ "libc", "libredox", @@ -12582,16 +11414,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "os_pipe" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - [[package]] name = "ouroboros" version = "0.18.5" @@ -12613,7 +11435,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12622,12 +11444,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4" -[[package]] -name = "outref" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" - [[package]] name = "owned_ttf_parser" version = "0.25.1" @@ -12639,69 +11455,14 @@ dependencies = [ [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" dependencies = [ "supports-color 2.1.0", "supports-color 3.0.2", ] -[[package]] -name = "p12" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4873306de53fe82e7e484df31e1e947d61514b6ea2ed6cd7b45d63006fd9224" -dependencies = [ - "cbc", - "cipher", - "des", - "getrandom 0.2.17", - "hmac", - "lazy_static", - "rc2", - "sha1", - "yasna", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p521" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" -dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", - "primeorder", - "rand_core 0.6.4", - "sha2", -] - [[package]] name = "pango" version = "0.18.3" @@ -12727,32 +11488,13 @@ dependencies = [ "system-deps", ] -[[package]] -name = "par-core" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f" -dependencies = [ - "once_cell", -] - -[[package]] -name = "par-iter" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eae0176a010bb94b9a67f0eb9da0fd31410817d58850649c54f485124c9a71a" -dependencies = [ - "either", - "par-core", -] - [[package]] name = "parcel_selectors" version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54fd03f1ad26cb6b3ec1b7414fa78a3bd639e7dbb421b1a60513c96ce886a196" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cssparser 0.33.0", "log", "phf 0.11.3", @@ -12769,7 +11511,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "485b74d7218068b2b7c0e3ff12fbc61ae11d57cb5d8224f525bd304c6be05bbb" dependencies = [ - "base64-simd 0.7.0", + "base64-simd", "data-url 0.1.1", "rkyv 0.7.46", "serde", @@ -12820,23 +11562,18 @@ dependencies = [ "swash", ] -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + [[package]] name = "path-absolutize" version = "3.1.1" @@ -12846,12 +11583,6 @@ dependencies = [ "path-dedot", ] -[[package]] -name = "path-clean" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" - [[package]] name = "path-dedot" version = "3.1.1" @@ -12874,43 +11605,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82040a392923abe6279c00ab4aff62d5250d1c8555dc780e4b02783a7aa74863" dependencies = [ "fallible-iterator 0.2.0", - "scroll 0.11.0", + "scroll", "uuid", ] -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -12969,7 +11667,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -12992,16 +11690,6 @@ dependencies = [ "indexmap 2.13.0", ] -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset 0.5.7", - "indexmap 2.13.0", -] - [[package]] name = "petgraph" version = "0.8.3" @@ -13015,73 +11703,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "pgp" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1877a97fd422433220ad272eb008ec55691944b1200e9eb204e3cb2cb69d34e9" -dependencies = [ - "aes", - "aes-gcm", - "aes-kw", - "argon2", - "base64 0.22.1", - "bitfield", - "block-padding", - "blowfish", - "bstr", - "buffer-redux", - "byteorder", - "camellia", - "cast5", - "cfb-mode", - "chrono", - "cipher", - "const-oid", - "crc24", - "curve25519-dalek", - "derive_builder", - "derive_more 1.0.0", - "des", - "digest", - "dsa", - "eax", - "ecdsa", - "ed25519-dalek", - "elliptic-curve", - "flate2", - "generic-array 0.14.7", - "hex", - "hkdf", - "idea", - "iter-read", - "k256", - "log", - "md-5", - "nom 7.1.3", - "num-bigint-dig", - "num-traits", - "num_enum", - "ocb3", - "p256", - "p384", - "p521", - "rand 0.8.5", - "ripemd", - "rsa", - "sha1", - "sha1-checked", - "sha2", - "sha3", - "signature", - "smallvec", - "thiserror 1.0.69", - "twofish", - "x25519-dalek", - "x448", - "zeroize", -] - [[package]] name = "phf" version = "0.8.0" @@ -13186,7 +11807,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -13224,29 +11845,29 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.10" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -13256,9 +11877,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", @@ -13377,6 +11998,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.11.0", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -13387,7 +12021,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -13443,21 +12077,6 @@ dependencies = [ "serde", ] -[[package]] -name = "posthog-rs" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "610d236077c94c96194b0d2b40989bda4bf8faf60e15a0da6ef8ffd4c78f6c1c" -dependencies = [ - "chrono", - "derive_builder", - "reqwest 0.11.27", - "semver", - "serde", - "serde_json", - "uuid", -] - [[package]] name = "potential_utf" version = "0.1.4" @@ -13512,7 +12131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32db37eb2b0ec0af154e9c1b33425902d8cd9481e35167c4e9ffb28fec3916bb" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -13532,16 +12151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", + "syn 2.0.117", ] [[package]] @@ -13565,11 +12175,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.23.5", + "toml_edit 0.25.4+spec-1.1.0", ] [[package]] @@ -13596,28 +12206,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.114", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -13641,7 +12229,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "version_check", "yansi", ] @@ -13672,7 +12260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -13695,7 +12283,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -13713,16 +12301,6 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" -[[package]] -name = "psm" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d" -dependencies = [ - "ar_archive_writer", - "cc", -] - [[package]] name = "ptr_meta" version = "0.1.4" @@ -13760,7 +12338,7 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -13816,6 +12394,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.9" @@ -13828,8 +12415,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.36", - "socket2 0.6.2", + "rustls", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -13848,7 +12435,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash 2.1.1", - "rustls 0.23.36", + "rustls", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -13866,16 +12453,16 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -13886,18 +12473,18 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_fmt" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce082a9940a7ace2ad4a8b7d0b1eac6aa378895f18be598230c5f2284ac05426" - [[package]] name = "radsort" version = "0.1.1" @@ -14035,9 +12622,9 @@ dependencies = [ [[package]] name = "range-alloc" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" [[package]] name = "rangemap" @@ -14045,60 +12632,22 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" -[[package]] -name = "rasn" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9b0d03fbc7d2dcfdd35086c43ce30ac5ff62ed7eff4397e4f4f2995a2b0e2a" -dependencies = [ - "arrayvec", - "bitvec", - "bitvec-nom2", - "bytes", - "chrono", - "either", - "jzon", - "konst", - "nom 7.1.3", - "num-bigint", - "num-integer", - "num-traits", - "once_cell", - "rasn-derive", - "snafu", -] - -[[package]] -name = "rasn-derive" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbaf7105cd254b632f4732fbcc243ce750cef87d8335826125ef6df5733b5a0c" -dependencies = [ - "either", - "itertools 0.10.5", - "proc-macro2", - "quote", - "rayon", - "syn 1.0.109", - "uuid", -] - [[package]] name = "ratatui" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cassowary", - "compact_str 0.8.1", - "crossterm 0.28.1", + "compact_str", + "crossterm", "indoc", "instability", "itertools 0.13.0", "lru 0.12.5", "paste", - "strum 0.26.3", + "strum", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", @@ -14115,7 +12664,7 @@ dependencies = [ "arrayvec", "av1-grain", "bitstream-io", - "built 0.7.7", + "built", "cfg-if", "interpolate_name", "itertools 0.12.1", @@ -14186,15 +12735,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rc2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd" -dependencies = [ - "cipher", -] - [[package]] name = "read-fonts" version = "0.35.0" @@ -14227,27 +12767,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags 2.9.4", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 1.0.69", + "bitflags 2.11.0", ] [[package]] @@ -14278,7 +12807,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14306,25 +12835,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" - -[[package]] -name = "regress" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2057b2325e68a893284d1538021ab90279adac1139957ca2a74426c6f118fb48" -dependencies = [ - "hashbrown 0.16.1", - "memchr", -] - -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "remove_dir_all" @@ -14336,7 +12849,7 @@ dependencies = [ "cvt", "fs_at", "libc", - "normpath 1.5.0", + "normpath", "windows-sys 0.59.0", ] @@ -14364,48 +12877,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-rustls 0.24.2", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration 0.5.1", - "tokio", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.25.4", - "winreg", -] - [[package]] name = "reqwest" version = "0.12.28" @@ -14420,12 +12891,12 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", - "hyper-rustls 0.27.7", + "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "js-sys", @@ -14436,15 +12907,15 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.36", + "rustls", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tokio-native-tls", - "tokio-rustls 0.26.4", + "tokio-rustls", "tokio-util", "tower 0.5.3", "tower-http", @@ -14457,16 +12928,6 @@ dependencies = [ "webpki-roots 1.0.6", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "rfd" version = "0.17.2" @@ -14478,7 +12939,7 @@ dependencies = [ "js-sys", "libc", "log", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -14493,9 +12954,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.52" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" dependencies = [ "bytemuck", ] @@ -14507,7 +12968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" dependencies = [ "ahash 0.8.12", - "bitflags 2.9.4", + "bitflags 2.11.0", "instant", "num-traits", "once_cell", @@ -14525,7 +12986,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14542,15 +13003,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest", -] - [[package]] name = "rkyv" version = "0.7.46" @@ -14571,9 +13023,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360b333c61ae24e5af3ae7c8660bd6b21ccd8200dbbc5d33c2454421e85b9c69" +checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" dependencies = [ "bytecheck 0.8.2", "bytes", @@ -14583,7 +13035,7 @@ dependencies = [ "ptr_meta 0.3.1", "rancor", "rend 0.5.3", - "rkyv_derive 0.8.14", + "rkyv_derive 0.8.15", "tinyvec", "uuid", ] @@ -14601,13 +13053,13 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02f8cdd12b307ab69fe0acf4cd2249c7460eb89dce64a0febadf934ebb6a9e" +checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -14621,11 +13073,10 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" dependencies = [ - "byteorder", "rmp", "serde", ] @@ -14647,7 +13098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" dependencies = [ "base64 0.22.1", - "bitflags 2.9.4", + "bitflags 2.11.0", "serde", "serde_derive", "unicode-ident", @@ -14661,32 +13112,29 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rpm" -version = "0.16.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ffe5b31ea4c7c76a361dc29b986e11fb6987313453088e1fd95f34ecc1ba6" +checksum = "95c0e45982d67e35e3bbffb24d95e3d0282211ac2dad755b2ab24b9ffc07b199" dependencies = [ - "bitflags 2.9.4", - "bzip2", - "chrono", - "cpio", + "base64 0.22.1", + "bitflags 2.11.0", "digest", "enum-display-derive", "enum-primitive-derive", "flate2", "hex", - "itertools 0.13.0", + "itertools 0.14.0", "log", "md-5", - "nom 7.1.3", + "nom 8.0.0", "num", "num-derive", "num-traits", - "pgp", "sha1", "sha2", + "sha3", "thiserror 2.0.18", - "xz2", - "zstd", + "zeroize", ] [[package]] @@ -14715,7 +13163,7 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "fallible-iterator 0.3.0", "fallible-streaming-iterator", "hashlink 0.9.1", @@ -14766,22 +13214,13 @@ dependencies = [ "semver", ] -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -14790,84 +13229,32 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.21.12" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "log", - "ring", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls" -version = "0.23.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" -dependencies = [ - "aws-lc-rs", "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.9", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework 2.11.1", -] - -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe 0.1.6", - "rustls-pemfile 2.2.0", - "rustls-pki-types", - "schannel", - "security-framework 2.11.1", -] - [[package]] name = "rustls-native-certs" version = "0.8.3" @@ -14877,16 +13264,7 @@ dependencies = [ "openssl-probe 0.2.1", "rustls-pki-types", "schannel", - "security-framework 3.5.1", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", + "security-framework", ] [[package]] @@ -14908,34 +13286,12 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -14953,7 +13309,7 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "libm", "smallvec", @@ -14970,7 +13326,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "core_maths", "log", @@ -14982,37 +13338,20 @@ dependencies = [ "unicode-script", ] -[[package]] -name = "ruzstd" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" -dependencies = [ - "byteorder", - "derive_more 0.99.20", - "twox-hash 1.6.3", -] - [[package]] name = "ruzstd" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" dependencies = [ - "twox-hash 2.1.2", + "twox-hash", ] [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" - -[[package]] -name = "ryu-js" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd29631678d6fb0903b69223673e122c32e9ae559d0960a38d574695ebc0ea15" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safe_arch" @@ -15043,9 +13382,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -15062,18 +13401,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - [[package]] name = "schemars" version = "1.2.1" @@ -15096,7 +13423,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -15108,7 +13435,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -15129,36 +13456,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" -[[package]] -name = "scroll" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6" -dependencies = [ - "scroll_derive", -] - -[[package]] -name = "scroll_derive" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sctk-adwaita" version = "0.10.1" @@ -15178,40 +13475,13 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array 0.14.7", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -15220,9 +13490,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -15252,7 +13522,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09975d3195f34dce9c7b381cb0f00c3c13381d4d3735c0f1a9c894b283b302ab" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cssparser 0.35.0", "derive_more 2.1.1", "log", @@ -15292,12 +13562,12 @@ checksum = "d832c086ece0dacc29fb2947bb4219b8f6e12fe9e40b7108f9e57c4224e47b5c" dependencies = [ "either", "flate2", - "hyper 1.8.1", + "hyper", "indicatif", "log", "quick-xml 0.37.5", "regex", - "reqwest 0.12.28", + "reqwest", "self-replace", "semver", "serde_json", @@ -15373,22 +13643,25 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" -[[package]] -name = "seq-macro" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" - [[package]] name = "serde" -version = "1.0.221" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "341877e04a22458705eb4e131a1508483c877dca2792b3781d4e5d8a6019ec43" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] +[[package]] +name = "serde-content" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3753ca04f350fa92d00b6146a3555e63c55388c9ef2e11e09bce2ff1c0b509c6" +dependencies = [ + "serde", +] + [[package]] name = "serde-untagged" version = "0.1.9" @@ -15422,18 +13695,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "serde-xml-rs" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" -dependencies = [ - "log", - "serde", - "thiserror 1.0.69", - "xml-rs", -] - [[package]] name = "serde-xml-rs" version = "0.8.2" @@ -15448,22 +13709,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.221" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c459bc0a14c840cb403fc14b148620de1e0778c96ecd6e0c8c3cacb6d8d00fe" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.221" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6185cf75117e20e62b1ff867b9518577271e58abe0037c40bb4794969355ab0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -15474,7 +13735,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -15531,7 +13792,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -15545,9 +13806,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -15564,51 +13825,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.13.0", - "schemars 0.9.0", - "schemars 1.2.1", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e" -dependencies = [ - "darling 0.21.3", - "proc-macro2", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.13.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "servo_arc" version = "0.2.0" @@ -15648,7 +13864,6 @@ checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" dependencies = [ "digest", "sha1", - "zeroize", ] [[package]] @@ -15746,7 +13961,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987" dependencies = [ - "outref 0.1.0", + "outref", ] [[package]] @@ -15755,6 +13970,16 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + [[package]] name = "simd_helpers" version = "0.1.0" @@ -15770,12 +13995,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "simple-file-manifest" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd19be0257552dd56d1bb6946f89f193c6e5b9f13cc9327c4bc84a357507c74" - [[package]] name = "simplecss" version = "0.2.2" @@ -15830,7 +14049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb251b407f50028476a600541542b605bb864d35d9ee1de4f6cab45d88475e6d" dependencies = [ "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -15884,7 +14103,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -15912,29 +14131,6 @@ dependencies = [ "serde", ] -[[package]] -name = "snafu" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" -dependencies = [ - "backtrace", - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "socket2" version = "0.5.10" @@ -15947,23 +14143,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "socks" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" -dependencies = [ - "byteorder", - "libc", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -15992,18 +14177,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "spake2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5482afe85a0b6ce956c945401598dbc527593c77ba51d0a87a586938b1b893a" -dependencies = [ - "curve25519-dalek", - "hkdf", - "rand_core 0.6.4", - "sha2", -] - [[package]] name = "spin" version = "0.9.8" @@ -16028,7 +14201,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", ] [[package]] @@ -16066,7 +14239,7 @@ dependencies = [ "bstr", "bytes", "chrono", - "crc 3.4.0", + "crc", "crossbeam-queue", "either", "event-listener 5.4.1", @@ -16086,7 +14259,7 @@ dependencies = [ "once_cell", "percent-encoding", "rust_decimal", - "rustls 0.23.36", + "rustls", "serde", "serde_json", "sha2", @@ -16111,7 +14284,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -16134,7 +14307,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.114", + "syn 2.0.117", "tokio", "url", ] @@ -16148,11 +14321,11 @@ dependencies = [ "atoi", "base64 0.22.1", "bigdecimal", - "bitflags 2.9.4", + "bitflags 2.11.0", "byteorder", "bytes", "chrono", - "crc 3.4.0", + "crc", "digest", "dotenvy", "either", @@ -16196,10 +14369,10 @@ dependencies = [ "base64 0.22.1", "bigdecimal", "bit-vec 0.6.3", - "bitflags 2.9.4", + "bitflags 2.11.0", "byteorder", "chrono", - "crc 3.4.0", + "crc", "dotenvy", "etcetera", "futures-channel", @@ -16273,19 +14446,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "stacker" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.59.0", -] - [[package]] name = "stackfuture" version = "0.3.1" @@ -16357,17 +14517,6 @@ dependencies = [ "quote", ] -[[package]] -name = "string_enum" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e" -dependencies = [ - "quote", - "swc_macros_common", - "syn 2.0.114", -] - [[package]] name = "stringprep" version = "0.1.5" @@ -16391,16 +14540,7 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros 0.26.4", -] - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros 0.27.2", + "strum_macros", ] [[package]] @@ -16413,19 +14553,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.114", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -16437,7 +14565,7 @@ dependencies = [ "app_units", "arrayvec", "atomic_refcell", - "bitflags 2.9.4", + "bitflags 2.11.0", "byteorder", "cssparser 0.35.0", "derive_more 2.1.1", @@ -16511,8 +14639,8 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.114", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -16521,7 +14649,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c0101ada15990fee6269257538cbcbc6f01462dc4b22d6f820edb6a4427e82" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "stylo_malloc_size_of", ] @@ -16567,7 +14695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f58034d08877fb80a5496301fde9111bc57e761f4a78045222af30ce95ec150f" dependencies = [ "app_units", - "bitflags 2.9.4", + "bitflags 2.11.0", "cssparser 0.35.0", "euclid", "malloc_size_of_derive", @@ -16676,441 +14804,6 @@ dependencies = [ "zeno", ] -[[package]] -name = "swc_allocator" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7eefd2c8b228a8c73056482b2ae4b3a1071fbe07638e3b55ceca8570cc48bb" -dependencies = [ - "allocator-api2", - "bumpalo", - "hashbrown 0.14.5", - "rustc-hash 2.1.1", -] - -[[package]] -name = "swc_atoms" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fda66fe4175cddc27a79643ded7ce297b6b2bf133f038ec5907fb4673e4b2ba" -dependencies = [ - "hstr", - "once_cell", - "serde", -] - -[[package]] -name = "swc_bundler" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737a723cd89c6123b1cac43b0c62acd12e65585a155aefd09c78ce59a47ec617" -dependencies = [ - "anyhow", - "crc 2.1.0", - "indexmap 2.13.0", - "is-macro", - "once_cell", - "petgraph 0.7.1", - "radix_fmt", - "relative-path", - "rustc-hash 2.1.1", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_loader", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_optimization", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_graph_analyzer", - "tracing", -] - -[[package]] -name = "swc_common" -version = "13.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e103c3a47a343a175e9f65603122c5d3c8ec007776b00a4a4bc368a1347c4770" -dependencies = [ - "anyhow", - "ast_node", - "better_scoped_tls", - "bytes-str", - "either", - "from_variant", - "new_debug_unreachable", - "num-bigint", - "once_cell", - "rustc-hash 2.1.1", - "serde", - "siphasher 0.3.11", - "swc_atoms", - "swc_eq_ignore_macros", - "swc_visit", - "termcolor", - "tracing", - "unicode-width 0.1.14", - "url", -] - -[[package]] -name = "swc_config" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d94f41e0f3c4c119a06af5e164674b63ae7eb6d7c1c60e46036c4a548f9fbe44" -dependencies = [ - "anyhow", - "bytes-str", - "dashmap 5.5.3", - "globset", - "indexmap 2.13.0", - "once_cell", - "regex", - "regress", - "rustc-hash 2.1.1", - "serde", - "serde_json", - "swc_config_macro", - "swc_sourcemap", -] - -[[package]] -name = "swc_config_macro" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b416e8ce6de17dc5ea496e10c7012b35bbc0e3fef38d2e065eed936490db0b3" -dependencies = [ - "proc-macro2", - "quote", - "swc_macros_common", - "syn 2.0.114", -] - -[[package]] -name = "swc_ecma_ast" -version = "13.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de29559723afd8436efd427648b22346fd408f741f93c0464aa0815857e69a4" -dependencies = [ - "bitflags 2.9.4", - "is-macro", - "num-bigint", - "once_cell", - "phf 0.11.3", - "rustc-hash 2.1.1", - "serde", - "string_enum", - "swc_atoms", - "swc_common", - "swc_visit", - "unicode-id-start", -] - -[[package]] -name = "swc_ecma_codegen" -version = "15.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a639a205901c8cc0af6f2d7ff4548eb86f0935b1e70662423a9298a304ead6c2" -dependencies = [ - "ascii", - "compact_str 0.7.1", - "memchr", - "num-bigint", - "once_cell", - "regex", - "rustc-hash 2.1.1", - "ryu-js", - "serde", - "swc_allocator", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_codegen_macros", - "swc_sourcemap", - "tracing", -] - -[[package]] -name = "swc_ecma_codegen_macros" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e276dc62c0a2625a560397827989c82a93fd545fcf6f7faec0935a82cc4ddbb8" -dependencies = [ - "proc-macro2", - "swc_macros_common", - "syn 2.0.114", -] - -[[package]] -name = "swc_ecma_lexer" -version = "19.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180810f860c9128eb74b970da8826e0d86fd8f99808c2fbe224cc5c714af2b70" -dependencies = [ - "arrayvec", - "bitflags 2.9.4", - "either", - "new_debug_unreachable", - "num-bigint", - "phf 0.11.3", - "rustc-hash 2.1.1", - "seq-macro", - "serde", - "smallvec", - "smartstring", - "stacker", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "tracing", -] - -[[package]] -name = "swc_ecma_loader" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b08fa5f55ac0188a35a75f27574329e129b06cbb0d517ab7b2093eff45b2745" -dependencies = [ - "anyhow", - "dashmap 5.5.3", - "lru 0.10.1", - "normpath 0.2.0", - "once_cell", - "parking_lot", - "path-clean", - "pathdiff", - "rustc-hash 2.1.1", - "serde", - "serde_json", - "swc_atoms", - "swc_common", - "tracing", -] - -[[package]] -name = "swc_ecma_minifier" -version = "25.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d4c5bc7a0bb058aee1dd1de2d413b5ac5f84a22859af93f927136755116245" -dependencies = [ - "arrayvec", - "bitflags 2.9.4", - "indexmap 2.13.0", - "num-bigint", - "num_cpus", - "once_cell", - "par-core", - "par-iter", - "parking_lot", - "phf 0.11.3", - "radix_fmt", - "rustc-hash 2.1.1", - "ryu-js", - "serde", - "serde_json", - "swc_atoms", - "swc_common", - "swc_config", - "swc_ecma_ast", - "swc_ecma_codegen", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_transforms_optimization", - "swc_ecma_usage_analyzer", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_timer", - "tracing", -] - -[[package]] -name = "swc_ecma_parser" -version = "19.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84ef9d621665426e0d617fa019c45b88118b25a8b0a2a7f47f6b8077ee87486" -dependencies = [ - "either", - "num-bigint", - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_lexer", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_base" -version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2231c70c3cb3ce733e71224b287ce494a60d928b1b0f151129ff18d023cc7b0" -dependencies = [ - "better_scoped_tls", - "indexmap 2.13.0", - "once_cell", - "par-core", - "phf 0.11.3", - "rustc-hash 2.1.1", - "serde", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_utils", - "swc_ecma_visit", - "tracing", -] - -[[package]] -name = "swc_ecma_transforms_optimization" -version = "21.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8cef11f213b127beb33eab4265e8c252e3a874dd4206893bfc836d80b73086" -dependencies = [ - "bytes-str", - "dashmap 5.5.3", - "indexmap 2.13.0", - "once_cell", - "par-core", - "petgraph 0.7.1", - "rustc-hash 2.1.1", - "serde_json", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_parser", - "swc_ecma_transforms_base", - "swc_ecma_utils", - "swc_ecma_visit", - "tracing", -] - -[[package]] -name = "swc_ecma_usage_analyzer" -version = "19.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a370efe3cefc6e07089e312ea06b1d1179efa3f418d6f2947f3a09b8565c29" -dependencies = [ - "bitflags 2.9.4", - "indexmap 2.13.0", - "rustc-hash 2.1.1", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_utils", - "swc_ecma_visit", - "swc_timer", - "tracing", -] - -[[package]] -name = "swc_ecma_utils" -version = "18.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21c9a1dda55bddf62dae2130640452813ad70df9014450852b94753b154885ec" -dependencies = [ - "indexmap 2.13.0", - "num_cpus", - "once_cell", - "par-core", - "rustc-hash 2.1.1", - "ryu-js", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_ecma_visit", - "tracing", -] - -[[package]] -name = "swc_ecma_visit" -version = "13.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ad28e3449b376bfe1f2bde28bfcf305961ba23c1e205bedb03a7c108a1d1ff6" -dependencies = [ - "new_debug_unreachable", - "num-bigint", - "swc_atoms", - "swc_common", - "swc_ecma_ast", - "swc_visit", - "tracing", -] - -[[package]] -name = "swc_eq_ignore_macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "swc_graph_analyzer" -version = "14.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a42314199595da31f253f6ea4dd3d67e9ed0a5745d0b07dad11de200b4ae9336" -dependencies = [ - "auto_impl", - "petgraph 0.7.1", - "rustc-hash 2.1.1", - "tracing", -] - -[[package]] -name = "swc_macros_common" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] - -[[package]] -name = "swc_sourcemap" -version = "9.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de08ef00f816acdd1a58ee8a81c0e1a59eefef2093aefe5611f256fa6b64c4d7" -dependencies = [ - "base64-simd 0.8.0", - "bitvec", - "bytes-str", - "data-encoding", - "debugid", - "if_chain", - "rustc-hash 2.1.1", - "serde", - "serde_json", - "unicode-id-start", - "url", -] - -[[package]] -name = "swc_timer" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db06b46cc832f7cf83c2ce21905fc465d01443a2bdccf63644383e1f5847532" -dependencies = [ - "tracing", -] - -[[package]] -name = "swc_visit" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2" -dependencies = [ - "either", - "new_debug_unreachable", -] - [[package]] name = "syn" version = "1.0.109" @@ -17124,21 +14817,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -17148,18 +14835,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -17168,7 +14843,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -17180,19 +14855,6 @@ dependencies = [ "libc", ] -[[package]] -name = "sysctl" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225e483f02d0ad107168dc57381a8a40c3aeea6abe47f37506931f861643cfa8" -dependencies = [ - "bitflags 1.3.2", - "byteorder", - "libc", - "thiserror 1.0.69", - "walkdir", -] - [[package]] name = "sysinfo" version = "0.35.2" @@ -17221,36 +14883,15 @@ dependencies = [ "windows 0.61.3", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "system-configuration-sys 0.5.0", -] - [[package]] name = "system-configuration" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "core-foundation 0.9.4", - "system-configuration-sys 0.6.0", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", + "system-configuration-sys", ] [[package]] @@ -17302,36 +14943,34 @@ dependencies = [ [[package]] name = "tao" -version = "0.34.5" +version = "0.34.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" +checksum = "6e06d52c379e63da659a483a958110bbde891695a0ecb53e48cc7786d5eda7bb" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.6.2", "core-foundation 0.10.1", - "core-graphics 0.24.0", + "core-graphics 0.25.0", "crossbeam-channel", - "dispatch", + "dispatch2", "dlopen2", "dpi", "gdkwayland-sys", "gdkx11-sys", "gtk", "jni 0.21.1", - "lazy_static", "libc", "log", "ndk 0.9.0", "ndk-context", "ndk-sys 0.6.0+11769913", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-foundation 0.3.2", "once_cell", "parking_lot", "raw-window-handle 0.5.2", "raw-window-handle 0.6.2", - "scopeguard", "tao-macros", "unicode-segmentation", "url", @@ -17349,7 +14988,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -17377,9 +15016,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "target-lexicon" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dd07eb858a2067e2f3c7155d54e929265c264e6f37efe3ee7a8d1b5a1dd0ba" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" dependencies = [ "serde", ] @@ -17390,114 +15029,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "591ef38edfb78ca4771ee32cf494cb8771944bee237a9b91fc9c1424ac4b777b" -[[package]] -name = "tauri-bundler" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c47183072f4d7c9248d6cb28fd33d0e6af2d399db9d55fb98022c2e5316f7d6" -dependencies = [ - "anyhow", - "ar", - "bitness", - "dirs 6.0.0", - "dunce", - "flate2", - "glob", - "handlebars", - "heck 0.5.0", - "hex", - "image", - "log", - "md5", - "os_pipe", - "plist", - "regex", - "rpm", - "semver", - "serde", - "serde_json", - "sha1", - "sha2", - "strsim", - "tar", - "tauri-icns", - "tauri-macos-sign", - "tauri-utils", - "tempfile", - "thiserror 2.0.18", - "time", - "ureq", - "url", - "uuid", - "walkdir", - "which 7.0.3", - "windows-registry 0.5.3", - "windows-sys 0.60.2", - "zip 4.6.1", -] - -[[package]] -name = "tauri-icns" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b7eb4d0d43724ba9ba6a6717420ee68aee377816a3edbb45db8c18862b1431" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "tauri-macos-sign" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e9edbc522842252c79713cfa3e1b0cf3f694f1428c749e939f8dcbdf62864a" -dependencies = [ - "anyhow", - "apple-codesign", - "chrono", - "dirs 6.0.0", - "log", - "once-cell-regex", - "os_pipe", - "p12", - "plist", - "rand 0.9.2", - "serde", - "serde_json", - "tempfile", - "x509-certificate", -] - -[[package]] -name = "tauri-utils" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" -dependencies = [ - "anyhow", - "ctor", - "dunce", - "glob", - "http 1.4.0", - "infer", - "json-patch", - "log", - "memchr", - "phf 0.11.3", - "regex", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "thiserror 2.0.18", - "toml 0.8.23", - "url", - "urlpattern", - "uuid", - "walkdir", -] - [[package]] name = "tempfile" version = "3.19.1" @@ -17507,7 +15038,7 @@ dependencies = [ "fastrand", "getrandom 0.3.4", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -17547,7 +15078,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.60.2", ] @@ -17583,7 +15114,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -17594,7 +15125,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -17751,15 +15282,15 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.114", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -17767,7 +15298,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -17775,13 +15306,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -17794,23 +15325,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.36", + "rustls", "tokio", ] @@ -17826,18 +15347,6 @@ dependencies = [ "tokio-util", ] -[[package]] -name = "tokio-tungstenite" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.27.0", -] - [[package]] name = "tokio-tungstenite" version = "0.28.0" @@ -17882,17 +15391,32 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.6" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap 2.13.0", "serde_core", - "serde_spanned 1.0.1", - "toml_datetime 0.7.1", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.14", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 1.0.0+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", ] [[package]] @@ -17906,9 +15430,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.1" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] @@ -17946,28 +15479,40 @@ dependencies = [ "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] name = "toml_edit" -version = "0.23.5" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.13.0", - "toml_datetime 0.7.1", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow 0.7.14", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 1.0.0+spec-1.1.0", + "toml_parser", + "winnow 0.7.15", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "winnow 0.7.14", + "winnow 0.7.15", ] [[package]] @@ -17993,11 +15538,11 @@ dependencies = [ "axum 0.7.9", "base64 0.22.1", "bytes", - "h2 0.4.13", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-timeout", "hyper-util", "percent-encoding", @@ -18041,7 +15586,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -18056,12 +15601,12 @@ checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", "base64 0.22.1", - "bitflags 2.9.4", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "http-range-header", "httpdate", @@ -18111,7 +15656,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -18180,9 +15725,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -18217,30 +15762,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" dependencies = [ "crossbeam-channel", - "dirs 6.0.0", + "dirs", "libappindicator", "muda", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-core-graphics", "objc2-foundation 0.3.2", "once_cell", - "png", + "png 0.17.16", "thiserror 2.0.18", "windows-sys 0.60.2", ] -[[package]] -name = "triomphe" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" -dependencies = [ - "serde", - "stable_deref_trait", -] - [[package]] name = "try-lock" version = "0.2.5" @@ -18249,9 +15784,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f614c21bd3a61bad9501d75cbb7686f00386c806d7f456778432c25cf86948a" +checksum = "47c635f0191bd3a2941013e5062667100969f8c4e9cd787c14f977265d73616e" dependencies = [ "dissimilar", "glob", @@ -18260,7 +15795,7 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml 0.9.6", + "toml 1.0.6+spec-1.1.0", ] [[package]] @@ -18284,28 +15819,6 @@ dependencies = [ "core_maths", ] -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.4.0", - "httparse", - "log", - "rand 0.8.5", - "rustls 0.22.4", - "rustls-native-certs 0.7.3", - "rustls-pki-types", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.27.0" @@ -18314,12 +15827,10 @@ checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", - "native-tls", "rand 0.9.2", - "rustls 0.23.36", "sha1", "thiserror 2.0.18", "utf-8", @@ -18333,34 +15844,17 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", + "native-tls", "rand 0.9.2", + "rustls", "sha1", "thiserror 2.0.18", "utf-8", ] -[[package]] -name = "twofish" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" -dependencies = [ - "cipher", -] - -[[package]] -name = "twox-hash" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if", - "static_assertions", -] - [[package]] name = "twox-hash" version = "2.1.2" @@ -18384,15 +15878,6 @@ name = "typewit" version = "1.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c1ae7cc0fdb8b842d65d127cb981574b0d2b249b74d1c7a2986863dc134f71" -dependencies = [ - "typewit_proc_macros", -] - -[[package]] -name = "typewit_proc_macros" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6" [[package]] name = "ucd-trie" @@ -18402,13 +15887,13 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uds_windows" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "51b70b87d15e91f553711b40df3048faf27a7a04e01e0ddc0cf9309f0af7c2ca" dependencies = [ "memoffset", "tempfile", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -18429,65 +15914,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "uname" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" -dependencies = [ - "libc", -] - -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "version_check", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicase" version = "2.9.0" @@ -18530,17 +15956,11 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" -[[package]] -name = "unicode-id-start" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" - [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-linebreak" @@ -18620,47 +16040,12 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" -dependencies = [ - "base64 0.22.1", - "log", - "percent-encoding", - "rustls 0.23.36", - "rustls-pki-types", - "socks", - "ureq-proto", - "utf-8", - "webpki-roots 1.0.6", -] - -[[package]] -name = "ureq-proto" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" -dependencies = [ - "base64 0.22.1", - "http 1.4.0", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.8" @@ -18680,18 +16065,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - [[package]] name = "usvg" version = "0.45.1" @@ -18745,11 +16118,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.20.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "serde_core", "sha1_smol", @@ -18787,7 +16160,7 @@ checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -18812,7 +16185,7 @@ dependencies = [ "futures-intrusive", "log", "peniko", - "png", + "png 0.17.16", "skrifa", "static_assertions", "thiserror 2.0.18", @@ -18896,12 +16269,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -[[package]] -name = "vsimd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" - [[package]] name = "walkdir" version = "2.5.0" @@ -18938,7 +16305,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -18969,7 +16336,7 @@ checksum = "59195a1db0e95b920366d949ba5e0d3fc0e70b67c09be15ce5abb790106b0571" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -18993,6 +16360,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasite" version = "0.1.0" @@ -19001,9 +16377,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -19012,22 +16388,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-externref-xform" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940542c5cdbe96c35f98b5da5c65fb9d18df55a0cb1d81fc5ca4acc4fda4d61c" -dependencies = [ - "anyhow", - "walrus", - "wasm-bindgen-wasm-conventions", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -19039,9 +16404,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -19049,31 +16414,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.58" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45649196a53b0b7a15101d845d44d2dda7374fc1b5b5e2bbf58b7577ff4b346d" +checksum = "6311c867385cc7d5602463b31825d454d0837a3aba7cdb5e56d5201792a3f7fe" dependencies = [ "async-trait", "cast", @@ -19093,33 +16458,20 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.58" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f579cdd0123ac74b94e1a4a72bd963cf30ebac343f2df347da0b8df24cdebed2" +checksum = "67008cdde4769831958536b0f11b3bdd0380bde882be17fff9c2f34bb4549abd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8145dd1593bf0fb137dbfa85b8be79ec560a447298955877804640e40c2d6ea" - -[[package]] -name = "wasm-bindgen-wasm-conventions" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c24fcaa34d2d84407122cfb1d3f37c3586756cf462be18e049b49245a16c08" -dependencies = [ - "anyhow", - "leb128", - "log", - "walrus", - "wasmparser 0.214.0", -] +checksum = "cfe29135b180b72b04c74aa97b2b4a2ef275161eff9a6c7955ea9eaedc7e1d4e" [[package]] name = "wasm-encoder" @@ -19132,12 +16484,24 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.235.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc393c395cb621367ff02d854179882b9a351b4e0c93d1397e6090b53a5c2a" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser 0.235.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", ] [[package]] @@ -19167,7 +16531,7 @@ dependencies = [ "futures", "getrandom 0.3.4", "js-sys", - "reqwest 0.12.28", + "reqwest", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -19182,7 +16546,7 @@ dependencies = [ "proc-macro2", "quote", "sha2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -19222,7 +16586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5309c1090e3e84dad0d382f42064e9933fdaedb87e468cc239f0eabea73ddcb6" dependencies = [ "ahash 0.8.12", - "bitflags 2.9.4", + "bitflags 2.11.0", "hashbrown 0.14.5", "indexmap 2.13.0", "semver", @@ -19235,7 +16599,7 @@ version = "0.235.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap 2.13.0", "semver", @@ -19248,18 +16612,30 @@ version = "0.236.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", ] [[package]] name = "wayland-backend" -version = "0.3.12" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" +checksum = "aa75f400b7f719bcd68b3f47cd939ba654cedeef690f486db71331eec4c6a406" dependencies = [ "cc", "downcast-rs 1.2.1", - "rustix 1.1.3", + "rustix 1.1.4", "scoped-tls", "smallvec", "wayland-sys", @@ -19267,12 +16643,12 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.12" +version = "0.31.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" +checksum = "ab51d9f7c071abeee76007e2b742499e535148035bb835f97aaed1338cf516c3" dependencies = [ - "bitflags 2.9.4", - "rustix 1.1.3", + "bitflags 2.11.0", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] @@ -19283,29 +16659,29 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.12" +version = "0.31.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5864c4b5b6064b06b1e8b74ead4a98a6c45a285fe7a0e784d24735f011fdb078" +checksum = "4b3298683470fbdc6ca40151dfc48c8f2fd4c41a26e13042f801f85002384091" dependencies = [ - "rustix 1.1.3", + "rustix 1.1.4", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.32.10" +version = "0.32.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" +checksum = "b23b5df31ceff1328f06ac607591d5ba360cf58f90c8fad4ac8d3a55a3c4aec7" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -19313,11 +16689,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa98634619300a535a9a97f338aed9a5ff1e01a461943e8346ff4ae26007306b" +checksum = "d392fc283a87774afc9beefcd6f931582bb97fe0e6ced0b306a62cb1d026527c" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -19326,11 +16702,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" +checksum = "78248e4cc0eff8163370ba5c158630dcae1f3497a586b826eca2ef5f348d6235" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -19339,20 +16715,20 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.8" +version = "0.31.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" +checksum = "c86287151a309799b821ca709b7345a048a2956af05957c89cb824ab919fa4e3" dependencies = [ "proc-macro2", - "quick-xml 0.38.4", + "quick-xml 0.39.2", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" +checksum = "374f6b70e8e0d6bf9461a32988fd553b59ff630964924dad6e4a4eb6bd538d17" dependencies = [ "dlib", "log", @@ -19362,9 +16738,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -19394,15 +16770,15 @@ dependencies = [ [[package]] name = "webbrowser" -version = "1.0.6" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f1243ef785213e3a32fa0396093424a3a6ea566f9948497e5a2309261a4c97" +checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16" dependencies = [ "core-foundation 0.10.1", - "jni 0.21.1", + "jni 0.22.3", "log", "ndk-context", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-foundation 0.3.2", "url", "web-sys", @@ -19452,12 +16828,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.11" @@ -19498,7 +16868,7 @@ checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -19550,7 +16920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70b6ff82bbf6e9206828e1a3178e851f8c20f1c9028e74dd3a8090741ccd5798" dependencies = [ "arrayvec", - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg-if", "cfg_aliases 0.2.1", "document-features", @@ -19580,7 +16950,7 @@ checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" dependencies = [ "arrayvec", "bit-vec 0.6.3", - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg_aliases 0.1.1", "codespan-reporting 0.11.1", "indexmap 2.13.0", @@ -19607,7 +16977,7 @@ dependencies = [ "arrayvec", "bit-set 0.8.0", "bit-vec 0.8.0", - "bitflags 2.9.4", + "bitflags 2.11.0", "cfg_aliases 0.2.1", "document-features", "hashbrown 0.15.5", @@ -19676,7 +17046,7 @@ dependencies = [ "arrayvec", "ash 0.37.3+1.3.251", "bit-set 0.5.3", - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "cfg_aliases 0.1.1", "core-graphics-types 0.1.3", @@ -19721,7 +17091,7 @@ dependencies = [ "arrayvec", "ash 0.38.0+1.3.281", "bit-set 0.8.0", - "bitflags 2.9.4", + "bitflags 2.11.0", "block", "bytemuck", "cfg-if", @@ -19778,7 +17148,7 @@ version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "js-sys", "web-sys", ] @@ -19789,7 +17159,7 @@ version = "26.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca7a8d8af57c18f57d393601a1fb159ace8b2328f1b6b5f80893f7d672c9ae2" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "bytemuck", "js-sys", "log", @@ -19810,25 +17180,11 @@ dependencies = [ [[package]] name = "which" -version = "7.0.3" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459" dependencies = [ - "either", - "env_home", - "rustix 1.1.3", - "winsafe", -] - -[[package]] -name = "which" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" -dependencies = [ - "env_home", - "rustix 1.1.3", - "winsafe", + "libc", ] [[package]] @@ -20056,7 +17412,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -20067,7 +17423,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -20078,7 +17434,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -20089,7 +17445,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -20124,17 +17480,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - [[package]] name = "windows-registry" version = "0.6.1" @@ -20536,14 +17881,14 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" dependencies = [ "ahash 0.8.12", "android-activity", "atomic-waker", - "bitflags 2.9.4", + "bitflags 2.11.0", "block2 0.5.1", "bytemuck", "calloop", @@ -20597,34 +17942,100 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.244.0", + "wasm-metadata", + "wasmparser 0.244.0", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.244.0", +] [[package]] name = "writeable" @@ -20642,18 +18053,18 @@ dependencies = [ "block2 0.6.2", "cookie", "crossbeam-channel", - "dirs 6.0.0", + "dirs", "dpi", "dunce", "gtk", "html5ever 0.29.1", - "http 1.4.0", + "http", "javascriptcore-rs", "jni 0.21.1", "kuchikiki", "libc", "ndk 0.9.0", - "objc2 0.6.3", + "objc2 0.6.4", "objc2-app-kit 0.3.2", "objc2-core-foundation", "objc2-foundation 0.3.2", @@ -20729,7 +18140,7 @@ dependencies = [ "libc", "libloading 0.8.9", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "x11rb-protocol", ] @@ -20739,58 +18150,6 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek", - "rand_core 0.6.4", - "serde", - "zeroize", -] - -[[package]] -name = "x448" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" -dependencies = [ - "ed448-goldilocks", - "hex", - "rand_core 0.5.1", -] - -[[package]] -name = "x509" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3cec94c3999f31341553f358ef55f65fc031291a022cd42ec0ce7219560c76" -dependencies = [ - "chrono", - "cookie-factory", -] - -[[package]] -name = "x509-certificate" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85" -dependencies = [ - "bcder", - "bytes", - "chrono", - "der", - "hex", - "pem", - "ring", - "signature", - "spki", - "thiserror 1.0.69", - "zeroize", -] - [[package]] name = "xattr" version = "1.6.1" @@ -20798,7 +18157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.3", + "rustix 1.1.4", ] [[package]] @@ -20823,7 +18182,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.11.0", "dlib", "log", "once_cell", @@ -20870,27 +18229,12 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" - [[package]] name = "yazi" version = "0.2.1" @@ -20927,8 +18271,8 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -20987,7 +18331,7 @@ checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "zbus-lockstep", "zbus_xml", "zvariant", @@ -20999,10 +18343,10 @@ version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "zvariant_utils", ] @@ -21038,22 +18382,22 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -21073,8 +18417,8 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -21082,20 +18426,6 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.114", -] [[package]] name = "zerotrie" @@ -21128,19 +18458,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "byteorder", - "crc32fast", - "crossbeam-utils", - "flate2", + "syn 2.0.117", ] [[package]] @@ -21175,17 +18493,6 @@ dependencies = [ "zopfli", ] -[[package]] -name = "zip_structs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce824a6bfffe8942820fa36d24973b7c83a40896749a42e33de0abdd11750ee5" -dependencies = [ - "byteorder", - "bytesize", - "thiserror 1.0.69", -] - [[package]] name = "zipsign-api" version = "0.1.5" @@ -21199,15 +18506,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zopfli" @@ -21292,10 +18599,10 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ - "proc-macro-crate 3.4.0", + "proc-macro-crate 3.5.0", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "zvariant_utils", ] @@ -21307,5 +18614,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] diff --git a/Cargo.toml b/Cargo.toml index 1ce425651..1739b85c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ "packages/core", "packages/core-types", "packages/cli", - "packages/cli-opt", "packages/cli-config", "packages/cli-telemetry", "packages/core-macro", @@ -22,7 +21,6 @@ members = [ "packages/interpreter", "packages/liveview", "packages/autofmt", - "packages/check", "packages/devtools-types", "packages/devtools", "packages/document", @@ -140,7 +138,6 @@ dioxus-desktop = { path = "packages/desktop", version = "0.7.3", default-feature dioxus-interpreter-js = { path = "packages/interpreter", version = "0.7.3" } dioxus-liveview = { path = "packages/liveview", version = "0.7.3" } dioxus-autofmt = { path = "packages/autofmt", version = "0.7.3" } -dioxus-check = { path = "packages/check", version = "0.7.3" } dioxus-rsx = { path = "packages/rsx", version = "0.7.3" } dioxus-rsx-hotreload = { path = "packages/rsx-hotreload", version = "0.7.3" } dioxus-rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.7.3" } @@ -164,7 +161,6 @@ generational-box = { path = "packages/generational-box", version = "0.7.3" } lazy-js-bundle = { path = "packages/lazy-js-bundle", version = "0.7.3" } # cli -dioxus-cli-opt = { path = "packages/cli-opt", version = "0.7.3" } dioxus-cli-telemetry = { path = "packages/cli-telemetry", version = "0.7.3" } dioxus-cli-config = { path = "packages/cli-config", version = "0.7.3" } @@ -229,7 +225,7 @@ futures = "0.3.31" futures-channel = "0.3.31" futures-util = { version = "0.3", default-features = false } rustc-hash = "2.1.1" -wasm-bindgen = "0.2.105" +wasm-bindgen = "0.2.114" wasm-bindgen-futures = "0.4.50" js-sys = "0.3" web-sys = { version = "0.3.77", default-features = false } @@ -238,9 +234,12 @@ thiserror = "2.0.12" prettyplease = { version = "0.2.35", features = ["verbatim"] } const_format = "0.2.34" cargo_toml = { version = "0.22.1" } -tauri-utils = { version = "=2.5.0" } -tauri-macos-sign = { version = "=2.2.0" } -tauri-bundler = { version = "=2.5.0" } +icns = "0.4.0" +image = "0.25" +zip = { version = "4", default-features = false, features = ["deflate"] } +sha1 = "0.10" +md5 = "0.8.0" +rpm = { version = "0.19.0", default-features = false, features = ["gzip-compression"] } lru = "0.16.0" async-trait = "0.1.88" axum = { version = "0.8.4", default-features = false } @@ -282,8 +281,8 @@ ciborium = "0.2.2" base64 = "0.22.1" uuid = "1.17.0" convert_case = "0.8.0" -tungstenite = { version = "0.27.0" } -tokio-tungstenite = { version = "0.27.0" } +tungstenite = { version = "0.28.0" } +tokio-tungstenite = { version = "0.28.0" } gloo-timers = "0.3.0" internment = { version = "0.8.6" } proc-macro2-diagnostics = { version = "0.10", default-features = false } @@ -359,7 +358,7 @@ gloo-dialogs = "0.2.0" ansi-to-tui = "7.0" ansi-to-html = "0.2.2" path-absolutize = "3.1" -crossterm = { version = "0.29.0" } +crossterm = { version = "0.28.1" } ratatui = { version = "0.29.0" } shell-words = "1.1.0" @@ -367,6 +366,11 @@ shell-words = "1.1.0" keyboard-types = { version = "0.7", default-features = false } winit = { version = "0.30.11", features = ["rwh_06"] } +# reduce debug symbols to line tables only - speeds up compiles and shrinks artifacts +# backtraces still show file:line, but debugger can't inspect variables +[profile.dev] +debug = "line-tables-only" + # our release profile should be fast to compile and fast to run # when we ship our CI builds, we turn on LTO which improves perf leftover by turning on incremental [profile.release] diff --git a/_typos.toml b/_typos.toml index 3d558d515..81648ac4e 100644 --- a/_typos.toml +++ b/_typos.toml @@ -10,6 +10,8 @@ seeked = "seeked" udid = "udid" # Part of Blitz's API unparented = "unparented" +# bundle type / archive +flate2="flate2" [files] extend-exclude = ["notes/translations/*", "CHANGELOG.md", "*.js"] diff --git a/examples/01-app-demos/hotdog/Cargo.toml b/examples/01-app-demos/hotdog/Cargo.toml index 537a171ff..f29b3e8a1 100644 --- a/examples/01-app-demos/hotdog/Cargo.toml +++ b/examples/01-app-demos/hotdog/Cargo.toml @@ -10,7 +10,7 @@ dioxus = { workspace = true, features = ["fullstack", "router"] } reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -rusqlite = { version = "0.32.0", optional = true } +rusqlite = { version = "0.32.0", optional = true, features = ["bundled"] } # Bundle SQLite so Windows/MSVC builds do not require an external sqlite3.lib. anyhow = { workspace = true } [features] diff --git a/flake.lock b/flake.lock index cb01f9be0..e5f71db0a 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,20 @@ { "nodes": { + "crane": { + "locked": { + "lastModified": 1773189535, + "narHash": "sha256-E1G/Or6MWeP+L6mpQ0iTFLpzSzlpGrITfU2220Gq47g=", + "owner": "ipetkov", + "repo": "crane", + "rev": "6fa2fb4cf4a89ba49fc9dd5a3eb6cde99d388269", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "flake-parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" @@ -67,6 +82,7 @@ }, "root": { "inputs": { + "crane": "crane", "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", "rust-overlay": "rust-overlay", diff --git a/flake.nix b/flake.nix index 7728cb86e..6ac865568 100644 --- a/flake.nix +++ b/flake.nix @@ -5,8 +5,7 @@ systems.url = "github:nix-systems/default"; rust-overlay.url = "github:oxalica/rust-overlay"; - # crane.url = "github:ipetkov/crane"; - # crane.inputs.nixpkgs.follows = "nixpkgs"; + crane.url = "github:ipetkov/crane"; }; outputs = @@ -16,14 +15,18 @@ perSystem = { - config, self', - pkgs, - lib, system, ... }: let + pkgs = import inputs.nixpkgs { + inherit system; + overlays = [ + inputs.rust-overlay.overlays.default + ]; + }; + lib = pkgs.lib; rustToolchain = pkgs.rust-bin.stable.latest.default.override { extensions = [ "rust-src" @@ -31,6 +34,7 @@ "clippy" ]; }; + craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rustToolchain; rustBuildInputs = [ pkgs.openssl pkgs.libiconv @@ -49,6 +53,26 @@ ]; cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); + fullSrc = pkgs.lib.cleanSource ./.; + cargoSrc = craneLib.cleanCargoSource fullSrc; + + commonArgs = { + src = fullSrc; + strictDeps = true; + buildInputs = rustBuildInputs; + nativeBuildInputs = [ + pkgs.pkg-config + ]; + }; + + cargoArtifacts = craneLib.buildDepsOnly ( + commonArgs + // { + src = cargoSrc; + pname = "dioxus-deps"; + version = cargoToml.package.version; + } + ); rustPackage = package: @@ -56,53 +80,32 @@ binary ? package, features ? [ ], }: - (pkgs.makeRustPlatform { - cargo = rustToolchain; - rustc = rustToolchain; - }).buildRustPackage - { + craneLib.buildPackage ( + commonArgs + // { pname = package; version = cargoToml.package.version; - src = pkgs.lib.cleanSource ./.; - cargoLock.lockFile = ./Cargo.lock; - buildInputs = rustBuildInputs; - nativeBuildInputs = [ - rustToolchain - pkgs.pkg-config - ]; - buildPhase = '' - mkdir -p .cargo - cp ${./Cargo.lock} Cargo.lock - cargo build --release --package ${package} ${ - lib.concatStringsSep " " (map (f: "--features ${f}") features) - } - ''; - installPhase = '' + inherit cargoArtifacts; + cargoExtraArgs = "--locked --package ${package} ${ + lib.concatStringsSep " " (map (f: "--features ${f}") features) + }"; + doCheck = false; # Disable tests to avoid building deps for them + installPhaseCommand = '' mkdir -p $out/bin - ls -alR target/release cp target/release/${binary} $out/bin/ ''; - doCheck = false; # Disable tests to avoid building deps for them - }; - - # This is useful when building crates as packages - # Note that it does require a `Cargo.lock` which this repo does not have - # craneLib = (inputs.crane.mkLib pkgs).overrideToolchain rustToolchain; + } + ); in { - _module.args.pkgs = import inputs.nixpkgs { - inherit system; - overlays = [ - inputs.rust-overlay.overlays.default - ]; - }; - packages.dioxus-cli = ( rustPackage "dioxus-cli" { binary = "dx"; features = [ "no-downloads" ]; } ); + packages.default = self'.packages.dioxus-cli; + checks.dioxus-cli = self'.packages.dioxus-cli; devShells.default = pkgs.mkShell { name = "dioxus-dev"; diff --git a/notes/releases/0.7.0-alpha.0.md b/notes/releases/0.7.0-alpha.0.md index f66857fe9..fb04ce278 100644 --- a/notes/releases/0.7.0-alpha.0.md +++ b/notes/releases/0.7.0-alpha.0.md @@ -135,7 +135,7 @@ Blitz combines a number of exciting projects to bring customizable HTML renderin Blitz is an extremely capable renderer, often producing results indistinguishable from browsers like Chrome and Safari: -Not every CSS feature is supported yet, with some bugs like incorrect writing direction or the occasional layout quirk. Our support matrix is here: https://blitz-website.fly.dev/status/css +Not every CSS feature is supported yet, with some bugs like incorrect writing direction or the occasional layout quirk. The samples that Blitz can create are quite incredible. Servo’s website: diff --git a/packages/asset-resolver/src/native.rs b/packages/asset-resolver/src/native.rs index 55b2b43a9..954c60988 100644 --- a/packages/asset-resolver/src/native.rs +++ b/packages/asset-resolver/src/native.rs @@ -145,15 +145,15 @@ pub fn serve_asset(path: &str) -> Result>, AssetServeError> { /// - [x] iOS /// - [x] Windows /// - [x] Linux (appimage) -/// - [ ] Linux (rpm) +/// - [x] Linux (rpm) /// - [x] Linux (deb) -/// - [ ] Android +/// - [x] Android (returns current exe.) #[allow(unreachable_code)] fn get_asset_root() -> PathBuf { let cur_exe = std::env::current_exe().unwrap(); - #[cfg(target_os = "macos")] - { + // In macOS bundles, the assets are in the `Resources` directory + if cfg!(target_os = "macos") { return cur_exe .parent() .unwrap() @@ -162,20 +162,19 @@ fn get_asset_root() -> PathBuf { .join("Resources"); } - #[cfg(target_os = "linux")] - { - // In linux bundles, the assets are placed in the lib/$product_name directory - // bin/ - // main - // lib/ - // $product_name/ - // assets/ + // In linux bundles, the assets are placed in the lib/$product_name directory + // bin/ + // main + // lib/ + // $product_name/ + // assets/ + if cfg!(target_os = "linux") { if let Some(product_name) = dioxus_cli_config::product_name() { - let lib_asset_path = || { - let path = cur_exe.parent()?.parent()?.join("lib").join(product_name); - path.exists().then_some(path) - }; - if let Some(asset_dir) = lib_asset_path() { + let path = cur_exe + .parent() + .and_then(|parent| parent.parent()) + .map(|root| root.join("lib").join(product_name)); + if let Some(asset_dir) = path.filter(|path| path.exists()) { return asset_dir; } } diff --git a/packages/check/Cargo.toml b/packages/check/Cargo.toml deleted file mode 100644 index 44327d5b0..000000000 --- a/packages/check/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "dioxus-check" -version = { workspace = true } -edition = "2021" -authors = ["Dioxus Labs"] -description = "Checks Dioxus RSX files for issues" -license = "MIT/Apache-2.0" -repository = "https://github.com/DioxusLabs/dioxus/" -homepage = "https://dioxuslabs.com" -keywords = ["web", "desktop", "mobile", "gui", "wasm"] - -[dependencies] -proc-macro2 = { workspace = true, features = ["span-locations"] } -quote = {workspace = true } -syn = { workspace = true, features = ["full", "extra-traits", "visit"] } -owo-colors = { workspace = true, features = ["supports-colors"] } - -[dev-dependencies] -indoc = "2.0.6" -pretty_assertions = { workspace = true } diff --git a/packages/check/README.md b/packages/check/README.md deleted file mode 100644 index 244b1b257..000000000 --- a/packages/check/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# dioxus-check - -[![Crates.io][crates-badge]][crates-url] -[![MIT licensed][mit-badge]][mit-url] -[![Build Status][actions-badge]][actions-url] -[![Discord chat][discord-badge]][discord-url] - -[crates-badge]: https://img.shields.io/crates/v/dioxus-autofmt.svg -[crates-url]: https://crates.io/crates/dioxus-check -[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg -[mit-url]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT -[actions-badge]: https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg -[actions-url]: https://github.com/dioxuslabs/dioxus/actions?query=workflow%3ACI+branch%3Amaster -[discord-badge]: https://img.shields.io/discord/899851952891002890.svg?logo=discord&style=flat-square -[discord-url]: https://discord.gg/XgGxMSkvUM - -[Website](https://dioxuslabs.com) | -[Guides](https://dioxuslabs.com/learn/0.7/) | -[API Docs](https://docs.rs/dioxus-check) | -[Chat](https://discord.gg/XgGxMSkvUM) - -## Overview - -`dioxus-check` analyzes Dioxus source code and reports errors and warnings. Primarily, it enforces the [Rules of Hooks](https://dioxuslabs.com/learn/0.7/essentials/basics/hooks#rules-of-hooks). - -## Contributing - -- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues). -- Join the discord and ask questions! - -## License - -This project is licensed under the [MIT license]. - -[mit license]: https://github.com/dioxuslabs/dioxus/blob/main/LICENSE-MIT - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you shall be licensed as MIT without any additional -terms or conditions. diff --git a/packages/check/src/lib.rs b/packages/check/src/lib.rs deleted file mode 100644 index 43a164514..000000000 --- a/packages/check/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![doc = include_str!("../README.md")] -#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] -#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] - -mod check; -mod issues; -mod metadata; - -pub use check::check_file; -pub use issues::{Issue, IssueReport}; diff --git a/packages/cli-opt/Cargo.toml b/packages/cli-opt/Cargo.toml deleted file mode 100644 index d9a93a88b..000000000 --- a/packages/cli-opt/Cargo.toml +++ /dev/null @@ -1,81 +0,0 @@ -[package] -name = "dioxus-cli-opt" -edition = "2021" -version.workspace = true -authors = ["Jonathan Kelley", "Evan Almloff"] -license = "MIT OR Apache-2.0" -repository = "https://github.com/DioxusLabs/dioxus/" -homepage = "https://dioxuslabs.com" -description = "CLI optimizations for dioxus-cli" -keywords = ["dom", "ui", "gui", "react"] - -[dependencies] -anyhow = { workspace = true } -manganis = { workspace = true } -manganis-core = { workspace = true } -object = { workspace = true, features = ["wasm"] } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -const-serialize = { workspace = true, features = ["serde"] } -tracing = { workspace = true } -rayon = "1.10.0" - -# Image compression/conversion -# - JPEG -mozjpeg = { version = "0.10.13", default-features = false, features = [ - "parallel", -] } -# - PNG -imagequant = "4.4.0" -png = "0.17.16" - -# Image format/conversion -image = { version = "0.25", features = ["avif"] } - -# CSS Minification -lightningcss = { version = "1.0.0-alpha.67", features = [ - "browserslist", - "into_owned", -] } - -# SCSS Processing -grass = "0.13.4" -codemap = "0.1.3" - -# Js minification - swc has introduces minor versions with breaking changes in the past so we pin all of their crates -# Root dependencies -swc_bundler = { version = "=24.0.0", default-features = false } -swc_common = { version = "=13.0.4", features = [ - "tty-emitter", -], default-features = false } -swc_ecma_ast = { version = "=13.0.1", default-features = false } -swc_ecma_codegen = { version = "=15.0.2", default-features = false } -swc_ecma_loader = { version = "=13.0.0", features = [ - "cache", - "node", -], default-features = false } -swc_ecma_minifier = { version = "=25.0.0", default-features = false } -swc_ecma_parser = { version = "=19.0.0", default-features = false } -swc_ecma_transforms_base = { version = "=20.0.0", default-features = false } -swc_ecma_visit = { version = "=13.0.0", default-features = false } - -# Pinned from cargo tree --prefix none | grep -Eo 'swc[^ ]+ [^ ]+' | sort -u -swc_allocator = { version = "4.0.1" } -swc_atoms = { version = "6.0.1" } -swc_config = { version = "3.1.1" } -swc_config_macro = { version = "1.0.1" } -swc_ecma_codegen_macros = { version = "2.0.2" } -swc_ecma_lexer = { version = "19.0.1" } -swc_ecma_transforms_optimization = { version = "21.0.0" } -swc_ecma_usage_analyzer = { version = "19.0.2" } -swc_ecma_utils = { version = "18.1.0" } -swc_eq_ignore_macros = { version = "1.0.1" } -swc_graph_analyzer = { version = "14.0.1" } -swc_macros_common = { version = "1.0.1" } -swc_sourcemap = { version = "9.3.2" } -swc_timer = { version = "1.0.0" } -swc_visit = { version = "2.0.1" } -browserslist-rs = { version = "=0.19.0" } - -[build-dependencies] -built = { version = "0.8.0", features = ["git2"] } diff --git a/packages/cli-opt/build.rs b/packages/cli-opt/build.rs deleted file mode 100644 index d8f91cb91..000000000 --- a/packages/cli-opt/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); -} diff --git a/packages/cli-opt/src/build_info.rs b/packages/cli-opt/src/build_info.rs deleted file mode 100644 index 17332cac8..000000000 --- a/packages/cli-opt/src/build_info.rs +++ /dev/null @@ -1,10 +0,0 @@ -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); - -pub(crate) fn version() -> String { - format!( - "{} ({})", - PKG_VERSION, - GIT_COMMIT_HASH_SHORT.unwrap_or("was built without git repository") - ) -} diff --git a/packages/cli-opt/src/image/jpg.rs b/packages/cli-opt/src/image/jpg.rs deleted file mode 100644 index abe0c6bcf..000000000 --- a/packages/cli-opt/src/image/jpg.rs +++ /dev/null @@ -1,23 +0,0 @@ -use image::{DynamicImage, EncodableLayout}; -use std::{ - io::{BufWriter, Write}, - path::Path, -}; - -pub(crate) fn compress_jpg(image: DynamicImage, output_location: &Path) -> anyhow::Result<()> { - let mut comp = mozjpeg::Compress::new(mozjpeg::ColorSpace::JCS_EXT_RGBX); - let width = image.width() as usize; - let height = image.height() as usize; - - comp.set_size(width, height); - let mut comp = comp.start_compress(Vec::new())?; // any io::Write will work - - comp.write_scanlines(image.to_rgba8().as_bytes())?; - - let jpeg_bytes = comp.finish()?; - - let file = std::fs::File::create(output_location)?; - let w = &mut BufWriter::new(file); - w.write_all(&jpeg_bytes)?; - Ok(()) -} diff --git a/packages/cli-opt/src/image/png.rs b/packages/cli-opt/src/image/png.rs deleted file mode 100644 index 039e64cdc..000000000 --- a/packages/cli-opt/src/image/png.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::{io::BufWriter, path::Path}; - -use image::DynamicImage; - -pub(crate) fn compress_png(image: DynamicImage, output_location: &Path) { - // Image loading/saving is outside scope of this library - let width = image.width() as usize; - let height = image.height() as usize; - let bitmap: Vec<_> = image - .into_rgba8() - .pixels() - .map(|px| imagequant::RGBA::new(px[0], px[1], px[2], px[3])) - .collect(); - - // Configure the library - let mut liq = imagequant::new(); - liq.set_speed(5).unwrap(); - liq.set_quality(0, 99).unwrap(); - - // Describe the bitmap - let mut img = liq.new_image(&bitmap[..], width, height, 0.0).unwrap(); - - // The magic happens in quantize() - let mut res = match liq.quantize(&mut img) { - Ok(res) => res, - Err(err) => panic!("Quantization failed, because: {err:?}"), - }; - - let (palette, pixels) = res.remapped(&mut img).unwrap(); - - let file = std::fs::File::create(output_location).unwrap(); - let w = &mut BufWriter::new(file); - - let mut encoder = png::Encoder::new(w, width as u32, height as u32); - encoder.set_color(png::ColorType::Rgba); - let mut flattened_palette = Vec::new(); - let mut alpha_palette = Vec::new(); - for px in palette { - flattened_palette.push(px.r); - flattened_palette.push(px.g); - flattened_palette.push(px.b); - alpha_palette.push(px.a); - } - encoder.set_palette(flattened_palette); - encoder.set_trns(alpha_palette); - encoder.set_depth(png::BitDepth::Eight); - encoder.set_color(png::ColorType::Indexed); - encoder.set_compression(png::Compression::Best); - let mut writer = encoder.write_header().unwrap(); - writer.write_image_data(&pixels).unwrap(); - writer.finish().unwrap(); -} diff --git a/packages/cli-opt/src/js.rs b/packages/cli-opt/src/js.rs deleted file mode 100644 index 52ab2b0c8..000000000 --- a/packages/cli-opt/src/js.rs +++ /dev/null @@ -1,295 +0,0 @@ -use std::hash::Hasher; -use std::path::Path; -use std::path::PathBuf; - -use anyhow::Context; -use manganis_core::JsAssetOptions; -use swc_common::errors::Emitter; -use swc_common::errors::Handler; -use swc_common::input::SourceFileInput; -use swc_ecma_minifier::option::{ExtraOptions, MinifyOptions}; -use swc_ecma_parser::lexer::Lexer; -use swc_ecma_parser::Parser; -use swc_ecma_transforms_base::fixer::fixer; -use swc_ecma_visit::VisitMutWith; - -use std::collections::HashMap; - -use anyhow::Error; -use swc_bundler::{Bundler, Config, Load, ModuleData, ModuleRecord}; -use swc_common::{ - errors::HANDLER, sync::Lrc, FileName, FilePathMapping, Globals, Mark, SourceMap, Span, GLOBALS, -}; -use swc_ecma_ast::*; -use swc_ecma_codegen::text_writer::JsWriter; -use swc_ecma_loader::{resolvers::node::NodeModulesResolver, TargetEnv}; -use swc_ecma_parser::{parse_file_as_module, Syntax}; - -use crate::hash::hash_file_contents; - -struct TracingEmitter; - -impl Emitter for TracingEmitter { - fn emit(&mut self, db: &mut swc_common::errors::DiagnosticBuilder<'_>) { - match db.level { - swc_common::errors::Level::Bug - | swc_common::errors::Level::Fatal - | swc_common::errors::Level::PhaseFatal - | swc_common::errors::Level::Error => tracing::error!("{}", db.message()), - swc_common::errors::Level::Warning - | swc_common::errors::Level::FailureNote - | swc_common::errors::Level::Cancelled => tracing::warn!("{}", db.message()), - swc_common::errors::Level::Note | swc_common::errors::Level::Help => { - tracing::trace!("{}", db.message()) - } - } - } -} - -/// Run a closure with the swc globals and handler set up -fn inside_handler(f: impl FnOnce(&Globals, Lrc) -> O) -> O { - let globals = Globals::new(); - let cm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let handler = Handler::with_emitter_and_flags(Box::new(TracingEmitter), Default::default()); - GLOBALS.set(&globals, || HANDLER.set(&handler, || f(&globals, cm))) -} - -fn bundle_js_to_writer( - file: PathBuf, - bundle: bool, - minify: bool, - write_to: &mut impl std::io::Write, -) -> anyhow::Result<()> { - inside_handler(|globals, cm| { - bundle_js_to_writer_inside_handler(globals, cm, file, bundle, minify, write_to) - }) -} - -fn resolve_js_inside_handler( - globals: &Globals, - file: PathBuf, - bundle: bool, - cm: &Lrc, -) -> anyhow::Result { - if bundle { - let node_resolver = NodeModulesResolver::new(TargetEnv::Browser, Default::default(), true); - let mut bundler = Bundler::new( - globals, - cm.clone(), - PathLoader { cm: cm.clone() }, - node_resolver, - Config { - require: true, - ..Default::default() - }, - Box::new(Hook), - ); - let mut entries = HashMap::default(); - entries.insert("main".to_string(), FileName::Real(file)); - - let mut bundles = bundler - .bundle(entries) - .context("failed to bundle javascript with swc")?; - // Since we only inserted one entry, there should only be one bundle in the output - let bundle = bundles - .pop() - .ok_or_else(|| anyhow::anyhow!("swc did not output any bundles"))?; - Ok(bundle.module) - } else { - let fm = cm.load_file(Path::new(&file)).expect("Failed to load file"); - - let lexer = Lexer::new( - Default::default(), - Default::default(), - SourceFileInput::from(&*fm), - None, - ); - let mut parser = Parser::new_from(lexer); - - parser.parse_module().map_err(|err| { - HANDLER.with(|handler| { - let mut error = err.into_diagnostic(handler); - // swc errors panic on drop if you don't cancel them - error.cancel(); - anyhow::anyhow!("{}", error.message()) - }) - }) - } -} - -fn bundle_js_to_writer_inside_handler( - globals: &Globals, - cm: Lrc, - file: PathBuf, - bundle: bool, - minify: bool, - write_to: &mut impl std::io::Write, -) -> anyhow::Result<()> { - let mut module = resolve_js_inside_handler(globals, file, bundle, &cm)?; - - if minify { - module = swc_ecma_minifier::optimize( - std::mem::take(&mut module).into(), - cm.clone(), - None, - None, - &MinifyOptions { - rename: true, - compress: None, - mangle: None, - ..Default::default() - }, - &ExtraOptions { - unresolved_mark: Mark::new(), - top_level_mark: Mark::new(), - mangle_name_cache: None, - }, - ) - .expect_module(); - module.visit_mut_with(&mut fixer(None)); - } - - let mut emitter = swc_ecma_codegen::Emitter { - cfg: swc_ecma_codegen::Config::default().with_minify(minify), - cm: cm.clone(), - comments: None, - wr: Box::new(JsWriter::new(cm, "\n", write_to, None)), - }; - - emitter.emit_module(&module)?; - - Ok(()) -} - -struct PathLoader { - cm: Lrc, -} - -impl Load for PathLoader { - fn load(&self, file: &FileName) -> anyhow::Result { - let file = match file { - FileName::Real(v) => v, - _ => anyhow::bail!("Only real files are supported"), - }; - - let fm = self.cm.load_file(file)?; - - let module = HANDLER.with(|handler| { - parse_file_as_module( - &fm, - Syntax::Es(Default::default()), - Default::default(), - None, - &mut Vec::new(), - ) - .map_err(|err| { - let mut error = err.into_diagnostic(handler); - // swc errors panic on drop if you don't cancel them - error.cancel(); - anyhow::anyhow!("{}", error.message()) - }) - .context("Failed to parse javascript") - })?; - - Ok(ModuleData { - fm, - module, - helpers: Default::default(), - }) - } -} - -// Adapted from https://github.com/swc-project/swc/blob/624680b7896cef9d8e30bd5ff910538298016974/bindings/binding_core_node/src/bundle.rs#L266-L302 -struct Hook; - -impl swc_bundler::Hook for Hook { - fn get_import_meta_props( - &self, - span: Span, - module_record: &ModuleRecord, - ) -> Result, Error> { - let file_name = module_record.file_name.to_string(); - - Ok(vec![ - KeyValueProp { - key: PropName::Ident(IdentName::new("url".into(), span)), - value: Box::new(Expr::Lit(Lit::Str(Str { - span, - raw: None, - value: file_name.into(), - }))), - }, - KeyValueProp { - key: PropName::Ident(IdentName::new("main".into(), span)), - value: Box::new(if module_record.is_entry { - Expr::Member(MemberExpr { - span, - obj: Box::new(Expr::MetaProp(MetaPropExpr { - span, - kind: MetaPropKind::ImportMeta, - })), - prop: MemberProp::Ident(IdentName::new("main".into(), span)), - }) - } else { - Expr::Lit(Lit::Bool(Bool { span, value: false })) - }), - }, - ]) - } -} - -pub(crate) fn process_js( - js_options: &JsAssetOptions, - source: &Path, - output_path: &Path, - bundle: bool, -) -> anyhow::Result<()> { - let mut writer = std::io::BufWriter::new(std::fs::File::create(output_path)?); - if js_options.minified() { - if let Err(err) = bundle_js_to_writer(source.to_path_buf(), bundle, true, &mut writer) { - tracing::error!("Failed to minify js. Falling back to non-minified: {err}"); - } else { - return Ok(()); - } - } - let mut source_file = std::fs::File::open(source)?; - std::io::copy(&mut source_file, &mut writer).with_context(|| { - format!( - "Failed to write js to output location: {}", - output_path.display() - ) - })?; - - Ok(()) -} - -fn hash_js_module(file: PathBuf, hasher: &mut impl Hasher, bundle: bool) -> anyhow::Result<()> { - inside_handler(|globals, cm| { - _ = resolve_js_inside_handler(globals, file, bundle, &cm)?; - - for file in cm.files().iter() { - let hash = file.src_hash; - hasher.write(&hash.to_le_bytes()); - } - - Ok(()) - }) -} - -pub(crate) fn hash_js( - js_options: &JsAssetOptions, - source: &Path, - hasher: &mut impl Hasher, - bundle: bool, -) -> anyhow::Result<()> { - if js_options.minified() { - if let Err(err) = hash_js_module(source.to_path_buf(), hasher, bundle) { - tracing::error!("Failed to minify js. Falling back to non-minified: {err}"); - hash_file_contents(source, hasher)?; - } - } else { - hash_file_contents(source, hasher)?; - } - - Ok(()) -} diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 842805f4d..1ebca01c5 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -7,11 +7,10 @@ description = "CLI for building fullstack web, desktop, and mobile apps with a s repository = "https://github.com/DioxusLabs/dioxus/" license = "MIT OR Apache-2.0" keywords = ["mobile", "gui", "cli", "dioxus", "wasm"] -rust-version = "1.81.0" +rust-version = "1.82.0" [dependencies] dioxus-autofmt = { workspace = true } -dioxus-check = { workspace = true } dioxus-rsx-rosetta = { workspace = true } dioxus-rsx = { workspace = true } dioxus-rsx-hotreload = { workspace = true } @@ -20,8 +19,6 @@ dioxus-core = { workspace = true, features = ["serialize"] } dioxus-core-types = { workspace = true } dioxus-devtools-types = { workspace = true } dioxus-cli-config = { workspace = true } -dioxus-cli-opt = { workspace = true } -dioxus-fullstack = { workspace = true } dioxus-dx-wire-format = { workspace = true } wasm-split-cli = { workspace = true } depinfo = { workspace = true } @@ -33,10 +30,8 @@ bytes = { workspace = true } clap = { workspace = true, features = ["derive", "cargo"] } convert_case = { workspace = true } thiserror = { workspace = true } -uuid = { workspace = true, features = ["v4"] } -# swc uses serde internals that break with newer versions of serde. We should move off of swc eventually, -# but pinning serde fixes the issue in the meantime -serde = { version = "=1.0.221", features = ["derive"] } +uuid = { workspace = true, features = ["v4", "v5", "v7"] } +serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } toml = { workspace = true } cargo_toml = { workspace = true, features = ["features"] } @@ -61,9 +56,10 @@ console = "0.16.0" ctrlc = "3.4.7" axum = { workspace = true, default-features = true, features = ["ws"] } -axum-server = { workspace = true, features = ["tls-rustls"] } +axum-server = { workspace = true, features = ["tls-rustls-no-provider"] } axum-extra = { workspace = true, features = ["typed-header"] } tower-http = { workspace = true, features = ["full"] } +owo-colors = { workspace = true, features = ["supports-colors"] } proc-macro2 = { workspace = true, features = ["span-locations"] } syn = { workspace = true, features = [ "full", @@ -72,13 +68,12 @@ syn = { workspace = true, features = [ "visit-mut", ] } -headers = "0.4.1" walkdir = "2" dunce = { workspace = true } # tools download dirs = { workspace = true } -reqwest = { workspace = true, features = ["rustls-tls", "trust-dns", "json"] } +reqwest = { workspace = true, default-features = false, features = ["rustls-tls", "json"] } tower = { workspace = true } git2 = "0.20.2" @@ -95,9 +90,14 @@ toml_edit = "0.22.27" prettyplease = { workspace = true } # Assets +lightningcss = { version = "1.0.0-alpha.67", features = ["browserslist", "into_owned"] } +grass = "0.13.4" +codemap = "0.1.3" +mozjpeg = { version = "0.10.13", default-features = false, features = ["parallel"] } +imagequant = "4.4.0" +png = "0.17.16" brotli = "8.0.1" ignore = "0.4.23" -env_logger = { workspace = true } const-serialize = { workspace = true, features = ["serde"] } const-serialize-07 = { package = "const-serialize", version = "=0.7.2", features = ["serde"] } @@ -127,7 +127,6 @@ manganis = { workspace = true } manganis-core = { workspace = true } manganis-core-07 = { workspace = true } target-lexicon = { version = "0.13.2", features = ["serde", "serde_support"] } -wasm-encoder = "0.235.0" # Extracting data from an executable object = { workspace = true, features = ["all"] } @@ -136,26 +135,26 @@ itertools = { workspace = true } throbber-widgets-tui = "0.8.0" unicode-segmentation = "1.12.0" handlebars = "6.3.2" -strum = { version = "0.27.1", features = ["derive"] } -memmap = "0.7.0" walrus = { workspace = true, features = ["parallel"] } wasmparser = { workspace = true } -tauri-utils = { workspace = true } -tauri-macos-sign = { workspace = true } -tauri-bundler = { workspace = true } +# bundler dependencies +icns = { workspace = true } +image = { workspace = true } +zip = { workspace = true } +sha1 = { workspace = true } +sha2 = { workspace = true } +md5 = { workspace = true } +rpm = { workspace = true } include_dir = "0.7.4" flate2 = "1.1.2" tar = "0.4.44" -local-ip-address = "0.6.5" dircpy = "0.3.19" plist = "1.7.4" -memoize = "0.5.1" backtrace = "0.3.74" ar = "0.9.0" -wasm-bindgen-externref-xform = "0.2.100" pdb = "0.8.0" -self_update = { version = "0.42.0", features = [ +self_update = { version = "0.42.0", default-features = false, features = [ "archive-tar", "archive-zip", "compression-flate2", @@ -163,7 +162,6 @@ self_update = { version = "0.42.0", features = [ ] } self-replace = "1.5.0" cargo-config2 = { workspace = true } -posthog-rs = "0.3.5" serde_json5 = "0.2.1" fs2 = "0.4.3" sentry-backtrace = "0.42.0" @@ -175,9 +173,6 @@ home = "=0.5.11" [target.'cfg(target_env = "musl")'.dependencies] git2 = { version = "0.20.2", features = ["vendored-libgit2"] } -[build-dependencies] -built = { version = "0.8.0", features = ["git2"] } - [features] default = [] tokio-console = ["dep:console-subscriber", "tokio/tracing"] @@ -190,6 +185,8 @@ name = "dx" [dev-dependencies] escargot = "0.5" +indoc = "2.0.6" +pretty_assertions = { workspace = true } [package.metadata.binstall] pkg-url = "{ repo }/releases/download/v{ version }/dx-{ target }{ archive-suffix }" diff --git a/packages/cli/assets/default_icon.png b/packages/cli/assets/default_icon.png new file mode 100644 index 000000000..4ad62768f Binary files /dev/null and b/packages/cli/assets/default_icon.png differ diff --git a/packages/cli/build.rs b/packages/cli/build.rs index d8f91cb91..6b8f08332 100644 --- a/packages/cli/build.rs +++ b/packages/cli/build.rs @@ -1,3 +1,42 @@ +use std::process::Command; + fn main() { - built::write_built_file().expect("Failed to acquire build-time information"); + println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA"); + println!("cargo:rerun-if-env-changed=DIOXUS_CLI_GIT_SHA_SHORT"); + println!("cargo:rerun-if-changed=.git/HEAD"); + println!("cargo:rerun-if-changed=.git/refs"); + + let full_hash = std::env::var("DIOXUS_CLI_GIT_SHA") + .ok() + .or_else(resolve_git_commit_hash); + + if let Some(full_hash) = full_hash { + println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA={full_hash}"); + + let short_hash = std::env::var("DIOXUS_CLI_GIT_SHA_SHORT") + .ok() + .unwrap_or_else(|| full_hash.chars().take(7).collect()); + + println!("cargo:rustc-env=DIOXUS_CLI_GIT_SHA_SHORT={short_hash}"); + } +} + +fn resolve_git_commit_hash() -> Option { + let output = Command::new("git") + .args(["rev-parse", "HEAD"]) + .output() + .ok()?; + + if !output.status.success() { + return None; + } + + let hash = String::from_utf8(output.stdout).ok()?; + let hash = hash.trim().to_string(); + + if hash.is_empty() { + None + } else { + Some(hash) + } } diff --git a/packages/cli/schema.json b/packages/cli/schema.json index 0f93edc69..f432250f9 100644 --- a/packages/cli/schema.json +++ b/packages/cli/schema.json @@ -3266,14 +3266,14 @@ ] }, "upgrade_code": { - "description": "A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**, otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n\nBy default, tauri generates this code by generating a Uuid v5 using the string `.exe.app.x64` in the DNS namespace. You can use Tauri's CLI to generate and print this code for you by running `tauri inspect wix-upgrade-code`.\n\nIt is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code whenever you want to change your product name.", + "description": "A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**, otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app.\n\nBy default, this code is generated as a Uuid v5 using the string `.exe.app.x64` in the DNS namespace.\n\nIt is recommended that you set this value in your Dioxus.toml to avoid accidental changes in your upgrade code whenever you want to change your product name.", "type": [ "string", "null" ] }, "version": { - "description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\nBecause a valid version is required for MSI installer, it will be derived from [`tauri_bundler::PackageSettings::version`] if this field is not set.\n\nThe first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255. The third and fourth fields have a maximum value of 65,535.\n\nSee for more info.", + "description": "MSI installer version in the format `major.minor.patch.build` (build is optional).\n\nBecause a valid version is required for MSI installer, it will be derived from the package version if this field is not set.\n\nThe first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255. The third and fourth fields have a maximum value of 65,535.\n\nSee for more info.", "type": [ "string", "null" diff --git a/packages/cli/src/build/assets.rs b/packages/cli/src/build/assets.rs index 7824feccf..9b2ba1e70 100644 --- a/packages/cli/src/build/assets.rs +++ b/packages/cli/src/build/assets.rs @@ -33,10 +33,10 @@ use std::{ path::{Path, PathBuf}, }; +use crate::opt::AssetManifest; use crate::Result; use anyhow::{bail, Context}; use const_serialize::{deserialize_const, serialize_const, ConstVec}; -use dioxus_cli_opt::AssetManifest; use manganis::{AssetOptions, AssetVariant, BundledAsset, ImageFormat, ImageSize}; use manganis_core::{AndroidArtifactMetadata, SwiftPackageMetadata, SymbolData}; use object::{File, Object, ObjectSection, ObjectSymbol, ReadCache, ReadRef, Section, Symbol}; @@ -811,7 +811,7 @@ pub(crate) async fn extract_symbols_from_file( // Add the hash to each asset in parallel assets .par_iter_mut() - .for_each(dioxus_cli_opt::add_hash_to_asset); + .for_each(crate::opt::add_hash_to_asset); // Write back only assets to the binary file (permissions are not modified) for entry in write_entries { diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index e83bd0b2f..34f9816f8 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1,10 +1,10 @@ +use crate::opt::process_file_to; use crate::{ build::cache::ObjectCache, serve::WebServer, verbosity_or_default, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, BundleFormat, ProgressRx, ProgressTx, Result, RustcArgs, StructuredOutput, }; use anyhow::{bail, Context, Error}; -use dioxus_cli_opt::process_file_to; use futures_util::{future::OptionFuture, pin_mut, FutureExt}; use itertools::Itertools; use std::{ @@ -475,9 +475,12 @@ impl AppBuilder { current, total, krate, + fresh, .. } => { - tracing::info!("Compiled [{current:>3}/{total}]: {krate}"); + if !fresh { + tracing::info!("Compiled [{current:>3}/{total}]: {krate}"); + } } BuildStage::RunningBindgen => tracing::info!("Running wasm-bindgen..."), BuildStage::CopyingAssets { @@ -509,7 +512,7 @@ impl AppBuilder { } BuilderUpdate::BuildFailed { err } => { // Flush remaining compiler messages - while let Ok(Some(msg)) = self.rx.try_next() { + while let Ok(msg) = self.rx.try_recv() { if let BuilderUpdate::CompilerMessage { message } = msg { tracing::info!(json = %StructuredOutput::RustcOutput { message: message.clone() }, %message); } @@ -760,7 +763,8 @@ impl AppBuilder { let to = asset_dir.join(bundled.bundled_path()); tracing::debug!("Copying asset from patch: {}", from.display()); - if let Err(e) = dioxus_cli_opt::process_file_to(bundled.options(), &from, &to) { + let esbuild = crate::esbuild::Esbuild::path_if_installed(); + if let Err(e) = process_file_to(bundled.options(), &from, &to, esbuild.as_deref()) { tracing::error!("Failed to copy asset: {e}"); continue; } @@ -869,7 +873,8 @@ impl AppBuilder { // the asset would be in a new location because the contents and hash have changed. Since we are // hotreloading, we need to use the old asset location it was originally written to. let options = *resource.options(); - let res = process_file_to(&options, &changed_file, &output_path); + let esbuild = crate::esbuild::Esbuild::path_if_installed(); + let res = process_file_to(&options, &changed_file, &output_path, esbuild.as_deref()); let bundled_name = PathBuf::from(resource.bundled_path()); if let Err(e) = res { tracing::debug!("Failed to hotreload asset {e}"); diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index aedb0cc4f..c68cd9b05 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -137,12 +137,19 @@ impl BuildContext { tracing::trace!(dx_src = ?TraceSrc::Cargo, "{line}"); } - pub(crate) fn status_build_progress(&self, count: usize, total: usize, name: String) { + pub(crate) fn status_build_progress( + &self, + count: usize, + total: usize, + name: String, + fresh: bool, + ) { _ = self.tx.unbounded_send(BuilderUpdate::Progress { stage: BuildStage::Compiling { current: count, total, krate: name, + fresh, }, }); } diff --git a/packages/cli/src/build/ios_swift.rs b/packages/cli/src/build/ios_swift.rs index 56394e161..e8bd886d0 100644 --- a/packages/cli/src/build/ios_swift.rs +++ b/packages/cli/src/build/ios_swift.rs @@ -144,9 +144,9 @@ pub async fn create_framework_bundle( std::fs::write(resources_dir.join("Info.plist"), info_plist)?; // Create symbolic links (required for macOS framework structure) - let versions_dir = framework_dir.join("Versions"); #[cfg(unix)] { + let versions_dir = framework_dir.join("Versions"); std::os::unix::fs::symlink("A", versions_dir.join("Current"))?; std::os::unix::fs::symlink( format!("Versions/Current/{}", framework_name), diff --git a/packages/cli/src/build/manifest.rs b/packages/cli/src/build/manifest.rs index 2d1d3bd26..f2e78fbdf 100644 --- a/packages/cli/src/build/manifest.rs +++ b/packages/cli/src/build/manifest.rs @@ -8,7 +8,7 @@ //! Eventually, we might want to expand this to include more metadata about the build, including //! build time, target platform, etc. -use dioxus_cli_opt::AssetManifest; +use crate::opt::AssetManifest; use serde::{Deserialize, Serialize}; #[derive(Default, Serialize, Deserialize)] diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 608a63836..824061a14 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -1420,8 +1420,25 @@ fn name_is_bindgen_symbol(name: &str) -> bool { || name.contains("__wbindgen_externref") || name.contains("wasm_bindgen8describe6inform") || name.contains("wasm_bindgen..describe..WasmDescribe") - || name.contains("wasm_bindgen..closure..WasmClosure$GT$8describe") - || name.contains("wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe") + || (name.contains("wasm_bindgen..closure..WasmClosure") && name.contains("describe")) + || (name.contains("wasm_bindgen7closure16Closure") && name.contains("describe")) + || (name.contains("wasm_bindgen7convert8closures") && name.contains("describe_invoke")) +} + +// Test for bindgen symbols. As we find more bad symbols, add them here +#[test] +fn bindgen_symbol_catch() { + let symbol = "_ZN12wasm_bindgen7convert8closures1_142_$LT$impl$u20$wasm_bindgen..closure..WasmClosure$u20$for$u20$dyn$u20$core..ops..function..FnMut$LT$$LP$$RP$$GT$$u2b$Output$u20$$u3d$$u20$R$GT$15describe_invoke17h4373f8b6570333dcE"; + assert!(name_is_bindgen_symbol(symbol)); + + // matches_legacy_wasm_bindgen_closure_describe_symbols + let symbol = "_ZN12wasm_bindgen7closure16Closure$LT$T$GT$4wrap8describe17h1234567890abcdefE"; + assert!(name_is_bindgen_symbol(symbol)); + + // does_not_match_saved_runtime_exports + assert!(!name_is_bindgen_symbol("__wbindgen_malloc")); + assert!(!name_is_bindgen_symbol("__wbindgen_realloc")); + assert!(!name_is_bindgen_symbol("__wbindgen_free")); } /// Manually parse the data section from a wasm module diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index ab629bc41..b0e9d883a 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -320,6 +320,7 @@ //! - xbuild: use super::HotpatchModuleCache; +use crate::opt::{process_file_to, AssetManifest}; use crate::{ AndroidTools, AppManifest, BuildContext, BuildId, BundleFormat, DioxusConfig, Error, LinkAction, LinkerFlavor, ObjectCache, Platform, Renderer, Result, RustcArgs, TargetArgs, @@ -331,7 +332,6 @@ use cargo_toml::{Profile, Profiles, StripSetting}; use depinfo::RustcDepInfo; use dioxus_cli_config::{format_base_path_meta_element, PRODUCT_NAME_ENV}; use dioxus_cli_config::{APP_TITLE_ENV, ASSET_ROOT_ENV}; -use dioxus_cli_opt::{process_file_to, AssetManifest}; use itertools::Itertools; use krates::{cm::TargetKind, NodeId}; use manganis::{AssetOptions, BundledAsset, SwiftPackageMetadata}; @@ -1306,7 +1306,12 @@ impl BuildRequest { } Message::CompilerArtifact(artifact) => { units_compiled += 1; - ctx.status_build_progress(units_compiled, crate_count, artifact.target.name); + ctx.status_build_progress( + units_compiled, + crate_count, + artifact.target.name, + artifact.fresh, + ); output_location = artifact.executable.map(Into::into); } // todo: this can occasionally swallow errors, so we should figure out what exactly is going wrong @@ -2280,6 +2285,7 @@ impl BuildRequest { // Parallel Copy over the assets and keep track of progress with an atomic counter let progress = ctx.tx.clone(); let ws_dir = self.workspace_dir(); + let esbuild_path = crate::esbuild::Esbuild::path_if_installed(); // Optimizing assets is expensive and blocking, so we do it in a tokio spawn blocking task tokio::task::spawn_blocking(move || { @@ -2292,7 +2298,7 @@ impl BuildRequest { "Starting asset copy {processing}/{asset_count} from {from_:?}" ); - let res = process_file_to(options, from, to); + let res = process_file_to(options, from, to, esbuild_path.as_deref()); if let Err(err) = res.as_ref() { tracing::error!("Failed to copy asset {from:?}: {err}"); } @@ -5524,6 +5530,25 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ .join("app-debug.apk") } + pub(crate) fn release_apk_path(&self) -> PathBuf { + self.root_dir() + .join("app") + .join("build") + .join("outputs") + .join("apk") + .join("release") + .join("app-release.apk") + } + + pub(crate) fn android_apk_path(&self) -> PathBuf { + let assembled_release = self.release && self.config.bundle.android.is_some(); + if assembled_release { + self.release_apk_path() + } else { + self.debug_apk_path() + } + } + /// We only really currently care about: /// /// - app dir (.app, .exe, .apk, etc) @@ -5683,6 +5708,9 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ self.verify_toolchain_installed().await?; + // esbuild is used for JS asset processing on all platforms + let _esbuild_path = crate::esbuild::Esbuild::get_or_install().await?; + match self.bundle { BundleFormat::Web => self.verify_web_tooling().await?, BundleFormat::Ios => self.verify_ios_tooling().await?, @@ -5970,7 +5998,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ if let Some(name) = entry.file_name().to_str() { if name.starts_with(&prefix) && name.ends_with(".rlib") { let mtime = entry.metadata().ok()?.modified().ok()?; - if best.as_ref().map_or(true, |(_, t)| mtime > *t) { + if best.as_ref().is_none_or(|(_, t)| mtime > *t) { best = Some((entry.path(), mtime)); } } diff --git a/packages/cli/src/bundle_utils.rs b/packages/cli/src/bundle_utils.rs deleted file mode 100644 index e58e289ec..000000000 --- a/packages/cli/src/bundle_utils.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::{ - config::BundleConfig, CustomSignCommandSettings, DebianSettings, MacOsSettings, - NSISInstallerMode, NsisSettings, PackageType, WebviewInstallMode, WindowsSettings, WixSettings, -}; - -impl From for tauri_bundler::NsisSettings { - fn from(val: NsisSettings) -> Self { - tauri_bundler::NsisSettings { - header_image: val.header_image, - sidebar_image: val.sidebar_image, - installer_icon: val.installer_icon, - install_mode: val.install_mode.into(), - languages: val.languages, - display_language_selector: val.display_language_selector, - custom_language_files: None, - template: None, - compression: tauri_utils::config::NsisCompression::None, - start_menu_folder: val.start_menu_folder, - installer_hooks: val.installer_hooks, - minimum_webview2_version: val.minimum_webview2_version, - } - } -} - -impl From for tauri_bundler::BundleSettings { - fn from(val: BundleConfig) -> Self { - tauri_bundler::BundleSettings { - identifier: val.identifier, - publisher: val.publisher, - icon: val.icon, - resources: val.resources, - copyright: val.copyright, - category: val.category.and_then(|c| c.parse().ok()), - short_description: val.short_description, - long_description: val.long_description, - external_bin: val.external_bin, - deb: val.deb.map(Into::into).unwrap_or_default(), - macos: val.macos.map(Into::into).unwrap_or_default(), - windows: val.windows.map(Into::into).unwrap_or_default(), - ..Default::default() - } - } -} - -impl From for tauri_bundler::DebianSettings { - fn from(val: DebianSettings) -> Self { - tauri_bundler::DebianSettings { - depends: val.depends, - files: val.files, - desktop_template: val.desktop_template, - provides: val.provides, - conflicts: val.conflicts, - replaces: val.replaces, - section: val.section, - priority: val.priority, - changelog: val.changelog, - pre_install_script: val.pre_install_script, - post_install_script: val.post_install_script, - pre_remove_script: val.pre_remove_script, - post_remove_script: val.post_remove_script, - recommends: val.recommends, - } - } -} - -impl From for tauri_bundler::WixSettings { - fn from(val: WixSettings) -> Self { - tauri_bundler::WixSettings { - language: tauri_bundler::bundle::WixLanguage({ - let mut languages: Vec<_> = val - .language - .iter() - .map(|l| { - ( - l.0.clone(), - tauri_bundler::bundle::WixLanguageConfig { - locale_path: l.1.clone(), - }, - ) - }) - .collect(); - if languages.is_empty() { - languages.push(("en-US".into(), Default::default())); - } - languages - }), - template: val.template, - fragment_paths: val.fragment_paths, - component_group_refs: val.component_group_refs, - component_refs: val.component_refs, - feature_group_refs: val.feature_group_refs, - feature_refs: val.feature_refs, - merge_refs: val.merge_refs, - enable_elevated_update_task: val.enable_elevated_update_task, - banner_path: val.banner_path, - dialog_image_path: val.dialog_image_path, - fips_compliant: val.fips_compliant, - version: val.version, - upgrade_code: val.upgrade_code, - } - } -} - -impl From for tauri_bundler::MacOsSettings { - fn from(val: MacOsSettings) -> Self { - tauri_bundler::MacOsSettings { - frameworks: val.frameworks, - minimum_system_version: val.minimum_system_version, - exception_domain: val.exception_domain, - signing_identity: val.signing_identity, - provider_short_name: val.provider_short_name, - entitlements: val.entitlements, - info_plist_path: val.info_plist_path, - files: val.files, - hardened_runtime: val.hardened_runtime, - bundle_version: val.bundle_version, - bundle_name: val.bundle_name, - } - } -} - -#[allow(deprecated)] -impl From for tauri_bundler::WindowsSettings { - fn from(val: WindowsSettings) -> Self { - tauri_bundler::WindowsSettings { - digest_algorithm: val.digest_algorithm, - certificate_thumbprint: val.certificate_thumbprint, - timestamp_url: val.timestamp_url, - tsp: val.tsp, - wix: val.wix.map(Into::into), - webview_install_mode: val.webview_install_mode.into(), - allow_downgrades: val.allow_downgrades, - nsis: val.nsis.map(Into::into), - sign_command: val.sign_command.map(Into::into), - - icon_path: val.icon_path.unwrap_or("./icons/icon.ico".into()), - } - } -} - -impl From for tauri_utils::config::NSISInstallerMode { - fn from(val: NSISInstallerMode) -> Self { - match val { - NSISInstallerMode::CurrentUser => tauri_utils::config::NSISInstallerMode::CurrentUser, - NSISInstallerMode::PerMachine => tauri_utils::config::NSISInstallerMode::PerMachine, - NSISInstallerMode::Both => tauri_utils::config::NSISInstallerMode::Both, - } - } -} - -impl From for tauri_bundler::PackageType { - fn from(value: PackageType) -> Self { - match value { - PackageType::MacOsBundle => Self::MacOsBundle, - PackageType::IosBundle => Self::IosBundle, - PackageType::WindowsMsi => Self::WindowsMsi, - PackageType::Deb => Self::Deb, - PackageType::Rpm => Self::Rpm, - PackageType::AppImage => Self::AppImage, - PackageType::Dmg => Self::Dmg, - PackageType::Updater => Self::Updater, - PackageType::Nsis => Self::Nsis, - } - } -} - -impl WebviewInstallMode { - fn into(self) -> tauri_utils::config::WebviewInstallMode { - match self { - Self::Skip => tauri_utils::config::WebviewInstallMode::Skip, - Self::DownloadBootstrapper { silent } => { - tauri_utils::config::WebviewInstallMode::DownloadBootstrapper { silent } - } - Self::EmbedBootstrapper { silent } => { - tauri_utils::config::WebviewInstallMode::EmbedBootstrapper { silent } - } - Self::OfflineInstaller { silent } => { - tauri_utils::config::WebviewInstallMode::OfflineInstaller { silent } - } - Self::FixedRuntime { path } => { - tauri_utils::config::WebviewInstallMode::FixedRuntime { path } - } - } - } -} - -impl From for tauri_bundler::CustomSignCommandSettings { - fn from(val: CustomSignCommandSettings) -> Self { - tauri_bundler::CustomSignCommandSettings { - cmd: val.cmd, - args: val.args, - } - } -} diff --git a/packages/cli/src/bundler/ios.rs b/packages/cli/src/bundler/ios.rs new file mode 100644 index 000000000..d7bce341e --- /dev/null +++ b/packages/cli/src/bundler/ios.rs @@ -0,0 +1,125 @@ +use super::{copy_dir_recursive, zip_dir_recursive, Arch, Bundle, BundleContext}; +use crate::PackageType; +use anyhow::{bail, Context, Result}; +use std::path::{Path, PathBuf}; +use tokio::process::Command; + +pub(crate) struct IosBundled { + pub ipa: Vec, + #[allow(dead_code)] + pub app: Vec, +} + +impl BundleContext<'_> { + /// Return the already-assembled iOS `.app` bundle produced by the build pipeline. + pub(crate) async fn bundle_ios_app(&self) -> Result> { + let app_path = self.build.root_dir(); + if !app_path.exists() { + bail!( + "iOS app bundle not found at {}. Ensure the iOS build completed successfully.", + app_path.display() + ); + } + + Ok(vec![app_path]) + } + + /// Package a signed iOS `.app` into an App Store distributable `.ipa`. + pub(crate) async fn bundle_ios_ipa(&self, bundles: &[Bundle]) -> Result { + validate_ios_ipa_target(&self.target(), self.binary_arch())?; + + let (app_paths, synthesized_app_paths) = if let Some(app_bundle) = bundles + .iter() + .find(|bundle| bundle.package_type == PackageType::IosApp) + { + (app_bundle.bundle_paths.clone(), Vec::new()) + } else { + let paths = self.bundle_ios_app().await?; + (paths.clone(), paths) + }; + + if app_paths.is_empty() { + bail!("No iOS .app bundle found to package into an .ipa"); + } + + let app_path = &app_paths[0]; + if !app_path.exists() { + bail!( + "iOS .app bundle does not exist at expected path: {}", + app_path.display() + ); + } + + verify_codesigned_app(app_path).await?; + + let output_dir = self.project_out_directory().join("bundle").join("ipa"); + std::fs::create_dir_all(&output_dir)?; + + let ipa_name = format!( + "{}_{}_{}.ipa", + self.product_name(), + self.version_string(), + self.binary_arch() + ); + let output_path = output_dir.join(ipa_name); + + tracing::info!("Creating iOS IPA at {}", output_path.display()); + + let staging_dir = + tempfile::tempdir().context("Failed to create temp dir for IPA staging")?; + let payload_dir = staging_dir.path().join("Payload"); + std::fs::create_dir_all(&payload_dir)?; + + let staged_app = payload_dir.join( + app_path + .file_name() + .context("iOS .app bundle is missing a file name")?, + ); + copy_dir_recursive(app_path, &staged_app)?; + + if output_path.exists() { + std::fs::remove_file(&output_path)?; + } + zip_dir_recursive(staging_dir.path(), &output_path)?; + + tracing::info!("IPA created at {}", output_path.display()); + + Ok(IosBundled { + ipa: vec![output_path], + app: synthesized_app_paths, + }) + } +} + +fn validate_ios_ipa_target(target: &str, arch: Arch) -> Result<()> { + let is_device = arch == Arch::AArch64 && !target.contains("sim"); + if !is_device { + bail!( + "IPA packaging requires a physical-device iOS target. Resolved target was `{target}`." + ); + } + + Ok(()) +} + +async fn verify_codesigned_app(app_path: &Path) -> Result<()> { + verify_codesigned_app_with(Path::new("codesign"), app_path).await +} + +async fn verify_codesigned_app_with(codesign: &Path, app_path: &Path) -> Result<()> { + let output = Command::new(codesign) + .args(["--verify", "--deep", "--strict"]) + .arg(app_path) + .output() + .await + .with_context(|| format!("Failed to run `{}`", codesign.display()))?; + + if !output.status.success() { + bail!( + "iOS .app bundle must be codesigned before creating an .ipa: {}", + String::from_utf8_lossy(&output.stderr).trim() + ); + } + + Ok(()) +} diff --git a/packages/cli/src/bundler/linux.rs b/packages/cli/src/bundler/linux.rs new file mode 100644 index 000000000..9f1556d2e --- /dev/null +++ b/packages/cli/src/bundler/linux.rs @@ -0,0 +1,1140 @@ +use crate::{ + bundler::{AppCategory, BundleContext}, + PackageType, +}; +use anyhow::{bail, Context, Result}; +use handlebars::Handlebars; +use image::{GenericImageView, ImageFormat}; +use std::{ + fs::{self, File}, + io::{BufReader, Cursor, Write}, + path::{Path, PathBuf}, +}; +use tokio::process::Command; + +const DEFAULT_LINUX_ICON_PNG: &[u8] = include_bytes!("../../assets/default_icon.png"); + +impl BundleContext<'_> { + /// Build a self-contained Linux AppImage using `linuxdeploy`. + /// + /// AppImage bundling is implemented as a two-phase process: + /// 1. Construct an AppDir directory tree that looks like a normal Linux desktop + /// installation rooted at `usr/`. + /// 2. Hand that AppDir to `linuxdeploy`, which turns it into a runnable + /// `.AppImage` executable. + /// + /// Concretely, this method: + /// 1. Creates `project_out_directory()/bundle/appimage/.AppDir`. + /// 2. Reuses the shared Linux payload generator so the AppDir contains the same + /// executable, resources, `.desktop` file, icons, and sidecar binaries used by + /// other Linux package formats. + /// 3. Adds the top-level `AppRun`, desktop file, and icon symlinks expected by + /// AppImage tooling. + /// 4. Invokes the pre-resolved `linuxdeploy` binary with `OUTPUT=` so the + /// final artifact lands at a deterministic path. + /// 5. Renames the output if `linuxdeploy` used its own filename convention. + /// 6. Removes the temporary AppDir after the final image has been created. + /// + /// The result is a single `.AppImage` file in + /// `project_out_directory()/bundle/appimage`. + pub(crate) async fn bundle_linux_appimage(&self) -> Result> { + let name = self.main_binary_name().to_string(); + let version = self.version_string(); + let arch = self.binary_arch(); + let arch_str = arch.appimage_arch(); + + let output_dir = self.project_out_directory().join("bundle").join("appimage"); + fs::create_dir_all(&output_dir)?; + + let appimage_filename = format!("{name}_{version}_{arch_str}.AppImage"); + let appimage_path = output_dir.join(&appimage_filename); + + tracing::info!("Bundling {appimage_filename}..."); + + let appdir = output_dir.join(format!("{name}.AppDir")); + if appdir.exists() { + fs::remove_dir_all(&appdir)?; + } + fs::create_dir_all(&appdir)?; + + self.generate_linux_common_data(&appdir)?; + self.create_linux_appdir_symlinks(&appdir, &name)?; + + let linuxdeploy = self + .tools + .linuxdeploy + .as_ref() + .context("linuxdeploy was not resolved. This is a bug.")?; + + tracing::info!("Running linuxdeploy..."); + + let output = Command::new(linuxdeploy) + .arg("--appdir") + .arg(&appdir) + .arg("--output") + .arg("appimage") + .env("OUTPUT", &appimage_path) + // Run the linuxdeploy AppImage in extract-and-run mode to avoid host + // runtime/FUSE differences on distros and CI runners. + .env("APPIMAGE_EXTRACT_AND_RUN", "1") + .env("NO_STRIP", "true") + .current_dir(&output_dir) + .output() + .await + .with_context(|| format!("Failed to run linuxdeploy: {}", linuxdeploy.display()))?; + + if !output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + bail!( + "linuxdeploy failed with exit code {:?}\nstdout:\n{}\nstderr:\n{}", + output.status.code(), + stdout.trim(), + stderr.trim() + ); + } + + if !appimage_path.exists() { + let found = find_appimage_output(&output_dir, &name)?; + if let Some(found_path) = found { + if found_path != appimage_path { + fs::rename(&found_path, &appimage_path).with_context(|| { + format!( + "Failed to rename {} to {}", + found_path.display(), + appimage_path.display() + ) + })?; + } + } else { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + bail!( + "AppImage was not created. Expected at: {}\nlinuxdeploy stdout:\n{}\nlinuxdeploy stderr:\n{}", + appimage_path.display(), + stdout.trim(), + stderr.trim() + ); + } + } + + let _ = fs::remove_dir_all(&appdir); + + tracing::info!("Created AppImage: {}", appimage_path.display()); + Ok(vec![appimage_path]) + } + + /// Build a Debian `.deb` package entirely in Rust. + /// + /// The Debian bundler does not shell out to `dpkg-deb`. Instead it assembles the + /// archive directly from its three standard members: + /// - `debian-binary` + /// - `control.tar.gz` + /// - `data.tar.gz` + /// + /// The bundling pipeline is: + /// 1. Create a temporary `_data` directory containing the Linux install payload. + /// This payload places the main executable in `/usr/bin`, resources in + /// `/usr/lib/`, freedesktop metadata under `/usr/share`, sidecar + /// binaries, custom files, and an optional compressed changelog. + /// 2. Compute the installed size from that payload tree. + /// 3. Build `control.tar.gz`, including the `control` metadata file, `md5sums`, + /// and any maintainer scripts configured in the Debian settings. + /// 4. Build `data.tar.gz` from the staged payload tree. + /// 5. Write the final `.deb` as an `ar` archive in the correct member order. + /// 6. Remove the temporary `_data` directory after assembly completes. + /// + /// The final package is emitted to `project_out_directory()/bundle/deb`. + pub(crate) async fn bundle_linux_deb(&self) -> Result> { + let arch = self.binary_arch().deb_arch(); + let package_name = self.deb_package_name(); + let version = self.version_string(); + + let output_dir = self.project_out_directory().join("bundle").join("deb"); + fs::create_dir_all(&output_dir)?; + + let deb_filename = format!("{package_name}_{version}_{arch}.deb"); + let deb_path = output_dir.join(&deb_filename); + + tracing::info!("Bundling {deb_filename}..."); + + let data_dir = output_dir.join("_data"); + if data_dir.exists() { + fs::remove_dir_all(&data_dir)?; + } + fs::create_dir_all(&data_dir)?; + + self.generate_linux_common_data(&data_dir)?; + self.add_linux_deb_data(&data_dir)?; + + let installed_size = dir_size_kb(&data_dir)?; + let control_tar = + self.build_linux_control_tar(&package_name, &version, arch, installed_size, &data_dir)?; + let data_tar = build_data_tar(&data_dir)?; + + let deb_file = File::create(&deb_path) + .with_context(|| format!("Failed to create {}", deb_path.display()))?; + let mut ar_builder = ar::Builder::new(deb_file); + + let debian_binary = b"2.0\n"; + let mut header = ar::Header::new(b"debian-binary".to_vec(), debian_binary.len() as u64); + header.set_mode(0o100644); + header.set_mtime(0); + header.set_uid(0); + header.set_gid(0); + ar_builder.append(&header, &debian_binary[..])?; + + let mut header = ar::Header::new(b"control.tar.gz".to_vec(), control_tar.len() as u64); + header.set_mode(0o100644); + header.set_mtime(0); + header.set_uid(0); + header.set_gid(0); + ar_builder.append(&header, control_tar.as_slice())?; + + let mut header = ar::Header::new(b"data.tar.gz".to_vec(), data_tar.len() as u64); + header.set_mode(0o100644); + header.set_mtime(0); + header.set_uid(0); + header.set_gid(0); + ar_builder.append(&header, data_tar.as_slice())?; + + let _ = fs::remove_dir_all(&data_dir); + + tracing::info!("Created deb package: {}", deb_path.display()); + Ok(vec![deb_path]) + } + + /// Build an RPM package using the `rpm` crate. + /// + /// RPM bundling mirrors the Linux desktop payload used by the Debian flow, but it + /// expresses the package through `rpm::PackageBuilder` instead of manually + /// constructing archive members. + /// + /// The method performs these steps: + /// 1. Initialize the RPM builder with package identity, version, architecture, + /// description, and license metadata. + /// 2. Add the main executable at `/usr/bin/`. + /// 3. Generate a freedesktop `.desktop` file and add it under + /// `/usr/share/applications`. + /// 4. Add configured icons under the appropriate `hicolor` directories. + /// 5. Copy resources into a temporary directory, enumerate them, and add each file + /// under `/usr/lib//...`. + /// 6. Copy configured sidecar binaries into `/usr/bin` so RPM payloads match the + /// other Linux formats. + /// 7. Reuse Debian-style custom files and maintainer scripts where applicable. + /// 8. Attach runtime dependency declarations from the Debian settings. + /// 9. Serialize the final package to disk and remove the temporary staging area. + /// + /// The resulting artifact is written to `project_out_directory()/bundle/rpm`. + pub(crate) async fn bundle_linux_rpm(&self) -> Result> { + let name = self.main_binary_name().to_string(); + let version = self.version_string(); + let arch = self.binary_arch().rpm_arch(); + let license = self.license().unwrap_or("Unknown").to_string(); + let description = self.short_description(); + let resource_dir_name = self.linux_resource_dir_name(); + + let output_dir = self.project_out_directory().join("bundle").join("rpm"); + fs::create_dir_all(&output_dir)?; + + let rpm_filename = format!("{name}-{version}-1.{arch}.rpm"); + let rpm_path = output_dir.join(&rpm_filename); + + tracing::info!("Bundling {rpm_filename}..."); + + let mut builder = rpm::PackageBuilder::new(&name, &version, &license, arch, &description) + .using_config(rpm::BuildConfig::v4().compression(rpm::CompressionType::Gzip)); + + let binary_path = self.main_binary_path(); + let dest_bin = format!("/usr/bin/{name}"); + builder = builder + .with_file(&binary_path, rpm::FileOptions::new(dest_bin).mode(0o755)) + .context("Failed to add binary to RPM")?; + + let deb_settings = self.deb(); + let desktop_content = + self.generate_linux_desktop_file(deb_settings.desktop_template.as_deref())?; + let desktop_dest = format!("/usr/share/applications/{name}.desktop"); + + let temp_dir = output_dir.join("_rpm_temp"); + fs::create_dir_all(&temp_dir)?; + let temp_desktop = temp_dir.join(format!("{name}.desktop")); + fs::write(&temp_desktop, &desktop_content)?; + + builder = builder + .with_file( + &temp_desktop, + rpm::FileOptions::new(desktop_dest).mode(0o644), + ) + .context("Failed to add desktop file to RPM")?; + + let icon_files = self.linux_icon_files_or_default(&temp_dir)?; + for icon_path in &icon_files { + let ext = icon_path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + .to_lowercase(); + + match ext.as_str() { + "png" => { + if let Ok(file) = fs::File::open(icon_path) { + let decoder = png::Decoder::new(BufReader::new(file)); + if let Ok(reader) = decoder.read_info() { + let info = reader.info(); + let (w, h) = (info.width, info.height); + let size = w.max(h); + let dest = + format!("/usr/share/icons/hicolor/{size}x{size}/apps/{name}.png"); + builder = builder + .with_file(icon_path, rpm::FileOptions::new(dest).mode(0o644)) + .context("Failed to add icon to RPM")?; + } + } + } + "svg" => { + let dest = format!("/usr/share/icons/hicolor/scalable/apps/{name}.svg"); + builder = builder + .with_file(icon_path, rpm::FileOptions::new(dest).mode(0o644)) + .context("Failed to add SVG icon to RPM")?; + } + "ico" => { + let img = image::open(icon_path).with_context(|| { + format!("Failed to decode ICO icon: {}", icon_path.display()) + })?; + let (width, height) = img.dimensions(); + let size = width.max(height); + + let converted_icon = temp_dir.join(format!("{name}-{size}.png")); + img.save_with_format(&converted_icon, ImageFormat::Png) + .with_context(|| { + format!( + "Failed to convert ICO icon {} -> {}", + icon_path.display(), + converted_icon.display() + ) + })?; + + let dest = format!("/usr/share/icons/hicolor/{size}x{size}/apps/{name}.png"); + builder = builder + .with_file(&converted_icon, rpm::FileOptions::new(dest).mode(0o644)) + .context("Failed to add converted ICO icon to RPM")?; + } + _ => { + tracing::warn!( + "Skipping icon with unsupported extension '{}': {}", + ext, + icon_path.display() + ); + } + } + } + + let resource_temp = temp_dir.join("resources"); + fs::create_dir_all(&resource_temp)?; + self.copy_resources(&resource_temp)?; + + let resource_files = collect_files(&resource_temp)?; + for (src, relative) in &resource_files { + let dest = format!( + "/usr/lib/{resource_dir_name}/{}", + relative.to_string_lossy().replace('\\', "/") + ); + builder = builder + .with_file(src, rpm::FileOptions::new(&dest).mode(0o644)) + .with_context(|| format!("Failed to add resource {} to RPM", relative.display()))?; + } + + let ext_bin_temp = temp_dir.join("external_bin"); + fs::create_dir_all(&ext_bin_temp)?; + let external_bins = self.copy_external_binaries(&ext_bin_temp)?; + for src in &external_bins { + let dest_name = src + .file_name() + .and_then(|name| name.to_str()) + .context("External binary is missing a file name")?; + let dest = format!("/usr/bin/{dest_name}"); + builder = builder + .with_file(src, rpm::FileOptions::new(&dest).mode(0o755)) + .with_context(|| format!("Failed to add external binary {dest_name} to RPM"))?; + } + + let crate_dir = self.crate_dir(); + for (dest_path, src_path) in &deb_settings.files { + let src = if src_path.is_absolute() { + src_path.clone() + } else { + crate_dir.join(src_path) + }; + if src.exists() { + let dest = dest_path.to_string_lossy().to_string(); + let dest = if dest.starts_with('/') { + dest + } else { + format!("/{dest}") + }; + builder = builder + .with_file(&src, rpm::FileOptions::new(&dest).mode(0o644)) + .context("Failed to add custom file to RPM")?; + } + } + + if let Some(script_path) = &deb_settings.pre_install_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read_to_string(&path).with_context(|| { + format!("Failed to read pre-install script: {}", path.display()) + })?; + builder = builder.pre_install_script(content); + } + if let Some(script_path) = &deb_settings.post_install_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read_to_string(&path).with_context(|| { + format!("Failed to read post-install script: {}", path.display()) + })?; + builder = builder.post_install_script(content); + } + if let Some(script_path) = &deb_settings.pre_remove_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read_to_string(&path).with_context(|| { + format!("Failed to read pre-uninstall script: {}", path.display()) + })?; + builder = builder.pre_uninstall_script(content); + } + if let Some(script_path) = &deb_settings.post_remove_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read_to_string(&path).with_context(|| { + format!("Failed to read post-uninstall script: {}", path.display()) + })?; + builder = builder.post_uninstall_script(content); + } + + if let Some(deps) = &deb_settings.depends { + for dep in deps { + builder = builder.requires(rpm::Dependency::any(dep)); + } + } + + let package = builder.build().context("Failed to build RPM package")?; + + let mut rpm_file = fs::File::create(&rpm_path) + .with_context(|| format!("Failed to create {}", rpm_path.display()))?; + package + .write(&mut rpm_file) + .context("Failed to write RPM package")?; + + let _ = fs::remove_dir_all(&temp_dir); + + tracing::info!("Created RPM package: {}", rpm_path.display()); + Ok(vec![rpm_path]) + } + + /// Resolve or produce the final Android distributable for the requested package type. + /// + /// Android is different from the desktop bundlers in this module: most of the + /// packaging work already happens during the build pipeline when the Gradle + /// project, Android resources, manifests, and native libraries are assembled. + /// By the time this method runs, bundling is mostly about surfacing the final + /// artifact that should be handed back to the CLI. + /// + /// Supported package types: + /// - [`PackageType::Apk`]: validate that the APK produced by the normal Android + /// assemble flow exists and return its path. + /// - [`PackageType::Aab`]: invoke the dedicated Gradle bundle path through + /// `BuildRequest::android_gradle_bundle` and return the generated `.aab`. + /// + /// This method intentionally does not restage files or rewrite Android metadata. + /// It is the bridge from the Android build pipeline to the CLI's common bundle + /// reporting interface. + pub(crate) async fn bundle_android(&self, package_type: PackageType) -> Result> { + match package_type { + PackageType::Apk => { + let apk = self.build.android_apk_path(); + if !apk.exists() { + bail!( + "APK output not found at {}. Ensure gradle assemble completed successfully.", + apk.display() + ); + } + Ok(vec![apk]) + } + + PackageType::Aab => { + let aab = self + .build + .android_gradle_bundle() + .await + .context("Failed to run gradle bundleRelease")?; + Ok(vec![aab]) + } + _ => bail!("Unsupported Android package type: {package_type:?}"), + } + } + + /// Generate the Linux payload shared across bundle formats. + fn generate_linux_common_data(&self, data_dir: &Path) -> Result<()> { + let bin_name = self.main_binary_name(); + let resource_dir_name = self.linux_resource_dir_name(); + + let bin_dir = data_dir.join("usr/bin"); + fs::create_dir_all(&bin_dir)?; + let bin_dest = bin_dir.join(bin_name); + fs::copy(self.main_binary_path(), &bin_dest) + .with_context(|| format!("Failed to copy binary to {}", bin_dest.display()))?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&bin_dest, fs::Permissions::from_mode(0o755))?; + } + + let desktop_dir = data_dir.join("usr/share/applications"); + fs::create_dir_all(&desktop_dir)?; + + let deb_settings = self.deb(); + let desktop_content = + self.generate_linux_desktop_file(deb_settings.desktop_template.as_deref())?; + let desktop_path = desktop_dir.join(format!("{bin_name}.desktop")); + fs::write(&desktop_path, &desktop_content)?; + + self.copy_linux_icons(data_dir)?; + + let resource_dir = data_dir.join(format!("usr/lib/{resource_dir_name}")); + fs::create_dir_all(&resource_dir)?; + self.copy_resources(&resource_dir)?; + + let ext_bin_dir = data_dir.join("usr/bin"); + self.copy_external_binaries(&ext_bin_dir)?; + + Ok(()) + } + + /// Add Debian-specific payload extras to a staged Linux tree. + fn add_linux_deb_data(&self, data_dir: &Path) -> Result<()> { + let deb_settings = self.deb(); + let package_name = self.deb_package_name(); + for (deb_path, src_path) in &deb_settings.files { + let dest = data_dir.join(deb_path.strip_prefix("/").unwrap_or(deb_path)); + if let Some(parent) = dest.parent() { + fs::create_dir_all(parent)?; + } + let src = if src_path.is_absolute() { + src_path.clone() + } else { + self.crate_dir().join(src_path) + }; + fs::copy(&src, &dest).with_context(|| { + format!( + "Failed to copy custom deb file {} -> {}", + src.display(), + dest.display() + ) + })?; + } + + if let Some(changelog_path) = &deb_settings.changelog { + let changelog_src = if changelog_path.is_absolute() { + changelog_path.clone() + } else { + self.crate_dir().join(changelog_path) + }; + if changelog_src.exists() { + let doc_dir = data_dir.join(format!("usr/share/doc/{package_name}")); + fs::create_dir_all(&doc_dir)?; + let changelog_content = fs::read(&changelog_src)?; + let mut encoder = + flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::best()); + encoder.write_all(&changelog_content)?; + let compressed = encoder.finish()?; + fs::write(doc_dir.join("changelog.gz"), compressed)?; + } + } + + Ok(()) + } + + /// Directory name used for bundled Linux resources. + fn linux_resource_dir_name(&self) -> String { + self.product_name() + } + + /// Generate the contents of a .desktop file for the given bundle context. + fn generate_linux_desktop_file(&self, desktop_template: Option<&Path>) -> Result { + let mut handlebars = Handlebars::new(); + handlebars.set_strict_mode(false); + + let template = match desktop_template { + // Path to template + Some(path) => fs::read_to_string(path) + .with_context(|| format!("Failed to read desktop template: {}", path.display()))?, + + // Default .desktop file template (Handlebars). + None => String::from( + "[Desktop Entry] +Categories={{categories}} +{{#if comment}} +Comment={{comment}} +{{/if}} +Exec={{exec}} +Icon={{icon}} +Name={{name}} +Terminal=false +Type=Application +", + ), + }; + + handlebars + .register_template_string("desktop", &template) + .context("Failed to register desktop template")?; + + let categories = self + .app_category() + .and_then(|c| c.parse::().ok()) + .map(|cat| cat.freedesktop_categories().to_string()) + .unwrap_or_default(); + + let bin_name = self.main_binary_name(); + let product_name = self.product_name(); + let description = self.short_description(); + let has_comment = !description.is_empty(); + + let mut json_data = serde_json::Map::new(); + json_data.insert("categories".into(), serde_json::Value::String(categories)); + json_data.insert( + "exec".into(), + serde_json::Value::String(bin_name.to_string()), + ); + json_data.insert( + "icon".into(), + serde_json::Value::String(bin_name.to_string()), + ); + json_data.insert("name".into(), serde_json::Value::String(product_name)); + if has_comment { + json_data.insert("comment".into(), serde_json::Value::String(description)); + } + + let rendered = handlebars + .render("desktop", &json_data) + .context("Failed to render desktop template")?; + + Ok(rendered) + } + + /// Copy icon files into the freedesktop hicolor icon theme hierarchy. + fn copy_linux_icons(&self, data_dir: &Path) -> Result> { + let icon_files = self.linux_icon_files_or_default(data_dir)?; + let bin_name = self.main_binary_name(); + let mut paths = Vec::new(); + + for icon_path in &icon_files { + let ext = icon_path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or("") + .to_lowercase(); + + match ext.as_str() { + "png" => { + let file = fs::File::open(icon_path).with_context(|| { + format!("Failed to open PNG icon: {}", icon_path.display()) + })?; + let decoder = png::Decoder::new(BufReader::new(file)); + let reader = decoder.read_info().with_context(|| { + format!("Failed to decode PNG dimensions: {}", icon_path.display()) + })?; + let info = reader.info(); + let (width, height) = (info.width, info.height); + let size = width.max(height); + + let dest_dir = + data_dir.join(format!("usr/share/icons/hicolor/{size}x{size}/apps")); + fs::create_dir_all(&dest_dir)?; + + let dest = dest_dir.join(format!("{bin_name}.png")); + fs::copy(icon_path, &dest).with_context(|| { + format!( + "Failed to copy icon {} -> {}", + icon_path.display(), + dest.display() + ) + })?; + + tracing::debug!("Copied icon {}x{}: {}", size, size, dest.display()); + paths.push(dest); + } + "svg" => { + let dest_dir = data_dir.join("usr/share/icons/hicolor/scalable/apps"); + fs::create_dir_all(&dest_dir)?; + + let dest = dest_dir.join(format!("{bin_name}.svg")); + fs::copy(icon_path, &dest).with_context(|| { + format!( + "Failed to copy icon {} -> {}", + icon_path.display(), + dest.display() + ) + })?; + + tracing::debug!("Copied SVG icon: {}", dest.display()); + paths.push(dest); + } + "ico" => { + let img = image::open(icon_path).with_context(|| { + format!("Failed to decode ICO icon: {}", icon_path.display()) + })?; + let (width, height) = img.dimensions(); + let size = width.max(height); + + let dest_dir = + data_dir.join(format!("usr/share/icons/hicolor/{size}x{size}/apps")); + fs::create_dir_all(&dest_dir)?; + + let dest = dest_dir.join(format!("{bin_name}.png")); + img.save_with_format(&dest, ImageFormat::Png) + .with_context(|| { + format!( + "Failed to convert ICO icon {} -> {}", + icon_path.display(), + dest.display() + ) + })?; + + tracing::debug!( + "Converted ICO icon {}x{} to PNG: {}", + width, + height, + dest.display() + ); + paths.push(dest); + } + _ => { + tracing::warn!( + "Skipping icon with unsupported extension '{}': {}", + ext, + icon_path.display() + ); + } + } + } + + Ok(paths) + } + + fn linux_icon_files_or_default(&self, scratch_dir: &Path) -> Result> { + let icon_files = self.icon_files()?; + if !icon_files.is_empty() { + return Ok(icon_files); + } + + fs::create_dir_all(scratch_dir)?; + let default_icon = scratch_dir.join(".dx-default-icon.png"); + if !default_icon.exists() { + fs::write(&default_icon, DEFAULT_LINUX_ICON_PNG).with_context(|| { + format!( + "Failed to write default Linux icon to {}", + default_icon.display() + ) + })?; + } + + tracing::info!("No bundle icons configured; using the default Dioxus icon"); + Ok(vec![default_icon]) + } + + /// Create the top-level symlinks in the AppDir that AppImage/linuxdeploy expects. + fn create_linux_appdir_symlinks(&self, appdir: &Path, name: &str) -> Result<()> { + let apprun = appdir.join("AppRun"); + let bin_target = format!("usr/bin/{name}"); + + #[cfg(unix)] + { + std::os::unix::fs::symlink(&bin_target, &apprun).with_context(|| { + format!( + "Failed to create AppRun symlink: {} -> {}", + apprun.display(), + bin_target + ) + })?; + } + + #[cfg(not(unix))] + { + let src = appdir.join(&bin_target); + if src.exists() { + fs::copy(&src, &apprun)?; + } + } + + let desktop_link = appdir.join(format!("{name}.desktop")); + let desktop_target = format!("usr/share/applications/{name}.desktop"); + + #[cfg(unix)] + { + std::os::unix::fs::symlink(&desktop_target, &desktop_link).with_context(|| { + format!( + "Failed to create desktop symlink: {} -> {}", + desktop_link.display(), + desktop_target + ) + })?; + } + + #[cfg(not(unix))] + { + let src = appdir.join(&desktop_target); + if src.exists() { + fs::copy(&src, &desktop_link)?; + } + } + + if let Some(icon_in_appdir) = find_icon_in_appdir(appdir, name) { + create_appdir_icon_links(appdir, &icon_in_appdir, name)?; + } + + Ok(()) + } + + /// Build the control.tar.gz containing the control file, md5sums, and maintainer scripts. + fn build_linux_control_tar( + &self, + package_name: &str, + version: &str, + arch: &str, + installed_size: u64, + data_dir: &Path, + ) -> Result> { + let buf = Vec::new(); + let encoder = flate2::write::GzEncoder::new(buf, flate2::Compression::default()); + let mut tar = tar::Builder::new(encoder); + + let control = + self.generate_linux_control_file(package_name, version, arch, installed_size)?; + append_tar_bytes(&mut tar, "./control", control.as_bytes(), 0o644)?; + + let md5sums = generate_md5sums(data_dir)?; + append_tar_bytes(&mut tar, "./md5sums", md5sums.as_bytes(), 0o644)?; + + let deb = self.deb(); + let crate_dir = self.crate_dir(); + + if let Some(script_path) = &deb.pre_install_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read(&path) + .with_context(|| format!("Failed to read preinst script: {}", path.display()))?; + append_tar_bytes(&mut tar, "./preinst", &content, 0o755)?; + } + if let Some(script_path) = &deb.post_install_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read(&path) + .with_context(|| format!("Failed to read postinst script: {}", path.display()))?; + append_tar_bytes(&mut tar, "./postinst", &content, 0o755)?; + } + if let Some(script_path) = &deb.pre_remove_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read(&path) + .with_context(|| format!("Failed to read prerm script: {}", path.display()))?; + append_tar_bytes(&mut tar, "./prerm", &content, 0o755)?; + } + if let Some(script_path) = &deb.post_remove_script { + let path = resolve_path(&crate_dir, script_path); + let content = fs::read(&path) + .with_context(|| format!("Failed to read postrm script: {}", path.display()))?; + append_tar_bytes(&mut tar, "./postrm", &content, 0o755)?; + } + + let encoder = tar.into_inner()?; + let data = encoder.finish()?; + Ok(data) + } + + /// Generate the Debian control file content. + fn generate_linux_control_file( + &self, + package_name: &str, + version: &str, + arch: &str, + installed_size: u64, + ) -> Result { + let deb = self.deb(); + + let mut control = String::new(); + control.push_str(&format!("Package: {package_name}\n")); + control.push_str(&format!("Version: {version}\n")); + control.push_str(&format!("Architecture: {arch}\n")); + control.push_str(&format!("Installed-Size: {installed_size}\n")); + + let description = self.short_description(); + if !description.is_empty() { + control.push_str(&format!("Description: {description}\n")); + + if let Some(long_desc) = self.long_description() { + for line in long_desc.lines() { + if line.is_empty() { + control.push_str(" .\n"); + } else { + control.push_str(&format!(" {line}\n")); + } + } + } + } + + let section = deb.section.as_deref().unwrap_or("utils"); + control.push_str(&format!("Section: {section}\n")); + + let priority = deb.priority.as_deref().unwrap_or("optional"); + control.push_str(&format!("Priority: {priority}\n")); + + if let Some(url) = self.homepage_url() { + control.push_str(&format!("Homepage: {url}\n")); + } + + let maintainer = self + .authors_comma_separated() + .unwrap_or_else(|| "Unknown".to_string()); + control.push_str(&format!("Maintainer: {maintainer}\n")); + + if let Some(deps) = &deb.depends { + if !deps.is_empty() { + control.push_str(&format!("Depends: {}\n", deps.join(", "))); + } + } + + if let Some(recs) = &deb.recommends { + if !recs.is_empty() { + control.push_str(&format!("Recommends: {}\n", recs.join(", "))); + } + } + + if let Some(provs) = &deb.provides { + if !provs.is_empty() { + control.push_str(&format!("Provides: {}\n", provs.join(", "))); + } + } + + if let Some(conflicts) = &deb.conflicts { + if !conflicts.is_empty() { + control.push_str(&format!("Conflicts: {}\n", conflicts.join(", "))); + } + } + + if let Some(replaces) = &deb.replaces { + if !replaces.is_empty() { + control.push_str(&format!("Replaces: {}\n", replaces.join(", "))); + } + } + + Ok(control) + } + + /// Generate a Debian-friendly package name. + fn deb_package_name(&self) -> String { + self.main_binary_name().to_lowercase().replace('_', "-") + } +} + +/// Find the best icon file within the AppDir's hicolor directory. +fn find_icon_in_appdir(appdir: &Path, name: &str) -> Option { + let icons_dir = appdir.join("usr/share/icons/hicolor"); + if !icons_dir.exists() { + return None; + } + + let mut best_png: Option<(u32, PathBuf)> = None; + let mut svg_icon: Option = None; + let png_name = format!("{name}.png"); + let svg_name = format!("{name}.svg"); + + if let Ok(entries) = fs::read_dir(&icons_dir) { + for entry in entries.flatten() { + let size_dir = entry.path(); + let png_icon = size_dir.join("apps").join(&png_name); + if png_icon.exists() { + let dir_name = entry.file_name().to_string_lossy().to_string(); + if let Some(size_str) = dir_name.split('x').next() { + if let Ok(size) = size_str.parse::() { + if best_png + .as_ref() + .is_none_or(|(best_size, _)| size > *best_size) + { + best_png = Some((size, png_icon)); + } + } + } + } + + let svg_path = size_dir.join("apps").join(&svg_name); + if svg_icon.is_none() && svg_path.exists() { + svg_icon = Some(svg_path); + } + } + } + + best_png.map(|(_, path)| path).or(svg_icon) +} + +/// Create the AppDir root icon links that linuxdeploy expects. +fn create_appdir_icon_links(appdir: &Path, icon_path: &Path, name: &str) -> Result<()> { + let ext = icon_path + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("png"); + let named_icon = appdir.join(format!("{name}.{ext}")); + let dir_icon = appdir.join(".DirIcon"); + + #[cfg(unix)] + { + let relative = icon_path.strip_prefix(appdir).unwrap_or(icon_path); + let relative_str = relative.to_string_lossy().to_string(); + std::os::unix::fs::symlink(&relative_str, &named_icon).with_context(|| { + format!( + "Failed to create icon symlink: {} -> {}", + named_icon.display(), + relative_str + ) + })?; + std::os::unix::fs::symlink(&relative_str, &dir_icon).with_context(|| { + format!( + "Failed to create .DirIcon symlink: {} -> {}", + dir_icon.display(), + relative_str + ) + })?; + } + + #[cfg(not(unix))] + { + fs::copy(icon_path, &named_icon).with_context(|| { + format!( + "Failed to copy icon {} to {}", + icon_path.display(), + named_icon.display() + ) + })?; + fs::copy(icon_path, &dir_icon).with_context(|| { + format!( + "Failed to copy icon {} to {}", + icon_path.display(), + dir_icon.display() + ) + })?; + } + + Ok(()) +} + +/// Search for an .AppImage file in the output directory. +fn find_appimage_output(dir: &Path, name: &str) -> Result> { + if let Ok(entries) = fs::read_dir(dir) { + for entry in entries.flatten() { + let path = entry.path(); + if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) { + if file_name.ends_with(".AppImage") && file_name.contains(name) { + return Ok(Some(path)); + } + } + } + } + Ok(None) +} + +/// Build data.tar.gz from the data directory. +fn build_data_tar(data_dir: &Path) -> Result> { + let buf = Vec::new(); + let encoder = flate2::write::GzEncoder::new(buf, flate2::Compression::default()); + let mut tar = tar::Builder::new(encoder); + + tar.append_dir_all(".", data_dir) + .context("Failed to build data.tar.gz")?; + + let encoder = tar.into_inner()?; + let data = encoder.finish()?; + Ok(data) +} + +/// Generate md5sums file for all files in the data directory. +fn generate_md5sums(data_dir: &Path) -> Result { + let mut md5sums = String::new(); + + for entry in walkdir::WalkDir::new(data_dir) { + let entry = entry?; + if !entry.file_type().is_file() { + continue; + } + + let path = entry.path(); + let content = fs::read(path)?; + let digest = md5::compute(&content); + let relative = path.strip_prefix(data_dir).unwrap_or(path); + let relative_str = relative.to_string_lossy().replace('\\', "/"); + + md5sums.push_str(&format!("{:x} {relative_str}\n", digest)); + } + + Ok(md5sums) +} + +/// Append raw bytes as a file entry in a tar archive. +fn append_tar_bytes( + tar: &mut tar::Builder, + path: &str, + data: &[u8], + mode: u32, +) -> Result<()> { + let mut header = tar::Header::new_gnu(); + header.set_path(path)?; + header.set_size(data.len() as u64); + header.set_mode(mode); + header.set_mtime(0); + header.set_uid(0); + header.set_gid(0); + header.set_cksum(); + + tar.append(&header, Cursor::new(data)) + .with_context(|| format!("Failed to add {path} to tar"))?; + + Ok(()) +} + +/// Collect all files in a directory, returning (absolute_path, relative_path) pairs. +fn collect_files(dir: &Path) -> Result> { + let mut files = Vec::new(); + if !dir.exists() { + return Ok(files); + } + for entry in walkdir::WalkDir::new(dir) { + let entry = entry?; + if !entry.file_type().is_file() { + continue; + } + let abs = entry.path().to_path_buf(); + let rel = entry + .path() + .strip_prefix(dir) + .unwrap_or(entry.path()) + .to_path_buf(); + files.push((abs, rel)); + } + Ok(files) +} + +/// Calculate total size of a directory tree in kilobytes. +fn dir_size_kb(path: &Path) -> Result { + let mut total: u64 = 0; + for entry in walkdir::WalkDir::new(path) { + let entry = entry?; + if entry.file_type().is_file() { + total += entry.metadata()?.len(); + } + } + + Ok(total.div_ceil(1024)) +} + +/// Resolve a path that may be relative to the crate directory. +fn resolve_path(crate_dir: &Path, path: &Path) -> PathBuf { + if path.is_absolute() { + path.to_path_buf() + } else { + crate_dir.join(path) + } +} diff --git a/packages/cli/src/bundler/macos.rs b/packages/cli/src/bundler/macos.rs new file mode 100644 index 000000000..f9805be3a --- /dev/null +++ b/packages/cli/src/bundler/macos.rs @@ -0,0 +1,981 @@ +use crate::bundler::{copy_dir_recursive, AppCategory, Bundle, BundleContext}; +use crate::{MacOsSettings, PackageType}; +use anyhow::{bail, Context, Result}; +use image::{DynamicImage, ImageReader}; +use std::fs::{self, File}; +use std::io::BufWriter; +use std::path::{Path, PathBuf}; +use std::process::{Command as StdCommand, Stdio}; +use tokio::io::AsyncWriteExt; +use tokio::process::Command; + +impl BundleContext<'_> { + /// Create the final macOS `.app` bundle and apply Apple-specific metadata. + /// + /// This is the primary macOS bundling routine. It takes the executable produced by + /// the Dioxus build pipeline and assembles the canonical app bundle structure in + /// `project_out_directory()/macos/.app`. + /// + /// The method performs the following steps: + /// 1. Resolve macOS settings and choose the bundle/display name. + /// 2. Remove any existing output bundle to keep the result deterministic. + /// 3. Create the `Contents/`, `Contents/MacOS`, `Contents/Resources`, and + /// optionally `Contents/Frameworks` directories. + /// 4. Copy the main executable into `Contents/MacOS/` and mark it executable. + /// 5. Build or copy an `.icns` file into `Contents/Resources/`. + /// 6. Copy configured resources and sidecar binaries into the bundle. + /// 7. Copy configured frameworks and arbitrary extra files from + /// `MacOsSettings::files`. + /// 8. Generate an `Info.plist` from bundle metadata, unless a custom plist path + /// was configured and exists. + /// 9. Write the legacy `PkgInfo` file. + /// 10. If signing is configured, sign frameworks first, then the main binary, and + /// finally the enclosing `.app`. + /// 11. If notarization credentials are available, zip the `.app`, submit it to + /// Apple's notary service, staple the result, and delete the temporary zip. + /// + /// Contributor notes: + /// - The build system guarantees the executable exists, but this method is + /// responsible for the final `.app` on-disk shape. + /// - Framework paths may be absolute or relative to the crate directory. Missing + /// paths are tolerated because some entries may refer to system frameworks. + /// - Lack of signing or notarization credentials is not fatal; an unsigned `.app` + /// is still returned as a valid bundle artifact. + pub(crate) async fn bundle_macos_app(&self) -> Result> { + let product_name = self.product_name(); + let macos_settings = self.macos(); + + let bundle_name = macos_settings + .bundle_name + .as_deref() + .unwrap_or(&product_name); + + let output_dir = self.project_out_directory().join("macos"); + let app_dir = output_dir.join(format!("{bundle_name}.app")); + + tracing::info!("Creating macOS .app bundle at {}", app_dir.display()); + + if app_dir.exists() { + fs::remove_dir_all(&app_dir) + .with_context(|| format!("Failed to clean existing .app: {}", app_dir.display()))?; + } + + let contents_dir = app_dir.join("Contents"); + let macos_dir = contents_dir.join("MacOS"); + let resources_dir = contents_dir.join("Resources"); + let frameworks_dir = contents_dir.join("Frameworks"); + + fs::create_dir_all(&macos_dir)?; + fs::create_dir_all(&resources_dir)?; + + let binary_src = self.main_binary_path(); + let binary_dest = macos_dir.join(self.main_binary_name()); + tracing::debug!( + "Copying binary: {} -> {}", + binary_src.display(), + binary_dest.display() + ); + fs::copy(&binary_src, &binary_dest) + .with_context(|| format!("Failed to copy main binary to {}", binary_dest.display()))?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&binary_dest, fs::Permissions::from_mode(0o755))?; + } + + let icon_path = self.create_macos_icns_file(&resources_dir)?; + let icon_filename = icon_path + .as_ref() + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().to_string()); + + self.copy_resources(&resources_dir)?; + let _external_bins = self.copy_external_binaries(&macos_dir)?; + + if let Some(frameworks) = &macos_settings.frameworks { + if !frameworks.is_empty() { + fs::create_dir_all(&frameworks_dir)?; + for framework in frameworks { + let framework_path = PathBuf::from(framework); + if !framework_path.exists() { + let resolved = self.crate_dir().join(&framework_path); + if resolved.exists() { + copy_framework(&resolved, &frameworks_dir)?; + } else { + tracing::debug!( + "Framework not found as file, assuming system framework: {}", + framework + ); + } + } else { + copy_framework(&framework_path, &frameworks_dir)?; + } + } + } + } + + for (bundle_path, source_path) in &macos_settings.files { + let dest = contents_dir.join(bundle_path); + let src = if source_path.is_relative() { + self.crate_dir().join(source_path) + } else { + source_path.clone() + }; + + if !src.exists() { + tracing::warn!( + "Custom file not found: {} (for bundle path {})", + src.display(), + bundle_path.display() + ); + continue; + } + + if let Some(parent) = dest.parent() { + fs::create_dir_all(parent)?; + } + + if src.is_dir() { + copy_dir_recursive(&src, &dest)?; + } else { + fs::copy(&src, &dest).with_context(|| { + format!( + "Failed to copy custom file {} -> {}", + src.display(), + dest.display() + ) + })?; + } + } + + let info_plist = self.create_macos_info_plist(&macos_settings, icon_filename.as_deref())?; + let plist_path = contents_dir.join("Info.plist"); + + if let Some(custom_plist_path) = &macos_settings.info_plist_path { + let custom_path = if custom_plist_path.is_relative() { + self.crate_dir().join(custom_plist_path) + } else { + custom_plist_path.clone() + }; + if custom_path.exists() { + tracing::info!("Using custom Info.plist from {}", custom_path.display()); + fs::copy(&custom_path, &plist_path)?; + } else { + tracing::warn!( + "Custom Info.plist not found at {}, generating default", + custom_path.display() + ); + write_plist(&info_plist, &plist_path)?; + } + } else { + write_plist(&info_plist, &plist_path)?; + } + + fs::write(contents_dir.join("PkgInfo"), "APPL????")?; + + let signing_identity = setup_keychain(macos_settings.signing_identity.as_deref()).await?; + + if let Some(identity) = &signing_identity { + tracing::info!("Signing .app bundle with identity: {}", identity.identity); + + let mut sign_targets = Vec::new(); + + if frameworks_dir.exists() { + for entry in fs::read_dir(&frameworks_dir)? { + let entry = entry?; + sign_targets.push(SignTarget { path: entry.path() }); + } + } + + sign_targets.push(SignTarget { + path: binary_dest.clone(), + }); + sign_targets.push(SignTarget { + path: app_dir.clone(), + }); + + sign_paths(identity, sign_targets, &macos_settings).await?; + + let should_notarize = + std::env::var("APPLE_ID").is_ok() || std::env::var("APPLE_API_KEY").is_ok(); + + if should_notarize { + let zip_path = output_dir.join(format!("{bundle_name}.zip")); + tracing::info!("Creating zip for notarization: {}", zip_path.display()); + + let status = Command::new("ditto") + .args([ + "-c", + "-k", + "--sequesterRsrc", + "--keepParent", + &app_dir.display().to_string(), + &zip_path.display().to_string(), + ]) + .status() + .await + .context("Failed to run ditto for zip creation")?; + + if !status.success() { + bail!("ditto failed to create zip for notarization"); + } + + match notarize(&zip_path).await { + Ok(()) => { + let _ = fs::remove_file(&zip_path); + } + Err(e) => { + let _ = fs::remove_file(&zip_path); + return Err(e.context("Notarization failed")); + } + } + } + } else { + tracing::debug!("No signing identity found; skipping code signing"); + } + + Ok(vec![app_dir]) + } + + /// Package the macOS application bundle into a distributable `.dmg` disk image. + /// + /// DMG generation depends on having a complete `.app` bundle first. If the + /// current bundling pass already produced one, this method reuses it. Otherwise it + /// calls [`BundleContext::bundle_macos_app`] internally and returns both the + /// resulting `.dmg` and the intermediate `.app` paths so the higher-level + /// orchestrator can decide whether the `.app` should be preserved or cleaned up. + /// + /// The process is: + /// 1. Resolve the `.app` input, either from `bundles` or by building it now. + /// 2. Create a temporary staging directory containing the `.app` and an + /// `Applications` symlink for drag-and-drop installation. + /// 3. Invoke `hdiutil create -format UDZO` to build a compressed, read-only disk + /// image in `project_out_directory()/macos`. + /// 4. Optionally sign the generated `.dmg`. + /// 5. Optionally notarize and staple the `.dmg` if Apple credentials are present. + /// + /// Only the final `.dmg` and, when synthesized as a prerequisite, the `.app` are + /// considered outputs. The temporary DMG staging directory is always discarded. + pub(crate) async fn bundle_macos_dmg(&self, bundles: &[Bundle]) -> Result { + let product_name = self.product_name(); + let macos_settings = self.macos(); + + let bundle_name = macos_settings + .bundle_name + .as_deref() + .unwrap_or(&product_name); + + let (app_paths, app_bundle_paths) = if let Some(app_bundle) = bundles + .iter() + .find(|b| b.package_type == PackageType::MacOsBundle) + { + (app_bundle.bundle_paths.clone(), Vec::new()) + } else { + let paths = self.bundle_macos_app().await?; + (paths.clone(), paths) + }; + + if app_paths.is_empty() { + bail!("No .app bundle found to package into a DMG"); + } + + let app_path = &app_paths[0]; + if !app_path.exists() { + bail!( + ".app bundle does not exist at expected path: {}", + app_path.display() + ); + } + + let output_dir = self.project_out_directory().join("macos"); + fs::create_dir_all(&output_dir)?; + + let dmg_filename = format!( + "{}_{}_{}", + bundle_name, + self.version_string(), + self.binary_arch() + ); + let dmg_path = output_dir.join(format!("{dmg_filename}.dmg")); + + tracing::info!("Creating DMG at {}", dmg_path.display()); + + let staging_dir = + tempfile::tempdir().context("Failed to create temp dir for DMG staging")?; + let staging_path = staging_dir.path(); + + let staged_app = staging_path.join(app_path.file_name().unwrap()); + copy_dir_recursive(app_path, &staged_app)?; + + #[cfg(unix)] + { + std::os::unix::fs::symlink("/Applications", staging_path.join("Applications")) + .context("Failed to create /Applications symlink in DMG staging")?; + } + + if dmg_path.exists() { + fs::remove_file(&dmg_path)?; + } + + let status = Command::new("hdiutil") + .args([ + "create", + "-volname", + bundle_name, + "-srcfolder", + &staging_path.display().to_string(), + "-ov", + "-format", + "UDZO", + &dmg_path.display().to_string(), + ]) + .status() + .await + .context("Failed to execute `hdiutil create`")?; + + if !status.success() { + bail!("`hdiutil create` failed with exit code: {status}"); + } + + tracing::info!("DMG created at {}", dmg_path.display()); + + let signing_identity = setup_keychain(macos_settings.signing_identity.as_deref()).await?; + if let Some(identity) = &signing_identity { + tracing::info!("Signing DMG with identity: {}", identity.identity); + sign_paths( + identity, + vec![SignTarget { + path: dmg_path.clone(), + }], + &macos_settings, + ) + .await?; + + let should_notarize = + std::env::var("APPLE_ID").is_ok() || std::env::var("APPLE_API_KEY").is_ok(); + + if should_notarize { + notarize(&dmg_path).await?; + } + } + + Ok(DmgBundled { + dmg: vec![dmg_path], + app: app_bundle_paths, + }) + } + + /// Create an ICNS file from the icon files configured in the bundle context. + fn create_macos_icns_file(&self, out_dir: &Path) -> Result> { + let icon_paths = self.icon_files()?; + if icon_paths.is_empty() { + return Ok(None); + } + + let dest_path = out_dir.join(format!("{}.icns", self.product_name())); + + for icon_path in &icon_paths { + if icon_path + .extension() + .map(|ext| ext.eq_ignore_ascii_case("icns")) + .unwrap_or(false) + { + tracing::info!("Copying existing .icns file: {}", icon_path.display()); + std::fs::copy(icon_path, &dest_path).with_context(|| { + format!( + "Failed to copy .icns file from {} to {}", + icon_path.display(), + dest_path.display() + ) + })?; + return Ok(Some(dest_path)); + } + } + + let mut family = icns::IconFamily::new(); + + for icon_path in &icon_paths { + let ext = icon_path + .extension() + .map(|e| e.to_string_lossy().to_lowercase()) + .unwrap_or_default(); + + if ext != "png" { + tracing::debug!("Skipping non-PNG icon file: {}", icon_path.display()); + continue; + } + + let img = ImageReader::open(icon_path) + .with_context(|| format!("Failed to open icon image: {}", icon_path.display()))? + .decode() + .with_context(|| format!("Failed to decode icon image: {}", icon_path.display()))?; + + add_image_to_family(&mut family, &img)?; + } + + if family.is_empty() { + tracing::warn!("No valid icon images found; skipping .icns generation"); + return Ok(None); + } + + let file = File::create(&dest_path) + .with_context(|| format!("Failed to create {}", dest_path.display()))?; + let writer = BufWriter::new(file); + family + .write(writer) + .with_context(|| format!("Failed to write .icns to {}", dest_path.display()))?; + + tracing::info!("Generated .icns at {}", dest_path.display()); + Ok(Some(dest_path)) + } + + /// Build a `plist::Dictionary` for Info.plist. + fn create_macos_info_plist( + &self, + macos_settings: &MacOsSettings, + icon_filename: Option<&str>, + ) -> Result { + let mut dict = plist::Dictionary::new(); + + let product_name = self.product_name(); + let bundle_name = macos_settings + .bundle_name + .as_deref() + .unwrap_or(&product_name); + + dict.insert( + "CFBundleDevelopmentRegion".into(), + plist::Value::String("English".into()), + ); + dict.insert( + "CFBundleDisplayName".into(), + plist::Value::String(bundle_name.to_string()), + ); + dict.insert( + "CFBundleExecutable".into(), + plist::Value::String(self.main_binary_name().to_string()), + ); + + if let Some(icon) = icon_filename { + dict.insert( + "CFBundleIconFile".into(), + plist::Value::String(icon.to_string()), + ); + } + + dict.insert( + "CFBundleIdentifier".into(), + plist::Value::String(self.bundle_identifier()), + ); + dict.insert( + "CFBundleInfoDictionaryVersion".into(), + plist::Value::String("6.0".into()), + ); + dict.insert( + "CFBundleName".into(), + plist::Value::String(bundle_name.to_string()), + ); + dict.insert( + "CFBundlePackageType".into(), + plist::Value::String("APPL".into()), + ); + + let version = self.version_string(); + let bundle_version = macos_settings.bundle_version.as_deref().unwrap_or(&version); + + dict.insert( + "CFBundleShortVersionString".into(), + plist::Value::String(version.clone()), + ); + dict.insert( + "CFBundleVersion".into(), + plist::Value::String(bundle_version.to_string()), + ); + + let min_version = macos_settings + .minimum_system_version + .as_deref() + .unwrap_or("10.13"); + dict.insert( + "LSMinimumSystemVersion".into(), + plist::Value::String(min_version.to_string()), + ); + + if let Some(category_str) = self.app_category() { + if let Ok(category) = category_str.parse::() { + dict.insert( + "LSApplicationCategoryType".into(), + plist::Value::String(category.macos_application_category_type().to_string()), + ); + } + } + + if let Some(copyright) = self.copyright_string() { + dict.insert( + "NSHumanReadableCopyright".into(), + plist::Value::String(copyright.to_string()), + ); + } + + dict.insert( + "NSHighResolutionCapable".into(), + plist::Value::Boolean(true), + ); + + if let Some(domain) = &macos_settings.exception_domain { + let mut ats_dict = plist::Dictionary::new(); + let mut exception_dict = plist::Dictionary::new(); + let mut domain_dict = plist::Dictionary::new(); + domain_dict.insert( + "NSExceptionAllowsInsecureHTTPLoads".into(), + plist::Value::Boolean(true), + ); + domain_dict.insert("NSIncludesSubdomains".into(), plist::Value::Boolean(true)); + exception_dict.insert(domain.clone(), plist::Value::Dictionary(domain_dict)); + ats_dict.insert( + "NSExceptionDomains".into(), + plist::Value::Dictionary(exception_dict), + ); + dict.insert( + "NSAppTransportSecurity".into(), + plist::Value::Dictionary(ats_dict), + ); + } + + if let Some(provider) = &macos_settings.provider_short_name { + dict.insert( + "ITSAppUsesNonExemptEncryption".into(), + plist::Value::Boolean(false), + ); + let _ = provider; + } + + Ok(dict) + } +} + +/// Add all appropriate size variants of an image to the ICNS family. +fn add_image_to_family(family: &mut icns::IconFamily, img: &DynamicImage) -> Result<()> { + // The icon sizes (in points) we generate for the .icns file, along with their + // densities. macOS expects both 1x and 2x variants. + const ICON_SIZES: &[(u32, u32, u32)] = &[ + (16, 16, 1), + (16, 16, 2), + (32, 32, 1), + (32, 32, 2), + (64, 64, 1), + (64, 64, 2), + (128, 128, 1), + (128, 128, 2), + (256, 256, 1), + (256, 256, 2), + (512, 512, 1), + (512, 512, 2), + ]; + + for &(width, height, density) in ICON_SIZES { + let pixel_width = width * density; + let pixel_height = height * density; + + let icon_type = + match icns::IconType::from_pixel_size_and_density(pixel_width, pixel_height, density) { + Some(t) => t, + None => continue, + }; + + if family.has_icon_with_type(icon_type) { + continue; + } + + let resized = img.resize_exact( + pixel_width, + pixel_height, + image::imageops::FilterType::Lanczos3, + ); + + let rgba = resized.to_rgba8(); + let icns_image = icns::Image::from_data( + icns::PixelFormat::RGBA, + pixel_width, + pixel_height, + rgba.into_raw(), + ) + .with_context(|| { + format!("Failed to create icns::Image for {pixel_width}x{pixel_height}@{density}x") + })?; + + family + .add_icon_with_type(&icns_image, icon_type) + .with_context(|| format!("Failed to add icon type {icon_type:?} to ICNS family"))?; + } + + Ok(()) +} + +/// Write a plist dictionary to a file. +fn write_plist(dict: &plist::Dictionary, path: &Path) -> Result<()> { + plist::Value::Dictionary(dict.clone()) + .to_file_xml(path) + .with_context(|| format!("Failed to write Info.plist to {}", path.display())) +} + +/// Copy a framework (directory or .dylib) to the Frameworks directory. +fn copy_framework(src: &Path, frameworks_dir: &Path) -> Result<()> { + let dest = frameworks_dir.join(src.file_name().context("Framework path has no filename")?); + + tracing::debug!("Copying framework: {} -> {}", src.display(), dest.display()); + + if src.is_dir() { + copy_dir_recursive(src, &dest)?; + } else { + fs::copy(src, &dest)?; + } + + Ok(()) +} + +/// Set up the signing identity. +async fn setup_keychain(identity: Option<&str>) -> Result> { + let certificate_encoded = std::env::var("APPLE_CERTIFICATE").ok(); + let certificate_password = std::env::var("APPLE_CERTIFICATE_PASSWORD") + .ok() + .unwrap_or_default(); + + if let Some(cert_base64) = certificate_encoded { + tracing::info!("Setting up temporary keychain for code signing (CI mode)"); + let keychain = setup_temp_keychain(&cert_base64, &certificate_password).await?; + let identity_name = find_identity_in_keychain(&keychain.path).await?; + return Ok(Some(SigningIdentity { + identity: identity_name, + temp_keychain: Some(keychain), + })); + } + + if let Some(id) = identity { + if !id.is_empty() { + return Ok(Some(SigningIdentity { + identity: id.to_string(), + temp_keychain: None, + })); + } + } + + Ok(None) +} + +/// Set up a temporary keychain and import the certificate into it. +async fn setup_temp_keychain(cert_base64: &str, password: &str) -> Result { + use std::io::Write; + + let keychain_password = "dioxus-bundle-keychain"; + let keychain_path = std::env::temp_dir().join("dioxus-signing.keychain-db"); + + let cert_data = base64_decode(cert_base64) + .await + .context("Failed to decode APPLE_CERTIFICATE from base64")?; + + let cert_file = std::env::temp_dir().join("dioxus-signing-cert.p12"); + let mut f = std::fs::File::create(&cert_file)?; + f.write_all(&cert_data)?; + drop(f); + + let _ = Command::new("security") + .args(["delete-keychain", &keychain_path.display().to_string()]) + .output() + .await; + + let mut create_keychain_cmd = Command::new("security"); + create_keychain_cmd.args([ + "create-keychain", + "-p", + keychain_password, + &keychain_path.display().to_string(), + ]); + run_command(&mut create_keychain_cmd, "create-keychain").await?; + + let mut import_cmd = Command::new("security"); + import_cmd.args([ + "import", + &cert_file.display().to_string(), + "-k", + &keychain_path.display().to_string(), + "-P", + password, + "-T", + "/usr/bin/codesign", + "-T", + "/usr/bin/security", + ]); + run_command(&mut import_cmd, "import certificate").await?; + + let output = Command::new("security") + .args(["list-keychains", "-d", "user"]) + .output() + .await + .context("Failed to list keychains")?; + let current_keychains = String::from_utf8_lossy(&output.stdout); + let mut keychains: Vec = current_keychains + .lines() + .map(|l| l.trim().trim_matches('"').to_string()) + .filter(|l| !l.is_empty()) + .collect(); + keychains.insert(0, keychain_path.display().to_string()); + + let mut cmd = Command::new("security"); + cmd.args(["list-keychains", "-d", "user", "-s"]); + for kc in &keychains { + cmd.arg(kc); + } + run_command(&mut cmd, "list-keychains -s").await?; + + let mut unlock_cmd = Command::new("security"); + unlock_cmd.args([ + "unlock-keychain", + "-p", + keychain_password, + &keychain_path.display().to_string(), + ]); + run_command(&mut unlock_cmd, "unlock-keychain").await?; + + let mut set_partition_cmd = Command::new("security"); + set_partition_cmd.args([ + "set-key-partition-list", + "-S", + "apple-tool:,apple:", + "-s", + "-k", + keychain_password, + &keychain_path.display().to_string(), + ]); + run_command(&mut set_partition_cmd, "set-key-partition-list").await?; + + let _ = std::fs::remove_file(&cert_file); + + Ok(TempKeychain { + path: keychain_path, + password: keychain_password.to_string(), + }) +} + +/// Find the signing identity in a keychain using `security find-identity`. +async fn find_identity_in_keychain(keychain_path: &Path) -> Result { + let output = Command::new("security") + .args([ + "find-identity", + "-v", + "-p", + "codesigning", + &keychain_path.display().to_string(), + ]) + .output() + .await + .context("Failed to run `security find-identity`")?; + + let stdout = String::from_utf8_lossy(&output.stdout); + + for line in stdout.lines() { + let line = line.trim(); + if line.starts_with("1)") + || line.contains("Developer ID") + || line.contains("Apple Development") + { + if let Some(start) = line.find('"') { + if let Some(end) = line.rfind('"') { + if end > start { + return Ok(line[start + 1..end].to_string()); + } + } + } + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 2 && parts[1].len() == 40 { + return Ok(parts[1].to_string()); + } + } + } + + bail!( + "No valid signing identity found in keychain {}.\nOutput: {}", + keychain_path.display(), + stdout + ) +} + +/// Sign a list of paths with the given identity. +async fn sign_paths( + identity: &SigningIdentity, + targets: Vec, + settings: &MacOsSettings, +) -> Result<()> { + for target in &targets { + sign_path(identity, target, settings).await?; + } + Ok(()) +} + +/// Sign a single path with `codesign`. +async fn sign_path( + identity: &SigningIdentity, + target: &SignTarget, + settings: &MacOsSettings, +) -> Result<()> { + tracing::info!("Signing: {}", target.path.display()); + + let mut cmd = Command::new("codesign"); + cmd.args(["--force", "--sign", &identity.identity]); + + if settings.hardened_runtime { + cmd.arg("--options"); + cmd.arg("runtime"); + } + + if target + .path + .extension() + .map(|e| e == "app" || e == "framework") + .unwrap_or(false) + { + cmd.arg("--deep"); + } + + if let Some(entitlements) = &settings.entitlements { + cmd.args(["--entitlements", entitlements]); + } + + if let Some(keychain) = &identity.temp_keychain { + cmd.args(["--keychain", &keychain.path.display().to_string()]); + } + + cmd.arg(&target.path); + + run_command(&mut cmd, &format!("codesign {}", target.path.display())).await?; + Ok(()) +} + +/// Notarize a .app or .dmg with Apple's notary service. +async fn notarize(app_path: &Path) -> Result<()> { + let apple_id = std::env::var("APPLE_ID").ok(); + let apple_password = std::env::var("APPLE_PASSWORD").ok(); + let apple_team_id = std::env::var("APPLE_TEAM_ID").ok(); + let api_key = std::env::var("APPLE_API_KEY").ok(); + let api_issuer = std::env::var("APPLE_API_ISSUER").ok(); + let api_key_path = std::env::var("APPLE_API_KEY_PATH").ok(); + + let mut cmd = Command::new("xcrun"); + cmd.args(["notarytool", "submit"]); + cmd.arg(app_path); + + if let (Some(key), Some(issuer), Some(key_path)) = (&api_key, &api_issuer, &api_key_path) { + cmd.args(["--key", key_path]); + cmd.args(["--key-id", key]); + cmd.args(["--issuer", issuer]); + } else if let (Some(id), Some(pwd), Some(team)) = (&apple_id, &apple_password, &apple_team_id) { + cmd.args(["--apple-id", id]); + cmd.args(["--password", pwd]); + cmd.args(["--team-id", team]); + } else { + bail!( + "Notarization requires either:\n\ + - APPLE_ID, APPLE_PASSWORD, and APPLE_TEAM_ID env vars, or\n\ + - APPLE_API_KEY, APPLE_API_ISSUER, and APPLE_API_KEY_PATH env vars" + ); + } + + cmd.arg("--wait"); + + tracing::info!("Submitting {} for notarization...", app_path.display()); + run_command(&mut cmd, "xcrun notarytool submit").await?; + + tracing::info!("Stapling notarization ticket..."); + let mut staple_cmd = Command::new("xcrun"); + staple_cmd.args(["stapler", "staple"]).arg(app_path); + run_command(&mut staple_cmd, "xcrun stapler staple").await?; + + tracing::info!("Notarization complete for {}", app_path.display()); + Ok(()) +} + +/// Helper to run a command and return a nice error on failure. +async fn run_command(cmd: &mut Command, description: &str) -> Result<()> { + tracing::debug!("Running: {:?}", cmd); + let status = cmd + .status() + .await + .with_context(|| format!("Failed to execute `{description}`"))?; + + if !status.success() { + bail!("`{description}` failed with exit code: {}", status); + } + Ok(()) +} + +/// Decode base64 (standard or URL-safe). +async fn base64_decode(input: &str) -> Result> { + let mut child = Command::new("base64") + .args(["--decode"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("Failed to decode base64 certificate")?; + + if let Some(mut stdin) = child.stdin.take() { + stdin + .write_all(input.as_bytes()) + .await + .context("Failed writing base64 certificate to decoder stdin")?; + } + + let output = child + .wait_with_output() + .await + .context("Failed to decode base64 certificate")?; + + if !output.status.success() { + bail!( + "base64 --decode failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + Ok(output.stdout) +} + +/// The result of DMG bundling, which may include both the `.dmg` and `.app` outputs. +pub(crate) struct DmgBundled { + /// Paths to the generated `.dmg` file(s). + pub dmg: Vec, + /// Paths to the generated `.app` bundle(s) (if the `.app` was built as a dependency). + pub app: Vec, +} + +/// A target to be code-signed. +struct SignTarget { + path: PathBuf, +} + +/// A code signing identity, optionally backed by a temporary keychain. +struct SigningIdentity { + /// The identity string passed to `codesign --sign`. + /// This is either a team/certificate name or a SHA-1 hash. + identity: String, + /// If we created a temporary keychain to import a certificate, + /// this holds its path so we can clean it up later. + temp_keychain: Option, +} + +/// A temporary keychain created for CI certificate import. +#[allow(dead_code)] +struct TempKeychain { + path: PathBuf, + password: String, +} + +impl Drop for TempKeychain { + fn drop(&mut self) { + tracing::debug!("Cleaning up temporary keychain: {}", self.path.display()); + let _ = StdCommand::new("security") + .args(["delete-keychain", &self.path.display().to_string()]) + .status(); + } +} diff --git a/packages/cli/src/bundler/mod.rs b/packages/cli/src/bundler/mod.rs new file mode 100644 index 000000000..5870b64a9 --- /dev/null +++ b/packages/cli/src/bundler/mod.rs @@ -0,0 +1,892 @@ +mod ios; +mod linux; +mod macos; +mod tools; +mod updater; +mod windows; + +use crate::PackageType; +use crate::{BuildRequest, DebianSettings, MacOsSettings, WindowsSettings}; +use anyhow::Context; +use anyhow::Result; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use tools::ResolvedTools; + +/// A completed bundle with its output paths. +#[derive(Debug)] +pub(crate) struct Bundle { + pub package_type: PackageType, + pub bundle_paths: Vec, +} + +/// BundleContext wraps a BuildRequest and provides the settings API +/// that bundle format modules need. This is the adapter between +/// Dioxus build infrastructure and the individual bundler implementations. +pub(crate) struct BundleContext<'a> { + pub(crate) build: &'a BuildRequest, + + pub(crate) package_types: Vec, + + /// Pre-computed resource map: source path -> target path in bundle + pub(crate) resources_map: HashMap, + + /// Pre-resolved tool paths (NSIS, WiX, linuxdeploy, WebView2). + pub(crate) tools: ResolvedTools, +} + +impl<'a> BundleContext<'a> { + /// Create a new BundleContext from a BuildRequest and optional package types. + pub(crate) async fn new( + build: &'a BuildRequest, + package_types: &Option>, + ) -> Result { + let package_types = package_types.clone().unwrap_or_default(); + + // Build the resources map from assets + config resources + let mut resources_map = HashMap::new(); + + let asset_dir = build.asset_dir(); + if asset_dir.exists() { + for entry in walkdir::WalkDir::new(&asset_dir) { + let entry = entry?; + let path = entry.path(); + if path.is_file() { + let old = path + .canonicalize() + .with_context(|| format!("Failed to canonicalize {entry:?}"))?; + let new = + PathBuf::from("assets").join(path.strip_prefix(&asset_dir).unwrap_or(path)); + resources_map.insert(old.display().to_string(), new.display().to_string()); + } + } + } + + // Merge in any custom resources from config + if let Some(resources) = &build.config.bundle.resources { + for resource_path in resources { + resources_map.insert(resource_path.clone(), String::new()); + } + } + + let tools_dir = dirs::cache_dir() + .unwrap_or_else(|| PathBuf::from(".cache")) + .join("dioxus"); + let _ = std::fs::create_dir_all(&tools_dir); + + let arch = { + let target = build.triple.to_string(); + if target.starts_with("x86_64") { + Arch::X86_64 + } else if target.starts_with('i') { + Arch::X86 + } else if target.starts_with("arm") && target.ends_with("hf") { + Arch::Armhf + } else if target.starts_with("arm") { + Arch::Armel + } else if target.starts_with("aarch64") { + Arch::AArch64 + } else if target.starts_with("riscv64") { + Arch::Riscv64 + } else if target.starts_with("universal") { + Arch::Universal + } else if cfg!(target_arch = "x86_64") { + Arch::X86_64 + } else if cfg!(target_arch = "aarch64") { + Arch::AArch64 + } else { + Arch::X86_64 + } + }; + + let windows_settings = build.config.bundle.windows.clone().unwrap_or_default(); + let tools = + tools::resolve_tools(&tools_dir, &package_types, &windows_settings, arch).await?; + + Ok(Self { + build, + package_types, + resources_map, + tools, + }) + } + + /// Bundle the project for every configured package type, in dependency order. + /// + /// This is the orchestration entrypoint for native packaging. It owns the + /// sequencing rules between bundle formats, but delegates the actual format + /// implementation to the per-platform `bundle_*` methods on `BundleContext`. + /// + /// The method performs the following high-level workflow: + /// 1. Read the resolved package types from the immutable context. + /// 2. Sort them so prerequisite artifacts are built before dependents. + /// In practice, this means raw distributable formats such as `.app`, `.deb`, + /// `.rpm`, `.AppImage`, `.msi`, `.exe`, `.apk`, and `.aab` run before + /// dependent archive formats like `.ipa`/`.dmg`, and `Updater` always runs last. + /// 3. Dispatch to the top-level format-specific bundling method for each package + /// type and collect the artifact paths it returns. + /// 4. Reuse outputs where formats depend on each other. For example, DMG creation + /// may synthesize a `.app` bundle and return it alongside the `.dmg`. + /// 5. Remove intermediate macOS `.app` bundles when they were only needed as an + /// implementation detail of `.dmg` or updater generation. + /// + /// The returned [`Bundle`] values are the canonical record of what was emitted by + /// the bundling pass. Later steps such as updater packaging rely on these paths + /// rather than rediscovering artifacts on disk. + pub(crate) async fn bundle_project(&self) -> Result> { + let mut package_types = self.package_types(); + + // Sort so dependencies come first (e.g. .app before .dmg) + package_types.sort_by_key(|ptype| match ptype { + PackageType::MacOsBundle + | PackageType::IosApp + | PackageType::WindowsMsi + | PackageType::Nsis + | PackageType::Deb + | PackageType::Rpm + | PackageType::AppImage + | PackageType::Apk + | PackageType::Aab => 0, + PackageType::Ipa | PackageType::Dmg => 1, + PackageType::Updater => 2, + }); + + let mut bundles = Vec::::new(); + + for package_type in &package_types { + // Skip if already built (e.g. DMG already built .app) + if bundles.iter().any(|b| b.package_type == *package_type) { + continue; + } + + let bundle_paths = match package_type { + PackageType::MacOsBundle => self.bundle_macos_app().await?, + PackageType::IosApp => self.bundle_ios_app().await?, + PackageType::Ipa => self.bundle_ios_ipa(&bundles).await?.ipa, + PackageType::Dmg => { + let bundled = self.bundle_macos_dmg(&bundles).await?; + if !bundled.app.is_empty() { + bundles.push(Bundle { + package_type: PackageType::MacOsBundle, + bundle_paths: bundled.app, + }); + } + bundled.dmg + } + PackageType::Deb => self.bundle_linux_deb().await?, + PackageType::Rpm => self.bundle_linux_rpm().await?, + PackageType::AppImage => self.bundle_linux_appimage().await?, + PackageType::WindowsMsi => self.bundle_windows_msi().await?, + PackageType::Nsis => self.bundle_windows_nsis().await?, + PackageType::Updater => self.bundle_updater(&bundles).await?, + PackageType::Apk | PackageType::Aab => self.bundle_android(*package_type).await?, + }; + + bundles.push(Bundle { + package_type: *package_type, + bundle_paths, + }); + } + + // On macOS, clean up .app if only building dmg or updater + if !package_types.contains(&PackageType::MacOsBundle) { + if let Some(idx) = bundles + .iter() + .position(|b| b.package_type == PackageType::MacOsBundle) + { + let app_bundle = bundles.remove(idx); + for path in &app_bundle.bundle_paths { + tracing::info!("Cleaning up intermediate .app: {}", path.display()); + if path.is_dir() { + let _ = std::fs::remove_dir_all(path); + } else { + let _ = std::fs::remove_file(path); + } + } + } + } + + for bundle in &bundles { + for path in &bundle.bundle_paths { + tracing::info!("Bundled: {}", path.display()); + } + } + + Ok(bundles) + } + + /// The package types to bundle. + pub(crate) fn package_types(&self) -> Vec { + self.package_types.clone() + } + + /// The product name (PascalCase). + pub(crate) fn product_name(&self) -> String { + self.build.bundled_app_name() + } + + /// The bundle identifier (e.g. "com.example.app"). + pub(crate) fn bundle_identifier(&self) -> String { + self.build.bundle_identifier() + } + + /// The publisher name. + pub(crate) fn publisher(&self) -> Option<&str> { + self.build.config.bundle.publisher.as_deref() + } + + /// The version string from Cargo.toml. + pub(crate) fn version_string(&self) -> String { + self.build.package().version.to_string() + } + + /// The short description. + pub(crate) fn short_description(&self) -> String { + self.build + .config + .bundle + .short_description + .clone() + .or_else(|| self.build.package().description.clone()) + .unwrap_or_default() + } + + /// The long description. + pub(crate) fn long_description(&self) -> Option<&str> { + self.build.config.bundle.long_description.as_deref() + } + + /// The copyright string. + pub(crate) fn copyright_string(&self) -> Option<&str> { + self.build.config.bundle.copyright.as_deref() + } + + /// The authors list. + pub(crate) fn authors(&self) -> &[String] { + &self.build.package().authors + } + + /// Authors as a comma-separated string. + pub(crate) fn authors_comma_separated(&self) -> Option { + let names = self.authors(); + if names.is_empty() { + None + } else { + Some(names.join(", ")) + } + } + + /// The homepage URL. + pub(crate) fn homepage_url(&self) -> Option { + self.build + .package() + .homepage + .as_ref() + .filter(|hp| !hp.is_empty()) + .cloned() + } + + /// The license string. + pub(crate) fn license(&self) -> Option<&str> { + self.build.package().license.as_deref() + } + + /// The app category string. + pub(crate) fn app_category(&self) -> Option<&str> { + self.build.config.bundle.category.as_deref() + } + + /// The main binary name (without extension). + pub(crate) fn main_binary_name(&self) -> &str { + self.build.executable_name() + } + + /// The path to the main binary. + pub(crate) fn main_binary_path(&self) -> PathBuf { + self.build.main_exe() + } + + /// The output directory for bundles. + pub(crate) fn project_out_directory(&self) -> PathBuf { + self.build.bundle_dir(self.build.bundle) + } + + /// The target triple string. + pub(crate) fn target(&self) -> String { + self.build.triple.to_string() + } + + /// The binary architecture. + pub(crate) fn binary_arch(&self) -> Arch { + let target = self.target(); + if target.starts_with("x86_64") { + Arch::X86_64 + } else if target.starts_with('i') { + Arch::X86 + } else if target.starts_with("arm") && target.ends_with("hf") { + Arch::Armhf + } else if target.starts_with("arm") { + Arch::Armel + } else if target.starts_with("aarch64") { + Arch::AArch64 + } else if target.starts_with("riscv64") { + Arch::Riscv64 + } else if target.starts_with("universal") { + Arch::Universal + } else { + // Default to the host arch + if cfg!(target_arch = "x86_64") { + Arch::X86_64 + } else if cfg!(target_arch = "aarch64") { + Arch::AArch64 + } else { + Arch::X86_64 + } + } + } + + /// Icon file paths from config, canonicalized relative to the crate dir. + pub(crate) fn icon_files(&self) -> Result> { + let mut icons = Vec::new(); + if let Some(icon_paths) = &self.build.config.bundle.icon { + for icon in icon_paths { + let icon_path = self + .build + .crate_dir() + .join(icon) + .canonicalize() + .with_context(|| format!("Failed to canonicalize icon path: {icon}"))?; + icons.push(icon_path); + } + } + Ok(icons) + } + + /// Copy resources to the given path. + pub(crate) fn copy_resources(&self, dest: &Path) -> Result<()> { + for (src, target) in &self.resources_map { + let src_path = PathBuf::from(src); + if !src_path.exists() { + tracing::warn!("Resource not found: {src}"); + continue; + } + let dest_path = if target.is_empty() { + dest.join(src_path.file_name().unwrap_or_default()) + } else { + dest.join(target) + }; + if let Some(parent) = dest_path.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::copy(&src_path, &dest_path).with_context(|| { + format!("Failed to copy resource {src} -> {}", dest_path.display()) + })?; + } + Ok(()) + } + + /// Copy external binaries to the given path. + pub(crate) fn copy_external_binaries(&self, dest: &Path) -> Result> { + let mut paths = Vec::new(); + if let Some(bins) = &self.build.config.bundle.external_bin { + let target = self.target(); + for bin in bins { + let src = PathBuf::from(format!("{bin}-{target}")); + if src.exists() { + let dest_name = src + .file_name() + .unwrap() + .to_string_lossy() + .replace(&format!("-{target}"), ""); + let dest_path = dest.join(dest_name); + std::fs::copy(&src, &dest_path)?; + paths.push(dest_path); + } + } + } + Ok(paths) + } + + /// The crate directory (where Cargo.toml is). + pub(crate) fn crate_dir(&self) -> PathBuf { + self.build.crate_dir() + } + + /// Debian settings from config. + pub(crate) fn deb(&self) -> DebianSettings { + self.build.config.bundle.deb.clone().unwrap_or_default() + } + + /// macOS settings from config. + pub(crate) fn macos(&self) -> MacOsSettings { + self.build.config.bundle.macos.clone().unwrap_or_default() + } + + /// Windows settings from config. + pub(crate) fn windows(&self) -> WindowsSettings { + self.build.config.bundle.windows.clone().unwrap_or_default() + } +} + +/// The architecture of the target binary. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum Arch { + X86_64, + X86, + AArch64, + Armhf, + Armel, + Riscv64, + Universal, +} + +impl Arch { + pub(crate) fn as_str(&self) -> &'static str { + match self { + Arch::X86_64 => "x86_64", + Arch::X86 => "x86", + Arch::AArch64 => "aarch64", + Arch::Armhf => "armhf", + Arch::Armel => "armel", + Arch::Riscv64 => "riscv64", + Arch::Universal => "universal", + } + } + + pub(crate) fn deb_arch(&self) -> &'static str { + match self { + Arch::X86_64 => "amd64", + Arch::X86 => "i386", + Arch::AArch64 => "arm64", + Arch::Armhf => "armhf", + Arch::Armel => "armel", + Arch::Riscv64 => "riscv64", + Arch::Universal => "all", + } + } + + pub(crate) fn rpm_arch(&self) -> &'static str { + match self { + Arch::X86_64 => "x86_64", + Arch::X86 => "i686", + Arch::AArch64 => "aarch64", + Arch::Armhf => "armv7hl", + Arch::Armel => "armv6l", + Arch::Riscv64 => "riscv64", + Arch::Universal => "noarch", + } + } + + pub(crate) fn appimage_arch(&self) -> &'static str { + match self { + Arch::X86_64 => "x86_64", + Arch::X86 => "i386", + Arch::AArch64 => "aarch64", + Arch::Armhf => "armhf", + Arch::Armel => "armel", + Arch::Riscv64 => "riscv64", + Arch::Universal => "x86_64", + } + } + + pub(crate) fn windows_arch(&self) -> &'static str { + match self { + Arch::X86_64 => "x64", + Arch::X86 => "x86", + Arch::AArch64 => "arm64", + _ => "x64", + } + } + + pub(crate) fn wix_arch(&self) -> &'static str { + match self { + Arch::X86_64 => "x64", + Arch::X86 => "x86", + Arch::AArch64 => "arm64", + _ => "x64", + } + } + + pub(crate) fn wix_program_files_folder(&self) -> &'static str { + match self { + Arch::X86 => "ProgramFilesFolder", + Arch::X86_64 | Arch::AArch64 => "ProgramFiles64Folder", + _ => "ProgramFiles64Folder", + } + } + + pub(crate) fn linuxdeploy_arch(&self) -> &'static str { + match self { + Arch::X86_64 => "x86_64", + Arch::X86 => "i386", + Arch::AArch64 => "aarch64", + Arch::Armhf | Arch::Armel => "armhf", + Arch::Riscv64 | Arch::Universal => "x86_64", + } + } +} + +impl std::fmt::Display for Arch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +use std::str::FromStr; + +/// The possible app categories. +/// Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(crate) enum AppCategory { + Business, + DeveloperTool, + Education, + Entertainment, + Finance, + Game, + ActionGame, + AdventureGame, + ArcadeGame, + BoardGame, + CardGame, + CasinoGame, + DiceGame, + EducationalGame, + FamilyGame, + KidsGame, + MusicGame, + PuzzleGame, + RacingGame, + RolePlayingGame, + SimulationGame, + SportsGame, + StrategyGame, + TriviaGame, + WordGame, + GraphicsAndDesign, + HealthcareAndFitness, + Lifestyle, + Medical, + Music, + News, + Photography, + Productivity, + Reference, + SocialNetworking, + Sports, + Travel, + Utility, + Video, + Weather, +} + +impl FromStr for AppCategory { + type Err = String; + + fn from_str(input: &str) -> Result { + const MACOS_APP_CATEGORY_PREFIX: &str = "public.app-category."; + + let mut input = input.to_ascii_lowercase(); + if input.starts_with(MACOS_APP_CATEGORY_PREFIX) { + input = input + .split_at(MACOS_APP_CATEGORY_PREFIX.len()) + .1 + .to_string(); + } + input = input.replace(' ', ""); + input = input.replace('-', ""); + + for &(string, category) in CATEGORY_STRINGS.iter() { + if input == string { + return Ok(category); + } + } + Err(format!("Unknown app category: {input}")) + } +} + +impl AppCategory { + /// Map to closest set of Freedesktop categories. + pub(crate) fn freedesktop_categories(self) -> &'static str { + match &self { + AppCategory::Business => "Office;", + AppCategory::DeveloperTool => "Development;", + AppCategory::Education => "Education;", + AppCategory::Entertainment => "Network;", + AppCategory::Finance => "Office;Finance;", + AppCategory::Game => "Game;", + AppCategory::ActionGame => "Game;ActionGame;", + AppCategory::AdventureGame => "Game;AdventureGame;", + AppCategory::ArcadeGame => "Game;ArcadeGame;", + AppCategory::BoardGame => "Game;BoardGame;", + AppCategory::CardGame => "Game;CardGame;", + AppCategory::CasinoGame => "Game;", + AppCategory::DiceGame => "Game;", + AppCategory::EducationalGame => "Game;Education;", + AppCategory::FamilyGame => "Game;", + AppCategory::KidsGame => "Game;KidsGame;", + AppCategory::MusicGame => "Game;", + AppCategory::PuzzleGame => "Game;LogicGame;", + AppCategory::RacingGame => "Game;", + AppCategory::RolePlayingGame => "Game;RolePlaying;", + AppCategory::SimulationGame => "Game;Simulation;", + AppCategory::SportsGame => "Game;SportsGame;", + AppCategory::StrategyGame => "Game;StrategyGame;", + AppCategory::TriviaGame => "Game;", + AppCategory::WordGame => "Game;", + AppCategory::GraphicsAndDesign => "Graphics;", + AppCategory::HealthcareAndFitness => "Science;", + AppCategory::Lifestyle => "Education;", + AppCategory::Medical => "Science;MedicalSoftware;", + AppCategory::Music => "AudioVideo;Audio;Music;", + AppCategory::News => "Network;News;", + AppCategory::Photography => "Graphics;Photography;", + AppCategory::Productivity => "Office;", + AppCategory::Reference => "Education;", + AppCategory::SocialNetworking => "Network;", + AppCategory::Sports => "Education;Sports;", + AppCategory::Travel => "Education;", + AppCategory::Utility => "Utility;", + AppCategory::Video => "AudioVideo;Video;", + AppCategory::Weather => "Science;", + } + } + + /// Map to macOS LSApplicationCategoryType. + pub(crate) fn macos_application_category_type(self) -> &'static str { + match &self { + AppCategory::Business => "public.app-category.business", + AppCategory::DeveloperTool => "public.app-category.developer-tools", + AppCategory::Education => "public.app-category.education", + AppCategory::Entertainment => "public.app-category.entertainment", + AppCategory::Finance => "public.app-category.finance", + AppCategory::Game => "public.app-category.games", + AppCategory::ActionGame => "public.app-category.action-games", + AppCategory::AdventureGame => "public.app-category.adventure-games", + AppCategory::ArcadeGame => "public.app-category.arcade-games", + AppCategory::BoardGame => "public.app-category.board-games", + AppCategory::CardGame => "public.app-category.card-games", + AppCategory::CasinoGame => "public.app-category.casino-games", + AppCategory::DiceGame => "public.app-category.dice-games", + AppCategory::EducationalGame => "public.app-category.educational-games", + AppCategory::FamilyGame => "public.app-category.family-games", + AppCategory::KidsGame => "public.app-category.kids-games", + AppCategory::MusicGame => "public.app-category.music-games", + AppCategory::PuzzleGame => "public.app-category.puzzle-games", + AppCategory::RacingGame => "public.app-category.racing-games", + AppCategory::RolePlayingGame => "public.app-category.role-playing-games", + AppCategory::SimulationGame => "public.app-category.simulation-games", + AppCategory::SportsGame => "public.app-category.sports-games", + AppCategory::StrategyGame => "public.app-category.strategy-games", + AppCategory::TriviaGame => "public.app-category.trivia-games", + AppCategory::WordGame => "public.app-category.word-games", + AppCategory::GraphicsAndDesign => "public.app-category.graphics-design", + AppCategory::HealthcareAndFitness => "public.app-category.healthcare-fitness", + AppCategory::Lifestyle => "public.app-category.lifestyle", + AppCategory::Medical => "public.app-category.medical", + AppCategory::Music => "public.app-category.music", + AppCategory::News => "public.app-category.news", + AppCategory::Photography => "public.app-category.photography", + AppCategory::Productivity => "public.app-category.productivity", + AppCategory::Reference => "public.app-category.reference", + AppCategory::SocialNetworking => "public.app-category.social-networking", + AppCategory::Sports => "public.app-category.sports", + AppCategory::Travel => "public.app-category.travel", + AppCategory::Utility => "public.app-category.utilities", + AppCategory::Video => "public.app-category.video", + AppCategory::Weather => "public.app-category.weather", + } + } +} + +const CATEGORY_STRINGS: &[(&str, AppCategory)] = &[ + ("actiongame", AppCategory::ActionGame), + ("actiongames", AppCategory::ActionGame), + ("adventuregame", AppCategory::AdventureGame), + ("adventuregames", AppCategory::AdventureGame), + ("arcadegame", AppCategory::ArcadeGame), + ("arcadegames", AppCategory::ArcadeGame), + ("boardgame", AppCategory::BoardGame), + ("boardgames", AppCategory::BoardGame), + ("business", AppCategory::Business), + ("cardgame", AppCategory::CardGame), + ("cardgames", AppCategory::CardGame), + ("casinogame", AppCategory::CasinoGame), + ("casinogames", AppCategory::CasinoGame), + ("developer", AppCategory::DeveloperTool), + ("developertool", AppCategory::DeveloperTool), + ("developertools", AppCategory::DeveloperTool), + ("development", AppCategory::DeveloperTool), + ("dicegame", AppCategory::DiceGame), + ("dicegames", AppCategory::DiceGame), + ("education", AppCategory::Education), + ("educationalgame", AppCategory::EducationalGame), + ("educationalgames", AppCategory::EducationalGame), + ("entertainment", AppCategory::Entertainment), + ("familygame", AppCategory::FamilyGame), + ("familygames", AppCategory::FamilyGame), + ("finance", AppCategory::Finance), + ("fitness", AppCategory::HealthcareAndFitness), + ("game", AppCategory::Game), + ("games", AppCategory::Game), + ("graphicdesign", AppCategory::GraphicsAndDesign), + ("graphicsanddesign", AppCategory::GraphicsAndDesign), + ("graphicsdesign", AppCategory::GraphicsAndDesign), + ("healthcareandfitness", AppCategory::HealthcareAndFitness), + ("healthcarefitness", AppCategory::HealthcareAndFitness), + ("kidsgame", AppCategory::KidsGame), + ("kidsgames", AppCategory::KidsGame), + ("lifestyle", AppCategory::Lifestyle), + ("logicgame", AppCategory::PuzzleGame), + ("medical", AppCategory::Medical), + ("music", AppCategory::Music), + ("musicgame", AppCategory::MusicGame), + ("musicgames", AppCategory::MusicGame), + ("news", AppCategory::News), + ("photography", AppCategory::Photography), + ("productivity", AppCategory::Productivity), + ("puzzlegame", AppCategory::PuzzleGame), + ("puzzlegames", AppCategory::PuzzleGame), + ("racinggame", AppCategory::RacingGame), + ("racinggames", AppCategory::RacingGame), + ("reference", AppCategory::Reference), + ("roleplaying", AppCategory::RolePlayingGame), + ("roleplayinggame", AppCategory::RolePlayingGame), + ("roleplayinggames", AppCategory::RolePlayingGame), + ("rpg", AppCategory::RolePlayingGame), + ("simulationgame", AppCategory::SimulationGame), + ("simulationgames", AppCategory::SimulationGame), + ("socialnetwork", AppCategory::SocialNetworking), + ("socialnetworking", AppCategory::SocialNetworking), + ("sports", AppCategory::Sports), + ("sportsgame", AppCategory::SportsGame), + ("sportsgames", AppCategory::SportsGame), + ("strategygame", AppCategory::StrategyGame), + ("strategygames", AppCategory::StrategyGame), + ("travel", AppCategory::Travel), + ("triviagame", AppCategory::TriviaGame), + ("triviagames", AppCategory::TriviaGame), + ("utilities", AppCategory::Utility), + ("utility", AppCategory::Utility), + ("video", AppCategory::Video), + ("weather", AppCategory::Weather), + ("wordgame", AppCategory::WordGame), + ("wordgames", AppCategory::WordGame), +]; + +/// Recursively copy a directory tree. +/// +/// Preserves symlinks on unix targets and falls back to copying link targets on non-unix. +pub(crate) fn copy_dir_recursive(src: &Path, dest: &Path) -> Result<()> { + std::fs::create_dir_all(dest)?; + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let source_path = entry.path(); + let dest_path = dest.join(entry.file_name()); + let file_type = entry.file_type()?; + + if file_type.is_dir() { + copy_dir_recursive(&source_path, &dest_path)?; + } else if file_type.is_symlink() { + #[cfg(unix)] + { + let target = std::fs::read_link(&source_path)?; + std::os::unix::fs::symlink(&target, &dest_path)?; + } + + #[cfg(not(unix))] + { + std::fs::copy(&source_path, &dest_path)?; + } + } else { + std::fs::copy(&source_path, &dest_path)?; + } + } + + Ok(()) +} + +/// Recursively zip a directory tree while preserving relative paths and Unix modes. +pub(crate) fn zip_dir_recursive(src: &Path, dest: &Path) -> Result<()> { + use std::fs::File; + use std::io::{Read, Write}; + + let file = + File::create(dest).with_context(|| format!("Failed to create {}", dest.display()))?; + let mut zip = zip::ZipWriter::new(file); + + for entry in walkdir::WalkDir::new(src) { + let entry = entry?; + let path = entry.path(); + let relative = path + .strip_prefix(src) + .with_context(|| format!("Failed to strip prefix for {}", path.display()))?; + + if relative.as_os_str().is_empty() { + continue; + } + + let archive_path = relative.to_string_lossy().replace('\\', "/"); + let metadata = entry.metadata()?; + #[cfg(unix)] + let mode = { + use std::os::unix::fs::PermissionsExt; + metadata.permissions().mode() + }; + #[cfg(not(unix))] + let mode = if metadata.is_dir() { 0o755 } else { 0o644 }; + + let options = zip::write::SimpleFileOptions::default() + .compression_method(zip::CompressionMethod::Deflated) + .unix_permissions(mode); + + if metadata.is_dir() { + zip.add_directory(format!("{archive_path}/"), options)?; + continue; + } + + zip.start_file(&archive_path, options)?; + let mut src_file = File::open(path)?; + let mut buffer = Vec::new(); + src_file.read_to_end(&mut buffer)?; + zip.write_all(&buffer)?; + } + + zip.finish()?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::zip_dir_recursive; + + #[test] + fn zip_dir_preserves_layout_and_permissions() { + let temp = tempfile::tempdir().unwrap(); + let src = temp.path().join("src"); + let payload = src.join("Payload/Test.app"); + std::fs::create_dir_all(&payload).unwrap(); + + let exec = payload.join("runner"); + std::fs::write(&exec, "hello").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&exec, std::fs::Permissions::from_mode(0o755)).unwrap(); + } + + let zip_path = temp.path().join("bundle.zip"); + zip_dir_recursive(&src, &zip_path).unwrap(); + + let file = std::fs::File::open(&zip_path).unwrap(); + let mut archive = zip::ZipArchive::new(file).unwrap(); + + let entry = archive.by_name("Payload/Test.app/runner").unwrap(); + let expected_mode = if cfg!(unix) { 0o755 } else { 0o644 }; + assert_eq!( + entry.unix_mode().map(|mode| mode & 0o777), + Some(expected_mode) + ); + } +} diff --git a/packages/cli/src/bundler/tools.rs b/packages/cli/src/bundler/tools.rs new file mode 100644 index 000000000..56216b5ec --- /dev/null +++ b/packages/cli/src/bundler/tools.rs @@ -0,0 +1,305 @@ +//! Tool downloading and caching for external bundling tools. +//! +//! All downloads happen upfront via `resolve_tools()` before any bundling starts. +//! This keeps tool downloads out of the bundle format modules. + +use super::Arch; +use crate::{PackageType, WebviewInstallMode, WindowsSettings}; +use anyhow::{bail, Context, Result}; +use std::path::{Path, PathBuf}; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + +/// NSIS download URL and expected hash +const NSIS_URL: &str = + "https://github.com/tauri-apps/binary-releases/releases/download/nsis-3.11/nsis-3.11.zip"; +const NSIS_SHA1: &str = "EF7FF767E5CBD9EDD22ADD3A32C9B8F4500BB10D"; + +/// WiX download URL and expected hash +const WIX_URL: &str = + "https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip"; +const WIX_SHA256: &str = "6ac824e1642d6f7277d0ed7ea09411a508f6116ba6fae0aa5f2c7daa2ff43d31"; + +/// linuxdeploy download base URL +const LINUXDEPLOY_URL_BASE: &str = + "https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy"; + +/// WebView2 download URLs. +const WEBVIEW2_BOOTSTRAPPER_URL: &str = "https://go.microsoft.com/fwlink/p/?LinkId=2124703"; +const WEBVIEW2_X64_INSTALLER_URL: &str = "https://go.microsoft.com/fwlink/?linkid=2099617"; +const WEBVIEW2_X86_INSTALLER_URL: &str = "https://go.microsoft.com/fwlink/?linkid=2099617"; +const WEBVIEW2_ARM64_INSTALLER_URL: &str = "https://go.microsoft.com/fwlink/?linkid=2099617"; + +/// Pre-resolved tool paths. All downloads happen before bundling starts. +pub(crate) struct ResolvedTools { + /// Path to the NSIS directory (contains makensis). Set if NSIS bundling is requested. + pub nsis_dir: Option, + /// Path to the WiX directory (contains candle.exe, light.exe). Set if MSI bundling is requested. + pub wix_dir: Option, + /// Path to the linuxdeploy binary. Set if AppImage bundling is requested. + pub linuxdeploy: Option, + /// Path to a downloaded WebView2 bootstrapper or offline installer, if needed by NSIS. + pub webview2_installer: Option, +} + +/// Resolve and download all tools needed for the given package types. +/// +/// This must be called before `bundle_project()` so that no HTTP calls happen +/// during the actual bundling phase. +pub(crate) async fn resolve_tools( + tools_dir: &Path, + package_types: &[PackageType], + windows_settings: &WindowsSettings, + arch: Arch, +) -> Result { + let mut resolved = ResolvedTools { + nsis_dir: None, + wix_dir: None, + linuxdeploy: None, + webview2_installer: None, + }; + + for pt in package_types { + match pt { + PackageType::Nsis => { + resolved.nsis_dir = Some(ensure_nsis(tools_dir).await?); + resolved.webview2_installer = + resolve_webview2(tools_dir, windows_settings, arch).await?; + } + PackageType::WindowsMsi => { + resolved.wix_dir = Some(ensure_wix(tools_dir).await?); + } + PackageType::AppImage => { + let linuxdeploy_arch = arch.linuxdeploy_arch(); + resolved.linuxdeploy = Some(ensure_linuxdeploy(tools_dir, linuxdeploy_arch).await?); + } + _ => {} + } + } + + Ok(resolved) +} + +/// Determine if a WebView2 installer needs to be downloaded based on NSIS settings, +/// and download it if so. +async fn resolve_webview2( + tools_dir: &Path, + settings: &WindowsSettings, + arch: Arch, +) -> Result> { + let mode = &settings.webview_install_mode; + + match mode { + WebviewInstallMode::Skip | WebviewInstallMode::FixedRuntime { .. } => Ok(None), + WebviewInstallMode::DownloadBootstrapper { .. } + | WebviewInstallMode::EmbedBootstrapper { .. } => { + Ok(Some(download_webview2_bootstrapper(tools_dir).await?)) + } + WebviewInstallMode::OfflineInstaller { .. } => { + let arch_str = arch.windows_arch(); + Ok(Some( + download_webview2_offline_installer(tools_dir, arch_str).await?, + )) + } + } +} + +async fn ensure_nsis(tools_dir: &Path) -> Result { + let nsis_dir = tools_dir.join("nsis-3.11"); + let makensis = if cfg!(target_os = "windows") { + nsis_dir.join("makensis.exe") + } else { + nsis_dir.join("makensis") + }; + + if makensis.exists() { + return Ok(nsis_dir); + } + + if cfg!(feature = "no-downloads") { + bail!("NSIS not found and automatic downloads are disabled. Install NSIS manually."); + } + + tracing::info!("Downloading NSIS..."); + + let data = download_and_verify(NSIS_URL, NSIS_SHA1, HashAlgo::Sha1).await?; + extract_zip(&data, tools_dir)?; + + if !makensis.exists() { + bail!( + "NSIS extraction succeeded but makensis not found at {}", + makensis.display() + ); + } + + #[cfg(unix)] + let _ = std::fs::set_permissions(&makensis, std::fs::Permissions::from_mode(0o755)); + + Ok(nsis_dir) +} + +async fn ensure_wix(tools_dir: &Path) -> Result { + let wix_dir = tools_dir.join("wix314"); + let candle = wix_dir.join("candle.exe"); + + if candle.exists() { + return Ok(wix_dir); + } + + if cfg!(feature = "no-downloads") { + bail!("WiX not found and automatic downloads are disabled. Install WiX manually."); + } + + tracing::info!("Downloading WiX toolset..."); + let data = download_and_verify(WIX_URL, WIX_SHA256, HashAlgo::Sha256).await?; + + std::fs::create_dir_all(&wix_dir)?; + extract_zip(&data, &wix_dir)?; + + if !candle.exists() { + bail!( + "WiX extraction succeeded but candle.exe not found at {}", + candle.display() + ); + } + + Ok(wix_dir) +} + +async fn ensure_linuxdeploy(tools_dir: &Path, arch: &str) -> Result { + let linuxdeploy_name = format!("linuxdeploy-{arch}.AppImage"); + let linuxdeploy_path = tools_dir.join(&linuxdeploy_name); + + if linuxdeploy_path.exists() { + return Ok(linuxdeploy_path); + } + + if cfg!(feature = "no-downloads") { + bail!( + "linuxdeploy not found and automatic downloads are disabled. Install linuxdeploy manually." + ); + } + + let url = format!("{LINUXDEPLOY_URL_BASE}/{linuxdeploy_name}"); + tracing::info!("Downloading linuxdeploy from {url}..."); + + let data = download_bytes(&url).await?; + std::fs::create_dir_all(tools_dir)?; + std::fs::write(&linuxdeploy_path, &data)?; + + #[cfg(unix)] + std::fs::set_permissions(&linuxdeploy_path, std::fs::Permissions::from_mode(0o755))?; + + Ok(linuxdeploy_path) +} + +async fn download_webview2_bootstrapper(tools_dir: &Path) -> Result { + let path = tools_dir.join("MicrosoftEdgeWebview2Setup.exe"); + if path.exists() { + return Ok(path); + } + tracing::info!("Downloading WebView2 bootstrapper..."); + let data = download_bytes(WEBVIEW2_BOOTSTRAPPER_URL).await?; + std::fs::create_dir_all(tools_dir)?; + std::fs::write(&path, &data).context("Failed to write WebView2 bootstrapper")?; + Ok(path) +} + +async fn download_webview2_offline_installer(tools_dir: &Path, arch: &str) -> Result { + let name = format!("MicrosoftEdgeWebView2RuntimeInstaller_{arch}.exe"); + let path = tools_dir.join(&name); + if path.exists() { + return Ok(path); + } + let url = match arch { + "x64" => WEBVIEW2_X64_INSTALLER_URL, + "x86" => WEBVIEW2_X86_INSTALLER_URL, + "arm64" => WEBVIEW2_ARM64_INSTALLER_URL, + _ => bail!("Unsupported architecture for WebView2 offline installer: {arch}"), + }; + tracing::info!("Downloading WebView2 offline installer for {arch}..."); + let data = download_bytes(url).await?; + std::fs::create_dir_all(tools_dir)?; + std::fs::write(&path, &data).context("Failed to write WebView2 offline installer")?; + Ok(path) +} + +enum HashAlgo { + Sha1, + Sha256, +} + +async fn download_and_verify(url: &str, expected_hash: &str, algo: HashAlgo) -> Result> { + let data = download_bytes(url).await?; + + let computed = match algo { + HashAlgo::Sha1 => { + use sha1::Digest; + let mut hasher = sha1::Sha1::new(); + hasher.update(&data); + format!("{:X}", hasher.finalize()) + } + HashAlgo::Sha256 => { + use sha2::Digest; + let mut hasher = sha2::Sha256::new(); + hasher.update(&data); + format!("{:x}", hasher.finalize()) + } + }; + + if computed.to_uppercase() != expected_hash.to_uppercase() { + bail!("Hash mismatch for {url}: expected {expected_hash}, got {computed}"); + } + + Ok(data) +} + +/// Download bytes from a URL using the async reqwest client. +pub(crate) async fn download_bytes(url: &str) -> Result> { + let response = reqwest::get(url) + .await + .with_context(|| format!("Failed to download {url}"))?; + + if !response.status().is_success() { + bail!("Download failed with status {}: {url}", response.status()); + } + + response + .bytes() + .await + .map(|b| b.to_vec()) + .with_context(|| format!("Failed to read response body from {url}")) +} + +/// Extract a zip archive to a directory. +fn extract_zip(data: &[u8], dest: &Path) -> Result<()> { + use std::io::Cursor; + + let cursor = Cursor::new(data); + let mut archive = zip::ZipArchive::new(cursor).context("Failed to read zip archive")?; + + std::fs::create_dir_all(dest)?; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let outpath = dest.join(file.mangled_name()); + + if file.name().ends_with('/') { + std::fs::create_dir_all(&outpath)?; + } else { + if let Some(parent) = outpath.parent() { + std::fs::create_dir_all(parent)?; + } + let mut outfile = std::fs::File::create(&outpath)?; + std::io::copy(&mut file, &mut outfile)?; + + #[cfg(unix)] + if let Some(mode) = file.unix_mode() { + std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))?; + } + } + } + + Ok(()) +} diff --git a/packages/cli/src/bundler/updater.rs b/packages/cli/src/bundler/updater.rs new file mode 100644 index 000000000..78601c7f3 --- /dev/null +++ b/packages/cli/src/bundler/updater.rs @@ -0,0 +1,152 @@ +//! Updater bundle creation. +//! +//! Creates zip/tar.gz archives of bundle artifacts for auto-update distribution. + +use super::{Bundle, BundleContext}; +use crate::PackageType; +use anyhow::{Context, Result}; +use std::{ + fs::File, + io, + path::{Path, PathBuf}, +}; + +impl BundleContext<'_> { + /// Repackage previously-built bundle artifacts into updater-friendly archives. + /// + /// This step never builds the application itself. Instead, it consumes the + /// [`Bundle`] records produced earlier in the main bundling pass and wraps those + /// artifacts into archive formats that are convenient for update distribution. + /// + /// Archive mapping: + /// - macOS `.app` bundles become `.app.tar.gz` archives so the bundle directory + /// layout is preserved exactly. + /// - Windows installers (`.msi` and NSIS `.exe`) become single-file `.zip` + /// archives. + /// - Linux artifacts (`.AppImage` and `.deb`) become single-file `.tar.gz` + /// archives. + /// + /// All outputs are written to `project_out_directory()/bundle/updater`. This + /// method assumes the input bundles are already finalized and signed as needed; it + /// performs no platform-specific mutation beyond wrapping them in the selected + /// archive container. + pub(crate) async fn bundle_updater(&self, bundles: &[Bundle]) -> Result> { + let mut updater_paths = Vec::new(); + let output_dir = self.project_out_directory().join("bundle").join("updater"); + std::fs::create_dir_all(&output_dir)?; + + for bundle in bundles { + match bundle.package_type { + PackageType::MacOsBundle => { + // Create .tar.gz of the .app bundle + for app_path in &bundle.bundle_paths { + let tar_path = output_dir.join(format!( + "{}_{}.app.tar.gz", + self.product_name(), + self.version_string() + )); + create_tar_gz(app_path, &tar_path)?; + tracing::info!("Created updater archive: {}", tar_path.display()); + updater_paths.push(tar_path); + } + } + PackageType::Nsis | PackageType::WindowsMsi => { + // Create .zip of the installer + for installer_path in &bundle.bundle_paths { + let zip_path = output_dir.join(format!( + "{}.zip", + installer_path + .file_stem() + .unwrap_or_default() + .to_string_lossy() + )); + create_zip(installer_path, &zip_path)?; + tracing::info!("Created updater archive: {}", zip_path.display()); + updater_paths.push(zip_path); + } + } + PackageType::AppImage | PackageType::Deb => { + // Create .tar.gz of the artifact + for artifact_path in &bundle.bundle_paths { + let tar_path = output_dir.join(format!( + "{}.tar.gz", + artifact_path + .file_name() + .unwrap_or_default() + .to_string_lossy() + )); + create_tar_gz_single_file(artifact_path, &tar_path)?; + tracing::info!("Created updater archive: {}", tar_path.display()); + updater_paths.push(tar_path); + } + } + _ => {} + } + } + + Ok(updater_paths) + } +} + +/// Create a .tar.gz of a directory (e.g., a .app bundle). +fn create_tar_gz(src_dir: &Path, dest: &Path) -> Result<()> { + let file = + File::create(dest).with_context(|| format!("Failed to create {}", dest.display()))?; + let enc = flate2::write::GzEncoder::new(file, flate2::Compression::default()); + let mut tar = tar::Builder::new(enc); + + let dir_name = src_dir + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + + tar.append_dir_all(&dir_name, src_dir) + .with_context(|| format!("Failed to add {} to tar", src_dir.display()))?; + + tar.into_inner()?.finish()?; + Ok(()) +} + +/// Create a .tar.gz containing a single file. +fn create_tar_gz_single_file(src_file: &Path, dest: &Path) -> Result<()> { + let file = + File::create(dest).with_context(|| format!("Failed to create {}", dest.display()))?; + let enc = flate2::write::GzEncoder::new(file, flate2::Compression::default()); + let mut tar = tar::Builder::new(enc); + + let file_name = src_file + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + + tar.append_path_with_name(src_file, &file_name) + .with_context(|| format!("Failed to add {} to tar", src_file.display()))?; + + tar.into_inner()?.finish()?; + Ok(()) +} + +/// Create a .zip containing a single file. +fn create_zip(src_file: &Path, dest: &Path) -> Result<()> { + let file = + File::create(dest).with_context(|| format!("Failed to create {}", dest.display()))?; + let mut zip = zip::ZipWriter::new(file); + + let file_name = src_file + .file_name() + .unwrap_or_default() + .to_string_lossy() + .to_string(); + + let options = zip::write::SimpleFileOptions::default() + .compression_method(zip::CompressionMethod::Deflated); + + zip.start_file(&file_name, options)?; + let mut src = File::open(src_file)?; + io::copy(&mut src, &mut zip)?; + + zip.finish()?; + Ok(()) +} diff --git a/packages/cli/src/bundler/windows.rs b/packages/cli/src/bundler/windows.rs new file mode 100644 index 000000000..796f48468 --- /dev/null +++ b/packages/cli/src/bundler/windows.rs @@ -0,0 +1,1300 @@ +use crate::bundler::BundleContext; +use crate::{NSISInstallerMode, WebviewInstallMode, WindowsSettings}; +use anyhow::{bail, Context, Result}; +use handlebars::Handlebars; +use serde_json::Value as JsonValue; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; +use tokio::process::Command; +use uuid::Uuid; + +impl BundleContext<'_> { + /// Build a Windows MSI installer using the WiX toolset. + /// + /// This method stages the application into a temporary installer layout, renders a + /// WiX source file, compiles that source into `.wixobj` files with `candle.exe`, + /// and links the final `.msi` with `light.exe`. + /// + /// The bundling pipeline is: + /// 1. Resolve the WiX toolchain path that was pre-fetched during context setup. + /// 2. Normalize the application version into WiX's constrained version format. + /// 3. Compute installer metadata such as the upgrade code, publisher, icons, + /// license files, banner images, and optional fragment references. + /// 4. Populate a `_staging` directory containing the main executable, copied + /// resources, and external binaries. + /// 5. Render the `.wxs` source from either the built-in template or a user + /// supplied template. + /// 6. Compile the main `.wxs` and any configured fragment files with + /// `candle.exe`. + /// 7. Link all generated `.wixobj` files into the final `.msi` with `light.exe`. + /// 8. Optionally sign the resulting installer with the configured Windows signing + /// settings. + /// + /// The final artifact is written to `project_out_directory()/bundle/msi`. + /// Intermediate `.wxs`, `.wixobj`, and `_staging` files are intentionally left in + /// place because they are useful when diagnosing packaging failures. + pub(crate) async fn bundle_windows_msi(&self) -> Result> { + let output_dir = self.project_out_directory().join("bundle").join("msi"); + std::fs::create_dir_all(&output_dir).context("Failed to create MSI output directory")?; + + let wix_settings = self.windows().wix.unwrap_or_default(); + let windows_settings = self.windows(); + + let wix_dir = self + .tools + .wix_dir + .as_ref() + .context("WiX tools were not resolved. This is a bug.")?; + let candle = wix_dir.join("candle.exe"); + let light = wix_dir.join("light.exe"); + + let arch = self.binary_arch(); + let arch_str = arch.windows_arch(); + let wix_arch = arch.wix_arch(); + let wix_program_files_folder = arch.wix_program_files_folder(); + + let product_name = self.product_name(); + let version = wix_version( + &wix_settings + .version + .clone() + .unwrap_or_else(|| self.version_string()), + )?; + + let msi_name = format!("{product_name}_{version}_{arch_str}.msi"); + let output_path = output_dir.join(&msi_name); + + let upgrade_code = wix_settings.upgrade_code.unwrap_or_else(|| { + let input = format!("{}.exe.app.{}", product_name, arch_str); + Uuid::new_v5(&Uuid::NAMESPACE_DNS, input.as_bytes()) + }); + + let staging_dir = output_dir.join("_staging"); + if staging_dir.exists() { + std::fs::remove_dir_all(&staging_dir)?; + } + std::fs::create_dir_all(&staging_dir)?; + + let main_binary_src = self.main_binary_path(); + let main_binary_name = format!("{}.exe", self.main_binary_name()); + let main_binary_dest = staging_dir.join(&main_binary_name); + std::fs::copy(&main_binary_src, &main_binary_dest).with_context(|| { + format!( + "Failed to copy main binary from {} to {}", + main_binary_src.display(), + main_binary_dest.display() + ) + })?; + + let resources_dir = staging_dir.join("resources"); + std::fs::create_dir_all(&resources_dir)?; + self.copy_resources(&resources_dir)?; + self.copy_external_binaries(&staging_dir)?; + let staged_files = collect_staged_files(&staging_dir, Some(&main_binary_dest))?; + let (install_tree, component_refs_xml) = render_wix_install_tree(&staged_files); + + let mut data = BTreeMap::new(); + data.insert( + "product_name".to_string(), + serde_json::Value::String(product_name.clone()), + ); + data.insert( + "version".to_string(), + serde_json::Value::String(version.clone()), + ); + data.insert( + "upgrade_code".to_string(), + serde_json::Value::String(upgrade_code.to_string()), + ); + data.insert( + "wix_program_files_folder".to_string(), + serde_json::Value::String(wix_program_files_folder.to_string()), + ); + data.insert( + "main_binary_name".to_string(), + serde_json::Value::String(main_binary_name.clone()), + ); + data.insert( + "main_binary_path".to_string(), + serde_json::Value::String(main_binary_dest.to_string_lossy().replace('/', "\\")), + ); + data.insert( + "short_description".to_string(), + serde_json::Value::String(self.short_description()), + ); + data.insert( + "bundle_id".to_string(), + serde_json::Value::String(self.bundle_identifier()), + ); + + let publisher = self + .publisher() + .map(|s| s.to_string()) + .or_else(|| self.authors_comma_separated()) + .unwrap_or_else(|| product_name.clone()); + data.insert( + "publisher".to_string(), + serde_json::Value::String(publisher), + ); + + data.insert( + "allow_downgrades".to_string(), + serde_json::Value::Bool(windows_settings.allow_downgrades), + ); + data.insert( + "fips_compliant".to_string(), + serde_json::Value::Bool(wix_settings.fips_compliant), + ); + + if let Some(icon_path) = &windows_settings.icon_path { + let icon = self.crate_dir().join(icon_path); + data.insert( + "icon_path".to_string(), + serde_json::Value::String(icon.to_string_lossy().replace('/', "\\")), + ); + } + + if let Some(license) = &wix_settings.license { + let license_path = self.crate_dir().join(license); + data.insert( + "license".to_string(), + serde_json::Value::String(license_path.to_string_lossy().replace('/', "\\")), + ); + } + + if let Some(banner) = &wix_settings.banner_path { + let banner_path = self.crate_dir().join(banner); + data.insert( + "banner_path".to_string(), + serde_json::Value::String(banner_path.to_string_lossy().replace('/', "\\")), + ); + } + if let Some(dialog) = &wix_settings.dialog_image_path { + let dialog_path = self.crate_dir().join(dialog); + data.insert( + "dialog_image_path".to_string(), + serde_json::Value::String(dialog_path.to_string_lossy().replace('/', "\\")), + ); + } + + let to_json_array = |v: &[String]| -> serde_json::Value { + serde_json::Value::Array( + v.iter() + .map(|s| serde_json::Value::String(s.clone())) + .collect(), + ) + }; + data.insert( + "component_group_refs".to_string(), + to_json_array(&wix_settings.component_group_refs), + ); + data.insert( + "component_refs".to_string(), + to_json_array(&wix_settings.component_refs), + ); + data.insert( + "feature_group_refs".to_string(), + to_json_array(&wix_settings.feature_group_refs), + ); + data.insert( + "feature_refs".to_string(), + to_json_array(&wix_settings.feature_refs), + ); + data.insert( + "merge_refs".to_string(), + to_json_array(&wix_settings.merge_refs), + ); + data.insert( + "install_tree".to_string(), + serde_json::Value::String(install_tree), + ); + data.insert( + "component_refs_xml".to_string(), + serde_json::Value::String(component_refs_xml), + ); + + let mut hbs = Handlebars::new(); + hbs.set_strict_mode(false); + hbs.register_escape_fn(|s: &str| s.to_string()); + hbs.register_template_string( + "wix", + if let Some(custom_template) = &wix_settings.template { + let template_path = self.crate_dir().join(custom_template); + std::fs::read_to_string(&template_path).with_context(|| { + format!( + "Failed to read custom WiX template: {}", + template_path.display() + ) + })? + } else { + WIX_TEMPLATE.to_string() + }, + ) + .context("Failed to parse WiX template")?; + + let wxs_content = hbs + .render("wix", &data) + .context("Failed to render WiX template")?; + + let wxs_path = output_dir.join(format!("{product_name}.wxs")); + std::fs::write(&wxs_path, &wxs_content).context("Failed to write WiX source file")?; + + let mut fragment_wxs_paths = Vec::new(); + for fragment in &wix_settings.fragment_paths { + let frag_path = self.crate_dir().join(fragment); + if !frag_path.exists() { + bail!("WiX fragment file not found: {}", frag_path.display()); + } + fragment_wxs_paths.push(frag_path); + } + + tracing::info!("Running candle.exe to compile WiX source..."); + let wixobj_path = output_dir.join(format!("{product_name}.wixobj")); + + let mut candle_cmd = Command::new(&candle); + candle_cmd + .arg("-arch") + .arg(wix_arch) + .arg("-o") + .arg(&wixobj_path) + .arg(&wxs_path); + candle_cmd.arg("-ext").arg("WixUIExtension"); + + tracing::debug!("candle command: {:?}", candle_cmd); + + let candle_output = candle_cmd + .output() + .await + .with_context(|| format!("Failed to run candle.exe at {}", candle.display()))?; + + if !candle_output.status.success() { + let stderr = String::from_utf8_lossy(&candle_output.stderr); + let stdout = String::from_utf8_lossy(&candle_output.stdout); + bail!( + "candle.exe failed (exit code {:?}):\nstdout: {}\nstderr: {}", + candle_output.status.code(), + stdout, + stderr + ); + } + + let mut all_wixobj_paths = vec![wixobj_path]; + for frag_wxs in &fragment_wxs_paths { + let frag_name = frag_wxs.file_stem().unwrap_or_default().to_string_lossy(); + let frag_wixobj = output_dir.join(format!("{frag_name}.wixobj")); + + let mut frag_candle = Command::new(&candle); + frag_candle + .arg("-arch") + .arg(wix_arch) + .arg("-o") + .arg(&frag_wixobj) + .arg(frag_wxs); + + let frag_output = frag_candle.output().await.with_context(|| { + format!("Failed to compile WiX fragment: {}", frag_wxs.display()) + })?; + + if !frag_output.status.success() { + let stderr = String::from_utf8_lossy(&frag_output.stderr); + bail!( + "candle.exe failed on fragment {} (exit code {:?}):\n{}", + frag_wxs.display(), + frag_output.status.code(), + stderr + ); + } + all_wixobj_paths.push(frag_wixobj); + } + + tracing::info!("Running light.exe to link MSI..."); + + let mut light_cmd = Command::new(&light); + light_cmd + .arg("-o") + .arg(&output_path) + .arg("-ext") + .arg("WixUIExtension"); + + for wixobj in &all_wixobj_paths { + light_cmd.arg(wixobj); + } + + tracing::debug!("light command: {:?}", light_cmd); + + let light_output = light_cmd + .output() + .await + .with_context(|| format!("Failed to run light.exe at {}", light.display()))?; + + if !light_output.status.success() { + let stderr = String::from_utf8_lossy(&light_output.stderr); + let stdout = String::from_utf8_lossy(&light_output.stdout); + bail!( + "light.exe failed (exit code {:?}):\nstdout: {}\nstderr: {}", + light_output.status.code(), + stdout, + stderr + ); + } + + if can_sign_windows(&windows_settings) { + self.try_sign_windows(&output_path).await?; + } + + if !output_path.exists() { + bail!( + "light.exe completed but MSI not found at {}", + output_path.display() + ); + } + + tracing::info!("MSI installer created: {}", output_path.display()); + Ok(vec![output_path]) + } + + /// Build a Windows installer executable using NSIS. + /// + /// NSIS packaging follows the same broad staging pattern as MSI bundling, but the + /// installer behavior is expressed through a generated `.nsi` script rather than + /// WiX XML. + /// + /// The method performs these steps: + /// 1. Resolve the `makensis` binary prepared during tool resolution. + /// 2. Create an `_staging` directory containing the main executable, copied + /// resources, and external binaries. + /// 3. Translate bundle settings into template values, including install mode, + /// installer imagery, licensing, language selection, and publisher metadata. + /// 4. Generate the WebView2 installation snippet when the selected webview mode + /// requires bootstrapping or bundling the Microsoft runtime installer. + /// 5. Render the `.nsi` script from either the built-in template or a custom + /// template file. + /// 6. Invoke `makensis` to compile the script into the final installer `.exe`. + /// 7. Optionally sign the generated installer. + /// + /// The resulting installer is written to `project_out_directory()/bundle/nsis`. + /// As with the MSI flow, the script and staging directory are preserved for + /// debugging rather than treated as disposable hidden state. + pub(crate) async fn bundle_windows_nsis(&self) -> Result> { + let output_dir = self.project_out_directory().join("bundle").join("nsis"); + std::fs::create_dir_all(&output_dir).context("Failed to create NSIS output directory")?; + + let nsis_settings = self.windows().nsis.unwrap_or_default(); + let windows_settings = self.windows(); + + let nsis_dir = self + .tools + .nsis_dir + .as_ref() + .context("NSIS tools were not resolved. This is a bug.")?; + let makensis = if cfg!(target_os = "windows") { + nsis_dir.join("makensis.exe") + } else { + nsis_dir.join("makensis") + }; + + let arch = self.binary_arch(); + let arch_str = arch.windows_arch(); + + let product_name = self.product_name(); + let version = self.version_string(); + let installer_name = format!("{product_name}_{version}_{arch_str}-setup.exe"); + let output_path = output_dir.join(&installer_name); + + let staging_dir = output_dir.join("_staging"); + if staging_dir.exists() { + std::fs::remove_dir_all(&staging_dir)?; + } + std::fs::create_dir_all(&staging_dir)?; + + let main_binary_src = self.main_binary_path(); + let main_binary_name = if cfg!(target_os = "windows") || self.target().contains("windows") { + format!("{}.exe", self.main_binary_name()) + } else { + self.main_binary_name().to_string() + }; + let main_binary_dest = staging_dir.join(&main_binary_name); + std::fs::copy(&main_binary_src, &main_binary_dest).with_context(|| { + format!( + "Failed to copy main binary from {} to {}", + main_binary_src.display(), + main_binary_dest.display() + ) + })?; + + let resources_dir = staging_dir.join("resources"); + std::fs::create_dir_all(&resources_dir)?; + self.copy_resources(&resources_dir)?; + self.copy_external_binaries(&staging_dir)?; + let staged_files = collect_staged_files(&staging_dir, Some(&main_binary_dest))?; + + let (install_webview, webview_install_code) = + self.generate_windows_webview_install_code(&windows_settings.webview_install_mode)?; + + let mut data = BTreeMap::new(); + data.insert( + "product_name".to_string(), + JsonValue::String(product_name.clone()), + ); + data.insert("version".to_string(), JsonValue::String(version.clone())); + data.insert( + "output_path".to_string(), + JsonValue::String(output_path.to_string_lossy().replace('/', "\\")), + ); + data.insert( + "main_binary_path".to_string(), + JsonValue::String(main_binary_dest.to_string_lossy().replace('/', "\\")), + ); + data.insert( + "main_binary_name".to_string(), + JsonValue::String(main_binary_name.clone()), + ); + data.insert( + "short_description".to_string(), + JsonValue::String(self.short_description()), + ); + data.insert( + "bundle_id".to_string(), + JsonValue::String(self.bundle_identifier()), + ); + + let publisher = self + .publisher() + .map(|s| s.to_string()) + .or_else(|| self.authors_comma_separated()) + .unwrap_or_else(|| product_name.clone()); + data.insert("publisher".to_string(), JsonValue::String(publisher)); + + if let Some(copyright) = self.copyright_string() { + data.insert( + "copyright".to_string(), + JsonValue::String(copyright.to_string()), + ); + } + + let install_mode = &nsis_settings.install_mode; + data.insert( + "install_mode_per_machine".to_string(), + JsonValue::Bool(matches!(install_mode, NSISInstallerMode::PerMachine)), + ); + data.insert( + "install_mode_both".to_string(), + JsonValue::Bool(matches!(install_mode, NSISInstallerMode::Both)), + ); + + let start_menu_folder = nsis_settings + .start_menu_folder + .clone() + .unwrap_or_else(|| product_name.clone()); + data.insert( + "start_menu_folder".to_string(), + JsonValue::String(start_menu_folder), + ); + + if let Some(icon) = &nsis_settings.installer_icon { + let icon_path = self.crate_dir().join(icon); + data.insert( + "installer_icon".to_string(), + JsonValue::String(icon_path.to_string_lossy().replace('/', "\\")), + ); + } + + if let Some(header) = &nsis_settings.header_image { + let header_path = self.crate_dir().join(header); + data.insert( + "header_image".to_string(), + JsonValue::String(header_path.to_string_lossy().replace('/', "\\")), + ); + } + + if let Some(sidebar) = &nsis_settings.sidebar_image { + let sidebar_path = self.crate_dir().join(sidebar); + data.insert( + "sidebar_image".to_string(), + JsonValue::String(sidebar_path.to_string_lossy().replace('/', "\\")), + ); + } + + if let Some(license) = &nsis_settings.license { + let license_path = self.crate_dir().join(license); + data.insert( + "license".to_string(), + JsonValue::String(license_path.to_string_lossy().replace('/', "\\")), + ); + } + + if let Some(hooks) = &nsis_settings.installer_hooks { + let hooks_path = self.crate_dir().join(hooks); + data.insert( + "installer_hooks".to_string(), + JsonValue::String(hooks_path.to_string_lossy().replace('/', "\\")), + ); + } + + if let Some(languages) = &nsis_settings.languages { + let lang_values: Vec = languages + .iter() + .filter(|l| l.as_str() != "English") + .map(|l| JsonValue::String(l.clone())) + .collect(); + data.insert( + "additional_languages".to_string(), + JsonValue::Array(lang_values), + ); + } + + data.insert( + "install_webview".to_string(), + JsonValue::Bool(install_webview), + ); + data.insert( + "webview_install_code".to_string(), + JsonValue::String(webview_install_code), + ); + data.insert( + "staged_files".to_string(), + JsonValue::Array( + staged_files + .iter() + .map(|file| { + serde_json::json!({ + "source": windows_path_string(&file.source), + "target_dir": windows_rel_dir(&file.relative_path), + }) + }) + .collect(), + ), + ); + + let nsi_content = if let Some(custom_template) = &nsis_settings.template { + let template_path = self.crate_dir().join(custom_template); + let template_str = std::fs::read_to_string(&template_path).with_context(|| { + format!( + "Failed to read custom NSIS template: {}", + template_path.display() + ) + })?; + render_template(&template_str, &data)? + } else { + render_template(NSIS_TEMPLATE, &data)? + }; + + let nsi_path = output_dir.join(format!("{product_name}.nsi")); + std::fs::write(&nsi_path, &nsi_content).context("Failed to write NSIS script")?; + + tracing::info!("Running makensis to build NSIS installer..."); + let mut cmd = Command::new(&makensis); + cmd.arg("-NOCD"); + cmd.arg(&nsi_path); + + tracing::debug!("makensis command: {:?}", cmd); + + let output = cmd + .output() + .await + .with_context(|| format!("Failed to run makensis at {}", makensis.display()))?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + bail!( + "makensis failed (exit code {:?}):\nstdout: {}\nstderr: {}", + output.status.code(), + stdout, + stderr + ); + } + + if can_sign_windows(&windows_settings) { + self.try_sign_windows(&output_path).await?; + } + + if !output_path.exists() { + bail!( + "makensis completed but installer not found at {}", + output_path.display() + ); + } + + tracing::info!("NSIS installer created: {}", output_path.display()); + Ok(vec![output_path]) + } + + /// Attempt to sign a binary at the given path using the Windows signing configuration. + async fn try_sign_windows(&self, path: &Path) -> Result<()> { + let settings = self.windows(); + + if !can_sign_windows(&settings) { + return Ok(()); + } + + tracing::info!("Signing {}", path.display()); + + if let Some(sign_cmd) = &settings.sign_command { + return run_custom_sign_command(path, &sign_cmd.cmd, &sign_cmd.args).await; + } + + if let Some(thumbprint) = &settings.certificate_thumbprint { + return run_signtool_sign(path, thumbprint, &settings).await; + } + + Ok(()) + } + + /// Generate the NSIS code snippet for WebView2 installation based on the install mode. + fn generate_windows_webview_install_code( + &self, + mode: &WebviewInstallMode, + ) -> Result<(bool, String)> { + match mode { + WebviewInstallMode::Skip | WebviewInstallMode::FixedRuntime { .. } => { + Ok((false, String::new())) + } + + WebviewInstallMode::DownloadBootstrapper { silent } + | WebviewInstallMode::EmbedBootstrapper { silent } => { + let installer_path = self + .tools + .webview2_installer + .as_ref() + .context("WebView2 installer was not pre-downloaded. This is a bug.")?; + let silent_flag = if *silent { " /silent" } else { "" }; + let code = format!( + r#" ; Install WebView2 via bootstrapper + SetOutPath "$TEMP" + File "{bootstrapper}" + ExecWait '"$TEMP\MicrosoftEdgeWebview2Setup.exe"{silent_flag} /install' $0 + Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe""#, + bootstrapper = installer_path.to_string_lossy().replace('/', "\\"), + ); + Ok((true, code)) + } + + WebviewInstallMode::OfflineInstaller { silent } => { + let installer_path = self + .tools + .webview2_installer + .as_ref() + .context("WebView2 installer was not pre-downloaded. This is a bug.")?; + let silent_flag = if *silent { " /silent" } else { "" }; + let installer_name = installer_path + .file_name() + .unwrap_or_default() + .to_string_lossy(); + let code = format!( + r#" ; Install WebView2 via offline installer + SetOutPath "$TEMP" + File "{installer}" + ExecWait '"$TEMP\{installer_name}"{silent_flag} /install' $0 + Delete "$TEMP\{installer_name}""#, + installer = installer_path.to_string_lossy().replace('/', "\\"), + ); + Ok((true, code)) + } + } + } +} + +#[derive(Clone, Debug)] +struct StagedFile { + source: PathBuf, + relative_path: PathBuf, +} + +#[derive(Default)] +struct WixDirTree { + files: Vec, + dirs: BTreeMap, +} + +impl WixDirTree { + fn insert(&mut self, file: StagedFile) { + let mut cursor = self; + if let Some(parent) = file.relative_path.parent() { + for component in parent.components() { + let name = component.as_os_str().to_string_lossy().to_string(); + if name.is_empty() { + continue; + } + cursor = cursor.dirs.entry(name).or_default(); + } + } + cursor.files.push(file); + } +} + +/// Returns `true` if the Windows settings have signing configured. +fn can_sign_windows(settings: &WindowsSettings) -> bool { + settings.certificate_thumbprint.is_some() || settings.sign_command.is_some() +} + +/// Collect all files in the staging directory except the explicitly skipped file. +fn collect_staged_files(staging_dir: &Path, skip_file: Option<&Path>) -> Result> { + let mut files = Vec::new(); + + for entry in walkdir::WalkDir::new(staging_dir) { + let entry = entry?; + if !entry.file_type().is_file() { + continue; + } + + let path = entry.path().to_path_buf(); + if skip_file.is_some_and(|skip| skip == path) { + continue; + } + + files.push(StagedFile { + relative_path: path + .strip_prefix(staging_dir) + .unwrap_or(entry.path()) + .to_path_buf(), + source: path, + }); + } + + files.sort_by(|a, b| a.relative_path.cmp(&b.relative_path)); + Ok(files) +} + +/// Render the staged file tree into WiX `` / `` XML fragments. +fn render_wix_install_tree(staged_files: &[StagedFile]) -> (String, String) { + let mut tree = WixDirTree::default(); + for file in staged_files { + tree.insert(file.clone()); + } + + let mut next_id = 0usize; + let mut component_refs = String::new(); + let install_tree = render_wix_dir_contents(&tree, 20, &mut next_id, &mut component_refs); + (install_tree, component_refs) +} + +fn render_wix_dir_contents( + tree: &WixDirTree, + indent: usize, + next_id: &mut usize, + component_refs: &mut String, +) -> String { + let mut xml = String::new(); + let pad = " ".repeat(indent); + let file_pad = " ".repeat(indent + 4); + + for file in &tree.files { + let component_id = format!("Component_{}", *next_id); + *next_id += 1; + let file_id = format!("File_{}", *next_id); + *next_id += 1; + + component_refs.push_str(&format!( + "\n " + )); + + let file_name = file + .relative_path + .file_name() + .unwrap_or_default() + .to_string_lossy(); + let source = windows_path_string(&file.source); + xml.push_str(&format!( + "\n{pad}\n{file_pad}\n{pad}", + xml_attr_escape(&file_name), + xml_attr_escape(&source), + )); + } + + for (name, child) in &tree.dirs { + let dir_id = format!("Directory_{}", *next_id); + *next_id += 1; + let child_xml = render_wix_dir_contents(child, indent + 4, next_id, component_refs); + xml.push_str(&format!( + "\n{pad}{child_xml}\n{pad}", + xml_attr_escape(name), + )); + } + + xml +} + +fn windows_path_string(path: &Path) -> String { + path.to_string_lossy().replace('/', "\\") +} + +fn windows_rel_dir(relative_path: &Path) -> String { + relative_path + .parent() + .map(|p| p.to_string_lossy().replace('/', "\\")) + .unwrap_or_default() +} + +fn xml_attr_escape(input: &str) -> String { + input + .replace('&', "&") + .replace('"', """) + .replace('\'', "'") + .replace('<', "<") + .replace('>', ">") +} + +/// Run a custom signing command. The `%1` placeholder in args is replaced +/// with the path to the binary to sign. +async fn run_custom_sign_command(path: &Path, cmd: &str, args: &[String]) -> Result<()> { + let path_str = path.to_string_lossy(); + let resolved_args: Vec = args + .iter() + .map(|arg| arg.replace("%1", &path_str)) + .collect(); + + tracing::debug!("Running custom sign command: {} {:?}", cmd, resolved_args); + + let status = Command::new(cmd) + .args(&resolved_args) + .status() + .await + .with_context(|| format!("Failed to run custom sign command: {cmd}"))?; + + if !status.success() { + bail!( + "Custom sign command failed with exit code: {:?}", + status.code() + ); + } + + Ok(()) +} + +/// Run signtool.exe to sign a binary with a certificate thumbprint. +async fn run_signtool_sign( + path: &Path, + thumbprint: &str, + settings: &WindowsSettings, +) -> Result<()> { + let mut args = vec![ + "sign".to_string(), + "/fd".to_string(), + settings + .digest_algorithm + .clone() + .unwrap_or_else(|| "sha256".to_string()), + "/sha1".to_string(), + thumbprint.to_string(), + ]; + + if let Some(timestamp_url) = &settings.timestamp_url { + if settings.tsp { + args.push("/tr".to_string()); + args.push(timestamp_url.clone()); + args.push("/td".to_string()); + args.push( + settings + .digest_algorithm + .clone() + .unwrap_or_else(|| "sha256".to_string()), + ); + } else { + args.push("/t".to_string()); + args.push(timestamp_url.clone()); + } + } + + args.push(path.to_string_lossy().to_string()); + + tracing::debug!("Running signtool with args: {:?}", args); + + let status = Command::new("signtool.exe") + .args(&args) + .status() + .await + .context("Failed to run signtool.exe. Is the Windows SDK installed?")?; + + if !status.success() { + bail!("signtool.exe failed with exit code: {:?}", status.code()); + } + + Ok(()) +} + +/// Render a Handlebars template with the given data. +fn render_template(template: &str, data: &BTreeMap) -> Result { + let mut hbs = Handlebars::new(); + hbs.set_strict_mode(false); + hbs.register_escape_fn(|s: &str| s.to_string()); + hbs.register_template_string("nsis", template) + .context("Failed to parse NSIS template")?; + hbs.render("nsis", data) + .context("Failed to render NSIS template") +} + +/// Convert a semver version string to a WiX-compatible version. +fn wix_version(version: &str) -> Result { + let version = version.split('-').next().unwrap_or(version); + let parts: Vec<&str> = version.split('.').collect(); + + if parts.len() < 2 || parts.len() > 4 { + bail!( + "Invalid version for MSI: '{}'. Expected format: major.minor.patch[.build]", + version + ); + } + + for (i, part) in parts.iter().enumerate() { + let num: u64 = part + .parse() + .with_context(|| format!("Invalid version component: '{part}'"))?; + match i { + 0 | 1 => { + if num > 255 { + bail!("Version component {part} exceeds maximum value of 255"); + } + } + 2 | 3 => { + if num > 65535 { + bail!("Version component {part} exceeds maximum value of 65535"); + } + } + _ => {} + } + } + + let version_str = if parts.len() == 2 { + format!("{}.{}.0", parts[0], parts[1]) + } else { + parts.join(".") + }; + + Ok(version_str) +} + +/// The embedded WiX template. +const WIX_TEMPLATE: &str = r#" + + + + + + + + + + {{#if icon_path}} + + + {{/if}} + + {{#if license}} + + {{/if}} + + {{#if banner_path}} + + {{/if}} + {{#if dialog_image_path}} + + {{/if}} + + + + + + + + + + +{{{install_tree}}} + + + + + + + + + + + + + + + + + + + + + + + + + +{{{component_refs_xml}}} + {{#each component_group_refs}} + + {{/each}} + {{#each component_refs}} + + {{/each}} + {{#each feature_group_refs}} + + {{/each}} + {{#each feature_refs}} + + {{/each}} + {{#each merge_refs}} + + {{/each}} + + + +"#; + +/// The embedded NSIS template script. +const NSIS_TEMPLATE: &str = r#"!include "MUI2.nsh" +!include "FileFunc.nsh" +!include "x64.nsh" + +; Basic installer attributes +Name "{{product_name}}" +OutFile "{{output_path}}" +Unicode true +{{#if install_mode_per_machine}} +InstallDir "$PROGRAMFILES\{{product_name}}" +{{else}} +InstallDir "$LOCALAPPDATA\Programs\{{product_name}}" +{{/if}} + +; Request appropriate privileges +{{#if install_mode_per_machine}} +RequestExecutionLevel admin +{{else if install_mode_both}} +RequestExecutionLevel admin +{{else}} +RequestExecutionLevel user +{{/if}} + +; Version information +VIProductVersion "{{version}}.0" +VIAddVersionKey "ProductName" "{{product_name}}" +VIAddVersionKey "FileVersion" "{{version}}" +VIAddVersionKey "ProductVersion" "{{version}}" +VIAddVersionKey "FileDescription" "{{short_description}}" +{{#if publisher}} +VIAddVersionKey "CompanyName" "{{publisher}}" +{{/if}} +{{#if copyright}} +VIAddVersionKey "LegalCopyright" "{{copyright}}" +{{/if}} + +; MUI settings +!define MUI_ABORTWARNING +{{#if installer_icon}} +!define MUI_ICON "{{installer_icon}}" +{{/if}} +{{#if header_image}} +!define MUI_HEADERIMAGE +!define MUI_HEADERIMAGE_BITMAP "{{header_image}}" +{{/if}} +{{#if sidebar_image}} +!define MUI_WELCOMEFINISHPAGE_BITMAP "{{sidebar_image}}" +{{/if}} + +; Pages +{{#if license}} +!insertmacro MUI_PAGE_LICENSE "{{license}}" +{{/if}} +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + +; Uninstaller pages +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES + +; Language +!insertmacro MUI_LANGUAGE "English" +{{#each additional_languages}} +!insertmacro MUI_LANGUAGE "{{this}}" +{{/each}} + +; Installer section +Section "Install" + SetOutPath $INSTDIR + + ; Install main binary + File "{{main_binary_path}}" + + ; Install resources + {{#each staged_files}} + SetOutPath "$INSTDIR{{#if this.target_dir}}\{{this.target_dir}}{{/if}}" + File "{{this.source}}" + {{/each}} + + SetOutPath $INSTDIR + + ; Create uninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + ; Create Start Menu shortcuts + CreateDirectory "$SMPROGRAMS\{{start_menu_folder}}" + CreateShortcut "$SMPROGRAMS\{{start_menu_folder}}\{{product_name}}.lnk" "$INSTDIR\{{main_binary_name}}" + CreateShortcut "$SMPROGRAMS\{{start_menu_folder}}\Uninstall {{product_name}}.lnk" "$INSTDIR\uninstall.exe" + + ; Create Desktop shortcut + CreateShortcut "$DESKTOP\{{product_name}}.lnk" "$INSTDIR\{{main_binary_name}}" + + ; Write registry keys for Add/Remove Programs + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{bundle_id}}" \ + "DisplayName" "{{product_name}}" + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{bundle_id}}" \ + "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{bundle_id}}" \ + "DisplayVersion" "{{version}}" + {{#if publisher}} + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{bundle_id}}" \ + "Publisher" "{{publisher}}" + {{/if}} + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{bundle_id}}" \ + "InstallLocation" "$INSTDIR" + + ; Get installed size + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{bundle_id}}" \ + "EstimatedSize" "$0" + + {{#if install_webview}} + ; WebView2 installation + {{webview_install_code}} + {{/if}} + +SectionEnd + +{{#if installer_hooks}} +!include "{{installer_hooks}}" +{{/if}} + +; Uninstaller section +Section "Uninstall" + ; Remove files + RMDir /r "$INSTDIR" + + ; Remove Start Menu items + RMDir /r "$SMPROGRAMS\{{start_menu_folder}}" + + ; Remove Desktop shortcut + Delete "$DESKTOP\{{product_name}}.lnk" + + ; Remove registry keys + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\{{bundle_id}}" +SectionEnd +"#; + +#[cfg(test)] +mod tests { + use super::WIX_TEMPLATE; + use crate::bundler::Arch; + use handlebars::Handlebars; + use serde_json::json; + + #[test] + fn wix_program_files_folder_matches_architecture() { + assert_eq!(Arch::X86.wix_program_files_folder(), "ProgramFilesFolder"); + assert_eq!( + Arch::X86_64.wix_program_files_folder(), + "ProgramFiles64Folder" + ); + assert_eq!( + Arch::AArch64.wix_program_files_folder(), + "ProgramFiles64Folder" + ); + } + + #[test] + fn wix_template_uses_arch_specific_program_files_folder() { + let mut hbs = Handlebars::new(); + hbs.set_strict_mode(false); + hbs.register_escape_fn(|s: &str| s.to_string()); + hbs.register_template_string("wix", WIX_TEMPLATE).unwrap(); + + let rendered = hbs + .render( + "wix", + &json!({ + "product_name": "Hotdog", + "upgrade_code": "00000000-0000-0000-0000-000000000000", + "version": "0.1.0", + "publisher": "Dioxus Labs", + "main_binary_name": "hotdog.exe", + "main_binary_path": "C:\\staging\\hotdog.exe", + "short_description": "Hotdog app", + "allow_downgrades": false, + "fips_compliant": false, + "install_tree": "", + "component_refs_xml": "", + "component_group_refs": [], + "component_refs": [], + "feature_group_refs": [], + "feature_refs": [], + "merge_refs": [], + "wix_program_files_folder": "ProgramFiles64Folder" + }), + ) + .unwrap(); + + assert!(rendered.contains("")); + assert!(!rendered.contains("")); + } +} diff --git a/packages/cli/src/cargo_toml.rs b/packages/cli/src/cargo_toml.rs index 55ce38487..d07326d11 100644 --- a/packages/cli/src/cargo_toml.rs +++ b/packages/cli/src/cargo_toml.rs @@ -67,43 +67,43 @@ fn extend_manifest_config_toml(manifest: &mut Manifest, path: &Path) { /// Merge the new profiles into the target profiles. Keep the existing values if they exist. fn merge_profiles(target: &mut Profiles, new: Profiles) { if let Some(new_release) = new.release { - if target.release.is_none() { - target.release = Some(new_release); + if let Some(profile) = target.release.as_mut() { + merge_profile(profile, new_release); } else { - merge_profile(target.release.as_mut().unwrap(), new_release); + target.release = Some(new_release); } } if let Some(new_dev) = new.dev { - if target.dev.is_none() { - target.dev = Some(new_dev); + if let Some(profile) = target.dev.as_mut() { + merge_profile(profile, new_dev); } else { - merge_profile(target.dev.as_mut().unwrap(), new_dev); + target.dev = Some(new_dev); } } if let Some(new_test) = new.test { - if target.test.is_none() { - target.test = Some(new_test); + if let Some(profile) = target.test.as_mut() { + merge_profile(profile, new_test); } else { - merge_profile(target.test.as_mut().unwrap(), new_test); + target.test = Some(new_test); } } if let Some(new_bench) = new.bench { - if target.bench.is_none() { - target.bench = Some(new_bench); + if let Some(profile) = target.bench.as_mut() { + merge_profile(profile, new_bench); } else { - merge_profile(target.bench.as_mut().unwrap(), new_bench); + target.bench = Some(new_bench); } } #[allow(deprecated)] if let Some(new_doc) = new.doc { - if target.doc.is_none() { - target.doc = Some(new_doc); + if let Some(profile) = target.doc.as_mut() { + merge_profile(profile, new_doc); } else { - merge_profile(target.doc.as_mut().unwrap(), new_doc); + target.doc = Some(new_doc); } } diff --git a/packages/check/src/issues.rs b/packages/cli/src/check/issues.rs similarity index 99% rename from packages/check/src/issues.rs rename to packages/cli/src/check/issues.rs index 7e39d17ba..37e21910c 100644 --- a/packages/check/src/issues.rs +++ b/packages/cli/src/check/issues.rs @@ -7,7 +7,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::metadata::{ +use super::metadata::{ AnyLoopInfo, AsyncInfo, ClosureInfo, ConditionalInfo, ForInfo, HookInfo, IfInfo, MatchInfo, WhileInfo, }; @@ -229,7 +229,7 @@ impl std::fmt::Display for Issue { #[cfg(test)] mod tests { - use crate::check_file; + use crate::check::check_file; use indoc::indoc; use pretty_assertions::assert_eq; diff --git a/packages/check/src/metadata.rs b/packages/cli/src/check/metadata.rs similarity index 99% rename from packages/check/src/metadata.rs rename to packages/cli/src/check/metadata.rs index b36f7bab4..8a165d226 100644 --- a/packages/check/src/metadata.rs +++ b/packages/cli/src/check/metadata.rs @@ -168,6 +168,7 @@ pub struct Span { } impl Span { + #[cfg(test)] pub fn new_from_str(source_text: &str, start: LineColumn) -> Self { let mut lines = source_text.lines(); let first_line = lines.next().unwrap_or_default(); diff --git a/packages/check/src/check.rs b/packages/cli/src/check/mod.rs similarity index 99% rename from packages/check/src/check.rs rename to packages/cli/src/check/mod.rs index 0335b82f0..0fde5df85 100644 --- a/packages/check/src/check.rs +++ b/packages/cli/src/check/mod.rs @@ -1,8 +1,10 @@ -use std::path::PathBuf; +mod issues; +mod metadata; +use std::path::PathBuf; use syn::{spanned::Spanned, visit::Visit, Pat}; -use crate::{ +use { issues::{Issue, IssueReport}, metadata::{ AnyLoopInfo, AsyncInfo, ClosureInfo, ComponentInfo, ConditionalInfo, FnInfo, ForInfo, @@ -289,7 +291,7 @@ impl<'ast> syn::visit::Visit<'ast> for VisitHooks { #[cfg(test)] mod tests { - use crate::metadata::{ + use crate::check::metadata::{ AnyLoopInfo, ClosureInfo, ConditionalInfo, ForInfo, HookInfo, IfInfo, LineColumn, LoopInfo, MatchInfo, Span, WhileInfo, }; diff --git a/packages/cli/src/cli/build_assets.rs b/packages/cli/src/cli/build_assets.rs index 3ccaa2a80..890f6ead7 100644 --- a/packages/cli/src/cli/build_assets.rs +++ b/packages/cli/src/cli/build_assets.rs @@ -1,8 +1,8 @@ use std::{fs::create_dir_all, path::PathBuf}; +use crate::opt::process_file_to; use crate::{extract_assets_from_file, Result, StructuredOutput}; use clap::Parser; -use dioxus_cli_opt::process_file_to; use tracing::debug; #[derive(Clone, Debug, Parser)] @@ -28,7 +28,7 @@ impl BuildAssets { destination_path.display(), asset ); - process_file_to(asset.options(), &source_path, &destination_path)?; + process_file_to(asset.options(), &source_path, &destination_path, None)?; } Ok(StructuredOutput::Success) diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index cf8314614..a37fa5be2 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -1,10 +1,10 @@ -use crate::{AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, BundleFormat}; -use anyhow::{bail, Context}; +use crate::{ + AppBuilder, BuildArgs, BuildId, BuildMode, BuildRequest, BundleFormat, PackageType, Platform, +}; +use anyhow::Context; use path_absolutize::Absolutize; -use std::collections::HashMap; -use tauri_bundler::{BundleBinary, BundleSettings, PackageSettings, SettingsBuilder}; - -use walkdir::WalkDir; +use std::path::PathBuf; +use target_lexicon::Triple; use super::*; @@ -15,7 +15,7 @@ use super::*; pub struct Bundle { /// The package types to bundle #[clap(long)] - pub package_types: Option>, + pub package_types: Option>, /// The directory in which the final bundle will be placed. /// @@ -37,15 +37,14 @@ impl Bundle { pub(crate) async fn bundle(mut self) -> Result { tracing::info!("Bundling project..."); + self.force_ios_bundle_device_target_if_needed()?; let BuildTargets { client, server } = self.args.into_targets().await?; - let mut server_artifacts = None; let client_artifacts = AppBuilder::started(&client, BuildMode::Base { run: false }, BuildId::PRIMARY)? .finish_build() .await?; - - tracing::info!(path = ?client.root_dir(), "Client build completed successfully! 🚀"); + let mut server_artifacts = None; if let Some(server) = server.as_ref() { // If the server is present, we need to build it as well @@ -58,11 +57,13 @@ impl Bundle { tracing::info!(path = ?client.root_dir(), "Server build completed successfully! 🚀"); } - // If we're building for iOS, we need to bundle the iOS bundle - if client.bundle == BundleFormat::Ios && self.package_types.is_none() { - self.package_types = Some(vec![crate::PackageType::IosBundle]); + // Fill platform-specific defaults for package types when omitted. + if self.package_types.is_none() { + self.package_types = Self::default_package_types(client.bundle); } + Self::validate_package_types_for_bundle(client.bundle, self.package_types.as_deref())?; + let mut bundles = vec![]; // Copy the server over if it exists @@ -70,31 +71,24 @@ impl Bundle { bundles.push(server.main_exe()); } - // Create a list of bundles that we might need to copy + // Create a list of bundles that we might need to copy. + // Package-type based bundling is handled by the bundler module. match client.bundle { - // By default, mac/win/linux work with tauri bundle - BundleFormat::MacOS | BundleFormat::Linux | BundleFormat::Windows => { - tracing::info!("Running desktop bundler..."); - for bundle in Self::bundle_desktop(&client, &self.package_types)? { + // Desktop and Android platforms use package-type dispatch in the bundler module. + BundleFormat::MacOS + | BundleFormat::Ios + | BundleFormat::Linux + | BundleFormat::Windows + | BundleFormat::Android => { + tracing::info!("Running package bundler..."); + for bundle in Self::bundle_with_package_types(&client, &self.package_types).await? { bundles.extend(bundle.bundle_paths); } } - // Web/ios can just use their root_dir + // Web can use its root_dir directly. BundleFormat::Web => bundles.push(client.root_dir()), - BundleFormat::Ios => { - tracing::warn!("iOS bundles are not currently codesigned! You will need to codesign the app before distributing."); - bundles.push(client.root_dir()) - } BundleFormat::Server => bundles.push(client.root_dir()), - - BundleFormat::Android => { - let aab = client - .android_gradle_bundle() - .await - .context("Failed to run gradle bundleRelease")?; - bundles.push(aab); - } }; // Copy the bundles to the output directory if one was specified @@ -148,147 +142,45 @@ impl Bundle { }) } - fn bundle_desktop( + async fn bundle_with_package_types( build: &BuildRequest, - package_types: &Option>, - ) -> Result, Error> { - let krate = &build; - let exe = build.main_exe(); + package_types: &Option>, + ) -> Result, Error> { + use anyhow::bail; - _ = std::fs::remove_dir_all(krate.bundle_dir(build.bundle)); + if matches!( + build.bundle, + BundleFormat::MacOS | BundleFormat::Linux | BundleFormat::Windows + ) { + let krate = &build; + let exe = build.main_exe(); - let package = krate.package(); - let mut name: PathBuf = krate.executable_name().into(); - if cfg!(windows) { - name.set_extension("exe"); - } - std::fs::create_dir_all(krate.bundle_dir(build.bundle)) - .context("Failed to create bundle directory")?; - std::fs::copy(&exe, krate.bundle_dir(build.bundle).join(&name)) - .with_context(|| "Failed to copy the output executable into the bundle directory")?; + _ = std::fs::remove_dir_all(krate.bundle_dir(build.bundle)); - let binaries = vec![ - // We use the name of the exe but it has to be in the same directory - BundleBinary::new(krate.executable_name().to_string(), true) - .set_src_path(Some(exe.display().to_string())), - ]; + let mut name: PathBuf = krate.executable_name().into(); + if cfg!(windows) { + name.set_extension("exe"); + } + std::fs::create_dir_all(krate.bundle_dir(build.bundle)) + .context("Failed to create bundle directory")?; + std::fs::copy(&exe, krate.bundle_dir(build.bundle).join(&name)).with_context(|| { + "Failed to copy the output executable into the bundle directory" + })?; - let mut bundle_settings: BundleSettings = krate.config.bundle.clone().into(); - - // Check if required fields are provided instead of failing silently. - if bundle_settings.identifier.is_none() { - bail!("\n\nBundle identifier was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\nidentifier = \"com.mycompany\"\n\n"); - } - if bundle_settings.publisher.is_none() { - bail!("\n\nBundle publisher was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\npublisher = \"MyCompany\"\n\n"); - } - - /// Resolve an icon path relative to the crate dir - fn canonicalize_icon_path(build: &BuildRequest, icon: &mut String) -> Result<(), Error> { - let icon_path = build - .crate_dir() - .join(&icon) - .canonicalize() - .with_context(|| format!("Failed to canonicalize path to icon {icon:?}"))?; - *icon = icon_path.to_string_lossy().to_string(); - Ok(()) - } - - // Resolve bundle.icon relative to the crate dir - if let Some(icons) = bundle_settings.icon.as_mut() { - for icon in icons.iter_mut() { - canonicalize_icon_path(build, icon)?; + // Check if required fields are provided instead of failing silently. + if build.config.bundle.identifier.is_none() { + bail!("\n\nBundle identifier was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\nidentifier = \"com.mycompany\"\n\n"); + } + if build.config.bundle.publisher.is_none() { + bail!("\n\nBundle publisher was not provided in `Dioxus.toml`. Add it as:\n\n[bundle]\npublisher = \"MyCompany\"\n\n"); } } - #[allow(deprecated)] - if cfg!(windows) { - // Resolve bundle.windows.icon_path relative to the crate dir - let mut windows_icon_path = bundle_settings - .windows - .icon_path - .to_string_lossy() - .to_string(); - canonicalize_icon_path(build, &mut windows_icon_path)?; - bundle_settings.windows.icon_path = PathBuf::from(&windows_icon_path); + let ctx = crate::bundler::BundleContext::new(build, package_types).await?; - let windows_icon_override = krate.config.bundle.windows.as_ref().map(|w| &w.icon_path); - if windows_icon_override.is_none() { - let icon_path = bundle_settings - .icon - .as_ref() - .and_then(|icons| icons.first()); + tracing::debug!("Bundling project for {:?}", ctx.package_types()); - if let Some(icon_path) = icon_path { - bundle_settings.icon = Some(vec![icon_path.into()]); - }; - } - } - - if bundle_settings.resources_map.is_none() { - bundle_settings.resources_map = Some(HashMap::new()); - } - - let asset_dir = build.asset_dir(); - if asset_dir.exists() { - for entry in WalkDir::new(&asset_dir) { - let entry = entry.unwrap(); - let path = entry.path(); - - if path.is_file() { - let old = path - .canonicalize() - .with_context(|| format!("Failed to canonicalize {entry:?}"))?; - let new = - PathBuf::from("assets").join(path.strip_prefix(&asset_dir).unwrap_or(path)); - - tracing::debug!("Bundled asset: {old:?} -> {new:?}"); - bundle_settings - .resources_map - .as_mut() - .expect("to be set") - .insert(old.display().to_string(), new.display().to_string()); - } - } - } - - for resource_path in bundle_settings.resources.take().into_iter().flatten() { - bundle_settings - .resources_map - .as_mut() - .expect("to be set") - .insert(resource_path, "".to_string()); - } - - let mut settings = SettingsBuilder::new() - .project_out_directory(krate.bundle_dir(build.bundle)) - .package_settings(PackageSettings { - product_name: krate.bundled_app_name(), - version: package.version.to_string(), - description: package.description.clone().unwrap_or_default(), - homepage: Some(package.homepage.clone().unwrap_or_default()), - authors: Some(package.authors.clone()), - default_run: Some(name.display().to_string()), - }) - .log_level(log::Level::Debug) - .binaries(binaries) - .bundle_settings(bundle_settings); - - if let Some(packages) = &package_types { - settings = settings.package_types(packages.iter().map(|p| (*p).into()).collect()); - } - - settings = settings.target(build.triple.to_string()); - - let settings = settings - .build() - .context("failed to bundle tauri bundle settings")?; - tracing::debug!("Bundling project with settings: {:#?}", settings); - if cfg!(target_os = "macos") { - std::env::set_var("CI", "true"); - } - - let bundles = tauri_bundler::bundle::bundle_project(&settings).inspect_err(|err| { + let bundles = ctx.bundle_project().await.inspect_err(|err| { tracing::error!("Failed to bundle project: {:#?}", err); if cfg!(target_os = "macos") { tracing::error!("Make sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)"); @@ -297,4 +189,127 @@ impl Bundle { Ok(bundles) } + + fn default_package_types(bundle: BundleFormat) -> Option> { + match bundle { + BundleFormat::Ios => Some(vec![crate::PackageType::Ipa]), + BundleFormat::Android => Some(vec![crate::PackageType::Aab]), + _ => None, + } + } + + fn force_ios_bundle_device_target_if_needed(&mut self) -> Result<()> { + let client_args = self + .args + .client + .as_mut() + .map(|args| &mut args.build_arguments) + .unwrap_or(&mut self.args.shared.build_arguments); + + if client_args.platform == Platform::Ios && client_args.target.is_none() { + client_args.target = Some("aarch64-apple-ios".parse::()?); + } + + Ok(()) + } + + fn validate_package_types_for_bundle( + bundle: BundleFormat, + package_types: Option<&[PackageType]>, + ) -> Result<(), Error> { + let Some(package_types) = package_types else { + return Ok(()); + }; + + let is_package_supported = |package_type: &PackageType| -> bool { + match bundle { + BundleFormat::MacOS => matches!( + package_type, + PackageType::MacOsBundle | PackageType::Dmg | PackageType::Updater + ), + BundleFormat::Linux => matches!( + package_type, + PackageType::Deb + | PackageType::Rpm + | PackageType::AppImage + | PackageType::Updater + ), + BundleFormat::Windows => { + matches!( + package_type, + PackageType::WindowsMsi | PackageType::Nsis | PackageType::Updater + ) + } + BundleFormat::Android => { + matches!(package_type, PackageType::Apk | PackageType::Aab) + } + BundleFormat::Ios => matches!(package_type, PackageType::IosApp | PackageType::Ipa), + BundleFormat::Web | BundleFormat::Server => false, + } + }; + + if let Some(invalid) = package_types.iter().find(|pt| !is_package_supported(pt)) { + anyhow::bail!( + "Package type '{invalid:?}' is not supported for bundle format '{bundle}'." + ); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::Bundle; + use crate::{ + cli::CommandWithPlatformOverrides, BuildArgs, BundleFormat, PackageType, Platform, + }; + + #[test] + fn ios_bundle_defaults_to_ipa() { + assert_eq!( + Bundle::default_package_types(BundleFormat::Ios), + Some(vec![PackageType::Ipa]) + ); + } + + #[test] + fn validates_ios_package_types() { + assert!(Bundle::validate_package_types_for_bundle( + BundleFormat::Ios, + Some(&[PackageType::IosApp, PackageType::Ipa]), + ) + .is_ok()); + + assert!(Bundle::validate_package_types_for_bundle( + BundleFormat::Ios, + Some(&[PackageType::Dmg]), + ) + .is_err()); + } + + #[test] + fn bundle_ios_defaults_to_device_target() { + let mut bundle = Bundle { + package_types: None, + out_dir: None, + args: CommandWithPlatformOverrides { + shared: BuildArgs { + build_arguments: crate::TargetArgs { + platform: Platform::Ios, + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }, + }; + + bundle.force_ios_bundle_device_target_if_needed().unwrap(); + + assert_eq!( + bundle.args.shared.build_arguments.target, + Some("aarch64-apple-ios".parse().unwrap()) + ); + } } diff --git a/packages/cli/src/cli/check.rs b/packages/cli/src/cli/check.rs index bd1c2f501..e58f9fc29 100644 --- a/packages/cli/src/cli/check.rs +++ b/packages/cli/src/cli/check.rs @@ -84,7 +84,7 @@ async fn check_files_and_report(files_to_check: Vec) -> Result<()> { let res = tokio::spawn(async move { tokio::fs::read_to_string(&_path) .await - .map(|contents| dioxus_check::check_file(_path, &contents)) + .map(|contents| crate::check::check_file(_path, &contents)) }) .await; diff --git a/packages/cli/src/cli/platform_override.rs b/packages/cli/src/cli/platform_override.rs index 7cbc94c14..826e712b6 100644 --- a/packages/cli/src/cli/platform_override.rs +++ b/packages/cli/src/cli/platform_override.rs @@ -63,7 +63,7 @@ where { fn augment_args(cmd: clap::Command) -> clap::Command { T::augment_args(cmd).defer(|cmd| { - PlatformOverrides::::augment_subcommands(cmd.disable_help_subcommand(true)) + PlatformOverrides::::augment_subcommands(cmd.disable_help_subcommand(true)) }) } @@ -182,3 +182,62 @@ where unimplemented!() } } + +#[cfg(test)] +mod tests { + use clap::Parser; + + #[test] + fn cli_parses_bundle_with_package_types_without_recursing() { + let cli = crate::Cli::try_parse_from([ + "dx", + "bundle", + "--platform", + "desktop", + "--package-types", + "msi", + ]) + .expect("bundle command should parse"); + + assert!(matches!(cli.action, crate::Commands::Bundle(_))); + } + + #[test] + fn cli_parses_platform_override_subcommands_without_recursing() { + let cli = crate::Cli::try_parse_from([ + "dx", + "bundle", + "--platform", + "desktop", + "@client", + "--release", + "@server", + "--platform", + "server", + ]) + .expect("platform override subcommands should parse"); + + let crate::Commands::Bundle(bundle) = cli.action else { + panic!("expected bundle command"); + }; + + let client = bundle + .args + .client + .as_ref() + .expect("@client override should be captured"); + let server = bundle + .args + .server + .as_ref() + .expect("@server override should be captured"); + + assert!(!bundle.args.shared.build_arguments.release); + assert!(client.build_arguments.release); + assert_eq!( + server.build_arguments.platform, + crate::Platform::Server, + "@server should override the platform" + ); + } +} diff --git a/packages/cli/src/cli/print.rs b/packages/cli/src/cli/print.rs index d6313f79b..ec2998f5d 100644 --- a/packages/cli/src/cli/print.rs +++ b/packages/cli/src/cli/print.rs @@ -178,8 +178,6 @@ impl Print { /// /// This function comes from shell-escape fn escape_windows(s: Cow) -> Cow { - use std::iter::repeat; - let mut needs_escape = s.is_empty(); for ch in s.chars() { match ch { @@ -202,15 +200,15 @@ impl Print { match chars.next() { Some('"') => { - es.extend(repeat('\\').take(nslashes * 2 + 1)); + es.extend(std::iter::repeat_n('\\', nslashes * 2 + 1)); es.push('"'); } Some(c) => { - es.extend(repeat('\\').take(nslashes)); + es.extend(std::iter::repeat_n('\\', nslashes)); es.push(c); } None => { - es.extend(repeat('\\').take(nslashes * 2)); + es.extend(std::iter::repeat_n('\\', nslashes * 2)); break; } } diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index 830043b1c..08f186177 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -82,10 +82,13 @@ impl RunArgs { current, total, krate, + fresh, } => { - tracing::debug!( - "[{bundle_format}] ({current}/{total}) Compiling {krate} ", - ) + if !fresh { + tracing::debug!( + "[{bundle_format}] ({current}/{total}) Compiling {krate} ", + ) + } } BuildStage::RunningBindgen => { tracing::info!("[{bundle_format}] Running WASM bindgen") diff --git a/packages/cli/src/config/bundle.rs b/packages/cli/src/config/bundle.rs index ac27b54e0..3bff37ea4 100644 --- a/packages/cli/src/config/bundle.rs +++ b/packages/cli/src/config/bundle.rs @@ -121,7 +121,7 @@ pub(crate) struct WixSettings { pub(crate) fips_compliant: bool, /// MSI installer version in the format `major.minor.patch.build` (build is optional). /// - /// Because a valid version is required for MSI installer, it will be derived from [`tauri_bundler::PackageSettings::version`] if this field is not set. + /// Because a valid version is required for MSI installer, it will be derived from the package version if this field is not set. /// /// The first field is the major version and has a maximum value of 255. The second field is the minor version and has a maximum value of 255. /// The third and fourth fields have a maximum value of 65,535. @@ -132,10 +132,9 @@ pub(crate) struct WixSettings { /// A GUID upgrade code for MSI installer. This code **_must stay the same across all of your updates_**, /// otherwise, Windows will treat your update as a different app and your users will have duplicate versions of your app. /// - /// By default, tauri generates this code by generating a Uuid v5 using the string `.exe.app.x64` in the DNS namespace. - /// You can use Tauri's CLI to generate and print this code for you by running `tauri inspect wix-upgrade-code`. + /// By default, this code is generated as a Uuid v5 using the string `.exe.app.x64` in the DNS namespace. /// - /// It is recommended that you set this value in your tauri config file to avoid accidental changes in your upgrade code + /// It is recommended that you set this value in your Dioxus.toml to avoid accidental changes in your upgrade code /// whenever you want to change your product name. #[serde(default)] #[schemars(with = "Option")] @@ -146,28 +145,39 @@ pub(crate) struct WixSettings { pub(crate) struct MacOsSettings { #[serde(default)] pub(crate) bundle_version: Option, + #[serde(default)] pub(crate) frameworks: Option>, + #[serde(default)] pub(crate) minimum_system_version: Option, + #[serde(default)] pub(crate) license: Option, + #[serde(default)] pub(crate) exception_domain: Option, + #[serde(default)] pub(crate) signing_identity: Option, + #[serde(default)] pub(crate) provider_short_name: Option, + #[serde(default)] pub(crate) entitlements: Option, + #[serde(default)] pub(crate) info_plist_path: Option, + #[serde(default)] pub(crate) bundle_name: Option, + /// List of custom files to add to the application bundle. /// Maps the path in the Contents directory in the app to the path of the file to include (relative to the current working directory). #[serde(default)] pub files: HashMap, + /// Preserve the hardened runtime version flag, see /// /// Settings this to `false` is useful when using an ad-hoc signature, making it less strict. @@ -290,15 +300,19 @@ pub struct CustomSignCommandSettings { pub args: Vec, } -#[derive(Clone, Copy, Debug, clap::ValueEnum, Serialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum, Serialize)] pub(crate) enum PackageType { /// The macOS application bundle (.app). #[clap(name = "macos")] MacOsBundle, - /// The iOS app bundle. + /// The raw iOS application bundle (.app). #[clap(name = "ios")] - IosBundle, + IosApp, + + /// The distributable iOS application archive (.ipa). + #[clap(name = "ipa")] + Ipa, /// The Windows bundle (.msi). #[clap(name = "msi")] @@ -327,6 +341,14 @@ pub(crate) enum PackageType { /// The Updater bundle (a patch of an existing app) #[clap(name = "updater")] Updater, + + /// Android application package (.apk). + #[clap(name = "apk")] + Apk, + + /// Android App Bundle (.aab). + #[clap(name = "aab")] + Aab, } impl FromStr for PackageType { @@ -335,7 +357,8 @@ impl FromStr for PackageType { fn from_str(s: &str) -> Result { match s { "macos" => Ok(PackageType::MacOsBundle), - "ios" => Ok(PackageType::IosBundle), + "ios" => Ok(PackageType::IosApp), + "ipa" => Ok(PackageType::Ipa), "msi" => Ok(PackageType::WindowsMsi), "nsis" => Ok(PackageType::Nsis), "deb" => Ok(PackageType::Deb), @@ -343,6 +366,8 @@ impl FromStr for PackageType { "appimage" => Ok(PackageType::AppImage), "dmg" => Ok(PackageType::Dmg), "updater" => Ok(PackageType::Updater), + "apk" => Ok(PackageType::Apk), + "aab" => Ok(PackageType::Aab), _ => Err(format!("{s} is not a valid package type")), } } diff --git a/packages/cli/src/dx_build_info.rs b/packages/cli/src/dx_build_info.rs index d11fb6389..48bcacedb 100644 --- a/packages/cli/src/dx_build_info.rs +++ b/packages/cli/src/dx_build_info.rs @@ -1,2 +1,8 @@ -// The file has been placed there by the build script. -include!(concat!(env!("OUT_DIR"), "/built.rs")); +pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const PKG_VERSION_MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR"); +pub const PKG_VERSION_MINOR: &str = env!("CARGO_PKG_VERSION_MINOR"); +pub const PKG_VERSION_PATCH: &str = env!("CARGO_PKG_VERSION_PATCH"); +pub const PKG_VERSION_PRE: &str = env!("CARGO_PKG_VERSION_PRE"); + +pub const GIT_COMMIT_HASH: Option<&str> = option_env!("DIOXUS_CLI_GIT_SHA"); +pub const GIT_COMMIT_HASH_SHORT: Option<&str> = option_env!("DIOXUS_CLI_GIT_SHA_SHORT"); diff --git a/packages/cli/src/esbuild.rs b/packages/cli/src/esbuild.rs new file mode 100644 index 000000000..eddad4c58 --- /dev/null +++ b/packages/cli/src/esbuild.rs @@ -0,0 +1,207 @@ +//! esbuild binary download, caching, and invocation. +//! +//! esbuild is used for JavaScript bundling and minification, replacing the +//! previous SWC Rust library dependencies. The standalone Go binary is +//! downloaded from the npm registry and cached locally. + +use crate::{CliSettings, Workspace}; +use anyhow::{anyhow, Context}; +use std::path::PathBuf; + +/// Pinned esbuild version. +const ESBUILD_VERSION: &str = "0.27.3"; + +pub(crate) struct Esbuild; + +impl Esbuild { + /// Ensure the esbuild binary is available, downloading if needed. + /// Returns the path to the binary. + pub(crate) async fn get_or_install() -> anyhow::Result { + if CliSettings::prefer_no_downloads() { + which::which("esbuild") + .map_err(|_| anyhow!("esbuild not found on PATH and downloads are disabled")) + } else { + let path = Self::installed_bin_path(); + if !path.exists() { + Self::install_from_npm().await?; + } + Ok(path) + } + } + + /// Return the esbuild binary path if it's already been installed or is on PATH. + /// This is a sync check intended for use after `verify_tooling` has been called. + pub(crate) fn path_if_installed() -> Option { + let path = Self::installed_bin_path(); + if path.exists() { + Some(path) + } else { + which::which("esbuild").ok() + } + } + + /// The path where we cache the esbuild binary. + fn installed_bin_path() -> PathBuf { + let dir = Workspace::dioxus_data_dir().join("esbuild"); + let name = if cfg!(windows) { + format!("esbuild-{ESBUILD_VERSION}.exe") + } else { + format!("esbuild-{ESBUILD_VERSION}") + }; + dir.join(name) + } + + /// Download esbuild from the npm registry and extract the binary. + /// + /// esbuild publishes platform-specific packages to npm as tar.gz archives. + /// Each archive contains `package/bin/esbuild[.exe]`. + async fn install_from_npm() -> anyhow::Result<()> { + let platform = Self::npm_platform_package() + .ok_or_else(|| anyhow!("No esbuild binary available for this platform"))?; + + let url = format!( + "https://registry.npmjs.org/@esbuild/{platform}/-/{platform}-{ESBUILD_VERSION}.tgz" + ); + + tracing::info!("Installing esbuild v{ESBUILD_VERSION} from {url}..."); + + let bytes = reqwest::get(&url) + .await + .with_context(|| format!("Failed to download esbuild from {url}"))? + .bytes() + .await + .context("Failed to read esbuild download response")?; + + // Extract `package/bin/esbuild[.exe]` from the tar.gz + let binary_data = Self::extract_binary_from_tgz(&bytes)?; + + let binary_path = Self::installed_bin_path(); + std::fs::create_dir_all(binary_path.parent().unwrap()) + .context("Failed to create esbuild cache directory")?; + + std::fs::write(&binary_path, &binary_data).context("Failed to write esbuild binary")?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = binary_path.metadata()?.permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&binary_path, perms)?; + } + + tracing::info!("esbuild v{ESBUILD_VERSION} installed successfully"); + Ok(()) + } + + /// Extract the esbuild binary from an npm tar.gz package. + /// + /// The binary is at `package/bin/esbuild` (or `package/bin/esbuild.exe` on Windows). + fn extract_binary_from_tgz(tgz_bytes: &[u8]) -> anyhow::Result> { + let bin_name = if cfg!(windows) { + "esbuild.exe" + } else { + "esbuild" + }; + Self::extract_binary_named_from_tgz(tgz_bytes, bin_name) + } + + fn extract_binary_named_from_tgz(tgz_bytes: &[u8], bin_name: &str) -> anyhow::Result> { + use flate2::read::GzDecoder; + use std::io::Read; + use tar::Archive; + + let decoder = GzDecoder::new(tgz_bytes); + let mut archive = Archive::new(decoder); + + let expected_paths = [ + format!("package/bin/{bin_name}"), + format!("package/{bin_name}"), + ]; + let mut archive_entries = Vec::new(); + + for entry in archive.entries().context("Failed to read tar entries")? { + let mut entry = entry.context("Failed to read tar entry")?; + let path = entry.path().context("Failed to read entry path")?; + let path_string = path.to_string_lossy().replace('\\', "/"); + + if expected_paths.contains(&path_string) { + let mut data = Vec::new(); + entry + .read_to_end(&mut data) + .context("Failed to read esbuild binary from archive")?; + return Ok(data); + } + + archive_entries.push(path_string); + } + + archive_entries.sort(); + archive_entries.truncate(10); + + anyhow::bail!( + "esbuild binary not found in archive (expected one of {}). Found entries: {}", + expected_paths.join(", "), + archive_entries.join(", ") + ); + } + + /// Map the host platform to the npm package name for esbuild. + /// + /// esbuild publishes per-platform packages under `@esbuild/{name}`: + /// - darwin-arm64, darwin-x64 + /// - linux-x64, linux-arm64 + /// - win32-x64, win32-arm64 + fn npm_platform_package() -> Option<&'static str> { + if cfg!(all(target_os = "macos", target_arch = "aarch64")) { + Some("darwin-arm64") + } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) { + Some("darwin-x64") + } else if cfg!(all(target_os = "linux", target_arch = "x86_64")) { + Some("linux-x64") + } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) { + Some("linux-arm64") + } else if cfg!(all(target_os = "windows", target_arch = "x86_64")) { + Some("win32-x64") + } else if cfg!(all(target_os = "windows", target_arch = "aarch64")) { + Some("win32-arm64") + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::Esbuild; + use flate2::{write::GzEncoder, Compression}; + + fn tgz_with_entries(entries: &[(&str, &[u8])]) -> Vec { + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + { + let mut tar = tar::Builder::new(&mut encoder); + for (path, contents) in entries { + let mut header = tar::Header::new_gnu(); + header.set_size(contents.len() as u64); + header.set_mode(0o644); + header.set_cksum(); + tar.append_data(&mut header, *path, *contents).unwrap(); + } + tar.finish().unwrap(); + } + encoder.finish().unwrap() + } + + #[test] + fn extracts_binary_from_bin_layout() { + let tgz = tgz_with_entries(&[("package/bin/esbuild.exe", b"windows-binary")]); + let extracted = Esbuild::extract_binary_named_from_tgz(&tgz, "esbuild.exe").unwrap(); + assert_eq!(extracted, b"windows-binary"); + } + + #[test] + fn extracts_binary_from_package_root_layout() { + let tgz = tgz_with_entries(&[("package/esbuild.exe", b"windows-binary")]); + let extracted = Esbuild::extract_binary_named_from_tgz(&tgz, "esbuild.exe").unwrap(); + assert_eq!(extracted, b"windows-binary"); + } +} diff --git a/packages/cli/src/logging.rs b/packages/cli/src/logging.rs index 0ca34e596..932eb78bc 100644 --- a/packages/cli/src/logging.rs +++ b/packages/cli/src/logging.rs @@ -42,14 +42,14 @@ use futures_util::FutureExt; use itertools::Itertools; use serde::{Deserialize, Serialize}; use serde_json::json; -use std::{any::Any, io::Read, str::FromStr, sync::Arc, time::SystemTime}; +use std::{any::Any, io::Read, pin::Pin, str::FromStr, sync::Arc, time::SystemTime}; use std::{borrow::Cow, sync::OnceLock}; use std::{ collections::HashMap, env, fmt::{Debug, Display, Write as _}, sync::atomic::{AtomicBool, Ordering}, - time::Instant, + time::{Duration, Instant}, }; use std::{future::Future, panic::AssertUnwindSafe}; use tracing::{field::Visit, Level, Subscriber}; @@ -139,10 +139,9 @@ impl TraceController { /// /// We pass the TraceController around the CLI in a few places, namely the serve command so the TUI /// can access things like the logs. - pub async fn main(run_app: impl FnOnce(Commands, Self) -> F) -> StructuredOutput - where - F: Future>, - { + pub async fn main( + run_app: impl FnOnce(Commands, Self) -> Pin>>>, + ) -> StructuredOutput { let args = Cli::parse(); let tui_active = Arc::new(AtomicBool::new(false)); let is_serve_cmd = matches!(args.action, Commands::Serve(_)); @@ -178,7 +177,7 @@ impl TraceController { // We complete filter out a few fields that are not relevant to the user, like `dx_src` and `json` let fmt_layer = tracing_subscriber::fmt::layer() - .with_target(args.verbosity.verbose) + .with_target(false) .fmt_fields( format::debug_fn(move |writer, field, value| { if field.name() == "json" && !args.verbosity.json_output { @@ -521,7 +520,7 @@ impl TraceController { error_handled, } = event; - let mut ph_event = posthog_rs::Event::new(action, distinct_id.to_string()); + let mut ph_event = PosthogEvent::new(action, distinct_id.to_string()); // The reporter's fields _ = ph_event.insert_prop("is_ci", CliSettings::is_ci()); @@ -649,7 +648,7 @@ impl TraceController { reset_cursor(); // re-emit any remaining messages in case they're useful. - while let Ok(Some(msg)) = self.tui_rx.lock().await.try_next() { + while let Ok(msg) = self.tui_rx.lock().await.try_recv() { let content = match msg.content { TraceContent::Text(text) => text, TraceContent::Cargo(msg) => msg.message.to_string(), @@ -729,7 +728,7 @@ impl TraceController { true => { let mut msgs = self.telemetry_rx.lock().await; - let request_body = std::iter::from_fn(|| msgs.try_next().ok().flatten()) + let request_body = std::iter::from_fn(|| msgs.try_recv().ok()) .filter_map(|msg| serde_json::to_value(msg).ok()) .collect::>(); @@ -740,8 +739,7 @@ impl TraceController { false => { let mut msgs = self.telemetry_rx.lock().await; - let msg_list = - std::iter::from_fn(|| msgs.try_next().ok().flatten()).collect::>(); + let msg_list = std::iter::from_fn(|| msgs.try_recv().ok()).collect::>(); if !msg_list.is_empty() { let dest = Workspace::dioxus_data_dir() @@ -1336,10 +1334,43 @@ impl Default for PrettyUptime { impl FormatTime for PrettyUptime { fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result { - let e = self.epoch.elapsed(); - write!(w, "{:4}.{:2}s", e.as_secs(), e.subsec_millis()) + Self::write_elapsed(self.epoch.elapsed(), w) } } + +impl PrettyUptime { + fn write_elapsed(elapsed: Duration, mut w: impl std::fmt::Write) -> std::fmt::Result { + write!( + w, + "{:3}.{:02}s", + elapsed.as_secs(), + elapsed.subsec_millis() / 10 + ) + } +} + +#[cfg(test)] +mod pretty_uptime_tests { + use super::PrettyUptime; + use std::time::Duration; + + #[test] + fn pretty_uptime_pads_centiseconds_to_keep_a_stable_width() { + let cases = [ + (Duration::from_millis(92_993), " 92.99s"), + (Duration::from_millis(93_085), " 93.08s"), + (Duration::from_millis(93_130), " 93.13s"), + (Duration::from_millis(999_999), "999.99s"), + ]; + + for (elapsed, expected) in cases { + let mut rendered = String::new(); + PrettyUptime::write_elapsed(elapsed, &mut rendered).unwrap(); + assert_eq!(rendered, expected); + } + } +} + /// Run the provided future and wait for it to complete, handling Ctrl-C gracefully. /// /// If ctrl-c is pressed twice, it exits immediately, skipping our telemetry flush. @@ -1372,7 +1403,37 @@ async fn run_with_ctrl_c( /// We only store non-pii information in telemetry to track issues and performance /// across the CLI. #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Reporter { - pub session_id: Uuid, - pub distinct_id: Uuid, +struct Reporter { + session_id: Uuid, + distinct_id: Uuid, +} + +/// A minimal PostHog event, replacing the `posthog-rs` crate dependency. +/// See +#[derive(Serialize, Debug)] +struct PosthogEvent { + event: String, + #[serde(rename = "$distinct_id")] + distinct_id: String, + properties: std::collections::HashMap, +} + +impl PosthogEvent { + fn new(event: impl Into, distinct_id: impl Into) -> Self { + Self { + event: event.into(), + distinct_id: distinct_id.into(), + properties: std::collections::HashMap::new(), + } + } + + fn insert_prop( + &mut self, + key: impl Into, + value: V, + ) -> Result<(), serde_json::Error> { + self.properties + .insert(key.into(), serde_json::to_value(value)?); + Ok(()) + } } diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 7e66665c9..dce3197e5 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -5,15 +5,18 @@ #![allow(clippy::doc_overindented_list_items)] mod build; -mod bundle_utils; +mod bundler; mod cargo_toml; +mod check; mod cli; mod config; mod devcfg; mod dx_build_info; mod error; +mod esbuild; mod fastfs; mod logging; +mod opt; mod platform; mod rustcwrapper; mod serve; @@ -53,25 +56,27 @@ async fn main() -> ExitCode { } // Run under the tracing collector so we can capture errors/panics. - let result = TraceController::main(|args, tracer| async move { - match args { - Commands::Serve(opts) => opts.serve(&tracer).await, - Commands::Translate(opts) => opts.translate(), - Commands::New(opts) => opts.create().await, - Commands::Init(opts) => opts.init().await, - Commands::Config(opts) => opts.config().await, - Commands::Autoformat(opts) => opts.autoformat().await, - Commands::Check(opts) => opts.check().await, - Commands::Build(opts) => opts.build().await, - Commands::Bundle(opts) => opts.bundle().await, - Commands::Run(opts) => opts.run().await, - Commands::SelfUpdate(opts) => opts.self_update().await, - Commands::Tools(BuildTools::BuildAssets(opts)) => opts.run().await, - Commands::Tools(BuildTools::HotpatchTip(opts)) => opts.run().await, - Commands::Doctor(opts) => opts.doctor().await, - Commands::Print(opts) => opts.print().await, - Commands::Components(opts) => opts.run().await, - } + let result = TraceController::main(|args, tracer| { + Box::pin(async move { + match args { + Commands::Serve(opts) => opts.serve(&tracer).await, + Commands::Translate(opts) => opts.translate(), + Commands::New(opts) => opts.create().await, + Commands::Init(opts) => opts.init().await, + Commands::Config(opts) => opts.config().await, + Commands::Autoformat(opts) => opts.autoformat().await, + Commands::Check(opts) => opts.check().await, + Commands::Build(opts) => opts.build().await, + Commands::Bundle(opts) => opts.bundle().await, + Commands::Run(opts) => opts.run().await, + Commands::SelfUpdate(opts) => opts.self_update().await, + Commands::Tools(BuildTools::BuildAssets(opts)) => opts.run().await, + Commands::Tools(BuildTools::HotpatchTip(opts)) => opts.run().await, + Commands::Doctor(opts) => opts.doctor().await, + Commands::Print(opts) => opts.print().await, + Commands::Components(opts) => opts.run().await, + } + }) }); // Print the structured output in JSON format for third-party tools to consume. diff --git a/packages/cli-opt/src/css.rs b/packages/cli/src/opt/css.rs similarity index 100% rename from packages/cli-opt/src/css.rs rename to packages/cli/src/opt/css.rs diff --git a/packages/cli-opt/src/file.rs b/packages/cli/src/opt/file.rs similarity index 94% rename from packages/cli-opt/src/file.rs rename to packages/cli/src/opt/file.rs index 1f0820a18..df8badfc6 100644 --- a/packages/cli-opt/src/file.rs +++ b/packages/cli/src/opt/file.rs @@ -3,7 +3,7 @@ use manganis::{AssetOptions, CssModuleAssetOptions, FolderAssetOptions}; use manganis_core::{AssetVariant, CssAssetOptions, ImageAssetOptions, JsAssetOptions}; use std::path::Path; -use crate::css::{process_css_module, process_scss}; +use crate::opt::css::{process_css_module, process_scss}; use super::{ css::process_css, folder::process_folder, image::process_image, js::process_js, @@ -11,12 +11,13 @@ use super::{ }; /// Process a specific file asset with the given options reading from the source and writing to the output path -pub fn process_file_to( +pub(crate) fn process_file_to( options: &AssetOptions, source: &Path, output_path: &Path, + esbuild_path: Option<&Path>, ) -> anyhow::Result<()> { - process_file_to_with_options(options, source, output_path, false) + process_file_to_with_options(options, source, output_path, false, esbuild_path) } /// Process a specific file asset with additional options @@ -25,6 +26,7 @@ pub(crate) fn process_file_to_with_options( source: &Path, output_path: &Path, in_folder: bool, + esbuild_path: Option<&Path>, ) -> anyhow::Result<()> { // If the file already exists and this is a hashed asset, then we must have a file // with the same hash already. The hash has the file contents and options, so if we @@ -61,7 +63,7 @@ pub(crate) fn process_file_to_with_options( process_scss(options, source, &temp_path)?; } ResolvedAssetType::Js(options) => { - process_js(options, source, &temp_path, !in_folder)?; + process_js(options, source, &temp_path, !in_folder, esbuild_path)?; } ResolvedAssetType::Image(options) => { process_image(options, source, &temp_path)?; @@ -70,7 +72,7 @@ pub(crate) fn process_file_to_with_options( process_json(source, &temp_path)?; } ResolvedAssetType::Folder(_) => { - process_folder(source, &temp_path)?; + process_folder(source, &temp_path, esbuild_path)?; } ResolvedAssetType::File => { let source_file = std::fs::File::open(source)?; diff --git a/packages/cli-opt/src/folder.rs b/packages/cli/src/opt/folder.rs similarity index 67% rename from packages/cli-opt/src/folder.rs rename to packages/cli/src/opt/folder.rs index 1448176ae..87fa05ee3 100644 --- a/packages/cli-opt/src/folder.rs +++ b/packages/cli/src/opt/folder.rs @@ -2,10 +2,14 @@ use std::path::Path; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use crate::file::process_file_to_with_options; +use crate::opt::file::process_file_to_with_options; /// Process a folder, optimizing and copying all assets into the output folder -pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> { +pub fn process_folder( + source: &Path, + output_folder: &Path, + esbuild_path: Option<&Path>, +) -> anyhow::Result<()> { // Create the folder std::fs::create_dir_all(output_folder)?; @@ -21,9 +25,9 @@ pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> let metadata = file.metadata()?; let output_path = output_folder.join(file.strip_prefix(source)?); if metadata.is_dir() { - process_folder(&file, &output_path) + process_folder(&file, &output_path, esbuild_path) } else { - process_file_minimal(&file, &output_path) + process_file_minimal(&file, &output_path, esbuild_path) } })?; @@ -31,12 +35,17 @@ pub fn process_folder(source: &Path, output_folder: &Path) -> anyhow::Result<()> } /// Optimize a file without changing any of its contents significantly (e.g. by changing the extension) -fn process_file_minimal(input_path: &Path, output_path: &Path) -> anyhow::Result<()> { +fn process_file_minimal( + input_path: &Path, + output_path: &Path, + esbuild_path: Option<&Path>, +) -> anyhow::Result<()> { process_file_to_with_options( &manganis_core::AssetOptions::builder().into_asset_options(), input_path, output_path, true, + esbuild_path, )?; Ok(()) } diff --git a/packages/cli-opt/src/hash.rs b/packages/cli/src/opt/hash.rs similarity index 97% rename from packages/cli-opt/src/hash.rs rename to packages/cli/src/opt/hash.rs index 3127bcfbd..3dcfcf4be 100644 --- a/packages/cli-opt/src/hash.rs +++ b/packages/cli/src/opt/hash.rs @@ -6,7 +6,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::{ +use crate::opt::{ css::hash_scss, file::{resolve_asset_options, ResolvedAssetType}, js::hash_js, @@ -48,8 +48,8 @@ fn hash_file(options: &AssetOptions, source: &Path) -> anyhow::Result let mut hash = std::collections::hash_map::DefaultHasher::new(); options.hash(&mut hash); - // Hash the version of CLI opt - hash.write(crate::build_info::version().as_bytes()); + // Hash the version of the CLI + hash.write(crate::dx_build_info::PKG_VERSION.as_bytes()); hash_file_with_options(options, source, &mut hash, false)?; let hash = hash.finish(); @@ -128,7 +128,7 @@ pub(crate) fn hash_file_contents(source: &Path, hasher: &mut impl Hasher) -> any } /// Add a hash to the asset, or log an error if it fails -pub fn add_hash_to_asset(asset: &mut BundledAsset) { +pub(crate) fn add_hash_to_asset(asset: &mut BundledAsset) { let source = asset.absolute_source_path(); match AssetHash::hash_file_contents(asset.options(), source) { Ok(hash) => { diff --git a/packages/cli-opt/src/image/mod.rs b/packages/cli/src/opt/image.rs similarity index 52% rename from packages/cli-opt/src/image/mod.rs rename to packages/cli/src/opt/image.rs index 515079f64..ea25ac7ea 100644 --- a/packages/cli-opt/src/image/mod.rs +++ b/packages/cli/src/opt/image.rs @@ -1,12 +1,8 @@ -use std::path::Path; - use anyhow::Context; -use jpg::compress_jpg; +use image::{DynamicImage, EncodableLayout}; use manganis_core::{ImageAssetOptions, ImageFormat, ImageSize}; -use png::compress_png; - -mod jpg; -mod png; +use std::io::{BufWriter, Write}; +use std::path::Path; pub(crate) fn process_image( image_options: &ImageAssetOptions, @@ -71,3 +67,70 @@ pub(crate) fn process_image( Ok(()) } + +pub(crate) fn compress_png(image: DynamicImage, output_location: &Path) { + // Image loading/saving is outside scope of this library + let width = image.width() as usize; + let height = image.height() as usize; + let bitmap: Vec<_> = image + .into_rgba8() + .pixels() + .map(|px| imagequant::RGBA::new(px[0], px[1], px[2], px[3])) + .collect(); + + // Configure the library + let mut liq = imagequant::new(); + liq.set_speed(5).unwrap(); + liq.set_quality(0, 99).unwrap(); + + // Describe the bitmap + let mut img = liq.new_image(&bitmap[..], width, height, 0.0).unwrap(); + + // The magic happens in quantize() + let mut res = match liq.quantize(&mut img) { + Ok(res) => res, + Err(err) => panic!("Quantization failed, because: {err:?}"), + }; + + let (palette, pixels) = res.remapped(&mut img).unwrap(); + + let file = std::fs::File::create(output_location).unwrap(); + let w = &mut BufWriter::new(file); + + let mut encoder = png::Encoder::new(w, width as u32, height as u32); + encoder.set_color(png::ColorType::Rgba); + let mut flattened_palette = Vec::new(); + let mut alpha_palette = Vec::new(); + for px in palette { + flattened_palette.push(px.r); + flattened_palette.push(px.g); + flattened_palette.push(px.b); + alpha_palette.push(px.a); + } + encoder.set_palette(flattened_palette); + encoder.set_trns(alpha_palette); + encoder.set_depth(png::BitDepth::Eight); + encoder.set_color(png::ColorType::Indexed); + encoder.set_compression(png::Compression::Best); + let mut writer = encoder.write_header().unwrap(); + writer.write_image_data(&pixels).unwrap(); + writer.finish().unwrap(); +} + +pub(crate) fn compress_jpg(image: DynamicImage, output_location: &Path) -> anyhow::Result<()> { + let mut comp = mozjpeg::Compress::new(mozjpeg::ColorSpace::JCS_EXT_RGBX); + let width = image.width() as usize; + let height = image.height() as usize; + + comp.set_size(width, height); + let mut comp = comp.start_compress(Vec::new())?; // any io::Write will work + + comp.write_scanlines(image.to_rgba8().as_bytes())?; + + let jpeg_bytes = comp.finish()?; + + let file = std::fs::File::create(output_location)?; + let w = &mut BufWriter::new(file); + w.write_all(&jpeg_bytes)?; + Ok(()) +} diff --git a/packages/cli/src/opt/js.rs b/packages/cli/src/opt/js.rs new file mode 100644 index 000000000..976df3de6 --- /dev/null +++ b/packages/cli/src/opt/js.rs @@ -0,0 +1,87 @@ +use std::path::Path; + +use anyhow::Context; +use manganis_core::JsAssetOptions; + +use crate::opt::hash::hash_file_contents; + +pub(crate) fn process_js( + js_options: &JsAssetOptions, + source: &Path, + output_path: &Path, + bundle: bool, + esbuild_path: Option<&Path>, +) -> anyhow::Result<()> { + let minify = js_options.minified(); + let needs_esbuild = minify || bundle; + + if needs_esbuild { + if let Some(esbuild) = esbuild_path { + match run_esbuild(esbuild, source, output_path, bundle, minify) { + Ok(()) => return Ok(()), + Err(err) => { + tracing::error!( + "Failed to process JS with esbuild. Falling back to copy: {err}" + ); + } + } + } else { + tracing::warn!("esbuild binary path not set. Copying JS without processing."); + } + } + + // Fallback: copy unprocessed + let mut source_file = std::fs::File::open(source)?; + let mut writer = std::io::BufWriter::new(std::fs::File::create(output_path)?); + std::io::copy(&mut source_file, &mut writer).with_context(|| { + format!( + "Failed to write JS to output location: {}", + output_path.display() + ) + })?; + + Ok(()) +} + +/// Run esbuild to bundle and/or minify a JavaScript file. +fn run_esbuild( + esbuild: &Path, + source: &Path, + output_path: &Path, + bundle: bool, + minify: bool, +) -> anyhow::Result<()> { + let mut cmd = std::process::Command::new(esbuild); + cmd.arg(source); + cmd.arg(format!("--outfile={}", output_path.display())); + cmd.arg("--log-level=warning"); + + if bundle { + cmd.arg("--bundle"); + cmd.arg("--format=esm"); + } + + if minify { + cmd.arg("--minify"); + } + + tracing::debug!("Running esbuild: {:?}", cmd); + + let output = cmd.output().context("Failed to run esbuild")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + anyhow::bail!("esbuild failed: {stderr}"); + } + + Ok(()) +} + +pub(crate) fn hash_js( + _js_options: &JsAssetOptions, + source: &Path, + hasher: &mut impl std::hash::Hasher, + _bundle: bool, +) -> anyhow::Result<()> { + hash_file_contents(source, hasher) +} diff --git a/packages/cli-opt/src/json.rs b/packages/cli/src/opt/json.rs similarity index 100% rename from packages/cli-opt/src/json.rs rename to packages/cli/src/opt/json.rs diff --git a/packages/cli-opt/src/lib.rs b/packages/cli/src/opt/mod.rs similarity index 62% rename from packages/cli-opt/src/lib.rs rename to packages/cli/src/opt/mod.rs index 45c8c84f8..9dfee818b 100644 --- a/packages/cli-opt/src/lib.rs +++ b/packages/cli/src/opt/mod.rs @@ -1,13 +1,8 @@ -use anyhow::Context; -use manganis::AssetOptions; use manganis_core::BundledAsset; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashSet}; use std::path::{Path, PathBuf}; -use std::sync::{Arc, RwLock}; -mod build_info; mod css; mod file; mod folder; @@ -16,14 +11,14 @@ mod image; mod js; mod json; -pub use file::process_file_to; -pub use hash::add_hash_to_asset; +pub(crate) use file::process_file_to; +pub(crate) use hash::add_hash_to_asset; /// A manifest of all assets collected from dependencies /// /// This will be filled in primarily by incremental compilation artifacts. #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] -pub struct AssetManifest { +pub(crate) struct AssetManifest { /// Map of bundled asset name to the asset itself assets: BTreeMap>, } @@ -88,41 +83,4 @@ impl AssetManifest { .flat_map(|assets| assets.iter()) .filter(move |asset| seen.insert(asset.bundled_path())) } - - pub fn load_from_file(path: &Path) -> anyhow::Result { - let src = std::fs::read_to_string(path)?; - - serde_json::from_str(&src) - .with_context(|| format!("Failed to parse asset manifest from {path:?}\n{src}")) - } -} - -/// Optimize a list of assets in parallel -pub fn optimize_all_assets( - assets_to_transfer: Vec<(PathBuf, PathBuf, AssetOptions)>, - on_optimization_start: impl FnMut(&Path, &Path, &AssetOptions) + Sync + Send, - on_optimization_end: impl FnMut(&Path, &Path, &AssetOptions) + Sync + Send, -) -> anyhow::Result<()> { - let on_optimization_start = Arc::new(RwLock::new(on_optimization_start)); - let on_optimization_end = Arc::new(RwLock::new(on_optimization_end)); - assets_to_transfer - .par_iter() - .try_for_each(|(from, to, options)| { - { - let mut on_optimization_start = on_optimization_start.write().unwrap(); - on_optimization_start(from, to, options); - } - - let res = process_file_to(options, from, to); - if let Err(err) = res.as_ref() { - tracing::error!("Failed to copy asset {from:?}: {err}"); - } - - { - let mut on_optimization_end = on_optimization_end.write().unwrap(); - on_optimization_end(from, to, options); - } - - res.map(|_| ()) - }) } diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index d1c612c31..6a0637a11 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -439,6 +439,7 @@ impl Output { /// Render the current state of everything to the console screen pub fn render(&mut self, runner: &AppServer, server: &WebServer) { if !self.interactive { + self.emit_logs_to_tracing(); return; } @@ -1094,11 +1095,31 @@ impl Output { lines } + + /// Drains the current log queue to tracing so they still get printed even if the output is non-interactive + fn emit_logs_to_tracing(&mut self) { + // In non-interactive mode (CI, piped output), drain pending logs to tracing + // so they still appear in stdout/stderr instead of being silently dropped. + while let Some(log) = self.pending_logs.pop_back() { + let msg = match &log.content { + TraceContent::Text(s) => s.as_str(), + TraceContent::Cargo(d) => d.message.as_str(), + }; + match log.level { + Level::ERROR => tracing::error!("{msg}"), + Level::WARN => tracing::warn!("{msg}"), + Level::INFO => tracing::info!("{msg}"), + Level::DEBUG => tracing::debug!("{msg}"), + Level::TRACE => tracing::trace!("{msg}"), + } + } + } } impl std::ops::Drop for Output { fn drop(&mut self) { if !self.interactive { + self.emit_logs_to_tracing(); return; } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 2810e7d4f..714b725a7 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -300,7 +300,7 @@ impl AppServer { let mut changes: Vec<_> = event.into_iter().collect(); // Dequeue in bulk if we can, we might've received a lot of events in one go - while let Some(event) = self.watcher_rx.try_next().ok().flatten() { + while let Ok(event) = self.watcher_rx.try_recv() { changes.push(event); } @@ -1313,7 +1313,7 @@ impl AppServer { let depth = relative.components().count(); let is_better = best_match .as_ref() - .map_or(true, |(_, best_depth)| depth < *best_depth); + .is_none_or(|(_, best_depth)| depth < *best_depth); if is_better { best_match = Some((krate.name.replace('-', "_"), depth)); } diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 7f58defb0..e5429d5a4 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -26,7 +26,7 @@ use futures_util::{ StreamExt, }; use hyper::HeaderMap; -use rustls::crypto::{aws_lc_rs::default_provider, CryptoProvider}; +use rustls::crypto::{ring::default_provider, CryptoProvider}; use serde::{Deserialize, Serialize}; use std::{ convert::Infallible, diff --git a/packages/cli/src/wasm_opt.rs b/packages/cli/src/wasm_opt.rs index 477a77b25..9a75a5c6a 100644 --- a/packages/cli/src/wasm_opt.rs +++ b/packages/cli/src/wasm_opt.rs @@ -135,15 +135,15 @@ async fn find_latest_wasm_opt_download_url() -> anyhow::Result { // Find the platform identifier based on the current OS and architecture // hardcoded for now to get around github api rate limits if cfg!(all(target_os = "windows", target_arch = "x86_64")) { - return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-windows.tar.gz".to_string()); + return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_127/binaryen-version_127-x86_64-windows.tar.gz".to_string()); } else if cfg!(all(target_os = "linux", target_arch = "x86_64")) { - return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-linux.tar.gz".to_string()); + return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_127/binaryen-version_127-x86_64-linux.tar.gz".to_string()); } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) { - return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-aarch64-linux.tar.gz".to_string()); + return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_127/binaryen-version_127-aarch64-linux.tar.gz".to_string()); } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) { - return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-x86_64-macos.tar.gz".to_string()); + return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_127/binaryen-version_127-x86_64-macos.tar.gz".to_string()); } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) { - return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_123/binaryen-version_123-arm64-macos.tar.gz".to_string()); + return Ok("https://github.com/WebAssembly/binaryen/releases/download/version_127/binaryen-version_127-arm64-macos.tar.gz".to_string()); }; let url = "https://api.github.com/repos/WebAssembly/binaryen/releases/latest"; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index bc1fced02..646160018 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -479,7 +479,7 @@ impl VirtualDom { /// Queue any pending events fn queue_events(&mut self) { // Prevent a task from deadlocking the runtime by repeatedly queueing itself - while let Ok(Some(msg)) = self.rx.try_next() { + while let Ok(msg) = self.rx.try_recv() { match msg { SchedulerMsg::Immediate(id) => self.mark_dirty(id), SchedulerMsg::TaskNotified(task) => self.mark_task_dirty(Task::from_id(task)), diff --git a/packages/desktop/src/config.rs b/packages/desktop/src/config.rs index ec570cdc3..d1281d8c5 100644 --- a/packages/desktop/src/config.rs +++ b/packages/desktop/src/config.rs @@ -131,9 +131,16 @@ impl Config { self } - /// set the directory where data will be stored in release mode. + /// Set the directory where WebView2 stores its user data (cookies, cache, IndexedDB, etc.). /// - /// > Note: This **must** be set when bundling on Windows. + /// If not set, Dioxus automatically chooses a sensible default: + /// - **Windows:** `%LOCALAPPDATA%/` — this avoids the WebView2 default of + /// placing data next to the executable, which fails in read-only locations like + /// `Program Files` or on certain drive types (e.g. ReFS dev drives). + /// - **macOS/Linux:** managed by WebKit automatically. + /// + /// You typically only need this if you want multiple apps to share a WebView2 profile + /// or need the data in a specific location for compliance/IT policy reasons. pub fn with_data_directory(mut self, path: impl Into) -> Self { self.data_dir = Some(path.into()); self diff --git a/packages/desktop/src/edits.rs b/packages/desktop/src/edits.rs index 8112a17bf..53b82fbdf 100644 --- a/packages/desktop/src/edits.rs +++ b/packages/desktop/src/edits.rs @@ -231,6 +231,7 @@ impl EditWebsocket { let hex_encoded_server_key = encode_key_string(¤t_server_location.server_key); let mut location = None; + #[allow(clippy::result_large_err)] let on_request = |req: &Request, res| { // Try to parse the webview id and key from the path let path = req.uri().path(); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index d4051bf4b..22b3cb6c8 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -3,6 +3,7 @@ #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] #![deny(missing_docs)] #![cfg_attr(docsrs, feature(doc_cfg))] +#![allow(unexpected_cfgs)] mod android_sync_lock; mod app; diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 38421c92c..e1ec136d2 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -260,12 +260,23 @@ impl WebviewInstance { unsafe { let window: id = window.ns_window() as id; - #[allow(unexpected_cfgs)] let _: () = msg_send![window, setCollectionBehavior: NSWindowCollectionBehavior::NSWindowCollectionBehaviorManaged]; } } - let mut web_context = WebContext::new(cfg.data_dir.clone()); + let mut web_context = WebContext::new(cfg.data_dir.clone().or_else(|| { + // On Windows, WebView2 defaults to storing its data next to the executable. + // This fails on certain drives (e.g. ReFS dev drives, Program Files) where the + // directory may not be writable. Fall back to %LOCALAPPDATA%/ automatically. + if cfg!(windows) { + let exe = std::env::current_exe().ok()?; + let name = exe.file_stem()?.to_str()?; + let local_app_data = std::env::var("LOCALAPPDATA").ok()?; + Some(std::path::PathBuf::from(local_app_data).join(name)) + } else { + None + } + })); let edit_queue = shared.websocket.create_queue(); let asset_handlers = AssetHandlerRegistry::new(); let edits = WebviewEdits::new(dom.runtime(), edit_queue.clone()); diff --git a/packages/dx-wire-format/src/lib.rs b/packages/dx-wire-format/src/lib.rs index de8823614..e3e632065 100644 --- a/packages/dx-wire-format/src/lib.rs +++ b/packages/dx-wire-format/src/lib.rs @@ -94,6 +94,7 @@ pub enum BuildStage { current: usize, total: usize, krate: String, + fresh: bool, }, RunningBindgen, SplittingBundle, diff --git a/packages/playwright-tests/playwright.config.js b/packages/playwright-tests/playwright.config.js index 075f95f1b..ef154c8eb 100644 --- a/packages/playwright-tests/playwright.config.js +++ b/packages/playwright-tests/playwright.config.js @@ -1,251 +1,112 @@ // @ts-check const { defineConfig, devices } = require("@playwright/test"); +const { execSync } = require("child_process"); +const fs = require("fs"); const path = require("path"); -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); +// Copy a directory to a temp location for tests that modify source files (hot-patch tests). +// Done in JS so the webServer command is a simple `dx serve` with a `cwd`, which keeps +// stdout/stderr piping clean on both Unix and Windows cmd.exe. +function copyToTemp(src, dest) { + const absSrc = path.resolve(__dirname, src); + const absDest = path.resolve(__dirname, dest); + fs.rmSync(absDest, { recursive: true, force: true }); + fs.cpSync(absSrc, absDest, { recursive: true }); +} -let webServer = []; -let grep = undefined; -let grepInvert = undefined; +const repoRoot = path.resolve(__dirname, "..", ".."); +const dx = path.join(repoRoot, "target", "release", process.platform === "win32" ? "dx.exe" : "dx"); + +const ALL_SERVERS = [ + { + specs: ["liveview.spec.js"], port: 3030, + command: "cargo run --package dioxus-playwright-liveview-test --bin dioxus-playwright-liveview-test", + env: { CARGO_TERM_PROGRESS_WHEN: "never" } + }, + { specs: ["web.spec.js"], port: 9990, cwd: "web", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 9990` }, + { specs: ["web-routing.spec.js"], port: 2020, cwd: "web-routing", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 2020` }, + { specs: ["web-hash-routing.spec.js"], port: 2021, cwd: "web-hash-routing", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 2021` }, + { specs: ["fullstack.spec.js"], port: 3333, cwd: "fullstack", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 3333 --release` }, + { specs: ["fullstack-errors.spec.js"], port: 3232, cwd: "fullstack-errors", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 3232` }, + { specs: ["fullstack-mounted.spec.js"], port: 7777, cwd: "fullstack-mounted", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 7777` }, + { specs: ["fullstack-routing.spec.js"], port: 8888, cwd: "fullstack-routing", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 8888` }, + { specs: ["fullstack-spread.spec.js"], port: 7980, cwd: "fullstack-spread", command: `${dx} run --verbose --force-sequential --web --addr 127.0.0.1 --port 7980` }, + { specs: ["fullstack-hydration-order.spec.js"], port: 7979, cwd: "fullstack-hydration-order", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 7979` }, + { specs: ["fullstack-error-codes.spec.js"], port: 8124, cwd: "fullstack-error-codes", command: `${dx} run --force-sequential --addr 127.0.0.1 --port 8124` }, + { specs: ["nested-suspense.spec.js", "nested-suspense-no-js.spec.js"], port: 5050, cwd: "nested-suspense", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 5050` }, + { specs: ["nested-suspense-ssg.spec.js"], port: 6060, cwd: "nested-suspense", command: `${dx} run --bin nested-suspense-ssg --force-sequential --web --ssg --addr 127.0.0.1 --port 6060` }, + { specs: ["suspense-carousel.spec.js"], port: 4040, cwd: "suspense-carousel", command: `${dx} run --force-sequential --web --addr 127.0.0.1 --port 4040` }, + { specs: ["cli-optimization.spec.js"], port: 8989, cwd: "cli-optimization", command: `${dx} run --addr 127.0.0.1 --port 8989` }, + { specs: ["wasm-split.spec.js"], port: 8001, cwd: "wasm-split-harness", command: `${dx} run --bin wasm-split-harness --web --addr 127.0.0.1 --port 8001 --wasm-split --profile wasm-split-release` }, + { specs: ["default-features-disabled.spec.js"], port: 8002, cwd: "default-features-disabled", command: `${dx} run --force-sequential --addr 127.0.0.1 --port 8002` }, + { specs: ["web-patch.spec.js"], port: 9980, cwd: "web-hot-patch-temp", setup: () => copyToTemp("web-hot-patch", "web-hot-patch-temp"), command: `${dx} serve --verbose --force-sequential --web --addr 127.0.0.1 --port 9980 --hot-patch --exit-on-error` }, + { specs: ["web-patch-fullstack.spec.js"], port: 9981, cwd: "web-hot-patch-fullstack-temp", setup: () => copyToTemp("web-hot-patch-fullstack", "web-hot-patch-fullstack-temp"), command: `${dx} serve --verbose --force-sequential --web --addr 127.0.0.1 --port 9981 --hot-patch --exit-on-error` }, +]; + if (process.platform === "win32") { - webServer = [ - { - cwd: path.join(process.cwd(), "windows-headless"), - command: - "cargo run --package dioxus-cli --release -- run --force-sequential", - port: 8787, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - ]; - grep = /windows/; -} else { - webServer = [ - { - command: - "cargo run --package dioxus-playwright-liveview-test --bin dioxus-playwright-liveview-test", - port: 3030, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "web"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 9990', - port: 9990, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "web-routing"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 2020', - port: 2020, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "web-hash-routing"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 2021', - port: 2021, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "fullstack"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 3333 --release', - port: 3333, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "fullstack-errors"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 3232', - port: 3232, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "fullstack-mounted"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 7777', - port: 7777, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "fullstack-spread"), - command: - 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 7980', - port: 7980, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "fullstack-routing"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 8888', - port: 8888, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "suspense-carousel"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 4040', - port: 4040, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "nested-suspense"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 5050', - port: 5050, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "nested-suspense"), - command: - 'cargo run --package dioxus-cli --release -- run --bin nested-suspense-ssg --force-sequential --web --ssg --addr "127.0.0.1" --port 6060', - port: 6060, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "fullstack-hydration-order"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --web --addr "127.0.0.1" --port 7979', - port: 7979, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "cli-optimization"), - command: - 'cargo run --package dioxus-cli --release -- run --addr "127.0.0.1" --port 8989', - port: 8989, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "wasm-split-harness"), - command: - 'cargo run --package dioxus-cli --release -- run --bin wasm-split-harness --web --addr "127.0.0.1" --port 8001 --wasm-split --profile wasm-split-release', - port: 8001, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "default-features-disabled"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --addr "127.0.0.1" --port 8002', - port: 8002, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "barebones-template"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --addr "127.0.0.1" --port 8123', - port: 8123, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - command: - 'rm -rf ./web-hot-patch-temp && cp -r ./web-hot-patch ./web-hot-patch-temp && cd web-hot-patch-temp && cargo run --manifest-path ../../cli/Cargo.toml --release -- serve --verbose --force-sequential --web --addr "127.0.0.1" --port 9980 --hot-patch --exit-on-error', - port: 9980, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - command: - 'rm -rf ./web-hot-patch-fullstack-temp && cp -r ./web-hot-patch-fullstack ./web-hot-patch-fullstack-temp && cd web-hot-patch-fullstack-temp && cargo run --manifest-path ../../cli/Cargo.toml --release -- serve --verbose --force-sequential --web --addr "127.0.0.1" --port 9981 --hot-patch --exit-on-error', - port: 9981, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - { - cwd: path.join(process.cwd(), "fullstack-error-codes"), - command: - 'cargo run --package dioxus-cli --release -- run --force-sequential --addr "127.0.0.1" --port 8124', - port: 8124, - timeout: 50 * 60 * 1000, - reuseExistingServer: !process.env.CI, - stdout: "pipe", - }, - ]; + ALL_SERVERS.push({ specs: ["windows.spec.js"], port: 8787, cwd: "windows-headless", command: `${dx} run --force-sequential` }); + ALL_SERVERS.push({ specs: ["windows-hotpatch-fullstack.spec.js"], port: 8788, cwd: "windows-hotpatch-fullstack-temp", setup: () => copyToTemp("windows-hotpatch-fullstack", "windows-hotpatch-fullstack-temp"), command: `${dx} serve --verbose --force-sequential --hot-patch --exit-on-error` }); +} + +// Determine which servers to start based on spec files in argv and platform. +// On Windows only windows.spec.js runs (grep filter below), so don't bother +// starting servers for tests that will never execute — they just crash and +// pollute the logs. +const specArgs = process.argv.filter((a) => a.endsWith(".spec.js")).map((a) => path.basename(a)); +const isWindows = process.platform === "win32"; +const activeServers = specArgs.length > 0 + ? ALL_SERVERS.filter((s) => s.specs.some((spec) => specArgs.includes(spec))) + : isWindows + ? ALL_SERVERS.filter((s) => s.specs.some((spec) => spec.includes("windows"))) + : ALL_SERVERS.filter((s) => !s.specs.some((spec) => spec.includes("windows"))); + +// Run any setup functions (e.g. copying source to temp dirs for hot-patch tests) +// Only happens on initialize +// +// Build dx once: main process builds and sets env var; workers inherit it and skip. +// todo: implement proper fixtures https://www.youtube.com/watch?v=3i6cJUFO_m4 +if (!process.env._DX_BUILT) { + for (const s of activeServers) { + if (s.setup) s.setup(); + } + + execSync("cargo build --package dioxus-cli --release", { + cwd: repoRoot, + env: { ...process.env, CARGO_TERM_PROGRESS_WHEN: "never" }, + stdio: "inherit", + }); - grepInvert = /windows/; + process.env._DX_BUILT = "1"; } -/** - * @see https://playwright.dev/docs/test-configuration - */ module.exports = defineConfig({ testDir: ".", - /* Run tests in files in parallel */ fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, - /* Retry on CI only */ retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: "html", - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://127.0.0.1:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "retain-on-failure", - // Increase the timeout for navigations to give dx time to build the project navigationTimeout: 50 * 60 * 1000, }, - timeout: 50 * 60 * 1000, - - /* Configure projects for major browsers */ + webServer: activeServers.map((s) => ({ + command: s.command, + port: s.port, + cwd: s.cwd ? path.join(__dirname, s.cwd) : __dirname, + timeout: 50 * 60 * 1000, + reuseExistingServer: !process.env.CI, + stdout: "pipe", + stderr: "pipe", + env: s.env, + })), projects: [ { name: "chromium", - grep, - grepInvert, + grep: process.platform === "win32" ? /windows/ : undefined, + grepInvert: process.platform !== "win32" ? /windows/ : undefined, use: { ...devices["Desktop Chrome"] }, }, ], - - /* Run your local dev server before starting the tests */ - webServer, }); diff --git a/packages/playwright-tests/web-hot-patch/Cargo.toml b/packages/playwright-tests/web-hot-patch/Cargo.toml index 4e1aa40ce..9973da751 100644 --- a/packages/playwright-tests/web-hot-patch/Cargo.toml +++ b/packages/playwright-tests/web-hot-patch/Cargo.toml @@ -8,7 +8,7 @@ publish = false [dependencies] dioxus = { path = "../../dioxus", features = ["web"] } -wasm-bindgen = { version = "0.2.100" } +wasm-bindgen = { version = "0.2.114" } web-sys = { version = "*", features = ["MouseEvent"] } [workspace] diff --git a/packages/playwright-tests/windows-hotpatch-fullstack.spec.js b/packages/playwright-tests/windows-hotpatch-fullstack.spec.js new file mode 100644 index 000000000..95937c198 --- /dev/null +++ b/packages/playwright-tests/windows-hotpatch-fullstack.spec.js @@ -0,0 +1,78 @@ +// @ts-check +const { test: base, expect } = require("@playwright/test"); +const fs = require("fs"); + +const test = base.extend({ + browser: async ({ playwright }, use) => { + const browser = await playwright.chromium.connectOverCDP("http://127.0.0.1:8788"); + await use(browser); + await browser.close(); + }, + context: async ({ browser }, use) => { + const context = browser.contexts()[0]; + await use(context); + }, + page: async ({ context }, use) => { + const page = context.pages()[0]; + await use(page); + }, +}); + +const hotPatchTimeout = { timeout: 1000 * 60 * 2 }; + +test("windows fullstack hotpatch", async ({ page }) => { + const mainPath = "windows-hotpatch-fullstack-temp/src/main.rs"; + const stylePath = "windows-hotpatch-fullstack-temp/assets/style.css"; + let mainContent = fs.readFileSync(mainPath, "utf8"); + let styleContent = fs.readFileSync(stylePath, "utf8"); + + // Reset any changes from prior runs. + mainContent = mainContent.replace(/Ok\(\s*2\s*\)/g, "Ok(1)"); + mainContent = mainContent.replace("Click button! Count:", "Click me! Count:"); + fs.writeFileSync(mainPath, mainContent); + styleContent = styleContent.replace("background-color: blue;", "background-color: red;"); + fs.writeFileSync(stylePath, styleContent); + + // ** Verify the initial fat build is working ** + const main = page.locator("#main"); + await expect(main).toContainText("Click me! Count: 0"); + + // Click and verify the server function works. + const button = page.locator("button#increment-button"); + await button.click(); + await page.waitForTimeout(1000); + await expect(main).toContainText("Click me! Count: 1"); + + // Verify initial CSS is applied. + await expect(main).toHaveCSS("background-color", "rgb(255, 0, 0)"); + + // ** Hot patch: change server fn return value and button text ** + const updatedContent = mainContent + .replace(/Ok\(\s*1\s*\)/g, "Ok(2)") + .replace("Click me! Count:", "Click button! Count:"); + fs.writeFileSync(mainPath, updatedContent); + + // Wait for the hot patch to apply (text should update). + await expect(main).toContainText("Click button! Count: 1", hotPatchTimeout); + + // Click and verify the new increment amount from the patched server fn. + await button.click(); + await page.waitForTimeout(5000); + await expect(main).toContainText("Click button! Count: 3"); + + // ** Hot patch CSS ** + const updatedStyle = styleContent.replace( + "background-color: red;", + "background-color: blue;" + ); + fs.writeFileSync(stylePath, updatedStyle); + + await page.waitForTimeout(1000); + + // Wait for the CSS hot patch to apply. + await expect(main).toHaveCSS( + "background-color", + "rgb(0, 0, 255)", + hotPatchTimeout + ); +}); diff --git a/packages/playwright-tests/windows-hotpatch-fullstack/Cargo.toml b/packages/playwright-tests/windows-hotpatch-fullstack/Cargo.toml new file mode 100644 index 000000000..16784c5f9 --- /dev/null +++ b/packages/playwright-tests/windows-hotpatch-fullstack/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "windows-hotpatch-fullstack" +edition = "2021" +version = "0.0.1" +publish = false + +[dependencies] +dioxus = { path = "../../dioxus", features = ["fullstack"] } + +[features] +desktop = ["dioxus/desktop"] +server = ["dioxus/server"] + +[workspace] diff --git a/packages/playwright-tests/windows-hotpatch-fullstack/assets/style.css b/packages/playwright-tests/windows-hotpatch-fullstack/assets/style.css new file mode 100644 index 000000000..8dcca08c7 --- /dev/null +++ b/packages/playwright-tests/windows-hotpatch-fullstack/assets/style.css @@ -0,0 +1,3 @@ +#main { + background-color: red; +} diff --git a/packages/playwright-tests/windows-hotpatch-fullstack/src/main.rs b/packages/playwright-tests/windows-hotpatch-fullstack/src/main.rs new file mode 100644 index 000000000..ba9ba4f3c --- /dev/null +++ b/packages/playwright-tests/windows-hotpatch-fullstack/src/main.rs @@ -0,0 +1,38 @@ +use dioxus::prelude::*; + +const CSS: Asset = asset!("/assets/style.css"); + +fn app() -> Element { + let mut num = use_signal(|| 0); + + rsx! { + document::Link { + href: CSS, + rel: "stylesheet", + } + button { + id: "increment-button", + onclick: move |_| async move { + let increment_amount = get_count().await.unwrap(); + *num.write() += increment_amount; + }, + "Click me! Count: {num}" + } + } +} + +#[post("/api/get_count")] +async fn get_count() -> Result { + Ok(1) +} + +fn main() { + let builder = dioxus::LaunchBuilder::new(); + + #[cfg(feature = "desktop")] + let builder = builder.with_cfg( + dioxus::desktop::Config::new().with_windows_browser_args("--remote-debugging-port=8788"), + ); + + builder.launch(app); +} diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index a93774472..71f00087f 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -76,7 +76,7 @@ impl Memo { spawn_isomorphic(async move { while rx.next().await.is_some() { // Remove any pending updates - while rx.try_next().is_ok() {} + while rx.try_recv().is_ok() {} memo.recompute(); } }); diff --git a/packages/stores-macro/src/extend.rs b/packages/stores-macro/src/extend.rs index 622b7c0f6..63179b679 100644 --- a/packages/stores-macro/src/extend.rs +++ b/packages/stores-macro/src/extend.rs @@ -17,9 +17,9 @@ pub(crate) fn extend_store(args: ExtendArgs, mut input: ItemImpl) -> syn::Result let visibility = args .visibility .unwrap_or_else(|| syn::Visibility::Inherited); - if input.trait_.is_some() { + if let Some(input_trait) = input.trait_.as_ref() { return Err(syn::Error::new_spanned( - input.trait_.unwrap().1, + input_trait.1.clone(), "The `store` attribute can only be used on `impl Store { ... }` blocks, not trait implementations.", )); } @@ -86,7 +86,8 @@ pub(crate) fn extend_store(args: ExtendArgs, mut input: ItemImpl) -> syn::Result let trait_definition = impl_to_trait_body(&extension_name, &input, &visibility)?; // Reformat the type to be generic over the lens - input.self_ty = Box::new(parse_quote!(#store_path<#item, #lens_generic>)); + input.self_ty = parse_quote!(#store_path<#item, #lens_generic>); + // Change the standalone impl block to a trait impl block let (_, trait_generics, _) = input.generics.split_for_impl(); input.trait_ = Some((