diff --git a/package-lock.json b/package-lock.json index ee38fa8..a9c32df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2096,6 +2096,8 @@ "aikido-npx": "bin/aikido-npx.js", "aikido-pip": "bin/aikido-pip.js", "aikido-pip3": "bin/aikido-pip3.js", + "aikido-python": "bin/aikido-python.js", + "aikido-python3": "bin/aikido-python3.js", "aikido-pnpm": "bin/aikido-pnpm.js", "aikido-pnpx": "bin/aikido-pnpx.js", "aikido-yarn": "bin/aikido-yarn.js", diff --git a/packages/safe-chain/bin/aikido-python.js b/packages/safe-chain/bin/aikido-python.js new file mode 100644 index 0000000..c22c601 --- /dev/null +++ b/packages/safe-chain/bin/aikido-python.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + + +import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js"; +import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js"; +import { main } from "../src/main.js"; + +const argv = process.argv.slice(2); + +const supportedArgs = ["pip", "pip3"]; + +if (argv[0] === "-m" && argv[1] && supportedArgs.includes(argv[1])) { + setEcoSystem(ECOSYSTEM_PY); + + initializePackageManager(argv[1]); + var exitCode = await main(argv.slice(2)); + process.exit(exitCode); +} else { + // Fallback: run the real python + const { spawn } = await import("child_process"); + spawn("python", argv, { stdio: "inherit" }); +} diff --git a/packages/safe-chain/bin/aikido-python3.js b/packages/safe-chain/bin/aikido-python3.js new file mode 100644 index 0000000..48659e5 --- /dev/null +++ b/packages/safe-chain/bin/aikido-python3.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + + +import { initializePackageManager } from "../src/packagemanager/currentPackageManager.js"; +import { setEcoSystem, ECOSYSTEM_PY } from "../src/config/settings.js"; +import { main } from "../src/main.js"; + +const argv = process.argv.slice(2); + +const supportedArgs = ["pip", "pip3"]; + +if (argv[0] === "-m" && argv[1] && supportedArgs.includes(argv[1])) { + setEcoSystem(ECOSYSTEM_PY); + // python3 -m pip or python3 -m pip3: always use pip3 package manager + initializePackageManager("pip3"); + var exitCode = await main(argv.slice(2)); + process.exit(exitCode); +} else { + // Fallback: run the real python3 + const { spawn } = await import("child_process"); + spawn("python3", argv, { stdio: "inherit" }); +} diff --git a/packages/safe-chain/package.json b/packages/safe-chain/package.json index d93a058..f21a372 100644 --- a/packages/safe-chain/package.json +++ b/packages/safe-chain/package.json @@ -17,6 +17,8 @@ "aikido-bunx": "bin/aikido-bunx.js", "aikido-pip": "bin/aikido-pip.js", "aikido-pip3": "bin/aikido-pip3.js", + "aikido-python": "bin/aikido-python.js", + "aikido-python3": "bin/aikido-python3.js", "safe-chain": "bin/safe-chain.js" }, "type": "module", diff --git a/packages/safe-chain/src/shell-integration/helpers.js b/packages/safe-chain/src/shell-integration/helpers.js index 4ba7c24..c405c54 100644 --- a/packages/safe-chain/src/shell-integration/helpers.js +++ b/packages/safe-chain/src/shell-integration/helpers.js @@ -22,6 +22,8 @@ export const knownAikidoTools = [ { tool: "bunx", aikidoCommand: "aikido-bunx" }, { tool: "pip", aikidoCommand: "aikido-pip" }, { tool: "pip3", aikidoCommand: "aikido-pip3" }, + { tool: "python", aikidoCommand: "aikido-python" }, + { tool: "python3", aikidoCommand: "aikido-python3" }, // When adding a new tool here, also update the documentation for the new tool in the README.md ]; diff --git a/packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-python-wrapper.template.sh b/packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-python-wrapper.template.sh deleted file mode 100644 index c4edf2a..0000000 --- a/packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-python-wrapper.template.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh -# Generated wrapper for python/python3 by safe-chain -# Intercepts `python[3] -m pip[...]` in CI environments - -# Function to remove shim from PATH (POSIX-compliant) -remove_shim_from_path() { - echo "$PATH" | sed "s|$HOME/.safe-chain/shims:||g" -} - -# Determine which python variant we were invoked as based on the script name -invoked=$(basename "$0") - -# If invoked as `python -m pip[...]` or `python3 -m pip[...]`, route to aikido -if [ "$1" = "-m" ] && [ -n "$2" ] && echo "$2" | grep -Eq '^pip(3)?$'; then - mod="$2" - shift 2 - if [ "$invoked" = "python3" ] || [ "$mod" = "pip3" ]; then - PATH=$(remove_shim_from_path) exec aikido-pip3 "$@" - else - PATH=$(remove_shim_from_path) exec aikido-pip "$@" - fi -fi - -# Otherwise, find and exec the real python/python3 matching the invoked name -original_cmd=$(PATH=$(remove_shim_from_path) command -v "$invoked") -if [ -n "$original_cmd" ]; then - exec "$original_cmd" "$@" -else - echo "Error: Could not find original $invoked" >&2 - exit 1 -fi diff --git a/packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh b/packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh index 6e6d826..7663006 100644 --- a/packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh +++ b/packages/safe-chain/src/shell-integration/path-wrappers/templates/unix-wrapper.template.sh @@ -7,6 +7,8 @@ remove_shim_from_path() { echo "$PATH" | sed "s|$HOME/.safe-chain/shims:||g" } +echo "[safe-chain debug] command -v {{AIKIDO_COMMAND}} (raw PATH): $(command -v {{AIKIDO_COMMAND}} 2>/dev/null || echo notfound)" >&2 +echo "[safe-chain debug] PATH (raw): $PATH" >&2 if command -v {{AIKIDO_COMMAND}} >/dev/null 2>&1; then # Remove shim directory from PATH when calling {{AIKIDO_COMMAND}} to prevent infinite loops PATH=$(remove_shim_from_path) exec {{AIKIDO_COMMAND}} "$@" @@ -19,4 +21,4 @@ else echo "Error: Could not find original {{PACKAGE_MANAGER}}" >&2 exit 1 fi -fi \ No newline at end of file +fi diff --git a/packages/safe-chain/src/shell-integration/path-wrappers/templates/windows-python-wrapper.template.cmd b/packages/safe-chain/src/shell-integration/path-wrappers/templates/windows-python-wrapper.template.cmd deleted file mode 100644 index 5b4ddd9..0000000 --- a/packages/safe-chain/src/shell-integration/path-wrappers/templates/windows-python-wrapper.template.cmd +++ /dev/null @@ -1,44 +0,0 @@ -@echo off -REM Generated wrapper for python/python3 by safe-chain -REM Intercepts `python[3] -m pip[...]` in CI environments - -REM Remove shim directory from PATH to prevent infinite loops -set "SHIM_DIR=%USERPROFILE%\.safe-chain\shims" -call set "CLEAN_PATH=%%PATH:%SHIM_DIR%;=%%" - -REM Determine invoked name (python or python3) from the script name -set "INVOKED=%~n0" - -REM Check for -m pip or -m pip3 without parentheses to avoid parser issues -if /I "%1" NEQ "-m" goto FALLBACK - -set "SECOND=%2" -if /I "%SECOND%"=="pip3" goto CALL_PIP3 -if /I "%SECOND%"=="pip" goto CALL_PIP -goto FALLBACK - -:CALL_PIP3 -shift -shift -set "PATH=%CLEAN_PATH%" & aikido-pip3 %1 %2 %3 %4 %5 %6 %7 %8 %9 -goto :eof - -:CALL_PIP -shift -shift -if /I "%INVOKED%"=="python3" ( - set "PATH=%CLEAN_PATH%" & aikido-pip3 %1 %2 %3 %4 %5 %6 %7 %8 %9 -) else ( - set "PATH=%CLEAN_PATH%" & aikido-pip %1 %2 %3 %4 %5 %6 %7 %8 %9 -) -goto :eof - -REM Fallback to real python/python3 matching the invoked name -:FALLBACK -for /f "tokens=*" %%i in ('set "PATH=%CLEAN_PATH%" ^& where %INVOKED% 2^>nul') do ( - "%%i" %* - goto :eof -) - -echo Error: Could not find original %INVOKED% 1>&2 -exit /b 1 diff --git a/packages/safe-chain/src/shell-integration/setup-ci.js b/packages/safe-chain/src/shell-integration/setup-ci.js index b061bb6..926386d 100644 --- a/packages/safe-chain/src/shell-integration/setup-ci.js +++ b/packages/safe-chain/src/shell-integration/setup-ci.js @@ -66,53 +66,12 @@ function createUnixShims(shimsDir) { created++; } - // Also create python and python3 shims to support `python[3] -m pip[3]` in CI - createUnixPythonShims(shimsDir); - ui.writeInformation( `Created ${created} Unix shim(s) in ${shimsDir}` ); } -/** - * @param {string} shimsDir - */ -function createUnixPythonShims(shimsDir) { - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - const entries = [ - { - name: "python", - template: path.resolve( - __dirname, - "path-wrappers", - "templates", - "unix-python-wrapper.template.sh" - ), - }, - { - name: "python3", - template: path.resolve( - __dirname, - "path-wrappers", - "templates", - "unix-python-wrapper.template.sh" - ), - }, - ]; - - for (const entry of entries) { - if (!fs.existsSync(entry.template)) { - ui.writeError(`Template file not found: ${entry.template}`); - continue; - } - const shimContent = fs.readFileSync(entry.template, "utf-8"); - const shimPath = `${shimsDir}/${entry.name}`; - fs.writeFileSync(shimPath, shimContent, "utf-8"); - fs.chmodSync(shimPath, 0o755); - } -} /** * @param {string} shimsDir @@ -149,53 +108,11 @@ function createWindowsShims(shimsDir) { created++; } - // Also create python and python3 shims for Windows to support `python[3] -m pip[3]` in CI - createWindowsPythonShims(shimsDir); - ui.writeInformation( `Created ${created} Windows shim(s) in ${shimsDir}` ); } -/** - * @param {string} shimsDir - */ -function createWindowsPythonShims(shimsDir) { - const __filename = fileURLToPath(import.meta.url); - const __dirname = path.dirname(__filename); - - const entries = [ - { - name: "python.cmd", - template: path.resolve( - __dirname, - "path-wrappers", - "templates", - "windows-python-wrapper.template.cmd" - ), - }, - { - name: "python3.cmd", - template: path.resolve( - __dirname, - "path-wrappers", - "templates", - "windows-python-wrapper.template.cmd" - ), - }, - ]; - - for (const entry of entries) { - if (!fs.existsSync(entry.template)) { - ui.writeError(`Windows template file not found: ${entry.template}`); - continue; - } - const shimContent = fs.readFileSync(entry.template, "utf-8"); - const shimPath = `${shimsDir}/${entry.name}`; - fs.writeFileSync(shimPath, shimContent, "utf-8"); - } -} - /** * @param {string} shimsDir * diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish b/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish index 13494d1..ebf89ff 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-fish.fish @@ -79,26 +79,10 @@ end # `python -m pip`, `python -m pip3`. function python - if test (count $argv) -ge 2; and test $argv[1] = "-m"; and string match -qr '^pip(3)?$' -- $argv[2] - set mod $argv[2] - set args $argv[3..-1] - if test $mod = "pip3" - wrapSafeChainCommand "pip3" "aikido-pip3" $args - else - wrapSafeChainCommand "pip" "aikido-pip" $args - end - else - command python $argv - end + wrapSafeChainCommand "python" "aikido-python" $argv end # `python3 -m pip`, `python3 -m pip3'. function python3 - if test (count $argv) -ge 2; and test $argv[1] = "-m"; and string match -qr '^pip(3)?$' -- $argv[2] - set args $argv[3..-1] - # python3 always uses pip3, regardless of whether user types `pip` or `pip3` - wrapSafeChainCommand "pip3" "aikido-pip3" $args - else - command python3 $argv - end + wrapSafeChainCommand "python3" "aikido-python3" $argv end diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh b/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh index 05b8b81..278b31a 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-posix.sh @@ -71,26 +71,10 @@ function pip3() { # `python -m pip`, `python -m pip3`. function python() { - if [[ "$1" == "-m" && "$2" == pip* ]]; then - local mod="$2" - shift 2 - if [[ "$mod" == "pip3" ]]; then - wrapSafeChainCommand "pip3" "aikido-pip3" "$@" - else - wrapSafeChainCommand "pip" "aikido-pip" "$@" - fi - else - command python "$@" - fi + wrapSafeChainCommand "python" "aikido-python" "$@" } # `python3 -m pip`, `python3 -m pip3'. function python3() { - if [[ "$1" == "-m" && "$2" == pip* ]]; then - shift 2 - # python3 always uses pip3, regardless of whether user types `pip` or `pip3` - wrapSafeChainCommand "pip3" "aikido-pip3" "$@" - else - command python3 "$@" - fi + wrapSafeChainCommand "python3" "aikido-python3" "$@" } diff --git a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 index 6425f2f..b692107 100644 --- a/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 +++ b/packages/safe-chain/src/shell-integration/startup-scripts/init-pwsh.ps1 @@ -97,27 +97,11 @@ function pip3 { # `python -m pip`, `python -m pip3`. function python { - param([Parameter(ValueFromRemainingArguments=$true)]$Args) - if ($Args.Length -ge 2 -and $Args[0] -eq '-m' -and $Args[1] -match '^pip(3)?$') { - $pipArgs = if ($Args.Length -gt 2) { $Args | Select-Object -Skip 2 } else { @() } - if ($Args[1] -eq 'pip3') { Invoke-WrappedCommand 'pip3' 'aikido-pip3' $pipArgs } - else { Invoke-WrappedCommand 'pip' 'aikido-pip' $pipArgs } - } - else { - Invoke-RealCommand 'python' $Args - } + Invoke-WrappedCommand 'python' 'aikido-python' $args } # `python3 -m pip`, `python3 -m pip3'. function python3 { - param([Parameter(ValueFromRemainingArguments=$true)]$Args) - if ($Args.Length -ge 2 -and $Args[0] -eq '-m' -and $Args[1] -match '^pip(3)?$') { - # python3 always uses pip3, regardless of whether user types `pip` or `pip3` - $pipArgs = if ($Args.Length -gt 2) { $Args | Select-Object -Skip 2 } else { @() } - Invoke-WrappedCommand 'pip3' 'aikido-pip3' $pipArgs - } - else { - Invoke-RealCommand 'python3' $Args - } + Invoke-WrappedCommand 'python3' 'aikido-python3' $args }