fix(extensibility): guarantee string getPackageDir in compiled shim builds

omp's canonical getPackageDir() returns undefined inside a bun --compile
binary (import.meta.dir is /$bunfs/root, no owning package.json — issue
#1423). Re-exporting it directly broke pi's string-valued contract:
legacy extensions doing path.join(getPackageDir(), ...) crashed at
runtime in the shipped binary, the primary distribution.

Wrap the canonical helper so the shim always returns a string, falling
back to the executable's directory in compiled mode (where the binary is
the install root). PI_PACKAGE_DIR and dev/source/npm-dist walk-up still
win. Test covers the compiled-mode fallback via isCompiledBinary spy.

Fixes #5968
This commit is contained in:
roboomp
2026-07-18 06:50:00 +00:00
parent 4a25993682
commit 5082763d49
3 changed files with 50 additions and 10 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
### Fixed
- Fixed legacy pi extensions failing extension validation when importing `getPackageDir` or `getProjectDir` from `@earendil-works/pi-coding-agent` (aliased to the legacy shim). The shim only re-exported `getAgentDir`; the two missing path helpers now resolve — `getProjectDir` from `@oh-my-pi/pi-utils` and `getPackageDir` from omp's canonical package-root helper — so extensions like `@gotgenes/pi-permission-system` install and load ([#5968](https://github.com/can1357/oh-my-pi/issues/5968)).
- Fixed legacy pi extensions failing extension validation when importing `getPackageDir` or `getProjectDir` from `@earendil-works/pi-coding-agent` (aliased to the legacy shim). The shim only re-exported `getAgentDir`; the two missing path helpers now resolve — `getProjectDir` from `@oh-my-pi/pi-utils`, and `getPackageDir` as a string-valued wrapper over omp's canonical package-root helper that falls back to the executable's directory inside `bun --compile` binaries (where the canonical helper returns `undefined`), matching pi's string contract. Extensions like `@gotgenes/pi-permission-system` install and load, and `path.join(getPackageDir(), …)` no longer crashes in the shipped binary ([#5968](https://github.com/can1357/oh-my-pi/issues/5968)).
## [17.0.4] - 2026-07-18
@@ -22,8 +22,10 @@ import {
getAgentDbPath,
getAgentDir,
getProjectDir,
isCompiledBinary,
parseFrontmatter as parseOmpFrontmatter,
} from "@oh-my-pi/pi-utils";
import { getPackageDir as getOmpPackageDir } from "../config";
import type { PromptTemplate } from "../config/prompt-templates";
import { type SettingPath, Settings } from "../config/settings";
import { EditTool } from "../edit";
@@ -1336,12 +1338,28 @@ export function readStoredCredential(provider: string): AuthCredential | undefin
// Pi SDK path helpers. `export * from "../index"` above only forwards
// `getAgentDir`; `getProjectDir` (a `@oh-my-pi/pi-utils` helper) and
// `getPackageDir` (omp's canonical coding-agent package-root helper, matching
// pi's "install directory of the coding-agent package" semantics) are absent
// from that barrel, so legacy extensions importing either fail Bun's static
// export check during validation (issue #5968).
// `getPackageDir` are absent from that barrel, so legacy extensions importing
// either fail Bun's static export check during validation (issue #5968).
export { getProjectDir } from "@oh-my-pi/pi-utils";
export { getPackageDir } from "../config";
/**
* Coding-agent package install directory, matching pi's string-valued
* `getPackageDir()` contract (extensions do `path.join(getPackageDir(), ...)`
* to auto-allow bundled docs/resources).
*
* omp's canonical `getPackageDir()` (`../config`) returns `undefined` inside a
* `bun --compile` binary — `import.meta.dir` is `/$bunfs/root` and no owning
* `package.json` exists (issue #1423). Returning `undefined` there would crash
* every legacy `path.join(getPackageDir(), ...)` at runtime in the shipped
* binary, the primary distribution. So fall back to the executable's own
* directory in compiled mode, where the binary *is* the install root. The
* `PI_PACKAGE_DIR` override and dev/source/npm-dist walk-up still win via the
* canonical helper.
*/
export function getPackageDir(): string {
return getOmpPackageDir() ?? (isCompiledBinary() ? path.dirname(process.execPath) : process.cwd());
}
export * from "../index";
export { formatBytes as formatSize } from "../tools/render-utils";
export { Type } from "./typebox";
@@ -1,6 +1,8 @@
import { describe, expect, it } from "bun:test";
import { afterEach, describe, expect, it, spyOn, vi } from "bun:test";
import * as path from "node:path";
import * as configModule from "@oh-my-pi/pi-coding-agent/config";
import * as shim from "@oh-my-pi/pi-coding-agent/extensibility/legacy-pi-coding-agent-shim";
import * as utils from "@oh-my-pi/pi-utils";
// Issue #5968: pi extensions import the SDK path helpers (`getAgentDir`,
// `getProjectDir`, `getPackageDir`) from `@earendil-works/pi-coding-agent`,
@@ -10,17 +12,37 @@ import * as shim from "@oh-my-pi/pi-coding-agent/extensibility/legacy-pi-coding-
// error and any importing extension failed validation. These pin the full
// path-helper surface through the public package specifier.
describe("legacy shim path helpers", () => {
afterEach(() => vi.restoreAllMocks());
it("exports the three pi SDK path helpers as callable functions", () => {
expect(typeof shim.getAgentDir).toBe("function");
expect(typeof shim.getProjectDir).toBe("function");
expect(typeof shim.getPackageDir).toBe("function");
});
it("getPackageDir resolves the coding-agent package root", () => {
it("getPackageDir resolves the coding-agent package root in source mode", () => {
// omp's canonical helper returns the package root containing package.json
// (pi's "install directory of the coding-agent package" semantics).
const dir = shim.getPackageDir();
expect(dir).toBeDefined();
expect(path.basename(dir as string)).toBe("coding-agent");
expect(path.basename(dir)).toBe("coding-agent");
});
// Pi's getPackageDir() is string-valued: extensions do
// `path.join(getPackageDir(), ...)`. omp's canonical helper returns
// `undefined` inside a `bun --compile` binary (import.meta.dir is
// /$bunfs/root, no package.json — issue #1423), which would crash every
// such call in the shipped binary. The shim MUST fall back to a real
// directory instead of forwarding undefined.
it("getPackageDir returns a string even when the canonical helper yields undefined", () => {
spyOn(configModule, "getPackageDir").mockReturnValue(undefined);
const dir = shim.getPackageDir();
expect(typeof dir).toBe("string");
expect(dir.length).toBeGreaterThan(0);
});
it("getPackageDir falls back to the executable directory in compiled-binary mode", () => {
spyOn(configModule, "getPackageDir").mockReturnValue(undefined);
spyOn(utils, "isCompiledBinary").mockReturnValue(true);
expect(shim.getPackageDir()).toBe(path.dirname(process.execPath));
});
});