Skip to content

Commit f4e7218

Browse files
committed
Expand tilde in LocalRuntime srcBaseDir at construction time
Previously, LocalRuntime would store srcBaseDir paths with tildes unexpanded, leading to incorrect path construction when using path.join(). This simplifies logic by ensuring all local paths are fully expanded at the IPC boundary. Changes: - Add expandTilde() utility for local file system path expansion - Update LocalRuntime constructor to expand tilde in srcBaseDir - Add comprehensive tests for both utilities For SSH runtime, tilde paths are preserved and expanded using expandTildeForSSH() when constructing remote commands, which is the correct behavior for remote paths.
1 parent 842609c commit f4e7218

File tree

4 files changed

+100
-3
lines changed

4 files changed

+100
-3
lines changed

src/runtime/LocalRuntime.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { describe, expect, it } from "bun:test";
2+
import * as os from "os";
3+
import * as path from "path";
4+
import { LocalRuntime } from "./LocalRuntime";
5+
6+
describe("LocalRuntime constructor", () => {
7+
it("should expand tilde in srcBaseDir", () => {
8+
const runtime = new LocalRuntime("~/workspace");
9+
const workspacePath = runtime.getWorkspacePath("/home/user/project", "branch");
10+
11+
// The workspace path should use the expanded home directory
12+
const expected = path.join(os.homedir(), "workspace", "project", "branch");
13+
expect(workspacePath).toBe(expected);
14+
});
15+
16+
it("should handle absolute paths without expansion", () => {
17+
const runtime = new LocalRuntime("/absolute/path");
18+
const workspacePath = runtime.getWorkspacePath("/home/user/project", "branch");
19+
20+
const expected = path.join("/absolute/path", "project", "branch");
21+
expect(workspacePath).toBe(expected);
22+
});
23+
24+
it("should handle bare tilde", () => {
25+
const runtime = new LocalRuntime("~");
26+
const workspacePath = runtime.getWorkspacePath("/home/user/project", "branch");
27+
28+
const expected = path.join(os.homedir(), "project", "branch");
29+
expect(workspacePath).toBe(expected);
30+
});
31+
});

src/runtime/LocalRuntime.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { checkInitHookExists, getInitHookPath, createLineBufferedLoggers } from
2222
import { execAsync } from "../utils/disposableExec";
2323
import { getProjectName } from "../utils/runtime/helpers";
2424
import { getErrorMessage } from "../utils/errors";
25+
import { expandTilde } from "./tildeExpansion";
2526

2627
/**
2728
* Local runtime implementation that executes commands and file operations
@@ -31,7 +32,8 @@ export class LocalRuntime implements Runtime {
3132
private readonly srcBaseDir: string;
3233

3334
constructor(srcBaseDir: string) {
34-
this.srcBaseDir = srcBaseDir;
35+
// Expand tilde to actual home directory path for local file system operations
36+
this.srcBaseDir = expandTilde(srcBaseDir);
3537
}
3638

3739
async exec(command: string, options: ExecOptions): Promise<ExecStream> {

src/runtime/tildeExpansion.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, expect, it } from "bun:test";
2+
import * as os from "os";
3+
import * as path from "path";
4+
import { expandTilde } from "./tildeExpansion";
5+
6+
describe("expandTilde", () => {
7+
it("should expand ~ to home directory", () => {
8+
const result = expandTilde("~");
9+
expect(result).toBe(os.homedir());
10+
});
11+
12+
it("should expand ~/path to home directory + path", () => {
13+
const result = expandTilde("~/workspace");
14+
expect(result).toBe(path.join(os.homedir(), "workspace"));
15+
});
16+
17+
it("should leave absolute paths unchanged", () => {
18+
const absolutePath = "/abs/path/to/dir";
19+
const result = expandTilde(absolutePath);
20+
expect(result).toBe(absolutePath);
21+
});
22+
23+
it("should leave relative paths unchanged", () => {
24+
const relativePath = "relative/path";
25+
const result = expandTilde(relativePath);
26+
expect(result).toBe(relativePath);
27+
});
28+
29+
it("should handle nested paths correctly", () => {
30+
const result = expandTilde("~/workspace/project/subdir");
31+
expect(result).toBe(path.join(os.homedir(), "workspace/project/subdir"));
32+
});
33+
});

src/runtime/tildeExpansion.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,42 @@
11
/**
2-
* Utilities for handling tilde path expansion in SSH commands
2+
* Utilities for handling tilde path expansion
33
*
4-
* When running commands over SSH, tilde paths need special handling:
4+
* For SSH commands, tilde paths need special handling:
55
* - Quoted tildes won't expand: `cd '~'` fails, but `cd "$HOME"` works
66
* - Must escape special shell characters when using $HOME expansion
7+
*
8+
* For local paths, tildes should be expanded to actual file system paths.
79
*/
810

11+
import * as os from "os";
12+
import * as path from "path";
13+
14+
/**
15+
* Expand tilde to actual home directory path for local file system operations.
16+
*
17+
* Converts:
18+
* - "~" → "/home/user" (actual home directory)
19+
* - "~/path" → "/home/user/path"
20+
* - "/abs/path" → "/abs/path" (unchanged)
21+
*
22+
* @param filePath - Path that may contain tilde prefix
23+
* @returns Fully expanded absolute path
24+
*
25+
* @example
26+
* expandTilde("~") // => "/home/user"
27+
* expandTilde("~/workspace") // => "/home/user/workspace"
28+
* expandTilde("/abs/path") // => "/abs/path"
29+
*/
30+
export function expandTilde(filePath: string): string {
31+
if (filePath === "~") {
32+
return os.homedir();
33+
} else if (filePath.startsWith("~/")) {
34+
return path.join(os.homedir(), filePath.slice(2));
35+
} else {
36+
return filePath;
37+
}
38+
}
39+
940
/**
1041
* Expand tilde path to $HOME-based path for use in SSH commands.
1142
*

0 commit comments

Comments
 (0)