Merge PR #6527: fix(coding-agent): self-update binary install when its dir overlaps npm/bun bin dir (@am423)
This commit is contained in:
@@ -40,6 +40,9 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed advisor retry amplification after transient Codex SSE socket closures by limiting each advisor-level try to one provider transport attempt.
|
||||
### Fixed
|
||||
|
||||
- Fixed `omp update` aborting with `npm error EEXIST` on standalone binary installs whose directory coincides with the global npm/bun bin dir (for example `npm prefix -g` set to `~/.local`, which the installer also targets). The install-target resolver classified the binary as npm/bun-managed from directory containment alone, so `npm install -g` tried to replace a regular file its symlink step would clobber; it now treats a plain executable (not a symlink) in a package-manager bin dir as the standalone binary and self-updates it in place ([#6527](https://github.com/can1357/oh-my-pi/pull/6527) by [@am423](https://github.com/am423)).
|
||||
|
||||
## [17.1.2] - 2026-07-24
|
||||
|
||||
|
||||
@@ -209,6 +209,13 @@ interface UpdateMethodResolutionOptions {
|
||||
miseBinDirs?: readonly string[];
|
||||
miseDataDir?: string;
|
||||
npmBinDir?: string;
|
||||
/**
|
||||
* Whether the resolved omp path is a plain file (the standalone binary)
|
||||
* rather than a package-manager symlink. Stops a binary install from being
|
||||
* misrouted to npm/bun when the global bin dir overlaps the installer's
|
||||
* target directory.
|
||||
*/
|
||||
ompIsRegularFile?: boolean;
|
||||
}
|
||||
|
||||
type UpdateTarget =
|
||||
@@ -223,15 +230,22 @@ function resolveUpdateMethod(
|
||||
bunBinDir: string | undefined,
|
||||
options: UpdateMethodResolutionOptions = {},
|
||||
): UpdateMethod {
|
||||
const { homebrewPrefix, miseBinDirs = [], miseDataDir, npmBinDir } = options;
|
||||
const { homebrewPrefix, miseBinDirs = [], miseDataDir, npmBinDir, ompIsRegularFile = false } = options;
|
||||
const launcherExtension = path.extname(ompPath).toLowerCase();
|
||||
const isWindowsScriptLauncher =
|
||||
launcherExtension === ".cmd" || launcherExtension === ".ps1" || launcherExtension === ".bat";
|
||||
if (homebrewPrefix && isPathInDirectory(ompPath, path.join(homebrewPrefix, "bin"))) return "brew";
|
||||
if (miseBinDirs.some(dir => isPathInDirectory(ompPath, dir))) return "mise";
|
||||
if (miseDataDir && isPathInDirectory(ompPath, path.join(miseDataDir, "shims"))) return "mise";
|
||||
if (bunBinDir && isPathInDirectory(ompPath, bunBinDir)) return "bun";
|
||||
if ((npmBinDir && isPathInDirectory(ompPath, npmBinDir)) || isWindowsScriptLauncher) return "npm";
|
||||
// A plain executable file in a package-manager bin dir is the standalone
|
||||
// binary the installer placed there, not an npm/bun-managed install (those
|
||||
// symlink into node_modules). When the global bin dir overlaps the
|
||||
// installer's default (~/.local/bin), classifying by directory alone routes
|
||||
// a binary install through npm/bun, whose reinstall then collides with the
|
||||
// existing file (npm EEXIST). Fall through to binary replacement instead.
|
||||
if (bunBinDir && isPathInDirectory(ompPath, bunBinDir) && !ompIsRegularFile) return "bun";
|
||||
if ((npmBinDir && isPathInDirectory(ompPath, npmBinDir) && !ompIsRegularFile) || isWindowsScriptLauncher)
|
||||
return "npm";
|
||||
return "binary";
|
||||
}
|
||||
|
||||
@@ -252,7 +266,22 @@ async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
||||
const ompPath = resolveOmpPath();
|
||||
|
||||
if (ompPath) {
|
||||
const method = resolveUpdateMethod(ompPath, bunBinDir, { homebrewPrefix, miseBinDirs, miseDataDir, npmBinDir });
|
||||
// Package-manager installs symlink the bin entry into node_modules; the
|
||||
// standalone installer writes a plain executable. When the global bin dir
|
||||
// overlaps the installer's default (~/.local/bin), that file type — not
|
||||
// directory containment — distinguishes a binary install from npm/bun.
|
||||
let ompIsRegularFile = false;
|
||||
try {
|
||||
const stat = fs.lstatSync(ompPath);
|
||||
ompIsRegularFile = stat.isFile() && !stat.isSymbolicLink();
|
||||
} catch {}
|
||||
const method = resolveUpdateMethod(ompPath, bunBinDir, {
|
||||
homebrewPrefix,
|
||||
miseBinDirs,
|
||||
miseDataDir,
|
||||
npmBinDir,
|
||||
ompIsRegularFile,
|
||||
});
|
||||
if (method === "binary") return { method, path: ompPath };
|
||||
return { method };
|
||||
}
|
||||
|
||||
@@ -90,6 +90,36 @@ describe("update-cli install target detection", () => {
|
||||
expect(method).toBe("npm");
|
||||
});
|
||||
|
||||
it("uses binary update when a plain file in the npm global bin dir is the standalone binary, not an npm symlink", () => {
|
||||
// Regression: with `npm prefix -g` pointed at the installer's default
|
||||
// (~/.local), directory containment alone misclassified the standalone
|
||||
// binary as npm-managed, so `npm install -g` failed with EEXIST refusing
|
||||
// to overwrite the existing executable.
|
||||
const method = resolveUpdateMethodForTest("/home/u/.local/bin/omp", undefined, {
|
||||
npmBinDir: "/home/u/.local/bin",
|
||||
ompIsRegularFile: true,
|
||||
});
|
||||
|
||||
expect(method).toBe("binary");
|
||||
});
|
||||
|
||||
it("uses binary update when a plain file in the bun global bin dir is the standalone binary", () => {
|
||||
const method = resolveUpdateMethodForTest("/home/u/.local/bin/omp", "/home/u/.local/bin", {
|
||||
ompIsRegularFile: true,
|
||||
});
|
||||
|
||||
expect(method).toBe("binary");
|
||||
});
|
||||
|
||||
it("still uses npm update when the npm global bin entry is a package-manager symlink, not a plain file", () => {
|
||||
const method = resolveUpdateMethodForTest("/home/u/.local/bin/omp", undefined, {
|
||||
npmBinDir: "/home/u/.local/bin",
|
||||
ompIsRegularFile: false,
|
||||
});
|
||||
|
||||
expect(method).toBe("npm");
|
||||
});
|
||||
|
||||
it("uses binary update when prioritized omp is outside bun global bin", () => {
|
||||
const method = resolveUpdateMethodForTest("/Users/test/.local/bin/omp", "/Users/test/.bun/bin");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user