From 1ea99183afa82f32fc0eaeb06266b353f6283dae Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 11:10:17 +0800 Subject: [PATCH 1/8] 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 ++++++++++++++- scripts/set-build-version.mjs | 32 +++++++++++- 5 files changed, 184 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4a16fe80a..3a54d9c24c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,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: | @@ -91,6 +91,8 @@ jobs: cache_key: ubuntu - os: macos-15 cache_key: macos + - os: windows-latest + cache_key: windows steps: - uses: actions/checkout@v5 @@ -126,6 +128,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 @@ -156,6 +165,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 @@ -204,6 +219,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 @@ -299,16 +324,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 e80dd0751b..b21425c949 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -187,15 +187,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 }} @@ -337,8 +338,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 c47d57eefd..5736e0f4aa 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -284,6 +284,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@'), ); @@ -337,6 +347,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', @@ -393,17 +422,25 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => productControlContracts?.run ?? '', /bitfun-product-capabilities every_agent_runtime_delivery_profile_includes_product_control_discovery/, ); + 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::', ); }); -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'), ); @@ -415,6 +452,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); @@ -426,6 +472,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) { diff --git a/scripts/set-build-version.mjs b/scripts/set-build-version.mjs index deaaa91ddb..0066ceca86 100644 --- a/scripts/set-build-version.mjs +++ b/scripts/set-build-version.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node -import { readFileSync, writeFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -40,6 +41,35 @@ export function setBuildVersion(root, version) { /^version = "[^"]+"$/m, `version = "${version}"`, ); + + syncCargoLock(root); +} + +// Workspace members inherit the root version, so the lockfile carries a copy of +// it for every member. Leaving those stale breaks any later `cargo --locked` +// invocation. Cargo has to do the rewrite: a text substitution would also catch +// third-party crates that happen to publish the same version string, and would +// miss members that pin a version of their own. +// +// 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'))) { + return; + } + + const result = spawnSync('cargo', ['update', '--workspace'], { + cwd: root, + encoding: 'utf8', + }); + if (result.error) { + throw new Error(`Failed to run cargo update: ${result.error.message}`); + } + if (result.status !== 0) { + throw new Error( + `cargo update --workspace failed with exit code ${result.status}\n${result.stderr || ''}`, + ); + } } function replaceVersion(file, pattern, replacement) { From 148f12ca020c25711d88f8f1435dc1a9cbc94364 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 15:28:30 +0800 Subject: [PATCH 2/8] 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 b21425c949..7df9d09bcb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -315,7 +315,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 @@ -331,8 +333,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 @@ -340,7 +342,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 @@ -355,7 +357,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 @@ -363,7 +365,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}" \ @@ -388,7 +390,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: | @@ -413,7 +415,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 @@ -507,14 +509,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 @@ -524,7 +526,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 5736e0f4aa..db4a4b802f 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -488,17 +488,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 24fcff1561a702d852ecb97b1ffd6249c566187f Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 16:13:33 +0800 Subject: [PATCH 3/8] fix(ci): require nightly artifact parity --- .github/workflows/ci.yml | 29 ++-- .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 | 44 +++--- scripts/check-github-config.test.mjs | 144 +++++++++++++++--- 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, 376 insertions(+), 78 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a54d9c24c..de5bd79372 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,7 +82,7 @@ jobs: cli-test: name: CLI Tests (${{ matrix.os }}) runs-on: ${{ matrix.os }} - timeout-minutes: 15 + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -219,14 +219,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 @@ -265,7 +260,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 library tests run: cargo test --locked -p bitfun-core --lib @@ -337,6 +332,20 @@ 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, macOS CLI, and + # Linux CLI/Relay paths used by Nightly all succeed. + nightly-build-contract: + name: Nightly Build Contract + uses: ./.github/workflows/nightly.yml + permissions: + contents: read + with: + force_build: true + build_only: true + artifact_retention_days: 1 + # ── 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, 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 cbe85b6195..ff3b658076 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." @@ -57,6 +62,12 @@ jobs: with: ref: ${{ inputs.checkout_ref }} + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version-file: package.json + package-manager-cache: false + - name: Install Linux system dependencies shell: bash run: | @@ -85,6 +96,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: @@ -97,12 +111,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 @@ -112,7 +124,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 \ @@ -175,7 +187,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 7df9d09bcb..57f4774400 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 @@ -164,6 +201,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 @@ -188,7 +230,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' @@ -237,7 +281,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 @@ -261,13 +305,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 0b34ccd0ca..225935fd76 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 index 4748ef57d7..abb2a4c6cf 100644 --- a/BitFun-Installer/src-tauri/Cargo.lock +++ b/BitFun-Installer/src-tauri/Cargo.lock @@ -66,7 +66,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -425,9 +425,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "jobserver", @@ -604,9 +604,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ "cfg-if", ] @@ -854,7 +854,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1065,7 +1065,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -1144,7 +1144,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -2114,9 +2114,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lru-slab" @@ -2958,7 +2958,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3149,9 +3149,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.14" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "aws-lc-rs", "ring", @@ -3331,7 +3331,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3366,7 +3366,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -3653,9 +3653,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -4069,7 +4069,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4150,7 +4150,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] @@ -4510,9 +4510,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.24.1" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5476,7 +5476,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 3.0.3", + "syn 3.0.4", ] [[package]] diff --git a/scripts/check-github-config.test.mjs b/scripts/check-github-config.test.mjs index db4a4b802f..f625ef1c8a 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -347,24 +347,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'), @@ -376,7 +365,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 coreLibraryTests = rustJob.steps.find( (step) => step.name === 'Run core library tests', @@ -440,12 +429,33 @@ test('keeps Rust CI independent, restore-only on PRs, and target-focused', () => ); }); +test('ordinary CI requires the exact Nightly artifact producers', () => { + const workflow = yaml.parse( + readFileSync(path.join(repoRoot, '.github/workflows/ci.yml'), 'utf8'), + ); + const buildJob = workflow.jobs['nightly-build-contract']; + + assert.equal(buildJob.name, 'Nightly Build Contract'); + assert.equal(buildJob.needs, undefined); + assert.equal(buildJob.uses, './.github/workflows/nightly.yml'); + assert.deepEqual(buildJob.permissions, { contents: 'read' }); + assert.deepEqual(buildJob.with, { + force_build: true, + build_only: true, + artifact_retention_days: 1, + }); +}); + test('nightly validates generated inputs and projected lockfiles before packaging', () => { 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', ); @@ -462,6 +472,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( @@ -473,10 +497,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, @@ -488,6 +519,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'), @@ -635,10 +709,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'", @@ -728,3 +818,21 @@ 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`, + ); + } +}); 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 5282cb5e78184536639b5badc47f4f6dd8e6e9d9 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 16:51:39 +0800 Subject: [PATCH 4/8] fix(ci): isolate privileged nightly publishing --- .github/workflows/ci.yml | 7 +- .github/workflows/nightly-artifacts.yml | 241 +++++++++++++++++++++++ .github/workflows/nightly.yml | 244 ++---------------------- scripts/check-github-config.test.mjs | 65 +++++-- 4 files changed, 303 insertions(+), 254 deletions(-) create mode 100644 .github/workflows/nightly-artifacts.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de5bd79372..296c6df9af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -338,12 +338,13 @@ jobs: # Linux CLI/Relay paths used by Nightly all succeed. nightly-build-contract: name: Nightly Build Contract - 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 # ── DeepSeek Harness bridge: profile packaging on Windows ────────── diff --git a/.github/workflows/nightly-artifacts.yml b/.github/workflows/nightly-artifacts.yml new file mode 100644 index 0000000000..77484e6632 --- /dev/null +++ b/.github/workflows/nightly-artifacts.yml @@ -0,0 +1,241 @@ +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: 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: 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 57f4774400..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,210 +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: 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: 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 }} @@ -310,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 f625ef1c8a..6622791675 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -437,18 +437,22 @@ test('ordinary CI requires the exact Nightly artifact producers', () => { assert.equal(buildJob.name, 'Nightly Build Contract'); assert.equal(buildJob.needs, undefined); - assert.equal(buildJob.uses, './.github/workflows/nightly.yml'); + assert.equal(buildJob.uses, './.github/workflows/nightly-artifacts.yml'); 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, }); }); 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; @@ -472,18 +476,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); @@ -519,6 +516,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'), @@ -796,23 +817,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 be9a26296e87806bb3e9bb85aabdf24b6513ba74 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 17:45:47 +0800 Subject: [PATCH 5/8] 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 77484e6632..f5491fc20e 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -144,6 +144,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: Generate web API bindings run: pnpm --dir src/web-ui run gen:types diff --git a/BitFun-Installer/package-lock.json b/BitFun-Installer/package-lock.json index 56699761f0..0978a46959 100644 --- a/BitFun-Installer/package-lock.json +++ b/BitFun-Installer/package-lock.json @@ -8,15 +8,15 @@ "name": "bitfun-installer", "version": "0.2.18", "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 225935fd76..367cb9d1de 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 5b439043d0..384920c3c3 100644 --- a/package.json +++ b/package.json @@ -137,7 +137,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 6622791675..4ff2805c86 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -469,6 +469,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', ); @@ -483,6 +486,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( @@ -495,6 +499,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); assert.ok( committedMetadataIndex < patchIndex && + tauriAlignmentIndex < patchIndex && typeCheckIndex < patchIndex && patchIndex < metadataIndex && metadataIndex < buildIndex, @@ -505,6 +510,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 84dfcb213d..a5cd63c735 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 c967d51bae17d20e365d4be73130fc13ed736777 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 17:51:33 +0800 Subject: [PATCH 6/8] 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 4ff2805c86..bebc0e77e7 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -540,7 +540,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 844b181c9d4b342187b6812b3ce7b331068144d0 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 19:54:52 +0800 Subject: [PATCH 7/8] 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 f5491fc20e..da2a938e21 100644 --- a/.github/workflows/nightly-artifacts.yml +++ b/.github/workflows/nightly-artifacts.yml @@ -144,6 +144,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 bebc0e77e7..59bc38dc13 100644 --- a/scripts/check-github-config.test.mjs +++ b/scripts/check-github-config.test.mjs @@ -472,6 +472,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', ); @@ -486,6 +489,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); @@ -499,6 +503,7 @@ test('nightly validates generated inputs and projected lockfiles before packagin ); assert.ok( committedMetadataIndex < patchIndex && + installerI18nIndex < patchIndex && tauriAlignmentIndex < patchIndex && typeCheckIndex < patchIndex && patchIndex < metadataIndex && @@ -510,6 +515,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 59dedefc890c845084cbf4d3b4ba0875993b881f Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Mon, 24 Aug 2026 19:54:53 +0800 Subject: [PATCH 8/8] chore(installer): refresh projected model copy --- BitFun-Installer/src/i18n/locales/en.json | 2 +- BitFun-Installer/src/i18n/locales/zh-TW.json | 2 +- BitFun-Installer/src/i18n/locales/zh.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/BitFun-Installer/src/i18n/locales/en.json b/BitFun-Installer/src/i18n/locales/en.json index e5a53e8dc5..05dbc58eee 100644 --- a/BitFun-Installer/src/i18n/locales/en.json +++ b/BitFun-Installer/src/i18n/locales/en.json @@ -47,7 +47,7 @@ "modelName": "Model name (e.g. deepseek-v4-flash)", "skip": "Skip for now", "nextTheme": "Next: Theme", - "description": "Configure and manage AI model providers", + "description": "Manage default models, providers, subscriptions, catalog data, and network connections.", "providerLabel": "Select Model Provider", "selectProvider": "or select a preset provider", "customProvider": "Custom Configuration", diff --git a/BitFun-Installer/src/i18n/locales/zh-TW.json b/BitFun-Installer/src/i18n/locales/zh-TW.json index 1efde0d52a..3074ec69b3 100644 --- a/BitFun-Installer/src/i18n/locales/zh-TW.json +++ b/BitFun-Installer/src/i18n/locales/zh-TW.json @@ -47,7 +47,7 @@ "modelName": "模型名稱(如 deepseek-v4-flash)", "skip": "稍後設定", "nextTheme": "下一步:主題", - "description": "設定和管理 AI 模型提供商", + "description": "管理預設模型、模型服務、訂閱帳戶、模型目錄與網路連線。", "providerLabel": "選擇模型提供商", "selectProvider": "或選擇預設提供商", "customProvider": "自定義設定", diff --git a/BitFun-Installer/src/i18n/locales/zh.json b/BitFun-Installer/src/i18n/locales/zh.json index 6bf7eb42c5..e7ee699f09 100644 --- a/BitFun-Installer/src/i18n/locales/zh.json +++ b/BitFun-Installer/src/i18n/locales/zh.json @@ -47,7 +47,7 @@ "modelName": "模型名称(如 deepseek-v4-flash)", "skip": "稍后配置", "nextTheme": "下一步:主题", - "description": "配置和管理 AI 模型提供商", + "description": "管理默认模型、模型服务、订阅账户、模型目录与网络连接。", "providerLabel": "选择模型提供商", "selectProvider": "或选择预设提供商", "customProvider": "自定义配置",