Merge pull request #39 from AikidoSec/windows-cygwin-bash-support

Add support for Cygwin on windows
This commit is contained in:
Sander Declerck 2025-09-10 14:20:04 +02:00 committed by GitHub
commit 025f84407e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 105 additions and 2 deletions

View file

@ -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,

View file

@ -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,
},
});
@ -49,9 +80,14 @@ describe("Bash shell integration", () => {
if (fs.existsSync(mockStartupFile)) {
fs.unlinkSync(mockStartupFile);
}
if (windowsCygwinPath && fs.existsSync(windowsCygwinPath)) {
fs.unlinkSync(windowsCygwinPath);
windowsCygwinPath = "";
}
// Reset mocks
mock.reset();
platform = "linux";
});
describe("isInstalled", () => {
@ -77,6 +113,26 @@ 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";
windowsCygwinPath = mockStartupFile;
mockStartupFile = "DUMMY";
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", () => {