From 59ea7341607af5ec7660c5a0f9dc4d743008ee93 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 11:10:17 +0800 Subject: [PATCH 01/12] fix(ci): align nightly build contracts --- .github/workflows/ci.yml | 39 ++++++++++---- .github/workflows/nightly.yml | 12 +++-- scripts/check-github-config.test.mjs | 78 +++++++++++++++++++++++++++- scripts/release-channel.test.mjs | 42 ++++++++++++++- 4 files changed, 153 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86232051c4..9a6315cadb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: exit "$rc" - name: Verify release and version-generation contracts - run: node --test scripts/tauri-release-manifest.test.mjs scripts/linux-binaries-manifest.test.mjs scripts/version-generation.test.mjs + run: node --test scripts/release-channel.test.mjs scripts/tauri-release-manifest.test.mjs scripts/linux-binaries-manifest.test.mjs scripts/version-generation.test.mjs - name: Verify minisign download fallback run: | @@ -121,6 +121,8 @@ jobs: cache_key: ubuntu - os: macos-15 cache_key: macos + - os: windows-latest + cache_key: windows steps: - uses: actions/checkout@v5 @@ -155,6 +157,13 @@ jobs: if: runner.os == 'Linux' run: cargo test --locked -p bitfun-cli -p bitfun-acp -p bitfun-agent-runtime + # ConPTY is a Windows-only execution path. Keep its interactive contracts + # serialized: concurrent native PTYs contend on hosted runners and used to + # race action delivery, TUI restoration, and output observation in Nightly. + - name: Run Windows CLI terminal contracts + if: runner.os == 'Windows' + run: cargo test --locked -p bitfun-cli --test terminal_process_contracts -- --test-threads=1 + - name: Run SDK Host tests if: runner.os == 'Linux' run: cargo test --locked -p bitfun-sdk-host -p bitfun-sdk-host-app @@ -187,6 +196,12 @@ jobs: steps: - uses: actions/checkout@v5 + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: package.json + package-manager-cache: false + # Tauri code generation only requires its configured resource roots to # exist during check/test; distributable assets remain frontend-build's owner. - name: Create Tauri resource directories @@ -235,6 +250,16 @@ jobs: save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} cache-on-failure: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + # Nightly and beta project a prerelease version before their nested + # `cargo --locked` build hooks run. Exercise that state transition in the + # ordinary CI OS matrix so a green commit cannot carry a stale lockfile + # contract into packaging. + - name: Project CI build version + run: node scripts/set-build-version.mjs --version 0.0.0-nightly.ci + + - name: Verify projected Cargo metadata + run: cargo metadata --locked --no-deps + # rust-cache prunes the workspace target directory before saving it, so # native libraries stored under target need an independent cache lifecycle. - name: Restore Sherpa native libraries @@ -290,16 +315,10 @@ jobs: run: cargo test --locked -p bitfun-ai-adapters --features subscription-auth --lib subscription_auth # File watching is backed by a different OS API on every platform - # (ReadDirectoryChangesW / FSEvents / inotify), so watch registration - # regressions surface per-OS. The suite is behind a non-default feature - # and would otherwise never run anywhere. - # - # macOS is excluded: this suite already fails there before any of this - # branch's changes (the debounce/atomic-rename contracts time out under - # FSEvents coalescing), which is worth fixing separately rather than - # blocking unrelated work. + # (ReadDirectoryChangesW / FSEvents / inotify), so watch registration, + # debounce, and atomic-rename regressions must stay in the full OS matrix. + # The suite is behind a non-default feature and would otherwise never run. - name: Run file watch contract tests - if: runner.os != 'macOS' run: cargo test --locked -p bitfun-services-integrations --no-default-features --features file-watch --test file_watch_contracts # Search tools resolve paths and symlinks directly, which also differs diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 647d1c759c..9865d06d5b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -197,15 +197,16 @@ jobs: echo "package.json version: $(jq -r '.version' package.json)" echo "Cargo.toml version: $(grep 'x-release-please-version' Cargo.toml)" + - name: Verify projected Cargo metadata + run: cargo metadata --locked --no-deps + - name: Run Windows CLI terminal contracts if: runner.os == 'Windows' env: CARGO_INCREMENTAL: "0" CARGO_PROFILE_DEV_DEBUG: "0" CARGO_PROFILE_TEST_DEBUG: "0" - run: | - cargo generate-lockfile - cargo test --locked -p bitfun-cli --test terminal_process_contracts + run: cargo test --locked -p bitfun-cli --test terminal_process_contracts -- --test-threads=1 - name: Build desktop app run: ${{ matrix.platform.build_command }} @@ -347,8 +348,9 @@ jobs: shell: bash env: IMAGE_DIGEST: ${{ steps.relay-image.outputs.digest }} - run: bash scripts/relay/smoke-image.sh \ - "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" + run: | + bash scripts/relay/smoke-image.sh \ + "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" - name: Generate signed Relay image descriptor shell: bash diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index 75d411b6b7..1b323f5327 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -251,6 +251,16 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => assert.equal(cache?.with?.['cache-on-failure'], trustedMain); } + const cliJob = workflow.jobs['cli-test']; + assert.ok( + cliJob.strategy.matrix.include.some((entry) => entry.os === 'windows-latest'), + 'Windows ConPTY contracts must run before Nightly', + ); + assert.equal( + cliJob.steps.find((step) => step.name === 'Run Windows CLI terminal contracts')?.run, + 'cargo test --locked -p bitfun-cli --test terminal_process_contracts -- --test-threads=1', + ); + const rustCache = rustJob.steps.find((step) => step.uses?.startsWith('swatinem/rust-cache@'), ); @@ -304,6 +314,25 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => const commandByStep = new Map( rustJob.steps.map((step) => [step.name, step.run]), ); + const projectVersion = rustJob.steps.find( + (step) => step.name === 'Project CI build version', + ); + const verifyMetadata = rustJob.steps.find( + (step) => step.name === 'Verify projected Cargo metadata', + ); + assert.equal( + projectVersion?.run, + 'node scripts/set-build-version.mjs --version 0.0.0-nightly.ci', + ); + assert.equal(verifyMetadata?.run, 'cargo metadata --locked --no-deps'); + assert.ok( + rustJob.steps.indexOf(projectVersion) < rustJob.steps.indexOf(checkCompilation), + 'CI must project the build version before the locked workspace check', + ); + assert.ok( + rustJob.steps.indexOf(projectVersion) < rustJob.steps.indexOf(verifyMetadata), + 'CI must validate Cargo.lock after projecting the build version', + ); assert.equal( commandByStep.get('Run subscription authentication tests'), 'cargo test --locked -p bitfun-ai-adapters --features subscription-auth --lib subscription_auth', @@ -316,10 +345,18 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => installerCheck?.run, 'cargo check --manifest-path BitFun-Installer/src-tauri/Cargo.toml', ); + const fileWatchContracts = rustJob.steps.find( + (step) => step.name === 'Run file watch contract tests', + ); assert.equal( - commandByStep.get('Run file watch contract tests'), + fileWatchContracts?.run, 'cargo test --locked -p bitfun-services-integrations --no-default-features --features file-watch --test file_watch_contracts', ); + assert.equal( + fileWatchContracts?.if, + undefined, + 'file-watch contracts must exercise FSEvents on macOS', + ); assert.equal( commandByStep.get('Run search tool tests'), 'cargo test --locked -p tool-runtime --lib search::', @@ -464,7 +501,7 @@ foreach ($case in $cases) { assert.equal(truthTable.status, 0, `${truthTable.stdout}${truthTable.stderr}`); }); -test('generates web API bindings before nightly web type-check', () => { +test('nightly validates generated inputs and projected lockfiles before packaging', () => { const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), ); @@ -476,6 +513,15 @@ test('generates web API bindings before nightly web type-check', () => { const typeCheckIndex = steps.findIndex( (step) => step.name === 'Type-check web UI', ); + const patchIndex = steps.findIndex( + (step) => step.name === 'Patch nightly version', + ); + const metadataIndex = steps.findIndex( + (step) => step.name === 'Verify projected Cargo metadata', + ); + const buildIndex = steps.findIndex( + (step) => step.name === 'Build desktop app', + ); assert.notEqual(generationIndex, -1); assert.notEqual(typeCheckIndex, -1); @@ -487,6 +533,34 @@ test('generates web API bindings before nightly web type-check', () => { generationIndex < typeCheckIndex, 'nightly must generate web API bindings before type-checking the web UI', ); + assert.ok( + typeCheckIndex < patchIndex && patchIndex < metadataIndex && metadataIndex < buildIndex, + 'nightly must verify the projected lockfile before nested locked build hooks run', + ); + assert.equal(steps[metadataIndex].run, 'cargo metadata --locked --no-deps'); + assert.equal( + steps.some((step) => step.run?.includes('cargo generate-lockfile')), + false, + 'nightly must not hide stale committed lockfiles by regenerating them ad hoc', + ); + assert.equal( + steps.find((step) => step.name === 'Run Windows CLI terminal contracts')?.run, + 'cargo test --locked -p bitfun-cli --test terminal_process_contracts -- --test-threads=1', + ); +}); + +test('nightly Relay smoke test passes an exact image reference', () => { + const workflow = yaml.parse( + readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), + ); + const smoke = workflow.jobs['publish-nightly'].steps.find( + (step) => step.name === 'Smoke-test published Relay image on both platforms', + ); + + assert.equal( + smoke?.run, + 'bash scripts/relay/smoke-image.sh \\\n "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}"\n', + ); }); test('passes the verification key when signing the versioned Windows installer', () => { diff --git a/scripts/release-channel.test.mjs b/scripts/release-channel.test.mjs index 02bed3d72e..4f265c8927 100644 --- a/scripts/release-channel.test.mjs +++ b/scripts/release-channel.test.mjs @@ -1,4 +1,5 @@ import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; @@ -66,13 +67,44 @@ test('build version projection updates every release-owned version file', () => mkdirSync(path.dirname(file), { recursive: true }); writeFileSync(file, JSON.stringify({ version: '1.0.0', packages: { '': { version: '1.0.0' } } })); } - writeFixture(root, 'Cargo.toml', 'version = "1.0.0" # x-release-please-version\n'); + writeFixture( + root, + 'Cargo.toml', + `[package] +name = "build-version-fixture" +version = "1.0.0" # x-release-please-version +edition = "2021" + +[dependencies] +fixture-dependency = { path = "fixture-dependency" } + +[workspace] +members = [] +exclude = ["fixture-dependency", "src/apps/relay-server", "BitFun-Installer/src-tauri"] +`, + ); + writeFixture(root, 'src/lib.rs', 'pub fn fixture() {}\n'); + writeFixture( + root, + 'fixture-dependency/Cargo.toml', + `[package] +name = "fixture-dependency" +version = "1.0.0" +edition = "2021" +`, + ); + writeFixture(root, 'fixture-dependency/src/lib.rs', 'pub fn fixture_dependency() {}\n'); writeFixture( root, 'src/apps/relay-server/Cargo.toml', 'version = "1.0.0" # x-release-please-version\n', ); writeFixture(root, 'BitFun-Installer/src-tauri/Cargo.toml', 'version = "1.0.0"\n'); + const initialLock = spawnSync('cargo', ['generate-lockfile'], { + cwd: root, + encoding: 'utf8', + }); + assert.equal(initialLock.status, 0, initialLock.stderr); setBuildVersion(root, '1.1.0-beta.2'); @@ -86,6 +118,14 @@ test('build version projection updates every release-owned version file', () => readFileSync(path.join(root, 'src/apps/relay-server/Cargo.toml'), 'utf8'), /1\.1\.0-beta\.2/, ); + const lockfile = readFileSync(path.join(root, 'Cargo.lock'), 'utf8'); + assert.match(lockfile, /name = "build-version-fixture"\nversion = "1\.1\.0-beta\.2"/); + assert.match(lockfile, /name = "fixture-dependency"\nversion = "1\.0\.0"/); + const lockedMetadata = spawnSync('cargo', ['metadata', '--locked', '--no-deps'], { + cwd: root, + encoding: 'utf8', + }); + assert.equal(lockedMetadata.status, 0, lockedMetadata.stderr); }); function writeFixture(root, relative, content) { From e7cf46437e130f106cc462f68f0662c88ea9f1ed Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 15:28:30 +0800 Subject: [PATCH 02/12] fix(ci): scope nightly publishing to repository --- .github/workflows/nightly.yml | 26 +++++++++--------- scripts/check-github-config.test.mjs | 40 +++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 9865d06d5b..acc211f9e8 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -325,7 +325,9 @@ jobs: shell: bash env: NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} - run: echo "asset_version=${NIGHTLY_VERSION%%+*}" >>"$GITHUB_OUTPUT" + run: | + echo "asset_version=${NIGHTLY_VERSION%%+*}" >>"$GITHUB_OUTPUT" + echo "image=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/bitfun-relay-server" >>"$GITHUB_OUTPUT" - name: Build and push multi-platform Relay image id: relay-image @@ -341,8 +343,8 @@ jobs: VERSION=${{ needs.check-changes.outputs.nightly_version }} REVISION=${{ github.sha }} tags: | - ghcr.io/gcwing/bitfun-relay-server:${{ env.NIGHTLY_TAG }} - ghcr.io/gcwing/bitfun-relay-server:${{ steps.nightly-image-meta.outputs.asset_version }} + ${{ steps.nightly-image-meta.outputs.image }}:${{ env.NIGHTLY_TAG }} + ${{ steps.nightly-image-meta.outputs.image }}:${{ steps.nightly-image-meta.outputs.asset_version }} - name: Smoke-test published Relay image on both platforms shell: bash @@ -350,7 +352,7 @@ jobs: IMAGE_DIGEST: ${{ steps.relay-image.outputs.digest }} run: | bash scripts/relay/smoke-image.sh \ - "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" + "${{ steps.nightly-image-meta.outputs.image }}@${IMAGE_DIGEST}" - name: Generate signed Relay image descriptor shell: bash @@ -365,7 +367,7 @@ jobs: asset_version="${NIGHTLY_VERSION%%+*}" [[ "${IMAGE_DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]] docker buildx imagetools inspect \ - "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" \ + "${{ steps.nightly-image-meta.outputs.image }}@${IMAGE_DIGEST}" \ --raw >relay-image-manifest.json jq -e ' [.manifests[].platform | .os + "/" + .architecture] as $platforms @@ -373,7 +375,7 @@ jobs: and ($platforms | index("linux/arm64")) != null ' relay-image-manifest.json >/dev/null jq -n \ - --arg image "ghcr.io/gcwing/bitfun-relay-server" \ + --arg image "${{ steps.nightly-image-meta.outputs.image }}" \ --arg tag "${NIGHTLY_TAG}" \ --arg version "${asset_version}" \ --arg digest "${IMAGE_DIGEST}" \ @@ -398,7 +400,7 @@ jobs: clean_config="$(mktemp -d)" trap 'rm -rf "$clean_config"' EXIT DOCKER_CONFIG="$clean_config" docker buildx imagetools inspect \ - "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}" >/dev/null + "${{ steps.nightly-image-meta.outputs.image }}@${IMAGE_DIGEST}" >/dev/null - name: List release assets run: | @@ -423,7 +425,7 @@ jobs: --assets-dir linux-release-assets \ --version "${NIGHTLY_VERSION%%+*}" \ --tag "${{ env.NIGHTLY_TAG }}" \ - --repo "GCWing/BitFun" \ + --repo "${{ github.repository }}" \ --out linux-release-assets/linux-binaries.json - name: Prepare versioned Windows installer @@ -517,14 +519,14 @@ jobs: run: | set -euo pipefail curl -fsSL --retry 5 --retry-delay 3 \ - "https://github.com/GCWing/BitFun/releases/download/${NIGHTLY_TAG}/relay-image.json" \ + "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${NIGHTLY_TAG}/relay-image.json" \ -o relay-image.published.json test "$(jq -r '.tag' relay-image.published.json)" = "${NIGHTLY_TAG}" test "$(jq -r '.version' relay-image.published.json)" = "${NIGHTLY_VERSION%%+*}" - test "$(jq -r '.image' relay-image.published.json)" = "ghcr.io/gcwing/bitfun-relay-server" + test "$(jq -r '.image' relay-image.published.json)" = "${{ steps.nightly-image-meta.outputs.image }}" jq -e '.digest | test("^sha256:[0-9a-f]{64}$")' relay-image.published.json >/dev/null curl -fsSL --retry 5 --retry-delay 3 \ - "https://github.com/GCWing/BitFun/releases/download/${NIGHTLY_TAG}/relay-image.json.sig" \ + "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${NIGHTLY_TAG}/relay-image.json.sig" \ -o /dev/null - name: Verify published macOS CLI assets @@ -534,7 +536,7 @@ jobs: run: | set -euo pipefail ASSET_VERSION="${NIGHTLY_VERSION%%+*}" - RELEASE_ROOT="https://github.com/GCWing/BitFun/releases/download/${{ env.NIGHTLY_TAG }}" + RELEASE_ROOT="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${{ env.NIGHTLY_TAG }}" for target in aarch64-apple-darwin x86_64-apple-darwin; do archive="${RELEASE_ROOT}/bitfun-cli-${ASSET_VERSION}-${target}.tar.gz" curl -fsSL --retry 5 --retry-delay 3 "$archive" -o /dev/null diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index 1b323f5327..f11acf234c 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -549,17 +549,51 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); }); -test('nightly Relay smoke test passes an exact image reference', () => { +test('nightly publishes and verifies the Relay image in the current repository owner scope', () => { const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), ); - const smoke = workflow.jobs['publish-nightly'].steps.find( + const steps = workflow.jobs['publish-nightly'].steps; + const metadata = steps.find( + (step) => step.name === 'Resolve nightly image metadata', + ); + const publish = steps.find( + (step) => step.name === 'Build and push multi-platform Relay image', + ); + const smoke = steps.find( (step) => step.name === 'Smoke-test published Relay image on both platforms', ); + const manifest = steps.find( + (step) => step.name === 'Generate Linux binaries manifest', + ); + const verifyDescriptor = steps.find( + (step) => step.name === 'Verify published Relay image descriptor', + ); + const verifyMacCli = steps.find( + (step) => step.name === 'Verify published macOS CLI assets', + ); + const image = '${{ steps.nightly-image-meta.outputs.image }}'; + assert.match( + metadata?.run ?? '', + /image=ghcr\.io\/\$\{GITHUB_REPOSITORY_OWNER,,\}\/bitfun-relay-server/, + ); + assert.equal( + publish?.with?.tags, + `${image}:${'${{ env.NIGHTLY_TAG }}'}\n${image}:${'${{ steps.nightly-image-meta.outputs.asset_version }}'}\n`, + ); assert.equal( smoke?.run, - 'bash scripts/relay/smoke-image.sh \\\n "ghcr.io/gcwing/bitfun-relay-server@${IMAGE_DIGEST}"\n', + `bash scripts/relay/smoke-image.sh \\\n "${image}@\${IMAGE_DIGEST}"\n`, + ); + assert.match(manifest?.run ?? '', /--repo "\$\{\{ github\.repository \}\}"/); + assert.match( + verifyDescriptor?.run ?? '', + /\$\{GITHUB_SERVER_URL\}\/\$\{GITHUB_REPOSITORY\}\/releases\/download/, + ); + assert.match( + verifyMacCli?.run ?? '', + /\$\{GITHUB_SERVER_URL\}\/\$\{GITHUB_REPOSITORY\}\/releases\/download/, ); }); From 353baf7a1ab3742ad08e5a01de2ad6387f103d2c Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 16:13:33 +0800 Subject: [PATCH 03/12] fix(ci): require nightly artifact parity --- .github/workflows/ci.yml | 45 +- .github/workflows/desktop-package.yml | 10 + .github/workflows/linux-binaries.yml | 28 +- .github/workflows/nightly.yml | 64 +- BitFun-Installer/package.json | 8 +- BitFun-Installer/src-tauri/Cargo.lock | 5535 +++++++++++++++++ scripts/check-github-config.test.mjs | 181 +- scripts/release-channel.test.mjs | 41 +- scripts/set-build-version.mjs | 24 +- .../cli/tests/terminal_process_contracts.rs | 16 +- .../tests/file_watch_contracts.rs | 46 +- 11 files changed, 5923 insertions(+), 75 deletions(-) create mode 100644 BitFun-Installer/src-tauri/Cargo.lock diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a6315cadb..0fb814804d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,7 +112,7 @@ jobs: needs: rust-impact if: ${{ !cancelled() && needs.rust-impact.outputs.rust_required != 'false' }} runs-on: ${{ matrix.os }} - timeout-minutes: 15 + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -250,14 +250,9 @@ jobs: save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} cache-on-failure: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - # Nightly and beta project a prerelease version before their nested - # `cargo --locked` build hooks run. Exercise that state transition in the - # ordinary CI OS matrix so a green commit cannot carry a stale lockfile - # contract into packaging. - - name: Project CI build version - run: node scripts/set-build-version.mjs --version 0.0.0-nightly.ci - - - name: Verify projected Cargo metadata + # Validate the committed manifests and lockfile before any release + # projection is allowed to rewrite workspace package entries. + - name: Verify committed Cargo metadata run: cargo metadata --locked --no-deps # rust-cache prunes the workspace target directory before saving it, so @@ -296,7 +291,7 @@ jobs: # so the workspace check above cannot catch drift in its shared Rust APIs. - name: Check installer compilation if: runner.os == 'Windows' - run: cargo check --manifest-path BitFun-Installer/src-tauri/Cargo.toml + run: cargo check --locked --manifest-path BitFun-Installer/src-tauri/Cargo.toml - name: Run core and desktop library tests run: cargo test --locked -p bitfun-core -p bitfun-desktop --lib @@ -328,14 +323,31 @@ jobs: - name: Run search tool tests run: "cargo test --locked -p tool-runtime --lib search::" + # Execute the exact five-platform Nightly artifact producers with publishing + # disabled. This is the build contract: a required CI result cannot be green + # unless the same release linking, Tauri bundling, Installer, Extension Host, + # macOS CLI, and Linux CLI/Relay paths used by Nightly all succeed. + nightly-build-contract: + name: Nightly Build Contract + needs: rust-impact + if: ${{ !cancelled() && needs.rust-impact.result == 'success' }} + uses: ./.github/workflows/nightly.yml + permissions: + contents: read + with: + force_build: true + build_only: true + artifact_retention_days: 1 + # Stable Rust/CLI result for CI runs: matrix jobs may be skipped for a proven - # Web-only change, but this result verifies why they ran or skipped. + # Web-only change, but this result verifies why they ran or skipped and makes + # the exact Nightly artifact graph part of the required result. rust-validation-result: name: Rust / CLI Validation runs-on: ubuntu-latest timeout-minutes: 5 if: ${{ always() }} - needs: [rust-impact, cli-test, rust-build-check] + needs: [rust-impact, cli-test, rust-build-check, nightly-build-contract] steps: - name: Verify Rust and CLI result shell: pwsh @@ -344,6 +356,7 @@ jobs: IMPACT_RESULT: ${{ needs.rust-impact.result }} CLI_RESULT: ${{ needs.cli-test.result }} RUST_RESULT: ${{ needs.rust-build-check.result }} + BUILD_RESULT: ${{ needs.nightly-build-contract.result }} run: | $ErrorActionPreference = 'Stop' if ($env:IMPACT_RESULT -ne 'success') { @@ -352,13 +365,13 @@ jobs: switch ($env:RUST_REQUIRED) { 'false' { - if ($env:CLI_RESULT -ne 'skipped' -or $env:RUST_RESULT -ne 'skipped') { - throw "Expected skipped Rust and CLI jobs for a Web-only change; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT" + if ($env:CLI_RESULT -ne 'skipped' -or $env:RUST_RESULT -ne 'skipped' -or $env:BUILD_RESULT -ne 'success') { + throw "Expected skipped Rust/CLI jobs and a successful Nightly build contract for a Web-only change; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT Build=$env:BUILD_RESULT" } } 'true' { - if ($env:CLI_RESULT -ne 'success' -or $env:RUST_RESULT -ne 'success') { - throw "Expected successful Rust and CLI jobs; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT" + if ($env:CLI_RESULT -ne 'success' -or $env:RUST_RESULT -ne 'success' -or $env:BUILD_RESULT -ne 'success') { + throw "Expected successful Rust, CLI, and Nightly build jobs; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT Build=$env:BUILD_RESULT" } } default { diff --git a/.github/workflows/desktop-package.yml b/.github/workflows/desktop-package.yml index a88dd8401c..86c7fa0289 100644 --- a/.github/workflows/desktop-package.yml +++ b/.github/workflows/desktop-package.yml @@ -277,6 +277,11 @@ jobs: shared-key: "package-v2-${{ matrix.platform.name }}" cache-bin: false + - name: Verify committed Cargo metadata + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml + - name: Install dependencies run: pnpm install --frozen-lockfile @@ -298,6 +303,11 @@ jobs: if: needs.prepare.outputs.release_channel == 'beta' run: node scripts/set-build-version.mjs --version "${{ needs.prepare.outputs.version }}" + - name: Verify build Cargo metadata + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml + - name: Verify release version metadata run: node scripts/verify-release-version-sync.mjs --version "${{ needs.prepare.outputs.version }}" diff --git a/.github/workflows/linux-binaries.yml b/.github/workflows/linux-binaries.yml index 6b7f98df0e..1b3e376270 100644 --- a/.github/workflows/linux-binaries.yml +++ b/.github/workflows/linux-binaries.yml @@ -15,6 +15,11 @@ on: description: "Stable prefix that isolates artifacts in the caller run." required: true type: string + artifact_retention_days: + description: "Retention for uploaded build artifacts." + required: false + default: 7 + type: number secrets: release_signing_key: description: "Tauri/minisign private key, base64. Absent on forks: archives ship unsigned." @@ -62,6 +67,12 @@ jobs: with: bun-version: "1.3.14" + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: package.json + package-manager-cache: false + - name: Build plugin Host resources run: | bun install --cwd src/apps/extension-host --frozen-lockfile @@ -94,6 +105,9 @@ jobs: shared-key: "linux-binaries-v1-${{ matrix.platform.name }}" cache-bin: false + - name: Verify committed Cargo metadata + run: cargo metadata --locked --no-deps + - name: Patch build version shell: bash env: @@ -106,12 +120,10 @@ jobs: # the same way, which also keeps the two asset sets on one version string. ASSET_VERSION="${RELEASE_VERSION%%+*}" echo "ASSET_VERSION=${ASSET_VERSION}" >>"$GITHUB_ENV" - sed -i \ - "s/^version = \".*\" # x-release-please-version/version = \"${ASSET_VERSION}\" # x-release-please-version/" \ - Cargo.toml - sed -i \ - "s/^version = \".*\" # x-release-please-version/version = \"${ASSET_VERSION}\" # x-release-please-version/" \ - src/apps/relay-server/Cargo.toml + node scripts/set-build-version.mjs --version "$ASSET_VERSION" + + - name: Verify projected Cargo metadata + run: cargo metadata --locked --no-deps - name: Build CLI and Relay Server shell: bash @@ -121,7 +133,7 @@ jobs: # checksum-only. BITFUN_RELEASE_PUBKEY: ${{ secrets.release_pubkey }} run: | - cargo build --release \ + cargo build --locked --release \ --target ${{ matrix.platform.target }} \ -p bitfun-cli \ -p bitfun-relay-server \ @@ -184,7 +196,7 @@ jobs: with: name: bitfun-linux-${{ inputs.artifact_prefix }}-${{ matrix.platform.name }} if-no-files-found: error - retention-days: 7 + retention-days: ${{ inputs.artifact_retention_days }} path: | ${{ steps.cli-stage.outputs.archive }} ${{ steps.cli-stage.outputs.checksum }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index acc211f9e8..10e4df8233 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -5,13 +5,48 @@ on: # Weekdays at 02:00 UTC - cron: "0 2 * * 1-5" workflow_dispatch: + inputs: + build_only: + description: "Build and package every artifact without publishing it." + required: false + default: false + type: boolean + artifact_retention_days: + description: "Retention for build artifacts." + required: false + default: 7 + type: number + workflow_call: + inputs: + force_build: + description: "Run the build contract even when the latest commit is older than 25 hours." + required: false + default: true + type: boolean + build_only: + description: "Build and package every artifact without publishing it." + required: false + default: true + type: boolean + artifact_retention_days: + description: "Retention for build artifacts." + required: false + default: 1 + type: number + secrets: + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_UPDATER_PUBKEY: + required: false permissions: - contents: write - packages: write + contents: read concurrency: - group: nightly + # Reusable CI calls must not cancel a scheduled Nightly or another PR. + group: nightly-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: @@ -33,6 +68,8 @@ jobs: - name: Check for recent changes id: check shell: bash + env: + FORCE_BUILD: ${{ inputs.force_build || false }} run: | set -euo pipefail @@ -43,7 +80,7 @@ jobs: echo "nightly_version=$NIGHTLY_VERSION" >> "$GITHUB_OUTPUT" - if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" || "$FORCE_BUILD" == "true" ]]; then echo "should_build=true" >> "$GITHUB_OUTPUT" exit 0 fi @@ -169,6 +206,11 @@ jobs: shared-key: "nightly-v2-${{ matrix.platform.name }}" cache-bin: false + - name: Verify committed Cargo metadata + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml + - name: Install dependencies run: pnpm install --frozen-lockfile @@ -198,7 +240,9 @@ jobs: echo "Cargo.toml version: $(grep 'x-release-please-version' Cargo.toml)" - name: Verify projected Cargo metadata - run: cargo metadata --locked --no-deps + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml - name: Run Windows CLI terminal contracts if: runner.os == 'Windows' @@ -247,7 +291,7 @@ jobs: with: name: bitfun-nightly-${{ matrix.platform.name }}-bundle if-no-files-found: error - retention-days: 7 + retention-days: ${{ inputs.artifact_retention_days || 7 }} path: | target/*/release/bundle target/release/bundle @@ -271,13 +315,19 @@ jobs: checkout_ref: ${{ github.sha }} version: ${{ needs.check-changes.outputs.nightly_version }} artifact_prefix: nightly + artifact_retention_days: ${{ inputs.artifact_retention_days || 7 }} # ── Publish nightly pre-release ──────────────────────────────────── publish-nightly: name: Publish Nightly needs: [check-changes, package, linux-binaries] - if: needs.check-changes.outputs.should_build == 'true' + if: >- + needs.check-changes.outputs.should_build == 'true' && + inputs.build_only != true runs-on: ubuntu-latest + permissions: + contents: write + packages: write steps: - uses: actions/checkout@v5 diff --git a/BitFun-Installer/package.json b/BitFun-Installer/package.json index ae90699962..0ec21ff24b 100644 --- a/BitFun-Installer/package.json +++ b/BitFun-Installer/package.json @@ -12,10 +12,10 @@ "build": "pnpm run sync:i18n && tsc && vite build", "preview": "vite preview", "tauri:dev": "pnpm run sync:i18n && tauri dev", - "tauri:build": "pnpm run sync:i18n && tauri build", - "tauri:build:fast": "pnpm run sync:i18n && tauri build -- --profile release-fast", - "tauri:build:exe": "pnpm run sync:i18n && tauri build --no-bundle", - "tauri:build:exe:fast": "pnpm run sync:i18n && tauri build --no-bundle -- --profile release-fast", + "tauri:build": "pnpm run sync:i18n && tauri build -- --locked", + "tauri:build:fast": "pnpm run sync:i18n && tauri build -- --locked --profile release-fast", + "tauri:build:exe": "pnpm run sync:i18n && tauri build --no-bundle -- --locked", + "tauri:build:exe:fast": "pnpm run sync:i18n && tauri build --no-bundle -- --locked --profile release-fast", "installer:build": "node scripts/build-installer.cjs", "installer:build:fast": "node scripts/build-installer.cjs --mode fast", "installer:build:only": "node scripts/build-installer.cjs --skip-app-build", diff --git a/BitFun-Installer/src-tauri/Cargo.lock b/BitFun-Installer/src-tauri/Cargo.lock new file mode 100644 index 0000000000..0beca515fd --- /dev/null +++ b/BitFun-Installer/src-tauri/Cargo.lock @@ -0,0 +1,5535 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitfun-agent-stream" +version = "0.2.19" +dependencies = [ + "anyhow", + "async-trait", + "bitfun-core-types", + "bitfun-events", + "bitfun-tool-call-jsonrepair", + "futures", + "log", + "serde", + "serde_json", + "tokio", + "tokio-util", + "uuid", +] + +[[package]] +name = "bitfun-ai-adapters" +version = "0.2.19" +dependencies = [ + "anyhow", + "async-trait", + "bitfun-agent-stream", + "bitfun-core-types", + "chrono", + "eventsource-stream", + "futures", + "hex", + "log", + "reqwest", + "serde", + "serde_json", + "sha2", + "tokio", + "tokio-stream", + "tokio-util", + "urlencoding", +] + +[[package]] +name = "bitfun-core-types" +version = "0.2.19" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "bitfun-events" +version = "0.2.19" +dependencies = [ + "anyhow", + "async-trait", + "bitfun-core-types", + "chrono", + "log", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "bitfun-installer" +version = "0.2.19" +dependencies = [ + "anyhow", + "bitfun-ai-adapters", + "chrono", + "dirs 5.0.1", + "log", + "mslnk", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "winreg 0.52.0", + "zip", +] + +[[package]] +name = "bitfun-tool-call-jsonrepair" +version = "0.2.19" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[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.13.1", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[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", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.4+spec-1.1.0", + "vswhom", + "winreg 0.55.0", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "eventsource-stream" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" +dependencies = [ + "futures-core", + "nom", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "h2" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c0e8a181239723652be9062bb56ca5bf5f64011f73b623f6f4fc59086a228" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[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.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "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 = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.1", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +dependencies = [ + "libc", +] + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mslnk" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86c97310150b7f496a93f31690da7822b99d95ff68ca9d30fb09d3ad54375c76" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "log", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.1", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.1", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.1", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.1", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.13+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +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 = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[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", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.20", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.1", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45c444e496845d3f2a351146bff59aae4975b2280238df1dfaa0c7d1846f38e" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.1", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni 0.21.1", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs 6.0.0", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni 0.21.1", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.20", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs 6.0.0", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.20", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d3c1dbe38037e7f590cdf2492594d5ceebe031e7bc7e827509b22a999d2940" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.20", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ecc274121aca0c036a2b42d1cbe83d368d348f54e0bb8a735c2b1548e8f371" +dependencies = [ + "anyhow", + "dunce", + "glob", + "log", + "objc2-foundation", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.20", + "toml 1.1.4+spec-1.1.0", + "url", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni 0.21.1", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.20", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni 0.21.1", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.20", + "toml 1.1.4+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.4+spec-1.1.0", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.1", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +dependencies = [ + "crossbeam-channel", + "dirs 6.0.0", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[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 = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +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 = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.20", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs 6.0.0", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni 0.21.1", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.20", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index f11acf234c..ea9879fcf9 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -252,6 +252,7 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => } const cliJob = workflow.jobs['cli-test']; + assert.equal(cliJob['timeout-minutes'], 30); assert.ok( cliJob.strategy.matrix.include.some((entry) => entry.os === 'windows-latest'), 'Windows ConPTY contracts must run before Nightly', @@ -314,24 +315,13 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => const commandByStep = new Map( rustJob.steps.map((step) => [step.name, step.run]), ); - const projectVersion = rustJob.steps.find( - (step) => step.name === 'Project CI build version', - ); const verifyMetadata = rustJob.steps.find( - (step) => step.name === 'Verify projected Cargo metadata', - ); - assert.equal( - projectVersion?.run, - 'node scripts/set-build-version.mjs --version 0.0.0-nightly.ci', + (step) => step.name === 'Verify committed Cargo metadata', ); assert.equal(verifyMetadata?.run, 'cargo metadata --locked --no-deps'); assert.ok( - rustJob.steps.indexOf(projectVersion) < rustJob.steps.indexOf(checkCompilation), - 'CI must project the build version before the locked workspace check', - ); - assert.ok( - rustJob.steps.indexOf(projectVersion) < rustJob.steps.indexOf(verifyMetadata), - 'CI must validate Cargo.lock after projecting the build version', + rustJob.steps.indexOf(verifyMetadata) < rustJob.steps.indexOf(checkCompilation), + 'CI must validate the committed Cargo.lock before the workspace check', ); assert.equal( commandByStep.get('Run subscription authentication tests'), @@ -343,7 +333,7 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => assert.equal(installerCheck?.if, "runner.os == 'Windows'"); assert.equal( installerCheck?.run, - 'cargo check --manifest-path BitFun-Installer/src-tauri/Cargo.toml', + 'cargo check --locked --manifest-path BitFun-Installer/src-tauri/Cargo.toml', ); const fileWatchContracts = rustJob.steps.find( (step) => step.name === 'Run file watch contract tests', @@ -363,7 +353,7 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => ); }); -test('gates Rust and CLI validation behind one fail-closed impact decision', () => { +test('gates Rust and CLI validation behind one fail-closed impact decision', (t) => { const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8'), ); @@ -377,6 +367,7 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', () const impactJob = workflow.jobs['rust-impact']; const cliJob = workflow.jobs['cli-test']; const rustJob = workflow.jobs['rust-build-check']; + const buildJob = workflow.jobs['nightly-build-contract']; const resultJob = workflow.jobs['rust-validation-result']; const frontendJob = workflow.jobs['frontend-build']; @@ -420,20 +411,36 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', () assert.match(job.if, /rust_required != 'false'/); } + assert.equal(buildJob.name, 'Nightly Build Contract'); + assert.equal(buildJob.needs, 'rust-impact'); + assert.equal(buildJob.uses, './.github/workflows/nightly.yml'); + assert.match(buildJob.if, /rust-impact\.result == 'success'/); + assert.doesNotMatch(buildJob.if, /rust_required/); + assert.deepEqual(buildJob.permissions, { contents: 'read' }); + assert.deepEqual(buildJob.with, { + force_build: true, + build_only: true, + artifact_retention_days: 1, + }); + assert.equal(resultJob.name, 'Rust / CLI Validation'); assert.equal(resultJob.if, '${{ always() }}'); assert.deepEqual( [...resultJob.needs].sort(), - ['cli-test', 'rust-build-check', 'rust-impact'], + ['cli-test', 'nightly-build-contract', 'rust-build-check', 'rust-impact'], ); const verify = resultJob.steps.find((step) => step.name === 'Verify Rust and CLI result'); assert.equal(verify?.env?.RUST_REQUIRED, '${{ needs.rust-impact.outputs.rust_required }}'); assert.equal(verify?.env?.IMPACT_RESULT, '${{ needs.rust-impact.result }}'); assert.equal(verify?.env?.CLI_RESULT, '${{ needs.cli-test.result }}'); assert.equal(verify?.env?.RUST_RESULT, '${{ needs.rust-build-check.result }}'); + assert.equal( + verify?.env?.BUILD_RESULT, + '${{ needs.nightly-build-contract.result }}', + ); assert.equal(verify?.shell, 'pwsh'); - assert.match(verify?.run ?? '', /expected skipped Rust and CLI jobs/i); - assert.match(verify?.run ?? '', /expected successful Rust and CLI jobs/i); + assert.match(verify?.run ?? '', /successful Nightly build contract/i); + assert.match(verify?.run ?? '', /successful Rust, CLI, and Nightly build jobs/i); const statuses = ['success', 'skipped', 'failure', 'cancelled']; const cases = []; @@ -443,21 +450,25 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', () impactResult, cliResult: 'success', rustResult: 'success', + buildResult: 'success', expectedSuccess: false, }); } for (const rustRequired of ['false', 'true']) { for (const cliResult of statuses) { for (const rustResult of statuses) { - cases.push({ - rustRequired, - impactResult: 'success', - cliResult, - rustResult, - expectedSuccess: rustRequired === 'false' - ? cliResult === 'skipped' && rustResult === 'skipped' - : cliResult === 'success' && rustResult === 'success', - }); + for (const buildResult of statuses) { + cases.push({ + rustRequired, + impactResult: 'success', + cliResult, + rustResult, + buildResult, + expectedSuccess: buildResult === 'success' && (rustRequired === 'false' + ? cliResult === 'skipped' && rustResult === 'skipped' + : cliResult === 'success' && rustResult === 'success'), + }); + } } } } @@ -466,6 +477,7 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', () impactResult: 'success', cliResult: 'skipped', rustResult: 'skipped', + buildResult: 'success', expectedSuccess: false, }); const truthTable = spawnSync( @@ -485,6 +497,7 @@ foreach ($case in $cases) { $env:IMPACT_RESULT = [string]$case.impactResult $env:CLI_RESULT = [string]$case.cliResult $env:RUST_RESULT = [string]$case.rustResult + $env:BUILD_RESULT = [string]$case.buildResult $succeeded = $true try { & $verify } catch { $succeeded = $false } if ($succeeded -ne [bool]$case.expectedSuccess) { @@ -498,6 +511,10 @@ foreach ($case in $cases) { encoding: 'utf8', }, ); + if (truthTable.error?.code === 'ENOENT') { + t.skip('pwsh is not installed; GitHub-hosted runners execute this truth table'); + return; + } assert.equal(truthTable.status, 0, `${truthTable.stdout}${truthTable.stderr}`); }); @@ -505,8 +522,12 @@ test('nightly validates generated inputs and projected lockfiles before packagin const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), ); + const callInputs = workflow.on.workflow_call.inputs; const packageJob = workflow.jobs.package; const steps = packageJob.steps; + const committedMetadataIndex = steps.findIndex( + (step) => step.name === 'Verify committed Cargo metadata', + ); const generationIndex = steps.findIndex( (step) => step.name === 'Generate web API bindings', ); @@ -523,6 +544,20 @@ test('nightly validates generated inputs and projected lockfiles before packagin (step) => step.name === 'Build desktop app', ); + assert.equal(callInputs.force_build.default, true); + assert.equal(callInputs.build_only.default, true); + assert.equal(callInputs.artifact_retention_days.default, 1); + assert.equal(workflow.permissions.contents, 'read'); + assert.match( + workflow.jobs['publish-nightly'].if, + /inputs\.build_only != true/, + ); + assert.deepEqual(workflow.jobs['publish-nightly'].permissions, { + contents: 'write', + packages: 'write', + }); + + assert.notEqual(committedMetadataIndex, -1); assert.notEqual(generationIndex, -1); assert.notEqual(typeCheckIndex, -1); assert.equal( @@ -534,10 +569,17 @@ test('nightly validates generated inputs and projected lockfiles before packagin 'nightly must generate web API bindings before type-checking the web UI', ); assert.ok( - typeCheckIndex < patchIndex && patchIndex < metadataIndex && metadataIndex < buildIndex, + committedMetadataIndex < patchIndex && + typeCheckIndex < patchIndex && + patchIndex < metadataIndex && + metadataIndex < buildIndex, 'nightly must verify the projected lockfile before nested locked build hooks run', ); - assert.equal(steps[metadataIndex].run, 'cargo metadata --locked --no-deps'); + const expectedMetadata = + 'cargo metadata --locked --no-deps\n' + + 'cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml\n'; + assert.equal(steps[committedMetadataIndex].run, expectedMetadata); + assert.equal(steps[metadataIndex].run, expectedMetadata); assert.equal( steps.some((step) => step.run?.includes('cargo generate-lockfile')), false, @@ -549,6 +591,49 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); }); +test('Linux binary packaging uses the shared locked version projection contract', () => { + const workflow = yaml.parse( + readFileSync(path.join(repoRoot, '.github/workflows/linux-binaries.yml'), 'utf8'), + ); + const inputs = workflow.on.workflow_call.inputs; + const steps = workflow.jobs.build.steps; + const nodeIndex = steps.findIndex( + (step) => step.name === 'Setup Node.js', + ); + const committedIndex = steps.findIndex( + (step) => step.name === 'Verify committed Cargo metadata', + ); + const patchIndex = steps.findIndex( + (step) => step.name === 'Patch build version', + ); + const projectedIndex = steps.findIndex( + (step) => step.name === 'Verify projected Cargo metadata', + ); + const buildIndex = steps.findIndex( + (step) => step.name === 'Build CLI and Relay Server', + ); + + assert.equal(inputs.artifact_retention_days.default, 7); + assert.equal(steps[nodeIndex].uses, 'actions/setup-node@v5'); + assert.equal(steps[nodeIndex].with['node-version-file'], 'package.json'); + assert.ok( + nodeIndex < patchIndex && + committedIndex < patchIndex && + patchIndex < projectedIndex && + projectedIndex < buildIndex, + ); + assert.match(steps[patchIndex].run, /node scripts\/set-build-version\.mjs/); + assert.doesNotMatch(steps[patchIndex].run, /sed -i/); + assert.equal(steps[committedIndex].run, 'cargo metadata --locked --no-deps'); + assert.equal(steps[projectedIndex].run, 'cargo metadata --locked --no-deps'); + assert.match(steps[buildIndex].run, /cargo build --locked --release/); + const upload = steps.find((step) => step.uses?.startsWith('actions/upload-artifact@')); + assert.equal( + upload?.with?.['retention-days'], + '${{ inputs.artifact_retention_days }}', + ); +}); + test('nightly publishes and verifies the Relay image in the current repository owner scope', () => { const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), @@ -696,10 +781,26 @@ test('Desktop packaging keeps beta identity explicit and stable-safe', () => { const patchIndex = packageJob.steps.findIndex( (step) => step.name === 'Project beta build version', ); + const committedMetadataIndex = packageJob.steps.findIndex( + (step) => step.name === 'Verify committed Cargo metadata', + ); + const buildMetadataIndex = packageJob.steps.findIndex( + (step) => step.name === 'Verify build Cargo metadata', + ); const verifyIndex = packageJob.steps.findIndex( (step) => step.name === 'Verify release version metadata', ); - assert.ok(patchIndex >= 0 && patchIndex < verifyIndex); + assert.ok( + committedMetadataIndex >= 0 && + committedMetadataIndex < patchIndex && + patchIndex < buildMetadataIndex && + buildMetadataIndex < verifyIndex, + ); + const expectedMetadata = + 'cargo metadata --locked --no-deps\n' + + 'cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml\n'; + assert.equal(packageJob.steps[committedMetadataIndex].run, expectedMetadata); + assert.equal(packageJob.steps[buildMetadataIndex].run, expectedMetadata); assert.equal( packageJob.steps[patchIndex].if, "needs.prepare.outputs.release_channel == 'beta'", @@ -790,6 +891,24 @@ test('nightly and beta use the shared build-version projection', () => { assert.match(signingStep.run, /write-minisign-public-key\.mjs/); }); +test('Installer packaging consumes its committed Cargo.lock', () => { + const installer = JSON.parse( + readFileSync(path.join(repoRoot, 'BitFun-Installer/package.json'), 'utf8'), + ); + for (const scriptName of [ + 'tauri:build', + 'tauri:build:fast', + 'tauri:build:exe', + 'tauri:build:exe:fast', + ]) { + assert.match( + installer.scripts[scriptName], + /tauri build(?: --no-bundle)? -- --locked(?: |$)/, + `${scriptName} must reject installer lockfile drift`, + ); + } +}); + test('Linux Rust workflows do not install an unused native OpenSSL toolchain', () => { for (const workflowPath of [ '.github/workflows/ci.yml', diff --git a/scripts/release-channel.test.mjs b/scripts/release-channel.test.mjs index 4f265c8927..b943fec067 100644 --- a/scripts/release-channel.test.mjs +++ b/scripts/release-channel.test.mjs @@ -99,12 +99,30 @@ edition = "2021" 'src/apps/relay-server/Cargo.toml', 'version = "1.0.0" # x-release-please-version\n', ); - writeFixture(root, 'BitFun-Installer/src-tauri/Cargo.toml', 'version = "1.0.0"\n'); + writeFixture( + root, + 'BitFun-Installer/src-tauri/Cargo.toml', + `[package] +name = "bitfun-installer-fixture" +version = "1.0.0" +edition = "2021" + +[dependencies] +fixture-dependency = { path = "../../fixture-dependency" } +`, + ); + writeFixture(root, 'BitFun-Installer/src-tauri/src/lib.rs', 'pub fn installer_fixture() {}\n'); const initialLock = spawnSync('cargo', ['generate-lockfile'], { cwd: root, encoding: 'utf8', }); assert.equal(initialLock.status, 0, initialLock.stderr); + const initialInstallerLock = spawnSync( + 'cargo', + ['generate-lockfile', '--manifest-path', 'BitFun-Installer/src-tauri/Cargo.toml'], + { cwd: root, encoding: 'utf8' }, + ); + assert.equal(initialInstallerLock.status, 0, initialInstallerLock.stderr); setBuildVersion(root, '1.1.0-beta.2'); @@ -121,11 +139,32 @@ edition = "2021" const lockfile = readFileSync(path.join(root, 'Cargo.lock'), 'utf8'); assert.match(lockfile, /name = "build-version-fixture"\nversion = "1\.1\.0-beta\.2"/); assert.match(lockfile, /name = "fixture-dependency"\nversion = "1\.0\.0"/); + const installerLockfile = readFileSync( + path.join(root, 'BitFun-Installer/src-tauri/Cargo.lock'), + 'utf8', + ); + assert.match( + installerLockfile, + /name = "bitfun-installer-fixture"\nversion = "1\.1\.0-beta\.2"/, + ); + assert.match(installerLockfile, /name = "fixture-dependency"\nversion = "1\.0\.0"/); const lockedMetadata = spawnSync('cargo', ['metadata', '--locked', '--no-deps'], { cwd: root, encoding: 'utf8', }); assert.equal(lockedMetadata.status, 0, lockedMetadata.stderr); + const lockedInstallerMetadata = spawnSync( + 'cargo', + [ + 'metadata', + '--locked', + '--no-deps', + '--manifest-path', + 'BitFun-Installer/src-tauri/Cargo.toml', + ], + { cwd: root, encoding: 'utf8' }, + ); + assert.equal(lockedInstallerMetadata.status, 0, lockedInstallerMetadata.stderr); }); function writeFixture(root, relative, content) { diff --git a/scripts/set-build-version.mjs b/scripts/set-build-version.mjs index 0066ceca86..aee6d0622b 100644 --- a/scripts/set-build-version.mjs +++ b/scripts/set-build-version.mjs @@ -42,7 +42,15 @@ export function setBuildVersion(root, version) { `version = "${version}"`, ); - syncCargoLock(root); + syncCargoLock(root, { + lockfile: 'Cargo.lock', + label: 'workspace', + }); + syncCargoLock(root, { + lockfile: 'BitFun-Installer/src-tauri/Cargo.lock', + manifest: 'BitFun-Installer/src-tauri/Cargo.toml', + label: 'Installer', + }); } // Workspace members inherit the root version, so the lockfile carries a copy of @@ -53,21 +61,25 @@ export function setBuildVersion(root, version) { // // This cannot run with --offline: resolving the workspace walks every source, // and the git dependencies (tauri) are not in a cold CI cargo home yet. -function syncCargoLock(root) { - if (!existsSync(path.join(root, 'Cargo.lock'))) { +function syncCargoLock(root, { lockfile, manifest, label }) { + if (!existsSync(path.join(root, lockfile))) { return; } - const result = spawnSync('cargo', ['update', '--workspace'], { + const args = ['update', '--workspace']; + if (manifest) { + args.push('--manifest-path', manifest); + } + const result = spawnSync('cargo', args, { cwd: root, encoding: 'utf8', }); if (result.error) { - throw new Error(`Failed to run cargo update: ${result.error.message}`); + throw new Error(`Failed to update the ${label} Cargo.lock: ${result.error.message}`); } if (result.status !== 0) { throw new Error( - `cargo update --workspace failed with exit code ${result.status}\n${result.stderr || ''}`, + `cargo update --workspace failed for ${label} with exit code ${result.status}\n${result.stderr || ''}`, ); } } diff --git a/src/apps/cli/tests/terminal_process_contracts.rs b/src/apps/cli/tests/terminal_process_contracts.rs index 5df1aad772..94604c21e7 100644 --- a/src/apps/cli/tests/terminal_process_contracts.rs +++ b/src/apps/cli/tests/terminal_process_contracts.rs @@ -2,7 +2,7 @@ mod support; use portable_pty::{native_pty_system, CommandBuilder, PtySize}; use std::io::{Read, Write}; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard, OnceLock}; use std::thread; use std::time::{Duration, Instant}; use support::{ @@ -34,9 +34,18 @@ const MULTILINE_INPUT_SENTINEL: &str = "M7Q4"; const RECOVERY_INPUT: &[u8] = b"READY_AFTER_CANCEL K4W8"; const RECOVERY_INPUT_SENTINEL: &str = "K4W8"; +fn serialize_native_pty_contracts() -> MutexGuard<'static, ()> { + static NATIVE_PTY_GATE: OnceLock> = OnceLock::new(); + NATIVE_PTY_GATE + .get_or_init(|| Mutex::new(())) + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) +} + #[cfg(unix)] #[test] fn startup_bracketed_paste_attaches_an_image_path_without_rendering_the_path() { + let _serial = serialize_native_pty_contracts(); let server = MockOpenAiServer::immediate(); let environment = CliTestEnvironment::new(); environment.initialize_git_repository(); @@ -98,6 +107,7 @@ fn startup_bracketed_paste_attaches_an_image_path_without_rendering_the_path() { #[test] fn interactive_startup_survives_resize_multiline_input_and_emits_cleanup() { + let _serial = serialize_native_pty_contracts(); let environment = CliTestEnvironment::new(); let mut process = PtyProcess::spawn(environment.pty_command(), INITIAL_SIZE); @@ -180,6 +190,7 @@ fn interactive_startup_survives_resize_multiline_input_and_emits_cleanup() { #[test] fn active_turn_resize_can_be_cancelled_and_returns_to_editable_input() { + let _serial = serialize_native_pty_contracts(); let server = MockOpenAiServer::gated(); let environment = CliTestEnvironment::new(); environment.initialize_git_repository(); @@ -249,6 +260,7 @@ fn active_turn_resize_can_be_cancelled_and_returns_to_editable_input() { #[test] fn external_editor_restores_the_tui_and_applies_the_edited_draft() { + let _serial = serialize_native_pty_contracts(); let server = MockOpenAiServer::immediate(); let environment = CliTestEnvironment::new(); environment.initialize_git_repository(); @@ -320,6 +332,7 @@ fn external_editor_restores_the_tui_and_applies_the_edited_draft() { #[test] fn export_dialog_writes_markdown_under_the_local_cli_directory() { + let _serial = serialize_native_pty_contracts(); let server = MockOpenAiServer::immediate(); let environment = CliTestEnvironment::new(); environment.initialize_git_repository(); @@ -454,6 +467,7 @@ fn legacy_exec_stream_json_ctrl_c_emits_one_cancelled_terminal_and_disconnects() } fn assert_exec_stream_json_ctrl_c_contract(deprecated_entrypoint: bool) { + let _serial = serialize_native_pty_contracts(); let server = MockOpenAiServer::gated(); let environment = CliTestEnvironment::new(); environment.configure_mock_model(server.base_url()); diff --git a/src/crates/services/services-integrations/tests/file_watch_contracts.rs b/src/crates/services/services-integrations/tests/file_watch_contracts.rs index e58626b0aa..54b0c52bf7 100644 --- a/src/crates/services/services-integrations/tests/file_watch_contracts.rs +++ b/src/crates/services/services-integrations/tests/file_watch_contracts.rs @@ -1,11 +1,50 @@ #![cfg(feature = "file-watch")] use bitfun_services_integrations::file_watch::{ - FileWatchEventKind, FileWatchService, FileWatcherConfig, + FileWatchEvent, FileWatchEventKind, FileWatchService, FileWatcherConfig, }; use std::fs; +use std::path::Path; use std::sync::Arc; use std::time::Duration; +use tokio::sync::broadcast; + +/// FSEvents may accept a watch before its run loop has become observable. Use a +/// semantic probe instead of a sleep: once the backend reports this path, later +/// assertions no longer race native watcher startup. +async fn wait_until_watch_is_observable( + root: &Path, + events: &mut broadcast::Receiver>, +) { + let probe = root.join("bitfun-watch-ready-probe"); + let deadline = std::time::Instant::now() + Duration::from_secs(5); + let mut attempt = 0_u32; + + while std::time::Instant::now() < deadline { + attempt += 1; + fs::write(&probe, attempt.to_string()).expect("write file-watch readiness probe"); + let remaining = deadline.saturating_duration_since(std::time::Instant::now()); + let observation_window = remaining.min(Duration::from_millis(200)); + match tokio::time::timeout(observation_window, events.recv()).await { + Ok(Ok(batch)) + if batch + .iter() + .any(|event| event.path == probe.to_string_lossy()) => + { + return; + } + Ok(Ok(_)) | Ok(Err(broadcast::error::RecvError::Lagged(_))) | Err(_) => {} + Ok(Err(broadcast::error::RecvError::Closed)) => { + panic!("file-watch broadcast closed before readiness") + } + } + } + + panic!( + "native file watcher did not observe readiness probe {}", + probe.display() + ); +} #[tokio::test] async fn file_watch_preserves_missing_path_error() { @@ -71,6 +110,7 @@ async fn file_watch_publishes_debounced_batches_to_backend_subscribers() { .watch_path(temp.path().to_str().unwrap(), Some(config)) .await .expect("watch temp directory"); + wait_until_watch_is_observable(temp.path(), &mut events).await; let file = temp.path().join("command.md"); fs::write(&file, "first").expect("create watched file"); @@ -100,6 +140,7 @@ async fn file_watch_can_include_build_named_directories_for_semantic_sources() { .watch_path(temp.path().to_str().unwrap(), Some(config)) .await .expect("watch semantic source root"); + wait_until_watch_is_observable(temp.path(), &mut events).await; let file = build_skill.join("SKILL.md"); fs::write( @@ -142,6 +183,7 @@ async fn a_narrow_duplicate_registration_does_not_downgrade_recursive_watch() { .watch_path(temp.path().to_str().unwrap(), Some(recursive)) .await .expect("shared narrow watch"); + wait_until_watch_is_observable(temp.path(), &mut events).await; let file = nested.join("command.md"); fs::write(&file, "created").expect("nested file"); @@ -200,6 +242,7 @@ async fn re_registering_a_recreated_root_resumes_watching() { .watch_path(root.to_str().unwrap(), Some(config)) .await .expect("re-registration of a recreated root"); + wait_until_watch_is_observable(&root, &mut events).await; let file = root.join("command.md"); fs::write(&file, "created").expect("file in recreated root"); @@ -231,6 +274,7 @@ async fn atomic_rename_keeps_the_non_temporary_destination_path() { .watch_path(temp.path().to_str().unwrap(), Some(config)) .await .expect("watch temp directory"); + wait_until_watch_is_observable(temp.path(), &mut events).await; let temporary = temp.path().join("command.md.tmp"); let destination = temp.path().join("command.md"); From 1912cae31ba7eb0491d1be0416afe24aa09e3b6e Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 16:51:39 +0800 Subject: [PATCH 04/12] fix(ci): isolate privileged nightly publishing --- .github/workflows/ci.yml | 7 +- .github/workflows/nightly-artifacts.yml | 251 +++++++++++++++++++++++ .github/workflows/nightly.yml | 254 ++---------------------- scripts/check-github-config.test.mjs | 65 ++++-- 4 files changed, 313 insertions(+), 264 deletions(-) create mode 100644 .github/workflows/nightly-artifacts.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0fb814804d..14ab53d805 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -331,12 +331,13 @@ jobs: name: Nightly Build Contract needs: rust-impact if: ${{ !cancelled() && needs.rust-impact.result == 'success' }} - uses: ./.github/workflows/nightly.yml + uses: ./.github/workflows/nightly-artifacts.yml permissions: contents: read with: - force_build: true - build_only: true + checkout_ref: ${{ github.sha }} + version: 0.0.0-nightly.ci.${{ github.run_id }} + artifact_prefix: ci-${{ github.run_id }} artifact_retention_days: 1 # Stable Rust/CLI result for CI runs: matrix jobs may be skipped for a proven diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml new file mode 100644 index 0000000000..23b418d302 --- /dev/null +++ b/.github/workflows/nightly-artifacts.yml @@ -0,0 +1,251 @@ +name: Nightly Artifact Build + +on: + workflow_call: + inputs: + checkout_ref: + description: "Commit SHA or ref to build." + required: true + type: string + version: + description: "Prerelease version projected into every artifact producer." + required: true + type: string + artifact_prefix: + description: "Prefix isolating artifacts in the caller run." + required: true + type: string + artifact_retention_days: + description: "Retention for build artifacts." + required: false + default: 1 + type: number + secrets: + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_UPDATER_PUBKEY: + required: false + +permissions: + contents: read + +jobs: + # ── Patch version for nightly ────────────────────────────────────── + package: + name: Package (${{ matrix.platform.name }}) + runs-on: ${{ matrix.platform.os }} + env: + NODE_OPTIONS: --max-old-space-size=6144 + BITFUN_RELEASE_CHANNEL: nightly + # Nightly does not publish a Tauri latest.json feed yet. Preserve its + # existing stable updater endpoints until that publishing path exists. + TAURI_UPDATER_ENDPOINT: https://github.com/GCWing/BitFun/releases/latest/download/latest.json + TAURI_UPDATER_FALLBACK_ENDPOINT: https://openbitfun.com/release/latest.json + # Nightly relay archives are signed too (linux-binaries.yml receives the + # key), so nightly Desktop needs the same trust root to verify them. + BITFUN_RELEASE_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} + + strategy: + fail-fast: false + matrix: + platform: + - os: ubuntu-latest + name: linux-x64 + target: x86_64-unknown-linux-gnu + build_command: pnpm run desktop:build:linux -- --target x86_64-unknown-linux-gnu --bundles deb,rpm,appimage + - os: ubuntu-24.04-arm + name: linux-arm64 + target: aarch64-unknown-linux-gnu + # Fat LTO exhausts the hosted ARM runner while linking bitfun-desktop. + build_command: CARGO_PROFILE_RELEASE_LTO=thin pnpm run desktop:build:linux -- --target aarch64-unknown-linux-gnu --bundles deb,rpm,appimage + - os: macos-15 + name: macos-arm64 + target: aarch64-apple-darwin + build_command: pnpm run desktop:build:arm64 + - os: macos-15-intel + name: macos-x64 + target: x86_64-apple-darwin + build_command: pnpm run desktop:build:x86_64 + - os: windows-latest + name: windows-x64 + target: x86_64-pc-windows-msvc + build_command: pnpm run installer:build + + steps: + - uses: actions/checkout@v5 + with: + ref: ${{ inputs.checkout_ref }} + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.14" + + - name: Install Linux system dependencies (Tauri bundler) + if: runner.os == 'Linux' + shell: bash + run: | + sudo apt-get update + + if apt-cache show libwebkit2gtk-4.1-dev >/dev/null 2>&1; then + WEBKIT_PKG=libwebkit2gtk-4.1-dev + else + WEBKIT_PKG=libwebkit2gtk-4.0-dev + fi + + if apt-cache show libappindicator3-dev >/dev/null 2>&1; then + APPINDICATOR_PKG=libappindicator3-dev + else + APPINDICATOR_PKG=libayatana-appindicator3-dev + fi + + # Tauri pins AppImage GTK input methods to its bundled cache, so the + # fcitx5 GTK3 bridge must be present before linuxdeploy builds it. + sudo apt-get install -y --no-install-recommends \ + pkg-config \ + xdg-utils \ + libglib2.0-dev \ + libgtk-3-dev \ + fcitx5-frontend-gtk3 \ + libxdo-dev \ + "$WEBKIT_PKG" \ + "$APPINDICATOR_PKG" \ + librsvg2-dev \ + patchelf \ + fakeroot \ + rpm \ + libleptonica-dev \ + libtesseract-dev \ + tesseract-ocr \ + tesseract-ocr-eng + + - name: Setup pnpm + uses: pnpm/action-setup@v5 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + cache: pnpm + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.platform.target }} + + - name: Cache Rust build + uses: swatinem/rust-cache@v2 + with: + shared-key: "nightly-v2-${{ matrix.platform.name }}" + cache-bin: false + + - name: Verify committed Cargo metadata + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build plugin Host resources + run: | + bun install --cwd src/apps/extension-host --frozen-lockfile + bun run --cwd src/apps/extension-host build + + - name: Generate web API bindings + run: pnpm --dir src/web-ui run gen:types + + - name: Type-check web UI + run: pnpm run type-check:web + + - name: Patch nightly version + shell: bash + env: + NIGHTLY_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + + echo "Patching version to $NIGHTLY_VERSION" + ASSET_VERSION="${NIGHTLY_VERSION%%+*}" + node scripts/set-build-version.mjs --version "$ASSET_VERSION" + + echo "package.json version: $(jq -r '.version' package.json)" + echo "Cargo.toml version: $(grep 'x-release-please-version' Cargo.toml)" + + - name: Verify projected Cargo metadata + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml + + - name: Run Windows CLI terminal contracts + if: runner.os == 'Windows' + env: + CARGO_INCREMENTAL: "0" + CARGO_PROFILE_DEV_DEBUG: "0" + CARGO_PROFILE_TEST_DEBUG: "0" + run: cargo test --locked -p bitfun-cli --test terminal_process_contracts -- --test-threads=1 + + - name: Build desktop app + run: ${{ matrix.platform.build_command }} + + - name: Package macOS CLI for SSH dispatch + if: runner.os == 'macOS' + id: macos-cli + shell: bash + env: + NIGHTLY_VERSION: ${{ inputs.version }} + TARGET: ${{ matrix.platform.target }} + run: | + set -euo pipefail + ASSET_VERSION="${NIGHTLY_VERSION%%+*}" + cargo build --release --target "$TARGET" -p bitfun-cli + bash scripts/cli/package-unix.sh "$ASSET_VERSION" "$TARGET" + + - name: Sign macOS CLI archive and checksum + if: runner.os == 'macOS' + shell: bash + env: + BITFUN_SIGNING_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + BITFUN_SIGNING_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + BITFUN_SIGNING_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} + run: | + set -euo pipefail + bash scripts/sign-release-assets.sh \ + "${{ steps.macos-cli.outputs.archive }}" \ + "${{ steps.macos-cli.outputs.checksum }}" + + - name: Verify AppImage fcitx5 GTK module + if: runner.os == 'Linux' + shell: bash + run: bash scripts/ci/verify-appimage-fcitx.sh "${{ matrix.platform.target }}" + + - name: Upload bundles + uses: actions/upload-artifact@v6 + with: + name: bitfun-${{ inputs.artifact_prefix }}-${{ matrix.platform.name }}-bundle + if-no-files-found: error + retention-days: ${{ inputs.artifact_retention_days }} + path: | + target/*/release/bundle + target/release/bundle + src/apps/desktop/target/release/bundle + BitFun-Installer/src-tauri/target/release/bitfun-installer.exe + bitfun-cli-*-apple-darwin.tar.gz + bitfun-cli-*-apple-darwin.tar.gz.sha256 + bitfun-cli-*-apple-darwin.tar.gz.sig + bitfun-cli-*-apple-darwin.tar.gz.sha256.sig + + linux-binaries: + name: Linux CLI and Relay Server + uses: ./.github/workflows/linux-binaries.yml + secrets: + release_signing_key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + release_signing_password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + release_pubkey: ${{ secrets.TAURI_UPDATER_PUBKEY }} + with: + checkout_ref: ${{ inputs.checkout_ref }} + version: ${{ inputs.version }} + artifact_prefix: ${{ inputs.artifact_prefix }} + artifact_retention_days: ${{ inputs.artifact_retention_days }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 10e4df8233..e27b445884 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -16,37 +16,11 @@ on: required: false default: 7 type: number - workflow_call: - inputs: - force_build: - description: "Run the build contract even when the latest commit is older than 25 hours." - required: false - default: true - type: boolean - build_only: - description: "Build and package every artifact without publishing it." - required: false - default: true - type: boolean - artifact_retention_days: - description: "Retention for build artifacts." - required: false - default: 1 - type: number - secrets: - TAURI_SIGNING_PRIVATE_KEY: - required: false - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: - required: false - TAURI_UPDATER_PUBKEY: - required: false - permissions: contents: read concurrency: - # Reusable CI calls must not cancel a scheduled Nightly or another PR. - group: nightly-${{ github.workflow }}-${{ github.ref }} + group: nightly-${{ github.ref }} cancel-in-progress: true env: @@ -68,8 +42,6 @@ jobs: - name: Check for recent changes id: check shell: bash - env: - FORCE_BUILD: ${{ inputs.force_build || false }} run: | set -euo pipefail @@ -80,7 +52,7 @@ jobs: echo "nightly_version=$NIGHTLY_VERSION" >> "$GITHUB_OUTPUT" - if [[ "${{ github.event_name }}" == "workflow_dispatch" || "$FORCE_BUILD" == "true" ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then echo "should_build=true" >> "$GITHUB_OUTPUT" exit 0 fi @@ -97,220 +69,18 @@ jobs: echo "should_build=false" >> "$GITHUB_OUTPUT" fi - # ── Patch version for nightly ────────────────────────────────────── - package: - name: Package (${{ matrix.platform.name }}) - runs-on: ${{ matrix.platform.os }} - needs: check-changes - if: needs.check-changes.outputs.should_build == 'true' - env: - NODE_OPTIONS: --max-old-space-size=6144 - BITFUN_RELEASE_CHANNEL: nightly - # Nightly does not publish a Tauri latest.json feed yet. Preserve its - # existing stable updater endpoints until that publishing path exists. - TAURI_UPDATER_ENDPOINT: https://github.com/GCWing/BitFun/releases/latest/download/latest.json - TAURI_UPDATER_FALLBACK_ENDPOINT: https://openbitfun.com/release/latest.json - # Nightly relay archives are signed too (linux-binaries.yml receives the - # key), so nightly Desktop needs the same trust root to verify them. - BITFUN_RELEASE_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} - - strategy: - fail-fast: false - matrix: - platform: - - os: ubuntu-latest - name: linux-x64 - target: x86_64-unknown-linux-gnu - build_command: pnpm run desktop:build:linux -- --target x86_64-unknown-linux-gnu --bundles deb,rpm,appimage - - os: ubuntu-24.04-arm - name: linux-arm64 - target: aarch64-unknown-linux-gnu - # Fat LTO exhausts the hosted ARM runner while linking bitfun-desktop. - build_command: CARGO_PROFILE_RELEASE_LTO=thin pnpm run desktop:build:linux -- --target aarch64-unknown-linux-gnu --bundles deb,rpm,appimage - - os: macos-15 - name: macos-arm64 - target: aarch64-apple-darwin - build_command: pnpm run desktop:build:arm64 - - os: macos-15-intel - name: macos-x64 - target: x86_64-apple-darwin - build_command: pnpm run desktop:build:x86_64 - - os: windows-latest - name: windows-x64 - target: x86_64-pc-windows-msvc - build_command: pnpm run installer:build - - steps: - - uses: actions/checkout@v5 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: "1.3.14" - - - name: Install Linux system dependencies (Tauri bundler) - if: runner.os == 'Linux' - shell: bash - run: | - sudo apt-get update - - if apt-cache show libwebkit2gtk-4.1-dev >/dev/null 2>&1; then - WEBKIT_PKG=libwebkit2gtk-4.1-dev - else - WEBKIT_PKG=libwebkit2gtk-4.0-dev - fi - - if apt-cache show libappindicator3-dev >/dev/null 2>&1; then - APPINDICATOR_PKG=libappindicator3-dev - else - APPINDICATOR_PKG=libayatana-appindicator3-dev - fi - - # Tauri pins AppImage GTK input methods to its bundled cache, so the - # fcitx5 GTK3 bridge must be present before linuxdeploy builds it. - sudo apt-get install -y --no-install-recommends \ - pkg-config \ - xdg-utils \ - libglib2.0-dev \ - libgtk-3-dev \ - fcitx5-frontend-gtk3 \ - libxdo-dev \ - "$WEBKIT_PKG" \ - "$APPINDICATOR_PKG" \ - librsvg2-dev \ - patchelf \ - fakeroot \ - rpm \ - libleptonica-dev \ - libtesseract-dev \ - tesseract-ocr \ - tesseract-ocr-eng - - - name: Setup pnpm - uses: pnpm/action-setup@v5 - - - name: Setup Node.js - uses: actions/setup-node@v5 - with: - node-version: 22 - cache: pnpm - - - name: Setup Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.platform.target }} - - - name: Cache Rust build - uses: swatinem/rust-cache@v2 - with: - shared-key: "nightly-v2-${{ matrix.platform.name }}" - cache-bin: false - - - name: Verify committed Cargo metadata - run: | - cargo metadata --locked --no-deps - cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build plugin Host resources - run: | - bun install --cwd src/apps/extension-host --frozen-lockfile - bun run --cwd src/apps/extension-host build - - - name: Generate web API bindings - run: pnpm --dir src/web-ui run gen:types - - - name: Type-check web UI - run: pnpm run type-check:web - - - name: Patch nightly version - shell: bash - env: - NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} - run: | - set -euo pipefail - - echo "Patching version to $NIGHTLY_VERSION" - ASSET_VERSION="${NIGHTLY_VERSION%%+*}" - node scripts/set-build-version.mjs --version "$ASSET_VERSION" - - echo "package.json version: $(jq -r '.version' package.json)" - echo "Cargo.toml version: $(grep 'x-release-please-version' Cargo.toml)" - - - name: Verify projected Cargo metadata - run: | - cargo metadata --locked --no-deps - cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml - - - name: Run Windows CLI terminal contracts - if: runner.os == 'Windows' - env: - CARGO_INCREMENTAL: "0" - CARGO_PROFILE_DEV_DEBUG: "0" - CARGO_PROFILE_TEST_DEBUG: "0" - run: cargo test --locked -p bitfun-cli --test terminal_process_contracts -- --test-threads=1 - - - name: Build desktop app - run: ${{ matrix.platform.build_command }} - - - name: Package macOS CLI for SSH dispatch - if: runner.os == 'macOS' - id: macos-cli - shell: bash - env: - NIGHTLY_VERSION: ${{ needs.check-changes.outputs.nightly_version }} - TARGET: ${{ matrix.platform.target }} - run: | - set -euo pipefail - ASSET_VERSION="${NIGHTLY_VERSION%%+*}" - cargo build --release --target "$TARGET" -p bitfun-cli - bash scripts/cli/package-unix.sh "$ASSET_VERSION" "$TARGET" - - - name: Sign macOS CLI archive and checksum - if: runner.os == 'macOS' - shell: bash - env: - BITFUN_SIGNING_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - BITFUN_SIGNING_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - BITFUN_SIGNING_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} - run: | - set -euo pipefail - bash scripts/sign-release-assets.sh \ - "${{ steps.macos-cli.outputs.archive }}" \ - "${{ steps.macos-cli.outputs.checksum }}" - - - name: Verify AppImage fcitx5 GTK module - if: runner.os == 'Linux' - shell: bash - run: bash scripts/ci/verify-appimage-fcitx.sh "${{ matrix.platform.target }}" - - - name: Upload bundles - uses: actions/upload-artifact@v6 - with: - name: bitfun-nightly-${{ matrix.platform.name }}-bundle - if-no-files-found: error - retention-days: ${{ inputs.artifact_retention_days || 7 }} - path: | - target/*/release/bundle - target/release/bundle - src/apps/desktop/target/release/bundle - BitFun-Installer/src-tauri/target/release/bitfun-installer.exe - bitfun-cli-*-apple-darwin.tar.gz - bitfun-cli-*-apple-darwin.tar.gz.sha256 - bitfun-cli-*-apple-darwin.tar.gz.sig - bitfun-cli-*-apple-darwin.tar.gz.sha256.sig - - linux-binaries: - name: Linux CLI and Relay Server + # ── Build every Nightly artifact through the reusable CI contract ── + build-artifacts: + name: Build Nightly Artifacts needs: check-changes if: needs.check-changes.outputs.should_build == 'true' - uses: ./.github/workflows/linux-binaries.yml + uses: ./.github/workflows/nightly-artifacts.yml + permissions: + contents: read secrets: - release_signing_key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - release_signing_password: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - release_pubkey: ${{ secrets.TAURI_UPDATER_PUBKEY }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_UPDATER_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} with: checkout_ref: ${{ github.sha }} version: ${{ needs.check-changes.outputs.nightly_version }} @@ -320,7 +90,7 @@ jobs: # ── Publish nightly pre-release ──────────────────────────────────── publish-nightly: name: Publish Nightly - needs: [check-changes, package, linux-binaries] + needs: [check-changes, build-artifacts] if: >- needs.check-changes.outputs.should_build == 'true' && inputs.build_only != true diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index ea9879fcf9..c2bd2b9b56 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -413,13 +413,14 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', (t) assert.equal(buildJob.name, 'Nightly Build Contract'); assert.equal(buildJob.needs, 'rust-impact'); - assert.equal(buildJob.uses, './.github/workflows/nightly.yml'); + assert.equal(buildJob.uses, './.github/workflows/nightly-artifacts.yml'); assert.match(buildJob.if, /rust-impact\.result == 'success'/); assert.doesNotMatch(buildJob.if, /rust_required/); assert.deepEqual(buildJob.permissions, { contents: 'read' }); assert.deepEqual(buildJob.with, { - force_build: true, - build_only: true, + checkout_ref: '${{ github.sha }}', + version: '0.0.0-nightly.ci.${{ github.run_id }}', + artifact_prefix: 'ci-${{ github.run_id }}', artifact_retention_days: 1, }); @@ -520,7 +521,10 @@ foreach ($case in $cases) { test('nightly validates generated inputs and projected lockfiles before packaging', () => { const workflow = yaml.parse( - readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), + readFileSync( + path.join(repoRoot, '.github/workflows/nightly-artifacts.yml'), + 'utf8', + ), ); const callInputs = workflow.on.workflow_call.inputs; const packageJob = workflow.jobs.package; @@ -544,18 +548,11 @@ test('nightly validates generated inputs and projected lockfiles before packagin (step) => step.name === 'Build desktop app', ); - assert.equal(callInputs.force_build.default, true); - assert.equal(callInputs.build_only.default, true); + assert.equal(callInputs.checkout_ref.required, true); + assert.equal(callInputs.version.required, true); + assert.equal(callInputs.artifact_prefix.required, true); assert.equal(callInputs.artifact_retention_days.default, 1); assert.equal(workflow.permissions.contents, 'read'); - assert.match( - workflow.jobs['publish-nightly'].if, - /inputs\.build_only != true/, - ); - assert.deepEqual(workflow.jobs['publish-nightly'].permissions, { - contents: 'write', - packages: 'write', - }); assert.notEqual(committedMetadataIndex, -1); assert.notEqual(generationIndex, -1); @@ -591,6 +588,30 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); }); +test('nightly orchestrates the shared build before the separately privileged publish', () => { + const workflow = yaml.parse( + readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), + ); + const build = workflow.jobs['build-artifacts']; + const publish = workflow.jobs['publish-nightly']; + + assert.equal(workflow.on.workflow_call, undefined); + assert.equal(build.uses, './.github/workflows/nightly-artifacts.yml'); + assert.deepEqual(build.permissions, { contents: 'read' }); + assert.deepEqual(build.with, { + checkout_ref: '${{ github.sha }}', + version: '${{ needs.check-changes.outputs.nightly_version }}', + artifact_prefix: 'nightly', + artifact_retention_days: '${{ inputs.artifact_retention_days || 7 }}', + }); + assert.deepEqual(publish.needs, ['check-changes', 'build-artifacts']); + assert.match(publish.if, /inputs\.build_only != true/); + assert.deepEqual(publish.permissions, { + contents: 'write', + packages: 'write', + }); +}); + test('Linux binary packaging uses the shared locked version projection contract', () => { const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/linux-binaries.yml'), 'utf8'), @@ -868,23 +889,29 @@ test('beta publishing cannot advance the Relay latest image tag', () => { }); test('nightly and beta use the shared build-version projection', () => { + const artifacts = yaml.parse( + readFileSync( + path.join(repoRoot, '.github/workflows/nightly-artifacts.yml'), + 'utf8', + ), + ); const nightly = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), ); - const patch = nightly.jobs.package.steps.find( + const patch = artifacts.jobs.package.steps.find( (step) => step.name === 'Patch nightly version', ); assert.match(patch.run, /node scripts\/set-build-version\.mjs/); - assert.equal(nightly.jobs.package.env.BITFUN_RELEASE_CHANNEL, 'nightly'); + assert.equal(artifacts.jobs.package.env.BITFUN_RELEASE_CHANNEL, 'nightly'); assert.equal( - nightly.jobs.package.env.TAURI_UPDATER_ENDPOINT, + artifacts.jobs.package.env.TAURI_UPDATER_ENDPOINT, 'https://github.com/GCWing/BitFun/releases/latest/download/latest.json', ); assert.equal( - nightly.jobs.package.env.TAURI_UPDATER_FALLBACK_ENDPOINT, + artifacts.jobs.package.env.TAURI_UPDATER_FALLBACK_ENDPOINT, 'https://openbitfun.com/release/latest.json', ); - assert.equal(nightly.jobs.package.env.BITFUN_ENABLE_UPDATER_ARTIFACTS, undefined); + assert.equal(artifacts.jobs.package.env.BITFUN_ENABLE_UPDATER_ARTIFACTS, undefined); const signingStep = nightly.jobs['publish-nightly'].steps.find( (step) => step.name === 'Sign installer packages', ); From f9cab79d21e10712d755708c0592654637106e74 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 17:45:47 +0800 Subject: [PATCH 05/12] fix(installer): align locked Tauri packages --- .github/workflows/nightly-artifacts.yml | 11 ++ BitFun-Installer/package-lock.json | 114 +++++++++---------- BitFun-Installer/package.json | 6 +- package.json | 2 +- pnpm-lock.yaml | 142 ++++++++++++------------ scripts/check-github-config.test.mjs | 10 ++ src/web-ui/package.json | 4 +- 7 files changed, 155 insertions(+), 134 deletions(-) diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index 23b418d302..0f9887c327 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -149,6 +149,17 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Verify Installer Tauri package alignment + if: runner.os == 'Windows' + shell: bash + run: | + set -euo pipefail + info="$(pnpm --dir BitFun-Installer exec tauri info 2>&1)" + printf '%s\n' "$info" + if grep -Fq 'Error: Found version mismatched Tauri packages.' <<<"$info"; then + exit 1 + fi + - name: Build plugin Host resources run: | bun install --cwd src/apps/extension-host --frozen-lockfile diff --git a/BitFun-Installer/package-lock.json b/BitFun-Installer/package-lock.json index 7461b70a7b..5227a80517 100644 --- a/BitFun-Installer/package-lock.json +++ b/BitFun-Installer/package-lock.json @@ -8,15 +8,15 @@ "name": "bitfun-installer", "version": "0.2.19", "dependencies": { - "@tauri-apps/api": "^2.10.1", - "@tauri-apps/plugin-dialog": "^2.6.0", + "@tauri-apps/api": "^2.11.1", + "@tauri-apps/plugin-dialog": "^2.7.2", "i18next": "^25.8.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^16.5.3" }, "devDependencies": { - "@tauri-apps/cli": "^2.10.0", + "@tauri-apps/cli": "^2.11.4", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.6.0", @@ -1177,9 +1177,9 @@ ] }, "node_modules/@tauri-apps/api": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", - "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", "license": "Apache-2.0 OR MIT", "funding": { "type": "opencollective", @@ -1187,9 +1187,9 @@ } }, "node_modules/@tauri-apps/cli": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz", - "integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", "dev": true, "license": "Apache-2.0 OR MIT", "bin": { @@ -1203,23 +1203,23 @@ "url": "https://opencollective.com/tauri" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.10.0", - "@tauri-apps/cli-darwin-x64": "2.10.0", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", - "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", - "@tauri-apps/cli-linux-arm64-musl": "2.10.0", - "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-musl": "2.10.0", - "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", - "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", - "@tauri-apps/cli-win32-x64-msvc": "2.10.0" + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" } }, "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz", - "integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", "cpu": [ "arm64" ], @@ -1234,9 +1234,9 @@ } }, "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", - "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", "cpu": [ "x64" ], @@ -1251,9 +1251,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", - "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", "cpu": [ "arm" ], @@ -1268,9 +1268,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", - "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", "cpu": [ "arm64" ], @@ -1285,9 +1285,9 @@ } }, "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", - "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", "cpu": [ "arm64" ], @@ -1302,9 +1302,9 @@ } }, "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", - "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", "cpu": [ "riscv64" ], @@ -1319,9 +1319,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", - "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", "cpu": [ "x64" ], @@ -1336,9 +1336,9 @@ } }, "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", - "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", "cpu": [ "x64" ], @@ -1353,9 +1353,9 @@ } }, "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", - "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", "cpu": [ "arm64" ], @@ -1370,9 +1370,9 @@ } }, "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", - "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", "cpu": [ "ia32" ], @@ -1387,9 +1387,9 @@ } }, "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", - "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", "cpu": [ "x64" ], @@ -1404,12 +1404,12 @@ } }, "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz", - "integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.7.2.tgz", + "integrity": "sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg==", "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.8.0" + "@tauri-apps/api": "^2.11.0" } }, "node_modules/@types/babel__core": { diff --git a/BitFun-Installer/package.json b/BitFun-Installer/package.json index 0ec21ff24b..57eee05249 100644 --- a/BitFun-Installer/package.json +++ b/BitFun-Installer/package.json @@ -24,15 +24,15 @@ "type-check": "tsc --noEmit" }, "dependencies": { - "@tauri-apps/api": "^2.10.1", - "@tauri-apps/plugin-dialog": "^2.6.0", + "@tauri-apps/api": "^2.11.1", + "@tauri-apps/plugin-dialog": "^2.7.2", "i18next": "^25.8.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^16.5.3" }, "devDependencies": { - "@tauri-apps/cli": "^2.10.0", + "@tauri-apps/cli": "^2.11.4", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.6.0", diff --git a/package.json b/package.json index c2d0a16370..a999a94af8 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ }, "devDependencies": { "@noble/hashes": "^2.0.1", - "@tauri-apps/cli": "^2.10.0", + "@tauri-apps/cli": "^2.11.4", "@vitejs/plugin-react": "^4.6.0", "copyfiles": "^2.4.1", "cross-env": "^10.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db419207b9..4969dabc3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ importers: specifier: ^2.0.1 version: 2.0.1 '@tauri-apps/cli': - specifier: ^2.10.0 - version: 2.10.0 + specifier: ^2.11.4 + version: 2.11.4 '@vitejs/plugin-react': specifier: ^4.6.0 version: 4.7.0(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(sass@1.97.3)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) @@ -57,11 +57,11 @@ importers: BitFun-Installer: dependencies: '@tauri-apps/api': - specifier: ^2.10.1 - version: 2.10.1 + specifier: ^2.11.1 + version: 2.11.1 '@tauri-apps/plugin-dialog': - specifier: ^2.6.0 - version: 2.6.0 + specifier: ^2.7.2 + version: 2.7.2 i18next: specifier: ^25.8.0 version: 25.8.0(typescript@5.8.3) @@ -76,8 +76,8 @@ importers: version: 16.5.4(i18next@25.8.0(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) devDependencies: '@tauri-apps/cli': - specifier: ^2.10.0 - version: 2.10.0 + specifier: ^2.11.4 + version: 2.11.4 '@types/react': specifier: ^18.3.0 version: 18.3.27 @@ -232,14 +232,14 @@ importers: specifier: ^3.13.23 version: 3.13.23(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tauri-apps/api': - specifier: ^2.10.1 - version: 2.10.1 + specifier: ^2.11.1 + version: 2.11.1 '@tauri-apps/plugin-autostart': specifier: ^2.0.0 version: 2.5.1 '@tauri-apps/plugin-dialog': - specifier: ^2.6.0 - version: 2.6.0 + specifier: ^2.7.2 + version: 2.7.2 '@tauri-apps/plugin-fs': specifier: ^2.0.0 version: 2.4.5 @@ -1586,85 +1586,85 @@ packages: '@tanstack/virtual-core@3.13.23': resolution: {integrity: sha512-zSz2Z2HNyLjCplANTDyl3BcdQJc2k1+yyFoKhNRmCr7V7dY8o8q5m8uFTI1/Pg1kL+Hgrz6u3Xo6eFUB7l66cg==} - '@tauri-apps/api@2.10.1': - resolution: {integrity: sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==} + '@tauri-apps/api@2.11.1': + resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==} - '@tauri-apps/cli-darwin-arm64@2.10.0': - resolution: {integrity: sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==} + '@tauri-apps/cli-darwin-arm64@2.11.4': + resolution: {integrity: sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tauri-apps/cli-darwin-x64@2.10.0': - resolution: {integrity: sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==} + '@tauri-apps/cli-darwin-x64@2.11.4': + resolution: {integrity: sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tauri-apps/cli-linux-arm-gnueabihf@2.10.0': - resolution: {integrity: sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==} + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': + resolution: {integrity: sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tauri-apps/cli-linux-arm64-gnu@2.10.0': - resolution: {integrity: sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==} + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': + resolution: {integrity: sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-arm64-musl@2.10.0': - resolution: {integrity: sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==} + '@tauri-apps/cli-linux-arm64-musl@2.11.4': + resolution: {integrity: sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tauri-apps/cli-linux-riscv64-gnu@2.10.0': - resolution: {integrity: sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==} + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': + resolution: {integrity: sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - '@tauri-apps/cli-linux-x64-gnu@2.10.0': - resolution: {integrity: sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==} + '@tauri-apps/cli-linux-x64-gnu@2.11.4': + resolution: {integrity: sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-linux-x64-musl@2.10.0': - resolution: {integrity: sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==} + '@tauri-apps/cli-linux-x64-musl@2.11.4': + resolution: {integrity: sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tauri-apps/cli-win32-arm64-msvc@2.10.0': - resolution: {integrity: sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==} + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': + resolution: {integrity: sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tauri-apps/cli-win32-ia32-msvc@2.10.0': - resolution: {integrity: sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==} + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': + resolution: {integrity: sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@tauri-apps/cli-win32-x64-msvc@2.10.0': - resolution: {integrity: sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==} + '@tauri-apps/cli-win32-x64-msvc@2.11.4': + resolution: {integrity: sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tauri-apps/cli@2.10.0': - resolution: {integrity: sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==} + '@tauri-apps/cli@2.11.4': + resolution: {integrity: sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==} engines: {node: '>= 10'} hasBin: true '@tauri-apps/plugin-autostart@2.5.1': resolution: {integrity: sha512-zS/xx7yzveCcotkA+8TqkI2lysmG2wvQXv2HGAVExITmnFfHAdj1arGsbbfs3o6EktRHf6l34pJxc3YGG2mg7w==} - '@tauri-apps/plugin-dialog@2.6.0': - resolution: {integrity: sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==} + '@tauri-apps/plugin-dialog@2.7.2': + resolution: {integrity: sha512-pX0IGm1I3I6wc+zeKYcq1GSqogK6okCNX5fOdaNU5ab1AjGS6l1E5wFNjEb7meg7ZFSp0JUs+0jQGQNyOvLrsg==} '@tauri-apps/plugin-fs@2.4.5': resolution: {integrity: sha512-dVxWWGE6VrOxC7/jlhyE+ON/Cc2REJlM35R3PJX3UvFw2XwYhLGQVAIyrehenDdKjotipjYEVc4YjOl3qq90fA==} @@ -6683,78 +6683,78 @@ snapshots: '@tanstack/virtual-core@3.13.23': {} - '@tauri-apps/api@2.10.1': {} + '@tauri-apps/api@2.11.1': {} - '@tauri-apps/cli-darwin-arm64@2.10.0': + '@tauri-apps/cli-darwin-arm64@2.11.4': optional: true - '@tauri-apps/cli-darwin-x64@2.10.0': + '@tauri-apps/cli-darwin-x64@2.11.4': optional: true - '@tauri-apps/cli-linux-arm-gnueabihf@2.10.0': + '@tauri-apps/cli-linux-arm-gnueabihf@2.11.4': optional: true - '@tauri-apps/cli-linux-arm64-gnu@2.10.0': + '@tauri-apps/cli-linux-arm64-gnu@2.11.4': optional: true - '@tauri-apps/cli-linux-arm64-musl@2.10.0': + '@tauri-apps/cli-linux-arm64-musl@2.11.4': optional: true - '@tauri-apps/cli-linux-riscv64-gnu@2.10.0': + '@tauri-apps/cli-linux-riscv64-gnu@2.11.4': optional: true - '@tauri-apps/cli-linux-x64-gnu@2.10.0': + '@tauri-apps/cli-linux-x64-gnu@2.11.4': optional: true - '@tauri-apps/cli-linux-x64-musl@2.10.0': + '@tauri-apps/cli-linux-x64-musl@2.11.4': optional: true - '@tauri-apps/cli-win32-arm64-msvc@2.10.0': + '@tauri-apps/cli-win32-arm64-msvc@2.11.4': optional: true - '@tauri-apps/cli-win32-ia32-msvc@2.10.0': + '@tauri-apps/cli-win32-ia32-msvc@2.11.4': optional: true - '@tauri-apps/cli-win32-x64-msvc@2.10.0': + '@tauri-apps/cli-win32-x64-msvc@2.11.4': optional: true - '@tauri-apps/cli@2.10.0': + '@tauri-apps/cli@2.11.4': optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 2.10.0 - '@tauri-apps/cli-darwin-x64': 2.10.0 - '@tauri-apps/cli-linux-arm-gnueabihf': 2.10.0 - '@tauri-apps/cli-linux-arm64-gnu': 2.10.0 - '@tauri-apps/cli-linux-arm64-musl': 2.10.0 - '@tauri-apps/cli-linux-riscv64-gnu': 2.10.0 - '@tauri-apps/cli-linux-x64-gnu': 2.10.0 - '@tauri-apps/cli-linux-x64-musl': 2.10.0 - '@tauri-apps/cli-win32-arm64-msvc': 2.10.0 - '@tauri-apps/cli-win32-ia32-msvc': 2.10.0 - '@tauri-apps/cli-win32-x64-msvc': 2.10.0 + '@tauri-apps/cli-darwin-arm64': 2.11.4 + '@tauri-apps/cli-darwin-x64': 2.11.4 + '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.4 + '@tauri-apps/cli-linux-arm64-gnu': 2.11.4 + '@tauri-apps/cli-linux-arm64-musl': 2.11.4 + '@tauri-apps/cli-linux-riscv64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-gnu': 2.11.4 + '@tauri-apps/cli-linux-x64-musl': 2.11.4 + '@tauri-apps/cli-win32-arm64-msvc': 2.11.4 + '@tauri-apps/cli-win32-ia32-msvc': 2.11.4 + '@tauri-apps/cli-win32-x64-msvc': 2.11.4 '@tauri-apps/plugin-autostart@2.5.1': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.1 - '@tauri-apps/plugin-dialog@2.6.0': + '@tauri-apps/plugin-dialog@2.7.2': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.1 '@tauri-apps/plugin-fs@2.4.5': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.1 '@tauri-apps/plugin-log@2.8.0': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.1 '@tauri-apps/plugin-notification@2.3.3': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.1 '@tauri-apps/plugin-opener@2.5.3': dependencies: - '@tauri-apps/api': 2.10.1 + '@tauri-apps/api': 2.11.1 '@tiptap/core@3.20.4(@tiptap/pm@3.20.4)': dependencies: diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index c2bd2b9b56..ff514abad8 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -541,6 +541,9 @@ test('nightly validates generated inputs and projected lockfiles before packagin const patchIndex = steps.findIndex( (step) => step.name === 'Patch nightly version', ); + const tauriAlignmentIndex = steps.findIndex( + (step) => step.name === 'Verify Installer Tauri package alignment', + ); const metadataIndex = steps.findIndex( (step) => step.name === 'Verify projected Cargo metadata', ); @@ -555,6 +558,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin assert.equal(workflow.permissions.contents, 'read'); assert.notEqual(committedMetadataIndex, -1); + assert.notEqual(tauriAlignmentIndex, -1); assert.notEqual(generationIndex, -1); assert.notEqual(typeCheckIndex, -1); assert.equal( @@ -567,6 +571,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); assert.ok( committedMetadataIndex < patchIndex && + tauriAlignmentIndex < patchIndex && typeCheckIndex < patchIndex && patchIndex < metadataIndex && metadataIndex < buildIndex, @@ -577,6 +582,11 @@ test('nightly validates generated inputs and projected lockfiles before packagin 'cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml\n'; assert.equal(steps[committedMetadataIndex].run, expectedMetadata); assert.equal(steps[metadataIndex].run, expectedMetadata); + assert.equal(steps[tauriAlignmentIndex].if, "runner.os == 'Windows'"); + assert.match( + steps[tauriAlignmentIndex].run, + /Found version mismatched Tauri packages/, + ); assert.equal( steps.some((step) => step.run?.includes('cargo generate-lockfile')), false, diff --git a/src/web-ui/package.json b/src/web-ui/package.json index 298635a74e..a5471871b7 100644 --- a/src/web-ui/package.json +++ b/src/web-ui/package.json @@ -22,9 +22,9 @@ "dependencies": { "@monaco-editor/react": "^4.6.0", "@tanstack/react-virtual": "^3.13.23", - "@tauri-apps/api": "^2.10.1", + "@tauri-apps/api": "^2.11.1", "@tauri-apps/plugin-autostart": "^2.0.0", - "@tauri-apps/plugin-dialog": "^2.6.0", + "@tauri-apps/plugin-dialog": "^2.7.2", "@tauri-apps/plugin-fs": "^2.0.0", "@tauri-apps/plugin-log": "^2.8.0", "@tauri-apps/plugin-notification": "^2.3.3", From 78a28c76dad94a63f3c79298bc90a2bd05db031d Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 17:51:33 +0800 Subject: [PATCH 06/12] fix(ci): coerce nightly retention input --- .github/workflows/nightly.yml | 2 +- scripts/check-github-config.test.mjs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e27b445884..a0bc6723a0 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -85,7 +85,7 @@ jobs: checkout_ref: ${{ github.sha }} version: ${{ needs.check-changes.outputs.nightly_version }} artifact_prefix: nightly - artifact_retention_days: ${{ inputs.artifact_retention_days || 7 }} + artifact_retention_days: ${{ fromJSON(inputs.artifact_retention_days || '7') }} # ── Publish nightly pre-release ──────────────────────────────────── publish-nightly: diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index ff514abad8..f9fca3917a 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -612,7 +612,8 @@ test('nightly orchestrates the shared build before the separately privileged pub checkout_ref: '${{ github.sha }}', version: '${{ needs.check-changes.outputs.nightly_version }}', artifact_prefix: 'nightly', - artifact_retention_days: '${{ inputs.artifact_retention_days || 7 }}', + artifact_retention_days: + "${{ fromJSON(inputs.artifact_retention_days || '7') }}", }); assert.deepEqual(publish.needs, ['check-changes', 'build-artifacts']); assert.match(publish.if, /inputs\.build_only != true/); From 7f5bf9e0efabafccbd89c9a938cd2bfa6d0b7a6c Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 19:54:52 +0800 Subject: [PATCH 07/12] fix(installer): support reorganized locale sources --- .github/workflows/nightly-artifacts.yml | 4 ++ BitFun-Installer/scripts/sync-model-i18n.cjs | 35 ++++++++++---- BitFun-Installer/scripts/sync-theme-i18n.cjs | 48 +++++++++++--------- scripts/check-github-config.test.mjs | 10 ++++ 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index 0f9887c327..b4afde1e94 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -149,6 +149,10 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Verify Installer i18n projection + if: runner.os == 'Windows' + run: pnpm --dir BitFun-Installer run sync:i18n + - name: Verify Installer Tauri package alignment if: runner.os == 'Windows' shell: bash diff --git a/BitFun-Installer/scripts/sync-model-i18n.cjs b/BitFun-Installer/scripts/sync-model-i18n.cjs index 6a0640d22c..94c696b2aa 100644 --- a/BitFun-Installer/scripts/sync-model-i18n.cjs +++ b/BitFun-Installer/scripts/sync-model-i18n.cjs @@ -8,6 +8,30 @@ function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } +function resolveModelSettingsPath(localeDir) { + const settingsDir = path.join( + PROJECT_ROOT, + 'src', + 'web-ui', + 'src', + 'locales', + localeDir, + 'settings' + ); + const candidates = ['ai-model.json', 'models.json']; + const sourcePath = candidates + .map((fileName) => path.join(settingsDir, fileName)) + .find((candidate) => fs.existsSync(candidate)); + + if (!sourcePath) { + throw new Error( + `Missing model settings locale for ${localeDir}; expected one of: ${candidates.join(', ')}` + ); + } + + return sourcePath; +} + function writeJson(filePath, data) { fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, 'utf8'); } @@ -201,16 +225,7 @@ const LOCALE_MAP = { function syncOne(languageTag) { const { localeDir, installerLocale } = LOCALE_MAP[languageTag]; - const sourceAiModelPath = path.join( - PROJECT_ROOT, - 'src', - 'web-ui', - 'src', - 'locales', - localeDir, - 'settings', - 'ai-model.json' - ); + const sourceAiModelPath = resolveModelSettingsPath(localeDir); const sourceComponentsPath = path.join( PROJECT_ROOT, 'src', diff --git a/BitFun-Installer/scripts/sync-theme-i18n.cjs b/BitFun-Installer/scripts/sync-theme-i18n.cjs index 53f4505e5d..fac157f66a 100644 --- a/BitFun-Installer/scripts/sync-theme-i18n.cjs +++ b/BitFun-Installer/scripts/sync-theme-i18n.cjs @@ -24,8 +24,32 @@ function writeJson(filePath, data) { fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8"); } +function resolveApplicationSettingsPath(localeDir) { + const settingsDir = path.join( + PROJECT_ROOT, + "src", + "web-ui", + "src", + "locales", + localeDir, + "settings" + ); + const candidates = ["basics.json", "application.json"]; + const sourcePath = candidates + .map((fileName) => path.join(settingsDir, fileName)) + .find((candidate) => fs.existsSync(candidate)); + + if (!sourcePath) { + throw new Error( + `Missing application settings locale for ${localeDir}; expected one of: ${candidates.join(", ")}` + ); + } + + return sourcePath; +} + function extractThemeNames(source, sourceLabel) { - // Theme preset names live under settings/basics.json → appearance.presets (formerly theme.json → theme.presets). + // Theme preset names live under appearance.presets in the application settings locale. const presets = source?.appearance?.presets; if (!presets || typeof presets !== "object") { throw new Error(`Invalid appearance.presets in ${sourceLabel}`); @@ -55,26 +79,8 @@ function injectThemeNames(target, themeNames) { } function main() { - const sourceEnPath = path.join( - PROJECT_ROOT, - "src", - "web-ui", - "src", - "locales", - "en-US", - "settings", - "basics.json" - ); - const sourceZhPath = path.join( - PROJECT_ROOT, - "src", - "web-ui", - "src", - "locales", - "zh-CN", - "settings", - "basics.json" - ); + const sourceEnPath = resolveApplicationSettingsPath("en-US"); + const sourceZhPath = resolveApplicationSettingsPath("zh-CN"); const targetEnPath = path.join( INSTALLER_ROOT, diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index f9fca3917a..bdae5beed3 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -544,6 +544,9 @@ test('nightly validates generated inputs and projected lockfiles before packagin const tauriAlignmentIndex = steps.findIndex( (step) => step.name === 'Verify Installer Tauri package alignment', ); + const installerI18nIndex = steps.findIndex( + (step) => step.name === 'Verify Installer i18n projection', + ); const metadataIndex = steps.findIndex( (step) => step.name === 'Verify projected Cargo metadata', ); @@ -558,6 +561,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin assert.equal(workflow.permissions.contents, 'read'); assert.notEqual(committedMetadataIndex, -1); + assert.notEqual(installerI18nIndex, -1); assert.notEqual(tauriAlignmentIndex, -1); assert.notEqual(generationIndex, -1); assert.notEqual(typeCheckIndex, -1); @@ -571,6 +575,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); assert.ok( committedMetadataIndex < patchIndex && + installerI18nIndex < patchIndex && tauriAlignmentIndex < patchIndex && typeCheckIndex < patchIndex && patchIndex < metadataIndex && @@ -582,6 +587,11 @@ test('nightly validates generated inputs and projected lockfiles before packagin 'cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml\n'; assert.equal(steps[committedMetadataIndex].run, expectedMetadata); assert.equal(steps[metadataIndex].run, expectedMetadata); + assert.equal(steps[installerI18nIndex].if, "runner.os == 'Windows'"); + assert.equal( + steps[installerI18nIndex].run, + 'pnpm --dir BitFun-Installer run sync:i18n', + ); assert.equal(steps[tauriAlignmentIndex].if, "runner.os == 'Windows'"); assert.match( steps[tauriAlignmentIndex].run, From 6fdd9ca6debb432ad06bc11896db52c317a160a7 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 21:21:40 +0800 Subject: [PATCH 08/12] fix(ci): predict release impact without polluting caches --- .github/workflows/ci.yml | 170 +++++++--- .github/workflows/linux-binaries.yml | 15 +- .github/workflows/nightly-artifacts.yml | 98 ++++-- .github/workflows/nightly.yml | 7 + scripts/check-github-config.test.mjs | 219 ++++++++++--- scripts/ci/classify-build-impact.mjs | 335 ++++++++++++++++++++ scripts/ci/classify-build-impact.test.mjs | 270 ++++++++++++++++ scripts/ci/classify-rust-impact.mjs | 174 ---------- scripts/ci/classify-rust-impact.test.mjs | 231 -------------- src/apps/cli/src/dispatch/runner.rs | 24 +- src/apps/desktop/src/embedded_relay_host.rs | 37 ++- 11 files changed, 1051 insertions(+), 529 deletions(-) create mode 100644 scripts/ci/classify-build-impact.mjs create mode 100644 scripts/ci/classify-build-impact.test.mjs delete mode 100644 scripts/ci/classify-rust-impact.mjs delete mode 100644 scripts/ci/classify-rust-impact.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14ab53d805..784caa2b53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,13 +19,19 @@ permissions: contents: read jobs: - # ── Rust / CLI impact: conservative scheduling decision ──────────── - rust-impact: - name: Rust / CLI Impact + # ── Build impact: fast checks plus the smallest useful package set ── + build-impact: + name: Build Impact runs-on: ubuntu-latest timeout-minutes: 5 outputs: rust_required: ${{ steps.classify.outputs.rust_required }} + frontend_required: ${{ steps.classify.outputs.frontend_required }} + desktop_packages_required: ${{ steps.classify.outputs.desktop_packages_required }} + desktop_platforms: ${{ steps.classify.outputs.desktop_platforms }} + linux_binaries_required: ${{ steps.classify.outputs.linux_binaries_required }} + dsh_profile_required: ${{ steps.classify.outputs.dsh_profile_required }} + package_required: ${{ steps.classify.outputs.package_required }} reason: ${{ steps.classify.outputs.reason }} changed_count: ${{ steps.classify.outputs.changed_count }} steps: @@ -41,13 +47,13 @@ jobs: node-version-file: package.json package-manager-cache: false - - name: Classify Rust and CLI impact + - name: Classify build impact id: classify env: BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} RANGE_MODE: ${{ github.event_name == 'pull_request' && 'merge-base' || 'direct' }} - run: node scripts/ci/classify-rust-impact.mjs --base "$BASE_SHA" --head "$HEAD_SHA" --range-mode "$RANGE_MODE" + run: node scripts/ci/classify-build-impact.mjs --base "$BASE_SHA" --head "$HEAD_SHA" --range-mode "$RANGE_MODE" # ── Shell deploy assets: LF endings + syntax ─────────────────────── # Relay one-click deploy embeds these scripts into the Desktop binary and @@ -109,8 +115,8 @@ jobs: # ── CLI: independent tests ───────────────────────────────────────── cli-test: name: CLI Tests (${{ matrix.os }}) - needs: rust-impact - if: ${{ !cancelled() && needs.rust-impact.outputs.rust_required != 'false' }} + needs: build-impact + if: ${{ !cancelled() && needs.build-impact.outputs.rust_required != 'false' }} runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: @@ -143,11 +149,11 @@ jobs: - uses: swatinem/rust-cache@v2 with: - shared-key: "cli-ci-v2-${{ matrix.cache_key }}" + shared-key: "cli-ci-v3-${{ github.base_ref || github.ref_name }}-${{ matrix.cache_key }}" cache-bin: false # PRs restore trusted caches but never publish merge-ref artifacts. - save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - cache-on-failure: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + save-if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/1.0.0-explore') }} + cache-on-failure: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/1.0.0-explore') }} - name: Run CLI and ACP tests on macOS if: runner.os == 'macOS' @@ -178,8 +184,8 @@ jobs: # ── Rust: build check ───────────────────────────────────────────── rust-build-check: name: Rust Build Check (${{ matrix.os }}) - needs: rust-impact - if: ${{ !cancelled() && needs.rust-impact.outputs.rust_required != 'false' }} + needs: build-impact + if: ${{ !cancelled() && needs.build-impact.outputs.rust_required != 'false' }} runs-on: ${{ matrix.os }} env: # Keep the workspace check plus desktop test profiles within hosted-runner disk limits. @@ -243,12 +249,12 @@ jobs: - uses: swatinem/rust-cache@v2 with: - shared-key: "ci-check-v5-${{ runner.os }}-no-cargo-bin-v1" + shared-key: "ci-check-v6-${{ github.base_ref || github.ref_name }}-${{ runner.os }}-no-cargo-bin" cache-bin: false # PR caches are scoped to merge refs; trusted main pushes own shared # refreshes and retain completed dependency builds after late test failures. - save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} - cache-on-failure: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + save-if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/1.0.0-explore') }} + cache-on-failure: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/1.0.0-explore') }} # Validate the committed manifests and lockfile before any release # projection is allowed to rewrite workspace package entries. @@ -262,7 +268,7 @@ jobs: uses: actions/cache/restore@v5 with: path: target/sherpa-onnx-prebuilt - key: sherpa-onnx-v1-${{ runner.os }}-${{ runner.arch }}-1.13.4-static + key: sherpa-onnx-v2-${{ github.base_ref || github.ref_name }}-${{ runner.os }}-${{ runner.arch }}-1.13.4-static - name: Repair missing Sherpa native state shell: bash @@ -280,12 +286,12 @@ jobs: - name: Save Sherpa native libraries if: >- github.event_name == 'push' && - github.ref == 'refs/heads/main' && + (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/1.0.0-explore') && steps.sherpa-native-cache.outputs.cache-hit != 'true' uses: actions/cache/save@v5 with: path: target/sherpa-onnx-prebuilt - key: sherpa-onnx-v1-${{ runner.os }}-${{ runner.arch }}-1.13.4-static + key: sherpa-onnx-v2-${{ github.base_ref || github.ref_name }}-${{ runner.os }}-${{ runner.arch }}-1.13.4-static # The installer is intentionally excluded from the root Cargo workspace, # so the workspace check above cannot catch drift in its shared Rust APIs. @@ -323,14 +329,17 @@ jobs: - name: Run search tool tests run: "cargo test --locked -p tool-runtime --lib search::" - # Execute the exact five-platform Nightly artifact producers with publishing - # disabled. This is the build contract: a required CI result cannot be green - # unless the same release linking, Tauri bundling, Installer, Extension Host, - # macOS CLI, and Linux CLI/Relay paths used by Nightly all succeed. - nightly-build-contract: - name: Nightly Build Contract - needs: rust-impact - if: ${{ !cancelled() && needs.rust-impact.result == 'success' }} + # Package only paths whose delivery contract changed. Ordinary Rust and Web + # changes stay on fast compile/test/build gates; complete seven-producer + # coverage remains the weekday/manual Nightly owner. + package-impact-contract: + name: Impact-selected Package Contract + needs: build-impact + if: >- + !cancelled() && + github.event_name == 'pull_request' && + needs.build-impact.result == 'success' && + needs.build-impact.outputs.package_required == 'true' uses: ./.github/workflows/nightly-artifacts.yml permissions: contents: read @@ -339,40 +348,46 @@ jobs: version: 0.0.0-nightly.ci.${{ github.run_id }} artifact_prefix: ci-${{ github.run_id }} artifact_retention_days: 1 - - # Stable Rust/CLI result for CI runs: matrix jobs may be skipped for a proven - # Web-only change, but this result verifies why they ran or skipped and makes - # the exact Nightly artifact graph part of the required result. + build_desktop_packages: ${{ needs.build-impact.outputs.desktop_packages_required == 'true' }} + desktop_platforms: ${{ needs.build-impact.outputs.desktop_platforms }} + build_linux_binaries: ${{ needs.build-impact.outputs.linux_binaries_required == 'true' }} + upload_artifacts: false + cache_write: false + + # Stable required result: verify both fast Rust scheduling and whether the + # impact-selected PR package contract ran or intentionally skipped. rust-validation-result: name: Rust / CLI Validation runs-on: ubuntu-latest timeout-minutes: 5 if: ${{ always() }} - needs: [rust-impact, cli-test, rust-build-check, nightly-build-contract] + needs: [build-impact, cli-test, rust-build-check, package-impact-contract] steps: - name: Verify Rust and CLI result shell: pwsh env: - RUST_REQUIRED: ${{ needs.rust-impact.outputs.rust_required }} - IMPACT_RESULT: ${{ needs.rust-impact.result }} + RUST_REQUIRED: ${{ needs.build-impact.outputs.rust_required }} + PACKAGE_REQUIRED: ${{ needs.build-impact.outputs.package_required }} + EVENT_NAME: ${{ github.event_name }} + IMPACT_RESULT: ${{ needs.build-impact.result }} CLI_RESULT: ${{ needs.cli-test.result }} RUST_RESULT: ${{ needs.rust-build-check.result }} - BUILD_RESULT: ${{ needs.nightly-build-contract.result }} + BUILD_RESULT: ${{ needs.package-impact-contract.result }} run: | $ErrorActionPreference = 'Stop' if ($env:IMPACT_RESULT -ne 'success') { - throw "Rust impact classification did not succeed: $env:IMPACT_RESULT" + throw "Build impact classification did not succeed: $env:IMPACT_RESULT" } switch ($env:RUST_REQUIRED) { 'false' { - if ($env:CLI_RESULT -ne 'skipped' -or $env:RUST_RESULT -ne 'skipped' -or $env:BUILD_RESULT -ne 'success') { - throw "Expected skipped Rust/CLI jobs and a successful Nightly build contract for a Web-only change; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT Build=$env:BUILD_RESULT" + if ($env:CLI_RESULT -ne 'skipped' -or $env:RUST_RESULT -ne 'skipped') { + throw "Expected skipped Rust/CLI jobs; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT" } } 'true' { - if ($env:CLI_RESULT -ne 'success' -or $env:RUST_RESULT -ne 'success' -or $env:BUILD_RESULT -ne 'success') { - throw "Expected successful Rust, CLI, and Nightly build jobs; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT Build=$env:BUILD_RESULT" + if ($env:CLI_RESULT -ne 'success' -or $env:RUST_RESULT -ne 'success') { + throw "Expected successful Rust and CLI jobs; CLI=$env:CLI_RESULT Rust=$env:RUST_RESULT" } } default { @@ -380,6 +395,14 @@ jobs: } } + $packageExpected = $env:EVENT_NAME -eq 'pull_request' -and $env:PACKAGE_REQUIRED -eq 'true' + if ($packageExpected -and $env:BUILD_RESULT -ne 'success') { + throw "Expected successful impact-selected package jobs; Build=$env:BUILD_RESULT" + } + if (-not $packageExpected -and $env:BUILD_RESULT -ne 'skipped') { + throw "Expected package jobs to be skipped; Build=$env:BUILD_RESULT" + } + # ── DeepSeek Harness bridge: profile packaging on Windows ────────── # `prepare:dsh-profile` runs from `frontend:build-all` / official desktop # packaging, not from desktop:dev or cargo check. Until this job existed, @@ -390,6 +413,8 @@ jobs: # platform whose path and process rules differ. dsh-profile-windows: name: DSH Profile Packaging (windows-latest) + needs: build-impact + if: ${{ !cancelled() && needs.build-impact.outputs.dsh_profile_required == 'true' }} runs-on: windows-latest timeout-minutes: 15 steps: @@ -441,6 +466,7 @@ jobs: runs-on: ubuntu-latest env: NODE_OPTIONS: --max-old-space-size=6144 + needs: build-impact steps: - uses: actions/checkout@v5 with: @@ -452,7 +478,7 @@ jobs: - uses: actions/setup-node@v5 with: node-version: 22 - cache: pnpm + package-manager-cache: false - name: Check repository hygiene run: pnpm run check:repo-hygiene @@ -466,13 +492,56 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Setup Bun for package resource preflight + if: needs.build-impact.outputs.frontend_required != 'false' + uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.14" + + - name: Setup Rust for release projection preflight + if: needs.build-impact.outputs.frontend_required != 'false' + uses: dtolnay/rust-toolchain@stable + + - name: Verify committed release metadata + if: needs.build-impact.outputs.frontend_required != 'false' + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml + + - name: Verify Installer i18n projection + if: needs.build-impact.outputs.frontend_required != 'false' + run: pnpm --dir BitFun-Installer run sync:i18n + + - name: Verify Installer Tauri package alignment + if: needs.build-impact.outputs.frontend_required != 'false' + shell: bash + run: | + set -euo pipefail + info="$(pnpm --dir BitFun-Installer exec tauri info 2>&1)" + printf '%s\n' "$info" + if grep -Fq 'Error: Found version mismatched Tauri packages.' <<<"$info"; then + exit 1 + fi + + - name: Build plugin Host resources + if: needs.build-impact.outputs.frontend_required != 'false' + run: | + bun install --cwd src/apps/extension-host --frozen-lockfile + bun run --cwd src/apps/extension-host build + + - name: Generate web API bindings + if: needs.build-impact.outputs.frontend_required != 'false' + run: pnpm --dir src/web-ui run gen:types + - name: Validate PPT Live generated-file contract + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run test:ppt-live - name: Validate GitHub config run: pnpm run check:github-config - name: Select i18n contract profile + if: needs.build-impact.outputs.frontend_required != 'false' shell: bash run: | if git rev-parse HEAD^1 >/dev/null 2>&1 && @@ -481,34 +550,55 @@ jobs: fi - name: Validate i18n contract + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run i18n:contract:test:ci - name: Audit i18n resources + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run i18n:audit - name: Validate theme color audit contract + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run theme:color-audit:test - name: Audit theme color governance + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run theme:color-audit:all - name: Validate theme visual governance contract + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run theme:visual-contract - name: Validate WebKit compatibility gate + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run verify:webkit-compatibility:test - name: Lint web UI + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run lint:web - name: Run web UI tests + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm --dir src/web-ui run test:run - name: Build web UI + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run build:web - name: Type-check mobile web + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm --dir src/mobile-web run type-check - name: Build mobile web + if: needs.build-impact.outputs.frontend_required != 'false' run: pnpm run build:mobile-web + + - name: Project a Nightly version + if: needs.build-impact.outputs.frontend_required != 'false' + run: node scripts/set-build-version.mjs --version "0.0.0-nightly.ci.${{ github.run_id }}" + + - name: Verify projected release metadata + if: needs.build-impact.outputs.frontend_required != 'false' + run: | + cargo metadata --locked --no-deps + cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml diff --git a/.github/workflows/linux-binaries.yml b/.github/workflows/linux-binaries.yml index 1b3e376270..cf4185728d 100644 --- a/.github/workflows/linux-binaries.yml +++ b/.github/workflows/linux-binaries.yml @@ -20,6 +20,16 @@ on: required: false default: 7 type: number + upload_artifacts: + description: "Whether to upload archives for a publishing caller." + required: false + default: true + type: boolean + cache_write: + description: "Allow a trusted non-PR caller to refresh build caches." + required: false + default: false + type: boolean secrets: release_signing_key: description: "Tauri/minisign private key, base64. Absent on forks: archives ship unsigned." @@ -102,8 +112,10 @@ jobs: - name: Cache Rust build uses: swatinem/rust-cache@v2 with: - shared-key: "linux-binaries-v1-${{ matrix.platform.name }}" + shared-key: "linux-binaries-v2-${{ github.base_ref || github.ref_name }}-${{ matrix.platform.name }}" cache-bin: false + save-if: ${{ inputs.cache_write && github.event_name != 'pull_request' }} + cache-on-failure: ${{ inputs.cache_write && github.event_name != 'pull_request' }} - name: Verify committed Cargo metadata run: cargo metadata --locked --no-deps @@ -192,6 +204,7 @@ jobs: "$RELAY_ARCHIVE" "${RELAY_ARCHIVE}.sha256" - name: Upload Linux binary artifacts + if: ${{ inputs.upload_artifacts }} uses: actions/upload-artifact@v6 with: name: bitfun-linux-${{ inputs.artifact_prefix }}-${{ matrix.platform.name }} diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index b4afde1e94..4ef7dc1fd4 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -20,6 +20,31 @@ on: required: false default: 1 type: number + build_desktop_packages: + description: "Whether to run Desktop/Installer package producers." + required: false + default: true + type: boolean + desktop_platforms: + description: "JSON array of Desktop/Installer platform names to package." + required: false + default: '["linux-x64","linux-arm64","macos-arm64","macos-x64","windows-x64"]' + type: string + build_linux_binaries: + description: "Whether to build Linux CLI and Relay archives." + required: false + default: true + type: boolean + upload_artifacts: + description: "Whether to upload package outputs for a publishing caller." + required: false + default: true + type: boolean + cache_write: + description: "Allow a trusted non-PR caller to refresh build caches." + required: false + default: false + type: boolean secrets: TAURI_SIGNING_PRIVATE_KEY: required: false @@ -34,8 +59,9 @@ permissions: jobs: # ── Patch version for nightly ────────────────────────────────────── package: - name: Package (${{ matrix.platform.name }}) - runs-on: ${{ matrix.platform.os }} + name: Package (${{ matrix.platform }}) + if: ${{ inputs.build_desktop_packages }} + runs-on: ${{ fromJSON('{"linux-x64":"ubuntu-latest","linux-arm64":"ubuntu-24.04-arm","macos-arm64":"macos-15","macos-x64":"macos-15-intel","windows-x64":"windows-latest"}')[matrix.platform] }} env: NODE_OPTIONS: --max-old-space-size=6144 BITFUN_RELEASE_CHANNEL: nightly @@ -46,32 +72,12 @@ jobs: # Nightly relay archives are signed too (linux-binaries.yml receives the # key), so nightly Desktop needs the same trust root to verify them. BITFUN_RELEASE_PUBKEY: ${{ secrets.TAURI_UPDATER_PUBKEY }} + PACKAGE_TARGET: ${{ fromJSON('{"linux-x64":"x86_64-unknown-linux-gnu","linux-arm64":"aarch64-unknown-linux-gnu","macos-arm64":"aarch64-apple-darwin","macos-x64":"x86_64-apple-darwin","windows-x64":"x86_64-pc-windows-msvc"}')[matrix.platform] }} strategy: fail-fast: false matrix: - platform: - - os: ubuntu-latest - name: linux-x64 - target: x86_64-unknown-linux-gnu - build_command: pnpm run desktop:build:linux -- --target x86_64-unknown-linux-gnu --bundles deb,rpm,appimage - - os: ubuntu-24.04-arm - name: linux-arm64 - target: aarch64-unknown-linux-gnu - # Fat LTO exhausts the hosted ARM runner while linking bitfun-desktop. - build_command: CARGO_PROFILE_RELEASE_LTO=thin pnpm run desktop:build:linux -- --target aarch64-unknown-linux-gnu --bundles deb,rpm,appimage - - os: macos-15 - name: macos-arm64 - target: aarch64-apple-darwin - build_command: pnpm run desktop:build:arm64 - - os: macos-15-intel - name: macos-x64 - target: x86_64-apple-darwin - build_command: pnpm run desktop:build:x86_64 - - os: windows-latest - name: windows-x64 - target: x86_64-pc-windows-msvc - build_command: pnpm run installer:build + platform: ${{ fromJSON(inputs.desktop_platforms) }} steps: - uses: actions/checkout@v5 @@ -128,18 +134,22 @@ jobs: uses: actions/setup-node@v5 with: node-version: 22 - cache: pnpm + package-manager-cache: false - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable with: - targets: ${{ matrix.platform.target }} + targets: ${{ env.PACKAGE_TARGET }} - name: Cache Rust build uses: swatinem/rust-cache@v2 with: - shared-key: "nightly-v2-${{ matrix.platform.name }}" + shared-key: "nightly-v3-${{ github.base_ref || github.ref_name }}-${{ matrix.platform }}" cache-bin: false + # A PR merge ref can never become a reusable base cache. Even a + # trusted caller cannot override this guard. + save-if: ${{ inputs.cache_write && github.event_name != 'pull_request' }} + cache-on-failure: ${{ inputs.cache_write && github.event_name != 'pull_request' }} - name: Verify committed Cargo metadata run: | @@ -203,7 +213,29 @@ jobs: run: cargo test --locked -p bitfun-cli --test terminal_process_contracts -- --test-threads=1 - name: Build desktop app - run: ${{ matrix.platform.build_command }} + shell: bash + run: | + case "${{ matrix.platform }}" in + linux-x64) + pnpm run desktop:build:linux -- --target x86_64-unknown-linux-gnu --bundles deb,rpm,appimage + ;; + linux-arm64) + CARGO_PROFILE_RELEASE_LTO=thin pnpm run desktop:build:linux -- --target aarch64-unknown-linux-gnu --bundles deb,rpm,appimage + ;; + macos-arm64) + pnpm run desktop:build:arm64 + ;; + macos-x64) + pnpm run desktop:build:x86_64 + ;; + windows-x64) + pnpm run installer:build + ;; + *) + echo "Unsupported package platform: ${{ matrix.platform }}" >&2 + exit 1 + ;; + esac - name: Package macOS CLI for SSH dispatch if: runner.os == 'macOS' @@ -211,7 +243,7 @@ jobs: shell: bash env: NIGHTLY_VERSION: ${{ inputs.version }} - TARGET: ${{ matrix.platform.target }} + TARGET: ${{ env.PACKAGE_TARGET }} run: | set -euo pipefail ASSET_VERSION="${NIGHTLY_VERSION%%+*}" @@ -234,12 +266,13 @@ jobs: - name: Verify AppImage fcitx5 GTK module if: runner.os == 'Linux' shell: bash - run: bash scripts/ci/verify-appimage-fcitx.sh "${{ matrix.platform.target }}" + run: bash scripts/ci/verify-appimage-fcitx.sh "${{ env.PACKAGE_TARGET }}" - name: Upload bundles + if: ${{ inputs.upload_artifacts }} uses: actions/upload-artifact@v6 with: - name: bitfun-${{ inputs.artifact_prefix }}-${{ matrix.platform.name }}-bundle + name: bitfun-${{ inputs.artifact_prefix }}-${{ matrix.platform }}-bundle if-no-files-found: error retention-days: ${{ inputs.artifact_retention_days }} path: | @@ -254,6 +287,7 @@ jobs: linux-binaries: name: Linux CLI and Relay Server + if: ${{ inputs.build_linux_binaries }} uses: ./.github/workflows/linux-binaries.yml secrets: release_signing_key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} @@ -264,3 +298,5 @@ jobs: version: ${{ inputs.version }} artifact_prefix: ${{ inputs.artifact_prefix }} artifact_retention_days: ${{ inputs.artifact_retention_days }} + upload_artifacts: ${{ inputs.upload_artifacts }} + cache_write: ${{ inputs.cache_write }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a0bc6723a0..064f9d331b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -86,6 +86,13 @@ jobs: version: ${{ needs.check-changes.outputs.nightly_version }} artifact_prefix: nightly artifact_retention_days: ${{ fromJSON(inputs.artifact_retention_days || '7') }} + build_desktop_packages: true + desktop_platforms: '["linux-x64","linux-arm64","macos-arm64","macos-x64","windows-x64"]' + build_linux_binaries: true + upload_artifacts: true + # Fork validation is intentionally cache-read-only. Only the upstream + # repository may seed long-lived Nightly caches. + cache_write: ${{ github.repository_owner == 'GCWing' }} # ── Publish nightly pre-release ──────────────────────────────────── publish-nightly: diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index bdae5beed3..e29cfdedf0 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -209,12 +209,12 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => ); const rustJob = workflow.jobs['rust-build-check']; const frontendJob = workflow.jobs['frontend-build']; - const trustedMain = - "${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}"; + const trustedBase = + "${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/1.0.0-explore') }}"; assert.equal( rustJob.needs, - 'rust-impact', + 'build-impact', 'Rust validation must not wait for the frontend build', ); assert.equal( @@ -247,8 +247,13 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => false, `${jobName} must consume the committed Cargo.lock`, ); - assert.equal(cache?.with?.['save-if'], trustedMain); - assert.equal(cache?.with?.['cache-on-failure'], trustedMain); + assert.equal(cache?.with?.['save-if'], trustedBase); + assert.equal(cache?.with?.['cache-on-failure'], trustedBase); + assert.match( + cache?.with?.['shared-key'] ?? '', + /github\.base_ref \|\| github\.ref_name/, + `${jobName} must not mix main and explore build outputs`, + ); } const cliJob = workflow.jobs['cli-test']; @@ -284,7 +289,7 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => (step) => step.name === 'Save Sherpa native libraries', ); const sherpaCacheKey = - 'sherpa-onnx-v1-${{ runner.os }}-${{ runner.arch }}-1.13.4-static'; + 'sherpa-onnx-v2-${{ github.base_ref || github.ref_name }}-${{ runner.os }}-${{ runner.arch }}-1.13.4-static'; assert.equal(restoreSherpaCache?.uses, 'actions/cache/restore@v5'); assert.equal(restoreSherpaCache?.with?.path, 'target/sherpa-onnx-prebuilt'); @@ -299,7 +304,7 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => assert.equal(saveSherpaCache?.with?.key, sherpaCacheKey); assert.equal( saveSherpaCache?.if, - "github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.sherpa-native-cache.outputs.cache-hit != 'true'", + "github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/1.0.0-explore') && steps.sherpa-native-cache.outputs.cache-hit != 'true'", ); assert.ok( rustJob.steps.indexOf(restoreSherpaCache) < @@ -353,7 +358,7 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => ); }); -test('gates Rust and CLI validation behind one fail-closed impact decision', (t) => { +test('gates fast checks and PR packaging behind one fail-closed build decision', (t) => { const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8'), ); @@ -364,10 +369,10 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', (t) 'nested Markdown may be a Rust compile-time input and must trigger classification', ); } - const impactJob = workflow.jobs['rust-impact']; + const impactJob = workflow.jobs['build-impact']; const cliJob = workflow.jobs['cli-test']; const rustJob = workflow.jobs['rust-build-check']; - const buildJob = workflow.jobs['nightly-build-contract']; + const buildJob = workflow.jobs['package-impact-contract']; const resultJob = workflow.jobs['rust-validation-result']; const frontendJob = workflow.jobs['frontend-build']; @@ -379,17 +384,63 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', (t) frontendJob.steps.find((step) => step.name === 'Check core boundaries')?.run, 'pnpm run check:core-boundaries', ); + assert.equal(frontendJob.needs, 'build-impact'); + const frontendNode = frontendJob.steps.find((step) => + step.uses?.startsWith('actions/setup-node@')); + assert.equal(frontendNode?.with?.cache, undefined); + assert.equal(frontendNode?.with?.['package-manager-cache'], false); + const frontendGate = "needs.build-impact.outputs.frontend_required != 'false'"; + for (const stepName of [ + 'Verify committed release metadata', + 'Verify Installer i18n projection', + 'Verify Installer Tauri package alignment', + 'Build plugin Host resources', + 'Generate web API bindings', + 'Build web UI', + 'Build mobile web', + 'Project a Nightly version', + 'Verify projected release metadata', + ]) { + assert.equal( + frontendJob.steps.find((step) => step.name === stepName)?.if, + frontendGate, + `${stepName} must run for code changes and skip documentation-only changes`, + ); + } + const releaseMetadata = + 'cargo metadata --locked --no-deps\n' + + 'cargo metadata --locked --no-deps --manifest-path BitFun-Installer/src-tauri/Cargo.toml\n'; + assert.equal( + frontendJob.steps.find((step) => step.name === 'Verify committed release metadata')?.run, + releaseMetadata, + ); + assert.equal( + frontendJob.steps.find((step) => step.name === 'Verify projected release metadata')?.run, + releaseMetadata, + ); - assert.equal(impactJob.name, 'Rust / CLI Impact'); + assert.equal(impactJob.name, 'Build Impact'); assert.equal(impactJob['timeout-minutes'], 5); assert.equal( impactJob.outputs.rust_required, '${{ steps.classify.outputs.rust_required }}', ); + assert.equal( + impactJob.outputs.desktop_platforms, + '${{ steps.classify.outputs.desktop_platforms }}', + ); + assert.equal( + impactJob.outputs.package_required, + '${{ steps.classify.outputs.package_required }}', + ); + assert.equal( + impactJob.outputs.dsh_profile_required, + '${{ steps.classify.outputs.dsh_profile_required }}', + ); const checkout = impactJob.steps.find((step) => step.uses?.startsWith('actions/checkout@')); assert.equal(checkout?.with?.['fetch-depth'], 0); const classify = impactJob.steps.find((step) => step.id === 'classify'); - assert.match(classify?.run ?? '', /scripts\/ci\/classify-rust-impact\.mjs/); + assert.match(classify?.run ?? '', /scripts\/ci\/classify-build-impact\.mjs/); assert.equal( classify?.env?.BASE_SHA, '${{ github.event.pull_request.base.sha || github.event.before }}', @@ -405,49 +456,64 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', (t) assert.match(classify?.run ?? '', /--range-mode "\$RANGE_MODE"/); for (const job of [cliJob, rustJob]) { - assert.equal(job.needs, 'rust-impact'); + assert.equal(job.needs, 'build-impact'); assert.match(job.if, /!cancelled\(\)/); assert.doesNotMatch(job.if, /always\(\)/); assert.match(job.if, /rust_required != 'false'/); } - assert.equal(buildJob.name, 'Nightly Build Contract'); - assert.equal(buildJob.needs, 'rust-impact'); + assert.equal(buildJob.name, 'Impact-selected Package Contract'); + assert.equal(buildJob.needs, 'build-impact'); assert.equal(buildJob.uses, './.github/workflows/nightly-artifacts.yml'); - assert.match(buildJob.if, /rust-impact\.result == 'success'/); - assert.doesNotMatch(buildJob.if, /rust_required/); + assert.match(buildJob.if, /github\.event_name == 'pull_request'/); + assert.match(buildJob.if, /package_required == 'true'/); assert.deepEqual(buildJob.permissions, { contents: 'read' }); assert.deepEqual(buildJob.with, { checkout_ref: '${{ github.sha }}', version: '0.0.0-nightly.ci.${{ github.run_id }}', artifact_prefix: 'ci-${{ github.run_id }}', artifact_retention_days: 1, + build_desktop_packages: + "${{ needs.build-impact.outputs.desktop_packages_required == 'true' }}", + desktop_platforms: '${{ needs.build-impact.outputs.desktop_platforms }}', + build_linux_binaries: + "${{ needs.build-impact.outputs.linux_binaries_required == 'true' }}", + upload_artifacts: false, + cache_write: false, }); assert.equal(resultJob.name, 'Rust / CLI Validation'); assert.equal(resultJob.if, '${{ always() }}'); assert.deepEqual( [...resultJob.needs].sort(), - ['cli-test', 'nightly-build-contract', 'rust-build-check', 'rust-impact'], + ['build-impact', 'cli-test', 'package-impact-contract', 'rust-build-check'], ); const verify = resultJob.steps.find((step) => step.name === 'Verify Rust and CLI result'); - assert.equal(verify?.env?.RUST_REQUIRED, '${{ needs.rust-impact.outputs.rust_required }}'); - assert.equal(verify?.env?.IMPACT_RESULT, '${{ needs.rust-impact.result }}'); + assert.equal(verify?.env?.RUST_REQUIRED, '${{ needs.build-impact.outputs.rust_required }}'); + assert.equal(verify?.env?.PACKAGE_REQUIRED, '${{ needs.build-impact.outputs.package_required }}'); + assert.equal(verify?.env?.EVENT_NAME, '${{ github.event_name }}'); + assert.equal(verify?.env?.IMPACT_RESULT, '${{ needs.build-impact.result }}'); assert.equal(verify?.env?.CLI_RESULT, '${{ needs.cli-test.result }}'); assert.equal(verify?.env?.RUST_RESULT, '${{ needs.rust-build-check.result }}'); assert.equal( verify?.env?.BUILD_RESULT, - '${{ needs.nightly-build-contract.result }}', + '${{ needs.package-impact-contract.result }}', ); assert.equal(verify?.shell, 'pwsh'); - assert.match(verify?.run ?? '', /successful Nightly build contract/i); - assert.match(verify?.run ?? '', /successful Rust, CLI, and Nightly build jobs/i); + assert.match(verify?.run ?? '', /successful impact-selected package jobs/i); + assert.match(verify?.run ?? '', /successful Rust and CLI jobs/i); + + const dshJob = workflow.jobs['dsh-profile-windows']; + assert.equal(dshJob.needs, 'build-impact'); + assert.match(dshJob.if, /dsh_profile_required == 'true'/); const statuses = ['success', 'skipped', 'failure', 'cancelled']; const cases = []; for (const impactResult of statuses.filter((status) => status !== 'success')) { cases.push({ rustRequired: 'true', + packageRequired: 'true', + eventName: 'pull_request', impactResult, cliResult: 'success', rustResult: 'success', @@ -456,25 +522,37 @@ test('gates Rust and CLI validation behind one fail-closed impact decision', (t) }); } for (const rustRequired of ['false', 'true']) { - for (const cliResult of statuses) { - for (const rustResult of statuses) { - for (const buildResult of statuses) { - cases.push({ - rustRequired, - impactResult: 'success', - cliResult, - rustResult, - buildResult, - expectedSuccess: buildResult === 'success' && (rustRequired === 'false' - ? cliResult === 'skipped' && rustResult === 'skipped' - : cliResult === 'success' && rustResult === 'success'), - }); + for (const packageRequired of ['false', 'true']) { + for (const eventName of ['pull_request', 'push']) { + for (const cliResult of statuses) { + for (const rustResult of statuses) { + for (const buildResult of statuses) { + const packageExpected = eventName === 'pull_request' + && packageRequired === 'true'; + cases.push({ + rustRequired, + packageRequired, + eventName, + impactResult: 'success', + cliResult, + rustResult, + buildResult, + expectedSuccess: (packageExpected + ? buildResult === 'success' + : buildResult === 'skipped') && (rustRequired === 'false' + ? cliResult === 'skipped' && rustResult === 'skipped' + : cliResult === 'success' && rustResult === 'success'), + }); + } + } } } } } cases.push({ rustRequired: '', + packageRequired: 'false', + eventName: 'push', impactResult: 'success', cliResult: 'skipped', rustResult: 'skipped', @@ -495,6 +573,8 @@ ${verify.run} } foreach ($case in $cases) { $env:RUST_REQUIRED = [string]$case.rustRequired + $env:PACKAGE_REQUIRED = [string]$case.packageRequired + $env:EVENT_NAME = [string]$case.eventName $env:IMPACT_RESULT = [string]$case.impactResult $env:CLI_RESULT = [string]$case.cliResult $env:RUST_RESULT = [string]$case.rustResult @@ -558,8 +638,28 @@ test('nightly validates generated inputs and projected lockfiles before packagin assert.equal(callInputs.version.required, true); assert.equal(callInputs.artifact_prefix.required, true); assert.equal(callInputs.artifact_retention_days.default, 1); + assert.equal(callInputs.build_desktop_packages.default, true); + assert.equal(callInputs.build_linux_binaries.default, true); + assert.equal(callInputs.upload_artifacts.default, true); + assert.equal(callInputs.cache_write.default, false); + assert.equal( + callInputs.desktop_platforms.default, + '["linux-x64","linux-arm64","macos-arm64","macos-x64","windows-x64"]', + ); assert.equal(workflow.permissions.contents, 'read'); + const node = steps.find((step) => step.name === 'Setup Node.js'); + assert.equal(node?.with?.cache, undefined); + assert.equal(node?.with?.['package-manager-cache'], false); + const rustCache = steps.find((step) => + step.uses?.startsWith('swatinem/rust-cache@')); + const restoreOnlyOnPr = + "${{ inputs.cache_write && github.event_name != 'pull_request' }}"; + assert.equal(rustCache?.with?.['save-if'], restoreOnlyOnPr); + assert.equal(rustCache?.with?.['cache-on-failure'], restoreOnlyOnPr); + const upload = steps.find((step) => step.uses?.startsWith('actions/upload-artifact@')); + assert.equal(upload?.if, '${{ inputs.upload_artifacts }}'); + assert.notEqual(committedMetadataIndex, -1); assert.notEqual(installerI18nIndex, -1); assert.notEqual(tauriAlignmentIndex, -1); @@ -624,6 +724,12 @@ test('nightly orchestrates the shared build before the separately privileged pub artifact_prefix: 'nightly', artifact_retention_days: "${{ fromJSON(inputs.artifact_retention_days || '7') }}", + build_desktop_packages: true, + desktop_platforms: + '["linux-x64","linux-arm64","macos-arm64","macos-x64","windows-x64"]', + build_linux_binaries: true, + upload_artifacts: true, + cache_write: "${{ github.repository_owner == 'GCWing' }}", }); assert.deepEqual(publish.needs, ['check-changes', 'build-artifacts']); assert.match(publish.if, /inputs\.build_only != true/); @@ -656,6 +762,8 @@ test('Linux binary packaging uses the shared locked version projection contract' ); assert.equal(inputs.artifact_retention_days.default, 7); + assert.equal(inputs.upload_artifacts.default, true); + assert.equal(inputs.cache_write.default, false); assert.equal(steps[nodeIndex].uses, 'actions/setup-node@v5'); assert.equal(steps[nodeIndex].with['node-version-file'], 'package.json'); assert.ok( @@ -669,13 +777,52 @@ test('Linux binary packaging uses the shared locked version projection contract' assert.equal(steps[committedIndex].run, 'cargo metadata --locked --no-deps'); assert.equal(steps[projectedIndex].run, 'cargo metadata --locked --no-deps'); assert.match(steps[buildIndex].run, /cargo build --locked --release/); + const rustCache = steps.find((step) => + step.uses?.startsWith('swatinem/rust-cache@')); + const restoreOnlyOnPr = + "${{ inputs.cache_write && github.event_name != 'pull_request' }}"; + assert.equal(rustCache?.with?.['save-if'], restoreOnlyOnPr); + assert.equal(rustCache?.with?.['cache-on-failure'], restoreOnlyOnPr); const upload = steps.find((step) => step.uses?.startsWith('actions/upload-artifact@')); + assert.equal(upload?.if, '${{ inputs.upload_artifacts }}'); assert.equal( upload?.with?.['retention-days'], '${{ inputs.artifact_retention_days }}', ); }); +test('PR-capable release builds cannot save repository caches or upload CI packages', () => { + const ci = yaml.parse( + readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8'), + ); + const artifacts = yaml.parse( + readFileSync(path.join(repoRoot, '.github/workflows/nightly-artifacts.yml'), 'utf8'), + ); + const linux = yaml.parse( + readFileSync(path.join(repoRoot, '.github/workflows/linux-binaries.yml'), 'utf8'), + ); + + for (const workflow of [artifacts, linux]) { + for (const job of Object.values(workflow.jobs)) { + for (const cache of (job.steps ?? []).filter((step) => + step.uses?.startsWith('swatinem/rust-cache@'))) { + assert.match(cache.with['save-if'], /github\.event_name != 'pull_request'/); + assert.match(cache.with['cache-on-failure'], /github\.event_name != 'pull_request'/); + assert.match(cache.with['shared-key'], /github\.base_ref \|\| github\.ref_name/); + } + } + } + + const packageCaller = ci.jobs['package-impact-contract']; + assert.equal(packageCaller.with.cache_write, false); + assert.equal(packageCaller.with.upload_artifacts, false); + const node = artifacts.jobs.package.steps.find( + (step) => step.name === 'Setup Node.js', + ); + assert.equal(node.with.cache, undefined); + assert.equal(node.with['package-manager-cache'], false); +}); + test('nightly publishes and verifies the Relay image in the current repository owner scope', () => { const workflow = yaml.parse( readFileSync(path.join(repoRoot, '.github/workflows/nightly.yml'), 'utf8'), diff --git a/scripts/ci/classify-build-impact.mjs b/scripts/ci/classify-build-impact.mjs new file mode 100644 index 0000000000..b800f95d65 --- /dev/null +++ b/scripts/ci/classify-build-impact.mjs @@ -0,0 +1,335 @@ +import { appendFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { pathToFileURL } from 'node:url'; +import { rustWebUiSourceBoundaryRule } from '../core-boundaries/rules/source-rules.mjs'; +import { scanForbiddenContentUnder } from '../core-boundaries/source-content-checks.mjs'; + +function readArg(args, name) { + const index = args.indexOf(name); + return index >= 0 ? args[index + 1] : undefined; +} + +function changedPaths(base, head, rangeMode) { + const rangeArgs = rangeMode === 'merge-base' + ? [`${base}...${head}`] + : [base, head]; + const result = spawnSync( + 'git', + ['diff', '--no-renames', '--name-only', '-z', ...rangeArgs, '--'], + { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }, + ); + if (result.status !== 0) { + throw new Error(result.stderr.trim() || 'git diff failed'); + } + return result.stdout.split('\0').filter(Boolean); +} + +const ALL_DESKTOP_PLATFORMS = [ + 'linux-x64', + 'linux-arm64', + 'macos-arm64', + 'macos-x64', + 'windows-x64', +]; + +export function classifyBuildImpact(paths) { + const result = ({ + rustRequired, + frontendRequired, + desktopPlatforms = [], + linuxBinariesRequired = false, + dshProfileRequired = false, + reason, + }) => ({ + rustRequired, + frontendRequired, + desktopPackagesRequired: desktopPlatforms.length > 0, + desktopPlatforms, + linuxBinariesRequired, + dshProfileRequired, + packageRequired: desktopPlatforms.length > 0 || linuxBinariesRequired, + reason, + changedCount: paths.length, + }); + const failClosed = (reason) => result({ + rustRequired: true, + frontendRequired: true, + desktopPlatforms: ALL_DESKTOP_PLATFORMS, + linuxBinariesRequired: true, + dshProfileRequired: true, + reason, + }); + if (paths.length === 0) { + return failClosed('no-changes'); + } + if (paths.some((file) => !isValidRepositoryPath(file))) { + return failClosed('invalid-path'); + } + + const activePaths = paths.filter((file) => !isKnownNeutralPath(file)); + if (activePaths.length === 0) { + return result({ + rustRequired: false, + frontendRequired: false, + reason: 'ci-ignored-only', + }); + } + + const rustRequired = activePaths.some(isRustBuildInput) + || !activePaths.every((file) => file.startsWith('src/web-ui/')); + if (activePaths.some(isFullPackageInput)) { + return result({ + rustRequired, + frontendRequired: true, + desktopPlatforms: ALL_DESKTOP_PLATFORMS, + linuxBinariesRequired: true, + dshProfileRequired: true, + reason: 'full-package-input', + }); + } + + const desktopPlatforms = new Set(); + let linuxBinariesRequired = false; + for (const file of activePaths) { + if (isWindowsPackageInput(file)) { + desktopPlatforms.add('windows-x64'); + } + if (isMacPackageInput(file)) { + desktopPlatforms.add('macos-arm64'); + desktopPlatforms.add('macos-x64'); + } + if (isLinuxDesktopPackageInput(file)) { + desktopPlatforms.add('linux-x64'); + desktopPlatforms.add('linux-arm64'); + } + if (isLinuxBinaryPackageInput(file)) { + linuxBinariesRequired = true; + } + } + + const selectedPlatforms = ALL_DESKTOP_PLATFORMS.filter((platform) => + desktopPlatforms.has(platform)); + const dshProfileRequired = activePaths.some(isDshProfileInput); + const hasPackageImpact = selectedPlatforms.length > 0 || linuxBinariesRequired; + const reason = hasPackageImpact + ? 'platform-package-input' + : rustRequired + ? (activePaths.some(isRustBuildInput) ? 'rust-build-input' : 'outside-web-ui') + : 'web-ui-only'; + return result({ + rustRequired, + frontendRequired: true, + desktopPlatforms: selectedPlatforms, + linuxBinariesRequired, + dshProfileRequired, + reason, + }); +} + +function isValidRepositoryPath(file) { + return typeof file === 'string' + && file.length > 0 + && !file.startsWith('/') + && !/^[A-Za-z]:/.test(file) + && !file.includes('\\') + && !/[\r\n\0]/.test(file) + && file.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..'); +} + +function isRustBuildInput(file) { + const segments = file.split('/'); + const name = segments.at(-1); + return file.endsWith('.rs') + || name === 'Cargo.toml' + || name === 'Cargo.lock' + || name === 'build.rs' + || name === 'rust-toolchain' + || name === 'rust-toolchain.toml' + || segments.includes('.cargo'); +} + +function isFullPackageInput(file) { + const name = file.split('/').at(-1); + if ( + file === 'Cargo.toml' + || file === 'Cargo.lock' + || file === 'package.json' + || file === 'package-lock.json' + || file === 'pnpm-lock.yaml' + || file === 'pnpm-workspace.yaml' + || name === 'rust-toolchain' + || name === 'rust-toolchain.toml' + || file.startsWith('.cargo/') + ) { + return true; + } + if (name === 'Cargo.toml' && !file.startsWith('BitFun-Installer/')) { + return true; + } + if ( + file.startsWith('.github/workflows/') + || file === 'scripts/set-build-version.mjs' + || file === 'scripts/generate-version.cjs' + || file === 'scripts/frontend-build-all.mjs' + || file === 'scripts/desktop-tauri-build.mjs' + || file === 'src/apps/desktop/build.rs' + || file.startsWith('src/apps/desktop/tauri.') + || file.startsWith('src/apps/desktop/capabilities/') + || file.startsWith('src/apps/desktop/icons/') + || file === 'src/web-ui/package.json' + ) { + return true; + } + return false; +} + +function isWindowsPackageInput(file) { + return file.startsWith('BitFun-Installer/') + || file.startsWith('scripts/windows/') + || file.includes('/windows/') + || /(?:^|\/)(?:nsis|wix)(?:\/|\.|$)/i.test(file); +} + +function isMacPackageInput(file) { + return file.startsWith('scripts/ci/setup-macos-signing.') + || file.startsWith('scripts/ci/verify-macos-signing.') + || file.startsWith('scripts/macos/') + || file.includes('/macos/') + || /(?:entitlements|Info\.plist)$/i.test(file); +} + +function isLinuxDesktopPackageInput(file) { + return file === 'scripts/ci/verify-appimage-fcitx.sh' + || file.startsWith('scripts/linux/') + || file.includes('/linux/') + || /(?:appimage|linuxdeploy|\.deb\b|\.rpm\b)/i.test(file); +} + +function isLinuxBinaryPackageInput(file) { + return file === 'scripts/ci/check-glibc-floor.sh' + || file.startsWith('scripts/cli/package-unix.') + || file.startsWith('scripts/relay/package-unix.') + || file.startsWith('scripts/cli/test-install-unix.') + || file === 'src/apps/relay-server/Dockerfile.release'; +} + +function isDshProfileInput(file) { + return file.startsWith('packages/dsh-acp/') + || file === 'scripts/prepare-dsh-profile.mjs'; +} + +function isKnownNeutralPath(file) { + const isKnownDocumentation = file.endsWith('.md') + && (!file.includes('/') || file.startsWith('docs/')); + return isKnownDocumentation || file.startsWith('png/'); +} + +export function run(args = process.argv.slice(2), env = process.env) { + const base = readArg(args, '--base'); + const head = readArg(args, '--head'); + const rangeMode = readArg(args, '--range-mode') ?? 'direct'; + if (!base || !head) { + throw new Error( + 'Usage: classify-build-impact.mjs --base --head ' + + '[--range-mode direct|merge-base]', + ); + } + + const boundaryFindings = scanForbiddenContentUnder( + process.cwd(), + rustWebUiSourceBoundaryRule, + ); + if (boundaryFindings.length > 0) { + const details = boundaryFindings + .slice(0, 20) + .map((finding) => `${finding.repoPath}:${finding.line}: ${finding.message}`) + .join('\n'); + throw new Error(`${rustWebUiSourceBoundaryRule.reason}\n${details}`); + } + + let paths = []; + let result; + if ( + !isUsableCommitSha(base) + || !isUsableCommitSha(head) + || !['direct', 'merge-base'].includes(rangeMode) + ) { + result = { rustRequired: true, reason: 'invalid-range', changedCount: 0 }; + } else { + try { + paths = changedPaths(base, head, rangeMode); + result = classifyBuildImpact(paths); + } catch { + result = classifyBuildImpact([]); + result.reason = 'unavailable-range'; + } + } + if (result?.reason === 'invalid-range') { + result = classifyBuildImpact([]); + result.reason = 'invalid-range'; + } + const lines = [ + `rust_required=${result.rustRequired}`, + `frontend_required=${result.frontendRequired}`, + `desktop_packages_required=${result.desktopPackagesRequired}`, + `desktop_platforms=${JSON.stringify(result.desktopPlatforms)}`, + `linux_binaries_required=${result.linuxBinariesRequired}`, + `dsh_profile_required=${result.dshProfileRequired}`, + `package_required=${result.packageRequired}`, + `reason=${result.reason}`, + `changed_count=${result.changedCount}`, + ]; + if (env.GITHUB_OUTPUT) { + appendFileSync(env.GITHUB_OUTPUT, `${lines.join('\n')}\n`); + } else { + process.stdout.write(`${lines.join('\n')}\n`); + } + if (env.GITHUB_STEP_SUMMARY) { + appendFileSync(env.GITHUB_STEP_SUMMARY, renderSummary(result, paths)); + } + return result; +} + +function isUsableCommitSha(value) { + return /^[0-9a-f]{40}$/i.test(value) && !/^0{40}$/.test(value); +} + +function renderSummary(result, paths) { + const shownPaths = paths.slice(0, 20); + const lines = [ + '### Build impact classification', + '', + `- Required: ${result.rustRequired}`, + `- Frontend: ${result.frontendRequired}`, + `- Desktop packages: ${result.desktopPlatforms.join(', ') || 'none'}`, + `- Linux binaries: ${result.linuxBinariesRequired}`, + `- DSH profile: ${result.dshProfileRequired}`, + `- Reason: ${result.reason}`, + `- Changed files: ${result.changedCount}`, + ]; + if (shownPaths.length > 0) { + lines.push('', ...shownPaths.map((file) => `- ${escapeHtml(file)}`)); + } + if (paths.length > shownPaths.length) { + lines.push(`- ${paths.length - shownPaths.length} additional changed file(s) omitted`); + } + return `${lines.join('\n')}\n`; +} + +function escapeHtml(value) { + return value + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('\r', '\\r') + .replaceAll('\n', '\\n'); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + run(); + } catch (error) { + process.stderr.write(`${error.message || String(error)}\n`); + process.exitCode = 1; + } +} diff --git a/scripts/ci/classify-build-impact.test.mjs b/scripts/ci/classify-build-impact.test.mjs new file mode 100644 index 0000000000..4dee212dcb --- /dev/null +++ b/scripts/ci/classify-build-impact.test.mjs @@ -0,0 +1,270 @@ +import assert from 'node:assert/strict'; +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { test } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { classifyBuildImpact } from './classify-build-impact.mjs'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const scriptPath = path.join(repoRoot, 'scripts/ci/classify-build-impact.mjs'); +const allPlatforms = [ + 'linux-x64', + 'linux-arm64', + 'macos-arm64', + 'macos-x64', + 'windows-x64', +]; + +function expected({ + rustRequired, + frontendRequired = true, + desktopPlatforms = [], + linuxBinariesRequired = false, + dshProfileRequired = false, + reason, + changedCount = 1, +}) { + return { + rustRequired, + frontendRequired, + desktopPackagesRequired: desktopPlatforms.length > 0, + desktopPlatforms, + linuxBinariesRequired, + dshProfileRequired, + packageRequired: desktopPlatforms.length > 0 || linuxBinariesRequired, + reason, + changedCount, + }; +} + +function git(root, args) { + const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' }); + assert.equal(result.status, 0, result.stderr); + return result.stdout.trim(); +} + +function commit(root, message) { + git(root, ['add', '--all']); + git(root, [ + '-c', 'user.name=CI Impact Test', + '-c', 'user.email=ci-impact@example.invalid', + 'commit', '-m', message, + ]); + return git(root, ['rev-parse', 'HEAD']); +} + +function parseOutputs(file) { + return Object.fromEntries( + readFileSync(file, 'utf8').trim().split(/\r?\n/).map((line) => line.split('=', 2)), + ); +} + +function runClassifier(root, base, head, rangeMode = 'direct') { + const output = path.join(root, `github-output-${Date.now()}-${Math.random()}.txt`); + const summary = path.join(root, `github-summary-${Date.now()}-${Math.random()}.md`); + const result = spawnSync( + process.execPath, + [scriptPath, '--base', base, '--head', head, '--range-mode', rangeMode], + { + cwd: root, + env: { ...process.env, GITHUB_OUTPUT: output, GITHUB_STEP_SUMMARY: summary }, + encoding: 'utf8', + }, + ); + return { + ...result, + outputs: result.status === 0 ? parseOutputs(output) : undefined, + summary: existsSync(summary) ? readFileSync(summary, 'utf8') : undefined, + }; +} + +test('maps representative changes to the smallest predictive validation', () => { + const cases = [ + { + paths: ['docs/review-notes.md', 'png/example.png'], + result: expected({ + rustRequired: false, + frontendRequired: false, + reason: 'ci-ignored-only', + changedCount: 2, + }), + }, + { + paths: ['src/web-ui/src/example.ts'], + result: expected({ rustRequired: false, reason: 'web-ui-only' }), + }, + { + paths: ['src/crates/services/services-core/src/lib.rs'], + result: expected({ rustRequired: true, reason: 'rust-build-input' }), + }, + { + paths: ['BitFun-Installer/scripts/build-installer.cjs'], + result: expected({ + rustRequired: true, + desktopPlatforms: ['windows-x64'], + reason: 'platform-package-input', + }), + }, + { + paths: ['scripts/ci/setup-macos-signing.sh'], + result: expected({ + rustRequired: true, + desktopPlatforms: ['macos-arm64', 'macos-x64'], + reason: 'platform-package-input', + }), + }, + { + paths: ['scripts/ci/verify-appimage-fcitx.sh'], + result: expected({ + rustRequired: true, + desktopPlatforms: ['linux-x64', 'linux-arm64'], + reason: 'platform-package-input', + }), + }, + { + paths: ['scripts/cli/package-unix.sh'], + result: expected({ + rustRequired: true, + linuxBinariesRequired: true, + reason: 'platform-package-input', + }), + }, + { + paths: ['packages/dsh-acp/src/index.ts'], + result: expected({ + rustRequired: true, + dshProfileRequired: true, + reason: 'outside-web-ui', + }), + }, + { + paths: ['Cargo.lock'], + result: expected({ + rustRequired: true, + desktopPlatforms: allPlatforms, + linuxBinariesRequired: true, + dshProfileRequired: true, + reason: 'full-package-input', + }), + }, + { + paths: ['.github/workflows/nightly.yml'], + result: expected({ + rustRequired: true, + desktopPlatforms: allPlatforms, + linuxBinariesRequired: true, + dshProfileRequired: true, + reason: 'full-package-input', + }), + }, + ]; + + for (const { paths, result } of cases) { + assert.deepEqual(classifyBuildImpact(paths), result, paths.join(', ')); + } +}); + +test('keeps nested Markdown fail-closed because it may be a compile-time input', () => { + assert.deepEqual( + classifyBuildImpact([ + 'src/web-ui/src/example.ts', + 'src/crates/assembly/agent-content/prompts/agents/example.md', + ]), + expected({ + rustRequired: true, + reason: 'outside-web-ui', + changedCount: 2, + }), + ); +}); + +test('uses a merge-base range for pull requests and emits every workflow output', (t) => { + const root = mkdtempSync(path.join(tmpdir(), 'bitfun-build-impact-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + git(root, ['init', '--initial-branch=main']); + writeFileSync(path.join(root, 'README.md'), 'baseline\n'); + commit(root, 'baseline'); + + git(root, ['switch', '-c', 'feature']); + const installer = path.join(root, 'BitFun-Installer/scripts/build-installer.cjs'); + mkdirSync(path.dirname(installer), { recursive: true }); + writeFileSync(installer, 'export {};\n'); + const head = commit(root, 'installer change'); + + git(root, ['switch', 'main']); + writeFileSync(path.join(root, 'Cargo.lock'), 'version = 4\n'); + const currentBase = commit(root, 'base dependency change'); + + const pullRequest = runClassifier(root, currentBase, head, 'merge-base'); + assert.equal(pullRequest.status, 0, pullRequest.stderr); + assert.deepEqual(pullRequest.outputs, { + rust_required: 'true', + frontend_required: 'true', + desktop_packages_required: 'true', + desktop_platforms: '["windows-x64"]', + linux_binaries_required: 'false', + dsh_profile_required: 'false', + package_required: 'true', + reason: 'platform-package-input', + changed_count: '1', + }); + assert.match(pullRequest.summary, /Build impact classification/); + assert.match(pullRequest.summary, /Desktop packages:<\/strong> windows-x64/); + + const push = runClassifier(root, currentBase, head, 'direct'); + assert.deepEqual(JSON.parse(push.outputs.desktop_platforms), allPlatforms); + assert.equal(push.outputs.reason, 'full-package-input'); +}); + +test('fails closed when paths or event ranges are invalid or unavailable', (t) => { + for (const paths of [[], ['src/web-ui/../apps/desktop/src/lib.rs'], ['src\\web-ui\\x.ts']]) { + const result = classifyBuildImpact(paths); + assert.equal(result.rustRequired, true); + assert.equal(result.frontendRequired, true); + assert.deepEqual(result.desktopPlatforms, allPlatforms); + assert.equal(result.linuxBinariesRequired, true); + assert.equal(result.dshProfileRequired, true); + } + + const root = mkdtempSync(path.join(tmpdir(), 'bitfun-build-impact-range-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + git(root, ['init', '--initial-branch=main']); + writeFileSync(path.join(root, 'README.md'), 'baseline\n'); + const head = commit(root, 'baseline'); + + for (const [base, reason] of [ + ['0'.repeat(40), 'invalid-range'], + ['f'.repeat(40), 'unavailable-range'], + ]) { + const result = runClassifier(root, base, head); + assert.equal(result.status, 0, result.stderr); + assert.equal(result.outputs.reason, reason); + assert.equal(result.outputs.package_required, 'true'); + assert.deepEqual(JSON.parse(result.outputs.desktop_platforms), allPlatforms); + } +}); + +test('rejects tracked Rust sources that reference the Web UI source tree', (t) => { + const root = mkdtempSync(path.join(tmpdir(), 'bitfun-build-impact-boundary-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + git(root, ['init', '--initial-branch=main']); + writeFileSync(path.join(root, 'README.md'), 'baseline\n'); + const base = commit(root, 'baseline'); + const rustFile = path.join(root, 'src/lib.rs'); + mkdirSync(path.dirname(rustFile), { recursive: true }); + writeFileSync(rustFile, 'const WEB: &str = include_dir!("../web-ui/src");\n'); + const head = commit(root, 'forbidden Rust input'); + + const result = runClassifier(root, base, head); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /Rust source must not reference the Web UI source tree/); +}); diff --git a/scripts/ci/classify-rust-impact.mjs b/scripts/ci/classify-rust-impact.mjs deleted file mode 100644 index 17fb2301aa..0000000000 --- a/scripts/ci/classify-rust-impact.mjs +++ /dev/null @@ -1,174 +0,0 @@ -import { appendFileSync } from 'node:fs'; -import { spawnSync } from 'node:child_process'; -import { pathToFileURL } from 'node:url'; -import { rustWebUiSourceBoundaryRule } from '../core-boundaries/rules/source-rules.mjs'; -import { scanForbiddenContentUnder } from '../core-boundaries/source-content-checks.mjs'; - -function readArg(args, name) { - const index = args.indexOf(name); - return index >= 0 ? args[index + 1] : undefined; -} - -function changedPaths(base, head, rangeMode) { - const rangeArgs = rangeMode === 'merge-base' - ? [`${base}...${head}`] - : [base, head]; - const result = spawnSync( - 'git', - ['diff', '--no-renames', '--name-only', '-z', ...rangeArgs, '--'], - { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }, - ); - if (result.status !== 0) { - throw new Error(result.stderr.trim() || 'git diff failed'); - } - return result.stdout.split('\0').filter(Boolean); -} - -export function classifyRustImpact(paths) { - const result = (rustRequired, reason) => ({ - rustRequired, - reason, - changedCount: paths.length, - }); - if (paths.length === 0) { - return result(true, 'no-changes'); - } - if (paths.some((file) => !isValidRepositoryPath(file))) { - return result(true, 'invalid-path'); - } - if (paths.some(isRustBuildInput)) { - return result(true, 'rust-build-input'); - } - - const activePaths = paths.filter((file) => !isKnownNeutralPath(file)); - if (activePaths.length === 0) { - return result(false, 'ci-ignored-only'); - } - if (activePaths.every((file) => file.startsWith('src/web-ui/'))) { - return result(false, 'web-ui-only'); - } - return result(true, 'outside-web-ui'); -} - -function isValidRepositoryPath(file) { - return typeof file === 'string' - && file.length > 0 - && !file.startsWith('/') - && !/^[A-Za-z]:/.test(file) - && !file.includes('\\') - && !/[\r\n\0]/.test(file) - && file.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..'); -} - -function isRustBuildInput(file) { - const segments = file.split('/'); - const name = segments.at(-1); - return file.endsWith('.rs') - || name === 'Cargo.toml' - || name === 'Cargo.lock' - || name === 'build.rs' - || name === 'rust-toolchain' - || name === 'rust-toolchain.toml' - || segments.includes('.cargo'); -} - -function isKnownNeutralPath(file) { - const isKnownDocumentation = file.endsWith('.md') - && (!file.includes('/') || file.startsWith('docs/')); - return isKnownDocumentation || file.startsWith('png/'); -} - -export function run(args = process.argv.slice(2), env = process.env) { - const base = readArg(args, '--base'); - const head = readArg(args, '--head'); - const rangeMode = readArg(args, '--range-mode') ?? 'direct'; - if (!base || !head) { - throw new Error( - 'Usage: classify-rust-impact.mjs --base --head ' - + '[--range-mode direct|merge-base]', - ); - } - - const boundaryFindings = scanForbiddenContentUnder( - process.cwd(), - rustWebUiSourceBoundaryRule, - ); - if (boundaryFindings.length > 0) { - const details = boundaryFindings - .slice(0, 20) - .map((finding) => `${finding.repoPath}:${finding.line}: ${finding.message}`) - .join('\n'); - throw new Error(`${rustWebUiSourceBoundaryRule.reason}\n${details}`); - } - - let paths = []; - let result; - if ( - !isUsableCommitSha(base) - || !isUsableCommitSha(head) - || !['direct', 'merge-base'].includes(rangeMode) - ) { - result = { rustRequired: true, reason: 'invalid-range', changedCount: 0 }; - } else { - try { - paths = changedPaths(base, head, rangeMode); - result = classifyRustImpact(paths); - } catch { - result = { rustRequired: true, reason: 'unavailable-range', changedCount: 0 }; - } - } - const lines = [ - `rust_required=${result.rustRequired}`, - `reason=${result.reason}`, - `changed_count=${result.changedCount}`, - ]; - if (env.GITHUB_OUTPUT) { - appendFileSync(env.GITHUB_OUTPUT, `${lines.join('\n')}\n`); - } else { - process.stdout.write(`${lines.join('\n')}\n`); - } - if (env.GITHUB_STEP_SUMMARY) { - appendFileSync(env.GITHUB_STEP_SUMMARY, renderSummary(result, paths)); - } - return result; -} - -function isUsableCommitSha(value) { - return /^[0-9a-f]{40}$/i.test(value) && !/^0{40}$/.test(value); -} - -function renderSummary(result, paths) { - const shownPaths = paths.slice(0, 20); - const lines = [ - '### Rust/CLI impact classification', - '', - `- Required: ${result.rustRequired}`, - `- Reason: ${result.reason}`, - `- Changed files: ${result.changedCount}`, - ]; - if (shownPaths.length > 0) { - lines.push('', ...shownPaths.map((file) => `- ${escapeHtml(file)}`)); - } - if (paths.length > shownPaths.length) { - lines.push(`- ${paths.length - shownPaths.length} additional changed file(s) omitted`); - } - return `${lines.join('\n')}\n`; -} - -function escapeHtml(value) { - return value - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('\r', '\\r') - .replaceAll('\n', '\\n'); -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - try { - run(); - } catch (error) { - process.stderr.write(`${error.message || String(error)}\n`); - process.exitCode = 1; - } -} diff --git a/scripts/ci/classify-rust-impact.test.mjs b/scripts/ci/classify-rust-impact.test.mjs deleted file mode 100644 index 2daf5a3528..0000000000 --- a/scripts/ci/classify-rust-impact.test.mjs +++ /dev/null @@ -1,231 +0,0 @@ -import assert from 'node:assert/strict'; -import { - existsSync, - mkdirSync, - mkdtempSync, - readFileSync, - rmSync, - writeFileSync, -} from 'node:fs'; -import { tmpdir } from 'node:os'; -import path from 'node:path'; -import { spawnSync } from 'node:child_process'; -import { test } from 'node:test'; -import { fileURLToPath } from 'node:url'; -import { classifyRustImpact } from './classify-rust-impact.mjs'; - -const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); -const scriptPath = path.join(repoRoot, 'scripts/ci/classify-rust-impact.mjs'); - -function git(root, args) { - const result = spawnSync('git', args, { - cwd: root, - encoding: 'utf8', - }); - assert.equal(result.status, 0, result.stderr); - return result.stdout.trim(); -} - -function commit(root, message) { - git(root, ['add', '--all']); - git(root, [ - '-c', - 'user.name=CI Impact Test', - '-c', - 'user.email=ci-impact@example.invalid', - 'commit', - '-m', - message, - ]); - return git(root, ['rev-parse', 'HEAD']); -} - -function parseOutputs(file) { - return Object.fromEntries( - readFileSync(file, 'utf8') - .trim() - .split(/\r?\n/) - .map((line) => line.split('=', 2)), - ); -} - -function runClassifier(root, base, head, rangeMode = 'direct') { - const output = path.join(root, `github-output-${Date.now()}-${Math.random()}.txt`); - const summary = path.join(root, `github-summary-${Date.now()}-${Math.random()}.md`); - const result = spawnSync( - process.execPath, - [scriptPath, '--base', base, '--head', head, '--range-mode', rangeMode], - { - cwd: root, - env: { - ...process.env, - GITHUB_OUTPUT: output, - GITHUB_STEP_SUMMARY: summary, - }, - encoding: 'utf8', - }, - ); - return { - ...result, - outputs: result.status === 0 ? parseOutputs(output) : undefined, - summary: existsSync(summary) ? readFileSync(summary, 'utf8') : undefined, - }; -} - -test('skips Rust validation when a commit changes only Web UI files', (t) => { - const root = mkdtempSync(path.join(tmpdir(), 'bitfun-rust-impact-')); - t.after(() => rmSync(root, { recursive: true, force: true })); - - git(root, ['init', '--initial-branch=main']); - writeFileSync(path.join(root, 'README.md'), 'baseline\n'); - const base = commit(root, 'baseline'); - - const webFile = path.join(root, 'src/web-ui/src/example.ts'); - mkdirSync(path.dirname(webFile), { recursive: true }); - writeFileSync(webFile, 'export const example = true;\n'); - const head = commit(root, 'web change'); - - const result = runClassifier(root, base, head); - - assert.equal(result.status, 0, result.stderr); - assert.deepEqual(result.outputs, { - rust_required: 'false', - reason: 'web-ui-only', - changed_count: '1', - }); - assert.match(result.summary, /Rust\/CLI impact classification/); - assert.match(result.summary, /Required:<\/strong> false/); - assert.match(result.summary, /Reason:<\/strong> web-ui-only/); - assert.match(result.summary, /src\/web-ui\/src\/example\.ts<\/code>/); -}); - -test('uses a merge-base range for pull requests but a direct range for pushes', (t) => { - const root = mkdtempSync(path.join(tmpdir(), 'bitfun-rust-impact-diverged-')); - t.after(() => rmSync(root, { recursive: true, force: true })); - - git(root, ['init', '--initial-branch=main']); - writeFileSync(path.join(root, 'README.md'), 'baseline\n'); - commit(root, 'baseline'); - - git(root, ['switch', '-c', 'feature']); - const webFile = path.join(root, 'src/web-ui/src/example.ts'); - mkdirSync(path.dirname(webFile), { recursive: true }); - writeFileSync(webFile, 'export const example = true;\n'); - const head = commit(root, 'web change'); - - git(root, ['switch', 'main']); - const rustFile = path.join(root, 'src/apps/desktop/src/lib.rs'); - mkdirSync(path.dirname(rustFile), { recursive: true }); - writeFileSync(rustFile, 'pub fn base_change() {}\n'); - const currentBase = commit(root, 'base Rust change'); - - assert.deepEqual(runClassifier(root, currentBase, head, 'merge-base').outputs, { - rust_required: 'false', - reason: 'web-ui-only', - changed_count: '1', - }); - assert.deepEqual(runClassifier(root, currentBase, head, 'direct').outputs, { - rust_required: 'true', - reason: 'rust-build-input', - changed_count: '2', - }); -}); - -test('keeps workflow-ignored documentation neutral beside Web UI changes', () => { - assert.deepEqual( - classifyRustImpact([ - 'src/web-ui/src/example.ts', - 'src/web-ui/README.md', - 'docs/review-notes.md', - 'png/example/screenshot.png', - ]), - { - rustRequired: false, - reason: 'web-ui-only', - changedCount: 4, - }, - ); - assert.deepEqual(classifyRustImpact(['docs/review-notes.md', 'png/example.png']), { - rustRequired: false, - reason: 'ci-ignored-only', - changedCount: 2, - }); - assert.deepEqual( - classifyRustImpact([ - 'src/web-ui/src/example.ts', - 'src/crates/assembly/agent-content/prompts/agents/example.md', - ]), - { - rustRequired: true, - reason: 'outside-web-ui', - changedCount: 2, - }, - 'nested Markdown may be a Rust include input and must fail closed', - ); -}); - -test('requires Rust for ambiguous, native, or cross-boundary path sets', () => { - for (const [paths, reason] of [ - [[], 'no-changes'], - [['src/apps/desktop/src/lib.rs'], 'rust-build-input'], - [['src/web-ui/native/build.rs'], 'rust-build-input'], - [['src/web-ui/native/Cargo.toml'], 'rust-build-input'], - [['src/web-ui/src/example.ts', 'scripts/check-core-boundaries.mjs'], 'outside-web-ui'], - [['src/web-ui/../apps/desktop/src/lib.rs'], 'invalid-path'], - [['src\\web-ui\\src\\example.ts'], 'invalid-path'], - ]) { - assert.deepEqual(classifyRustImpact(paths), { - rustRequired: true, - reason, - changedCount: paths.length, - }); - } -}); - -test('requires Rust when the event range is invalid or unavailable', (t) => { - const root = mkdtempSync(path.join(tmpdir(), 'bitfun-rust-impact-range-')); - t.after(() => rmSync(root, { recursive: true, force: true })); - - git(root, ['init', '--initial-branch=main']); - writeFileSync(path.join(root, 'README.md'), 'baseline\n'); - const head = commit(root, 'baseline'); - - for (const [base, reason] of [ - ['0'.repeat(40), 'invalid-range'], - ['f'.repeat(40), 'unavailable-range'], - ]) { - const result = runClassifier(root, base, head); - assert.equal(result.status, 0, result.stderr); - assert.deepEqual(result.outputs, { - rust_required: 'true', - reason, - changed_count: '0', - }); - } - - const invalidMode = runClassifier(root, head, head, 'unsupported'); - assert.equal(invalidMode.status, 0, invalidMode.stderr); - assert.deepEqual(invalidMode.outputs, { - rust_required: 'true', - reason: 'invalid-range', - changed_count: '0', - }); -}); - -test('rejects tracked Rust sources that spell a Web UI input token', (t) => { - const root = mkdtempSync(path.join(tmpdir(), 'bitfun-rust-impact-boundary-')); - t.after(() => rmSync(root, { recursive: true, force: true })); - - git(root, ['init', '--initial-branch=main']); - writeFileSync(path.join(root, 'README.md'), 'baseline\n'); - const base = commit(root, 'baseline'); - - const rustFile = path.join(root, 'src/lib.rs'); - mkdirSync(path.dirname(rustFile), { recursive: true }); - writeFileSync(rustFile, 'const WEB: &str = include_dir!("../web-ui/src");\n'); - const head = commit(root, 'forbidden Rust input'); - - const result = runClassifier(root, base, head); - assert.notEqual(result.status, 0); - assert.match(result.stderr, /Rust source must not reference the Web UI source tree/); -}); diff --git a/src/apps/cli/src/dispatch/runner.rs b/src/apps/cli/src/dispatch/runner.rs index a5f115ddec..8b74d249dc 100644 --- a/src/apps/cli/src/dispatch/runner.rs +++ b/src/apps/cli/src/dispatch/runner.rs @@ -443,15 +443,23 @@ mod tests { let job_id = "leader-exits-after-term"; let ready_dir = tempfile::tempdir().expect("ready directory"); let ready_path = ready_dir.path().join("term-resistant-child-ready"); - let mut command = Command::new("/bin/sh"); + let mut command = Command::new("python3"); command - .arg("-c") - .arg( - "trap 'exit 0' TERM; trap '' HUP; \ - sh -c 'trap \"\" TERM HUP; printf ready > \"$BITFUN_DISPATCH_TERM_TEST_READY\"; \ - while :; do sleep 30; done' & \ - while :; do sleep 30; done", - ) + .args([ + "-c", + r#"import os, signal +signal.signal(signal.SIGTERM, lambda *_: os._exit(0)) +signal.signal(signal.SIGHUP, signal.SIG_IGN) +if os.fork() == 0: + signal.signal(signal.SIGTERM, signal.SIG_IGN) + with open(os.environ["BITFUN_DISPATCH_TERM_TEST_READY"], "w") as ready: + ready.write("ready") + while True: + signal.pause() +while True: + signal.pause() +"#, + ]) // These trailing arguments make the real process identity match // the hidden worker contract without launching BitFun Runtime. .args(["dispatch", "__run", "--job", job_id]) diff --git a/src/apps/desktop/src/embedded_relay_host.rs b/src/apps/desktop/src/embedded_relay_host.rs index 72fd793762..3f46584c6a 100644 --- a/src/apps/desktop/src/embedded_relay_host.rs +++ b/src/apps/desktop/src/embedded_relay_host.rs @@ -217,17 +217,16 @@ mod tests { /// milliseconds. async fn assert_port_released(port: u16, what: &str) { let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); - let mut last_err = None; loop { match tokio::net::TcpListener::bind(("0.0.0.0", port)).await { Ok(l) => { drop(l); return; } - Err(e) => last_err = Some(e), - } - if std::time::Instant::now() >= deadline { - panic!("{what}: port {port} never became bindable again: {last_err:?}"); + Err(error) if std::time::Instant::now() >= deadline => { + panic!("{what}: port {port} never became bindable again: {error}"); + } + Err(_) => {} } tokio::time::sleep(std::time::Duration::from_millis(25)).await; } @@ -256,6 +255,30 @@ mod tests { panic!("could not find a free port for the embedded relay: {last_err}"); } + /// Restart on the requested port once it is observably bindable. A stopped + /// Tokio listener releases synchronously, but the port was allocated from + /// the OS ephemeral range and another concurrent test/process may claim it + /// between `stop` and the next bind. + async fn restart_on_same_port(host: &DesktopEmbeddedRelayHost, port: u16, static_dir: String) { + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + loop { + match host.start(port, Some(static_dir.clone())).await { + Ok(()) => return, + Err(error) + if error + .to_string() + .starts_with(&format!("failed to bind embedded relay on port {port}:")) => + { + if std::time::Instant::now() >= deadline { + panic!("embedded relay could not reclaim port {port} after stop: {error}"); + } + } + Err(error) => panic!("embedded relay restart failed: {error}"), + } + tokio::time::sleep(std::time::Duration::from_millis(25)).await; + } + } + #[tokio::test] async fn bind_failure_does_not_create_an_active_runtime() { let occupied = tokio::net::TcpListener::bind("0.0.0.0:0") @@ -332,9 +355,7 @@ mod tests { drop(client); host.stop().await; - host.start(port, Some(static_dir.to_string_lossy().into_owned())) - .await - .expect("embedded relay should restart immediately on the same port"); + restart_on_same_port(&host, port, static_dir.to_string_lossy().into_owned()).await; host.stop().await; assert_port_released(port, "stop must release the listener before returning").await; From 07dae0535edd0db4edcea22e5c503c68346dd9f7 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 21:50:10 +0800 Subject: [PATCH 09/12] fix(ci): disable Bun cache writes in PR builds --- .github/workflows/ci.yml | 1 + .github/workflows/linux-binaries.yml | 1 + .github/workflows/nightly-artifacts.yml | 1 + scripts/check-github-config.test.mjs | 9 +++++++++ 4 files changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 784caa2b53..d9d980dc82 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -497,6 +497,7 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.14" + no-cache: true - name: Setup Rust for release projection preflight if: needs.build-impact.outputs.frontend_required != 'false' diff --git a/.github/workflows/linux-binaries.yml b/.github/workflows/linux-binaries.yml index cf4185728d..59bbd72473 100644 --- a/.github/workflows/linux-binaries.yml +++ b/.github/workflows/linux-binaries.yml @@ -76,6 +76,7 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.14" + no-cache: true - name: Setup Node.js uses: actions/setup-node@v5 diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index 4ef7dc1fd4..c58e5e05da 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -88,6 +88,7 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: "1.3.14" + no-cache: true - name: Install Linux system dependencies (Tauri bundler) if: runner.os == 'Linux' diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index e29cfdedf0..aab48aaed0 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -813,6 +813,15 @@ test('PR-capable release builds cannot save repository caches or upload CI packa } } + for (const workflow of [ci, artifacts, linux]) { + for (const job of Object.values(workflow.jobs)) { + for (const bun of (job.steps ?? []).filter((step) => + step.uses?.startsWith('oven-sh/setup-bun@'))) { + assert.equal(bun.with?.['no-cache'], true); + } + } + } + const packageCaller = ci.jobs['package-impact-contract']; assert.equal(packageCaller.with.cache_write, false); assert.equal(packageCaller.with.upload_artifacts, false); From 8425fbf6f3b39dc0e6ab4f5dd8ce38fcf66e419f Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Tue, 25 Aug 2026 01:09:39 +0800 Subject: [PATCH 10/12] fix(cli): release dispatch leases deterministically --- src/apps/cli/src/dispatch/store.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/apps/cli/src/dispatch/store.rs b/src/apps/cli/src/dispatch/store.rs index 02b594f0b8..df45ca5055 100644 --- a/src/apps/cli/src/dispatch/store.rs +++ b/src/apps/cli/src/dispatch/store.rs @@ -1674,6 +1674,14 @@ impl WorkspaceLock { } } +impl Drop for WorkspaceLock { + fn drop(&mut self) { + if let Err(error) = fs2::FileExt::unlock(&self._file) { + tracing::warn!("Failed to release workspace dispatch lock: {error}"); + } + } +} + pub(crate) struct DispatchLease { _file: File, } @@ -1692,6 +1700,14 @@ impl DispatchLease { } } +impl Drop for DispatchLease { + fn drop(&mut self) { + if let Err(error) = fs2::FileExt::unlock(&self._file) { + tracing::warn!("Failed to release dispatch lease: {error}"); + } + } +} + pub(super) struct JobLock { _file: File, } From 959b736d58c41d92da878665d5e076769ed0551b Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Tue, 25 Aug 2026 09:54:31 +0800 Subject: [PATCH 11/12] fix(ci): close predictive validation gaps --- .github/workflows/nightly.yml | 4 ++- scripts/check-github-config.test.mjs | 4 +++ scripts/ci/classify-build-impact.mjs | 30 +++++++++++++++++--- scripts/ci/classify-build-impact.test.mjs | 34 ++++++++++++++++++++++- src/apps/cli/src/dispatch/runner.rs | 24 ++++++---------- 5 files changed, 74 insertions(+), 22 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 064f9d331b..bae99bf69b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -20,7 +20,9 @@ permissions: contents: read concurrency: - group: nightly-${{ github.ref }} + # The release, tag, and GHCR image tags are repository-wide resources. Keep + # manual runs from other refs serialized with the scheduled main run. + group: nightly-${{ github.repository }} cancel-in-progress: true env: diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index aab48aaed0..5d796a638d 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -716,6 +716,10 @@ test('nightly orchestrates the shared build before the separately privileged pub const publish = workflow.jobs['publish-nightly']; assert.equal(workflow.on.workflow_call, undefined); + assert.deepEqual(workflow.concurrency, { + group: 'nightly-${{ github.repository }}', + 'cancel-in-progress': true, + }); assert.equal(build.uses, './.github/workflows/nightly-artifacts.yml'); assert.deepEqual(build.permissions, { contents: 'read' }); assert.deepEqual(build.with, { diff --git a/scripts/ci/classify-build-impact.mjs b/scripts/ci/classify-build-impact.mjs index b800f95d65..289f350c28 100644 --- a/scripts/ci/classify-build-impact.mjs +++ b/scripts/ci/classify-build-impact.mjs @@ -32,6 +32,31 @@ const ALL_DESKTOP_PLATFORMS = [ 'windows-x64', ]; +// These inputs define repository-level release metadata, signing, staging, or +// scheduling contracts shared by more than one artifact producer. A focused +// platform match is not sufficient for them: validate the complete producer +// set so a release-contract change cannot merge on compile-only evidence. +const FULL_PACKAGE_INPUTS = new Set([ + 'scripts/ci/classify-build-impact.mjs', + 'scripts/cli/package-unix.sh', + 'scripts/cli/package-windows.ps1', + 'scripts/collect-tauri-updater-assets.mjs', + 'scripts/desktop-tauri-build.mjs', + 'scripts/frontend-build-all.mjs', + 'scripts/generate-linux-binaries-manifest.mjs', + 'scripts/generate-tauri-latest-json.mjs', + 'scripts/generate-version.cjs', + 'scripts/openbitfun-release-sync.sh', + 'scripts/prepare-windows-installer-asset.mjs', + 'scripts/release-channel.mjs', + 'scripts/set-build-version.mjs', + 'scripts/sign-release-assets.sh', + 'scripts/stage-github-release-assets.mjs', + 'scripts/verify-release-version-sync.mjs', + 'scripts/verify-tauri-latest-json.mjs', + 'scripts/write-minisign-public-key.mjs', +]); + export function classifyBuildImpact(paths) { const result = ({ rustRequired, @@ -168,10 +193,7 @@ function isFullPackageInput(file) { } if ( file.startsWith('.github/workflows/') - || file === 'scripts/set-build-version.mjs' - || file === 'scripts/generate-version.cjs' - || file === 'scripts/frontend-build-all.mjs' - || file === 'scripts/desktop-tauri-build.mjs' + || FULL_PACKAGE_INPUTS.has(file) || file === 'src/apps/desktop/build.rs' || file.startsWith('src/apps/desktop/tauri.') || file.startsWith('src/apps/desktop/capabilities/') diff --git a/scripts/ci/classify-build-impact.test.mjs b/scripts/ci/classify-build-impact.test.mjs index 4dee212dcb..5e009c7015 100644 --- a/scripts/ci/classify-build-impact.test.mjs +++ b/scripts/ci/classify-build-impact.test.mjs @@ -134,8 +134,10 @@ test('maps representative changes to the smallest predictive validation', () => paths: ['scripts/cli/package-unix.sh'], result: expected({ rustRequired: true, + desktopPlatforms: allPlatforms, linuxBinariesRequired: true, - reason: 'platform-package-input', + dshProfileRequired: true, + reason: 'full-package-input', }), }, { @@ -166,6 +168,36 @@ test('maps representative changes to the smallest predictive validation', () => reason: 'full-package-input', }), }, + { + paths: ['scripts/sign-release-assets.sh'], + result: expected({ + rustRequired: true, + desktopPlatforms: allPlatforms, + linuxBinariesRequired: true, + dshProfileRequired: true, + reason: 'full-package-input', + }), + }, + { + paths: ['scripts/ci/classify-build-impact.mjs'], + result: expected({ + rustRequired: true, + desktopPlatforms: allPlatforms, + linuxBinariesRequired: true, + dshProfileRequired: true, + reason: 'full-package-input', + }), + }, + { + paths: ['scripts/cli/package-windows.ps1'], + result: expected({ + rustRequired: true, + desktopPlatforms: allPlatforms, + linuxBinariesRequired: true, + dshProfileRequired: true, + reason: 'full-package-input', + }), + }, ]; for (const { paths, result } of cases) { diff --git a/src/apps/cli/src/dispatch/runner.rs b/src/apps/cli/src/dispatch/runner.rs index 8b74d249dc..87e5b9ba91 100644 --- a/src/apps/cli/src/dispatch/runner.rs +++ b/src/apps/cli/src/dispatch/runner.rs @@ -443,23 +443,15 @@ mod tests { let job_id = "leader-exits-after-term"; let ready_dir = tempfile::tempdir().expect("ready directory"); let ready_path = ready_dir.path().join("term-resistant-child-ready"); - let mut command = Command::new("python3"); + let mut command = Command::new("/bin/sh"); command - .args([ - "-c", - r#"import os, signal -signal.signal(signal.SIGTERM, lambda *_: os._exit(0)) -signal.signal(signal.SIGHUP, signal.SIG_IGN) -if os.fork() == 0: - signal.signal(signal.SIGTERM, signal.SIG_IGN) - with open(os.environ["BITFUN_DISPATCH_TERM_TEST_READY"], "w") as ready: - ready.write("ready") - while True: - signal.pause() -while True: - signal.pause() -"#, - ]) + .arg("-c") + .arg( + "trap 'exit 0' TERM; trap '' HUP; \ + sh -c 'trap \"\" TERM HUP; printf ready > \ + \"$BITFUN_DISPATCH_TERM_TEST_READY\"; while :; do :; done' & \ + while :; do :; done", + ) // These trailing arguments make the real process identity match // the hidden worker contract without launching BitFun Runtime. .args(["dispatch", "__run", "--job", job_id]) From 522c6291ca26a1ab68dbee111448e66b0cd05b34 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Tue, 25 Aug 2026 13:07:19 +0800 Subject: [PATCH 12/12] fix(ci): harden predictive package validation --- .github/workflows/ci.yml | 2 + .github/workflows/linux-binaries.yml | 52 +++++++++ .github/workflows/nightly-artifacts.yml | 72 +++++++++++- .github/workflows/nightly.yml | 1 + scripts/check-github-config.test.mjs | 29 +++++ scripts/ci/classify-build-impact.mjs | 130 +++++++++++++++++++--- scripts/ci/classify-build-impact.test.mjs | 46 ++++++-- 7 files changed, 305 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9d980dc82..13743e57d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,7 @@ jobs: desktop_packages_required: ${{ steps.classify.outputs.desktop_packages_required }} desktop_platforms: ${{ steps.classify.outputs.desktop_platforms }} linux_binaries_required: ${{ steps.classify.outputs.linux_binaries_required }} + relay_image_required: ${{ steps.classify.outputs.relay_image_required }} dsh_profile_required: ${{ steps.classify.outputs.dsh_profile_required }} package_required: ${{ steps.classify.outputs.package_required }} reason: ${{ steps.classify.outputs.reason }} @@ -351,6 +352,7 @@ jobs: build_desktop_packages: ${{ needs.build-impact.outputs.desktop_packages_required == 'true' }} desktop_platforms: ${{ needs.build-impact.outputs.desktop_platforms }} build_linux_binaries: ${{ needs.build-impact.outputs.linux_binaries_required == 'true' }} + build_relay_image: ${{ needs.build-impact.outputs.relay_image_required == 'true' }} upload_artifacts: false cache_write: false diff --git a/.github/workflows/linux-binaries.yml b/.github/workflows/linux-binaries.yml index 59bbd72473..af21924880 100644 --- a/.github/workflows/linux-binaries.yml +++ b/.github/workflows/linux-binaries.yml @@ -30,6 +30,11 @@ on: required: false default: false type: boolean + validate_relay_image: + description: "Build the Relay runtime image for each native architecture without pushing it." + required: false + default: true + type: boolean secrets: release_signing_key: description: "Tauri/minisign private key, base64. Absent on forks: archives ship unsigned." @@ -62,9 +67,11 @@ jobs: - os: ubuntu-22.04 name: linux-x64 target: x86_64-unknown-linux-gnu + docker_arch: amd64 - os: ubuntu-22.04-arm name: linux-arm64 target: aarch64-unknown-linux-gnu + docker_arch: arm64 steps: - name: Checkout @@ -204,6 +211,51 @@ jobs: "$CLI_ARCHIVE" "${CLI_ARCHIVE}.sha256" \ "$RELAY_ARCHIVE" "${RELAY_ARCHIVE}.sha256" + - name: Verify Linux binary outputs + shell: bash + env: + CLI_ARCHIVE: ${{ steps.cli-stage.outputs.archive }} + CLI_CHECKSUM: ${{ steps.cli-stage.outputs.checksum }} + RELAY_ARCHIVE: ${{ steps.relay-stage.outputs.archive }} + RELAY_CHECKSUM: ${{ steps.relay-stage.outputs.checksum }} + run: | + set -euo pipefail + test -s "$CLI_ARCHIVE" + test -s "$CLI_CHECKSUM" + test -s "$RELAY_ARCHIVE" + test -s "$RELAY_CHECKSUM" + sha256sum --check "$CLI_CHECKSUM" + sha256sum --check "$RELAY_CHECKSUM" + + - name: Stage Relay image validation context + if: ${{ inputs.validate_relay_image }} + shell: bash + env: + RELAY_ARCHIVE: ${{ steps.relay-stage.outputs.archive }} + run: | + set -euo pipefail + mkdir -p relay-image-context + cp "$RELAY_ARCHIVE" relay-image-context/ + cp src/apps/relay-server/Dockerfile.release relay-image-context/ + + - name: Set up Docker Buildx + if: ${{ inputs.validate_relay_image }} + uses: docker/setup-buildx-action@v4 + + - name: Validate Relay runtime image + if: ${{ inputs.validate_relay_image }} + uses: docker/build-push-action@v7 + with: + context: relay-image-context + file: relay-image-context/Dockerfile.release + platforms: linux/${{ matrix.platform.docker_arch }} + push: false + provenance: false + sbom: false + build-args: | + VERSION=${{ inputs.version }} + REVISION=${{ inputs.checkout_ref }} + - name: Upload Linux binary artifacts if: ${{ inputs.upload_artifacts }} uses: actions/upload-artifact@v6 diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml index c58e5e05da..64e8d14fa8 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -35,6 +35,11 @@ on: required: false default: true type: boolean + build_relay_image: + description: "Whether to validate the Relay runtime image without publishing it." + required: false + default: true + type: boolean upload_artifacts: description: "Whether to upload package outputs for a publishing caller." required: false @@ -269,6 +274,70 @@ jobs: shell: bash run: bash scripts/ci/verify-appimage-fcitx.sh "${{ env.PACKAGE_TARGET }}" + # Keep output validation independent from artifact upload. PR callers do + # not upload packages, but still need evidence that every selected + # producer emitted the files its publishing caller will collect. + - name: Verify package outputs + shell: bash + run: | + set -euo pipefail + + find_bundle_file() { + local pattern="$1" + local root + for root in \ + "target/${PACKAGE_TARGET}/release/bundle" \ + "target/release/bundle" \ + "src/apps/desktop/target/${PACKAGE_TARGET}/release/bundle" \ + "src/apps/desktop/target/release/bundle"; do + if [[ -d "$root" ]] && find "$root" -type f -name "$pattern" -size +0c -print -quit | grep -q .; then + return 0 + fi + done + echo "Missing non-empty package output matching $pattern for ${{ matrix.platform }}" >&2 + return 1 + } + + find_bundle_dir() { + local pattern="$1" + local root + for root in \ + "target/${PACKAGE_TARGET}/release/bundle" \ + "target/release/bundle" \ + "src/apps/desktop/target/${PACKAGE_TARGET}/release/bundle" \ + "src/apps/desktop/target/release/bundle"; do + if [[ -d "$root" ]] && find "$root" -type d -name "$pattern" -print -quit | grep -q .; then + return 0 + fi + done + echo "Missing package directory matching $pattern for ${{ matrix.platform }}" >&2 + return 1 + } + + case "${{ matrix.platform }}" in + linux-*) + find_bundle_file '*.deb' + find_bundle_file '*.rpm' + find_bundle_file '*.AppImage' + ;; + macos-*) + find_bundle_dir '*.app' + find_bundle_file '*.dmg' + shopt -s nullglob + cli_archives=(bitfun-cli-*-"${PACKAGE_TARGET}".tar.gz) + [[ ${#cli_archives[@]} -eq 1 ]] + test -s "${cli_archives[0]}" + test -s "${cli_archives[0]}.sha256" + ;; + windows-x64) + test -s BitFun-Installer/src-tauri/target/release/bitfun-installer.exe + ;; + *) + echo "Unsupported package platform: ${{ matrix.platform }}" >&2 + exit 1 + ;; + esac + - name: Upload bundles if: ${{ inputs.upload_artifacts }} uses: actions/upload-artifact@v6 @@ -288,7 +357,7 @@ jobs: linux-binaries: name: Linux CLI and Relay Server - if: ${{ inputs.build_linux_binaries }} + if: ${{ inputs.build_linux_binaries || inputs.build_relay_image }} uses: ./.github/workflows/linux-binaries.yml secrets: release_signing_key: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} @@ -301,3 +370,4 @@ jobs: artifact_retention_days: ${{ inputs.artifact_retention_days }} upload_artifacts: ${{ inputs.upload_artifacts }} cache_write: ${{ inputs.cache_write }} + validate_relay_image: ${{ inputs.build_relay_image }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index bae99bf69b..4263689ec4 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -91,6 +91,7 @@ jobs: build_desktop_packages: true desktop_platforms: '["linux-x64","linux-arm64","macos-arm64","macos-x64","windows-x64"]' build_linux_binaries: true + build_relay_image: true upload_artifacts: true # Fork validation is intentionally cache-read-only. Only the upstream # repository may seed long-lived Nightly caches. diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index 5d796a638d..831c730fee 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -433,6 +433,10 @@ test('gates fast checks and PR packaging behind one fail-closed build decision', impactJob.outputs.package_required, '${{ steps.classify.outputs.package_required }}', ); + assert.equal( + impactJob.outputs.relay_image_required, + '${{ steps.classify.outputs.relay_image_required }}', + ); assert.equal( impactJob.outputs.dsh_profile_required, '${{ steps.classify.outputs.dsh_profile_required }}', @@ -478,6 +482,8 @@ test('gates fast checks and PR packaging behind one fail-closed build decision', desktop_platforms: '${{ needs.build-impact.outputs.desktop_platforms }}', build_linux_binaries: "${{ needs.build-impact.outputs.linux_binaries_required == 'true' }}", + build_relay_image: + "${{ needs.build-impact.outputs.relay_image_required == 'true' }}", upload_artifacts: false, cache_write: false, }); @@ -608,6 +614,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); const callInputs = workflow.on.workflow_call.inputs; const packageJob = workflow.jobs.package; + const linuxJob = workflow.jobs['linux-binaries']; const steps = packageJob.steps; const committedMetadataIndex = steps.findIndex( (step) => step.name === 'Verify committed Cargo metadata', @@ -640,6 +647,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin assert.equal(callInputs.artifact_retention_days.default, 1); assert.equal(callInputs.build_desktop_packages.default, true); assert.equal(callInputs.build_linux_binaries.default, true); + assert.equal(callInputs.build_relay_image.default, true); assert.equal(callInputs.upload_artifacts.default, true); assert.equal(callInputs.cache_write.default, false); assert.equal( @@ -647,6 +655,14 @@ test('nightly validates generated inputs and projected lockfiles before packagin '["linux-x64","linux-arm64","macos-arm64","macos-x64","windows-x64"]', ); assert.equal(workflow.permissions.contents, 'read'); + assert.equal( + linuxJob.if, + '${{ inputs.build_linux_binaries || inputs.build_relay_image }}', + ); + assert.equal( + linuxJob.with.validate_relay_image, + '${{ inputs.build_relay_image }}', + ); const node = steps.find((step) => step.name === 'Setup Node.js'); assert.equal(node?.with?.cache, undefined); @@ -658,6 +674,10 @@ test('nightly validates generated inputs and projected lockfiles before packagin assert.equal(rustCache?.with?.['save-if'], restoreOnlyOnPr); assert.equal(rustCache?.with?.['cache-on-failure'], restoreOnlyOnPr); const upload = steps.find((step) => step.uses?.startsWith('actions/upload-artifact@')); + const verifyOutputs = steps.find((step) => step.name === 'Verify package outputs'); + assert.match(verifyOutputs?.run ?? '', /bitfun-installer\.exe/); + assert.match(verifyOutputs?.run ?? '', /\*\.AppImage/); + assert.ok(steps.indexOf(verifyOutputs) < steps.indexOf(upload)); assert.equal(upload?.if, '${{ inputs.upload_artifacts }}'); assert.notEqual(committedMetadataIndex, -1); @@ -732,6 +752,7 @@ test('nightly orchestrates the shared build before the separately privileged pub desktop_platforms: '["linux-x64","linux-arm64","macos-arm64","macos-x64","windows-x64"]', build_linux_binaries: true, + build_relay_image: true, upload_artifacts: true, cache_write: "${{ github.repository_owner == 'GCWing' }}", }); @@ -768,6 +789,7 @@ test('Linux binary packaging uses the shared locked version projection contract' assert.equal(inputs.artifact_retention_days.default, 7); assert.equal(inputs.upload_artifacts.default, true); assert.equal(inputs.cache_write.default, false); + assert.equal(inputs.validate_relay_image.default, true); assert.equal(steps[nodeIndex].uses, 'actions/setup-node@v5'); assert.equal(steps[nodeIndex].with['node-version-file'], 'package.json'); assert.ok( @@ -788,6 +810,13 @@ test('Linux binary packaging uses the shared locked version projection contract' assert.equal(rustCache?.with?.['save-if'], restoreOnlyOnPr); assert.equal(rustCache?.with?.['cache-on-failure'], restoreOnlyOnPr); const upload = steps.find((step) => step.uses?.startsWith('actions/upload-artifact@')); + const verifyOutputs = steps.find((step) => step.name === 'Verify Linux binary outputs'); + const validateImage = steps.find((step) => step.name === 'Validate Relay runtime image'); + assert.match(verifyOutputs?.run ?? '', /sha256sum --check/); + assert.ok(steps.indexOf(verifyOutputs) < steps.indexOf(upload)); + assert.equal(validateImage?.if, '${{ inputs.validate_relay_image }}'); + assert.equal(validateImage?.with?.push, false); + assert.equal(validateImage?.with?.platforms, 'linux/${{ matrix.platform.docker_arch }}'); assert.equal(upload?.if, '${{ inputs.upload_artifacts }}'); assert.equal( upload?.with?.['retention-days'], diff --git a/scripts/ci/classify-build-impact.mjs b/scripts/ci/classify-build-impact.mjs index 289f350c28..7eacd08abb 100644 --- a/scripts/ci/classify-build-impact.mjs +++ b/scripts/ci/classify-build-impact.mjs @@ -1,6 +1,7 @@ -import { appendFileSync } from 'node:fs'; +import { appendFileSync, existsSync, readFileSync, statSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; -import { pathToFileURL } from 'node:url'; +import { dirname, relative, resolve, sep } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { rustWebUiSourceBoundaryRule } from '../core-boundaries/rules/source-rules.mjs'; import { scanForbiddenContentUnder } from '../core-boundaries/source-content-checks.mjs'; @@ -32,17 +33,15 @@ const ALL_DESKTOP_PLATFORMS = [ 'windows-x64', ]; +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); + // These inputs define repository-level release metadata, signing, staging, or // scheduling contracts shared by more than one artifact producer. A focused // platform match is not sufficient for them: validate the complete producer // set so a release-contract change cannot merge on compile-only evidence. -const FULL_PACKAGE_INPUTS = new Set([ +const ALL_PACKAGE_INPUTS = new Set([ 'scripts/ci/classify-build-impact.mjs', - 'scripts/cli/package-unix.sh', - 'scripts/cli/package-windows.ps1', 'scripts/collect-tauri-updater-assets.mjs', - 'scripts/desktop-tauri-build.mjs', - 'scripts/frontend-build-all.mjs', 'scripts/generate-linux-binaries-manifest.mjs', 'scripts/generate-tauri-latest-json.mjs', 'scripts/generate-version.cjs', @@ -57,12 +56,77 @@ const FULL_PACKAGE_INPUTS = new Set([ 'scripts/write-minisign-public-key.mjs', ]); +// Inputs consumed by the Desktop/Tauri package producer itself. Keep this +// separate from ALL_PACKAGE_INPUTS: these paths do not affect the standalone +// Linux CLI/Relay archives, so selecting those producers would add cost +// without validating the changed input. +const DESKTOP_PACKAGE_INPUTS = collectLocalModuleInputs([ + 'scripts/desktop-tauri-build.mjs', + // Tauri runs this entrypoint through beforeBuildCommand rather than an + // import from desktop-tauri-build.mjs. + 'scripts/frontend-build-all.mjs', +]); + +const DESKTOP_PACKAGE_PREFIXES = [ + 'products/', + 'resources/flashgrep/', + 'src/apps/desktop/capabilities/', + 'src/apps/desktop/dmg/', + 'src/apps/desktop/icons/', + 'src/apps/desktop/resources/', + 'src/apps/desktop/scripts/', +]; + +function collectLocalModuleInputs(entryPaths) { + const inputs = new Set(); + const pending = entryPaths.map((entry) => resolve(REPO_ROOT, entry)); + + while (pending.length > 0) { + const absolutePath = pending.pop(); + const repoPath = relative(REPO_ROOT, absolutePath).split(sep).join('/'); + if (repoPath === '..' || repoPath.startsWith('../') || inputs.has(repoPath)) { + continue; + } + if (!existsSync(absolutePath)) { + throw new Error(`Missing package producer module: ${repoPath}`); + } + inputs.add(repoPath); + + const source = readFileSync(absolutePath, 'utf8'); + const localImport = /(?:\b(?:import|export)\s+(?:[^'"\n]*?\s+from\s*)?|\bimport\s*\(\s*)['"](\.[^'"]+)['"]/g; + for (const match of source.matchAll(localImport)) { + const importedPath = resolveLocalModule(dirname(absolutePath), match[1]); + if (importedPath) { + pending.push(importedPath); + } + } + } + + return inputs; +} + +function resolveLocalModule(parentDirectory, specifier) { + const base = resolve(parentDirectory, specifier); + const candidates = [ + base, + `${base}.mjs`, + `${base}.js`, + `${base}.cjs`, + resolve(base, 'index.mjs'), + resolve(base, 'index.js'), + resolve(base, 'index.cjs'), + ]; + return candidates.find((candidate) => + existsSync(candidate) && statSync(candidate).isFile()); +} + export function classifyBuildImpact(paths) { const result = ({ rustRequired, frontendRequired, desktopPlatforms = [], linuxBinariesRequired = false, + relayImageRequired = false, dshProfileRequired = false, reason, }) => ({ @@ -71,8 +135,10 @@ export function classifyBuildImpact(paths) { desktopPackagesRequired: desktopPlatforms.length > 0, desktopPlatforms, linuxBinariesRequired, + relayImageRequired, dshProfileRequired, - packageRequired: desktopPlatforms.length > 0 || linuxBinariesRequired, + packageRequired: + desktopPlatforms.length > 0 || linuxBinariesRequired || relayImageRequired, reason, changedCount: paths.length, }); @@ -81,6 +147,7 @@ export function classifyBuildImpact(paths) { frontendRequired: true, desktopPlatforms: ALL_DESKTOP_PLATFORMS, linuxBinariesRequired: true, + relayImageRequired: true, dshProfileRequired: true, reason, }); @@ -108,6 +175,7 @@ export function classifyBuildImpact(paths) { frontendRequired: true, desktopPlatforms: ALL_DESKTOP_PLATFORMS, linuxBinariesRequired: true, + relayImageRequired: true, dshProfileRequired: true, reason: 'full-package-input', }); @@ -115,7 +183,13 @@ export function classifyBuildImpact(paths) { const desktopPlatforms = new Set(); let linuxBinariesRequired = false; + let relayImageRequired = false; for (const file of activePaths) { + if (isDesktopPackageInput(file)) { + for (const platform of ALL_DESKTOP_PLATFORMS) { + desktopPlatforms.add(platform); + } + } if (isWindowsPackageInput(file)) { desktopPlatforms.add('windows-x64'); } @@ -130,12 +204,20 @@ export function classifyBuildImpact(paths) { if (isLinuxBinaryPackageInput(file)) { linuxBinariesRequired = true; } + if (isRelayImageInput(file)) { + // The image validation consumes the archive produced in the same matrix + // job, so selecting it necessarily selects Linux binary packaging too. + linuxBinariesRequired = true; + relayImageRequired = true; + } } const selectedPlatforms = ALL_DESKTOP_PLATFORMS.filter((platform) => desktopPlatforms.has(platform)); const dshProfileRequired = activePaths.some(isDshProfileInput); - const hasPackageImpact = selectedPlatforms.length > 0 || linuxBinariesRequired; + const hasPackageImpact = selectedPlatforms.length > 0 + || linuxBinariesRequired + || relayImageRequired; const reason = hasPackageImpact ? 'platform-package-input' : rustRequired @@ -146,6 +228,7 @@ export function classifyBuildImpact(paths) { frontendRequired: true, desktopPlatforms: selectedPlatforms, linuxBinariesRequired, + relayImageRequired, dshProfileRequired, reason, }); @@ -193,27 +276,32 @@ function isFullPackageInput(file) { } if ( file.startsWith('.github/workflows/') - || FULL_PACKAGE_INPUTS.has(file) - || file === 'src/apps/desktop/build.rs' - || file.startsWith('src/apps/desktop/tauri.') - || file.startsWith('src/apps/desktop/capabilities/') - || file.startsWith('src/apps/desktop/icons/') - || file === 'src/web-ui/package.json' + || ALL_PACKAGE_INPUTS.has(file) ) { return true; } return false; } +function isDesktopPackageInput(file) { + return DESKTOP_PACKAGE_INPUTS.has(file) + || DESKTOP_PACKAGE_PREFIXES.some((prefix) => file.startsWith(prefix)) + || file === 'src/apps/desktop/build.rs' + || file.startsWith('src/apps/desktop/tauri.') + || file === 'src/web-ui/package.json'; +} + function isWindowsPackageInput(file) { return file.startsWith('BitFun-Installer/') + || file === 'scripts/cli/package-windows.ps1' || file.startsWith('scripts/windows/') || file.includes('/windows/') || /(?:^|\/)(?:nsis|wix)(?:\/|\.|$)/i.test(file); } function isMacPackageInput(file) { - return file.startsWith('scripts/ci/setup-macos-signing.') + return file === 'scripts/cli/package-unix.sh' + || file.startsWith('scripts/ci/setup-macos-signing.') || file.startsWith('scripts/ci/verify-macos-signing.') || file.startsWith('scripts/macos/') || file.includes('/macos/') @@ -231,8 +319,12 @@ function isLinuxBinaryPackageInput(file) { return file === 'scripts/ci/check-glibc-floor.sh' || file.startsWith('scripts/cli/package-unix.') || file.startsWith('scripts/relay/package-unix.') - || file.startsWith('scripts/cli/test-install-unix.') - || file === 'src/apps/relay-server/Dockerfile.release'; + || file.startsWith('scripts/cli/test-install-unix.'); +} + +function isRelayImageInput(file) { + return file === 'src/apps/relay-server/Dockerfile.release' + || file.startsWith('scripts/relay/package-unix.'); } function isDshProfileInput(file) { @@ -296,6 +388,7 @@ export function run(args = process.argv.slice(2), env = process.env) { `desktop_packages_required=${result.desktopPackagesRequired}`, `desktop_platforms=${JSON.stringify(result.desktopPlatforms)}`, `linux_binaries_required=${result.linuxBinariesRequired}`, + `relay_image_required=${result.relayImageRequired}`, `dsh_profile_required=${result.dshProfileRequired}`, `package_required=${result.packageRequired}`, `reason=${result.reason}`, @@ -325,6 +418,7 @@ function renderSummary(result, paths) { `- Frontend: ${result.frontendRequired}`, `- Desktop packages: ${result.desktopPlatforms.join(', ') || 'none'}`, `- Linux binaries: ${result.linuxBinariesRequired}`, + `- Relay image: ${result.relayImageRequired}`, `- DSH profile: ${result.dshProfileRequired}`, `- Reason: ${result.reason}`, `- Changed files: ${result.changedCount}`, diff --git a/scripts/ci/classify-build-impact.test.mjs b/scripts/ci/classify-build-impact.test.mjs index 5e009c7015..e141cf8776 100644 --- a/scripts/ci/classify-build-impact.test.mjs +++ b/scripts/ci/classify-build-impact.test.mjs @@ -29,6 +29,7 @@ function expected({ frontendRequired = true, desktopPlatforms = [], linuxBinariesRequired = false, + relayImageRequired = false, dshProfileRequired = false, reason, changedCount = 1, @@ -39,8 +40,10 @@ function expected({ desktopPackagesRequired: desktopPlatforms.length > 0, desktopPlatforms, linuxBinariesRequired, + relayImageRequired, dshProfileRequired, - packageRequired: desktopPlatforms.length > 0 || linuxBinariesRequired, + packageRequired: + desktopPlatforms.length > 0 || linuxBinariesRequired || relayImageRequired, reason, changedCount, }; @@ -130,14 +133,37 @@ test('maps representative changes to the smallest predictive validation', () => reason: 'platform-package-input', }), }, + ...[ + 'scripts/prepare-flashgrep-resource.mjs', + 'resources/flashgrep/VERSION.json', + 'src/apps/desktop/scripts/post-install-icons.sh', + 'src/apps/desktop/dmg/background.png', + 'scripts/product-customization/projections.mjs', + 'products/bitfun/product.jsonc', + ].map((desktopInput) => ({ + paths: [desktopInput], + result: expected({ + rustRequired: true, + desktopPlatforms: allPlatforms, + reason: 'platform-package-input', + }), + })), + { + paths: ['src/apps/relay-server/Dockerfile.release'], + result: expected({ + rustRequired: true, + linuxBinariesRequired: true, + relayImageRequired: true, + reason: 'platform-package-input', + }), + }, { paths: ['scripts/cli/package-unix.sh'], result: expected({ rustRequired: true, - desktopPlatforms: allPlatforms, + desktopPlatforms: ['macos-arm64', 'macos-x64'], linuxBinariesRequired: true, - dshProfileRequired: true, - reason: 'full-package-input', + reason: 'platform-package-input', }), }, { @@ -154,6 +180,7 @@ test('maps representative changes to the smallest predictive validation', () => rustRequired: true, desktopPlatforms: allPlatforms, linuxBinariesRequired: true, + relayImageRequired: true, dshProfileRequired: true, reason: 'full-package-input', }), @@ -164,6 +191,7 @@ test('maps representative changes to the smallest predictive validation', () => rustRequired: true, desktopPlatforms: allPlatforms, linuxBinariesRequired: true, + relayImageRequired: true, dshProfileRequired: true, reason: 'full-package-input', }), @@ -174,6 +202,7 @@ test('maps representative changes to the smallest predictive validation', () => rustRequired: true, desktopPlatforms: allPlatforms, linuxBinariesRequired: true, + relayImageRequired: true, dshProfileRequired: true, reason: 'full-package-input', }), @@ -184,6 +213,7 @@ test('maps representative changes to the smallest predictive validation', () => rustRequired: true, desktopPlatforms: allPlatforms, linuxBinariesRequired: true, + relayImageRequired: true, dshProfileRequired: true, reason: 'full-package-input', }), @@ -192,10 +222,8 @@ test('maps representative changes to the smallest predictive validation', () => paths: ['scripts/cli/package-windows.ps1'], result: expected({ rustRequired: true, - desktopPlatforms: allPlatforms, - linuxBinariesRequired: true, - dshProfileRequired: true, - reason: 'full-package-input', + desktopPlatforms: ['windows-x64'], + reason: 'platform-package-input', }), }, ]; @@ -244,6 +272,7 @@ test('uses a merge-base range for pull requests and emits every workflow output' desktop_packages_required: 'true', desktop_platforms: '["windows-x64"]', linux_binaries_required: 'false', + relay_image_required: 'false', dsh_profile_required: 'false', package_required: 'true', reason: 'platform-package-input', @@ -264,6 +293,7 @@ test('fails closed when paths or event ranges are invalid or unavailable', (t) = assert.equal(result.frontendRequired, true); assert.deepEqual(result.desktopPlatforms, allPlatforms); assert.equal(result.linuxBinariesRequired, true); + assert.equal(result.relayImageRequired, true); assert.equal(result.dshProfileRequired, true); }