From 67934dcb6b7e979b7794daa7063a8cfcfb00b383 Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 03:24:21 +0800
Subject: [PATCH 01/15] =?UTF-8?q?chore(ci):=20=E6=8B=86=E5=88=86=20dev/pre?=
=?UTF-8?q?view/master=20=E6=B5=81=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/dev.yml | 52 +++++++++++++++
.github/workflows/preview.yml | 118 ++++++++++++++++++++++++++++++++++
.github/workflows/release.yml | 114 ++++++++++++++++++++++++++++++++
.github/workflows/tauri.yml | 72 ---------------------
README.md | 15 ++++-
5 files changed, 296 insertions(+), 75 deletions(-)
create mode 100644 .github/workflows/dev.yml
create mode 100644 .github/workflows/preview.yml
create mode 100644 .github/workflows/release.yml
delete mode 100644 .github/workflows/tauri.yml
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
new file mode 100644
index 0000000..107202b
--- /dev/null
+++ b/.github/workflows/dev.yml
@@ -0,0 +1,52 @@
+name: Dev CI
+
+on:
+ push:
+ branches:
+ - dev
+ workflow_dispatch:
+
+concurrency:
+ group: dev-ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ runs-on: ${{ matrix.platform }}
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: [windows-latest, ubuntu-22.04]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: yarn
+ cache-dependency-path: yarn.lock
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache Rust
+ uses: swatinem/rust-cache@v2
+ with:
+ workspaces: |
+ src-tauri -> src-tauri/target
+
+ - name: Install Linux dependencies
+ if: matrix.platform == 'ubuntu-22.04'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+
+ - name: Build
+ run: yarn build
+
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
new file mode 100644
index 0000000..6d91df3
--- /dev/null
+++ b/.github/workflows/preview.yml
@@ -0,0 +1,118 @@
+name: Preview Prerelease
+
+on:
+ pull_request:
+ types: [closed]
+ branches:
+ - preview
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+concurrency:
+ group: preview-prerelease
+ cancel-in-progress: false
+
+jobs:
+ prepare:
+ if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'preview' && github.event.pull_request.head.ref == 'dev')
+ runs-on: ubuntu-latest
+ outputs:
+ tag_name: ${{ steps.vars.outputs.tag_name }}
+ app_version: ${{ steps.vars.outputs.app_version }}
+
+ steps:
+ - name: Checkout (preview)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: preview
+
+ - name: Configure git
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+ - name: Bump version, commit and tag
+ id: vars
+ shell: pwsh
+ run: |
+ $pkg = Get-Content -Raw package.json | ConvertFrom-Json
+ $baseVersion = [string]$pkg.version
+ if ($baseVersion.Contains("-")) { $baseVersion = $baseVersion.Split("-", 2)[0] }
+
+ $shortSha = (git rev-parse --short=7 HEAD).Trim()
+ $epoch = [int64](Get-Date -AsUTC -UFormat %s)
+ $hex = ("{0:x}" -f $epoch)
+ $preVersion = "$baseVersion-pre.$hex.$shortSha"
+ $tag = "v$preVersion"
+
+ if ((git ls-remote --tags origin "refs/tags/$tag")) {
+ throw "Tag already exists on origin: $tag"
+ }
+
+ ./scripts/set-version.ps1 -Version $preVersion
+ git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml
+ git commit -m "chore(version): 更新版本号为 $preVersion"
+ git tag $tag
+
+ git push origin preview
+ git push origin $tag
+
+ "tag_name=$tag" >> $env:GITHUB_OUTPUT
+ "app_version=$preVersion" >> $env:GITHUB_OUTPUT
+
+ build:
+ needs: prepare
+ runs-on: ${{ matrix.platform }}
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: [windows-latest, ubuntu-22.04]
+
+ steps:
+ - name: Checkout (tag)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ needs.prepare.outputs.tag_name }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: yarn
+ cache-dependency-path: yarn.lock
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache Rust
+ uses: swatinem/rust-cache@v2
+ with:
+ workspaces: |
+ src-tauri -> src-tauri/target
+
+ - name: Install Linux dependencies
+ if: matrix.platform == 'ubuntu-22.04'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
+
+ - name: Install frontend dependencies
+ run: yarn install --frozen-lockfile
+
+ - name: Quick web build
+ run: yarn build
+
+ - name: Build Tauri app and publish prerelease assets
+ uses: tauri-apps/tauri-action@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ projectPath: .
+ releaseDraft: false
+ prerelease: true
+ tagName: ${{ needs.prepare.outputs.tag_name }}
+ releaseName: EndCat ${{ needs.prepare.outputs.tag_name }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..0705664
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,114 @@
+name: Release Draft
+
+on:
+ pull_request:
+ types: [closed]
+ branches:
+ - master
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+concurrency:
+ group: master-release
+ cancel-in-progress: false
+
+jobs:
+ prepare:
+ if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master' && github.event.pull_request.head.ref == 'preview')
+ runs-on: ubuntu-latest
+ outputs:
+ tag_name: ${{ steps.vars.outputs.tag_name }}
+ app_version: ${{ steps.vars.outputs.app_version }}
+
+ steps:
+ - name: Checkout (master)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: master
+
+ - name: Configure git
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+
+ - name: Bump version, commit and tag
+ id: vars
+ shell: pwsh
+ run: |
+ $pkg = Get-Content -Raw package.json | ConvertFrom-Json
+ $current = [string]$pkg.version
+ $releaseVersion = $current
+ if ($releaseVersion.Contains("-")) { $releaseVersion = $releaseVersion.Split("-", 2)[0] }
+
+ $tag = "v$releaseVersion"
+ if ((git ls-remote --tags origin "refs/tags/$tag")) {
+ throw "Tag already exists on origin: $tag"
+ }
+
+ ./scripts/set-version.ps1 -Version $releaseVersion
+ git add package.json src-tauri/tauri.conf.json src-tauri/Cargo.toml
+ git commit -m "chore(version): 更新版本号为 $releaseVersion"
+ git tag $tag
+
+ git push origin master
+ git push origin $tag
+
+ "tag_name=$tag" >> $env:GITHUB_OUTPUT
+ "app_version=$releaseVersion" >> $env:GITHUB_OUTPUT
+
+ build:
+ needs: prepare
+ runs-on: ${{ matrix.platform }}
+ strategy:
+ fail-fast: false
+ matrix:
+ platform: [windows-latest, ubuntu-22.04]
+
+ steps:
+ - name: Checkout (tag)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ needs.prepare.outputs.tag_name }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 18
+ cache: yarn
+ cache-dependency-path: yarn.lock
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache Rust
+ uses: swatinem/rust-cache@v2
+ with:
+ workspaces: |
+ src-tauri -> src-tauri/target
+
+ - name: Install Linux dependencies
+ if: matrix.platform == 'ubuntu-22.04'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
+
+ - name: Install frontend dependencies
+ run: yarn install --frozen-lockfile
+
+ - name: Quick web build
+ run: yarn build
+
+ - name: Build Tauri app and create draft release
+ uses: tauri-apps/tauri-action@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ projectPath: .
+ releaseDraft: true
+ prerelease: false
+ tagName: ${{ needs.prepare.outputs.tag_name }}
+ releaseName: EndCat ${{ needs.prepare.outputs.tag_name }}
diff --git a/.github/workflows/tauri.yml b/.github/workflows/tauri.yml
deleted file mode 100644
index 4bf0dbc..0000000
--- a/.github/workflows/tauri.yml
+++ /dev/null
@@ -1,72 +0,0 @@
-name: Tauri Build
-
-on:
- push:
- branches:
- - main
- tags:
- - "v*"
- pull_request:
-
-jobs:
- build:
- runs-on: ${{ matrix.platform }}
- strategy:
- fail-fast: false
- matrix:
- platform: [windows-latest, ubuntu-22.04]
-
- permissions:
- contents: write
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: 18
- cache: yarn
- cache-dependency-path: yarn.lock
-
- - name: Setup Rust
- uses: dtolnay/rust-toolchain@stable
-
- - name: Cache Rust
- uses: swatinem/rust-cache@v2
- with:
- workspaces: |
- src-tauri -> src-tauri/target
-
- - name: Install Linux dependencies
- if: matrix.platform == 'ubuntu-22.04'
- run: |
- sudo apt-get update
- sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
-
- - name: Install frontend dependencies
- run: yarn install --frozen-lockfile
-
- - name: Quick web build (PR only)
- if: github.event_name == 'pull_request'
- run: yarn build
-
- - name: Build Tauri app
- uses: tauri-apps/tauri-action@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- projectPath: .
- releaseDraft: ${{ startsWith(github.ref, 'refs/tags/') }}
- prerelease: false
- tagName: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || '' }}
- releaseName: ${{ startsWith(github.ref, 'refs/tags/') && format('EndCat {0}', github.ref_name) || '' }}
-
- - name: Upload bundles
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: tauri-bundles-${{ matrix.platform }}
- path: src-tauri/target/release/bundle/**
- retention-days: 7
diff --git a/README.md b/README.md
index f8e338c..6b47ca3 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,7 @@
- Node.js (v18+)
- Rust (最新稳定版)
- VS Code (推荐)
+- Yarn v1(推荐)
### 启动项目
@@ -97,19 +98,27 @@
2. **安装依赖**
```bash
- npm install
+ yarn
```
3. **启动开发模式**
```bash
- npm run tauri dev
+ yarn tauri dev
```
4. **构建生产版本**
```bash
- npm run tauri build
+ yarn tauri build
```
+## 🧩 分支与发布流程
+
+- `dev`:push 触发 CI(`.github/workflows/dev.yml`),不发版
+- `preview`:合并 `dev -> preview` 的 PR 后自动创建 Prerelease(版本号:`{version}-pre.{hex_timestamp}.{short_sha}`)
+- `master`:合并 `preview -> master` 的 PR 后自动创建 Draft Release(正式版本号)
+
+> 版本号会同步写入 `package.json`、`src-tauri/tauri.conf.json`、`src-tauri/Cargo.toml`(脚本:`scripts/set-version.ps1`)。
+
## 贡献者
From 2e2d945d56cfc5aaf8f4e7acff55797336b6e074 Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 03:25:56 +0800
Subject: [PATCH 02/15] =?UTF-8?q?chore(version):=20=E5=8D=87=E7=BA=A7?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E8=87=B3=200.2.0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 2 +-
src-tauri/Cargo.lock | 2 +-
src-tauri/Cargo.toml | 2 +-
src-tauri/tauri.conf.json | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/package.json b/package.json
index c29bf4a..41d3b3c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "endfield-cat",
"private": true,
- "version": "0.1.3",
+ "version": "0.2.0",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 0cf8151..41b2e54 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -878,7 +878,7 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "endfield-cat"
-version = "0.1.3"
+version = "0.2.0"
dependencies = [
"futures-util",
"reqwest",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 1ce5a29..26495d6 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "endfield-cat"
-version = "0.1.3"
+version = "0.2.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index cfa2b8d..54d4bff 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "endfield-cat",
- "version": "0.1.3",
+ "version": "0.2.0",
"identifier": "org.boxcat.endfield-cat",
"build": {
"beforeDevCommand": "yarn dev",
From df2889e8e618c2cacdbc9da741d475df0cc714c5 Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 03:26:09 +0800
Subject: [PATCH 03/15] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=20gitignore?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index c555555..6a25e00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,4 +42,8 @@ endfield-cat-metadata
# Local TLS certs (self-signed)
/certs/*
-scripts
\ No newline at end of file
+scripts
+
+findings.md
+progress.md
+task_plan.md
From fe266d72f66b1e190fcab2fb3127cf9e898d434e Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 03:26:28 +0800
Subject: [PATCH 04/15] =?UTF-8?q?feat(updater):=20=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E9=A2=84=E5=8F=91=E5=B8=83=E6=9B=B4=E6=96=B0=E5=B9=B6=E4=BC=98?=
=?UTF-8?q?=E5=8C=96=E7=89=88=E6=9C=AC=E6=AF=94=E8=BE=83?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 4 +-
src-tauri/src/app_cmd.rs | 5 +
src-tauri/src/lib.rs | 1 +
src-tauri/src/services/release.rs | 91 +++++++++--
src/api/tauriCommands.ts | 4 +
src/components/UpdateDialog.vue | 42 ++++--
src/i18n/locales/en-US.ts | 2 +
src/i18n/locales/zh-CN.ts | 2 +
src/stores/updater.ts | 242 +++++++++++++++++++-----------
yarn.lock | 10 ++
10 files changed, 292 insertions(+), 111 deletions(-)
diff --git a/package.json b/package.json
index 41d3b3c..532dd68 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"@varlet/ui": "^3.13.1",
"echarts": "^5",
"pinia": "^3.0.4",
+ "semver": "^7.7.4",
"vue": "^3.5.13",
"vue-echarts": "^6",
"vue-i18n": "^11.2.8",
@@ -23,6 +24,7 @@
},
"devDependencies": {
"@tauri-apps/cli": "^2",
+ "@types/semver": "^7.7.1",
"@varlet/import-resolver": "^3.13.1",
"@vitejs/plugin-vue": "^5.2.1",
"typescript": "~5.6.2",
@@ -31,4 +33,4 @@
"vite": "^6.0.3",
"vue-tsc": "^2.1.10"
}
-}
\ No newline at end of file
+}
diff --git a/src-tauri/src/app_cmd.rs b/src-tauri/src/app_cmd.rs
index 665fb2d..97e6aed 100644
--- a/src-tauri/src/app_cmd.rs
+++ b/src-tauri/src/app_cmd.rs
@@ -111,6 +111,11 @@ pub async fn fetch_latest_release(client: State<'_, reqwest::Client>) -> Result<
release::fetch_latest_release(&client).await
}
+#[tauri::command]
+pub async fn fetch_latest_prerelease(client: State<'_, reqwest::Client>) -> Result {
+ release::fetch_latest_prerelease(&client).await
+}
+
#[tauri::command]
pub async fn download_and_apply_update(
window: tauri::Window,
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 367ee10..4d894b5 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -44,6 +44,7 @@ pub fn run() {
app_cmd::fetch_metadata_manifest,
app_cmd::check_metadata,
app_cmd::fetch_latest_release,
+ app_cmd::fetch_latest_prerelease,
app_cmd::download_and_apply_update,
app_cmd::test_github_mirror,
hg_api::auth::hg_exchange_user_token,
diff --git a/src-tauri/src/services/release.rs b/src-tauri/src/services/release.rs
index 52522eb..71ded7a 100644
--- a/src-tauri/src/services/release.rs
+++ b/src-tauri/src/services/release.rs
@@ -46,19 +46,23 @@ pub async fn fetch_latest_release(client: &reqwest::Client) -> Result Result Err(err.message),
}
}
+
+pub async fn fetch_latest_prerelease(client: &reqwest::Client) -> Result {
+ let url = "https://api.github.com/repos/BoxCatTeam/endfield-cat/releases?per_page=20";
+ let resp = client
+ .get(url)
+ .header("Accept", "application/vnd.github+json")
+ .header("User-Agent", "endfield-cat/tauri")
+ .send()
+ .await
+ .map_err(|e| e.to_string())?;
+
+ let status = resp.status();
+ if !status.is_success() {
+ return Err(format!("GitHub API status {}", status));
+ }
+
+ let json: serde_json::Value = resp.json().await.map_err(|e| e.to_string())?;
+ let releases = json.as_array().ok_or("Invalid GitHub response: expected array")?;
+
+ let target = releases.iter().find(|r| {
+ r.get("draft").and_then(|v| v.as_bool()) == Some(false)
+ && r.get("prerelease").and_then(|v| v.as_bool()) == Some(true)
+ });
+
+ let Some(target) = target else {
+ return Err("No prerelease found".to_string());
+ };
+
+ let tag_name = target
+ .get("tag_name")
+ .or_else(|| target.get("name"))
+ .and_then(|v| v.as_str())
+ .unwrap_or("")
+ .to_string();
+
+ if tag_name.is_empty() {
+ return Err("Missing tag_name in GitHub response".to_string());
+ }
+
+ let name = target.get("name").and_then(|v| v.as_str()).map(|s| s.to_string());
+ let html_url = target.get("html_url").and_then(|v| v.as_str()).map(|s| s.to_string());
+ let body = target.get("body").and_then(|v| v.as_str()).map(|s| s.to_string());
+
+ let download_url = if cfg!(target_os = "windows") {
+ target
+ .get("assets")
+ .and_then(|v| v.as_array())
+ .and_then(|assets| {
+ assets.iter().find_map(|asset| {
+ let name = asset.get("name").and_then(|v| v.as_str())?;
+ if name.ends_with(".exe") {
+ asset.get("browser_download_url").and_then(|v| v.as_str()).map(|s| s.to_string())
+ } else {
+ None
+ }
+ })
+ })
+ } else {
+ None
+ };
+
+ Ok(LatestRelease { tag_name, name, html_url, download_url, body })
+}
diff --git a/src/api/tauriCommands.ts b/src/api/tauriCommands.ts
index bc61247..ecd0ddb 100644
--- a/src/api/tauriCommands.ts
+++ b/src/api/tauriCommands.ts
@@ -39,6 +39,10 @@ export function fetchLatestRelease() {
return invoke("fetch_latest_release");
}
+export function fetchLatestPrerelease() {
+ return invoke("fetch_latest_prerelease");
+}
+
export function downloadAndApplyUpdate(downloadUrl: string) {
return invoke("download_and_apply_update", { downloadUrl });
}
diff --git a/src/components/UpdateDialog.vue b/src/components/UpdateDialog.vue
index 540838a..dda406f 100644
--- a/src/components/UpdateDialog.vue
+++ b/src/components/UpdateDialog.vue
@@ -1,17 +1,12 @@
@@ -36,14 +31,33 @@ onMounted(async ()=>{
{{ t('settings.update.later') }}
-
+
{{ t('settings.update.manualDownload') }}
+
+
+ {{ t('settings.update.installStable') }}
+
+
+ {{ t('settings.update.installPreview') }}
+
+
{{ t('settings.update.installNow') }}
diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts
index 356125f..47bcf78 100644
--- a/src/i18n/locales/en-US.ts
+++ b/src/i18n/locales/en-US.ts
@@ -102,6 +102,8 @@ export default {
downloadProgress: "Download progress: {progress}%",
preparing: "Preparing installation...",
installNow: "Update Now",
+ installStable: "Install Stable",
+ installPreview: "Install Preview",
manualDownload: "Manual Download",
later: "Later",
installFailed: "Update installation failed",
diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts
index b3ff934..fd96f67 100644
--- a/src/i18n/locales/zh-CN.ts
+++ b/src/i18n/locales/zh-CN.ts
@@ -102,6 +102,8 @@ export default {
downloadProgress: "下载进度: {progress}%",
preparing: "准备安装...",
installNow: "立即更新",
+ installStable: "安装稳定版",
+ installPreview: "安装预览版",
manualDownload: "手动下载",
later: "稍后",
installFailed: "更新安装失败",
diff --git a/src/stores/updater.ts b/src/stores/updater.ts
index f6d63a6..da29118 100644
--- a/src/stores/updater.ts
+++ b/src/stores/updater.ts
@@ -1,100 +1,174 @@
-import { defineStore } from 'pinia'
-import { ref } from 'vue'
-import { openUrl } from '@tauri-apps/plugin-opener'
-import { Snackbar } from '@varlet/ui'
-import i18n from '../i18n' // 直接使用全局 i18n 实例
-import { downloadAndApplyUpdate, fetchLatestRelease, getAppVersion } from '../api/tauriCommands'
+import { defineStore } from "pinia";
+import { ref } from "vue";
+import { openUrl } from "@tauri-apps/plugin-opener";
+import { Snackbar } from "@varlet/ui";
+import * as semver from "semver";
+import i18n from "../i18n";
+import { downloadAndApplyUpdate, fetchLatestPrerelease, fetchLatestRelease, getAppVersion } from "../api/tauriCommands";
export type LatestRelease = {
- tag_name: string
- name?: string
- html_url?: string
- download_url?: string
- body?: string
-}
-
-export const useUpdaterStore = defineStore('updater', () => {
- const updateInfo = ref(null)
- const showUpdateDialog = ref(false)
- const isUpdating = ref(false)
- const isChecking = ref(false)
-
- // 版本比较:返回远端是否更高
- const isNewerVersion = (local: string, remote: string): boolean => {
- const parseVersion = (v: string) => v.replace(/^v/, '').split('.').map(Number)
- const localParts = parseVersion(local)
- const remoteParts = parseVersion(remote)
-
- for (let i = 0; i < Math.max(localParts.length, remoteParts.length); i++) {
- const l = localParts[i] || 0
- const r = remoteParts[i] || 0
- if (r > l) return true
- if (r < l) return false
- }
- return false
+ tag_name: string;
+ name?: string;
+ html_url?: string;
+ download_url?: string;
+ body?: string;
+};
+
+type UpdateTarget = "primary" | "alt";
+
+const normalizeVersion = (v: string) => v.replace(/^v/i, "").trim();
+
+const parseSemver = (v: string) => {
+ const normalized = normalizeVersion(v);
+ const valid = semver.valid(normalized, { loose: true }) ?? semver.clean(normalized, { loose: true });
+ if (!valid) return null;
+ return semver.parse(valid, { loose: true });
+};
+
+const isHexLowerLike = (s: unknown) => typeof s === "string" && /^[0-9a-f]+$/i.test(s);
+
+const parsePreHexTimestamp = (v: semver.SemVer) => {
+ const pre = v.prerelease;
+ if (pre.length < 2) return null;
+ if (pre[0] !== "pre") return null;
+ const ts = pre[1];
+ if (!isHexLowerLike(ts)) return null;
+ try {
+ return BigInt(`0x${String(ts)}`);
+ } catch {
+ return null;
+ }
+};
+
+export const useUpdaterStore = defineStore("updater", () => {
+ const localVersion = ref("");
+ const updateInfo = ref(null);
+ const altUpdateInfo = ref(null);
+ const showUpdateDialog = ref(false);
+ const isUpdating = ref(false);
+ const isChecking = ref(false);
+
+ const isRemoteNewer = (local: string, remote: string) => {
+ const localParsed = parseSemver(local);
+ const remoteParsed = parseSemver(remote);
+ if (!localParsed || !remoteParsed) return false;
+
+ if (
+ localParsed.major === remoteParsed.major &&
+ localParsed.minor === remoteParsed.minor &&
+ localParsed.patch === remoteParsed.patch
+ ) {
+ const localTs = parsePreHexTimestamp(localParsed);
+ const remoteTs = parsePreHexTimestamp(remoteParsed);
+ if (localTs !== null && remoteTs !== null) {
+ if (remoteTs > localTs) return true;
+ if (remoteTs < localTs) return false;
+ }
}
- const checkForUpdate = async (silent = false) => {
- if (isChecking.value) return
- isChecking.value = true
- try {
- const [localVersion, release] = await Promise.all([
- getAppVersion(),
- fetchLatestRelease()
- ])
-
- if (release && isNewerVersion(localVersion, release.tag_name)) {
- updateInfo.value = release
- showUpdateDialog.value = true
- } else if (!silent) {
- // 主动检查时提示“已是最新”
- Snackbar.success(i18n.global.t('settings.update.alreadyLatest') || 'Already latest version')
- }
- } catch (error) {
- console.error("Failed to check for updates:", error)
- if (!silent) {
- Snackbar.error(i18n.global.t('settings.update.checkFailed') || 'Check failed')
- }
- } finally {
- isChecking.value = false
+ return semver.compare(remoteParsed, localParsed) > 0;
+ };
+
+ const checkForUpdate = async (silent = false) => {
+ if (isChecking.value) return;
+ isChecking.value = true;
+
+ showUpdateDialog.value = false;
+ updateInfo.value = null;
+ altUpdateInfo.value = null;
+
+ try {
+ localVersion.value = await getAppVersion();
+ const isPreviewBuild = normalizeVersion(localVersion.value).toLowerCase().includes("-pre");
+
+ if (isPreviewBuild) {
+ const [stableRes, preRes] = await Promise.allSettled([
+ fetchLatestRelease(),
+ fetchLatestPrerelease(),
+ ]);
+
+ const stable = stableRes.status === "fulfilled" ? stableRes.value : null;
+ const prerelease = preRes.status === "fulfilled" ? preRes.value : null;
+
+ const canUpdateStable = !!stable && isRemoteNewer(localVersion.value, stable.tag_name);
+ const canUpdatePre = !!prerelease && isRemoteNewer(localVersion.value, prerelease.tag_name);
+
+ if (canUpdateStable && canUpdatePre && stable && prerelease) {
+ updateInfo.value = stable;
+ altUpdateInfo.value = prerelease;
+ showUpdateDialog.value = true;
+ return;
}
- }
- const installUpdate = async () => {
- if (!updateInfo.value?.download_url) {
- Snackbar.error(i18n.global.t('settings.update.installFailed') || 'Install failed')
- return
+ const target = (canUpdateStable ? stable : null) ?? (canUpdatePre ? prerelease : null);
+ if (target) {
+ updateInfo.value = target;
+ showUpdateDialog.value = true;
+ return;
}
- isUpdating.value = true
- try {
- await downloadAndApplyUpdate(updateInfo.value.download_url)
- } catch (error) {
- console.error("Update failed:", error)
- Snackbar.error(i18n.global.t('settings.update.installFailed') || 'Install failed')
- isUpdating.value = false
+ if (!silent) {
+ Snackbar.success(i18n.global.t("settings.update.alreadyLatest") || "Already latest version");
}
+ return;
+ }
+
+ const release = await fetchLatestRelease();
+ if (release && isRemoteNewer(localVersion.value, release.tag_name)) {
+ updateInfo.value = release;
+ showUpdateDialog.value = true;
+ } else if (!silent) {
+ Snackbar.success(i18n.global.t("settings.update.alreadyLatest") || "Already latest version");
+ }
+ } catch (error) {
+ console.error("Failed to check for updates:", error);
+ if (!silent) {
+ Snackbar.error(i18n.global.t("settings.update.checkFailed") || "Check failed");
+ }
+ } finally {
+ isChecking.value = false;
}
+ };
- const manualDownload = async () => {
- if (updateInfo.value?.html_url) {
- await openUrl(updateInfo.value.html_url)
- }
- showUpdateDialog.value = false
+ const installUpdate = async (target: UpdateTarget = "primary") => {
+ const info = target === "alt" ? altUpdateInfo.value : updateInfo.value;
+ if (!info?.download_url) {
+ Snackbar.error(i18n.global.t("settings.update.installFailed") || "Install failed");
+ return;
}
- const dismissDialog = () => {
- showUpdateDialog.value = false
+ isUpdating.value = true;
+ try {
+ await downloadAndApplyUpdate(info.download_url);
+ } catch (error) {
+ console.error("Update failed:", error);
+ Snackbar.error(i18n.global.t("settings.update.installFailed") || "Install failed");
+ isUpdating.value = false;
}
+ };
- return {
- updateInfo,
- showUpdateDialog,
- isUpdating,
- isChecking,
- checkForUpdate,
- installUpdate,
- manualDownload,
- dismissDialog
+ const manualDownload = async (target: UpdateTarget = "primary") => {
+ const info = target === "alt" ? altUpdateInfo.value : updateInfo.value;
+ if (info?.html_url) {
+ await openUrl(info.html_url);
}
-})
+ showUpdateDialog.value = false;
+ };
+
+ const dismissDialog = () => {
+ showUpdateDialog.value = false;
+ };
+
+ return {
+ localVersion,
+ updateInfo,
+ altUpdateInfo,
+ showUpdateDialog,
+ isUpdating,
+ isChecking,
+ checkForUpdate,
+ installUpdate,
+ manualDownload,
+ dismissDialog,
+ };
+});
diff --git a/yarn.lock b/yarn.lock
index 879bd26..d5c368a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -438,6 +438,11 @@
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+"@types/semver@^7.7.1":
+ version "7.7.1"
+ resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528"
+ integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==
+
"@varlet/icons@3.13.1":
version "3.13.1"
resolved "https://registry.npmmirror.com/@varlet/icons/-/icons-3.13.1.tgz#fe496e4913826cb1c456849d496261b9537287cb"
@@ -982,6 +987,11 @@ scule@^1.3.0:
resolved "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz#6efbd22fd0bb801bdcc585c89266a7d2daa8fbd3"
integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==
+semver@^7.7.4:
+ version "7.7.4"
+ resolved "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a"
+ integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==
+
source-map-js@^1.0.2, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
From 8affa997e0c868ae7acee5e7d10a809abca0dbbf Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 03:29:48 +0800
Subject: [PATCH 05/15] =?UTF-8?q?chore(node):=20=E7=BB=9F=E4=B8=80?=
=?UTF-8?q?=E8=87=B3=20Node=2024.x?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/dev.yml | 2 +-
.github/workflows/preview.yml | 2 +-
.github/workflows/release.yml | 2 +-
README.md | 2 +-
package.json | 3 +++
5 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
index 107202b..82373d0 100644
--- a/.github/workflows/dev.yml
+++ b/.github/workflows/dev.yml
@@ -25,7 +25,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: 18
+ node-version: 24
cache: yarn
cache-dependency-path: yarn.lock
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
index 6d91df3..bd3c5da 100644
--- a/.github/workflows/preview.yml
+++ b/.github/workflows/preview.yml
@@ -81,7 +81,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: 18
+ node-version: 24
cache: yarn
cache-dependency-path: yarn.lock
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0705664..c1969c4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -77,7 +77,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
- node-version: 18
+ node-version: 24
cache: yarn
cache-dependency-path: yarn.lock
diff --git a/README.md b/README.md
index 6b47ca3..20ba21f 100644
--- a/README.md
+++ b/README.md
@@ -83,7 +83,7 @@
## 🚀 开发指南
### 前置要求
-- Node.js (v18+)
+- Node.js (24.x)
- Rust (最新稳定版)
- VS Code (推荐)
- Yarn v1(推荐)
diff --git a/package.json b/package.json
index 532dd68..3abf19c 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,9 @@
"private": true,
"version": "0.2.0",
"type": "module",
+ "engines": {
+ "node": ">=24 <25"
+ },
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
From 1c99a0522e63704870b81d130af9d5f0e52f99cb Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 03:41:46 +0800
Subject: [PATCH 06/15] =?UTF-8?q?chore(scripts):=20=E8=B7=9F=E8=B8=AA?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=90=8C=E6=AD=A5=E8=84=9A=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 -
scripts/set-version.ps1 | 109 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 109 insertions(+), 1 deletion(-)
create mode 100644 scripts/set-version.ps1
diff --git a/.gitignore b/.gitignore
index 6a25e00..4315962 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,7 +42,6 @@ endfield-cat-metadata
# Local TLS certs (self-signed)
/certs/*
-scripts
findings.md
progress.md
diff --git a/scripts/set-version.ps1 b/scripts/set-version.ps1
new file mode 100644
index 0000000..9b963cc
--- /dev/null
+++ b/scripts/set-version.ps1
@@ -0,0 +1,109 @@
+param(
+ [Parameter(Mandatory = $true)]
+ [string] $Version
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+
+$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
+Set-Location $repoRoot
+
+function Assert-SemVer {
+ param([Parameter(Mandatory = $true)][string]$Value)
+
+ $pattern = '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$'
+ if (-not ($Value -match $pattern)) {
+ throw "Invalid semver: $Value"
+ }
+}
+
+function Read-TextFile {
+ param([Parameter(Mandatory = $true)][string]$Path)
+ if (-not (Test-Path -LiteralPath $Path)) {
+ throw "File not found: $Path"
+ }
+ $content = [System.IO.File]::ReadAllText((Resolve-Path -LiteralPath $Path), [System.Text.Encoding]::UTF8)
+ if ($content.Length -gt 0 -and $content[0] -eq [char]0xFEFF) {
+ $content = $content.Substring(1)
+ }
+ return $content
+}
+
+function Write-TextFileUtf8NoBom {
+ param(
+ [Parameter(Mandatory = $true)][string]$Path,
+ [Parameter(Mandatory = $true)][string]$Content
+ )
+
+ $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
+ [System.IO.File]::WriteAllText((Resolve-Path -LiteralPath $Path), $Content, $utf8NoBom)
+}
+
+function Set-JsonVersionInPlace {
+ param(
+ [Parameter(Mandatory = $true)][string]$Path,
+ [Parameter(Mandatory = $true)][string]$NewVersion
+ )
+
+ $text = Read-TextFile -Path $Path
+
+ $re = [regex]'"version"\s*:\s*"([^"]+)"'
+ $m = $re.Match($text)
+ if (-not $m.Success) {
+ throw "Could not find `"version`" field in JSON: $Path"
+ }
+
+ $updated = $re.Replace($text, ('"version": "{0}"' -f $NewVersion), 1)
+ Write-TextFileUtf8NoBom -Path $Path -Content $updated
+}
+
+function Set-CargoPackageVersionInPlace {
+ param(
+ [Parameter(Mandatory = $true)][string]$Path,
+ [Parameter(Mandatory = $true)][string]$NewVersion
+ )
+
+ $text = Read-TextFile -Path $Path
+ $newline = "`n"
+ if ($text -match "`r`n") { $newline = "`r`n" }
+
+ $lines = $text -split "`r?`n"
+
+ $inPackage = $false
+ $changed = $false
+
+ for ($i = 0; $i -lt $lines.Length; $i++) {
+ $line = $lines[$i]
+
+ if ($line -match '^\s*\[package\]\s*$') {
+ $inPackage = $true
+ continue
+ }
+
+ if ($inPackage -and $line -match '^\s*\[.+\]\s*$') {
+ $inPackage = $false
+ }
+
+ if ($inPackage -and (-not $changed) -and $line -match '^\s*version\s*=\s*".*"\s*$') {
+ $indent = ($line -replace '^(\s*).+$', '$1')
+ $lines[$i] = ($indent + 'version = "' + $NewVersion + '"')
+ $changed = $true
+ }
+ }
+
+ if (-not $changed) {
+ throw "Could not update [package].version in Cargo.toml: $Path"
+ }
+
+ $updated = ($lines -join $newline)
+ Write-TextFileUtf8NoBom -Path $Path -Content $updated
+}
+
+Assert-SemVer -Value $Version
+
+Set-JsonVersionInPlace -Path "package.json" -NewVersion $Version
+Set-JsonVersionInPlace -Path "src-tauri/tauri.conf.json" -NewVersion $Version
+Set-CargoPackageVersionInPlace -Path "src-tauri/Cargo.toml" -NewVersion $Version
+
+Write-Host ("Version synced to {0}" -f $Version)
From 2f4adf8c9b5c436d4842db131726575299361a44 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 6 Feb 2026 19:45:02 +0000
Subject: [PATCH 07/15] =?UTF-8?q?chore(version):=20=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E4=B8=BA=200.2.0-pre.698644be.45f91?=
=?UTF-8?q?51?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 2 +-
src-tauri/Cargo.toml | 2 +-
src-tauri/tauri.conf.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 3abf19c..0d078ef 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "endfield-cat",
"private": true,
- "version": "0.2.0",
+ "version": "0.2.0-pre.698644be.45f9151",
"type": "module",
"engines": {
"node": ">=24 <25"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 26495d6..2a20c82 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "endfield-cat"
-version = "0.2.0"
+version = "0.2.0-pre.698644be.45f9151"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 54d4bff..d7d3a44 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "endfield-cat",
- "version": "0.2.0",
+ "version": "0.2.0-pre.698644be.45f9151",
"identifier": "org.boxcat.endfield-cat",
"build": {
"beforeDevCommand": "yarn dev",
From 43be2f1962e3de8c1a6e1ab9a2511d4bbd107481 Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 03:57:31 +0800
Subject: [PATCH 08/15] =?UTF-8?q?fix(ci):=20=E4=BF=AE=E6=AD=A3=20tauri-act?=
=?UTF-8?q?ion=20=E5=BC=95=E7=94=A8=E7=89=88=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/preview.yml | 2 +-
.github/workflows/release.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
index bd3c5da..6f42588 100644
--- a/.github/workflows/preview.yml
+++ b/.github/workflows/preview.yml
@@ -107,7 +107,7 @@ jobs:
run: yarn build
- name: Build Tauri app and publish prerelease assets
- uses: tauri-apps/tauri-action@v1
+ uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c1969c4..c65e0b1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -103,7 +103,7 @@ jobs:
run: yarn build
- name: Build Tauri app and create draft release
- uses: tauri-apps/tauri-action@v1
+ uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
From 6b6cc3a19f11d5d340ebedb3316c2fcc0e466280 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 6 Feb 2026 20:00:05 +0000
Subject: [PATCH 09/15] =?UTF-8?q?chore(version):=20=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E4=B8=BA=200.2.0-pre.69864845.b99c5?=
=?UTF-8?q?f1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 2 +-
src-tauri/Cargo.toml | 2 +-
src-tauri/tauri.conf.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 0d078ef..8a58afe 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "endfield-cat",
"private": true,
- "version": "0.2.0-pre.698644be.45f9151",
+ "version": "0.2.0-pre.69864845.b99c5f1",
"type": "module",
"engines": {
"node": ">=24 <25"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 2a20c82..6dc575e 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "endfield-cat"
-version = "0.2.0-pre.698644be.45f9151"
+version = "0.2.0-pre.69864845.b99c5f1"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index d7d3a44..9a74f28 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "endfield-cat",
- "version": "0.2.0-pre.698644be.45f9151",
+ "version": "0.2.0-pre.69864845.b99c5f1",
"identifier": "org.boxcat.endfield-cat",
"build": {
"beforeDevCommand": "yarn dev",
From a69ad6976142375aca697a734c19300e55bf75f6 Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sat, 7 Feb 2026 04:54:04 +0800
Subject: [PATCH 10/15] =?UTF-8?q?chore(ci):=20=E5=A2=9E=E5=8A=A0macOS?=
=?UTF-8?q?=E6=9E=84=E5=BB=BA=E5=B9=B6=E8=BE=93=E5=87=BA=E4=BE=BF=E6=90=BA?=
=?UTF-8?q?=E7=89=88exe?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/dev.yml | 5 ++++-
.github/workflows/preview.yml | 34 ++++++++++++++++++++++++++++++++--
.github/workflows/release.yml | 34 ++++++++++++++++++++++++++++++++--
src-tauri/tauri.conf.json | 10 ++++++++--
4 files changed, 76 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml
index 82373d0..97076a5 100644
--- a/.github/workflows/dev.yml
+++ b/.github/workflows/dev.yml
@@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- platform: [windows-latest, ubuntu-22.04]
+ platform: [windows-latest, macos-latest, ubuntu-22.04]
steps:
- name: Checkout
@@ -50,3 +50,6 @@ jobs:
- name: Build
run: yarn build
+ - name: Rust tests
+ run: cargo test --manifest-path src-tauri/Cargo.toml
+
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
index 6f42588..569acd1 100644
--- a/.github/workflows/preview.yml
+++ b/.github/workflows/preview.yml
@@ -69,7 +69,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- platform: [windows-latest, ubuntu-22.04]
+ platform: [windows-latest, macos-latest, ubuntu-22.04]
steps:
- name: Checkout (tag)
@@ -98,7 +98,7 @@ jobs:
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
- sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
+ sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf rpm
- name: Install frontend dependencies
run: yarn install --frozen-lockfile
@@ -106,6 +106,9 @@ jobs:
- name: Quick web build
run: yarn build
+ - name: Rust tests
+ run: cargo test --manifest-path src-tauri/Cargo.toml
+
- name: Build Tauri app and publish prerelease assets
uses: tauri-apps/tauri-action@v0
env:
@@ -116,3 +119,30 @@ jobs:
prerelease: true
tagName: ${{ needs.prepare.outputs.tag_name }}
releaseName: EndCat ${{ needs.prepare.outputs.tag_name }}
+
+ - name: Create portable exe (Windows)
+ if: matrix.platform == 'windows-latest'
+ shell: pwsh
+ run: |
+ $version = "${{ needs.prepare.outputs.app_version }}"
+ $exe = "src-tauri/target/release/endfield-cat.exe"
+ if (-not (Test-Path -LiteralPath $exe)) {
+ throw "Missing binary: $exe"
+ }
+
+ $outDir = "src-tauri/target/release/bundle/portable"
+ New-Item -ItemType Directory -Force -Path $outDir | Out-Null
+ $portableExe = Join-Path $outDir ("endfield-cat_{0}_x64_portable.exe" -f $version)
+ Copy-Item -LiteralPath $exe -Destination $portableExe -Force
+
+ "PORTABLE_EXE=$portableExe" >> $env:GITHUB_ENV
+
+ - name: Upload portable exe to release (Windows)
+ if: matrix.platform == 'windows-latest'
+ shell: pwsh
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ $tag = "${{ needs.prepare.outputs.tag_name }}"
+ if (-not $env:PORTABLE_EXE) { throw "PORTABLE_EXE env is not set" }
+ gh release upload $tag "$env:PORTABLE_EXE" --clobber --repo "$env:GITHUB_REPOSITORY"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c65e0b1..761210e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -65,7 +65,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- platform: [windows-latest, ubuntu-22.04]
+ platform: [windows-latest, macos-latest, ubuntu-22.04]
steps:
- name: Checkout (tag)
@@ -94,7 +94,7 @@ jobs:
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
- sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf
+ sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf rpm
- name: Install frontend dependencies
run: yarn install --frozen-lockfile
@@ -102,6 +102,9 @@ jobs:
- name: Quick web build
run: yarn build
+ - name: Rust tests
+ run: cargo test --manifest-path src-tauri/Cargo.toml
+
- name: Build Tauri app and create draft release
uses: tauri-apps/tauri-action@v0
env:
@@ -112,3 +115,30 @@ jobs:
prerelease: false
tagName: ${{ needs.prepare.outputs.tag_name }}
releaseName: EndCat ${{ needs.prepare.outputs.tag_name }}
+
+ - name: Create portable exe (Windows)
+ if: matrix.platform == 'windows-latest'
+ shell: pwsh
+ run: |
+ $version = "${{ needs.prepare.outputs.app_version }}"
+ $exe = "src-tauri/target/release/endfield-cat.exe"
+ if (-not (Test-Path -LiteralPath $exe)) {
+ throw "Missing binary: $exe"
+ }
+
+ $outDir = "src-tauri/target/release/bundle/portable"
+ New-Item -ItemType Directory -Force -Path $outDir | Out-Null
+ $portableExe = Join-Path $outDir ("endfield-cat_{0}_x64_portable.exe" -f $version)
+ Copy-Item -LiteralPath $exe -Destination $portableExe -Force
+
+ "PORTABLE_EXE=$portableExe" >> $env:GITHUB_ENV
+
+ - name: Upload portable exe to release (Windows)
+ if: matrix.platform == 'windows-latest'
+ shell: pwsh
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ $tag = "${{ needs.prepare.outputs.tag_name }}"
+ if (-not $env:PORTABLE_EXE) { throw "PORTABLE_EXE env is not set" }
+ gh release upload $tag "$env:PORTABLE_EXE" --clobber --repo "$env:GITHUB_REPOSITORY"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 54d4bff..330a549 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -38,7 +38,13 @@
},
"bundle": {
"active": true,
- "targets": "all",
+ "targets": [
+ "nsis",
+ "dmg",
+ "deb",
+ "rpm",
+ "appimage"
+ ],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@@ -47,4 +53,4 @@
"icons/icon.ico"
]
}
-}
\ No newline at end of file
+}
From 28efb03e4b138e42d87adaa1556cbec2d8b2336c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 6 Feb 2026 20:57:53 +0000
Subject: [PATCH 11/15] =?UTF-8?q?chore(version):=20=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E4=B8=BA=200.2.0-pre.698655d1.4bed4?=
=?UTF-8?q?5b?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 2 +-
src-tauri/Cargo.toml | 2 +-
src-tauri/tauri.conf.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 8a58afe..7c5efeb 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "endfield-cat",
"private": true,
- "version": "0.2.0-pre.69864845.b99c5f1",
+ "version": "0.2.0-pre.698655d1.4bed45b",
"type": "module",
"engines": {
"node": ">=24 <25"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 6dc575e..261cdc6 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "endfield-cat"
-version = "0.2.0-pre.69864845.b99c5f1"
+version = "0.2.0-pre.698655d1.4bed45b"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index c61ac8c..da79857 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "endfield-cat",
- "version": "0.2.0-pre.69864845.b99c5f1",
+ "version": "0.2.0-pre.698655d1.4bed45b",
"identifier": "org.boxcat.endfield-cat",
"build": {
"beforeDevCommand": "yarn dev",
From 49cabd117b238835d30b794cef9f69807d3c8e59 Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sun, 8 Feb 2026 04:26:35 +0800
Subject: [PATCH 12/15] =?UTF-8?q?fix(metadata):=20=E9=BB=98=E8=AE=A4?=
=?UTF-8?q?=E4=BD=BF=E7=94=A8=20latest=20=E8=8E=B7=E5=8F=96=E5=85=83?=
=?UTF-8?q?=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src-tauri/src/app_cmd.rs | 7 ++-----
src/pages/SettingsPage.vue | 4 ++--
src/stores/app.ts | 7 +++----
3 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/src-tauri/src/app_cmd.rs b/src-tauri/src/app_cmd.rs
index 97e6aed..2d42c4a 100644
--- a/src-tauri/src/app_cmd.rs
+++ b/src-tauri/src/app_cmd.rs
@@ -85,20 +85,17 @@ pub async fn reset_metadata(
#[tauri::command]
pub async fn update_metadata(
window: tauri::Window,
- app: AppHandle,
+ _app: AppHandle,
client: State<'_, reqwest::Client>,
base_url: Option,
) -> Result {
let exe_dir = exe_dir()?;
-
- // Use app version for metadata URL
- let app_version = app.package_info().version.to_string();
metadata::update_metadata(
&exe_dir,
&client,
base_url,
- Some(app_version),
+ None,
|progress| {
let _ = window.emit("metadata-update-progress", progress);
},
diff --git a/src/pages/SettingsPage.vue b/src/pages/SettingsPage.vue
index 99b0237..baa26d1 100644
--- a/src/pages/SettingsPage.vue
+++ b/src/pages/SettingsPage.vue
@@ -95,7 +95,7 @@ const testSourceConnection = async (source: MetadataSourceType) => {
connectivity.value[source] = { status: 'testing', latency: 0, error: '' }
const start = performance.now()
try {
- const version = appStore.currentAppVersion || metadataVersion.value
+ const version = metadataVersion.value.trim() || 'latest'
await fetchMetadataManifest({
baseUrl,
version
@@ -304,7 +304,7 @@ const verifyMetadataFiles = async () => {
const resetMetadata = async () => {
resetMetadataLoading.value = true
try {
- const version = appStore.currentAppVersion || metadataVersion.value
+ const version = metadataVersion.value.trim() || 'latest'
await resetMetadataCommand({
baseUrl: metadataBaseUrl.value,
version
diff --git a/src/stores/app.ts b/src/stores/app.ts
index 4914275..958aa73 100644
--- a/src/stores/app.ts
+++ b/src/stores/app.ts
@@ -91,8 +91,8 @@ export const useAppStore = defineStore('app', () => {
if (sourceType === 'custom') {
return normalizeBaseUrl(customBase ?? metadataCustomBase.value)
}
- // 优先使用 app version,latest 作为兜底
- const version = currentAppVersion.value || metadataVersion.value.trim() || DEFAULT_METADATA_VERSION
+ // 默认使用 latest,可通过 metadataVersion 覆盖
+ const version = metadataVersion.value.trim() || DEFAULT_METADATA_VERSION
if (sourceType === 'mirror') {
return METADATA_MIRROR_TEMPLATE.replace('{version}', version)
}
@@ -236,7 +236,7 @@ export const useAppStore = defineStore('app', () => {
if (metadataBaseUrl.value.trim()) {
try {
- const version = currentAppVersion.value || metadataVersion.value
+ const version = metadataVersion.value.trim() || DEFAULT_METADATA_VERSION
const remote = await fetchMetadataManifest({ baseUrl: metadataBaseUrl.value, version })
merged = { ...status, remote }
} catch (error) {
@@ -326,4 +326,3 @@ export const useAppStore = defineStore('app', () => {
}
})
-
From 683f33a242aba034de6c99043f7da7495ee17e71 Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sun, 8 Feb 2026 04:26:43 +0800
Subject: [PATCH 13/15] =?UTF-8?q?ci(release):=20=E6=B7=BB=E5=8A=A0?=
=?UTF-8?q?=E5=A4=9A=E5=B9=B3=E5=8F=B0=20ARM=20=E6=9E=84=E5=BB=BA=E7=9F=A9?=
=?UTF-8?q?=E9=98=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/release.yml | 39 ++++++++++++++++++++++++++---------
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 761210e..0559abc 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -61,11 +61,23 @@ jobs:
build:
needs: prepare
- runs-on: ${{ matrix.platform }}
+ runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
- platform: [windows-latest, macos-latest, ubuntu-22.04]
+ include:
+ - os: windows-latest
+ target: x86_64-pc-windows-msvc
+ - os: windows-latest
+ target: aarch64-pc-windows-msvc
+ - os: macos-13
+ target: x86_64-apple-darwin
+ - os: macos-14
+ target: aarch64-apple-darwin
+ - os: ubuntu-22.04
+ target: x86_64-unknown-linux-gnu
+ - os: ubuntu-22.04-arm64
+ target: aarch64-unknown-linux-gnu
steps:
- name: Checkout (tag)
@@ -84,6 +96,9 @@ jobs:
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
+ - name: Add Rust target
+ run: rustup target add ${{ matrix.target }}
+
- name: Cache Rust
uses: swatinem/rust-cache@v2
with:
@@ -91,7 +106,7 @@ jobs:
src-tauri -> src-tauri/target
- name: Install Linux dependencies
- if: matrix.platform == 'ubuntu-22.04'
+ if: startsWith(matrix.os, 'ubuntu-22.04')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf rpm
@@ -115,26 +130,30 @@ jobs:
prerelease: false
tagName: ${{ needs.prepare.outputs.tag_name }}
releaseName: EndCat ${{ needs.prepare.outputs.tag_name }}
+ target: ${{ matrix.target }}
- name: Create portable exe (Windows)
- if: matrix.platform == 'windows-latest'
+ if: matrix.os == 'windows-latest'
shell: pwsh
run: |
$version = "${{ needs.prepare.outputs.app_version }}"
- $exe = "src-tauri/target/release/endfield-cat.exe"
- if (-not (Test-Path -LiteralPath $exe)) {
- throw "Missing binary: $exe"
- }
+ $candidates = @(
+ "src-tauri/target/${{ matrix.target }}/release/endfield-cat.exe",
+ "src-tauri/target/release/endfield-cat.exe"
+ )
+ $exe = $candidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
+ if (-not $exe) { throw "Missing binary for target ${{ matrix.target }}" }
$outDir = "src-tauri/target/release/bundle/portable"
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
- $portableExe = Join-Path $outDir ("endfield-cat_{0}_x64_portable.exe" -f $version)
+ $suffix = if ("${{ matrix.target }}" -like "aarch64*") { "arm64" } else { "x64" }
+ $portableExe = Join-Path $outDir ("endfield-cat_{0}_{1}_portable.exe" -f $version, $suffix)
Copy-Item -LiteralPath $exe -Destination $portableExe -Force
"PORTABLE_EXE=$portableExe" >> $env:GITHUB_ENV
- name: Upload portable exe to release (Windows)
- if: matrix.platform == 'windows-latest'
+ if: matrix.os == 'windows-latest'
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
From 8215fe3135aad0172c5745c6b8f6d818811b9118 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 8 Feb 2026 11:36:59 +0000
Subject: [PATCH 14/15] =?UTF-8?q?chore(version):=20=E6=9B=B4=E6=96=B0?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7=E4=B8=BA=200.2.0-pre.6988755b.08770?=
=?UTF-8?q?3a?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package.json | 2 +-
src-tauri/Cargo.toml | 2 +-
src-tauri/tauri.conf.json | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 7c5efeb..c9da6b8 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "endfield-cat",
"private": true,
- "version": "0.2.0-pre.698655d1.4bed45b",
+ "version": "0.2.0-pre.6988755b.087703a",
"type": "module",
"engines": {
"node": ">=24 <25"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 261cdc6..ea02799 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "endfield-cat"
-version = "0.2.0-pre.698655d1.4bed45b"
+version = "0.2.0-pre.6988755b.087703a"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index da79857..7f01e01 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "endfield-cat",
- "version": "0.2.0-pre.698655d1.4bed45b",
+ "version": "0.2.0-pre.6988755b.087703a",
"identifier": "org.boxcat.endfield-cat",
"build": {
"beforeDevCommand": "yarn dev",
From 00a95bdeb3865009f69fda813a5666f2703523cc Mon Sep 17 00:00:00 2001
From: Yueer <36443462+YueerMoe@users.noreply.github.com>
Date: Sun, 8 Feb 2026 20:45:24 +0800
Subject: [PATCH 15/15] =?UTF-8?q?refactor(release):=20=E6=8F=90=E5=8F=96?=
=?UTF-8?q?=20release=20=E8=A7=A3=E6=9E=90=E5=B9=B6=E6=94=B9=E8=BF=9B?=
=?UTF-8?q?=E7=89=88=E6=9C=AC=E5=90=8C=E6=AD=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/preview.yml | 1 +
.github/workflows/release.yml | 1 +
scripts/set-version.ps1 | 10 +-
src-tauri/Cargo.lock | 2 +-
src-tauri/src/services/release.rs | 164 ++++++++++++++++--------------
5 files changed, 99 insertions(+), 79 deletions(-)
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
index 569acd1..9761644 100644
--- a/.github/workflows/preview.yml
+++ b/.github/workflows/preview.yml
@@ -117,6 +117,7 @@ jobs:
projectPath: .
releaseDraft: false
prerelease: true
+ releaseNotes: true
tagName: ${{ needs.prepare.outputs.tag_name }}
releaseName: EndCat ${{ needs.prepare.outputs.tag_name }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0559abc..ef2cbc0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -128,6 +128,7 @@ jobs:
projectPath: .
releaseDraft: true
prerelease: false
+ releaseNotes: true
tagName: ${{ needs.prepare.outputs.tag_name }}
releaseName: EndCat ${{ needs.prepare.outputs.tag_name }}
target: ${{ matrix.target }}
diff --git a/scripts/set-version.ps1 b/scripts/set-version.ps1
index 9b963cc..548c78e 100644
--- a/scripts/set-version.ps1
+++ b/scripts/set-version.ps1
@@ -85,10 +85,12 @@ function Set-CargoPackageVersionInPlace {
$inPackage = $false
}
- if ($inPackage -and (-not $changed) -and $line -match '^\s*version\s*=\s*".*"\s*$') {
- $indent = ($line -replace '^(\s*).+$', '$1')
- $lines[$i] = ($indent + 'version = "' + $NewVersion + '"')
- $changed = $true
+ if ($inPackage -and (-not $changed) -and $line -match '^\s*version\s*=\s*".*"') {
+ $updatedLine = $line -replace '(?<=version\s*=\s*")[^"]+', $NewVersion
+ if ($updatedLine -ne $line) {
+ $lines[$i] = $updatedLine
+ $changed = $true
+ }
}
}
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 41b2e54..904ce6e 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -878,7 +878,7 @@ checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
[[package]]
name = "endfield-cat"
-version = "0.2.0"
+version = "0.2.0-pre.6988755b.087703a"
dependencies = [
"futures-util",
"reqwest",
diff --git a/src-tauri/src/services/release.rs b/src-tauri/src/services/release.rs
index 71ded7a..16d0df4 100644
--- a/src-tauri/src/services/release.rs
+++ b/src-tauri/src/services/release.rs
@@ -16,63 +16,108 @@ struct FetchReleaseError {
status: Option,
}
+fn latest_release_from_json(json: &serde_json::Value) -> Result {
+ let tag_name = json
+ .get("tag_name")
+ .and_then(|v| v.as_str())
+ .filter(|s| !s.is_empty())
+ .or_else(|| {
+ json.get("name")
+ .and_then(|v| v.as_str())
+ .filter(|s| !s.is_empty())
+ })
+ .ok_or("Missing tag_name in GitHub response")?
+ .to_string();
+
+ let name = json
+ .get("name")
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string());
+ let html_url = json
+ .get("html_url")
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string());
+ let body = json
+ .get("body")
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string());
+
+ let download_url = if cfg!(target_os = "windows") {
+ json.get("assets")
+ .and_then(|v| v.as_array())
+ .and_then(|assets| {
+ assets.iter().find_map(|asset| {
+ let name = asset.get("name").and_then(|v| v.as_str())?;
+ if name.ends_with(".exe") {
+ asset
+ .get("browser_download_url")
+ .and_then(|v| v.as_str())
+ .map(|s| s.to_string())
+ } else {
+ None
+ }
+ })
+ })
+ } else {
+ None
+ };
+
+ Ok(LatestRelease {
+ tag_name,
+ name,
+ html_url,
+ download_url,
+ body,
+ })
+}
+
pub async fn fetch_latest_release(client: &reqwest::Client) -> Result {
- async fn fetch(client: &reqwest::Client, url: &str) -> Result {
+ async fn fetch(
+ client: &reqwest::Client,
+ url: &str,
+ ) -> Result {
let resp = client
.get(url)
.header("Accept", "application/vnd.github+json")
.header("User-Agent", "endfield-cat/tauri")
.send()
.await
- .map_err(|e| FetchReleaseError { message: e.to_string(), status: None })?;
+ .map_err(|e| FetchReleaseError {
+ message: e.to_string(),
+ status: None,
+ })?;
let status = resp.status();
if !status.is_success() {
- return Err(FetchReleaseError { message: format!("GitHub API status {}", status), status: Some(status) });
+ return Err(FetchReleaseError {
+ message: format!("GitHub API status {}", status),
+ status: Some(status),
+ });
}
- let json: serde_json::Value = resp.json().await.map_err(|e| FetchReleaseError { message: e.to_string(), status: None })?;
- let tag_name = json
- .get("tag_name")
- .or_else(|| json.get("name"))
- .and_then(|v| v.as_str())
- .unwrap_or("")
- .to_string();
+ let json: serde_json::Value = resp.json().await.map_err(|e| FetchReleaseError {
+ message: e.to_string(),
+ status: None,
+ })?;
- if tag_name.is_empty() {
- return Err(FetchReleaseError { message: "Missing tag_name in GitHub response".to_string(), status: None });
- }
-
- let name = json.get("name").and_then(|v| v.as_str()).map(|s| s.to_string());
- let html_url = json.get("html_url").and_then(|v| v.as_str()).map(|s| s.to_string());
- let body = json.get("body").and_then(|v| v.as_str()).map(|s| s.to_string());
-
- let download_url = if cfg!(target_os = "windows") {
- json.get("assets")
- .and_then(|v| v.as_array())
- .and_then(|assets| {
- assets.iter().find_map(|asset| {
- let name = asset.get("name").and_then(|v| v.as_str())?;
- if name.ends_with(".exe") {
- asset.get("browser_download_url").and_then(|v| v.as_str()).map(|s| s.to_string())
- } else {
- None
- }
- })
- })
- } else {
- None
- };
-
- Ok(LatestRelease { tag_name, name, html_url, download_url, body })
+ latest_release_from_json(&json).map_err(|message| FetchReleaseError {
+ message,
+ status: None,
+ })
}
let primary = "https://api.github.com/repos/BoxCatTeam/endfield-cat/releases/latest";
match fetch(client, primary).await {
Ok(res) => Ok(res),
- Err(err) if matches!(err.status, Some(StatusCode::FORBIDDEN) | Some(StatusCode::TOO_MANY_REQUESTS)) => {
+ Err(err)
+ if matches!(
+ err.status,
+ Some(StatusCode::FORBIDDEN) | Some(StatusCode::TOO_MANY_REQUESTS)
+ ) =>
+ {
// Fallback: use jsDelivr to read package.json for version to avoid GitHub API limits
- let fallback_url = "https://cdn.jsdelivr.net/gh/BoxCatTeam/endfield-cat@master/package.json";
+ let fallback_url =
+ "https://cdn.jsdelivr.net/gh/BoxCatTeam/endfield-cat@master/package.json";
let resp = client
.get(fallback_url)
.header("User-Agent", "endfield-cat/tauri")
@@ -90,7 +135,9 @@ pub async fn fetch_latest_release(client: &reqwest::Client) -> Result Result Result