diff --git a/.github/workflows/release-winget.yml b/.github/workflows/release-winget.yml
deleted file mode 100644
index bba6b4b..0000000
--- a/.github/workflows/release-winget.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Publish to WinGet
-on:
- push:
- tags:
- - 'v*'
- workflow_dispatch:
- inputs:
- release_tag:
- required: true
- description: 'Tag of release you want to publish'
- type: string
-
-jobs:
- publish:
- runs-on: windows-2025-vs2026
- steps:
- - uses: vedantmgoyal9/winget-releaser@v2
- with:
- identifier: Syngnat.GoNavi
- installers-regex: 'GoNavi-windows-(amd64|arm64)\.exe$'
- release-tag: ${{ inputs.release_tag || github.ref_name }}
- token: ${{ secrets.WINGET_TOKEN }}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 06dc72a..5aa0990 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -4114,7 +4114,7 @@ function App() {
) : null,
isLatestUpdateDownloaded ? (
} onClick={handleInstallFromProgress}>
- {isMacRuntime ? t('app.about.action.open_install_directory') : t('app.about.action.install_update')}
+ {t('app.about.action.install_update')}
) : null,
].filter(Boolean)}
@@ -5111,7 +5111,7 @@ function App() {
] : (updateDownloadProgress.status === 'done' ? [
,
] : (updateDownloadProgress.status === 'error' ? [
diff --git a/frontend/src/hooks/useAppUpdateManager.test.tsx b/frontend/src/hooks/useAppUpdateManager.test.tsx
new file mode 100644
index 0000000..6b1296f
--- /dev/null
+++ b/frontend/src/hooks/useAppUpdateManager.test.tsx
@@ -0,0 +1,159 @@
+import React from 'react';
+import { act, create, type ReactTestRenderer } from 'react-test-renderer';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { useAppUpdateManager } from './useAppUpdateManager';
+
+const runtimeApi = vi.hoisted(() => ({
+ EventsOn: vi.fn(() => vi.fn()),
+}));
+
+const messageApi = vi.hoisted(() => ({
+ info: vi.fn(),
+ success: vi.fn(),
+ error: vi.fn(),
+}));
+
+vi.mock('../../wailsjs/runtime', () => runtimeApi);
+
+vi.mock('antd', () => ({
+ message: messageApi,
+}));
+
+type BackendAppMock = {
+ CheckForUpdates: ReturnType;
+ CheckForUpdatesSilently: ReturnType;
+ DownloadUpdate: ReturnType;
+ InstallUpdateAndRestart: ReturnType;
+ OpenDownloadedUpdateDirectory: ReturnType;
+ GetAppInfo: ReturnType;
+};
+
+const createBackendAppMock = (): BackendAppMock => ({
+ CheckForUpdates: vi.fn(),
+ CheckForUpdatesSilently: vi.fn(),
+ DownloadUpdate: vi.fn(),
+ InstallUpdateAndRestart: vi.fn(),
+ OpenDownloadedUpdateDirectory: vi.fn(),
+ GetAppInfo: vi.fn(async () => ({ success: true, data: { version: '0.8.1', author: 'Syngnat' } })),
+});
+
+describe('useAppUpdateManager', () => {
+ let backendApp: BackendAppMock;
+ let hook: ReturnType | null = null;
+ let renderer: ReactTestRenderer | null = null;
+
+ const t = (key: string, params?: Record) => {
+ if (params?.version) return `${key}:${params.version}`;
+ if (params?.path) return `${key}:${params.path}`;
+ if (params?.error) return `${key}:${params.error}`;
+ return key;
+ };
+
+ const renderHook = (isMacRuntime: boolean) => {
+ const Harness = () => {
+ hook = useAppUpdateManager({
+ isMacRuntime,
+ runtimeBuildType: 'release',
+ t,
+ });
+ return null;
+ };
+
+ act(() => {
+ renderer = create();
+ });
+ };
+
+ beforeEach(() => {
+ backendApp = createBackendAppMock();
+ hook = null;
+ renderer = null;
+ runtimeApi.EventsOn.mockClear();
+ messageApi.info.mockReset();
+ messageApi.success.mockReset();
+ messageApi.error.mockReset();
+ vi.useFakeTimers();
+ vi.stubGlobal('window', {
+ setTimeout,
+ clearTimeout,
+ setInterval,
+ clearInterval,
+ go: {
+ app: {
+ App: backendApp,
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ act(() => {
+ renderer?.unmount();
+ });
+ vi.useRealTimers();
+ vi.unstubAllGlobals();
+ });
+
+ it('uses InstallUpdateAndRestart for downloaded macOS updates', async () => {
+ backendApp.CheckForUpdates.mockResolvedValue({
+ success: true,
+ data: {
+ hasUpdate: true,
+ currentVersion: '0.8.1',
+ latestVersion: '0.8.2',
+ downloaded: true,
+ assetSize: 1024,
+ },
+ });
+ backendApp.InstallUpdateAndRestart.mockResolvedValue({ success: true });
+ backendApp.OpenDownloadedUpdateDirectory.mockResolvedValue({ success: true });
+
+ renderHook(true);
+
+ await act(async () => {
+ await hook?.checkForUpdates(false);
+ });
+
+ await act(async () => {
+ await hook?.handleInstallFromProgress();
+ });
+
+ expect(backendApp.InstallUpdateAndRestart).toHaveBeenCalledTimes(1);
+ expect(backendApp.OpenDownloadedUpdateDirectory).not.toHaveBeenCalled();
+ });
+
+ it('does not auto-open the downloaded macOS package directory after download succeeds', async () => {
+ backendApp.CheckForUpdates.mockResolvedValue({
+ success: true,
+ data: {
+ hasUpdate: true,
+ currentVersion: '0.8.1',
+ latestVersion: '0.8.2',
+ downloaded: false,
+ assetSize: 2048,
+ },
+ });
+ backendApp.DownloadUpdate.mockResolvedValue({
+ success: true,
+ data: {
+ downloadPath: '/Users/test/Desktop/GoNavi-0.8.2-MacOS-Arm64.dmg',
+ },
+ });
+ backendApp.OpenDownloadedUpdateDirectory.mockResolvedValue({ success: true });
+
+ renderHook(true);
+
+ await act(async () => {
+ await hook?.checkForUpdates(false);
+ });
+
+ await act(async () => {
+ await hook?.downloadUpdate(hook?.lastUpdateInfo!, false);
+ });
+
+ expect(backendApp.DownloadUpdate).toHaveBeenCalledTimes(1);
+ expect(backendApp.OpenDownloadedUpdateDirectory).not.toHaveBeenCalled();
+ expect(hook?.lastUpdateInfo?.downloaded).toBe(true);
+ });
+});
diff --git a/frontend/src/hooks/useAppUpdateManager.ts b/frontend/src/hooks/useAppUpdateManager.ts
index d83e280..6f36d33 100644
--- a/frontend/src/hooks/useAppUpdateManager.ts
+++ b/frontend/src/hooks/useAppUpdateManager.ts
@@ -167,16 +167,6 @@ export const useAppUpdateManager = ({
void message.success({ content: t('app.about.message.download_completed'), duration: 2 });
}
setAboutUpdateStatus(formatAboutUpdateStatus({ ...info, downloaded: true }));
- if (isMacRuntime && !updateUserDismissedRef.current) {
- try {
- const openRes = await (window as any).go.app.App.OpenDownloadedUpdateDirectory();
- if (openRes?.success) {
- void message.success(openRes?.message || t('app.about.message.install_directory_opened_manual_replace'));
- }
- } catch (e) {
- console.warn('自动打开下载目录失败', e);
- }
- }
} else {
setUpdateDownloadProgress((prev) => ({
...prev,
@@ -218,28 +208,6 @@ export const useAppUpdateManager = ({
if (!canInstall) {
return;
}
- if (isMacRuntime) {
- const res = await (window as any).go.app.App.OpenDownloadedUpdateDirectory();
- if (!res?.success) {
- void message.error(t('app.about.message.open_install_directory_failed_with_error', { error: res?.message || t('common.unknown') }));
- updateDownloadedVersionRef.current = null;
- updateDownloadMetaRef.current = null;
- setUpdateDownloadProgress((prev) => ({
- ...prev,
- status: 'idle',
- percent: 0,
- downloaded: 0,
- open: false,
- }));
- setLastUpdateInfo((prev) => prev ? { ...prev, downloaded: false, downloadPath: undefined } : prev);
- setAboutUpdateStatus((prev) => lastUpdateInfo ? formatAboutUpdateStatus({ ...lastUpdateInfo, downloaded: false, downloadPath: undefined }) : prev);
- return;
- }
- updateInstallTriggeredVersionRef.current = updateDownloadProgress.version || lastUpdateInfo?.latestVersion || null;
- hideUpdateDownloadProgress();
- void message.success(res?.message || t('app.about.message.install_directory_opened_manual_replace'));
- return;
- }
const res = await (window as any).go.app.App.InstallUpdateAndRestart();
if (!res?.success) {
void message.error(t('app.about.message.install_failed_with_error', { error: res?.message || t('common.unknown') }));
@@ -247,7 +215,7 @@ export const useAppUpdateManager = ({
}
updateInstallTriggeredVersionRef.current = updateDownloadProgress.version || lastUpdateInfo?.latestVersion || null;
hideUpdateDownloadProgress();
- }, [formatAboutUpdateStatus, hideUpdateDownloadProgress, isMacRuntime, lastUpdateInfo, updateDownloadProgress.status, updateDownloadProgress.version, t]);
+ }, [hideUpdateDownloadProgress, lastUpdateInfo, updateDownloadProgress.status, updateDownloadProgress.version, t]);
const checkForUpdates = useCallback(async (silent: boolean) => {
if (updateCheckInFlightRef.current) return;
diff --git a/internal/app/methods_update.go b/internal/app/methods_update.go
index 7b6e909..73eb22b 100644
--- a/internal/app/methods_update.go
+++ b/internal/app/methods_update.go
@@ -1076,6 +1076,7 @@ if not exist "%SOURCE%" (
)
for %%I in ("%TARGET%") do set "TARGET_NAME=%%~nxI"
+for %%I in ("%TARGET%") do set "TARGET_DIR=%%~dpI"
for %%I in ("%SOURCE%") do set "SOURCE_EXT=%%~xI"
set "SOURCE_EXE="
@@ -1161,10 +1162,10 @@ exit /b 1
:move_done
del /F /Q "%TARGET_OLD%" >> "%LOG_FILE%" 2>&1
-start "" "%TARGET%" >> "%LOG_FILE%" 2>&1
+start "" /D "%TARGET_DIR%" "%TARGET%" >> "%LOG_FILE%" 2>&1
if %ERRORLEVEL% NEQ 0 (
call :log cmd start failed, trying powershell Start-Process
- powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath '%TARGET%'" >> "%LOG_FILE%" 2>&1
+ powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath '%TARGET%' -WorkingDirectory '%TARGET_DIR%'" >> "%LOG_FILE%" 2>&1
if !ERRORLEVEL! NEQ 0 (
call :log relaunch failed
exit /b 1
diff --git a/internal/app/methods_update_windows_script_test.go b/internal/app/methods_update_windows_script_test.go
index a8926b0..d3f58c5 100644
--- a/internal/app/methods_update_windows_script_test.go
+++ b/internal/app/methods_update_windows_script_test.go
@@ -102,7 +102,7 @@ func TestBuildWindowsScriptUsesDelayedErrorlevelInsideBlocks(t *testing.T) {
for _, token := range []string{
`if !ERRORLEVEL! NEQ 0 (`,
- `powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath '%TARGET%'" >> "%LOG_FILE%" 2>&1`,
+ `powershell -NoProfile -ExecutionPolicy Bypass -Command "Start-Process -FilePath '%TARGET%' -WorkingDirectory '%TARGET_DIR%'" >> "%LOG_FILE%" 2>&1`,
`set "TARGET_OLD=%TARGET%.old"`,
} {
if !strings.Contains(script, token) {
@@ -111,6 +111,26 @@ func TestBuildWindowsScriptUsesDelayedErrorlevelInsideBlocks(t *testing.T) {
}
}
+func TestBuildWindowsScriptRelaunchUsesTargetDirectory(t *testing.T) {
+ script := buildWindowsScript(
+ `C:\tmp\GoNavi-v0.5.0-windows-amd64.exe`,
+ `C:\Program Files\GoNavi\GoNavi.exe`,
+ `C:\Program Files\GoNavi\.gonavi-update-windows-v0.5.0`,
+ `C:\Program Files\GoNavi\logs\update-install.log`,
+ 99999,
+ )
+
+ for _, token := range []string{
+ `for %%I in ("%TARGET%") do set "TARGET_DIR=%%~dpI"`,
+ `start "" /D "%TARGET_DIR%" "%TARGET%" >> "%LOG_FILE%" 2>&1`,
+ `Start-Process -FilePath '%TARGET%' -WorkingDirectory '%TARGET_DIR%'`,
+ } {
+ if !strings.Contains(script, token) {
+ t.Fatalf("windows update relaunch missing token: %s\nscript:\n%s", token, script)
+ }
+ }
+}
+
func TestBuildWindowsLaunchCommandUsesDirectHiddenCall(t *testing.T) {
cmd := buildWindowsLaunchCommand(`C:\tmp\gonavi-update\update.cmd`)