Skip to content

Commit 0099804

Browse files
committed
fix(openshell): bundle upstream cli fallback
1 parent f8731b3 commit 0099804

File tree

4 files changed

+174
-3
lines changed

4 files changed

+174
-3
lines changed

extensions/openshell/src/cli.test.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
import { describe, expect, it } from "vitest";
2-
import { buildExecRemoteCommand, buildOpenShellBaseArgv, shellEscape } from "./cli.js";
1+
import { afterEach, describe, expect, it } from "vitest";
2+
import {
3+
buildExecRemoteCommand,
4+
buildOpenShellBaseArgv,
5+
resolveOpenShellCommand,
6+
setBundledOpenShellCommandResolverForTest,
7+
shellEscape,
8+
} from "./cli.js";
39
import { resolveOpenShellPluginConfig } from "./config.js";
410

511
describe("openshell cli helpers", () => {
12+
afterEach(() => {
13+
setBundledOpenShellCommandResolverForTest();
14+
});
15+
616
it("builds base argv with gateway overrides", () => {
717
const config = resolveOpenShellPluginConfig({
818
command: "/usr/local/bin/openshell",
@@ -18,6 +28,20 @@ describe("openshell cli helpers", () => {
1828
]);
1929
});
2030

31+
it("prefers the bundled openshell command when available", () => {
32+
setBundledOpenShellCommandResolverForTest(() => "/tmp/node_modules/.bin/openshell");
33+
const config = resolveOpenShellPluginConfig(undefined);
34+
35+
expect(resolveOpenShellCommand("openshell")).toBe("/tmp/node_modules/.bin/openshell");
36+
expect(buildOpenShellBaseArgv(config)).toEqual(["/tmp/node_modules/.bin/openshell"]);
37+
});
38+
39+
it("falls back to the PATH command when no bundled openshell is present", () => {
40+
setBundledOpenShellCommandResolverForTest(() => null);
41+
42+
expect(resolveOpenShellCommand("openshell")).toBe("openshell");
43+
});
44+
2145
it("shell escapes single quotes", () => {
2246
expect(shellEscape(`a'b`)).toBe(`'a'"'"'b'`);
2347
});

extensions/openshell/src/cli.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import fs from "node:fs";
2+
import { createRequire } from "node:module";
3+
import path from "node:path";
14
import {
25
buildExecRemoteCommand,
36
createSshSandboxSessionFromConfigText,
@@ -9,14 +12,54 @@ import type { ResolvedOpenShellPluginConfig } from "./config.js";
912

1013
export { buildExecRemoteCommand, shellEscape } from "openclaw/plugin-sdk/sandbox";
1114

15+
const require = createRequire(import.meta.url);
16+
17+
let cachedBundledOpenShellCommand: string | null | undefined;
18+
let bundledCommandResolverForTest: (() => string | null) | undefined;
19+
1220
export type OpenShellExecContext = {
1321
config: ResolvedOpenShellPluginConfig;
1422
sandboxName: string;
1523
timeoutMs?: number;
1624
};
1725

26+
export function setBundledOpenShellCommandResolverForTest(resolver?: () => string | null): void {
27+
bundledCommandResolverForTest = resolver;
28+
cachedBundledOpenShellCommand = undefined;
29+
}
30+
31+
function resolveBundledOpenShellCommand(): string | null {
32+
if (bundledCommandResolverForTest) {
33+
return bundledCommandResolverForTest();
34+
}
35+
if (cachedBundledOpenShellCommand !== undefined) {
36+
return cachedBundledOpenShellCommand;
37+
}
38+
try {
39+
const packageJsonPath = require.resolve("openshell/package.json");
40+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
41+
bin?: string | Record<string, string>;
42+
};
43+
const relativeBin =
44+
typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.openshell;
45+
cachedBundledOpenShellCommand = relativeBin
46+
? path.resolve(path.dirname(packageJsonPath), relativeBin)
47+
: null;
48+
} catch {
49+
cachedBundledOpenShellCommand = null;
50+
}
51+
return cachedBundledOpenShellCommand;
52+
}
53+
54+
export function resolveOpenShellCommand(command: string): string {
55+
if (command !== "openshell") {
56+
return command;
57+
}
58+
return resolveBundledOpenShellCommand() ?? command;
59+
}
60+
1861
export function buildOpenShellBaseArgv(config: ResolvedOpenShellPluginConfig): string[] {
19-
const argv = [config.command];
62+
const argv = [resolveOpenShellCommand(config.command)];
2063
if (config.gateway) {
2164
argv.push("--gateway", config.gateway);
2265
}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,9 @@
811811
"optional": true
812812
}
813813
},
814+
"optionalDependencies": {
815+
"openshell": "0.1.0"
816+
},
814817
"engines": {
815818
"node": ">=22.16.0"
816819
},

pnpm-lock.yaml

Lines changed: 101 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)