chore: defer file closes (#1180)

Mohamed Mahmoud created

## What?

Replaced manual `Close()` calls on every error path in the binary copy
block of `runUpdateCLI` with `defer`, and report close errors on `out`
via the named return value.

## Why?

The previous manual `Close()` calls on every return path were fragile
and silently discarded flush errors on `out`.

Closes #1056

Change summary

main.go | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)

Detailed changes

main.go 🔗

@@ -3371,7 +3371,7 @@ func isFlagSet(fs *flag.FlagSet, name string) bool {
 	return found
 }
 
-func runUpdateCLI() error {
+func runUpdateCLI() (err error) {
 	const api = "https://api.github.com/repos/floatpane/matcha/releases/latest"
 	resp, err := httpClient.Get(api)
 	if err != nil {
@@ -3638,18 +3638,22 @@ func runUpdateCLI() error {
 	if err != nil {
 		return fmt.Errorf("could not open new binary: %w", err)
 	}
+	defer in.Close()
 	out, err := os.OpenFile(tmpNew, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
 	if err != nil {
-		in.Close()
 		return fmt.Errorf("could not create temp binary in target dir: %w", err)
 	}
-	if _, err := io.Copy(out, in); err != nil {
-		in.Close()
-		out.Close()
+
+	defer func() {
+		cerr := out.Close()
+		if err == nil && cerr != nil {
+			err = fmt.Errorf("could not flush new binary to disk: %w", cerr)
+		}
+	}()
+
+	if _, err = io.Copy(out, in); err != nil {
 		return fmt.Errorf("could not write new binary to disk: %w", err)
 	}
-	in.Close()
-	out.Close()
 
 	// On Windows, a running executable cannot be overwritten directly.
 	// Move the old binary out of the way first, then rename the new one in.
@@ -3661,7 +3665,7 @@ func runUpdateCLI() error {
 		}
 	}
 
-	if err := os.Rename(tmpNew, execPath); err != nil {
+	if err = os.Rename(tmpNew, execPath); err != nil {
 		return fmt.Errorf("could not replace executable: %w", err)
 	}