From 24d4862dfdfd552a5d3e79d088daee90e558aba4 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Fri, 18 Jul 2025 15:55:58 +0200 Subject: [PATCH] Fix broken shell integration with nvm for Zsh and Bash --- src/shell-integration/setup.js | 2 +- .../{init-zsh.sh => init-posix.sh} | 0 .../supported-shells/bash.js | 18 ++++-- .../supported-shells/bash.spec.js | 64 +++++++++---------- src/shell-integration/supported-shells/zsh.js | 8 +-- .../supported-shells/zsh.spec.js | 24 ++++--- 6 files changed, 63 insertions(+), 53 deletions(-) rename src/shell-integration/startup-scripts/{init-zsh.sh => init-posix.sh} (100%) diff --git a/src/shell-integration/setup.js b/src/shell-integration/setup.js index 3f9bbb3..999f3d2 100644 --- a/src/shell-integration/setup.js +++ b/src/shell-integration/setup.js @@ -81,7 +81,7 @@ function setupShell(shell) { } function copyStartupFiles() { - const startupFiles = ["init-zsh.sh"]; + const startupFiles = ["init-posix.sh"]; for (const file of startupFiles) { const targetPath = path.join(os.homedir(), ".safe-chain", "scripts", file); diff --git a/src/shell-integration/startup-scripts/init-zsh.sh b/src/shell-integration/startup-scripts/init-posix.sh similarity index 100% rename from src/shell-integration/startup-scripts/init-zsh.sh rename to src/shell-integration/startup-scripts/init-posix.sh diff --git a/src/shell-integration/supported-shells/bash.js b/src/shell-integration/supported-shells/bash.js index 66b844d..3c4b1f9 100644 --- a/src/shell-integration/supported-shells/bash.js +++ b/src/shell-integration/supported-shells/bash.js @@ -21,18 +21,22 @@ function teardown(tools) { removeLinesMatchingPattern(startupFile, new RegExp(`^alias\\s+${tool}=`)); } + // Removes the line that sources the safe-chain bash initialization script (~/.aikido/scripts/init-posix.sh) + removeLinesMatchingPattern( + startupFile, + /^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh/ + ); + return true; } -function setup(tools) { +function setup() { const startupFile = getStartupFile(); - for (const { tool, aikidoCommand } of tools) { - addLineToFile( - startupFile, - `alias ${tool}="${aikidoCommand}" # Safe-chain alias for ${tool}` - ); - } + addLineToFile( + startupFile, + `source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script` + ); return true; } diff --git a/src/shell-integration/supported-shells/bash.spec.js b/src/shell-integration/supported-shells/bash.spec.js index ce666e5..e23addb 100644 --- a/src/shell-integration/supported-shells/bash.spec.js +++ b/src/shell-integration/supported-shells/bash.spec.js @@ -66,37 +66,16 @@ describe("Bash shell integration", () => { }); describe("setup", () => { - it("should add aliases for all provided tools", () => { - const tools = [ - { tool: "npm", aikidoCommand: "aikido-npm" }, - { tool: "npx", aikidoCommand: "aikido-npx" }, - { tool: "yarn", aikidoCommand: "aikido-yarn" }, - ]; - - const result = bash.setup(tools); + it("should add source line for bash initialization script", () => { + const result = bash.setup(); assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - content.includes('alias npm="aikido-npm" # Safe-chain alias for npm') + content.includes( + "source ~/.safe-chain/scripts/init-posix.sh # Safe-chain bash initialization script" + ) ); - assert.ok( - content.includes('alias npx="aikido-npx" # Safe-chain alias for npx') - ); - assert.ok( - content.includes('alias yarn="aikido-yarn" # Safe-chain alias for yarn') - ); - }); - - it("should handle empty tools array", () => { - const result = bash.setup([]); - assert.strictEqual(result, true); - - // File should be created during teardown call even if no tools are provided - if (fs.existsSync(mockStartupFile)) { - const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.strictEqual(content.trim(), ""); - } }); }); @@ -174,14 +153,14 @@ describe("Bash shell integration", () => { // Setup bash.setup(tools); let content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(content.includes('alias npm="aikido-npm"')); - assert.ok(content.includes('alias yarn="aikido-yarn"')); + assert.ok(content.includes("source ~/.safe-chain/scripts/init-posix.sh")); // Teardown bash.teardown(tools); content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("alias npm=")); - assert.ok(!content.includes("alias yarn=")); + assert.ok( + !content.includes("source ~/.safe-chain/scripts/init-posix.sh") + ); }); it("should handle multiple setup calls", () => { @@ -192,8 +171,29 @@ describe("Bash shell integration", () => { bash.setup(tools); const content = fs.readFileSync(mockStartupFile, "utf-8"); - const npmMatches = (content.match(/alias npm="/g) || []).length; - assert.strictEqual(npmMatches, 1, "Should not duplicate aliases"); + const sourceMatches = (content.match(/source.*init-posix\.sh/g) || []) + .length; + assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); + }); + + it("should handle mixed content with aliases and source lines", () => { + const initialContent = [ + "#!/bin/bash", + "alias npm='old-npm'", + "source ~/.safe-chain/scripts/init-posix.sh", + "alias ls='ls --color=auto'", + ].join("\n"); + + fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); + + // Teardown should remove both aliases and source line + bash.teardown(knownAikidoTools); + const content = fs.readFileSync(mockStartupFile, "utf-8"); + assert.ok(!content.includes("alias npm=")); + assert.ok( + !content.includes("source ~/.safe-chain/scripts/init-posix.sh") + ); + assert.ok(content.includes("alias ls=")); }); }); }); diff --git a/src/shell-integration/supported-shells/zsh.js b/src/shell-integration/supported-shells/zsh.js index 82f03af..b5167fe 100644 --- a/src/shell-integration/supported-shells/zsh.js +++ b/src/shell-integration/supported-shells/zsh.js @@ -21,21 +21,21 @@ function teardown(tools) { removeLinesMatchingPattern(startupFile, new RegExp(`^alias\\s+${tool}=`)); } - // Removes the line that sources the safe-chain zsh initialization script (~/.aikido/scripts/init-zsh.sh) + // Removes the line that sources the safe-chain zsh initialization script (~/.aikido/scripts/init-posix.sh) removeLinesMatchingPattern( startupFile, - /^source\s+~\/\.safe-chain\/scripts\/init-zsh\.sh/ + /^source\s+~\/\.safe-chain\/scripts\/init-posix\.sh/ ); return true; } -function setup(tools) { +function setup() { const startupFile = getStartupFile(); addLineToFile( startupFile, - `source ~/.safe-chain/scripts/init-zsh.sh # Safe-chain Zsh initialization script` + `source ~/.safe-chain/scripts/init-posix.sh # Safe-chain Zsh initialization script` ); return true; diff --git a/src/shell-integration/supported-shells/zsh.spec.js b/src/shell-integration/supported-shells/zsh.spec.js index 4b5a0f1..95c12ac 100644 --- a/src/shell-integration/supported-shells/zsh.spec.js +++ b/src/shell-integration/supported-shells/zsh.spec.js @@ -73,7 +73,7 @@ describe("Zsh shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( content.includes( - "source ~/.safe-chain/scripts/init-zsh.sh # Safe-chain Zsh initialization script" + "source ~/.safe-chain/scripts/init-posix.sh # Safe-chain Zsh initialization script" ) ); }); @@ -83,7 +83,7 @@ describe("Zsh shell integration", () => { assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(content.includes("source ~/.safe-chain/scripts/init-zsh.sh")); + assert.ok(content.includes("source ~/.safe-chain/scripts/init-posix.sh")); }); }); @@ -114,7 +114,7 @@ describe("Zsh shell integration", () => { it("should remove zsh initialization script source line", () => { const initialContent = [ "#!/bin/zsh", - "source ~/.safe-chain/scripts/init-zsh.sh", + "source ~/.safe-chain/scripts/init-posix.sh", "alias ls='ls --color=auto'", ].join("\n"); @@ -124,7 +124,9 @@ describe("Zsh shell integration", () => { assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("source ~/.safe-chain/scripts/init-zsh.sh")); + assert.ok( + !content.includes("source ~/.safe-chain/scripts/init-posix.sh") + ); assert.ok(content.includes("alias ls=")); }); @@ -178,12 +180,14 @@ describe("Zsh shell integration", () => { // Setup zsh.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(content.includes("source ~/.safe-chain/scripts/init-zsh.sh")); + assert.ok(content.includes("source ~/.safe-chain/scripts/init-posix.sh")); // Teardown zsh.teardown(tools); content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("source ~/.safe-chain/scripts/init-zsh.sh")); + assert.ok( + !content.includes("source ~/.safe-chain/scripts/init-posix.sh") + ); }); it("should handle multiple setup calls", () => { @@ -194,7 +198,7 @@ describe("Zsh shell integration", () => { zsh.setup(tools); const content = fs.readFileSync(mockStartupFile, "utf-8"); - const sourceMatches = (content.match(/source.*init-zsh\.sh/g) || []) + const sourceMatches = (content.match(/source.*init-posix\.sh/g) || []) .length; assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); }); @@ -203,7 +207,7 @@ describe("Zsh shell integration", () => { const initialContent = [ "#!/bin/zsh", "alias npm='old-npm'", - "source ~/.safe-chain/scripts/init-zsh.sh", + "source ~/.safe-chain/scripts/init-posix.sh", "alias ls='ls --color=auto'", ].join("\n"); @@ -213,7 +217,9 @@ describe("Zsh shell integration", () => { zsh.teardown(knownAikidoTools); const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok(!content.includes("alias npm=")); - assert.ok(!content.includes("source ~/.safe-chain/scripts/init-zsh.sh")); + assert.ok( + !content.includes("source ~/.safe-chain/scripts/init-posix.sh") + ); assert.ok(content.includes("alias ls=")); }); });