From 64f67f34257a2b26f2c17e41547e965e86be49cd Mon Sep 17 00:00:00 2001 From: Harshit Singh Bhandari <24b4506@iitb.ac.in> Date: Fri, 1 May 2026 01:04:29 +0530 Subject: [PATCH] fix(cli): skip rebuild when git install is already on latest version (#1585) * fix(cli): skip rebuild when git install is already on latest version After `git fetch`, compare local HEAD to remote HEAD. If they match, print "Already on latest version." and exit without running pnpm install, clean, build, or npm link. Without this check, `ao update` re-ran the full rebuild on every invocation even when nothing had changed, because the git path in `handleGitUpdate` (unlike the npm path) never called `checkForUpdate()` to short-circuit before delegating to the shell script. Fixes #1584 Co-Authored-By: Claude Sonnet 4.6 * fix(cli): keep running smoke tests when already on latest version The previous fix exited 0 immediately on the "already on latest" path, which silently skipped smoke tests that would otherwise verify the install. Restructure with an else-branch so the rebuild block is skipped but execution continues to the smoke-test gate, preserving the prior smoke-test behavior for the no-update case. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- .../__tests__/scripts/update-script.test.ts | 75 +++++++++++++++++++ packages/cli/src/assets/scripts/ao-update.sh | 51 +++++++------ 2 files changed, 104 insertions(+), 22 deletions(-) diff --git a/packages/cli/__tests__/scripts/update-script.test.ts b/packages/cli/__tests__/scripts/update-script.test.ts index d00aaf641..4c1966cf8 100644 --- a/packages/cli/__tests__/scripts/update-script.test.ts +++ b/packages/cli/__tests__/scripts/update-script.test.ts @@ -36,6 +36,8 @@ describe("ao-update.sh", () => { "status --porcelain") ;; "branch --show-current") printf 'main\\n' ;; "fetch origin main") ;; + "rev-parse HEAD") printf 'oldsha000\\n' ;; + "rev-parse origin/main") printf 'newsha111\\n' ;; "pull --ff-only origin main") ;; *) ;; esac\nexit 0`, @@ -96,6 +98,8 @@ esac\nexit 0`, "status --porcelain") ;; "branch --show-current") printf 'main\\n' ;; "fetch upstream main") ;; + "rev-parse HEAD") printf 'oldsha000\\n' ;; + "rev-parse upstream/main") printf 'newsha111\\n' ;; "pull --ff-only upstream main") ;; *) ;; esac\nexit 0`, @@ -232,6 +236,75 @@ exit 0`, expect(result.stderr).toContain("commit or stash"); }); + it("skips rebuild but still runs smoke tests when local HEAD matches remote HEAD", () => { + const tempRoot = mkdtempSync(join(tmpdir(), "ao-update-already-latest-")); + const fakeRepo = join(tempRoot, "repo"); + mkdirSync(join(fakeRepo, "packages", "cli"), { recursive: true }); + mkdirSync(join(fakeRepo, "packages", "ao", "bin"), { recursive: true }); + writeFileSync(join(fakeRepo, "packages", "ao", "bin", "ao.js"), "#!/usr/bin/env node\n"); + + const binDir = join(tempRoot, "bin"); + mkdirSync(binDir, { recursive: true }); + const commandLog = join(tempRoot, "commands.log"); + + const sha = "abc123def456abc123def456abc123def456abc123"; + + createFakeBinary( + binDir, + "git", + `printf 'git %s\\n' "$*" >> ${JSON.stringify(commandLog)} +case "$*" in + "remote get-url upstream") exit 1 ;; + "rev-parse --is-inside-work-tree") printf 'true\\n' ;; + "status --porcelain") ;; + "branch --show-current") printf 'main\\n' ;; + "fetch origin main") ;; + "rev-parse HEAD") printf '${sha}\\n' ;; + "rev-parse origin/main") printf '${sha}\\n' ;; + *) ;; +esac +exit 0`, + ); + createFakeBinary( + binDir, + "pnpm", + `printf 'pnpm %s\\n' "$*" >> ${JSON.stringify(commandLog)}\nif [ "$1" = "--version" ]; then\n printf '9.15.4\\n'\nfi\nexit 0`, + ); + createFakeBinary(binDir, "npm", `printf 'npm %s\\n' "$*" >> ${JSON.stringify(commandLog)}\nexit 0`); + createFakeBinary( + binDir, + "node", + `printf 'node %s\\n' "$*" >> ${JSON.stringify(commandLog)}\nif [ "$1" = "--version" ]; then\n printf 'v20.11.1\\n'\nfi\nexit 0`, + ); + + const result = spawnSync("bash", [scriptPath], { + env: { + ...process.env, + PATH: `${binDir}:${process.env.PATH || ""}`, + AO_REPO_ROOT: fakeRepo, + }, + encoding: "utf8", + }); + + const commands = readFileSync(commandLog, "utf8"); + rmSync(tempRoot, { recursive: true, force: true }); + + expect(result.status).toBe(0); + expect(result.stdout).toContain("Already on latest version"); + // Rebuild commands should NOT have run + expect(commands).not.toContain("pnpm install"); + expect(commands).not.toContain("pnpm --filter @aoagents/ao-core build"); + expect(commands).not.toContain("npm link"); + expect(commands).not.toContain("git pull --ff-only origin main"); + // Smoke tests SHOULD still have run + expect(commands).toContain( + `node ${join(fakeRepo, "packages", "ao", "bin", "ao.js")} --version`, + ); + expect(commands).toContain( + `node ${join(fakeRepo, "packages", "ao", "bin", "ao.js")} doctor --help`, + ); + }); + it("rejects conflicting smoke flags in the script", () => { const result = spawnSync("bash", [scriptPath, "--skip-smoke", "--smoke-only"], { encoding: "utf8", @@ -262,6 +335,8 @@ exit 0`, fi ;; "branch --show-current") printf "main\\n" ;; + "rev-parse HEAD") printf "oldsha000\\n" ;; + "rev-parse origin/main") printf "newsha111\\n" ;; "pull --ff-only origin main") touch ${JSON.stringify(join(tempRoot, "post-dirty"))} ;; esac exit 0`, diff --git a/packages/cli/src/assets/scripts/ao-update.sh b/packages/cli/src/assets/scripts/ao-update.sh index a227358b3..dc9876baf 100755 --- a/packages/cli/src/assets/scripts/ao-update.sh +++ b/packages/cli/src/assets/scripts/ao-update.sh @@ -177,32 +177,39 @@ if [ "$SMOKE_ONLY" = false ]; then maybe_sync_origin_with_upstream run_cmd git fetch "$UPDATE_REMOTE" "$TARGET_BRANCH" - run_cmd git pull --ff-only "$UPDATE_REMOTE" "$TARGET_BRANCH" - run_cmd pnpm install - run_cmd pnpm --filter @aoagents/ao-core clean - run_cmd pnpm --filter @aoagents/ao-cli clean - run_cmd pnpm --filter @aoagents/ao-web clean + local_sha="$(git rev-parse HEAD)" + remote_sha="$(git rev-parse "$UPDATE_REMOTE/$TARGET_BRANCH")" + if [ "$local_sha" = "$remote_sha" ]; then + printf '\nAlready on latest version.\n' + else + run_cmd git pull --ff-only "$UPDATE_REMOTE" "$TARGET_BRANCH" + run_cmd pnpm install - run_cmd pnpm --filter @aoagents/ao-core build - run_cmd pnpm --filter @aoagents/ao-cli build - run_cmd pnpm --filter @aoagents/ao-web build + run_cmd pnpm --filter @aoagents/ao-core clean + run_cmd pnpm --filter @aoagents/ao-cli clean + run_cmd pnpm --filter @aoagents/ao-web clean - printf '\nRefreshing ao launcher...\n' - ( - cd "$REPO_ROOT/packages/ao" - if npm link 2>/dev/null; then - : - elif [ -t 0 ]; then - printf ' Permission denied. Retrying with sudo...\n' - sudo npm link - else - printf 'ERROR: Permission denied. Run manually: cd %s/packages/ao && sudo npm link\n' "$REPO_ROOT" - exit 1 - fi - ) + run_cmd pnpm --filter @aoagents/ao-core build + run_cmd pnpm --filter @aoagents/ao-cli build + run_cmd pnpm --filter @aoagents/ao-web build - ensure_repo_clean "Update modified tracked files. Inspect git status, review the changes, and rerun after restoring a clean checkout if needed." + printf '\nRefreshing ao launcher...\n' + ( + cd "$REPO_ROOT/packages/ao" + if npm link 2>/dev/null; then + : + elif [ -t 0 ]; then + printf ' Permission denied. Retrying with sudo...\n' + sudo npm link + else + printf 'ERROR: Permission denied. Run manually: cd %s/packages/ao && sudo npm link\n' "$REPO_ROOT" + exit 1 + fi + ) + + ensure_repo_clean "Update modified tracked files. Inspect git status, review the changes, and rerun after restoring a clean checkout if needed." + fi fi if [ "$SKIP_SMOKE" = false ]; then