This commit is contained in:
Reinier Criel 2025-12-15 15:06:00 +01:00
parent fc5df6cd14
commit 7e460e50e1
18 changed files with 107 additions and 389 deletions

View file

@ -40,26 +40,14 @@ Installing the Aikido Safe Chain is easy with our one-line installer.
curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh
``` ```
**Include Python support (pip/pip3/uv):**
```shell
curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --include-python
```
### Windows (PowerShell) ### Windows (PowerShell)
**Default installation (JavaScript packages only):** **Default installation:**
```powershell ```powershell
iex (iwr "https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.ps1" -UseBasicParsing) iex (iwr "https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.ps1" -UseBasicParsing)
``` ```
**Include Python support (pip/pip3/uv):**
```powershell
iex "& { $(iwr 'https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.ps1' -UseBasicParsing) } -includepython"
```
### Verify the installation ### Verify the installation
1. **❗Restart your terminal** to start using the Aikido Safe Chain. 1. **❗Restart your terminal** to start using the Aikido Safe Chain.
@ -199,12 +187,6 @@ Use the `--ci` flag to automatically configure Aikido Safe Chain for CI/CD envir
curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci
``` ```
**With Python support:**
```shell
curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci --include-python
```
### Windows (Azure Pipelines, etc.) ### Windows (Azure Pipelines, etc.)
**JavaScript only:** **JavaScript only:**
@ -234,14 +216,12 @@ iex "& { $(iwr 'https://raw.githubusercontent.com/AikidoSec/safe-chain/main/inst
cache: "npm" cache: "npm"
- name: Install safe-chain - name: Install safe-chain
run: curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci --include-python run: curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
``` ```
> **Note:** Remove `--include-python` if you don't need Python (pip/pip3/uv/poetry) support.
## Azure DevOps Example ## Azure DevOps Example
```yaml ```yaml
@ -250,13 +230,11 @@ iex "& { $(iwr 'https://raw.githubusercontent.com/AikidoSec/safe-chain/main/inst
versionSpec: "22.x" versionSpec: "22.x"
displayName: "Install Node.js" displayName: "Install Node.js"
- script: curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci --include-python - script: curl -fsSL https://raw.githubusercontent.com/AikidoSec/safe-chain/main/install-scripts/install-safe-chain.sh | sh -s -- --ci
displayName: "Install safe-chain" displayName: "Install safe-chain"
- script: npm ci - script: npm ci
displayName: "Install dependencies" displayName: "Install dependencies"
``` ```
> **Note:** Remove `--include-python` if you don't need Python (pip/pip3/uv/poetry) support.
After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection. After setup, all subsequent package manager commands in your CI pipeline will automatically be protected by Aikido Safe Chain's malware detection.

View file

