mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Cleanup
This commit is contained in:
parent
1cf8fd1241
commit
d064d46668
32 changed files with 429 additions and 400 deletions
17
README.md
17
README.md
|
|
@ -318,16 +318,21 @@ The base URL should point to a server that mirrors the structure of `https://mal
|
||||||
|
|
||||||
## Custom Install Directory
|
## Custom Install Directory
|
||||||
|
|
||||||
By default, Safe Chain installs itself into `~/.safe-chain`. You can change this by setting `SAFE_CHAIN_DIR` before running the installer. This is useful for system-wide installations (e.g. inside a Docker image) or when you need to avoid conflicts with other tools.
|
By default, Safe Chain installs itself into `~/.safe-chain`. You can change this by passing an explicit install directory to the installer. This is useful for system-wide installations (e.g. inside a Docker image) or when you need to avoid conflicts with other tools.
|
||||||
|
|
||||||
When set, all Safe Chain data (binary, shims, scripts, config) is placed under the custom directory instead of `~/.safe-chain`.
|
When set, all Safe Chain data (binary, shims, scripts, config) is placed under the custom directory instead of `~/.safe-chain`.
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
export SAFE_CHAIN_DIR=/usr/local/.safe-chain
|
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --install-dir /usr/local/.safe-chain
|
||||||
curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a **one-time setting**. `safe-chain setup` automatically persists `SAFE_CHAIN_DIR` to your shell rc files (e.g. `~/.bashrc`, `~/.zshrc`) so that subsequent `safe-chain` commands (including teardown and re-setup) find the correct directory without needing the variable set again.
|
On Windows, use `-InstallDir`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
iex "& { $(iwr 'https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.ps1' -UseBasicParsing) } -InstallDir 'C:\ProgramData\safe-chain'"
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a one-time installer choice. Runtime shell integration and uninstall now discover the installation from the installed scripts or binary and do not rely on an environment variable.
|
||||||
|
|
||||||
# Usage in CI/CD
|
# Usage in CI/CD
|
||||||
|
|
||||||
|
|
@ -419,7 +424,7 @@ pipeline {
|
||||||
environment {
|
environment {
|
||||||
// Jenkins does not automatically persist PATH updates from setup-ci,
|
// Jenkins does not automatically persist PATH updates from setup-ci,
|
||||||
// so add the shims + binary directory explicitly for all stages.
|
// so add the shims + binary directory explicitly for all stages.
|
||||||
// If you set SAFE_CHAIN_DIR, replace ~/.safe-chain with that path here.
|
// If you installed into a custom directory, replace ~/.safe-chain with that path here.
|
||||||
PATH = "${env.HOME}/.safe-chain/shims:${env.HOME}/.safe-chain/bin:${env.PATH}"
|
PATH = "${env.HOME}/.safe-chain/shims:${env.HOME}/.safe-chain/bin:${env.PATH}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -475,7 +480,7 @@ To add safe-chain in GitLab pipelines, you need to install it in the image runni
|
||||||
# Install safe-chain
|
# Install safe-chain
|
||||||
RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
|
RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci
|
||||||
|
|
||||||
# Add safe-chain to PATH (update paths if you set SAFE_CHAIN_DIR during install)
|
# Add safe-chain to PATH (update paths if you used a custom install dir)
|
||||||
ENV PATH="/root/.safe-chain/shims:/root/.safe-chain/bin:${PATH}"
|
ENV PATH="/root/.safe-chain/shims:/root/.safe-chain/bin:${PATH}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,49 @@
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[switch]$ci,
|
[switch]$ci,
|
||||||
[switch]$includepython
|
[switch]$includepython,
|
||||||
|
[string]$InstallDir
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function Test-InstallDir {
|
||||||
|
param([string]$Dir)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($Dir)) {
|
||||||
|
return @{ Ok = $true; Normalized = $null }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not [System.IO.Path]::IsPathRooted($Dir)) {
|
||||||
|
return @{ Ok = $false; Reason = "-InstallDir must be an absolute path, got: $Dir" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Dir.Contains([System.IO.Path]::PathSeparator)) {
|
||||||
|
return @{ Ok = $false; Reason = "-InstallDir must not contain the PATH separator ($([System.IO.Path]::PathSeparator))" }
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalized = [System.IO.Path]::GetFullPath($Dir)
|
||||||
|
$root = [System.IO.Path]::GetPathRoot($normalized)
|
||||||
|
if ($normalized.TrimEnd('\', '/') -eq $root.TrimEnd('\', '/')) {
|
||||||
|
return @{ Ok = $false; Reason = "-InstallDir cannot be a root or drive-root directory" }
|
||||||
|
}
|
||||||
|
|
||||||
|
$segments = $normalized.Substring($root.Length).Split([char[]]@('\', '/'), [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||||
|
if ($segments -contains "..") {
|
||||||
|
return @{ Ok = $false; Reason = "-InstallDir must not contain path traversal segments" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{ Ok = $true; Normalized = $normalized }
|
||||||
|
}
|
||||||
|
|
||||||
$Version = $env:SAFE_CHAIN_VERSION # Will be fetched from latest release if not set
|
$Version = $env:SAFE_CHAIN_VERSION # Will be fetched from latest release if not set
|
||||||
|
$SafeChainBase = if ($InstallDir) { $InstallDir } else { Join-Path $env:USERPROFILE ".safe-chain" }
|
||||||
|
|
||||||
# Validate SAFE_CHAIN_DIR before use
|
$installDirValidation = Test-InstallDir -Dir $SafeChainBase
|
||||||
if ($env:SAFE_CHAIN_DIR) {
|
if (-not $installDirValidation.Ok) {
|
||||||
if (-not [System.IO.Path]::IsPathRooted($env:SAFE_CHAIN_DIR)) {
|
Write-Host "[ERROR] $($installDirValidation.Reason)" -ForegroundColor Red
|
||||||
Write-Host "[ERROR] SAFE_CHAIN_DIR must be an absolute path, got: $($env:SAFE_CHAIN_DIR)" -ForegroundColor Red; exit 1
|
exit 1
|
||||||
}
|
|
||||||
if ($env:SAFE_CHAIN_DIR -match '\.\.') {
|
|
||||||
Write-Host "[ERROR] SAFE_CHAIN_DIR must not contain path traversal (..)" -ForegroundColor Red; exit 1
|
|
||||||
}
|
|
||||||
if ($env:SAFE_CHAIN_DIR -match '^[A-Za-z]:[/\\]?$' -or $env:SAFE_CHAIN_DIR -eq '/') {
|
|
||||||
Write-Host "[ERROR] SAFE_CHAIN_DIR cannot be a root or drive-root directory" -ForegroundColor Red; exit 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$SafeChainBase = if ($env:SAFE_CHAIN_DIR) { $env:SAFE_CHAIN_DIR } else { Join-Path $env:USERPROFILE ".safe-chain" }
|
$SafeChainBase = $installDirValidation.Normalized
|
||||||
$InstallDir = Join-Path $SafeChainBase "bin"
|
$InstallDir = Join-Path $SafeChainBase "bin"
|
||||||
$RepoUrl = "https://github.com/AikidoSec/safe-chain"
|
$RepoUrl = "https://github.com/AikidoSec/safe-chain"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,50 @@
|
||||||
|
|
||||||
set -e # Exit on error
|
set -e # Exit on error
|
||||||
|
|
||||||
|
validate_install_dir() {
|
||||||
|
dir="$1"
|
||||||
|
|
||||||
|
if [ -z "$dir" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$dir" in
|
||||||
|
/*) ;;
|
||||||
|
*)
|
||||||
|
printf '[ERROR] --install-dir must be an absolute path, got: %s\n' "$dir" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$dir" in
|
||||||
|
*:*)
|
||||||
|
printf '[ERROR] --install-dir must not contain the PATH separator (:)\n' >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$dir" = "/" ]; then
|
||||||
|
printf '[ERROR] --install-dir cannot be a root or drive-root directory\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
old_ifs=$IFS
|
||||||
|
IFS='/'
|
||||||
|
set -- $dir
|
||||||
|
IFS=$old_ifs
|
||||||
|
|
||||||
|
for segment in "$@"; do
|
||||||
|
if [ "$segment" = ".." ]; then
|
||||||
|
printf '[ERROR] --install-dir must not contain path traversal segments\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
VERSION="${SAFE_CHAIN_VERSION:-}" # Will be fetched from latest release if not set
|
VERSION="${SAFE_CHAIN_VERSION:-}" # Will be fetched from latest release if not set
|
||||||
|
SAFE_CHAIN_BASE="${HOME}/.safe-chain"
|
||||||
|
|
||||||
# Validate SAFE_CHAIN_DIR before use
|
|
||||||
if [ -n "${SAFE_CHAIN_DIR}" ]; then
|
|
||||||
case "${SAFE_CHAIN_DIR}" in
|
|
||||||
/*) ;;
|
|
||||||
*) printf '[ERROR] SAFE_CHAIN_DIR must be an absolute path, got: %s\n' "${SAFE_CHAIN_DIR}" >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
case "${SAFE_CHAIN_DIR}" in
|
|
||||||
*../*|*/..*|..) printf '[ERROR] SAFE_CHAIN_DIR must not contain path traversal (..)\n' >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
if [ "${SAFE_CHAIN_DIR}" = "/" ]; then
|
|
||||||
printf '[ERROR] SAFE_CHAIN_DIR cannot be the root directory\n' >&2; exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
SAFE_CHAIN_BASE="${SAFE_CHAIN_DIR:-${HOME}/.safe-chain}"
|
|
||||||
INSTALL_DIR="${SAFE_CHAIN_BASE}/bin"
|
INSTALL_DIR="${SAFE_CHAIN_BASE}/bin"
|
||||||
REPO_URL="https://github.com/AikidoSec/safe-chain"
|
REPO_URL="https://github.com/AikidoSec/safe-chain"
|
||||||
|
|
||||||
|
|
@ -245,19 +271,33 @@ remove_nvm_installation() {
|
||||||
|
|
||||||
# Parse command-line arguments
|
# Parse command-line arguments
|
||||||
parse_arguments() {
|
parse_arguments() {
|
||||||
for arg in "$@"; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$arg" in
|
case "$1" in
|
||||||
--ci)
|
--ci)
|
||||||
USE_CI_SETUP=true
|
USE_CI_SETUP=true
|
||||||
;;
|
;;
|
||||||
|
--install-dir)
|
||||||
|
shift
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
error "Missing value for --install-dir"
|
||||||
|
fi
|
||||||
|
SAFE_CHAIN_BASE="$1"
|
||||||
|
;;
|
||||||
|
--install-dir=*)
|
||||||
|
SAFE_CHAIN_BASE="${1#--install-dir=}"
|
||||||
|
;;
|
||||||
--include-python)
|
--include-python)
|
||||||
warn "--include-python is deprecated and ignored. Python ecosystem is now included by default."
|
warn "--include-python is deprecated and ignored. Python ecosystem is now included by default."
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
error "Unknown argument: $arg"
|
error "Unknown argument: $1"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
|
validate_install_dir "${SAFE_CHAIN_BASE}"
|
||||||
|
INSTALL_DIR="${SAFE_CHAIN_BASE}/bin"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main installation
|
# Main installation
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,6 @@
|
||||||
# Use HOME on Unix, USERPROFILE on Windows (PowerShell Core is cross-platform)
|
# Use HOME on Unix, USERPROFILE on Windows (PowerShell Core is cross-platform)
|
||||||
$HomeDir = if ($env:HOME) { $env:HOME } else { $env:USERPROFILE }
|
$HomeDir = if ($env:HOME) { $env:HOME } else { $env:USERPROFILE }
|
||||||
|
|
||||||
# Validate SAFE_CHAIN_DIR before use
|
|
||||||
if ($env:SAFE_CHAIN_DIR) {
|
|
||||||
if (-not [System.IO.Path]::IsPathRooted($env:SAFE_CHAIN_DIR)) {
|
|
||||||
Write-Host "[ERROR] SAFE_CHAIN_DIR must be an absolute path, got: $($env:SAFE_CHAIN_DIR)" -ForegroundColor Red; exit 1
|
|
||||||
}
|
|
||||||
if ($env:SAFE_CHAIN_DIR -match '\.\.') {
|
|
||||||
Write-Host "[ERROR] SAFE_CHAIN_DIR must not contain path traversal (..)" -ForegroundColor Red; exit 1
|
|
||||||
}
|
|
||||||
if ($env:SAFE_CHAIN_DIR -match '^[A-Za-z]:[/\\]?$' -or $env:SAFE_CHAIN_DIR -eq '/') {
|
|
||||||
Write-Host "[ERROR] SAFE_CHAIN_DIR cannot be a root or drive-root directory" -ForegroundColor Red; exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$DotSafeChain = if ($env:SAFE_CHAIN_DIR) { $env:SAFE_CHAIN_DIR } else { Join-Path $HomeDir ".safe-chain" }
|
|
||||||
$InstallDir = Join-Path $DotSafeChain "bin"
|
|
||||||
|
|
||||||
# Helper functions
|
# Helper functions
|
||||||
function Write-Info {
|
function Write-Info {
|
||||||
param([string]$Message)
|
param([string]$Message)
|
||||||
|
|
@ -38,6 +22,64 @@ function Write-Error-Custom {
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Get-InstallDirFromBinaryPath {
|
||||||
|
param([string]$BinaryPath)
|
||||||
|
|
||||||
|
if ([string]::IsNullOrWhiteSpace($BinaryPath)) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resolvedPath = (Resolve-Path -LiteralPath $BinaryPath -ErrorAction Stop).Path
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$resolvedPath = [System.IO.Path]::GetFullPath($BinaryPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileName = [System.IO.Path]::GetFileName($resolvedPath)
|
||||||
|
if (($fileName -ne "safe-chain") -and ($fileName -ne "safe-chain.exe")) {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($resolvedPath -match '\.(js|cjs|mjs|cmd|ps1)$') {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$binDir = Split-Path -Parent $resolvedPath
|
||||||
|
if ((Split-Path -Leaf $binDir) -ne "bin") {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Split-Path -Parent $binDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SafeChainInstallDir {
|
||||||
|
$command = Get-Command safe-chain -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
if ($command) {
|
||||||
|
try {
|
||||||
|
$reportedInstallDir = & safe-chain get-install-dir 2>$null | Select-Object -First 1
|
||||||
|
if ($reportedInstallDir) {
|
||||||
|
$reportedInstallDir = $reportedInstallDir.Trim()
|
||||||
|
}
|
||||||
|
if ($reportedInstallDir) {
|
||||||
|
return $reportedInstallDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Fall back to deriving the install dir from the discovered command path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($command -and $command.Path) {
|
||||||
|
$discoveredInstallDir = Get-InstallDirFromBinaryPath -BinaryPath $command.Path
|
||||||
|
if ($discoveredInstallDir) {
|
||||||
|
return $discoveredInstallDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Join-Path $HomeDir ".safe-chain")
|
||||||
|
}
|
||||||
|
|
||||||
# Check and uninstall npm global package if present
|
# Check and uninstall npm global package if present
|
||||||
function Remove-NpmInstallation {
|
function Remove-NpmInstallation {
|
||||||
# Check if npm is available
|
# Check if npm is available
|
||||||
|
|
@ -90,6 +132,8 @@ function Remove-VoltaInstallation {
|
||||||
# Main uninstallation
|
# Main uninstallation
|
||||||
function Uninstall-SafeChain {
|
function Uninstall-SafeChain {
|
||||||
Write-Info "Uninstalling safe-chain..."
|
Write-Info "Uninstalling safe-chain..."
|
||||||
|
$DotSafeChain = Get-SafeChainInstallDir
|
||||||
|
$InstallDir = Join-Path $DotSafeChain "bin"
|
||||||
|
|
||||||
# Run teardown if safe-chain is available
|
# Run teardown if safe-chain is available
|
||||||
# Check for both safe-chain.exe (Windows) and safe-chain (Unix) since PowerShell Core runs on all platforms
|
# Check for both safe-chain.exe (Windows) and safe-chain (Unix) since PowerShell Core runs on all platforms
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,6 @@ set -e # Exit on error
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
# Validate SAFE_CHAIN_DIR before use
|
|
||||||
if [ -n "${SAFE_CHAIN_DIR}" ]; then
|
|
||||||
case "${SAFE_CHAIN_DIR}" in
|
|
||||||
/*) ;;
|
|
||||||
*) printf '[ERROR] SAFE_CHAIN_DIR must be an absolute path, got: %s\n' "${SAFE_CHAIN_DIR}" >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
case "${SAFE_CHAIN_DIR}" in
|
|
||||||
*../*|*/..*|..) printf '[ERROR] SAFE_CHAIN_DIR must not contain path traversal (..)\n' >&2; exit 1 ;;
|
|
||||||
esac
|
|
||||||
if [ "${SAFE_CHAIN_DIR}" = "/" ]; then
|
|
||||||
printf '[ERROR] SAFE_CHAIN_DIR cannot be the root directory\n' >&2; exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOT_SAFE_CHAIN="${SAFE_CHAIN_DIR:-${HOME}/.safe-chain}"
|
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
|
|
@ -49,6 +33,78 @@ command_exists() {
|
||||||
command -v "$1" >/dev/null 2>&1
|
command -v "$1" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolve_path() {
|
||||||
|
target="$1"
|
||||||
|
|
||||||
|
while [ -L "$target" ]; do
|
||||||
|
link_target=$(readlink "$target" 2>/dev/null || echo "")
|
||||||
|
if [ -z "$link_target" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$link_target" in
|
||||||
|
/*) target="$link_target" ;;
|
||||||
|
*)
|
||||||
|
target="$(dirname "$target")/$link_target"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
target_dir=$(dirname "$target")
|
||||||
|
target_name=$(basename "$target")
|
||||||
|
|
||||||
|
if cd "$target_dir" 2>/dev/null; then
|
||||||
|
printf '%s/%s\n' "$(pwd -P)" "$target_name"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$target"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
derive_install_dir_from_binary() {
|
||||||
|
binary_path="$1"
|
||||||
|
|
||||||
|
if [ -z "$binary_path" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
resolved_path=$(resolve_path "$binary_path")
|
||||||
|
binary_name=$(basename "$resolved_path")
|
||||||
|
case "$binary_name" in
|
||||||
|
safe-chain|safe-chain.exe) ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$resolved_path" in
|
||||||
|
*.js|*.cjs|*.mjs|*.cmd|*.ps1) return 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
binary_dir=$(dirname "$resolved_path")
|
||||||
|
if [ "$(basename "$binary_dir")" != "bin" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dirname "$binary_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_install_dir() {
|
||||||
|
if command_exists safe-chain; then
|
||||||
|
install_dir=$(safe-chain get-install-dir 2>/dev/null || true)
|
||||||
|
if [ -n "$install_dir" ]; then
|
||||||
|
printf '%s\n' "$install_dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
command_path=$(command -v safe-chain)
|
||||||
|
install_dir=$(derive_install_dir_from_binary "$command_path" || true)
|
||||||
|
if [ -n "$install_dir" ]; then
|
||||||
|
printf '%s\n' "$install_dir"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${HOME}/.safe-chain"
|
||||||
|
}
|
||||||
|
|
||||||
# Check and uninstall npm global package if present
|
# Check and uninstall npm global package if present
|
||||||
remove_npm_installation() {
|
remove_npm_installation() {
|
||||||
if ! command_exists npm; then
|
if ! command_exists npm; then
|
||||||
|
|
@ -154,6 +210,7 @@ remove_nvm_installation() {
|
||||||
|
|
||||||
# Main uninstallation
|
# Main uninstallation
|
||||||
main() {
|
main() {
|
||||||
|
DOT_SAFE_CHAIN=$(get_install_dir)
|
||||||
SAFE_CHAIN_LOCATION="$DOT_SAFE_CHAIN/bin/safe-chain"
|
SAFE_CHAIN_LOCATION="$DOT_SAFE_CHAIN/bin/safe-chain"
|
||||||
|
|
||||||
if [ -x "$SAFE_CHAIN_LOCATION" ]; then
|
if [ -x "$SAFE_CHAIN_LOCATION" ]; then
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { knownAikidoTools } from "../src/shell-integration/helpers.js";
|
import { knownAikidoTools } from "../src/shell-integration/helpers.js";
|
||||||
|
import { getInstalledSafeChainDir } from "../src/installLocation.js";
|
||||||
|
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
// This checks the current file's dirname in a way that's compatible with:
|
// This checks the current file's dirname in a way that's compatible with:
|
||||||
|
|
@ -67,6 +68,17 @@ if (tool) {
|
||||||
teardownDirectories();
|
teardownDirectories();
|
||||||
} else if (command === "setup-ci") {
|
} else if (command === "setup-ci") {
|
||||||
setupCi();
|
setupCi();
|
||||||
|
} else if (command === "get-install-dir") {
|
||||||
|
const installDir = getInstalledSafeChainDir();
|
||||||
|
if (!installDir) {
|
||||||
|
ui.writeError(
|
||||||
|
"Install directory is only available for packaged safe-chain binaries.",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.writeInformation(installDir);
|
||||||
|
process.exit(0);
|
||||||
} else if (command === "--version" || command === "-v" || command === "-v") {
|
} else if (command === "--version" || command === "-v" || command === "-v") {
|
||||||
(async () => {
|
(async () => {
|
||||||
ui.writeInformation(`Current safe-chain version: ${await getVersion()}`);
|
ui.writeInformation(`Current safe-chain version: ${await getVersion()}`);
|
||||||
|
|
@ -88,7 +100,7 @@ function writeHelp() {
|
||||||
ui.writeInformation(
|
ui.writeInformation(
|
||||||
`Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
|
`Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
|
||||||
"teardown",
|
"teardown",
|
||||||
)}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("help")}, ${chalk.cyan(
|
)}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("get-install-dir")}, ${chalk.cyan("help")}, ${chalk.cyan(
|
||||||
"--version",
|
"--version",
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
|
|
@ -108,6 +120,11 @@ 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.cyan(
|
||||||
|
"safe-chain get-install-dir",
|
||||||
|
)}: Print the install directory for packaged safe-chain binaries.`,
|
||||||
|
);
|
||||||
ui.writeInformation(
|
ui.writeInformation(
|
||||||
`- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan(
|
`- ${chalk.cyan("safe-chain --version")} (or ${chalk.cyan(
|
||||||
"-v",
|
"-v",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
import { getEcoSystem } from "./settings.js";
|
import { getEcoSystem } from "./settings.js";
|
||||||
import { getSafeChainDir } from "./environmentVariables.js";
|
import { getSafeChainBaseDir } from "./safeChainDir.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} SafeChainConfig
|
* @typedef {Object} SafeChainConfig
|
||||||
|
|
@ -305,7 +305,7 @@ function getConfigFilePath() {
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getSafeChainDirectory() {
|
export function getSafeChainDirectory() {
|
||||||
const safeChainDir = getSafeChainDir() ?? path.join(os.homedir(), ".safe-chain");
|
const safeChainDir = getSafeChainBaseDir();
|
||||||
|
|
||||||
if (!fs.existsSync(safeChainDir)) {
|
if (!fs.existsSync(safeChainDir)) {
|
||||||
fs.mkdirSync(safeChainDir, { recursive: true });
|
fs.mkdirSync(safeChainDir, { recursive: true });
|
||||||
|
|
|
||||||
|
|
@ -55,14 +55,3 @@ export function getMinimumPackageAgeExclusions() {
|
||||||
export function getMalwareListBaseUrl() {
|
export function getMalwareListBaseUrl() {
|
||||||
return process.env.SAFE_CHAIN_MALWARE_LIST_BASE_URL;
|
return process.env.SAFE_CHAIN_MALWARE_LIST_BASE_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the safe-chain base directory from environment variable.
|
|
||||||
* When set, all safe-chain data (bin, shims, scripts) will be placed under this directory
|
|
||||||
* instead of the default ~/.safe-chain, enabling system-wide installations.
|
|
||||||
* Example: "/usr/local/.safe-chain"
|
|
||||||
* @returns {string | undefined}
|
|
||||||
*/
|
|
||||||
export function getSafeChainDir() {
|
|
||||||
return process.env.SAFE_CHAIN_DIR;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
||||||
import assert from "node:assert";
|
|
||||||
|
|
||||||
const { getSafeChainDir } = await import("./environmentVariables.js");
|
|
||||||
|
|
||||||
describe("getSafeChainDir", () => {
|
|
||||||
let original;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
original = process.env.SAFE_CHAIN_DIR;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (original !== undefined) {
|
|
||||||
process.env.SAFE_CHAIN_DIR = original;
|
|
||||||
} else {
|
|
||||||
delete process.env.SAFE_CHAIN_DIR;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns undefined when SAFE_CHAIN_DIR is not set", () => {
|
|
||||||
delete process.env.SAFE_CHAIN_DIR;
|
|
||||||
assert.strictEqual(getSafeChainDir(), undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns the value of SAFE_CHAIN_DIR when set", () => {
|
|
||||||
process.env.SAFE_CHAIN_DIR = "/usr/local/.safe-chain";
|
|
||||||
assert.strictEqual(getSafeChainDir(), "/usr/local/.safe-chain");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
10
packages/safe-chain/src/config/safeChainDir.js
Normal file
10
packages/safe-chain/src/config/safeChainDir.js
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import os from "os";
|
||||||
|
import path from "path";
|
||||||
|
import { getInstalledSafeChainDir } from "../installLocation.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getSafeChainBaseDir() {
|
||||||
|
return getInstalledSafeChainDir() ?? path.join(os.homedir(), ".safe-chain");
|
||||||
|
}
|
||||||
39
packages/safe-chain/src/installLocation.js
Normal file
39
packages/safe-chain/src/installLocation.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} executablePath
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
export function deriveInstallDirFromExecutablePath(executablePath) {
|
||||||
|
if (!executablePath) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathLibrary = executablePath.includes("\\") ? path.win32 : path.posix;
|
||||||
|
const executableDir = pathLibrary.dirname(executablePath);
|
||||||
|
if (pathLibrary.basename(executableDir) !== "bin") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathLibrary.dirname(executableDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the install directory for a packaged safe-chain binary.
|
||||||
|
* Custom installation directories only apply to packaged binary installs.
|
||||||
|
* For npm/global/dev-script executions this intentionally returns undefined,
|
||||||
|
* which causes callers to fall back to the default ~/.safe-chain layout.
|
||||||
|
*
|
||||||
|
* @param {{ isPackaged?: boolean, executablePath?: string }} [options]
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
export function getInstalledSafeChainDir(options = {}) {
|
||||||
|
const isPackaged = options.isPackaged ?? Boolean(process.pkg);
|
||||||
|
if (!isPackaged) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deriveInstallDirFromExecutablePath(
|
||||||
|
options.executablePath ?? process.execPath,
|
||||||
|
);
|
||||||
|
}
|
||||||
51
packages/safe-chain/src/installLocation.spec.js
Normal file
51
packages/safe-chain/src/installLocation.spec.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import {
|
||||||
|
deriveInstallDirFromExecutablePath,
|
||||||
|
getInstalledSafeChainDir,
|
||||||
|
} from "./installLocation.js";
|
||||||
|
|
||||||
|
describe("deriveInstallDirFromExecutablePath", () => {
|
||||||
|
it("derives the install dir from a Unix binary path", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
deriveInstallDirFromExecutablePath("/usr/local/.safe-chain/bin/safe-chain"),
|
||||||
|
"/usr/local/.safe-chain",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("derives the install dir from a Windows binary path", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
deriveInstallDirFromExecutablePath("C:\\ProgramData\\safe-chain\\bin\\safe-chain.exe"),
|
||||||
|
"C:\\ProgramData\\safe-chain",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined when the executable is not inside a bin directory", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
deriveInstallDirFromExecutablePath("/usr/local/.safe-chain/safe-chain"),
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getInstalledSafeChainDir", () => {
|
||||||
|
it("returns undefined for non-packaged executions", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
getInstalledSafeChainDir({
|
||||||
|
isPackaged: false,
|
||||||
|
executablePath: "/usr/local/.safe-chain/bin/safe-chain",
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns the install dir for packaged executions", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
getInstalledSafeChainDir({
|
||||||
|
isPackaged: true,
|
||||||
|
executablePath: "/usr/local/.safe-chain/bin/safe-chain",
|
||||||
|
}),
|
||||||
|
"/usr/local/.safe-chain",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
import forge from "node-forge";
|
import forge from "node-forge";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import os from "os";
|
import { getSafeChainBaseDir } from "../config/safeChainDir.js";
|
||||||
import { getSafeChainDir } from "../config/environmentVariables.js";
|
|
||||||
|
|
||||||
const ca = loadCa();
|
const ca = loadCa();
|
||||||
|
|
||||||
const certCache = new Map();
|
const certCache = new Map();
|
||||||
|
|
||||||
function getCertFolder() {
|
function getCertFolder() {
|
||||||
const safeChainDir = getSafeChainDir() ?? path.join(os.homedir(), ".safe-chain");
|
return path.join(getSafeChainBaseDir(), "certs");
|
||||||
return path.join(safeChainDir, "certs");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,23 @@ import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
|
|
||||||
describe("certUtils", () => {
|
describe("certUtils", () => {
|
||||||
let originalSafeChainDir;
|
let installedSafeChainDir;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalSafeChainDir = process.env.SAFE_CHAIN_DIR;
|
installedSafeChainDir = undefined;
|
||||||
|
mock.module("../config/safeChainDir.js", {
|
||||||
|
namedExports: {
|
||||||
|
getSafeChainBaseDir: () => installedSafeChainDir ?? "/home/test/.safe-chain",
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (originalSafeChainDir === undefined) {
|
|
||||||
delete process.env.SAFE_CHAIN_DIR;
|
|
||||||
} else {
|
|
||||||
process.env.SAFE_CHAIN_DIR = originalSafeChainDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
mock.reset();
|
mock.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("stores CA certificates in SAFE_CHAIN_DIR when configured", async () => {
|
it("stores CA certificates in the packaged install dir when available", async () => {
|
||||||
process.env.SAFE_CHAIN_DIR = "/custom/safe-chain";
|
installedSafeChainDir = "/custom/safe-chain";
|
||||||
|
|
||||||
mock.module("fs", {
|
mock.module("fs", {
|
||||||
defaultExport: {
|
defaultExport: {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@ import * as os from "os";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { ECOSYSTEM_JS, ECOSYSTEM_PY } from "../config/settings.js";
|
import { ECOSYSTEM_JS, ECOSYSTEM_PY } from "../config/settings.js";
|
||||||
import { getSafeChainDir } from "../config/environmentVariables.js";
|
import { getSafeChainBaseDir } from "../config/safeChainDir.js";
|
||||||
export { getSafeChainDir };
|
|
||||||
import { safeSpawn } from "../utils/safeSpawn.js";
|
import { safeSpawn } from "../utils/safeSpawn.js";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
|
|
@ -125,12 +124,10 @@ export function getPackageManagerList() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the safe-chain base directory.
|
* Returns the safe-chain base directory.
|
||||||
* Uses SAFE_CHAIN_DIR environment variable when set, otherwise defaults to ~/.safe-chain.
|
* Uses the packaged binary location when available, otherwise defaults to ~/.safe-chain.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
export function getSafeChainBaseDir() {
|
export { getSafeChainBaseDir };
|
||||||
return getSafeChainDir() ?? path.join(os.homedir(), ".safe-chain");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
|
|
||||||
|
|
@ -185,64 +185,23 @@ describe("removeLinesMatchingPatternTests", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getSafeChainBaseDir / getBinDir / getShimsDir / getScriptsDir", () => {
|
describe("getSafeChainBaseDir / getBinDir / getShimsDir / getScriptsDir", () => {
|
||||||
const customDir = "/usr/local/.safe-chain";
|
it("defaults base dir to ~/.safe-chain when no packaged install dir is available", async () => {
|
||||||
|
|
||||||
let originalSafeChainDir;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
originalSafeChainDir = process.env.SAFE_CHAIN_DIR;
|
|
||||||
delete process.env.SAFE_CHAIN_DIR;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (originalSafeChainDir !== undefined) {
|
|
||||||
process.env.SAFE_CHAIN_DIR = originalSafeChainDir;
|
|
||||||
} else {
|
|
||||||
delete process.env.SAFE_CHAIN_DIR;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("defaults base dir to ~/.safe-chain when SAFE_CHAIN_DIR is not set", async () => {
|
|
||||||
const { getSafeChainBaseDir } = await import("./helpers.js");
|
const { getSafeChainBaseDir } = await import("./helpers.js");
|
||||||
assert.strictEqual(getSafeChainBaseDir(), path.join(homedir(), ".safe-chain"));
|
assert.strictEqual(getSafeChainBaseDir(), path.join(homedir(), ".safe-chain"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses SAFE_CHAIN_DIR as base dir when set", async () => {
|
|
||||||
process.env.SAFE_CHAIN_DIR = customDir;
|
|
||||||
const { getSafeChainBaseDir } = await import("./helpers.js");
|
|
||||||
assert.strictEqual(getSafeChainBaseDir(), customDir);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getBinDir returns ~/.safe-chain/bin by default", async () => {
|
it("getBinDir returns ~/.safe-chain/bin by default", async () => {
|
||||||
const { getBinDir } = await import("./helpers.js");
|
const { getBinDir } = await import("./helpers.js");
|
||||||
assert.strictEqual(getBinDir(), path.join(homedir(), ".safe-chain", "bin"));
|
assert.strictEqual(getBinDir(), path.join(homedir(), ".safe-chain", "bin"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getBinDir returns custom dir + /bin when SAFE_CHAIN_DIR is set", async () => {
|
|
||||||
process.env.SAFE_CHAIN_DIR = customDir;
|
|
||||||
const { getBinDir } = await import("./helpers.js");
|
|
||||||
assert.strictEqual(getBinDir(), `${customDir}/bin`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getShimsDir returns ~/.safe-chain/shims by default", async () => {
|
it("getShimsDir returns ~/.safe-chain/shims by default", async () => {
|
||||||
const { getShimsDir } = await import("./helpers.js");
|
const { getShimsDir } = await import("./helpers.js");
|
||||||
assert.strictEqual(getShimsDir(), path.join(homedir(), ".safe-chain", "shims"));
|
assert.strictEqual(getShimsDir(), path.join(homedir(), ".safe-chain", "shims"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getShimsDir returns custom dir + /shims when SAFE_CHAIN_DIR is set", async () => {
|
|
||||||
process.env.SAFE_CHAIN_DIR = customDir;
|
|
||||||
const { getShimsDir } = await import("./helpers.js");
|
|
||||||
assert.strictEqual(getShimsDir(), `${customDir}/shims`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("getScriptsDir returns ~/.safe-chain/scripts by default", async () => {
|
it("getScriptsDir returns ~/.safe-chain/scripts by default", async () => {
|
||||||
const { getScriptsDir } = await import("./helpers.js");
|
const { getScriptsDir } = await import("./helpers.js");
|
||||||
assert.strictEqual(getScriptsDir(), path.join(homedir(), ".safe-chain", "scripts"));
|
assert.strictEqual(getScriptsDir(), path.join(homedir(), ".safe-chain", "scripts"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("getScriptsDir returns custom dir + /scripts when SAFE_CHAIN_DIR is set", async () => {
|
|
||||||
process.env.SAFE_CHAIN_DIR = customDir;
|
|
||||||
const { getScriptsDir } = await import("./helpers.js");
|
|
||||||
assert.strictEqual(getScriptsDir(), `${customDir}/scripts`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
# Function to remove shim from PATH (POSIX-compliant)
|
# Function to remove shim from PATH (POSIX-compliant)
|
||||||
remove_shim_from_path() {
|
remove_shim_from_path() {
|
||||||
_safe_chain_shims="{{SHIMS_DIR}}"
|
_safe_chain_shims=$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd -P)
|
||||||
echo "$PATH" | sed "s|${_safe_chain_shims}:||g"
|
echo "$PATH" | sed "s|${_safe_chain_shims}:||g"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -13,11 +13,7 @@ if command -v safe-chain >/dev/null 2>&1; then
|
||||||
PATH=$(remove_shim_from_path) exec safe-chain {{PACKAGE_MANAGER}} "$@"
|
PATH=$(remove_shim_from_path) exec safe-chain {{PACKAGE_MANAGER}} "$@"
|
||||||
else
|
else
|
||||||
# safe-chain is not reachable — warn the user so they know protection is inactive
|
# safe-chain is not reachable — warn the user so they know protection is inactive
|
||||||
if [ -n "$SAFE_CHAIN_DIR" ]; then
|
|
||||||
printf "\033[43;30mWarning:\033[0m safe-chain is not accessible. Check that '%s/bin' is readable and executable by the current user.\n" "$SAFE_CHAIN_DIR" >&2
|
|
||||||
else
|
|
||||||
printf "\033[43;30mWarning:\033[0m safe-chain is not available to protect you from installing malware. {{PACKAGE_MANAGER}} will run without it.\n" >&2
|
printf "\033[43;30mWarning:\033[0m safe-chain is not available to protect you from installing malware. {{PACKAGE_MANAGER}} will run without it.\n" >&2
|
||||||
fi
|
|
||||||
|
|
||||||
# Dynamically find original {{PACKAGE_MANAGER}} (excluding this shim directory)
|
# Dynamically find original {{PACKAGE_MANAGER}} (excluding this shim directory)
|
||||||
original_cmd=$(PATH=$(remove_shim_from_path) command -v {{PACKAGE_MANAGER}})
|
original_cmd=$(PATH=$(remove_shim_from_path) command -v {{PACKAGE_MANAGER}})
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ REM Generated wrapper for {{PACKAGE_MANAGER}} by safe-chain
|
||||||
REM This wrapper intercepts {{PACKAGE_MANAGER}} calls for non-interactive environments
|
REM This wrapper intercepts {{PACKAGE_MANAGER}} calls for non-interactive environments
|
||||||
|
|
||||||
REM Remove shim directory from PATH to prevent infinite loops
|
REM Remove shim directory from PATH to prevent infinite loops
|
||||||
set "SHIM_DIR={{SHIMS_DIR}}"
|
set "SHIM_DIR=%~dp0"
|
||||||
|
if "%SHIM_DIR:~-1%"=="\" set "SHIM_DIR=%SHIM_DIR:~0,-1%"
|
||||||
call set "CLEAN_PATH=%%PATH:%SHIM_DIR%;=%%"
|
call set "CLEAN_PATH=%%PATH:%SHIM_DIR%;=%%"
|
||||||
|
|
||||||
REM Check if aikido command is available with clean PATH
|
REM Check if aikido command is available with clean PATH
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,7 @@ function createUnixShims(shimsDir) {
|
||||||
for (const toolInfo of getToolsToSetup()) {
|
for (const toolInfo of getToolsToSetup()) {
|
||||||
const shimContent = template
|
const shimContent = template
|
||||||
.replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool)
|
.replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool)
|
||||||
.replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand)
|
.replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand);
|
||||||
.replaceAll("{{SHIMS_DIR}}", shimsDir);
|
|
||||||
|
|
||||||
const shimPath = path.join(shimsDir, toolInfo.tool);
|
const shimPath = path.join(shimsDir, toolInfo.tool);
|
||||||
fs.writeFileSync(shimPath, shimContent, "utf-8");
|
fs.writeFileSync(shimPath, shimContent, "utf-8");
|
||||||
|
|
@ -109,8 +108,7 @@ function createWindowsShims(shimsDir) {
|
||||||
for (const toolInfo of getToolsToSetup()) {
|
for (const toolInfo of getToolsToSetup()) {
|
||||||
const shimContent = template
|
const shimContent = template
|
||||||
.replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool)
|
.replaceAll("{{PACKAGE_MANAGER}}", toolInfo.tool)
|
||||||
.replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand)
|
.replaceAll("{{AIKIDO_COMMAND}}", toolInfo.aikidoCommand);
|
||||||
.replaceAll("{{SHIMS_DIR}}", shimsDir);
|
|
||||||
|
|
||||||
const shimPath = `${shimsDir}/${toolInfo.tool}.cmd`;
|
const shimPath = `${shimsDir}/${toolInfo.tool}.cmd`;
|
||||||
fs.writeFileSync(shimPath, shimContent, "utf-8");
|
fs.writeFileSync(shimPath, shimContent, "utf-8");
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
# Guard against PATH separator injection: reject SAFE_CHAIN_DIR values containing ':'
|
set -l safe_chain_script (status filename)
|
||||||
set -l safe_chain_base $HOME/.safe-chain
|
set -l safe_chain_base (path dirname (path dirname $safe_chain_script))
|
||||||
if set -q SAFE_CHAIN_DIR; and not string match -q '*:*' -- $SAFE_CHAIN_DIR
|
|
||||||
set safe_chain_base $SAFE_CHAIN_DIR
|
|
||||||
end
|
|
||||||
set -gx PATH $PATH $safe_chain_base/bin
|
set -gx PATH $PATH $safe_chain_base/bin
|
||||||
|
|
||||||
function npx
|
function npx
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,22 @@
|
||||||
# Guard against PATH separator injection: reject SAFE_CHAIN_DIR values containing ':'
|
_get_safe_chain_script_path() {
|
||||||
case "${SAFE_CHAIN_DIR}" in
|
if [ -n "${BASH_SOURCE[0]:-}" ]; then
|
||||||
*:*) _sc_base="${HOME}/.safe-chain" ;;
|
printf '%s\n' "${BASH_SOURCE[0]}"
|
||||||
*) _sc_base="${SAFE_CHAIN_DIR:-${HOME}/.safe-chain}" ;;
|
return
|
||||||
esac
|
fi
|
||||||
|
|
||||||
|
if [ -n "${ZSH_VERSION:-}" ]; then
|
||||||
|
eval 'printf "%s\n" "${(%):-%N}"'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$0"
|
||||||
|
}
|
||||||
|
|
||||||
|
_sc_script_path="$(_get_safe_chain_script_path)"
|
||||||
|
_sc_scripts_dir=$(CDPATH= cd -- "$(dirname -- "$_sc_script_path")" 2>/dev/null && pwd -P)
|
||||||
|
_sc_base=$(dirname -- "$_sc_scripts_dir")
|
||||||
export PATH="$PATH:${_sc_base}/bin"
|
export PATH="$PATH:${_sc_base}/bin"
|
||||||
unset _sc_base
|
unset _sc_base _sc_script_path _sc_scripts_dir
|
||||||
|
|
||||||
function npx() {
|
function npx() {
|
||||||
wrapSafeChainCommand "npx" "$@"
|
wrapSafeChainCommand "npx" "$@"
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
# $IsWindows is only available in PowerShell Core 6.0+. If it doesn't exist, assume Windows PowerShell
|
# $IsWindows is only available in PowerShell Core 6.0+. If it doesn't exist, assume Windows PowerShell
|
||||||
$isWindowsPlatform = if (Test-Path variable:IsWindows) { $IsWindows } else { $true }
|
$isWindowsPlatform = if (Test-Path variable:IsWindows) { $IsWindows } else { $true }
|
||||||
$pathSeparator = if ($isWindowsPlatform) { ';' } else { ':' }
|
$pathSeparator = if ($isWindowsPlatform) { ';' } else { ':' }
|
||||||
# Guard against PATH separator injection: reject SAFE_CHAIN_DIR values containing the path separator
|
$safeChainBase = Split-Path -Parent $PSScriptRoot
|
||||||
$safeChainBase = if ($env:SAFE_CHAIN_DIR -and -not $env:SAFE_CHAIN_DIR.Contains($pathSeparator)) { $env:SAFE_CHAIN_DIR } else { Join-Path $HOME '.safe-chain' }
|
|
||||||
$safeChainBin = Join-Path $safeChainBase 'bin'
|
$safeChainBin = Join-Path $safeChainBase 'bin'
|
||||||
$env:PATH = "$env:PATH$pathSeparator$safeChainBin"
|
$env:PATH = "$env:PATH$pathSeparator$safeChainBin"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
doesExecutableExistOnSystem,
|
doesExecutableExistOnSystem,
|
||||||
removeLinesMatchingPattern,
|
removeLinesMatchingPattern,
|
||||||
getScriptsDir,
|
getScriptsDir,
|
||||||
getSafeChainDir,
|
|
||||||
} from "../helpers.js";
|
} from "../helpers.js";
|
||||||
import { execSync, spawnSync } from "child_process";
|
import { execSync, spawnSync } from "child_process";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
|
|
@ -54,15 +53,6 @@ function teardown(tools) {
|
||||||
function setup() {
|
function setup() {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
const customDir = getSafeChainDir();
|
|
||||||
if (customDir) {
|
|
||||||
addLineToFile(
|
|
||||||
startupFile,
|
|
||||||
`export SAFE_CHAIN_DIR="${customDir}" # Safe-chain installation directory`,
|
|
||||||
eol
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addLineToFile(
|
addLineToFile(
|
||||||
startupFile,
|
startupFile,
|
||||||
`source ${path.join(getScriptsDir(), "init-posix.sh")} # Safe-chain bash initialization script`,
|
`source ${path.join(getScriptsDir(), "init-posix.sh")} # Safe-chain bash initialization script`,
|
||||||
|
|
@ -143,18 +133,7 @@ function cygpathw(path) {
|
||||||
|
|
||||||
/** @param {string} preamble */
|
/** @param {string} preamble */
|
||||||
function buildManualInstructions(preamble) {
|
function buildManualInstructions(preamble) {
|
||||||
const customDir = getSafeChainDir();
|
const instructions = [preamble, ` source ${path.join(getScriptsDir(), "init-posix.sh")}`];
|
||||||
const instructions = [preamble];
|
|
||||||
|
|
||||||
if (customDir) {
|
|
||||||
instructions.push(
|
|
||||||
` export SAFE_CHAIN_DIR="${customDir}"`,
|
|
||||||
` source ${path.join(getScriptsDir(), "init-posix.sh")}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
instructions.push(` source ~/.safe-chain/scripts/init-posix.sh`);
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.push(`Then restart your terminal or run: source ~/.bashrc`);
|
instructions.push(`Then restart your terminal or run: source ~/.bashrc`);
|
||||||
return instructions;
|
return instructions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ describe("Bash shell integration", () => {
|
||||||
let bash;
|
let bash;
|
||||||
let windowsCygwinPath = "";
|
let windowsCygwinPath = "";
|
||||||
let platform = "linux";
|
let platform = "linux";
|
||||||
let getSafeChainDirResult = undefined;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Create temporary startup file for testing
|
// Create temporary startup file for testing
|
||||||
|
|
@ -21,7 +20,6 @@ describe("Bash shell integration", () => {
|
||||||
namedExports: {
|
namedExports: {
|
||||||
doesExecutableExistOnSystem: () => true,
|
doesExecutableExistOnSystem: () => true,
|
||||||
getScriptsDir: () => "/test-home/.safe-chain/scripts",
|
getScriptsDir: () => "/test-home/.safe-chain/scripts",
|
||||||
getSafeChainDir: () => getSafeChainDirResult,
|
|
||||||
addLineToFile: (filePath, line) => {
|
addLineToFile: (filePath, line) => {
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
fs.writeFileSync(filePath, "", "utf-8");
|
fs.writeFileSync(filePath, "", "utf-8");
|
||||||
|
|
@ -91,7 +89,6 @@ describe("Bash shell integration", () => {
|
||||||
// Reset mocks
|
// Reset mocks
|
||||||
mock.reset();
|
mock.reset();
|
||||||
platform = "linux";
|
platform = "linux";
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isInstalled", () => {
|
describe("isInstalled", () => {
|
||||||
|
|
@ -203,26 +200,18 @@ describe("Bash shell integration", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SAFE_CHAIN_DIR", () => {
|
describe("custom install dir", () => {
|
||||||
it("should write export line to rc file when custom dir is set", () => {
|
it("writes only the source line to the rc file", () => {
|
||||||
getSafeChainDirResult = "/custom/safe-chain";
|
|
||||||
bash.setup();
|
bash.setup();
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes('export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory')
|
content.includes("source /test-home/.safe-chain/scripts/init-posix.sh")
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it("should not write export line when no custom dir is set", () => {
|
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
bash.setup();
|
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove export line on teardown", () => {
|
it("removes legacy export lines on teardown", () => {
|
||||||
const initialContent = [
|
const initialContent = [
|
||||||
'#!/bin/bash',
|
'#!/bin/bash',
|
||||||
'export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory',
|
'export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory',
|
||||||
|
|
@ -236,12 +225,9 @@ describe("Bash shell integration", () => {
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show custom manual setup instructions when custom dir is set", () => {
|
it("shows source-only manual setup instructions", () => {
|
||||||
getSafeChainDirResult = "/custom/safe-chain";
|
|
||||||
|
|
||||||
assert.deepStrictEqual(bash.getManualSetupInstructions(), [
|
assert.deepStrictEqual(bash.getManualSetupInstructions(), [
|
||||||
"Add the following line to your ~/.bashrc file:",
|
"Add the following line to your ~/.bashrc file:",
|
||||||
' export SAFE_CHAIN_DIR="/custom/safe-chain"',
|
|
||||||
" source /test-home/.safe-chain/scripts/init-posix.sh",
|
" source /test-home/.safe-chain/scripts/init-posix.sh",
|
||||||
"Then restart your terminal or run: source ~/.bashrc",
|
"Then restart your terminal or run: source ~/.bashrc",
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
doesExecutableExistOnSystem,
|
doesExecutableExistOnSystem,
|
||||||
removeLinesMatchingPattern,
|
removeLinesMatchingPattern,
|
||||||
getScriptsDir,
|
getScriptsDir,
|
||||||
getSafeChainDir,
|
|
||||||
} from "../helpers.js";
|
} from "../helpers.js";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
@ -53,15 +52,6 @@ function teardown(tools) {
|
||||||
function setup() {
|
function setup() {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
const customDir = getSafeChainDir();
|
|
||||||
if (customDir) {
|
|
||||||
addLineToFile(
|
|
||||||
startupFile,
|
|
||||||
`set -gx SAFE_CHAIN_DIR "${customDir}" # Safe-chain installation directory`,
|
|
||||||
eol
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addLineToFile(
|
addLineToFile(
|
||||||
startupFile,
|
startupFile,
|
||||||
`source ${path.join(getScriptsDir(), "init-fish.fish")} # Safe-chain Fish initialization script`,
|
`source ${path.join(getScriptsDir(), "init-fish.fish")} # Safe-chain Fish initialization script`,
|
||||||
|
|
@ -86,18 +76,7 @@ function getStartupFile() {
|
||||||
|
|
||||||
/** @param {string} preamble */
|
/** @param {string} preamble */
|
||||||
function buildManualInstructions(preamble) {
|
function buildManualInstructions(preamble) {
|
||||||
const customDir = getSafeChainDir();
|
const instructions = [preamble, ` source ${path.join(getScriptsDir(), "init-fish.fish")}`];
|
||||||
const instructions = [preamble];
|
|
||||||
|
|
||||||
if (customDir) {
|
|
||||||
instructions.push(
|
|
||||||
` set -gx SAFE_CHAIN_DIR "${customDir}"`,
|
|
||||||
` source ${path.join(getScriptsDir(), "init-fish.fish")}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
instructions.push(` source ~/.safe-chain/scripts/init-fish.fish`);
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.push(
|
instructions.push(
|
||||||
`Then restart your terminal or run: source ~/.config/fish/config.fish`,
|
`Then restart your terminal or run: source ~/.config/fish/config.fish`,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { knownAikidoTools } from "../helpers.js";
|
||||||
describe("Fish shell integration", () => {
|
describe("Fish shell integration", () => {
|
||||||
let mockStartupFile;
|
let mockStartupFile;
|
||||||
let fish;
|
let fish;
|
||||||
let getSafeChainDirResult = undefined;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Create temporary startup file for testing
|
// Create temporary startup file for testing
|
||||||
|
|
@ -19,7 +18,6 @@ describe("Fish shell integration", () => {
|
||||||
namedExports: {
|
namedExports: {
|
||||||
doesExecutableExistOnSystem: () => true,
|
doesExecutableExistOnSystem: () => true,
|
||||||
getScriptsDir: () => "/test-home/.safe-chain/scripts",
|
getScriptsDir: () => "/test-home/.safe-chain/scripts",
|
||||||
getSafeChainDir: () => getSafeChainDirResult,
|
|
||||||
addLineToFile: (filePath, line) => {
|
addLineToFile: (filePath, line) => {
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
fs.writeFileSync(filePath, "", "utf-8");
|
fs.writeFileSync(filePath, "", "utf-8");
|
||||||
|
|
@ -55,7 +53,6 @@ describe("Fish shell integration", () => {
|
||||||
|
|
||||||
// Reset mocks
|
// Reset mocks
|
||||||
mock.reset();
|
mock.reset();
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isInstalled", () => {
|
describe("isInstalled", () => {
|
||||||
|
|
@ -156,26 +153,18 @@ describe("Fish shell integration", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SAFE_CHAIN_DIR", () => {
|
describe("custom install dir", () => {
|
||||||
it("should write set line to config file when custom dir is set", () => {
|
it("writes only the source line to the config file", () => {
|
||||||
getSafeChainDirResult = "/custom/safe-chain";
|
|
||||||
fish.setup();
|
fish.setup();
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes('set -gx SAFE_CHAIN_DIR "/custom/safe-chain" # Safe-chain installation directory')
|
content.includes("source /test-home/.safe-chain/scripts/init-fish.fish")
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it("should not write set line when no custom dir is set", () => {
|
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
fish.setup();
|
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove set line on teardown", () => {
|
it("removes legacy set lines on teardown", () => {
|
||||||
const initialContent = [
|
const initialContent = [
|
||||||
'set -gx SAFE_CHAIN_DIR "/custom/safe-chain" # Safe-chain installation directory',
|
'set -gx SAFE_CHAIN_DIR "/custom/safe-chain" # Safe-chain installation directory',
|
||||||
"source /test-home/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script",
|
"source /test-home/.safe-chain/scripts/init-fish.fish # Safe-chain Fish initialization script",
|
||||||
|
|
@ -188,12 +177,9 @@ describe("Fish shell integration", () => {
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show custom manual setup instructions when custom dir is set", () => {
|
it("shows source-only manual setup instructions", () => {
|
||||||
getSafeChainDirResult = "/custom/safe-chain";
|
|
||||||
|
|
||||||
assert.deepStrictEqual(fish.getManualSetupInstructions(), [
|
assert.deepStrictEqual(fish.getManualSetupInstructions(), [
|
||||||
"Add the following line to your ~/.config/fish/config.fish file:",
|
"Add the following line to your ~/.config/fish/config.fish file:",
|
||||||
' set -gx SAFE_CHAIN_DIR "/custom/safe-chain"',
|
|
||||||
" source /test-home/.safe-chain/scripts/init-fish.fish",
|
" source /test-home/.safe-chain/scripts/init-fish.fish",
|
||||||
"Then restart your terminal or run: source ~/.config/fish/config.fish",
|
"Then restart your terminal or run: source ~/.config/fish/config.fish",
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import {
|
||||||
removeLinesMatchingPattern,
|
removeLinesMatchingPattern,
|
||||||
validatePowerShellExecutionPolicy,
|
validatePowerShellExecutionPolicy,
|
||||||
getScriptsDir,
|
getScriptsDir,
|
||||||
getSafeChainDir,
|
|
||||||
} from "../helpers.js";
|
} from "../helpers.js";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
@ -58,14 +57,6 @@ async function setup() {
|
||||||
|
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
const customDir = getSafeChainDir();
|
|
||||||
if (customDir) {
|
|
||||||
addLineToFile(
|
|
||||||
startupFile,
|
|
||||||
`$env:SAFE_CHAIN_DIR = '${customDir}' # Safe-chain installation directory`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addLineToFile(
|
addLineToFile(
|
||||||
startupFile,
|
startupFile,
|
||||||
`. "${path.join(getScriptsDir(), "init-pwsh.ps1")}" # Safe-chain PowerShell initialization script`,
|
`. "${path.join(getScriptsDir(), "init-pwsh.ps1")}" # Safe-chain PowerShell initialization script`,
|
||||||
|
|
@ -89,18 +80,7 @@ function getStartupFile() {
|
||||||
|
|
||||||
/** @param {string} preamble */
|
/** @param {string} preamble */
|
||||||
function buildManualInstructions(preamble) {
|
function buildManualInstructions(preamble) {
|
||||||
const customDir = getSafeChainDir();
|
const instructions = [preamble, ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`];
|
||||||
const instructions = [preamble];
|
|
||||||
|
|
||||||
if (customDir) {
|
|
||||||
instructions.push(
|
|
||||||
` $env:SAFE_CHAIN_DIR = '${customDir}'`,
|
|
||||||
` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
instructions.push(` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.push(`Then restart your terminal or run: . $PROFILE`);
|
instructions.push(`Then restart your terminal or run: . $PROFILE`);
|
||||||
return instructions;
|
return instructions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ describe("PowerShell Core shell integration", () => {
|
||||||
let mockStartupFile;
|
let mockStartupFile;
|
||||||
let powershell;
|
let powershell;
|
||||||
let executionPolicyResult;
|
let executionPolicyResult;
|
||||||
let getSafeChainDirResult = undefined;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Create temporary startup file for testing
|
// Create temporary startup file for testing
|
||||||
|
|
@ -27,7 +26,6 @@ describe("PowerShell Core shell integration", () => {
|
||||||
mock.module("../helpers.js", {
|
mock.module("../helpers.js", {
|
||||||
namedExports: {
|
namedExports: {
|
||||||
doesExecutableExistOnSystem: () => true,
|
doesExecutableExistOnSystem: () => true,
|
||||||
getSafeChainDir: () => getSafeChainDirResult,
|
|
||||||
addLineToFile: (filePath, line) => {
|
addLineToFile: (filePath, line) => {
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
fs.writeFileSync(filePath, "", "utf-8");
|
fs.writeFileSync(filePath, "", "utf-8");
|
||||||
|
|
@ -65,7 +63,6 @@ describe("PowerShell Core shell integration", () => {
|
||||||
|
|
||||||
// Reset mocks
|
// Reset mocks
|
||||||
mock.reset();
|
mock.reset();
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isInstalled", () => {
|
describe("isInstalled", () => {
|
||||||
|
|
@ -209,26 +206,18 @@ describe("PowerShell Core shell integration", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SAFE_CHAIN_DIR", () => {
|
describe("custom install dir", () => {
|
||||||
it("should write $env:SAFE_CHAIN_DIR line to profile when custom dir is set", async () => {
|
it("writes only the source line to the profile", async () => {
|
||||||
getSafeChainDirResult = "C:\\custom\\safe-chain";
|
|
||||||
await powershell.setup();
|
await powershell.setup();
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes("$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory")
|
content.includes('. "/test-home/.safe-chain/scripts/init-pwsh.ps1"')
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it("should not write $env:SAFE_CHAIN_DIR line when no custom dir is set", async () => {
|
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
await powershell.setup();
|
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove $env:SAFE_CHAIN_DIR line on teardown", () => {
|
it("removes legacy env lines on teardown", () => {
|
||||||
const initialContent = [
|
const initialContent = [
|
||||||
"# PowerShell profile",
|
"# PowerShell profile",
|
||||||
"$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory",
|
"$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory",
|
||||||
|
|
@ -242,12 +231,9 @@ describe("PowerShell Core shell integration", () => {
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show custom manual setup instructions when custom dir is set", () => {
|
it("shows source-only manual setup instructions", () => {
|
||||||
getSafeChainDirResult = "C:\\custom\\safe-chain";
|
|
||||||
|
|
||||||
assert.deepStrictEqual(powershell.getManualSetupInstructions(), [
|
assert.deepStrictEqual(powershell.getManualSetupInstructions(), [
|
||||||
'Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):',
|
'Add the following line to your PowerShell profile (run "echo $PROFILE" to find its location):',
|
||||||
" $env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain'",
|
|
||||||
' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"',
|
' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"',
|
||||||
"Then restart your terminal or run: . $PROFILE",
|
"Then restart your terminal or run: . $PROFILE",
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import {
|
||||||
removeLinesMatchingPattern,
|
removeLinesMatchingPattern,
|
||||||
validatePowerShellExecutionPolicy,
|
validatePowerShellExecutionPolicy,
|
||||||
getScriptsDir,
|
getScriptsDir,
|
||||||
getSafeChainDir,
|
|
||||||
} from "../helpers.js";
|
} from "../helpers.js";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
@ -58,14 +57,6 @@ async function setup() {
|
||||||
|
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
const customDir = getSafeChainDir();
|
|
||||||
if (customDir) {
|
|
||||||
addLineToFile(
|
|
||||||
startupFile,
|
|
||||||
`$env:SAFE_CHAIN_DIR = '${customDir}' # Safe-chain installation directory`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addLineToFile(
|
addLineToFile(
|
||||||
startupFile,
|
startupFile,
|
||||||
`. "${path.join(getScriptsDir(), "init-pwsh.ps1")}" # Safe-chain PowerShell initialization script`,
|
`. "${path.join(getScriptsDir(), "init-pwsh.ps1")}" # Safe-chain PowerShell initialization script`,
|
||||||
|
|
@ -89,18 +80,7 @@ function getStartupFile() {
|
||||||
|
|
||||||
/** @param {string} preamble */
|
/** @param {string} preamble */
|
||||||
function buildManualInstructions(preamble) {
|
function buildManualInstructions(preamble) {
|
||||||
const customDir = getSafeChainDir();
|
const instructions = [preamble, ` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`];
|
||||||
const instructions = [preamble];
|
|
||||||
|
|
||||||
if (customDir) {
|
|
||||||
instructions.push(
|
|
||||||
` $env:SAFE_CHAIN_DIR = '${customDir}'`,
|
|
||||||
` . "${path.join(getScriptsDir(), "init-pwsh.ps1")}"`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
instructions.push(` . "$HOME\\.safe-chain\\scripts\\init-pwsh.ps1"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.push(`Then restart your terminal or run: . $PROFILE`);
|
instructions.push(`Then restart your terminal or run: . $PROFILE`);
|
||||||
return instructions;
|
return instructions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ describe("Windows PowerShell shell integration", () => {
|
||||||
let mockStartupFile;
|
let mockStartupFile;
|
||||||
let windowsPowershell;
|
let windowsPowershell;
|
||||||
let executionPolicyResult;
|
let executionPolicyResult;
|
||||||
let getSafeChainDirResult = undefined;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Create temporary startup file for testing
|
// Create temporary startup file for testing
|
||||||
|
|
@ -27,7 +26,6 @@ describe("Windows PowerShell shell integration", () => {
|
||||||
mock.module("../helpers.js", {
|
mock.module("../helpers.js", {
|
||||||
namedExports: {
|
namedExports: {
|
||||||
doesExecutableExistOnSystem: () => true,
|
doesExecutableExistOnSystem: () => true,
|
||||||
getSafeChainDir: () => getSafeChainDirResult,
|
|
||||||
addLineToFile: (filePath, line) => {
|
addLineToFile: (filePath, line) => {
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
fs.writeFileSync(filePath, "", "utf-8");
|
fs.writeFileSync(filePath, "", "utf-8");
|
||||||
|
|
@ -65,7 +63,6 @@ describe("Windows PowerShell shell integration", () => {
|
||||||
|
|
||||||
// Reset mocks
|
// Reset mocks
|
||||||
mock.reset();
|
mock.reset();
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isInstalled", () => {
|
describe("isInstalled", () => {
|
||||||
|
|
@ -209,26 +206,18 @@ describe("Windows PowerShell shell integration", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SAFE_CHAIN_DIR", () => {
|
describe("custom install dir", () => {
|
||||||
it("should write $env:SAFE_CHAIN_DIR line to profile when custom dir is set", async () => {
|
it("writes only the source line to the profile", async () => {
|
||||||
getSafeChainDirResult = "C:\\custom\\safe-chain";
|
|
||||||
await windowsPowershell.setup();
|
await windowsPowershell.setup();
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes("$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory")
|
content.includes('. "/test-home/.safe-chain/scripts/init-pwsh.ps1"')
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it("should not write $env:SAFE_CHAIN_DIR line when no custom dir is set", async () => {
|
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
await windowsPowershell.setup();
|
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove $env:SAFE_CHAIN_DIR line on teardown", () => {
|
it("removes legacy env lines on teardown", () => {
|
||||||
const initialContent = [
|
const initialContent = [
|
||||||
"# Windows PowerShell profile",
|
"# Windows PowerShell profile",
|
||||||
"$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory",
|
"$env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain' # Safe-chain installation directory",
|
||||||
|
|
@ -242,12 +231,9 @@ describe("Windows PowerShell shell integration", () => {
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show custom manual teardown instructions when custom dir is set", () => {
|
it("shows source-only manual teardown instructions", () => {
|
||||||
getSafeChainDirResult = "C:\\custom\\safe-chain";
|
|
||||||
|
|
||||||
assert.deepStrictEqual(windowsPowershell.getManualTeardownInstructions(), [
|
assert.deepStrictEqual(windowsPowershell.getManualTeardownInstructions(), [
|
||||||
'Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):',
|
'Remove the following line from your PowerShell profile (run "echo $PROFILE" to find its location):',
|
||||||
" $env:SAFE_CHAIN_DIR = 'C:\\custom\\safe-chain'",
|
|
||||||
' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"',
|
' . "/test-home/.safe-chain/scripts/init-pwsh.ps1"',
|
||||||
"Then restart your terminal or run: . $PROFILE",
|
"Then restart your terminal or run: . $PROFILE",
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import {
|
||||||
doesExecutableExistOnSystem,
|
doesExecutableExistOnSystem,
|
||||||
removeLinesMatchingPattern,
|
removeLinesMatchingPattern,
|
||||||
getScriptsDir,
|
getScriptsDir,
|
||||||
getSafeChainDir,
|
|
||||||
} from "../helpers.js";
|
} from "../helpers.js";
|
||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
@ -53,15 +52,6 @@ function teardown(tools) {
|
||||||
function setup() {
|
function setup() {
|
||||||
const startupFile = getStartupFile();
|
const startupFile = getStartupFile();
|
||||||
|
|
||||||
const customDir = getSafeChainDir();
|
|
||||||
if (customDir) {
|
|
||||||
addLineToFile(
|
|
||||||
startupFile,
|
|
||||||
`export SAFE_CHAIN_DIR="${customDir}" # Safe-chain installation directory`,
|
|
||||||
eol
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addLineToFile(
|
addLineToFile(
|
||||||
startupFile,
|
startupFile,
|
||||||
`source ${path.join(getScriptsDir(), "init-posix.sh")} # Safe-chain Zsh initialization script`,
|
`source ${path.join(getScriptsDir(), "init-posix.sh")} # Safe-chain Zsh initialization script`,
|
||||||
|
|
@ -86,18 +76,7 @@ function getStartupFile() {
|
||||||
|
|
||||||
/** @param {string} preamble */
|
/** @param {string} preamble */
|
||||||
function buildManualInstructions(preamble) {
|
function buildManualInstructions(preamble) {
|
||||||
const customDir = getSafeChainDir();
|
const instructions = [preamble, ` source ${path.join(getScriptsDir(), "init-posix.sh")}`];
|
||||||
const instructions = [preamble];
|
|
||||||
|
|
||||||
if (customDir) {
|
|
||||||
instructions.push(
|
|
||||||
` export SAFE_CHAIN_DIR="${customDir}"`,
|
|
||||||
` source ${path.join(getScriptsDir(), "init-posix.sh")}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
instructions.push(` source ~/.safe-chain/scripts/init-posix.sh`);
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.push(`Then restart your terminal or run: source ~/.zshrc`);
|
instructions.push(`Then restart your terminal or run: source ~/.zshrc`);
|
||||||
return instructions;
|
return instructions;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { knownAikidoTools } from "../helpers.js";
|
||||||
describe("Zsh shell integration", () => {
|
describe("Zsh shell integration", () => {
|
||||||
let mockStartupFile;
|
let mockStartupFile;
|
||||||
let zsh;
|
let zsh;
|
||||||
let getSafeChainDirResult = undefined;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Create temporary startup file for testing
|
// Create temporary startup file for testing
|
||||||
|
|
@ -19,7 +18,6 @@ describe("Zsh shell integration", () => {
|
||||||
namedExports: {
|
namedExports: {
|
||||||
doesExecutableExistOnSystem: () => true,
|
doesExecutableExistOnSystem: () => true,
|
||||||
getScriptsDir: () => "/test-home/.safe-chain/scripts",
|
getScriptsDir: () => "/test-home/.safe-chain/scripts",
|
||||||
getSafeChainDir: () => getSafeChainDirResult,
|
|
||||||
addLineToFile: (filePath, line) => {
|
addLineToFile: (filePath, line) => {
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
fs.writeFileSync(filePath, "", "utf-8");
|
fs.writeFileSync(filePath, "", "utf-8");
|
||||||
|
|
@ -55,7 +53,6 @@ describe("Zsh shell integration", () => {
|
||||||
|
|
||||||
// Reset mocks
|
// Reset mocks
|
||||||
mock.reset();
|
mock.reset();
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("isInstalled", () => {
|
describe("isInstalled", () => {
|
||||||
|
|
@ -174,26 +171,18 @@ describe("Zsh shell integration", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("SAFE_CHAIN_DIR", () => {
|
describe("custom install dir", () => {
|
||||||
it("should write export line to rc file when custom dir is set", () => {
|
it("writes only the source line to the rc file", () => {
|
||||||
getSafeChainDirResult = "/custom/safe-chain";
|
|
||||||
zsh.setup();
|
zsh.setup();
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes('export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory')
|
content.includes("source /test-home/.safe-chain/scripts/init-posix.sh")
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
it("should not write export line when no custom dir is set", () => {
|
|
||||||
getSafeChainDirResult = undefined;
|
|
||||||
zsh.setup();
|
|
||||||
|
|
||||||
const content = fs.readFileSync(mockStartupFile, "utf-8");
|
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should remove export line on teardown", () => {
|
it("removes legacy export lines on teardown", () => {
|
||||||
const initialContent = [
|
const initialContent = [
|
||||||
"#!/bin/zsh",
|
"#!/bin/zsh",
|
||||||
'export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory',
|
'export SAFE_CHAIN_DIR="/custom/safe-chain" # Safe-chain installation directory',
|
||||||
|
|
@ -207,12 +196,9 @@ describe("Zsh shell integration", () => {
|
||||||
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
assert.ok(!content.includes("SAFE_CHAIN_DIR"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show custom manual teardown instructions when custom dir is set", () => {
|
it("shows source-only manual teardown instructions", () => {
|
||||||
getSafeChainDirResult = "/custom/safe-chain";
|
|
||||||
|
|
||||||
assert.deepStrictEqual(zsh.getManualTeardownInstructions(), [
|
assert.deepStrictEqual(zsh.getManualTeardownInstructions(), [
|
||||||
"Remove the following line from your ~/.zshrc file:",
|
"Remove the following line from your ~/.zshrc file:",
|
||||||
' export SAFE_CHAIN_DIR="/custom/safe-chain"',
|
|
||||||
" source /test-home/.safe-chain/scripts/init-posix.sh",
|
" source /test-home/.safe-chain/scripts/init-posix.sh",
|
||||||
"Then restart your terminal or run: source ~/.zshrc",
|
"Then restart your terminal or run: source ~/.zshrc",
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue