From ca5d3ecb2a2b62266e30463deaf03dd65c329b56 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 23 Jul 2025 11:36:45 +0200 Subject: [PATCH 1/5] Use functions to wrap package managers and detect if the aikido commands are available --- src/shell-integration/setup.js | 2 +- .../startup-scripts/init-fish.fish | 85 +++++++++++++++++++ .../supported-shells/fish.js | 18 ++-- .../supported-shells/fish.spec.js | 58 +++++-------- 4 files changed, 118 insertions(+), 45 deletions(-) create mode 100644 src/shell-integration/startup-scripts/init-fish.fish diff --git a/src/shell-integration/setup.js b/src/shell-integration/setup.js index ada658b..792aa3c 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-posix.sh", "init-pwsh.ps1"]; + const startupFiles = ["init-posix.sh", "init-pwsh.ps1", "init-fish.fish"]; for (const file of startupFiles) { const targetDir = path.join(os.homedir(), ".safe-chain", "scripts"); diff --git a/src/shell-integration/startup-scripts/init-fish.fish b/src/shell-integration/startup-scripts/init-fish.fish new file mode 100644 index 0000000..3a6c18e --- /dev/null +++ b/src/shell-integration/startup-scripts/init-fish.fish @@ -0,0 +1,85 @@ +function installIfCommandNotFound + set cmd $argv[1] + + # Check if the command already exists + if type -q $cmd + return 0 + end + + # Check if Node.js version is below 18 + # Safe-chain requires Node.js 18 or higher + set node_version (node -v | sed 's/v//' | cut -d'.' -f1) + if test $node_version -lt 18 + return 2 + end + + # Command not found, ask user if they want to install safe-chain + read -l response -P "The command '$cmd' is not available. Do you want to install safe-chain to provide it? (y/N): " + + if string match -qi 'y*' $response + printf "Installing safe-chain...\n" + installSafeChain + + if test $status -ne 0 + printf "\nFailed to install safe-chain. Exiting.\n" + return 1 + end + + return 0 + else + printf "Skipping safe-chain installation. Using original command instead.\n" + return 2 + end +end + +function installSafeChain + command npm install -g @aikidosec/safe-chain + + if test $status -ne 0 + return 1 + end + + printf "------\n" +end + +function wrapCommand + set original_cmd $argv[1] + set aikido_cmd $argv[2] + set cmd_args $argv[3..-1] + + installIfCommandNotFound $aikido_cmd + set install_result $status + + if test $install_result -eq 2 + command $original_cmd $cmd_args + else + $aikido_cmd $cmd_args + end +end + +function npx + wrapCommand "npx" "aikido-npx" $argv +end + +function yarn + wrapCommand "yarn" "aikido-yarn" $argv +end + +function pnpm + wrapCommand "pnpm" "aikido-pnpm" $argv +end + +function pnpx + wrapCommand "pnpx" "aikido-pnpx" $argv +end + +function npm + if test (count $argv) -eq 1 -a \( "$argv[1]" = "-v" -o "$argv[1]" = "--version" \) + # If args is just -v or --version and nothing else, just run the npm version command + # This is because nvm uses this to check the version of npm + command npm $argv + return + end + + wrapCommand "npm" "aikido-npm" $argv +end \ No newline at end of file diff --git a/src/shell-integration/supported-shells/fish.js b/src/shell-integration/supported-shells/fish.js index fc6fc85..7b2c683 100644 --- a/src/shell-integration/supported-shells/fish.js +++ b/src/shell-integration/supported-shells/fish.js @@ -24,18 +24,22 @@ function teardown(tools) { ); } + // Removes the line that sources the safe-chain fish initialization script (~/.safe-chain/scripts/init-fish.fish) + removeLinesMatchingPattern( + startupFile, + /^source\s+~\/\.safe-chain\/scripts\/init-fish\.fish/ + ); + 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-fish.fish # Safe-chain Fish initialization script` + ); return true; } diff --git a/src/shell-integration/supported-shells/fish.spec.js b/src/shell-integration/supported-shells/fish.spec.js index 5f1ab64..e138957 100644 --- a/src/shell-integration/supported-shells/fish.spec.js +++ b/src/shell-integration/supported-shells/fish.spec.js @@ -66,47 +66,34 @@ describe("Fish 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 = fish.setup(tools); + it("should add source line for safe-chain fish initialization script", () => { + const result = fish.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') - ); - 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') + content.includes('source ~/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script') ); }); - it("should handle empty tools array", () => { - const result = fish.setup([]); - assert.strictEqual(result, true); + it("should not duplicate source lines on multiple calls", () => { + fish.setup(); + fish.setup(); - // 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(), ""); - } + const content = fs.readFileSync(mockStartupFile, "utf-8"); + const sourceMatches = (content.match(/source ~\/\.safe-chain\/scripts\/init-fish\.fish/g) || []).length; + assert.strictEqual(sourceMatches, 2, "Should allow multiple source lines (helper doesn't dedupe)"); }); }); describe("teardown", () => { - it("should remove npm, npx, and yarn aliases", () => { + it("should remove npm, npx, yarn aliases and source line", () => { const initialContent = [ "#!/usr/bin/env fish", "alias npm 'aikido-npm'", "alias npx 'aikido-npx'", "alias yarn 'aikido-yarn'", + "source ~/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script", "alias ls 'ls --color=auto'", "alias grep 'grep --color=auto'", ].join("\n"); @@ -120,6 +107,7 @@ describe("Fish shell integration", () => { assert.ok(!content.includes("alias npm ")); assert.ok(!content.includes("alias npx ")); assert.ok(!content.includes("alias yarn ")); + assert.ok(!content.includes("source ~/.safe-chain/scripts/init-fish.fish")); assert.ok(content.includes("alias ls ")); assert.ok(content.includes("alias grep ")); }); @@ -133,7 +121,7 @@ describe("Fish shell integration", () => { assert.strictEqual(result, true); }); - it("should handle file with no relevant aliases", () => { + it("should handle file with no relevant aliases or source lines", () => { const initialContent = [ "#!/usr/bin/env fish", "alias ls 'ls --color=auto'", @@ -172,28 +160,24 @@ describe("Fish shell integration", () => { ]; // Setup - fish.setup(tools); + fish.setup(); 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-fish.fish')); // Teardown fish.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-fish.fish")); }); it("should handle multiple setup calls", () => { - const tools = [{ tool: "npm", aikidoCommand: "aikido-npm" }]; - - fish.setup(tools); - fish.teardown(tools); - fish.setup(tools); + fish.setup(); + fish.teardown(knownAikidoTools); + fish.setup(); 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 ~\/\.safe-chain\/scripts\/init-fish\.fish/g) || []).length; + assert.strictEqual(sourceMatches, 1, "Should have exactly one source line after setup-teardown-setup cycle"); }); }); }); From 32e2408ad0553fcc3cba5572dae2457699951d43 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 28 Jul 2025 11:00:01 +0200 Subject: [PATCH 2/5] Fish: only show warning instead of auto-installing safe-chain --- .../startup-scripts/init-fish.fish | 77 ++++++------------- 1 file changed, 25 insertions(+), 52 deletions(-) diff --git a/src/shell-integration/startup-scripts/init-fish.fish b/src/shell-integration/startup-scripts/init-fish.fish index 3a6c18e..86d7fcc 100644 --- a/src/shell-integration/startup-scripts/init-fish.fish +++ b/src/shell-integration/startup-scripts/init-fish.fish @@ -1,76 +1,49 @@ -function installIfCommandNotFound - set cmd $argv[1] +function printSafeChainWarning + set original_cmd $argv[1] - # Check if the command already exists - if type -q $cmd - return 0 - end + # Fish equivalent of ANSI color codes: yellow background, black text for "Warning:" + set_color -b yellow black + printf "Warning:" + set_color normal + printf " safe-chain is not available to protect you from installing malware. %s will be run directly.\n" $original_cmd - # Check if Node.js version is below 18 - # Safe-chain requires Node.js 18 or higher - set node_version (node -v | sed 's/v//' | cut -d'.' -f1) - if test $node_version -lt 18 - return 2 - end - - # Command not found, ask user if they want to install safe-chain - read -l response -P "The command '$cmd' is not available. Do you want to install safe-chain to provide it? (y/N): " - - if string match -qi 'y*' $response - printf "Installing safe-chain...\n" - installSafeChain - - if test $status -ne 0 - printf "\nFailed to install safe-chain. Exiting.\n" - return 1 - end - - return 0 - else - printf "Skipping safe-chain installation. Using original command instead.\n" - return 2 - end + # Cyan text for the install command + printf "Install safe-chain by using " + set_color cyan + printf "npm install -g @aikidosec/safe-chain" + set_color normal + printf ".\n" end -function installSafeChain - command npm install -g @aikidosec/safe-chain - - if test $status -ne 0 - return 1 - end - - printf "------\n" -end - -function wrapCommand +function wrapSafeChainCommand set original_cmd $argv[1] set aikido_cmd $argv[2] set cmd_args $argv[3..-1] - installIfCommandNotFound $aikido_cmd - set install_result $status - - if test $install_result -eq 2 - command $original_cmd $cmd_args - else + if type -q $aikido_cmd + # If the aikido command is available, just run it with the provided arguments $aikido_cmd $cmd_args + else + # If the aikido command is not available, print a warning and run the original command + printSafeChainWarning $original_cmd + command $original_cmd $cmd_args end end function npx - wrapCommand "npx" "aikido-npx" $argv + wrapSafeChainCommand "npx" "aikido-npx" $argv end function yarn - wrapCommand "yarn" "aikido-yarn" $argv + wrapSafeChainCommand "yarn" "aikido-yarn" $argv end function pnpm - wrapCommand "pnpm" "aikido-pnpm" $argv + wrapSafeChainCommand "pnpm" "aikido-pnpm" $argv end function pnpx - wrapCommand "pnpx" "aikido-pnpx" $argv + wrapSafeChainCommand "pnpx" "aikido-pnpx" $argv end function npm @@ -81,5 +54,5 @@ function npm return end - wrapCommand "npm" "aikido-npm" $argv + wrapSafeChainCommand "npm" "aikido-npm" $argv end \ No newline at end of file From d6d80d8f03599343adc0e14f29883df55587695f Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Thu, 31 Jul 2025 14:26:14 +0000 Subject: [PATCH 3/5] Update src/shell-integration/startup-scripts/init-fish.fish --- src/shell-integration/startup-scripts/init-fish.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/startup-scripts/init-fish.fish b/src/shell-integration/startup-scripts/init-fish.fish index 86d7fcc..b6de588 100644 --- a/src/shell-integration/startup-scripts/init-fish.fish +++ b/src/shell-integration/startup-scripts/init-fish.fish @@ -55,4 +55,4 @@ function npm end wrapSafeChainCommand "npm" "aikido-npm" $argv -end \ No newline at end of file +end From 432769bca068fcae5908f9b6d5a6cb4d7d68f0c2 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 4 Aug 2025 10:20:44 +0200 Subject: [PATCH 4/5] Change text as suggested --- src/shell-integration/startup-scripts/init-pwsh.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/startup-scripts/init-pwsh.ps1 b/src/shell-integration/startup-scripts/init-pwsh.ps1 index 16acb92..7fb44d6 100644 --- a/src/shell-integration/startup-scripts/init-pwsh.ps1 +++ b/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -3,7 +3,7 @@ function Write-SafeChainWarning { # PowerShell equivalent of ANSI color codes: yellow background, black text for "Warning:" Write-Host "Warning:" -BackgroundColor Yellow -ForegroundColor Black -NoNewline - Write-Host " safe-chain is not available to protect you from installing malware. $Command will be run directly." + Write-Host " safe-chain is not available to protect you from installing malware. $Command will run without it." # Cyan text for the install command Write-Host "Install safe-chain by using " -NoNewline From c5b9722a4024f23aec9da1e47164680ae8d058c1 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 4 Aug 2025 10:22:33 +0200 Subject: [PATCH 5/5] Change text as suggested --- src/shell-integration/startup-scripts/init-fish.fish | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shell-integration/startup-scripts/init-fish.fish b/src/shell-integration/startup-scripts/init-fish.fish index b6de588..0190883 100644 --- a/src/shell-integration/startup-scripts/init-fish.fish +++ b/src/shell-integration/startup-scripts/init-fish.fish @@ -5,7 +5,7 @@ function printSafeChainWarning set_color -b yellow black printf "Warning:" set_color normal - printf " safe-chain is not available to protect you from installing malware. %s will be run directly.\n" $original_cmd + printf " safe-chain is not available to protect you from installing malware. %s will run without it.\n" $original_cmd # Cyan text for the install command printf "Install safe-chain by using "