diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 239de008..a13bd13e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -354,7 +354,7 @@ jobs: - name: Generate checksums run: | shopt -s nullglob - files=(release/*.AppImage release/*.dmg release/*.zip release/*.tar.gz dist/launcher/subminer) + files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz dist/launcher/subminer) if [ "${#files[@]}" -eq 0 ]; then echo "No release artifacts found for checksum generation." exit 1 @@ -392,6 +392,7 @@ jobs: artifacts=( release/*.AppImage release/*.dmg + release/*.exe release/*.zip release/*.tar.gz release/SHA256SUMS.txt diff --git a/Makefile b/Makefile index ed9e17e6..8655e651 100644 --- a/Makefile +++ b/Makefile @@ -117,6 +117,7 @@ build: @case "$(PLATFORM)" in \ linux) $(MAKE) --no-print-directory build-linux ;; \ macos) $(MAKE) --no-print-directory build-macos ;; \ + windows) printf '%s\n' "[INFO] Windows builds run via: bun run build:win" ;; \ *) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \ esac @@ -236,7 +237,7 @@ install-plugin: @cp -R ./plugin/subminer/. "$(MPV_SCRIPTS_DIR)/subminer/" @install -m 0644 "./$(PLUGIN_CONF)" "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf" @if [ "$(PLATFORM)" = "windows" ]; then \ - node ./scripts/configure-plugin-binary-path.mjs "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf" "$(CURDIR)" win32; \ + bun ./scripts/configure-plugin-binary-path.mjs "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf" "$(CURDIR)" win32; \ fi @printf '%s\n' "Installed to:" " $(MPV_SCRIPTS_DIR)/subminer/main.lua" " $(MPV_SCRIPTS_DIR)/subminer/" " $(MPV_SCRIPT_OPTS_DIR)/subminer.conf" diff --git a/plugin/subminer/binary.lua b/plugin/subminer/binary.lua index b62b886b..49bfba3c 100644 --- a/plugin/subminer/binary.lua +++ b/plugin/subminer/binary.lua @@ -249,17 +249,20 @@ try { local program_files_x86 = os.getenv("ProgramFiles(x86)") or "C:\\Program Files (x86)" local search_paths = {} - add_search_path(search_paths, "/Applications/SubMiner.app/Contents/MacOS/SubMiner") - add_search_path(search_paths, utils.join_path(home, "Applications", "SubMiner.app", "Contents", "MacOS", "SubMiner")) - add_search_path(search_paths, utils.join_path(app_data_local, "Programs", "SubMiner", "SubMiner.exe")) - add_search_path(search_paths, utils.join_path(local_app_data, "Programs", "SubMiner", "SubMiner.exe")) - add_search_path(search_paths, utils.join_path(program_files, "SubMiner", "SubMiner.exe")) - add_search_path(search_paths, utils.join_path(program_files_x86, "SubMiner", "SubMiner.exe")) - add_search_path(search_paths, "C:\\SubMiner\\SubMiner.exe") - add_search_path(search_paths, utils.join_path(home, ".local", "bin", "SubMiner.AppImage")) - add_search_path(search_paths, "/opt/SubMiner/SubMiner.AppImage") - add_search_path(search_paths, "/usr/local/bin/SubMiner") - add_search_path(search_paths, "/usr/bin/SubMiner") + if environment.is_windows() then + add_search_path(search_paths, utils.join_path(app_data_local, "Programs", "SubMiner", "SubMiner.exe")) + add_search_path(search_paths, utils.join_path(local_app_data, "Programs", "SubMiner", "SubMiner.exe")) + add_search_path(search_paths, utils.join_path(program_files, "SubMiner", "SubMiner.exe")) + add_search_path(search_paths, utils.join_path(program_files_x86, "SubMiner", "SubMiner.exe")) + add_search_path(search_paths, "C:\\SubMiner\\SubMiner.exe") + else + add_search_path(search_paths, "/Applications/SubMiner.app/Contents/MacOS/SubMiner") + add_search_path(search_paths, utils.join_path(home, "Applications", "SubMiner.app", "Contents", "MacOS", "SubMiner")) + add_search_path(search_paths, utils.join_path(home, ".local", "bin", "SubMiner.AppImage")) + add_search_path(search_paths, "/opt/SubMiner/SubMiner.AppImage") + add_search_path(search_paths, "/usr/local/bin/SubMiner") + add_search_path(search_paths, "/usr/bin/SubMiner") + end for _, path in ipairs(search_paths) do if file_exists(path) then diff --git a/scripts/get-mpv-window-windows.ps1 b/scripts/get-mpv-window-windows.ps1 index e941ad9c..2c5ef794 100644 --- a/scripts/get-mpv-window-windows.ps1 +++ b/scripts/get-mpv-window-windows.ps1 @@ -89,7 +89,7 @@ public static class SubMinerWindowsHelper { } } - $matches = New-Object System.Collections.Generic.List[object] + $mpvMatches = New-Object System.Collections.Generic.List[object] $foregroundWindow = [SubMinerWindowsHelper]::GetForegroundWindow() $callback = [SubMinerWindowsHelper+EnumWindowsProc]{ param([IntPtr]$hWnd, [IntPtr]$lParam) @@ -136,7 +136,7 @@ public static class SubMinerWindowsHelper { return $true } - $matches.Add([PSCustomObject]@{ + $mpvMatches.Add([PSCustomObject]@{ HWnd = $hWnd X = $bounds.X Y = $bounds.Y @@ -151,14 +151,14 @@ public static class SubMinerWindowsHelper { [void][SubMinerWindowsHelper]::EnumWindows($callback, [IntPtr]::Zero) - $focusedMatch = $matches | Where-Object { $_.IsForeground } | Select-Object -First 1 + $focusedMatch = $mpvMatches | Where-Object { $_.IsForeground } | Select-Object -First 1 if ($null -ne $focusedMatch) { [Console]::Error.WriteLine('focus=focused') } else { [Console]::Error.WriteLine('focus=not-focused') } - if ($matches.Count -eq 0) { + if ($mpvMatches.Count -eq 0) { Write-Output 'not-found' exit 0 } @@ -166,7 +166,7 @@ public static class SubMinerWindowsHelper { $bestMatch = if ($null -ne $focusedMatch) { $focusedMatch } else { - $matches | Sort-Object -Property Area, Width, Height -Descending | Select-Object -First 1 + $mpvMatches | Sort-Object -Property Area, Width, Height -Descending | Select-Object -First 1 } Write-Output "$($bestMatch.X),$($bestMatch.Y),$($bestMatch.Width),$($bestMatch.Height)" } catch { diff --git a/src/main/runtime/first-run-setup-service.test.ts b/src/main/runtime/first-run-setup-service.test.ts index bde48f71..ef7d0cec 100644 --- a/src/main/runtime/first-run-setup-service.test.ts +++ b/src/main/runtime/first-run-setup-service.test.ts @@ -203,3 +203,47 @@ test('setup service reflects detected Windows mpv shortcuts before preferences a assert.equal(snapshot.windowsMpvShortcuts.desktopInstalled, true); }); }); + +test('setup service persists Windows mpv shortcut preferences and status with one state write', async () => { + await withTempDir(async (root) => { + const configDir = path.join(root, 'SubMiner'); + fs.mkdirSync(configDir, { recursive: true }); + fs.writeFileSync(path.join(configDir, 'config.jsonc'), '{}'); + const stateChanges: string[] = []; + + const service = createFirstRunSetupService({ + platform: 'win32', + configDir, + getYomitanDictionaryCount: async () => 0, + detectPluginInstalled: () => false, + installPlugin: async () => ({ + ok: true, + pluginInstallStatus: 'installed', + pluginInstallPathSummary: null, + message: 'ok', + }), + applyWindowsMpvShortcuts: async () => ({ + ok: true, + status: 'installed', + message: 'shortcuts updated', + }), + onStateChanged: (state) => { + stateChanges.push(state.windowsMpvShortcutLastStatus); + }, + }); + + await service.ensureSetupStateInitialized(); + stateChanges.length = 0; + + const snapshot = await service.configureWindowsMpvShortcuts({ + startMenuEnabled: false, + desktopEnabled: true, + }); + + assert.equal(snapshot.windowsMpvShortcuts.startMenuEnabled, false); + assert.equal(snapshot.windowsMpvShortcuts.desktopEnabled, true); + assert.equal(snapshot.state.windowsMpvShortcutLastStatus, 'installed'); + assert.equal(snapshot.message, 'shortcuts updated'); + assert.deepEqual(stateChanges, ['installed']); + }); +}); diff --git a/src/main/runtime/first-run-setup-service.ts b/src/main/runtime/first-run-setup-service.ts index 29184dda..5541d7fc 100644 --- a/src/main/runtime/first-run-setup-service.ts +++ b/src/main/runtime/first-run-setup-service.ts @@ -294,20 +294,23 @@ export function createFirstRunSetupService(deps: { ); }, configureWindowsMpvShortcuts: async (preferences) => { - const nextState = writeState({ - ...readState(), - windowsMpvShortcutPreferences: { - startMenuEnabled: preferences.startMenuEnabled, - desktopEnabled: preferences.desktopEnabled, - }, - }); if (!isWindows || !deps.applyWindowsMpvShortcuts) { - return refreshWithState(nextState, null); + return refreshWithState( + writeState({ + ...readState(), + windowsMpvShortcutPreferences: { + startMenuEnabled: preferences.startMenuEnabled, + desktopEnabled: preferences.desktopEnabled, + }, + }), + null, + ); } const result = await deps.applyWindowsMpvShortcuts(preferences); + const latestState = readState(); return refreshWithState( writeState({ - ...readState(), + ...latestState, windowsMpvShortcutPreferences: { startMenuEnabled: preferences.startMenuEnabled, desktopEnabled: preferences.desktopEnabled, diff --git a/src/release-workflow.test.ts b/src/release-workflow.test.ts index 40739745..9916c5ad 100644 --- a/src/release-workflow.test.ts +++ b/src/release-workflow.test.ts @@ -5,6 +5,8 @@ import { resolve } from 'node:path'; const releaseWorkflowPath = resolve(__dirname, '../.github/workflows/release.yml'); const releaseWorkflow = readFileSync(releaseWorkflowPath, 'utf8'); +const makefilePath = resolve(__dirname, '../Makefile'); +const makefile = readFileSync(makefilePath, 'utf8'); test('publish release leaves prerelease unset so gh creates a normal release', () => { assert.ok(!releaseWorkflow.includes('--prerelease')); @@ -18,3 +20,13 @@ test('release workflow generates release notes from committed changelog output', assert.match(releaseWorkflow, /bun run changelog:release-notes/); assert.ok(!releaseWorkflow.includes('git log --pretty=format:"- %s"')); }); + +test('release workflow includes the Windows installer in checksums and uploaded assets', () => { + assert.match(releaseWorkflow, /files=\(release\/\*\.AppImage release\/\*\.dmg release\/\*\.exe release\/\*\.zip release\/\*\.tar\.gz dist\/launcher\/subminer\)/); + assert.match(releaseWorkflow, /artifacts=\([\s\S]*release\/\*\.exe[\s\S]*release\/SHA256SUMS\.txt[\s\S]*\)/); +}); + +test('Makefile routes Windows install-plugin setup through bun and documents Windows builds', () => { + assert.match(makefile, /windows\) printf '%s\\n' "\[INFO\] Windows builds run via: bun run build:win" ;;/); + assert.match(makefile, /bun \.\/scripts\/configure-plugin-binary-path\.mjs/); +});