From 33b78515df0590bceb6438609377708439cc79d3 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Sun, 1 Feb 2026 07:46:09 +0000 Subject: [PATCH] fix(cli): `bun update -i` select all ('A') now updates packages correctly When pressing 'A' to select all packages in interactive update mode, packages where current_version == update_version but current_version != latest_version were silently skipped during processing, leading to "Selected X packages to update" followed by "No packages selected for update". The fix applies the same logic as the spacebar handler: when selecting all packages, automatically set use_latest=true for packages that are at their constrained update version but have a newer latest version available. Fixes #26657 Co-Authored-By: Claude Opus 4.5 --- src/cli/update_interactive_command.zig | 7 + test/regression/issue/26657.test.ts | 194 +++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 test/regression/issue/26657.test.ts diff --git a/src/cli/update_interactive_command.zig b/src/cli/update_interactive_command.zig index 4890fc7648..092045b600 100644 --- a/src/cli/update_interactive_command.zig +++ b/src/cli/update_interactive_command.zig @@ -1662,6 +1662,13 @@ pub const UpdateInteractiveCommand = struct { }, 'a', 'A' => { @memset(state.selected, true); + // For packages where current == update version, auto-set use_latest + // so they get updated to the latest version (matching spacebar behavior) + for (state.packages) |*pkg| { + if (strings.eql(pkg.current_version, pkg.update_version)) { + pkg.use_latest = true; + } + } state.toggle_all = true; // Mark that 'a' was used }, 'n', 'N' => { diff --git a/test/regression/issue/26657.test.ts b/test/regression/issue/26657.test.ts new file mode 100644 index 0000000000..5bd66febb7 --- /dev/null +++ b/test/regression/issue/26657.test.ts @@ -0,0 +1,194 @@ +import { describe, expect, test } from "bun:test"; +import { readFileSync } from "fs"; +import { bunEnv, bunExe, tempDir } from "harness"; +import { join } from "path"; + +describe("bun update -i select all with 'A' key", () => { + // Issue #26657: When pressing 'A' to select all packages in interactive update, + // the UI shows "Selected X packages to update" but then shows "No packages selected for update" + // because packages where current_version == update_version were silently filtered out. + test("should update packages when 'A' is pressed to select all", async () => { + // Create a project with a package that has an old version + // The package constraint allows higher versions, and there's a newer latest version + using dir = tempDir("update-interactive-select-all", { + "package.json": JSON.stringify({ + name: "test-project", + version: "1.0.0", + dependencies: { + // Use a very old version that definitely has updates available + "is-even": "0.1.0", + }, + }), + }); + + // First, run bun install to create initial node_modules + await using installProc = Bun.spawn({ + cmd: [bunExe(), "install"], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [installStdout, installStderr, installExitCode] = await Promise.all([ + installProc.stdout.text(), + installProc.stderr.text(), + installProc.exited, + ]); + + // Check install succeeded before proceeding + if (installExitCode !== 0) { + console.log("Install STDOUT:", installStdout); + console.log("Install STDERR:", installStderr); + } + expect(installExitCode).toBe(0); + + // Verify initial installation + const initialPackageJson = JSON.parse(readFileSync(join(String(dir), "package.json"), "utf8")); + expect(initialPackageJson.dependencies["is-even"]).toBe("0.1.0"); + + // Now run update --interactive and press 'A' to select all, then Enter to confirm + await using updateProc = Bun.spawn({ + cmd: [bunExe(), "update", "--interactive"], + cwd: String(dir), + env: bunEnv, + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + }); + + try { + // Press 'A' to select all packages, then Enter to confirm + updateProc.stdin.write("A"); // select all + updateProc.stdin.write("\r"); // enter to confirm + updateProc.stdin.end(); + + const [stdout, stderr, exitCode] = await Promise.all([ + updateProc.stdout.text(), + updateProc.stderr.text(), + updateProc.exited, + ]); + + // Debug output if test fails + if (exitCode !== 0) { + console.log("STDOUT:", stdout); + console.log("STDERR:", stderr); + } + + // The bug was that it would say "No packages selected for update" + // Check that this error message does NOT appear (check before exitCode for better error messages) + expect(stdout).not.toContain("No packages selected for update"); + expect(stderr).not.toContain("No packages selected for update"); + + expect(exitCode).toBe(0); + + // Check that package.json was updated + const updatedPackageJson = JSON.parse(readFileSync(join(String(dir), "package.json"), "utf8")); + const updatedVersion = updatedPackageJson.dependencies["is-even"]; + + // The version should have changed from "0.1.0" + expect(updatedVersion).not.toBe("0.1.0"); + + // Verify node_modules was actually updated + const installedPkgJson = JSON.parse( + readFileSync(join(String(dir), "node_modules", "is-even", "package.json"), "utf8"), + ); + const installedVersion = installedPkgJson.version; + + // The installed version should NOT be the old version + expect(installedVersion).not.toBe("0.1.0"); + expect(Bun.semver.satisfies(installedVersion, ">0.1.0")).toBe(true); + } catch (err) { + // Ensure cleanup on failure + updateProc.stdin.end(); + updateProc.kill(); + throw err; + } + }); + + test("should handle packages where current equals update version but not latest", async () => { + // This is the core of issue #26657: packages that are at the highest version + // within their semver constraint but not at the latest version overall + using dir = tempDir("update-interactive-select-all-constrained", { + "package.json": JSON.stringify({ + name: "test-project", + version: "1.0.0", + dependencies: { + // Use a version constraint that limits updates + // The point is to have packages where current == update_version but current != latest + "is-even": "^1.0.0", + }, + }), + }); + + // First install + await using installProc = Bun.spawn({ + cmd: [bunExe(), "install"], + cwd: String(dir), + env: bunEnv, + stdout: "pipe", + stderr: "pipe", + }); + + const [installStdout, installStderr, installExitCode] = await Promise.all([ + installProc.stdout.text(), + installProc.stderr.text(), + installProc.exited, + ]); + + // Check install succeeded before proceeding + if (installExitCode !== 0) { + console.log("Install STDOUT:", installStdout); + console.log("Install STDERR:", installStderr); + } + expect(installExitCode).toBe(0); + + // Get the installed version + const installedPkgJson = JSON.parse( + readFileSync(join(String(dir), "node_modules", "is-even", "package.json"), "utf8"), + ); + const currentVersion = installedPkgJson.version; + + // Now run update --interactive with 'A' to select all + await using updateProc = Bun.spawn({ + cmd: [bunExe(), "update", "--interactive"], + cwd: String(dir), + env: bunEnv, + stdin: "pipe", + stdout: "pipe", + stderr: "pipe", + }); + + try { + updateProc.stdin.write("A"); // select all + updateProc.stdin.write("\r"); // confirm + updateProc.stdin.end(); + + const [stdout, stderr, exitCode] = await Promise.all([ + updateProc.stdout.text(), + updateProc.stderr.text(), + updateProc.exited, + ]); + + // If there were packages shown in the list, they should have been processed + // The key assertion: we should NOT see "Selected X packages" followed by "No packages selected" + const selectedMatch = stdout.match(/Selected (\d+) package/); + if (selectedMatch) { + const selectedCount = parseInt(selectedMatch[1], 10); + if (selectedCount > 0) { + // If packages were selected, they should have been processed (check before exitCode) + expect(stdout).not.toContain("No packages selected for update"); + expect(stderr).not.toContain("No packages selected for update"); + } + } + + // The command should succeed without "No packages selected for update" error + // (unless there are genuinely no outdated packages, which is a valid state) + expect(exitCode).toBe(0); + } catch (err) { + updateProc.stdin.end(); + updateProc.kill(); + throw err; + } + }); +});