Skip to content

Commit 3b9c723

Browse files
authored
Merge pull request #16 from 7a6163/fix/portable-windows-updater
Disable auto-updates and notify users on Windows portable builds
2 parents 58882e3 + b710089 commit 3b9c723

14 files changed

Lines changed: 143 additions & 47 deletions

File tree

.github/workflows/release.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ jobs:
9797
Write-Host "Packaging: $($exe.FullName)"
9898
$version = "${{ github.ref_name }}"
9999
$zipName = "Fanhuaji-${version}-windows-portable.zip"
100-
Compress-Archive -Path $exe.FullName -DestinationPath $zipName
100+
# Marker file so the app detects the portable build at runtime and
101+
# switches the updater to notify-only (no in-place self-update on Windows).
102+
$marker = Join-Path $relDir "portable"
103+
Set-Content -Path $marker -Value "This file marks the Fanhuaji portable build. Do not remove it." -NoNewline
104+
Compress-Archive -Path $exe.FullName, $marker -DestinationPath $zipName
101105
gh release upload ${{ github.ref_name }} $zipName --clobber
102106
Write-Host "Uploaded: $zipName"
103107
} else {

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
本檔案記錄繁化姬 Tauri 版的所有重要變更。格式基於 [Keep a Changelog](https://keepachangelog.com/)
44

5+
## [2.4.2] - 2026-07-07
6+
7+
### Fixed
8+
9+
- Windows 攜帶版(portable)建置不再嘗試自動更新(Tauri 更新器會下載 MSI 安裝程式而非就地更新),改為顯示通知訊息引導使用者手動下載
10+
- 檔案佇列計數文字改用 i18n 系統,切換語言時正確顯示對應語言
11+
- 工具列輸出命名 pill 標籤在切換語言後同步更新
12+
513
## [2.4.1] - 2026-07-06
614

715
### Fixed

package-lock.json

Lines changed: 40 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fanhuaji-tauri",
3-
"version": "2.4.1",
3+
"version": "2.4.2",
44
"private": true,
55
"scripts": {
66
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "fanhuaji-tauri"
3-
version = "2.4.1"
3+
version = "2.4.2"
44
edition = "2024"
55

66
[lib]

src-tauri/src/commands.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,18 @@ pub async fn convert_epub(
379379
})
380380
}
381381

382+
/// Returns `true` when this build is the Windows portable distribution, which
383+
/// is detected by a `portable` marker file shipped next to the executable.
384+
/// Portable builds cannot self-update in place (the Tauri Windows updater only
385+
/// runs an installer), so the frontend uses this to switch to a notify-only flow.
386+
#[tauri::command]
387+
pub fn is_portable() -> bool {
388+
std::env::current_exe()
389+
.ok()
390+
.and_then(|exe| exe.parent().map(|dir| dir.join("portable").exists()))
391+
.unwrap_or(false)
392+
}
393+
382394
pub fn run() {
383395
tauri::Builder::default()
384396
.manage(HttpClient(
@@ -403,6 +415,7 @@ pub fn run() {
403415
convert_file,
404416
convert_epub,
405417
preview_convert,
418+
is_portable,
406419
])
407420
.run(tauri::generate_context!())
408421
.expect("啟動應用程式時發生錯誤");

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Fanhuaji",
4-
"version": "2.4.1",
4+
"version": "2.4.2",
55
"identifier": "org.zhconvert.fanhuaji",
66
"build": {
77
"frontendDist": "../dist",

src/__tests__/updater.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,22 @@ vi.mock("@tauri-apps/plugin-process", () => ({
99
relaunch: vi.fn(),
1010
}));
1111

12+
vi.mock("@tauri-apps/api/core", () => ({
13+
invoke: vi.fn(),
14+
}));
15+
16+
import { invoke } from "@tauri-apps/api/core";
1217
import { check, type Update } from "@tauri-apps/plugin-updater";
1318
import { checkForUpdates, initUpdater } from "../updater";
1419

1520
const mockCheck = vi.mocked(check);
21+
const mockInvoke = vi.mocked(invoke);
1622

1723
describe("updater", () => {
1824
beforeEach(() => {
1925
vi.clearAllMocks();
26+
// Default: not portable (installed build)
27+
mockInvoke.mockResolvedValue(false);
2028
document.body.innerHTML = `
2129
<div id="update-status"></div>
2230
<button id="btn-check-update"></button>
@@ -128,6 +136,47 @@ describe("updater", () => {
128136
expect(status?.textContent).toContain("network error");
129137
consoleSpy.mockRestore();
130138
});
139+
140+
it("shows portableAvailable message when portable and update found", async () => {
141+
const mockUpdate = {
142+
version: "2.0.0",
143+
downloadAndInstall: vi.fn(),
144+
};
145+
mockCheck.mockResolvedValue(mockUpdate as unknown as Update);
146+
mockInvoke.mockResolvedValue(true); // is_portable = true
147+
const confirmSpy = vi.spyOn(window, "confirm");
148+
149+
await checkForUpdates(false);
150+
const status = document.getElementById("update-status");
151+
expect(status?.textContent).toContain("攜帶版無法自動更新");
152+
expect(mockUpdate.downloadAndInstall).not.toHaveBeenCalled();
153+
expect(confirmSpy).not.toHaveBeenCalled();
154+
});
155+
156+
it("does not download or install when portable", async () => {
157+
const mockUpdate = {
158+
version: "2.0.0",
159+
downloadAndInstall: vi.fn(),
160+
};
161+
mockCheck.mockResolvedValue(mockUpdate as unknown as Update);
162+
mockInvoke.mockResolvedValue(true);
163+
164+
await checkForUpdates(false);
165+
expect(mockUpdate.downloadAndInstall).not.toHaveBeenCalled();
166+
});
167+
168+
it("falls back to normal flow when invoke throws", async () => {
169+
const mockUpdate = {
170+
version: "2.0.0",
171+
downloadAndInstall: vi.fn().mockResolvedValue(undefined),
172+
};
173+
mockCheck.mockResolvedValue(mockUpdate as unknown as Update);
174+
mockInvoke.mockRejectedValue(new Error("command not found"));
175+
vi.spyOn(window, "confirm").mockReturnValue(true);
176+
177+
await checkForUpdates(false);
178+
expect(mockUpdate.downloadAndInstall).toHaveBeenCalled();
179+
});
131180
});
132181

133182
describe("initUpdater", () => {

src/i18n/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,14 @@
8181
"file.convertDoneWithWarnings": "Done ({warnings})",
8282
"file.converting": "Converting\u2026",
8383
"file.convertingChapter": "Converting\u2026 ({current}/{total} {name})",
84+
"queue.count": "\u00b7 {count} files",
8485

8586
"dom.missingElement": "Required element not found: {selector}",
8687

8788
"update.checking": "Checking\u2026",
8889
"update.upToDate": "You are up to date.",
8990
"update.found": "New version {version} available",
91+
"update.portableAvailable": "New version {version} available. Portable builds cannot auto-update; please download the latest portable ZIP from GitHub Releases.",
9092
"update.confirm": "New version {version} is available. Download and install?",
9193
"update.downloading": "Downloading update\u2026",
9294
"update.restarting": "Download complete, restarting\u2026",

0 commit comments

Comments
 (0)