-
Notifications
You must be signed in to change notification settings - Fork 3.9k
fix: HOTFIX only set cwd to workspace dir for file URIs #8867
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+215
−11
Merged
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| import { fileURLToPath } from "node:url"; | ||
| import os from "node:os"; | ||
| import { runTerminalCommandImpl } from "./runTerminalCommand"; | ||
|
|
||
| describe("runTerminalCommand cwd handling", () => { | ||
| const mockExtras = { | ||
| ide: { | ||
| getIdeInfo: jest.fn().async().mockResolvedValue({ remoteName: "local" }), | ||
| getWorkspaceDirs: jest.fn().async(), | ||
RomneyDa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| runCommand: jest.fn().async(), | ||
RomneyDa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
| toolCallId: "test-tool-call", | ||
| }; | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
|
|
||
| describe("workspace directory handling", () => { | ||
| it("should use file:// URI when available", async () => { | ||
| const fileUri = "file:///home/user/workspace"; | ||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue([fileUri]); | ||
|
|
||
| // We can't easily test the internal cwd without mocking child_process, | ||
| // but we can verify the function doesn't throw with file URIs | ||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("should skip non-file URIs and use the first file:// URI", async () => { | ||
| const workspaceDirs = [ | ||
| "vscode-vfs://github/user/repo", | ||
| "untitled:/Untitled-1", | ||
| "file:///home/user/workspace", | ||
| "file:///home/user/other-workspace", | ||
| ]; | ||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue(workspaceDirs); | ||
|
|
||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("should handle workspace with only non-file URIs", async () => { | ||
| const workspaceDirs = [ | ||
| "vscode-vfs://github/user/repo", | ||
| "untitled:/Untitled-1", | ||
| ]; | ||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue(workspaceDirs); | ||
|
|
||
| // Should fall back to HOME/USERPROFILE or process.cwd() without throwing | ||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("should handle empty workspace directories", async () => { | ||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue([]); | ||
|
|
||
| // Should fall back to HOME/USERPROFILE or process.cwd() without throwing | ||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("should properly convert file:// URIs to paths", () => { | ||
| const fileUri = "file:///home/user/workspace"; | ||
| const expectedPath = "/home/user/workspace"; | ||
|
|
||
| // Test that fileURLToPath works correctly with file:// URIs | ||
| expect(fileURLToPath(fileUri)).toBe(expectedPath); | ||
| }); | ||
|
|
||
| it("should throw error when trying to convert non-file URI", () => { | ||
| const nonFileUri = "vscode-vfs://github/user/repo"; | ||
|
|
||
| // This demonstrates why the fix is needed - fileURLToPath throws on non-file URIs | ||
| expect(() => fileURLToPath(nonFileUri)).toThrow(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("remote environment handling", () => { | ||
| it("should use ide.runCommand for non-enabled remote environments", async () => { | ||
| mockExtras.ide.getIdeInfo.mockResolvedValue({ | ||
| remoteName: "some-unsupported-remote", | ||
| }); | ||
|
|
||
| const result = await runTerminalCommandImpl( | ||
| { command: "echo test" }, | ||
| mockExtras as any, | ||
| ); | ||
|
|
||
| expect(mockExtras.ide.runCommand).toHaveBeenCalledWith("echo test"); | ||
| expect(result[0].content).toContain("Terminal output not available"); | ||
| }); | ||
|
|
||
| it("should handle local environment with file URIs", async () => { | ||
| mockExtras.ide.getIdeInfo.mockResolvedValue({ remoteName: "local" }); | ||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue([ | ||
| "file:///home/user/workspace", | ||
| ]); | ||
|
|
||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("should handle WSL environment", async () => { | ||
| mockExtras.ide.getIdeInfo.mockResolvedValue({ remoteName: "wsl" }); | ||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue([ | ||
| "file:///home/user/workspace", | ||
| ]); | ||
|
|
||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
|
|
||
| it("should handle dev-container environment", async () => { | ||
| mockExtras.ide.getIdeInfo.mockResolvedValue({ | ||
| remoteName: "dev-container", | ||
| }); | ||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue(["file:///workspace"]); | ||
|
|
||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("fallback behavior", () => { | ||
| it("should use HOME environment variable as fallback", async () => { | ||
| const originalHome = process.env.HOME; | ||
| process.env.HOME = "/home/testuser"; | ||
|
|
||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue([ | ||
| "vscode-vfs://github/user/repo", | ||
| ]); | ||
|
|
||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
|
|
||
| process.env.HOME = originalHome; | ||
| }); | ||
|
|
||
| it("should use USERPROFILE on Windows as fallback", async () => { | ||
| const originalHome = process.env.HOME; | ||
| const originalUserProfile = process.env.USERPROFILE; | ||
|
|
||
| delete process.env.HOME; | ||
| process.env.USERPROFILE = "C:\\Users\\TestUser"; | ||
|
|
||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue([]); | ||
|
|
||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
|
|
||
| process.env.HOME = originalHome; | ||
| process.env.USERPROFILE = originalUserProfile; | ||
RomneyDa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }); | ||
|
|
||
| it("should use os.tmpdir() as final fallback", async () => { | ||
| const originalHome = process.env.HOME; | ||
| const originalUserProfile = process.env.USERPROFILE; | ||
| const originalCwd = process.cwd; | ||
|
|
||
| delete process.env.HOME; | ||
| delete process.env.USERPROFILE; | ||
| // Mock process.cwd to throw an error | ||
| process.cwd = jest.fn().mockImplementation(() => { | ||
| throw new Error("No cwd available"); | ||
| }); | ||
|
|
||
| mockExtras.ide.getWorkspaceDirs.mockResolvedValue([]); | ||
|
|
||
| // Should fall back to os.tmpdir() without throwing | ||
| await expect( | ||
| runTerminalCommandImpl( | ||
| { command: "echo test", waitForCompletion: false }, | ||
| mockExtras as any, | ||
| ), | ||
| ).resolves.toBeDefined(); | ||
|
|
||
| process.env.HOME = originalHome; | ||
| process.env.USERPROFILE = originalUserProfile; | ||
| process.cwd = originalCwd; | ||
| }); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.