fix: force launcher relink during update (#1594)
* fix: force launcher relink during update (#1591) * fix: address launcher refresh review feedback (#1591) * fix: improve launcher refresh diagnostics (#1591)
This commit is contained in:
parent
b2cdf7adab
commit
9ca3c1fcd7
|
|
@ -6,6 +6,7 @@ import {
|
|||
mkdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
symlinkSync,
|
||||
utimesSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
|
|
@ -181,7 +182,7 @@ describe("ao-doctor.sh", () => {
|
|||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("FIXED");
|
||||
expect(npmCommands).toContain("link");
|
||||
expect(npmCommands).toContain("link --force");
|
||||
expect(result.stdout).toContain("launcher");
|
||||
expect(result.stdout).toContain("stale temp files");
|
||||
expect(staleStillExists).toBe(false);
|
||||
|
|
@ -191,6 +192,63 @@ describe("ao-doctor.sh", () => {
|
|||
expect(commentedWorktreeDirExists).toBe(false);
|
||||
});
|
||||
|
||||
it("repairs a dangling ao launcher shim in fix mode", () => {
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "ao-doctor-dangling-launcher-"));
|
||||
const fakeRepo = createHealthyRepo(tempRoot);
|
||||
const binDir = join(tempRoot, "bin");
|
||||
mkdirSync(binDir, { recursive: true });
|
||||
createHealthyPath(binDir);
|
||||
|
||||
const aoPath = join(binDir, "ao");
|
||||
rmSync(aoPath, { force: true });
|
||||
symlinkSync(join(tempRoot, "deleted-checkout", "dist", "index.js"), aoPath);
|
||||
|
||||
const npmLog = join(tempRoot, "npm.log");
|
||||
createFakeBinary(
|
||||
binDir,
|
||||
"npm",
|
||||
`printf '%s\n' "$*" >> ${JSON.stringify(npmLog)}
|
||||
if [ "$1" = "link" ]; then
|
||||
rm -f ${JSON.stringify(aoPath)}
|
||||
printf '#!/bin/bash\nexit 0\n' > ${JSON.stringify(aoPath)}
|
||||
chmod +x ${JSON.stringify(aoPath)}
|
||||
fi
|
||||
if [ "$1" = "bin" ]; then
|
||||
printf "/tmp/npm-bin\n"
|
||||
fi
|
||||
exit 0`,
|
||||
);
|
||||
|
||||
const configPath = join(tempRoot, "agent-orchestrator.yaml");
|
||||
const dataDir = join(tempRoot, "data");
|
||||
const worktreeDir = join(tempRoot, "worktrees");
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
mkdirSync(worktreeDir, { recursive: true });
|
||||
writeFileSync(
|
||||
configPath,
|
||||
[`dataDir: ${dataDir}`, `worktreeDir: ${worktreeDir}`, "projects: {}"].join("\n"),
|
||||
);
|
||||
|
||||
const result = spawnSync("bash", [scriptPath, "--fix"], {
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${binDir}:/bin:/usr/bin`,
|
||||
AO_REPO_ROOT: fakeRepo,
|
||||
AO_CONFIG_PATH: configPath,
|
||||
},
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
const npmCommands = existsSync(npmLog) ? readFileSync(npmLog, "utf8") : "";
|
||||
const repairedLauncherIsExecutable = existsSync(aoPath);
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(result.stdout).toContain("FIXED");
|
||||
expect(repairedLauncherIsExecutable).toBe(true);
|
||||
expect(npmCommands).toContain("link --force");
|
||||
});
|
||||
|
||||
it("reports a healthy packaged install without source-checkout failures", () => {
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "ao-doctor-package-"));
|
||||
const fakeInstall = createHealthyPackageInstall(tempRoot);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { chmodSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
chmodSync,
|
||||
existsSync,
|
||||
mkdtempSync,
|
||||
mkdirSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
|
@ -76,7 +84,7 @@ esac\nexit 0`,
|
|||
expect(commands).toContain("pnpm install");
|
||||
expect(commands).toContain("pnpm --filter @aoagents/ao-core clean");
|
||||
expect(commands).toContain("pnpm --filter @aoagents/ao-cli build");
|
||||
expect(commands).toContain("npm link");
|
||||
expect(commands).toContain("npm link --force");
|
||||
});
|
||||
|
||||
it("syncs the fork with upstream via gh and fast-forwards the local checkout from upstream", () => {
|
||||
|
|
@ -146,14 +154,77 @@ esac\nexit 0`,
|
|||
expect(commands).not.toContain("git fetch origin main");
|
||||
});
|
||||
|
||||
it("uses forced npm link so stale global ao shims are overwritten", () => {
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "ao-update-stale-shim-"));
|
||||
const fakeRepo = join(tempRoot, "repo");
|
||||
mkdirSync(join(fakeRepo, "packages", "cli"), { recursive: true });
|
||||
mkdirSync(join(fakeRepo, "packages", "ao"), { recursive: true });
|
||||
|
||||
const binDir = join(tempRoot, "bin");
|
||||
mkdirSync(binDir, { recursive: true });
|
||||
const commandLog = join(tempRoot, "commands.log");
|
||||
|
||||
createFakeBinary(
|
||||
binDir,
|
||||
"git",
|
||||
`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 'oldsha000\n' ;;
|
||||
"rev-parse origin/main") printf 'newsha111\n' ;;
|
||||
"pull --ff-only origin main") ;;
|
||||
esac
|
||||
exit 0`,
|
||||
);
|
||||
createFakeBinary(
|
||||
binDir,
|
||||
"pnpm",
|
||||
`if [ "$1" = "--version" ]; then printf '9.15.4\n'; fi
|
||||
exit 0`,
|
||||
);
|
||||
createFakeBinary(
|
||||
binDir,
|
||||
"npm",
|
||||
`printf 'npm %s\n' "$*" >> ${JSON.stringify(commandLog)}
|
||||
if [ "$*" = "link" ]; then
|
||||
printf 'npm error code EEXIST\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
exit 0`,
|
||||
);
|
||||
createFakeBinary(
|
||||
binDir,
|
||||
"node",
|
||||
`if [ "$1" = "--version" ]; then printf 'v20.11.1\n'; fi
|
||||
exit 0`,
|
||||
);
|
||||
|
||||
const result = spawnSync("bash", [scriptPath, "--skip-smoke"], {
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `${binDir}:${process.env.PATH || ""}`,
|
||||
AO_REPO_ROOT: fakeRepo,
|
||||
},
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
const commands = existsSync(commandLog) ? readFileSync(commandLog, "utf8") : "";
|
||||
rmSync(tempRoot, { recursive: true, force: true });
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
expect(commands).toContain("npm link --force");
|
||||
expect(commands).not.toContain("npm link\n");
|
||||
expect(result.stdout).not.toContain("Permission denied");
|
||||
});
|
||||
|
||||
it("runs the built-in smoke commands in smoke-only mode", () => {
|
||||
const tempRoot = mkdtempSync(join(tmpdir(), "ao-update-smoke-"));
|
||||
const fakeRepo = join(tempRoot, "repo");
|
||||
mkdirSync(join(fakeRepo, "packages", "ao", "bin"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(fakeRepo, "packages", "ao", "bin", "ao.js"),
|
||||
"#!/usr/bin/env node\n",
|
||||
);
|
||||
writeFileSync(join(fakeRepo, "packages", "ao", "bin", "ao.js"), "#!/usr/bin/env node\n");
|
||||
|
||||
const binDir = join(tempRoot, "bin");
|
||||
mkdirSync(binDir, { recursive: true });
|
||||
|
|
@ -270,7 +341,11 @@ exit 0`,
|
|||
"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,
|
||||
"npm",
|
||||
`printf 'npm %s\\n' "$*" >> ${JSON.stringify(commandLog)}\nexit 0`,
|
||||
);
|
||||
createFakeBinary(
|
||||
binDir,
|
||||
"node",
|
||||
|
|
|
|||
|
|
@ -215,23 +215,27 @@ check_launcher() {
|
|||
local ao_path
|
||||
ao_path="$(command -v ao 2>/dev/null || true)"
|
||||
if [ -n "$ao_path" ]; then
|
||||
pass "ao launcher resolves to $ao_path"
|
||||
return
|
||||
if [ -x "$ao_path" ]; then
|
||||
pass "ao launcher resolves to $ao_path"
|
||||
return
|
||||
fi
|
||||
warn "ao launcher resolves to $ao_path, but its target is missing or not executable"
|
||||
fi
|
||||
|
||||
if [ "$SCRIPT_LAYOUT" = "source-checkout" ] && [ "$FIX_MODE" = true ] && command -v npm >/dev/null 2>&1 && [ -d "$REPO_ROOT/packages/ao" ]; then
|
||||
if (cd "$REPO_ROOT/packages/ao" && npm link >/dev/null 2>&1) && command -v ao >/dev/null 2>&1; then
|
||||
fixed "ao launcher refreshed with npm link"
|
||||
if (cd "$REPO_ROOT/packages/ao" && npm link --force >/dev/null 2>&1) && command -v ao >/dev/null 2>&1; then
|
||||
fixed "ao launcher refreshed with npm link --force"
|
||||
return
|
||||
fi
|
||||
if [ -t 0 ]; then
|
||||
printf ' Permission denied. Retrying with sudo...\n'
|
||||
if (cd "$REPO_ROOT/packages/ao" && sudo npm link >/dev/null 2>&1) && command -v ao >/dev/null 2>&1; then
|
||||
fixed "ao launcher refreshed with sudo npm link"
|
||||
printf ' Launcher refresh failed. Retrying with sudo...\n'
|
||||
if (cd "$REPO_ROOT/packages/ao" && sudo npm link --force >/dev/null 2>&1) && command -v ao >/dev/null 2>&1; then
|
||||
fixed "ao launcher refreshed with sudo npm link --force"
|
||||
return
|
||||
fi
|
||||
printf 'ERROR: sudo npm link --force failed. Inspect npm output above.\n' >&2
|
||||
fi
|
||||
warn "ao launcher refresh failed. Fix: cd $REPO_ROOT/packages/ao && sudo npm link"
|
||||
warn "ao launcher refresh failed. Fix: cd $REPO_ROOT/packages/ao && sudo npm link --force"
|
||||
return
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -197,13 +197,20 @@ if [ "$SMOKE_ONLY" = false ]; then
|
|||
printf '\nRefreshing ao launcher...\n'
|
||||
(
|
||||
cd "$REPO_ROOT/packages/ao"
|
||||
if npm link 2>/dev/null; then
|
||||
:
|
||||
npm_link_error="$(mktemp)"
|
||||
if npm link --force 2>"$npm_link_error"; then
|
||||
rm -f "$npm_link_error"
|
||||
elif [ -t 0 ]; then
|
||||
printf ' Permission denied. Retrying with sudo...\n'
|
||||
sudo npm link
|
||||
rm -f "$npm_link_error"
|
||||
printf ' Launcher refresh failed. Retrying with sudo...\n'
|
||||
if ! sudo npm link --force; then
|
||||
printf 'ERROR: sudo npm link --force failed. Inspect npm output above.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
printf 'ERROR: Permission denied. Run manually: cd %s/packages/ao && sudo npm link\n' "$REPO_ROOT"
|
||||
cat "$npm_link_error" >&2
|
||||
rm -f "$npm_link_error"
|
||||
printf 'ERROR: Launcher refresh failed. Run manually: cd %s/packages/ao && sudo npm link --force\n' "$REPO_ROOT"
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
|
|
|
|||
|
|
@ -137,13 +137,20 @@ pnpm build
|
|||
echo ""
|
||||
echo "Linking CLI globally..."
|
||||
cd packages/ao
|
||||
if npm link 2>/dev/null; then
|
||||
:
|
||||
npm_link_error="$(mktemp)"
|
||||
if npm link --force 2>"$npm_link_error"; then
|
||||
rm -f "$npm_link_error"
|
||||
elif [ "$INTERACTIVE" = true ]; then
|
||||
echo " Permission denied. Retrying with sudo..."
|
||||
sudo npm link
|
||||
rm -f "$npm_link_error"
|
||||
echo " Launcher refresh failed. Retrying with sudo..."
|
||||
if ! sudo npm link --force; then
|
||||
echo "ERROR: sudo npm link --force failed. Inspect npm output above." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "ERROR: Permission denied. Run manually: cd packages/ao && sudo npm link"
|
||||
cat "$npm_link_error" >&2
|
||||
rm -f "$npm_link_error"
|
||||
echo "ERROR: Launcher refresh failed. Run manually: cd packages/ao && sudo npm link --force"
|
||||
exit 1
|
||||
fi
|
||||
cd "$REPO_ROOT"
|
||||
|
|
|
|||
Loading…
Reference in New Issue