Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions internal/desktopapp/desktopapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -856,13 +856,13 @@ func TestVerifyWindowsInstallerPassesPathViaEnvironment(t *testing.T) {
}
}

func TestWorkBuddyWindowsVersionPassesPathViaEnvironment(t *testing.T) {
func TestWindowsProductVersionPassesPathViaEnvironment(t *testing.T) {
runner := &scriptedRunner{results: []process.Result{{ExitCode: 0, Stdout: "5.3.11\n"}}}
path := `C:\Users\test\AppData\Local\Programs\WorkBuddy\WorkBuddy.exe`

version := workBuddyWindowsVersion(context.Background(), Options{Runner: runner}, path)
version := windowsProductVersion(context.Background(), Options{Runner: runner}, path)
if version == nil || *version != "5.3.11" {
t.Fatalf("workBuddyWindowsVersion() = %v, want 5.3.11", version)
t.Fatalf("windowsProductVersion() = %v, want 5.3.11", version)
}
argv := runner.calls[0]
if len(argv) != 5 {
Expand Down
8 changes: 5 additions & 3 deletions internal/desktopapp/dsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,10 @@ func inspectDSH(ctx context.Context, options Options) Status {
case "windows":
for _, candidate := range dshWindowsCandidates(options) {
if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
// The on-disk ProductVersion is authoritative for the same reason
// as the macOS plist: electron-updater replaces the app in place.
status.Installed, status.Path = true, candidate
status.Version = windowsProductVersion(ctx, options, candidate)
return status
}
}
Expand Down Expand Up @@ -302,9 +305,8 @@ func inspectDSHMacOS(ctx context.Context, options Options) (Status, error) {

// dshWindowsCandidates lists where the vendor's NSIS installer places the app.
// It is configured perMachine: false with a fixed directory, which for
// electron-builder is %LOCALAPPDATA%\Programs\<productName>. Not verified on
// Windows: this machine is macOS, so the path follows electron-builder's
// documented default rather than an observed installation.
// electron-builder is %LOCALAPPDATA%\Programs\<productName>. Observed on a
// Windows 11 install of 0.1.7-rc.2.
func dshWindowsCandidates(options Options) []string {
if len(options.SearchRoots) > 0 {
candidates := make([]string, 0, len(options.SearchRoots))
Expand Down
49 changes: 48 additions & 1 deletion internal/desktopapp/dsh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,59 @@ func TestInspectDSHWindowsLooksInThePerUserProgramsDirectory(t *testing.T) {
if err := os.WriteFile(exe, []byte("MZ"), 0o600); err != nil {
t.Fatal(err)
}
status = Inspect(context.Background(), DSHDesktopID, Options{Home: home, Platform: platform.For("windows", "x64")})
status = Inspect(context.Background(), DSHDesktopID, Options{Home: home, Platform: platform.For("windows", "x64"), Runner: &scriptedRunner{}})
if !status.Installed || status.Path != exe {
t.Fatalf("Inspect() after install = %#v", status)
}
}

// The app replaces itself through electron-updater, so the version reported is
// the installed exe's ProductVersion, read the way a real 0.1.7-rc.2 install
// reports it: PowerShell's UTF-8 output with a BOM and a trailing newline.
func TestInspectDSHWindowsReportsTheInstalledProductVersion(t *testing.T) {
home := t.TempDir()
exe := filepath.Join(home, "AppData", "Local", "Programs", "DeepSeek Harness", dshDesktopExeName)
if err := os.MkdirAll(filepath.Dir(exe), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(exe, []byte("MZ"), 0o600); err != nil {
t.Fatal(err)
}
runner := &scriptedRunner{results: []process.Result{{ExitCode: 0, Stdout: "\ufeff0.1.7-rc.2\r\n"}}}
status := Inspect(context.Background(), DSHDesktopID, Options{Home: home, Platform: platform.For("windows", "x64"), Runner: runner})
if !status.Installed || status.Path != exe {
t.Fatalf("Inspect() = %#v", status)
}
if status.Version == nil || *status.Version != "0.1.7-rc.2" {
t.Fatalf("Inspect().Version = %v, want 0.1.7-rc.2", status.Version)
}
if len(runner.calls) != 1 {
t.Fatalf("runner calls = %#v, want one version query", runner.calls)
}
if argv := runner.calls[0]; len(argv) != 5 || argv[0] != "powershell.exe" || strings.Contains(argv[4], exe) {
t.Fatalf("version query argv = %#v; the path must not be interpolated into the script", argv)
}
if got := runner.environments[0]["BOOTAGENT_VERSION_PATH"]; got != exe {
t.Fatalf("BOOTAGENT_VERSION_PATH = %q, want %q", got, exe)
}
}

func TestInspectDSHWindowsLeavesVersionUnsetWhenTheQueryFails(t *testing.T) {
home := t.TempDir()
exe := filepath.Join(home, "AppData", "Local", "Programs", "DeepSeek Harness", dshDesktopExeName)
if err := os.MkdirAll(filepath.Dir(exe), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(exe, []byte("MZ"), 0o600); err != nil {
t.Fatal(err)
}
runner := &scriptedRunner{results: []process.Result{{ExitCode: 1, Stderr: "Get-Item : access denied"}}}
status := Inspect(context.Background(), DSHDesktopID, Options{Home: home, Platform: platform.For("windows", "x64"), Runner: runner})
if !status.Installed || status.Version != nil || status.InspectionUnavailable != nil {
t.Fatalf("Inspect() = %#v, want installed with no version", status)
}
}

// Not offered means both halves: Supported is false so the UI shows no install
// entry, and Install refuses if called anyway.
func TestDSHIsNotOfferedOnIntelMacOrLinux(t *testing.T) {
Expand Down
13 changes: 13 additions & 0 deletions internal/desktopapp/windows_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ func approvedWindowsSigner(value string, allowed []string) bool {
return false
}

// windowsProductVersion reads the executable's VersionInfo.ProductVersion. It
// returns nil when the query fails: an unknown version is not an inspection
// failure worth surfacing.
func windowsProductVersion(ctx context.Context, options Options, path string) *string {
const script = `[Console]::OutputEncoding = [Text.Encoding]::UTF8
(Get-Item -LiteralPath $env:BOOTAGENT_VERSION_PATH).VersionInfo.ProductVersion`
result, err := runWithEnvironment(options, ctx, []string{"powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script}, map[string]string{"BOOTAGENT_VERSION_PATH": path}, inspectTimeout)
if err != nil || result.ExitCode != 0 {
return nil
}
return nonEmptyPointer(strings.TrimPrefix(strings.TrimSpace(result.Stdout), "\ufeff"))
}

func windowsAuthenticodeQuery() []string {
const script = `[Console]::OutputEncoding = [Text.Encoding]::UTF8
$signature = Get-AuthenticodeSignature -LiteralPath $env:BOOTAGENT_VERIFY_PATH
Expand Down
12 changes: 1 addition & 11 deletions internal/desktopapp/workbuddy.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func inspectWorkBuddyWindows(ctx context.Context, edition workBuddyEdition, opti
}
if !info.IsDir() {
status.Installed, status.Path = true, candidate
status.Version = workBuddyWindowsVersion(ctx, options, candidate)
status.Version = windowsProductVersion(ctx, options, candidate)
return status, nil
}
}
Expand Down Expand Up @@ -276,16 +276,6 @@ func workBuddyWindowsCandidates(edition workBuddyEdition, options Options) []str
return result
}

func workBuddyWindowsVersion(ctx context.Context, options Options, path string) *string {
const script = `[Console]::OutputEncoding = [Text.Encoding]::UTF8
(Get-Item -LiteralPath $env:BOOTAGENT_VERSION_PATH).VersionInfo.ProductVersion`
result, err := runWithEnvironment(options, ctx, []string{"powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script}, map[string]string{"BOOTAGENT_VERSION_PATH": path}, inspectTimeout)
if err != nil || result.ExitCode != 0 {
return nil
}
return nonEmptyPointer(strings.TrimSpace(result.Stdout))
}

// workBuddyStartAppsQuery matches on the Start menu display name, which carries
// no ".app" suffix. The name reaches the script through an environment variable
// rather than string interpolation, so quoting in the display name cannot alter
Expand Down
Loading