From 0a6fd4cbb740b8b82c732095e9c0584790a6703c Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 10 Sep 2025 13:31:30 +0200 Subject: [PATCH 1/4] Add support for Cygwin on windows - fixes #31 --- .../supported-shells/bash.js | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.js index 3c4b1f9..5f9c54e 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.js @@ -3,7 +3,8 @@ import { doesExecutableExistOnSystem, removeLinesMatchingPattern, } from "../helpers.js"; -import { execSync } from "child_process"; +import { execSync, spawnSync } from "child_process"; +import * as os from "os"; const shellName = "Bash"; const executableName = "bash"; @@ -43,10 +44,12 @@ function setup() { function getStartupFile() { try { - return execSync(startupFileCommand, { + var path = execSync(startupFileCommand, { encoding: "utf8", shell: executableName, }).trim(); + + return windowsFixPath(path); } catch (error) { throw new Error( `Command failed: ${startupFileCommand}. Error: ${error.message}` @@ -54,6 +57,50 @@ function getStartupFile() { } } +function windowsFixPath(path) { + try { + if (os.platform() !== "win32") { + return path; + } + + // On windows cygwin bash, paths are returned in format /c/user/..., but we need it in format C:\user\... + // To convert, the cygpath -w command can be used to convert to the desired format. + // Cygpath only exists on Cygwin, so we first check if the command is available. + // If it is, we use it to convert the path. + if (hasCygpath()) { + return cygpathw(path); + } + + return path; + } catch { + return path; + } +} + +function hasCygpath() { + try { + var result = spawnSync("where", ["cygpath"], { shell: executableName }); + return result.status === 0; + } catch { + return false; + } +} + +function cygpathw(path) { + try { + var result = spawnSync("cygpath", ["-w", path], { + encoding: "utf8", + shell: executableName, + }); + if (result.status === 0) { + return result.stdout.trim(); + } + return path; + } catch { + return path; + } +} + export default { name: shellName, isInstalled, From 68267284810944af9eee4ced45b6e48230c6e238 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 10 Sep 2025 14:10:25 +0200 Subject: [PATCH 2/4] Add test for cygwin flow --- .../supported-shells/bash.spec.js | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js index e23addb..ebe0676 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js @@ -8,6 +8,8 @@ import { knownAikidoTools } from "../helpers.js"; describe("Bash shell integration", () => { let mockStartupFile; let bash; + let windowsCygwinPath = ""; + let platform = "linux"; beforeEach(async () => { // Create temporary startup file for testing @@ -37,6 +39,35 @@ describe("Bash shell integration", () => { mock.module("child_process", { namedExports: { execSync: () => mockStartupFile, + spawnSync: (command, args) => { + if (platform !== "win32") { + return { status: 0 }; + } + + if (command === "where" && args[0] === "cygpath") { + return { + status: 0, + stdout: Buffer.from("C:\\cygwin64\\bin\\cygpath.exe\n"), + }; + } + + if ( + command === "cygpath" && + args[0] === "-w" && + args[1] === mockStartupFile + ) { + return { + status: 0, + stdout: windowsCygwinPath + "\n", + }; + } + }, + }, + }); + + mock.module("os", { + namedExports: { + platform: () => platform, }, }); @@ -52,6 +83,7 @@ describe("Bash shell integration", () => { // Reset mocks mock.reset(); + platform = "linux"; }); describe("isInstalled", () => { @@ -77,6 +109,23 @@ describe("Bash shell integration", () => { ) ); }); + + it("should use the correct startup file for cygwin bash on Windows", () => { + platform = "win32"; + // set windows path to the correct filename + windowsCygwinPath = mockStartupFile; + mockStartupFile = "DUMMY"; // This will be overridden by the mocked execSync + + const result = bash.setup(); + assert.strictEqual(result, true); + + const content = fs.readFileSync(windowsCygwinPath, "utf-8"); + assert.ok( + content.includes( + "source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script" + ) + ); + }); }); describe("teardown", () => { From 32ba9d71b5668775a970374aac00f6e60e6d41f1 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 10 Sep 2025 14:13:51 +0200 Subject: [PATCH 3/4] Cleanup test bash file for cygwin --- .../src/shell-integration/supported-shells/bash.spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js index ebe0676..bd82dfe 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js @@ -80,6 +80,10 @@ describe("Bash shell integration", () => { if (fs.existsSync(mockStartupFile)) { fs.unlinkSync(mockStartupFile); } + if (windowsCygwinPath && fs.existsSync(windowsCygwinPath)) { + fs.unlinkSync(windowsCygwinPath); + windowsCygwinPath = ""; + } // Reset mocks mock.reset(); From ca8bc31311994078a2063f4883f29cbb8ef8a428 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 10 Sep 2025 14:16:54 +0200 Subject: [PATCH 4/4] Add a more descriptive comment for the cygwin test --- .../src/shell-integration/supported-shells/bash.spec.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js index bd82dfe..aa7159f 100644 --- a/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js +++ b/packages/safe-chain/src/shell-integration/supported-shells/bash.spec.js @@ -115,10 +115,13 @@ describe("Bash shell integration", () => { }); it("should use the correct startup file for cygwin bash on Windows", () => { + // Simulate Windows platform with Cygwin bash + // The mockStartupFile contains a path we cannot open, so we set it to something useless + // The cygpath -w command that is mocked, will return the windowsCygwinPath variable + // This simulates the conversion from /c/Users/... to C:\Users\... platform = "win32"; - // set windows path to the correct filename windowsCygwinPath = mockStartupFile; - mockStartupFile = "DUMMY"; // This will be overridden by the mocked execSync + mockStartupFile = "DUMMY"; const result = bash.setup(); assert.strictEqual(result, true);