@ -181,9 +181,6 @@ function Install-SafeChain {
# Build setup command based on parameters # Build setup command based on parameters
$setupCmd = if ($ci) { "setup-ci" } else { "setup" } $setupCmd = if ($ci) { "setup-ci" } else { "setup" }
$setupArgs = @() $setupArgs = @()
if ($includepython) {
$setupArgs += "--include-python"
}
# Execute safe-chain setup # Execute safe-chain setup
Write-Info "Running safe-chain $setupCmd $(if ($setupArgs) { $setupArgs -join ' ' })..." Write-Info "Running safe-chain $setupCmd $(if ($setupArgs) { $setupArgs -join ' ' })..."

View file

@ -134,9 +134,6 @@ parse_arguments() {
--ci) --ci)
USE_CI_SETUP=true USE_CI_SETUP=true
;; ;;
--include-python)
INCLUDE_PYTHON=true
;;
*) *)
error "Unknown argument: $arg" error "Unknown argument: $arg"
;; ;;
@ -209,10 +206,6 @@ main() {
SETUP_CMD="setup-ci" SETUP_CMD="setup-ci"
fi fi
if [ "$INCLUDE_PYTHON" = "true" ]; then
SETUP_ARGS="--include-python"
fi
# Execute safe-chain setup # Execute safe-chain setup
info "Running safe-chain $SETUP_CMD $SETUP_ARGS..." info "Running safe-chain $SETUP_CMD $SETUP_ARGS..."
if ! "$FINAL_FILE" $SETUP_CMD $SETUP_ARGS; then if ! "$FINAL_FILE" $SETUP_CMD $SETUP_ARGS; then

View file

@ -95,11 +95,6 @@ function writeHelp() {
"safe-chain setup" "safe-chain setup"
)}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.` )}: This will setup your shell to wrap safe-chain around npm, npx, yarn, pnpm, pnpx, bun, bunx, pip and pip3.`
); );
ui.writeInformation(
` ${chalk.yellow(
"--include-python"
)}: Experimental: include Python package managers (pip, pip3) in the setup.`
);
ui.writeInformation( ui.writeInformation(
`- ${chalk.cyan( `- ${chalk.cyan(
"safe-chain teardown" "safe-chain teardown"
@ -110,11 +105,6 @@ function writeHelp() {
"safe-chain setup-ci" "safe-chain setup-ci"
)}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.` )}: This will setup safe-chain for CI environments by creating shims and modifying the PATH.`
); );
ui.writeInformation(
` ${chalk.yellow(
"--include-python"
)}: Experimental: include Python package managers (pip, pip3) in the setup.`
);
ui.writeInformation( ui.writeInformation(
`- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan( `- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan(
"-v" "-v"

View file

@ -1,11 +1,10 @@
/** /**
* @type {{loggingLevel: string | undefined, skipMinimumPackageAge: boolean | undefined, minimumPackageAgeHours: string | undefined, includePython: boolean}} * @type {{loggingLevel: string | undefined, skipMinimumPackageAge: boolean | undefined, minimumPackageAgeHours: string | undefined}}
*/ */
const state = { const state = {
loggingLevel: undefined, loggingLevel: undefined,
skipMinimumPackageAge: undefined, skipMinimumPackageAge: undefined,
minimumPackageAgeHours: undefined, minimumPackageAgeHours: undefined,
includePython: false,
}; };
const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-"; const SAFE_CHAIN_ARG_PREFIX = "--safe-chain-";
@ -34,7 +33,6 @@ export function initializeCliArguments(args) {
setLoggingLevel(safeChainArgs); setLoggingLevel(safeChainArgs);
setSkipMinimumPackageAge(safeChainArgs); setSkipMinimumPackageAge(safeChainArgs);
setMinimumPackageAgeHours(safeChainArgs); setMinimumPackageAgeHours(safeChainArgs);
setIncludePython(args);
return remainingArgs; return remainingArgs;
} }
@ -109,20 +107,6 @@ export function getMinimumPackageAgeHours() {
return state.minimumPackageAgeHours; return state.minimumPackageAgeHours;
} }
/**
* @param {string[]} args
*/
function setIncludePython(args) {
// This flag doesn't have the --safe-chain- prefix because
// it is only used for the safe-chain command itself and
// not when wrapped around package manager commands.
state.includePython = hasFlagArg(args, "--include-python");
}
export function includePython() {
return state.includePython;
}
/** /**
* @param {string[]} args * @param {string[]} args
* @param {string} flagName * @param {string} flagName

View file

@ -118,7 +118,7 @@ function copyStartupFiles() {
// Use absolute path for source // Use absolute path for source
const sourcePath = path.join( const sourcePath = path.join(
dirname, dirname,
includePython() ? "startup-scripts/include-python" : "startup-scripts", "startup-scripts",
file file
); );
fs.copyFileSync(sourcePath, targetPath); fs.copyFileSync(sourcePath, targetPath);

View file

@ -1,98 +0,0 @@
set -gx PATH $PATH $HOME/.safe-chain/bin
function npx
wrapSafeChainCommand "npx" $argv
end
function yarn
wrapSafeChainCommand "yarn" $argv
end
function pnpm
wrapSafeChainCommand "pnpm" $argv
end
function pnpx
wrapSafeChainCommand "pnpx" $argv
end
function bun
wrapSafeChainCommand "bun" $argv
end
function bunx
wrapSafeChainCommand "bunx" $argv
end
function npm
# If args is just -v or --version and nothing else, just run the `npm -v` command
# This is because nvm uses this to check the version of npm
set argc (count $argv)
if test $argc -eq 1
switch $argv[1]
case "-v" "--version"
command npm $argv
return
end
end
wrapSafeChainCommand "npm" $argv
end
function pip
wrapSafeChainCommand "pip" $argv
end
function pip3
wrapSafeChainCommand "pip3" $argv
end
function uv
wrapSafeChainCommand "uv" $argv
end
function poetry
wrapSafeChainCommand "poetry" $argv
end
# `python -m pip`, `python -m pip3`.
function python
wrapSafeChainCommand "python" $argv
end
# `python3 -m pip`, `python3 -m pip3'.
function python3
wrapSafeChainCommand "python3" $argv
end
function printSafeChainWarning
set original_cmd $argv[1]
# 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 run without it.\n" $original_cmd
# 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 wrapSafeChainCommand
set original_cmd $argv[1]
set cmd_args $argv[2..-1]
if type -q safe-chain
# If the safe-chain command is available, just run it with the provided arguments
safe-chain $original_cmd $cmd_args
else
# If the safe-chain command is not available, print a warning and run the original command
printSafeChainWarning $original_cmd
command $original_cmd $cmd_args
end
end

View file

@ -1,85 +0,0 @@
export PATH="$PATH:$HOME/.safe-chain/bin"
function npx() {
wrapSafeChainCommand "npx" "$@"
}
function yarn() {
wrapSafeChainCommand "yarn" "$@"
}
function pnpm() {
wrapSafeChainCommand "pnpm" "$@"
}
function pnpx() {
wrapSafeChainCommand "pnpx" "$@"
}
function bun() {
wrapSafeChainCommand "bun" "$@"
}
function bunx() {
wrapSafeChainCommand "bunx" "$@"
}
function npm() {
if [[ "$1" == "-v" || "$1" == "--version" ]] && [[ $# -eq 1 ]]; then
# 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 "$@"
return
fi
wrapSafeChainCommand "npm" "$@"
}
function pip() {
wrapSafeChainCommand "pip" "$@"
}
function pip3() {
wrapSafeChainCommand "pip3" "$@"
}
function uv() {
wrapSafeChainCommand "uv" "$@"
}
function poetry() {
wrapSafeChainCommand "poetry" "$@"
}
# `python -m pip`, `python -m pip3`.
function python() {
wrapSafeChainCommand "python" "$@"
}
# `python3 -m pip`, `python3 -m pip3'.
function python3() {
wrapSafeChainCommand "python3" "$@"
}
function printSafeChainWarning() {
# \033[43;30m is used to set the background color to yellow and text color to black
# \033[0m is used to reset the text formatting
printf "\033[43;30mWarning:\033[0m safe-chain is not available to protect you from installing malware. %s will run without it.\n" "$1"
# \033[36m is used to set the text color to cyan
printf "Install safe-chain by using \033[36mnpm install -g @aikidosec/safe-chain\033[0m.\n"
}
function wrapSafeChainCommand() {
local original_cmd="$1"
if command -v safe-chain > /dev/null 2>&1; then
# If the aikido command is available, just run it with the provided arguments
safe-chain "$@"
else
# If the aikido command is not available, print a warning and run the original command
printSafeChainWarning "$original_cmd"
command "$original_cmd" "$@"
fi
}

View file

@ -1,119 +0,0 @@
# Use cross-platform path separator (: on Unix, ; on Windows)
$pathSeparator = if ($IsWindows) { ';' } else { ':' }
$safeChainBin = Join-Path (Join-Path $HOME '.safe-chain') 'bin'
$env:PATH = "$env:PATH$pathSeparator$safeChainBin"
function npx {
Invoke-WrappedCommand "npx" $args
}
function yarn {
Invoke-WrappedCommand "yarn" $args
}
function pnpm {
Invoke-WrappedCommand "pnpm" $args
}
function pnpx {
Invoke-WrappedCommand "pnpx" $args
}
function bun {
Invoke-WrappedCommand "bun" $args
}
function bunx {
Invoke-WrappedCommand "bunx" $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" $args
}
function pip {
Invoke-WrappedCommand "pip" $args
}
function pip3 {
Invoke-WrappedCommand "pip3" $args
}
function uv {
Invoke-WrappedCommand "uv" $args
}
function poetry {
Invoke-WrappedCommand "poetry" $args
}
# `python -m pip`, `python -m pip3`.
function python {
Invoke-WrappedCommand 'python' $args
}
# `python3 -m pip`, `python3 -m pip3'.
function python3 {
Invoke-WrappedCommand 'python3' $args
}
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 run without it."
# 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 {
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
}
}
function Invoke-WrappedCommand {
param(
[string]$OriginalCmd,
[string[]]$Arguments
)
if (Test-CommandAvailable "safe-chain") {
& safe-chain $OriginalCmd @Arguments
}
else {
Write-SafeChainWarning $OriginalCmd
Invoke-RealCommand $OriginalCmd $Arguments
}
}

View file

@ -39,6 +39,33 @@ function npm
wrapSafeChainCommand "npm" $argv wrapSafeChainCommand "npm" $argv
end end
function pip
wrapSafeChainCommand "pip" $argv
end
function pip3
wrapSafeChainCommand "pip3" $argv
end
function uv
wrapSafeChainCommand "uv" $argv
end
function poetry
wrapSafeChainCommand "poetry" $argv
end
# `python -m pip`, `python -m pip3`.
function python
wrapSafeChainCommand "python" $argv
end
# `python3 -m pip`, `python3 -m pip3'.
function python3
wrapSafeChainCommand "python3" $argv
end
function printSafeChainWarning function printSafeChainWarning
set original_cmd $argv[1] set original_cmd $argv[1]

View file

@ -35,6 +35,33 @@ function npm() {
wrapSafeChainCommand "npm" "$@" wrapSafeChainCommand "npm" "$@"
} }
function pip() {
wrapSafeChainCommand "pip" "$@"
}
function pip3() {
wrapSafeChainCommand "pip3" "$@"
}
function uv() {
wrapSafeChainCommand "uv" "$@"
}
function poetry() {
wrapSafeChainCommand "poetry" "$@"
}
# `python -m pip`, `python -m pip3`.
function python() {
wrapSafeChainCommand "python" "$@"
}
# `python3 -m pip`, `python3 -m pip3'.
function python3() {
wrapSafeChainCommand "python3" "$@"
}
function printSafeChainWarning() { function printSafeChainWarning() {
# \033[43;30m is used to set the background color to yellow and text color to black # \033[43;30m is used to set the background color to yellow and text color to black
# \033[0m is used to reset the text formatting # \033[0m is used to reset the text formatting

View file

@ -38,6 +38,33 @@ function npm {
Invoke-WrappedCommand "npm" $args Invoke-WrappedCommand "npm" $args
} }
function pip {
Invoke-WrappedCommand "pip" $args
}
function pip3 {
Invoke-WrappedCommand "pip3" $args
}
function uv {
Invoke-WrappedCommand "uv" $args
}
function poetry {
Invoke-WrappedCommand "poetry" $args
}
# `python -m pip`, `python -m pip3`.
function python {
Invoke-WrappedCommand 'python' $args
}
# `python3 -m pip`, `python3 -m pip3'.
function python3 {
Invoke-WrappedCommand 'python3' $args
}
function Write-SafeChainWarning { function Write-SafeChainWarning {
param([string]$Command) param([string]$Command)

View file

@ -231,7 +231,7 @@ describe("E2E: NODE_EXTRA_CA_CERTS merging", () => {
it(`pip install works without NODE_EXTRA_CA_CERTS set`, async () => { it(`pip install works without NODE_EXTRA_CA_CERTS set`, async () => {
const shell = await container.openShell("zsh"); const shell = await container.openShell("zsh");
await shell.runCommand("safe-chain setup --include-python"); await shell.runCommand("safe-chain setup");
await shell.runCommand("unset NODE_EXTRA_CA_CERTS"); await shell.runCommand("unset NODE_EXTRA_CA_CERTS");
const result = await shell.runCommand( const result = await shell.runCommand(
@ -247,7 +247,7 @@ describe("E2E: NODE_EXTRA_CA_CERTS merging", () => {
it(`pip install works with valid NODE_EXTRA_CA_CERTS set`, async () => { it(`pip install works with valid NODE_EXTRA_CA_CERTS set`, async () => {
const shell = await container.openShell("zsh"); const shell = await container.openShell("zsh");
await shell.runCommand("safe-chain setup --include-python"); await shell.runCommand("safe-chain setup");
// Create a temporary valid certificate // Create a temporary valid certificate
await shell.runCommand("cp /etc/ssl/certs/ca-certificates.crt /tmp/pip-valid-certs.pem"); await shell.runCommand("cp /etc/ssl/certs/ca-certificates.crt /tmp/pip-valid-certs.pem");
@ -265,7 +265,7 @@ describe("E2E: NODE_EXTRA_CA_CERTS merging", () => {
it(`pip install handles non-existent NODE_EXTRA_CA_CERTS gracefully`, async () => { it(`pip install handles non-existent NODE_EXTRA_CA_CERTS gracefully`, async () => {
const shell = await container.openShell("zsh"); const shell = await container.openShell("zsh");
await shell.runCommand("safe-chain setup --include-python"); await shell.runCommand("safe-chain setup");
const result = await shell.runCommand( const result = await shell.runCommand(
'export NODE_EXTRA_CA_CERTS="/tmp/nonexistent-pip-certs.pem" && pip3 install --break-system-packages requests' 'export NODE_EXTRA_CA_CERTS="/tmp/nonexistent-pip-certs.pem" && pip3 install --break-system-packages requests'
@ -281,7 +281,7 @@ describe("E2E: NODE_EXTRA_CA_CERTS merging", () => {
it(`pip install handles invalid NODE_EXTRA_CA_CERTS gracefully`, async () => { it(`pip install handles invalid NODE_EXTRA_CA_CERTS gracefully`, async () => {
const shell = await container.openShell("zsh"); const shell = await container.openShell("zsh");
await shell.runCommand("safe-chain setup --include-python"); await shell.runCommand("safe-chain setup");
// Create invalid cert // Create invalid cert
await shell.runCommand( await shell.runCommand(

View file

@ -86,7 +86,7 @@ describe("E2E: safe-chain setup-ci command for pip/pip3", () => {
// Setup safe-chain CI shims // Setup safe-chain CI shims
const installationShell = await container.openShell(shell); const installationShell = await container.openShell(shell);
await installationShell.runCommand( await installationShell.runCommand(
"safe-chain setup-ci --include-python" "safe-chain setup-ci"
); );
// Add $HOME/.safe-chain/shims to PATH for subsequent shells // Add $HOME/.safe-chain/shims to PATH for subsequent shells
@ -115,7 +115,7 @@ describe("E2E: safe-chain setup-ci command for pip/pip3", () => {
it(`setup-ci routes python -m pip through safe-chain for ${shell}`, async () => { it(`setup-ci routes python -m pip through safe-chain for ${shell}`, async () => {
const installationShell = await container.openShell(shell); const installationShell = await container.openShell(shell);
await installationShell.runCommand( await installationShell.runCommand(
"safe-chain setup-ci --include-python" "safe-chain setup-ci"
); );
await installationShell.runCommand( await installationShell.runCommand(
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc" "echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
@ -138,7 +138,7 @@ describe("E2E: safe-chain setup-ci command for pip/pip3", () => {
it(`setup-ci routes python3 -m pip through safe-chain for ${shell}`, async () => { it(`setup-ci routes python3 -m pip through safe-chain for ${shell}`, async () => {
const installationShell = await container.openShell(shell); const installationShell = await container.openShell(shell);
await installationShell.runCommand( await installationShell.runCommand(
"safe-chain setup-ci --include-python" "safe-chain setup-ci"
); );
await installationShell.runCommand( await installationShell.runCommand(
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc" "echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
@ -161,7 +161,7 @@ describe("E2E: safe-chain setup-ci command for pip/pip3", () => {
it(`setup-ci routes pip through safe-chain for ${shell}`, async () => { it(`setup-ci routes pip through safe-chain for ${shell}`, async () => {
const installationShell = await container.openShell(shell); const installationShell = await container.openShell(shell);
await installationShell.runCommand( await installationShell.runCommand(
"safe-chain setup-ci --include-python" "safe-chain setup-ci"
); );
await installationShell.runCommand( await installationShell.runCommand(
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc" "echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"
@ -184,7 +184,7 @@ describe("E2E: safe-chain setup-ci command for pip/pip3", () => {
it(`setup-ci routes pip3 through safe-chain for ${shell}`, async () => { it(`setup-ci routes pip3 through safe-chain for ${shell}`, async () => {
const installationShell = await container.openShell(shell); const installationShell = await container.openShell(shell);
await installationShell.runCommand( await installationShell.runCommand(
"safe-chain setup-ci --include-python" "safe-chain setup-ci"
); );
await installationShell.runCommand( await installationShell.runCommand(
"echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc" "echo 'export PATH=\"$HOME/.safe-chain/shims:$PATH\"' >> ~/.zshrc"

View file

@ -15,7 +15,7 @@ describe("E2E: pip coverage", () => {
await container.start(); await container.start();
const installationShell = await container.openShell("zsh"); const installationShell = await container.openShell("zsh");
await installationShell.runCommand("safe-chain setup --include-python"); await installationShell.runCommand("safe-chain setup");
// Clear pip cache before each test to ensure fresh downloads through proxy // Clear pip cache before each test to ensure fresh downloads through proxy
await installationShell.runCommand("pip3 cache purge"); await installationShell.runCommand("pip3 cache purge");

View file

@ -15,7 +15,7 @@ describe("E2E: poetry coverage", () => {
await container.start(); await container.start();
const installationShell = await container.openShell("zsh"); const installationShell = await container.openShell("zsh");
await installationShell.runCommand("safe-chain setup --include-python"); await installationShell.runCommand("safe-chain setup");
// Clear poetry cache // Clear poetry cache
await installationShell.runCommand("command poetry cache clear pypi --all -n"); await installationShell.runCommand("command poetry cache clear pypi --all -n");

View file

@ -57,20 +57,18 @@ describe("E2E: safe-chain teardown command", () => {
assert.ok(checkScriptsGone.output.includes("missing"), "Scripts directory should be removed after teardown"); assert.ok(checkScriptsGone.output.includes("missing"), "Scripts directory should be removed after teardown");
}); });
it("safe-chain teardown removes shims directory created by setup-ci --include-python", async () => { it("safe-chain teardown removes shims directory created by setup-ci", async () => {
const shell = await container.openShell("bash"); const shell = await container.openShell("bash");
// Run setup-ci with --include-python // Run setup-ci
await shell.runCommand("safe-chain setup-ci --include-python"); await shell.runCommand("safe-chain setup-ci");
// Verify shims directory exists // Verify shims directory exists
const checkShimsExist = await shell.runCommand("test -d ~/.safe-chain/shims && echo 'exists' || echo 'missing'"); const checkShimsExist = await shell.runCommand("test -d ~/.safe-chain/shims && echo 'exists' || echo 'missing'");
assert.ok(checkShimsExist.output.includes("exists"), "Shims directory should exist after setup-ci --include-python"); assert.ok(checkShimsExist.output.includes("exists"), "Shims directory should exist after setup-ci");
// Verify Python shims were created // Verify Python shims were created
const checkPythonShims = await shell.runCommand("test -f ~/.safe-chain/shims/pip && echo 'exists' || echo 'missing'"); const checkPythonShims = await shell.runCommand("test -f ~/.safe-chain/shims/pip && echo 'exists' || echo 'missing'");
assert.ok(checkPythonShims.output.includes("exists"), "Python shims should exist after setup-ci --include-python"); assert.ok(checkPythonShims.output.includes("exists"), "Python shims should exist after setup-ci");
// Run teardown // Run teardown
await shell.runCommand("safe-chain teardown"); await shell.runCommand("safe-chain teardown");
@ -79,15 +77,14 @@ describe("E2E: safe-chain teardown command", () => {
assert.ok(checkShimsGone.output.includes("missing"), "Shims directory should be removed after teardown"); assert.ok(checkShimsGone.output.includes("missing"), "Shims directory should be removed after teardown");
}); });
it("safe-chain teardown removes scripts directory created by setup --include-python", async () => { it("safe-chain teardown removes scripts directory created by setup", async () => {
const shell = await container.openShell("bash"); const shell = await container.openShell("bash");
// Run setup with --include-python // Run setup
await shell.runCommand("safe-chain setup --include-python"); await shell.runCommand("safe-chain setup");
// Verify scripts directory exists // Verify scripts directory exists
const checkScriptsExist = await shell.runCommand("test -d ~/.safe-chain/scripts && echo 'exists' || echo 'missing'"); const checkScriptsExist = await shell.runCommand("test -d ~/.safe-chain/scripts && echo 'exists' || echo 'missing'");
assert.ok(checkScriptsExist.output.includes("exists"), "Scripts directory should exist after setup --include-python"); assert.ok(checkScriptsExist.output.includes("exists"), "Scripts directory should exist after setup");
// Run teardown // Run teardown
await shell.runCommand("safe-chain teardown"); await shell.runCommand("safe-chain teardown");

View file

@ -15,7 +15,7 @@ describe("E2E: uv coverage", () => {
await container.start(); await container.start();
const installationShell = await container.openShell("zsh"); const installationShell = await container.openShell("zsh");
await installationShell.runCommand("safe-chain setup --include-python"); await installationShell.runCommand("safe-chain setup");
// Clear uv cache // Clear uv cache
await installationShell.runCommand("uv cache clean"); await installationShell.runCommand("uv cache clean");