From 577b09bd39c3882da1e5b1fba499dc4f4bcb65e8 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 23 Jul 2025 11:16:38 +0200 Subject: [PATCH 1/4] Use powershell functions to wrap npm, npx, yarn, pnpm and pnpx --- src/shell-integration/setup.js | 2 +- .../startup-scripts/init-pwsh.ps1 | 122 ++++++++++++++++++ .../supported-shells/powershell.js | 18 ++- .../supported-shells/powershell.spec.js | 87 ++++++------- .../supported-shells/windowsPowershell.js | 18 ++- .../windowsPowershell.spec.js | 87 ++++++------- 6 files changed, 217 insertions(+), 117 deletions(-) create mode 100644 src/shell-integration/startup-scripts/init-pwsh.ps1 diff --git a/src/shell-integration/setup.js b/src/shell-integration/setup.js index 999f3d2..4cc2968 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"]; + const startupFiles = ["init-posix.sh", "init-pwsh.ps1"]; for (const file of startupFiles) { const targetPath = path.join(os.homedir(), ".safe-chain", "scripts", file); diff --git a/src/shell-integration/startup-scripts/init-pwsh.ps1 b/src/shell-integration/startup-scripts/init-pwsh.ps1 new file mode 100644 index 0000000..58a19c9 --- /dev/null +++ b/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -0,0 +1,122 @@ +function Test-CommandAvailable { + param([string]$Command) + + try { + Get-Command $Command -ErrorAction Stop | Out-Null + return $true + } catch { + return $false + } +} + +function Invoke-RealCommand { + param( + [string]$Command, + [string[]]$Arguments + ) + + # Find the real executable to avoid calling our wrapped functions + $realCommand = Get-Command -Name $Command -CommandType Application | Select-Object -First 1 + if ($realCommand) { + & $realCommand.Source @Arguments + } else { + # Fallback: try to call the .cmd version directly + & "$Command.cmd" @Arguments + } +} + +function Install-IfCommandNotFound { + param([string]$Command) + + # Check if the command already exists + if (Test-CommandAvailable $Command) { + return 0 + } + + # Check if Node.js version is below 18 + # Safe-chain requires Node.js 18 or higher + try { + $nodeVersion = (node -v) -replace 'v', '' | ForEach-Object { $_.Split('.')[0] } + if ([int]$nodeVersion -lt 18) { + return 2 + } + } catch { + return 2 + } + + # Command not found, ask user if they want to install safe-chain + $response = Read-Host "The command '$Command' is not available. Do you want to install safe-chain to provide it? (y/N)" + + if ($response -match '^[Yy]$') { + Write-Host "Installing safe-chain..." + $installResult = Install-SafeChain + + if ($installResult -ne 0) { + Write-Host "`nFailed to install safe-chain. Exiting." + return 1 + } + + return 0 + } else { + Write-Host "Skipping safe-chain installation. Using original command instead." + return 2 + } +} + +function Install-SafeChain { + try { + Invoke-RealCommand "npm" @("install", "-g", "@aikidosec/safe-chain") | Out-Null + + if ($LASTEXITCODE -ne 0) { + return 1 + } + + Write-Host "------" + return 0 + } catch { + return 1 + } +} + +function Invoke-WrappedCommand { + param( + [string]$OriginalCmd, + [string]$AikidoCmd, + [string[]]$Arguments + ) + + $installResult = Install-IfCommandNotFound $AikidoCmd + + if ($installResult -eq 2) { + Invoke-RealCommand $OriginalCmd $Arguments + } else { + & $AikidoCmd @Arguments + } +} + +function npx { + Invoke-WrappedCommand "npx" "aikido-npx" $args +} + +function yarn { + Invoke-WrappedCommand "yarn" "aikido-yarn" $args +} + +function pnpm { + Invoke-WrappedCommand "pnpm" "aikido-pnpm" $args +} + +function pnpx { + Invoke-WrappedCommand "pnpx" "aikido-pnpx" $args +} + +function npm { + # 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 + if (($args.Length -eq 1) -and (($args[0] -eq "-v") -or ($args[0] -eq "--version"))) { + Invoke-RealCommand "npm" $args + return + } + + Invoke-WrappedCommand "npm" "aikido-npm" $args +} \ No newline at end of file diff --git a/src/shell-integration/supported-shells/powershell.js b/src/shell-integration/supported-shells/powershell.js index 4690bb6..47524c2 100644 --- a/src/shell-integration/supported-shells/powershell.js +++ b/src/shell-integration/supported-shells/powershell.js @@ -24,18 +24,22 @@ function teardown(tools) { ); } + // Remove the line that sources the safe-chain PowerShell initialization script + removeLinesMatchingPattern( + startupFile, + /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/ + ); + return true; } -function setup(tools) { +function setup() { const startupFile = getStartupFile(); - for (const { tool, aikidoCommand } of tools) { - addLineToFile( - startupFile, - `Set-Alias ${tool} ${aikidoCommand} # Safe-chain alias for ${tool}` - ); - } + addLineToFile( + startupFile, + `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script` + ); return true; } diff --git a/src/shell-integration/supported-shells/powershell.spec.js b/src/shell-integration/supported-shells/powershell.spec.js index 9afade7..57d5098 100644 --- a/src/shell-integration/supported-shells/powershell.spec.js +++ b/src/shell-integration/supported-shells/powershell.spec.js @@ -69,49 +69,43 @@ describe("PowerShell Core 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 = powershell.setup(tools); + it("should add init-pwsh.ps1 source line", () => { + const result = powershell.setup(); assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - content.includes("Set-Alias npm aikido-npm # Safe-chain alias for npm") + content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script") ); - assert.ok( - content.includes("Set-Alias npx aikido-npx # Safe-chain alias for npx") - ); - assert.ok( - content.includes( - "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn" - ) - ); - }); - - it("should handle empty tools array", () => { - const result = powershell.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(), ""); - } }); }); describe("teardown", () => { - it("should remove npm, npx, and yarn aliases", () => { + it("should remove init-pwsh.ps1 source line", () => { const initialContent = [ "# PowerShell profile", - "Set-Alias npm aikido-npm", - "Set-Alias npx aikido-npx", - "Set-Alias yarn aikido-yarn", + ". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script", + "Set-Alias ls Get-ChildItem", + "Set-Alias grep Select-String", + ].join("\n"); + + fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); + + const result = powershell.teardown(knownAikidoTools); + assert.strictEqual(result, true); + + const content = fs.readFileSync(mockStartupFile, "utf-8"); + assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok(content.includes("Set-Alias ls ")); + assert.ok(content.includes("Set-Alias grep ")); + }); + + it("should remove old-style aliases from earlier versions", () => { + const initialContent = [ + "# PowerShell profile", + "Set-Alias npm aikido-npm # Safe-chain alias for npm", + "Set-Alias npx aikido-npx # Safe-chain alias for npx", + "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn", "Set-Alias ls Get-ChildItem", "Set-Alias grep Select-String", ].join("\n"); @@ -138,7 +132,7 @@ describe("PowerShell Core shell integration", () => { assert.strictEqual(result, true); }); - it("should handle file with no relevant aliases", () => { + it("should handle file with no relevant content", () => { const initialContent = [ "# PowerShell profile", "Set-Alias ls Get-ChildItem", @@ -171,34 +165,25 @@ describe("PowerShell Core shell integration", () => { describe("integration tests", () => { it("should handle complete setup and teardown cycle", () => { - const tools = [ - { tool: "npm", aikidoCommand: "aikido-npm" }, - { tool: "yarn", aikidoCommand: "aikido-yarn" }, - ]; - // Setup - powershell.setup(tools); + powershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(content.includes("Set-Alias npm aikido-npm")); - assert.ok(content.includes("Set-Alias yarn aikido-yarn")); + assert.ok(content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); // Teardown - powershell.teardown(tools); + powershell.teardown(knownAikidoTools); content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("Set-Alias npm ")); - assert.ok(!content.includes("Set-Alias yarn ")); + assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); }); it("should handle multiple setup calls", () => { - const tools = [{ tool: "npm", aikidoCommand: "aikido-npm" }]; - - powershell.setup(tools); - powershell.teardown(tools); - powershell.setup(tools); + powershell.setup(); + powershell.teardown(knownAikidoTools); + powershell.setup(); const content = fs.readFileSync(mockStartupFile, "utf-8"); - const npmMatches = (content.match(/Set-Alias npm /g) || []).length; - assert.strictEqual(npmMatches, 1, "Should not duplicate aliases"); + const sourceMatches = (content.match(/\. "\$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh\.ps1"/g) || []).length; + assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); }); }); }); diff --git a/src/shell-integration/supported-shells/windowsPowershell.js b/src/shell-integration/supported-shells/windowsPowershell.js index 118a0b9..03ff7f8 100644 --- a/src/shell-integration/supported-shells/windowsPowershell.js +++ b/src/shell-integration/supported-shells/windowsPowershell.js @@ -24,18 +24,22 @@ function teardown(tools) { ); } + // Remove the line that sources the safe-chain PowerShell initialization script + removeLinesMatchingPattern( + startupFile, + /^\.\s+["']?\$HOME[/\\].safe-chain[/\\]scripts[/\\]init-pwsh\.ps1["']?/ + ); + return true; } -function setup(tools) { +function setup() { const startupFile = getStartupFile(); - for (const { tool, aikidoCommand } of tools) { - addLineToFile( - startupFile, - `Set-Alias ${tool} ${aikidoCommand} # Safe-chain alias for ${tool}` - ); - } + addLineToFile( + startupFile, + `. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script` + ); return true; } diff --git a/src/shell-integration/supported-shells/windowsPowershell.spec.js b/src/shell-integration/supported-shells/windowsPowershell.spec.js index 85da9f1..96677a8 100644 --- a/src/shell-integration/supported-shells/windowsPowershell.spec.js +++ b/src/shell-integration/supported-shells/windowsPowershell.spec.js @@ -69,49 +69,43 @@ describe("Windows PowerShell 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 = windowsPowershell.setup(tools); + it("should add init-pwsh.ps1 source line", () => { + const result = windowsPowershell.setup(); assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - content.includes("Set-Alias npm aikido-npm # Safe-chain alias for npm") + content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script") ); - assert.ok( - content.includes("Set-Alias npx aikido-npx # Safe-chain alias for npx") - ); - assert.ok( - content.includes( - "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn" - ) - ); - }); - - it("should handle empty tools array", () => { - const result = windowsPowershell.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(), ""); - } }); }); describe("teardown", () => { - it("should remove npm, npx, and yarn aliases", () => { + it("should remove init-pwsh.ps1 source line", () => { const initialContent = [ "# Windows PowerShell profile", - "Set-Alias npm aikido-npm", - "Set-Alias npx aikido-npx", - "Set-Alias yarn aikido-yarn", + ". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script", + "Set-Alias ls Get-ChildItem", + "Set-Alias grep Select-String", + ].join("\n"); + + fs.writeFileSync(mockStartupFile, initialContent, "utf-8"); + + const result = windowsPowershell.teardown(knownAikidoTools); + assert.strictEqual(result, true); + + const content = fs.readFileSync(mockStartupFile, "utf-8"); + assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok(content.includes("Set-Alias ls ")); + assert.ok(content.includes("Set-Alias grep ")); + }); + + it("should remove old-style aliases from earlier versions", () => { + const initialContent = [ + "# Windows PowerShell profile", + "Set-Alias npm aikido-npm # Safe-chain alias for npm", + "Set-Alias npx aikido-npx # Safe-chain alias for npx", + "Set-Alias yarn aikido-yarn # Safe-chain alias for yarn", "Set-Alias ls Get-ChildItem", "Set-Alias grep Select-String", ].join("\n"); @@ -138,7 +132,7 @@ describe("Windows PowerShell shell integration", () => { assert.strictEqual(result, true); }); - it("should handle file with no relevant aliases", () => { + it("should handle file with no relevant content", () => { const initialContent = [ "# Windows PowerShell profile", "Set-Alias ls Get-ChildItem", @@ -171,34 +165,25 @@ describe("Windows PowerShell shell integration", () => { describe("integration tests", () => { it("should handle complete setup and teardown cycle", () => { - const tools = [ - { tool: "npm", aikidoCommand: "aikido-npm" }, - { tool: "yarn", aikidoCommand: "aikido-yarn" }, - ]; - // Setup - windowsPowershell.setup(tools); + windowsPowershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(content.includes("Set-Alias npm aikido-npm")); - assert.ok(content.includes("Set-Alias yarn aikido-yarn")); + assert.ok(content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); // Teardown - windowsPowershell.teardown(tools); + windowsPowershell.teardown(knownAikidoTools); content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes("Set-Alias npm ")); - assert.ok(!content.includes("Set-Alias yarn ")); + assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); }); it("should handle multiple setup calls", () => { - const tools = [{ tool: "npm", aikidoCommand: "aikido-npm" }]; - - windowsPowershell.setup(tools); - windowsPowershell.teardown(tools); - windowsPowershell.setup(tools); + windowsPowershell.setup(); + windowsPowershell.teardown(knownAikidoTools); + windowsPowershell.setup(); const content = fs.readFileSync(mockStartupFile, "utf-8"); - const npmMatches = (content.match(/Set-Alias npm /g) || []).length; - assert.strictEqual(npmMatches, 1, "Should not duplicate aliases"); + const sourceMatches = (content.match(/\. "\$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh\.ps1"/g) || []).length; + assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); }); }); }); From 5df4671988f116f602e5eb089b2fde00dee97712 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Wed, 23 Jul 2025 11:43:13 +0200 Subject: [PATCH 2/4] Fix paths in tests --- .../supported-shells/powershell.spec.js | 23 ++++++++++++++----- .../windowsPowershell.spec.js | 23 ++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/shell-integration/supported-shells/powershell.spec.js b/src/shell-integration/supported-shells/powershell.spec.js index 57d5098..3a15376 100644 --- a/src/shell-integration/supported-shells/powershell.spec.js +++ b/src/shell-integration/supported-shells/powershell.spec.js @@ -75,7 +75,9 @@ describe("PowerShell Core shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script") + content.includes( + '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script' + ) ); }); }); @@ -84,7 +86,7 @@ describe("PowerShell Core shell integration", () => { it("should remove init-pwsh.ps1 source line", () => { const initialContent = [ "# PowerShell profile", - ". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script", + '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script', "Set-Alias ls Get-ChildItem", "Set-Alias grep Select-String", ].join("\n"); @@ -95,7 +97,9 @@ describe("PowerShell Core shell integration", () => { assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok( + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + ); assert.ok(content.includes("Set-Alias ls ")); assert.ok(content.includes("Set-Alias grep ")); }); @@ -168,12 +172,16 @@ describe("PowerShell Core shell integration", () => { // Setup powershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok( + content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + ); // Teardown powershell.teardown(knownAikidoTools); content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok( + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + ); }); it("should handle multiple setup calls", () => { @@ -182,7 +190,10 @@ describe("PowerShell Core shell integration", () => { powershell.setup(); const content = fs.readFileSync(mockStartupFile, "utf-8"); - const sourceMatches = (content.match(/\. "\$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh\.ps1"/g) || []).length; + const sourceMatches = ( + content.match(/\. "\$HOME\\.safe-chain\\scripts\\init-pwsh\.ps1"/g) || + [] + ).length; assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); }); }); diff --git a/src/shell-integration/supported-shells/windowsPowershell.spec.js b/src/shell-integration/supported-shells/windowsPowershell.spec.js index 96677a8..c201c60 100644 --- a/src/shell-integration/supported-shells/windowsPowershell.spec.js +++ b/src/shell-integration/supported-shells/windowsPowershell.spec.js @@ -75,7 +75,9 @@ describe("Windows PowerShell shell integration", () => { const content = fs.readFileSync(mockStartupFile, "utf-8"); assert.ok( - content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script") + content.includes( + '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script' + ) ); }); }); @@ -84,7 +86,7 @@ describe("Windows PowerShell shell integration", () => { it("should remove init-pwsh.ps1 source line", () => { const initialContent = [ "# Windows PowerShell profile", - ". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\" # Safe-chain PowerShell initialization script", + '. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1" # Safe-chain PowerShell initialization script', "Set-Alias ls Get-ChildItem", "Set-Alias grep Select-String", ].join("\n"); @@ -95,7 +97,9 @@ describe("Windows PowerShell shell integration", () => { assert.strictEqual(result, true); const content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok( + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + ); assert.ok(content.includes("Set-Alias ls ")); assert.ok(content.includes("Set-Alias grep ")); }); @@ -168,12 +172,16 @@ describe("Windows PowerShell shell integration", () => { // Setup windowsPowershell.setup(); let content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok( + content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + ); // Teardown windowsPowershell.teardown(knownAikidoTools); content = fs.readFileSync(mockStartupFile, "utf-8"); - assert.ok(!content.includes(". \"$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh.ps1\"")); + assert.ok( + !content.includes('. "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"') + ); }); it("should handle multiple setup calls", () => { @@ -182,7 +190,10 @@ describe("Windows PowerShell shell integration", () => { windowsPowershell.setup(); const content = fs.readFileSync(mockStartupFile, "utf-8"); - const sourceMatches = (content.match(/\. "\$HOME\\\\.safe-chain\\\\scripts\\\\init-pwsh\.ps1"/g) || []).length; + const sourceMatches = ( + content.match(/\. "\$HOME\\.safe-chain\\scripts\\init-pwsh\.ps1"/g) || + [] + ).length; assert.strictEqual(sourceMatches, 1, "Should not duplicate source lines"); }); }); From 0e6d96fe1d7c65488cfad2f95e143ade24eb4da8 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 28 Jul 2025 10:37:36 +0200 Subject: [PATCH 3/4] Powershell: only print warning when safe-chain is unavailable --- .../startup-scripts/init-pwsh.ps1 | 84 +++++-------------- 1 file changed, 21 insertions(+), 63 deletions(-) diff --git a/src/shell-integration/startup-scripts/init-pwsh.ps1 b/src/shell-integration/startup-scripts/init-pwsh.ps1 index 58a19c9..7d85ee1 100644 --- a/src/shell-integration/startup-scripts/init-pwsh.ps1 +++ b/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -1,10 +1,24 @@ +function Write-SafeChainWarning { + param([string]$Command) + + # 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." + + # Cyan text for the install command + Write-Host "Install safe-chain by using " -NoNewline + Write-Host "npm install -g @aikidosec/safe-chain" -ForegroundColor Cyan -NoNewline + Write-Host "." +} + function Test-CommandAvailable { param([string]$Command) try { Get-Command $Command -ErrorAction Stop | Out-Null return $true - } catch { + } + catch { return $false } } @@ -19,62 +33,6 @@ function Invoke-RealCommand { $realCommand = Get-Command -Name $Command -CommandType Application | Select-Object -First 1 if ($realCommand) { & $realCommand.Source @Arguments - } else { - # Fallback: try to call the .cmd version directly - & "$Command.cmd" @Arguments - } -} - -function Install-IfCommandNotFound { - param([string]$Command) - - # Check if the command already exists - if (Test-CommandAvailable $Command) { - return 0 - } - - # Check if Node.js version is below 18 - # Safe-chain requires Node.js 18 or higher - try { - $nodeVersion = (node -v) -replace 'v', '' | ForEach-Object { $_.Split('.')[0] } - if ([int]$nodeVersion -lt 18) { - return 2 - } - } catch { - return 2 - } - - # Command not found, ask user if they want to install safe-chain - $response = Read-Host "The command '$Command' is not available. Do you want to install safe-chain to provide it? (y/N)" - - if ($response -match '^[Yy]$') { - Write-Host "Installing safe-chain..." - $installResult = Install-SafeChain - - if ($installResult -ne 0) { - Write-Host "`nFailed to install safe-chain. Exiting." - return 1 - } - - return 0 - } else { - Write-Host "Skipping safe-chain installation. Using original command instead." - return 2 - } -} - -function Install-SafeChain { - try { - Invoke-RealCommand "npm" @("install", "-g", "@aikidosec/safe-chain") | Out-Null - - if ($LASTEXITCODE -ne 0) { - return 1 - } - - Write-Host "------" - return 0 - } catch { - return 1 } } @@ -84,14 +42,14 @@ function Invoke-WrappedCommand { [string]$AikidoCmd, [string[]]$Arguments ) - - $installResult = Install-IfCommandNotFound $AikidoCmd - - if ($installResult -eq 2) { - Invoke-RealCommand $OriginalCmd $Arguments - } else { + + if (Test-CommandAvailable $AikidoCmd) { & $AikidoCmd @Arguments } + else { + Write-SafeChainWarning $OriginalCmd + Invoke-RealCommand $OriginalCmd $Arguments + } } function npx { From 2a7e2e3ed8d6c8f502463d9adba6dfd009730088 Mon Sep 17 00:00:00 2001 From: bitterpanda Date: Thu, 31 Jul 2025 14:26:29 +0000 Subject: [PATCH 4/4] Update src/shell-integration/startup-scripts/init-pwsh.ps1 --- 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 7d85ee1..16acb92 100644 --- a/src/shell-integration/startup-scripts/init-pwsh.ps1 +++ b/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -77,4 +77,4 @@ function npm { } Invoke-WrappedCommand "npm" "aikido-npm" $args -} \ No newline at end of file +}