mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
Merge branch 'main' into feat/support-insight-command
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
commit
a172696b86
150 changed files with 9730 additions and 2047 deletions
|
|
@ -67,13 +67,16 @@ register('${loaderUrl}', pathToFileURL('./'));
|
|||
`;
|
||||
writeFileSync(registerPath, registerCode);
|
||||
|
||||
// Preserve existing NODE_OPTIONS (e.g. VS Code debugger injects --inspect flags via NODE_OPTIONS)
|
||||
const existingNodeOptions = process.env.NODE_OPTIONS || '';
|
||||
const importFlag = `--import ${pathToFileURL(registerPath).href}`;
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
DEV: 'true',
|
||||
CLI_VERSION: 'dev',
|
||||
NODE_ENV: 'development',
|
||||
// Use --import with register() instead of deprecated --loader
|
||||
NODE_OPTIONS: `--import ${pathToFileURL(registerPath).href}`,
|
||||
NODE_OPTIONS: `${existingNodeOptions} ${importFlag}`.trim(),
|
||||
};
|
||||
|
||||
const nodeArgs = [tsxPath, cliEntry, ...process.argv.slice(2)];
|
||||
|
|
|
|||
|
|
@ -1,454 +1,553 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script to install Node.js and Qwen Code with source information
|
||||
# This script handles the installation process and sets the installation source
|
||||
# Qwen Code Installation Script
|
||||
# This script installs Node.js (via NVM) and Qwen Code CLI
|
||||
# Supports Linux and macOS
|
||||
#
|
||||
# Usage: install-qwen-with-source.sh --source [github|npm|internal|local-build]
|
||||
# install-qwen-with-source.sh -s [github|npm|internal|local-build]
|
||||
|
||||
# Disable pagers to prevent interactive prompts
|
||||
export GIT_PAGER=cat
|
||||
export PAGER=cat
|
||||
# Re-execute with bash if running with sh or other shells
|
||||
# This block must use POSIX-compliant syntax ([ not [[) since it runs before we know bash is available
|
||||
if [ -z "${BASH_VERSION}" ] && [ -z "${__QWEN_INSTALL_REEXEC:-}" ]; then
|
||||
# Check if we're in a git hook environment
|
||||
case "${0}" in
|
||||
*.git/hooks/*) export __QWEN_IN_GIT_HOOK=1 ;;
|
||||
esac
|
||||
if [ -n "${GIT_DIR:-}" ]; then
|
||||
export __QWEN_IN_GIT_HOOK=1
|
||||
fi
|
||||
|
||||
# Function to display usage
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -s, --source SOURCE Specify the installation source (e.g., github, npm, internal)"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
exit 1
|
||||
# Try to find bash
|
||||
if command -v bash >/dev/null 2>&1; then
|
||||
export __QWEN_INSTALL_REEXEC=1
|
||||
# Re-exec with bash, preserving all arguments
|
||||
exec bash -- "${0}" "$@"
|
||||
else
|
||||
echo "Error: This script requires bash. Please install bash first."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Enable strict mode (bash-specific options)
|
||||
# pipefail requires bash 3+; check before setting
|
||||
if [ -n "${BASH_VERSION:-}" ]; then
|
||||
# shellcheck disable=SC3040
|
||||
set -eo pipefail
|
||||
else
|
||||
set -e
|
||||
fi
|
||||
|
||||
# ============================================
|
||||
# Color definitions
|
||||
# ============================================
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# ============================================
|
||||
# Log functions
|
||||
# ============================================
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Utility functions
|
||||
# ============================================
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
get_shell_profile() {
|
||||
local current_shell
|
||||
current_shell=$(basename "${SHELL}")
|
||||
case "${current_shell}" in
|
||||
bash)
|
||||
echo "${HOME}/.bashrc"
|
||||
;;
|
||||
zsh)
|
||||
echo "${HOME}/.zshrc"
|
||||
;;
|
||||
fish)
|
||||
echo "${HOME}/.config/fish/config.fish"
|
||||
;;
|
||||
*)
|
||||
echo "${HOME}/.profile"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Parse command line arguments
|
||||
# ============================================
|
||||
SOURCE="unknown"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-s|--source)
|
||||
if [ -z "$2" ] || [[ "$2" == -* ]]; then
|
||||
echo "Error: --source requires a value"
|
||||
usage
|
||||
if [[ -z "$2" ]] || [[ "$2" == -* ]]; then
|
||||
log_error "--source requires a value"
|
||||
exit 1
|
||||
fi
|
||||
SOURCE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " -s, --source SOURCE Specify the installation source (e.g., github, npm, internal)"
|
||||
echo " -h, --help Show this help message"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
log_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "==========================================="
|
||||
echo "Qwen Code Installation Script with Source Tracking"
|
||||
echo "==========================================="
|
||||
# ============================================
|
||||
# Print header
|
||||
# ============================================
|
||||
echo "=========================================="
|
||||
echo " Qwen Code Installation Script"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
log_info "System: $(uname -s) $(uname -r)" || true
|
||||
log_info "Shell: $(basename "${SHELL}")"
|
||||
echo ""
|
||||
|
||||
# Function to check if a command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
# ============================================
|
||||
# Ensure download tool is available
|
||||
# ============================================
|
||||
ensure_download_tool() {
|
||||
if command_exists curl; then
|
||||
DOWNLOAD_CMD="curl"
|
||||
DOWNLOAD_ARGS="-fsSL"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command_exists wget; then
|
||||
DOWNLOAD_CMD="wget"
|
||||
DOWNLOAD_ARGS="-qO -"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_error "Neither curl nor wget found"
|
||||
log_info "Please install curl or wget manually:"
|
||||
echo " - macOS: brew install curl"
|
||||
echo " - Ubuntu/Debian: sudo apt-get install curl"
|
||||
echo " - CentOS/RHEL: sudo yum install curl"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to check and install Node.js
|
||||
install_nodejs() {
|
||||
if command_exists node; then
|
||||
NODE_VERSION=$(node --version)
|
||||
# Extract major version number (remove 'v' prefix and get first number)
|
||||
NODE_MAJOR_VERSION=$(echo "$NODE_VERSION" | sed 's/v//' | cut -d'.' -f1)
|
||||
|
||||
# Check if NODE_MAJOR_VERSION is a valid number
|
||||
if ! [[ "$NODE_MAJOR_VERSION" =~ ^[0-9]+$ ]]; then
|
||||
echo "⚠ Could not parse Node.js version: $NODE_VERSION"
|
||||
echo "Installing Node.js 20+..."
|
||||
install_nodejs_via_nvm
|
||||
elif [ "$NODE_MAJOR_VERSION" -ge 20 ]; then
|
||||
echo "✓ Node.js is already installed: $NODE_VERSION"
|
||||
|
||||
# Check npm after confirming Node.js exists
|
||||
if ! command_exists npm; then
|
||||
echo "⚠ npm not found, installing npm..."
|
||||
if install_npm_only; then
|
||||
echo "✓ npm installation completed"
|
||||
else
|
||||
echo "✗ Failed to install npm"
|
||||
echo "Please install npm manually or reinstall Node.js from: https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
NPM_VERSION=$(npm --version 2>/dev/null)
|
||||
if [ $? -eq 0 ] && [ -n "$NPM_VERSION" ]; then
|
||||
echo "✓ npm v$NPM_VERSION is available"
|
||||
else
|
||||
echo "⚠ npm exists but cannot execute, reinstalling..."
|
||||
if install_npm_only; then
|
||||
echo "✓ npm installation fixed"
|
||||
else
|
||||
echo "✗ Failed to fix npm"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
echo "⚠ Node.js $NODE_VERSION is installed, but Qwen Code requires Node.js 20+"
|
||||
echo "Installing Node.js 20+..."
|
||||
install_nodejs_via_nvm
|
||||
fi
|
||||
# ============================================
|
||||
# Clean npm configuration conflicts
|
||||
# ============================================
|
||||
clean_npmrc_conflict() {
|
||||
local npmrc="${HOME}/.npmrc"
|
||||
if [[ -f "${npmrc}" ]]; then
|
||||
log_info "Cleaning npmrc conflicts..."
|
||||
grep -Ev '^(prefix|globalconfig) *= *' "${npmrc}" > "${npmrc}.tmp" || true
|
||||
mv -f "${npmrc}.tmp" "${npmrc}" || true
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Install NVM
|
||||
# ============================================
|
||||
install_nvm() {
|
||||
local NVM_DIR="${NVM_DIR:-${HOME}/.nvm}"
|
||||
local NVM_VERSION="${NVM_VERSION:-v0.40.3}"
|
||||
|
||||
if [[ -s "${NVM_DIR}/nvm.sh" ]]; then
|
||||
log_info "NVM is already installed at ${NVM_DIR}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing NVM ${NVM_VERSION}..."
|
||||
|
||||
# Download and install NVM from Aliyun OSS
|
||||
# Use temporary file instead of pipe to avoid potential subshell issues
|
||||
local NVM_INSTALL_TEMP
|
||||
NVM_INSTALL_TEMP=$(mktemp)
|
||||
if "${DOWNLOAD_CMD}" "${DOWNLOAD_ARGS}" "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh" > "${NVM_INSTALL_TEMP}"; then
|
||||
# Run the script in current shell environment
|
||||
# shellcheck source=/dev/null
|
||||
. "${NVM_INSTALL_TEMP}"
|
||||
rm -f "${NVM_INSTALL_TEMP}"
|
||||
log_success "NVM installed successfully"
|
||||
else
|
||||
echo "Installing Node.js 20+..."
|
||||
install_nodejs_via_nvm
|
||||
rm -f "${NVM_INSTALL_TEMP}"
|
||||
log_error "Failed to install NVM"
|
||||
log_info "Please install NVM manually: https://github.com/nvm-sh/nvm#install--update-script"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to check if NVM installation is complete
|
||||
check_nvm_complete() {
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
|
||||
if [ ! -d "$NVM_DIR" ]; then
|
||||
return 1
|
||||
# Configure shell profile
|
||||
local PROFILE_FILE
|
||||
PROFILE_FILE=$(get_shell_profile)
|
||||
|
||||
# Check if profile file is writable
|
||||
if [[ -f "${PROFILE_FILE}" ]] && [[ ! -w "${PROFILE_FILE}" ]]; then
|
||||
log_warning "Cannot write to ${PROFILE_FILE} (permission denied)"
|
||||
log_info "Skipping shell profile configuration"
|
||||
log_info "You may need to manually add NVM configuration to your shell profile"
|
||||
elif ! grep -q 'NVM_DIR' "${PROFILE_FILE}" 2>/dev/null; then
|
||||
# shellcheck disable=SC2016
|
||||
# The following echo statements intentionally use single quotes to write literal strings
|
||||
{
|
||||
echo ""
|
||||
echo "# NVM configuration (added by Qwen Code installer)"
|
||||
echo "export NVM_DIR=\"\$HOME/.nvm\""
|
||||
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"'
|
||||
echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"'
|
||||
} >> "${PROFILE_FILE}" 2>/dev/null || {
|
||||
log_warning "Failed to write to ${PROFILE_FILE}"
|
||||
log_info "Skipping shell profile configuration"
|
||||
return 0
|
||||
}
|
||||
log_info "Added NVM config to ${PROFILE_FILE}"
|
||||
fi
|
||||
|
||||
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
|
||||
echo "⚠ Incomplete NVM: nvm.sh missing"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! \. "$NVM_DIR/nvm.sh" 2>/dev/null; then
|
||||
echo "⚠ Corrupted NVM: cannot load nvm.sh"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! command_exists nvm; then
|
||||
echo "⚠ Incomplete NVM: nvm command unavailable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
# Load NVM for current session
|
||||
export NVM_DIR="${NVM_DIR}"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh"
|
||||
|
||||
log_success "NVM configured successfully"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to uninstall NVM
|
||||
uninstall_nvm() {
|
||||
echo "Uninstalling NVM..."
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
# ============================================
|
||||
# Install Node.js via NVM
|
||||
# ============================================
|
||||
install_nodejs_with_nvm() {
|
||||
local NODE_VERSION="${NODE_VERSION:-20}"
|
||||
local NVM_DIR="${NVM_DIR:-${HOME}/.nvm}"
|
||||
|
||||
if [ -d "$NVM_DIR" ]; then
|
||||
# Try to remove the directory, check for errors
|
||||
if ! rm -rf "$NVM_DIR" 2>/dev/null; then
|
||||
echo "⚠ Failed to remove NVM directory (permission denied or files in use)"
|
||||
echo " Attempting with elevated permissions..."
|
||||
# Try with sudo if available
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
sudo rm -rf "$NVM_DIR" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
# Ensure NVM is loaded
|
||||
export NVM_DIR="${NVM_DIR}"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh"
|
||||
|
||||
# Verify removal
|
||||
if [ -d "$NVM_DIR" ]; then
|
||||
echo "⚠ Warning: Could not fully remove NVM directory at $NVM_DIR"
|
||||
echo " Some files may be in use by other processes."
|
||||
echo " Continuing anyway, but installation may fail..."
|
||||
else
|
||||
echo "✓ Removed NVM directory"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Clean shell configs
|
||||
for config in "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.zshrc" "$HOME/.profile"; do
|
||||
if [ -f "$config" ]; then
|
||||
cp "$config" "${config}.bak.$(date +%s)" 2>/dev/null
|
||||
sed -i.tmp '/NVM_DIR/d; /nvm.sh/d; /bash_completion/d' "$config" 2>/dev/null || \
|
||||
sed -i '' '/NVM_DIR/d; /nvm.sh/d; /bash_completion/d' "$config" 2>/dev/null
|
||||
rm -f "${config}.tmp" 2>/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# Unset nvm function to avoid conflicts with reinstallation
|
||||
unset -f nvm 2>/dev/null || true
|
||||
|
||||
echo "✓ Cleaned NVM configuration"
|
||||
}
|
||||
|
||||
# Function to install npm only
|
||||
install_npm_only() {
|
||||
echo "Installing npm separately..."
|
||||
|
||||
if command_exists curl; then
|
||||
echo "Attempting to install npm using: curl -qL https://www.npmjs.com/install.sh | sh"
|
||||
if curl -qL https://www.npmjs.com/install.sh | sh; then
|
||||
if command_exists npm && [ -n "$(npm --version 2>/dev/null)" ]; then
|
||||
echo "✓ npm v$(npm --version) installed via direct install script"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "curl command not found, proceeding with alternative methods"
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to install Node.js via nvm
|
||||
install_nodejs_via_nvm() {
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
|
||||
# Check NVM completeness
|
||||
if [ -d "$NVM_DIR" ]; then
|
||||
if ! check_nvm_complete; then
|
||||
echo "Detected incomplete NVM installation"
|
||||
uninstall_nvm
|
||||
# If directory still exists after uninstall (partial removal), try to clean it
|
||||
if [ -d "$NVM_DIR" ]; then
|
||||
echo " Cleaning up residual NVM files..."
|
||||
# Remove everything except we can't delete (probably in use)
|
||||
find "$NVM_DIR" -mindepth 1 -delete 2>/dev/null || true
|
||||
# If still can't remove the directory itself, warn but continue
|
||||
if [ -d "$NVM_DIR" ]; then
|
||||
echo " Note: Some NVM files are locked by running processes."
|
||||
echo " Will attempt to install NVM over existing directory..."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "✓ NVM already installed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install NVM if needed (either no dir or partial/corrupted)
|
||||
if [ ! -d "$NVM_DIR" ] || [ ! -s "$NVM_DIR/nvm.sh" ]; then
|
||||
echo "Downloading NVM..."
|
||||
|
||||
# Use mktemp for secure temporary file creation
|
||||
# Remove trailing slash from TMPDIR to avoid double slashes
|
||||
TEMP_DIR="${TMPDIR:-/tmp}"
|
||||
TEMP_DIR="${TEMP_DIR%/}"
|
||||
|
||||
# Retry mktemp a few times if it fails
|
||||
TMP_INSTALL_SCRIPT=""
|
||||
for i in 1 2 3; do
|
||||
TMP_INSTALL_SCRIPT=$(mktemp "${TEMP_DIR}/nvm_install.XXXXXXXXXX.sh" 2>/dev/null)
|
||||
if [ -n "$TMP_INSTALL_SCRIPT" ] && [ -f "$TMP_INSTALL_SCRIPT" ]; then
|
||||
break
|
||||
fi
|
||||
# Wait a bit before retry
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# Fallback if mktemp still fails
|
||||
if [ -z "$TMP_INSTALL_SCRIPT" ]; then
|
||||
TMP_INSTALL_SCRIPT="${TEMP_DIR}/nvm_install_$$_$(date +%s%N).sh"
|
||||
touch "$TMP_INSTALL_SCRIPT" 2>/dev/null || {
|
||||
echo "✗ Failed to create temporary file"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Ensure cleanup on exit
|
||||
trap 'rm -f "$TMP_INSTALL_SCRIPT"' EXIT
|
||||
|
||||
if curl -f -s -S -o "$TMP_INSTALL_SCRIPT" "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh"; then
|
||||
if bash "$TMP_INSTALL_SCRIPT"; then
|
||||
rm -f "$TMP_INSTALL_SCRIPT"
|
||||
trap - EXIT
|
||||
echo "✓ NVM installed"
|
||||
else
|
||||
echo "✗ NVM installation failed"
|
||||
rm -f "$TMP_INSTALL_SCRIPT"
|
||||
trap - EXIT
|
||||
echo "Please install Node.js manually from: https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "✗ Failed to download NVM"
|
||||
rm -f "$TMP_INSTALL_SCRIPT"
|
||||
trap - EXIT
|
||||
echo "Please check your internet connection or install Node.js manually from https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Load NVM
|
||||
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
||||
\. "$NVM_DIR/nvm.sh"
|
||||
else
|
||||
echo "✗ NVM installation failed - nvm.sh not found"
|
||||
echo "Please install Node.js manually from https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
|
||||
# Verify NVM loaded
|
||||
if ! command_exists nvm; then
|
||||
echo "✗ Failed to load NVM"
|
||||
echo "Please manually load NVM or install Node.js from https://nodejs.org/"
|
||||
exit 1
|
||||
log_error "NVM not loaded properly"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Install Node.js 20
|
||||
echo "Installing Node.js 20..."
|
||||
if nvm install 20 >/dev/null 2>&1; then
|
||||
nvm use 20 >/dev/null 2>&1
|
||||
nvm alias default 20 >/dev/null 2>&1
|
||||
# Set Node.js mirror source for faster downloads in China
|
||||
export NVM_NODEJS_ORG_MIRROR="https://npmmirror.com/mirrors/node"
|
||||
|
||||
# Install Node.js
|
||||
log_info "Installing Node.js v${NODE_VERSION}..."
|
||||
if nvm install "${NODE_VERSION}"; then
|
||||
nvm alias default "${NODE_VERSION}" || true
|
||||
nvm use default || true
|
||||
log_success "Node.js v${NODE_VERSION} installed successfully"
|
||||
|
||||
# Verify installation
|
||||
log_info "Node.js version: $(node -v)" || true
|
||||
log_info "npm version: $(npm -v)" || true
|
||||
|
||||
return 0
|
||||
else
|
||||
echo "✗ Failed to install Node.js 20"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify Node.js
|
||||
if ! command_exists node; then
|
||||
echo "✗ Node.js installation verification failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_VERSION=$(node --version 2>/dev/null)
|
||||
if [ $? -ne 0 ] || [ -z "$NODE_VERSION" ]; then
|
||||
echo "✗ Node.js cannot execute properly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Node.js $NODE_VERSION installed"
|
||||
|
||||
# Check npm separately
|
||||
if ! command_exists npm; then
|
||||
echo "⚠ npm not found"
|
||||
|
||||
if install_npm_only; then
|
||||
echo "✓ npm installation fixed"
|
||||
else
|
||||
echo "✗ Failed to install npm"
|
||||
echo "Please try:"
|
||||
echo " 1. Run this script again"
|
||||
echo " 2. Install Node.js from: https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
NPM_VERSION=$(npm --version 2>/dev/null)
|
||||
if [ $? -eq 0 ] && [ -n "$NPM_VERSION" ]; then
|
||||
echo "✓ npm v$NPM_VERSION installed"
|
||||
else
|
||||
echo "⚠ npm exists but cannot execute"
|
||||
|
||||
if install_npm_only; then
|
||||
echo "✓ npm installation fixed"
|
||||
else
|
||||
echo "✗ Failed to fix npm"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
log_error "Failed to install Node.js"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to check and install Qwen Code
|
||||
install_qwen_code() {
|
||||
if command_exists qwen; then
|
||||
QWEN_VERSION=$(qwen --version 2>/dev/null || echo "unknown")
|
||||
echo "✓ Qwen Code is already installed: $QWEN_VERSION"
|
||||
echo " Upgrading to the latest version..."
|
||||
# ============================================
|
||||
# Check Node.js version
|
||||
# ============================================
|
||||
check_node_version() {
|
||||
if ! command_exists node; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if running as root
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
# Running as root, no need for sudo
|
||||
NPM_INSTALL_CMD="npm install -g @qwen-code/qwen-code@latest"
|
||||
local current_version
|
||||
current_version=$(node -v | sed 's/v//')
|
||||
local major_version
|
||||
major_version=$(echo "${current_version}" | cut -d. -f1)
|
||||
|
||||
if [[ "${major_version}" -ge 20 ]]; then
|
||||
log_success "Node.js v${current_version} is already installed (>= 20)"
|
||||
return 0
|
||||
else
|
||||
# Not root, use sudo
|
||||
NPM_INSTALL_CMD="sudo npm install -g @qwen-code/qwen-code@latest"
|
||||
log_warning "Node.js v${current_version} is installed but version < 20"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Install Node.js
|
||||
# ============================================
|
||||
install_nodejs() {
|
||||
local platform
|
||||
platform=$(uname -s)
|
||||
|
||||
case "${platform}" in
|
||||
Linux|Darwin)
|
||||
log_info "Installing Node.js on ${platform}..."
|
||||
|
||||
# Install NVM
|
||||
if ! install_nvm; then
|
||||
log_error "Failed to install NVM"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Load NVM
|
||||
export NVM_DIR="${HOME}/.nvm"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh"
|
||||
|
||||
# Install Node.js
|
||||
if ! install_nodejs_with_nvm; then
|
||||
log_error "Failed to install Node.js"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
MINGW*|CYGWIN*|MSYS*)
|
||||
log_error "Windows platform detected. Please use Windows installer or WSL."
|
||||
log_info "Visit: https://nodejs.org/en/download/"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
log_error "Unsupported platform: ${platform}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Check and install Node.js
|
||||
# ============================================
|
||||
check_and_install_nodejs() {
|
||||
if check_node_version; then
|
||||
log_info "Using existing Node.js installation"
|
||||
clean_npmrc_conflict
|
||||
else
|
||||
log_warning "Installing or upgrading Node.js..."
|
||||
install_nodejs
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Fix npm permissions (without using sudo)
|
||||
# ============================================
|
||||
fix_npm_permissions() {
|
||||
log_info "Checking npm permissions..."
|
||||
|
||||
local NPM_GLOBAL_DIR
|
||||
NPM_GLOBAL_DIR=$(npm config get prefix 2>/dev/null) || true
|
||||
if [[ -z "${NPM_GLOBAL_DIR}" ]] || [[ "${NPM_GLOBAL_DIR}" == *"error"* ]]; then
|
||||
NPM_GLOBAL_DIR="${HOME}/.npm-global"
|
||||
npm config set prefix "${NPM_GLOBAL_DIR}"
|
||||
log_info "Set npm prefix to user directory: ${NPM_GLOBAL_DIR}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Install/Upgrade Qwen Code globally
|
||||
# Note: Don't suppress output to allow sudo password prompt to be visible
|
||||
if $NPM_INSTALL_CMD; then
|
||||
echo "✓ Qwen Code installed/upgraded successfully!"
|
||||
# SAFETY CHECK: Never modify system directories
|
||||
# This prevents catastrophic failures like breaking sudo setuid binaries
|
||||
case "${NPM_GLOBAL_DIR}" in
|
||||
/|/usr|/usr/local|/bin|/sbin|/lib|/lib64|/opt|/snap|/var|/etc)
|
||||
log_warning "npm prefix is a system directory (${NPM_GLOBAL_DIR})."
|
||||
log_info "Using user directory instead to avoid breaking system binaries."
|
||||
NPM_GLOBAL_DIR="${HOME}/.npm-global"
|
||||
npm config set prefix "${NPM_GLOBAL_DIR}"
|
||||
log_success "npm prefix set to: ${NPM_GLOBAL_DIR}"
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
# Safe to proceed with non-system directory
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create/Update source.json only if source parameter was provided
|
||||
if [ "$SOURCE" != "unknown" ]; then
|
||||
create_source_json
|
||||
else
|
||||
echo " (Skipping source.json creation - no source specified)"
|
||||
fi
|
||||
# Check if npm global directory is writable
|
||||
if [[ -w "${NPM_GLOBAL_DIR}" ]]; then
|
||||
log_info "npm global directory is writable"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# If not writable, use user directory
|
||||
log_warning "npm global directory is not writable: ${NPM_GLOBAL_DIR}"
|
||||
log_info "Setting npm prefix to user directory..."
|
||||
|
||||
NPM_GLOBAL_DIR="${HOME}/.npm-global"
|
||||
mkdir -p "${NPM_GLOBAL_DIR}"
|
||||
npm config set prefix "${NPM_GLOBAL_DIR}"
|
||||
|
||||
log_success "npm prefix set to: ${NPM_GLOBAL_DIR}"
|
||||
|
||||
# Add to PATH in shell profile
|
||||
local PROFILE_FILE
|
||||
PROFILE_FILE=$(get_shell_profile)
|
||||
if ! grep -q '.npm-global/bin' "${PROFILE_FILE}" 2>/dev/null; then
|
||||
{
|
||||
echo ""
|
||||
echo "# NPM global bin (added by Qwen Code installer)"
|
||||
echo "export PATH=\"\$HOME/.npm-global/bin:\$PATH\""
|
||||
} >> "${PROFILE_FILE}"
|
||||
log_info "Added npm global bin to PATH in ${PROFILE_FILE}"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Install Qwen Code
|
||||
# ============================================
|
||||
install_qwen_code() {
|
||||
# Ensure NVM node is in PATH
|
||||
export NVM_DIR="${HOME}/.nvm"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" 2>/dev/null || true
|
||||
|
||||
# Add npm global bin to PATH
|
||||
local NPM_GLOBAL_BIN
|
||||
NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null) || true
|
||||
if [[ -n "${NPM_GLOBAL_BIN}" ]]; then
|
||||
export PATH="${NPM_GLOBAL_BIN}:${PATH}"
|
||||
fi
|
||||
|
||||
if command_exists qwen; then
|
||||
local QWEN_VERSION
|
||||
QWEN_VERSION=$(qwen --version 2>/dev/null) || echo "unknown"
|
||||
log_success "Qwen Code is already installed: ${QWEN_VERSION}"
|
||||
log_info "Upgrading to the latest version..."
|
||||
fi
|
||||
|
||||
# Clean npmrc conflicts
|
||||
clean_npmrc_conflict
|
||||
|
||||
# Fix npm permissions if needed
|
||||
fix_npm_permissions
|
||||
|
||||
# Configure npm registry for faster downloads in China
|
||||
npm config set registry https://registry.npmmirror.com
|
||||
log_info "npm registry set to npmmirror"
|
||||
|
||||
# Install Qwen Code
|
||||
log_info "Installing Qwen Code..."
|
||||
if npm install -g @qwen-code/qwen-code@latest; then
|
||||
log_success "Qwen Code installed successfully!"
|
||||
|
||||
# Verify installation
|
||||
if command_exists qwen; then
|
||||
QWEN_VERSION=$(qwen --version 2>/dev/null || echo "unknown")
|
||||
echo "✓ Qwen Code is available as 'qwen' command"
|
||||
echo " Installed version: $QWEN_VERSION"
|
||||
else
|
||||
echo "⚠ Qwen Code installed but not in PATH"
|
||||
echo " You may need to restart your terminal"
|
||||
local qwen_version
|
||||
qwen_version=$(qwen --version 2>/dev/null) || qwen_version="unknown"
|
||||
log_info "Qwen Code version: ${qwen_version}"
|
||||
fi
|
||||
else
|
||||
echo "✗ Failed to install Qwen Code"
|
||||
log_error "Failed to install Qwen Code!"
|
||||
log_info "Please check your internet connection and try again"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create source.json if source parameter was provided
|
||||
if [[ "${SOURCE}" != "unknown" ]]; then
|
||||
create_source_json
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to create source.json
|
||||
# ============================================
|
||||
# Create source.json
|
||||
# ============================================
|
||||
create_source_json() {
|
||||
QWEN_DIR="$HOME/.qwen"
|
||||
local QWEN_DIR="${HOME}/.qwen"
|
||||
|
||||
# Create .qwen directory if it doesn't exist
|
||||
if [ ! -d "$QWEN_DIR" ]; then
|
||||
mkdir -p "$QWEN_DIR"
|
||||
fi
|
||||
mkdir -p "${QWEN_DIR}"
|
||||
|
||||
# Escape special characters in SOURCE for JSON
|
||||
# Replace backslashes first, then quotes
|
||||
ESCAPED_SOURCE=$(printf '%s' "$SOURCE" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
local ESCAPED_SOURCE
|
||||
ESCAPED_SOURCE=$(printf '%s' "${SOURCE}" | sed 's/\\/\\\\/g; s/"/\\"/g')
|
||||
|
||||
# Create source.json file
|
||||
cat > "$QWEN_DIR/source.json" <<EOF
|
||||
cat > "${QWEN_DIR}/source.json" <<EOF
|
||||
{
|
||||
"source": "$ESCAPED_SOURCE"
|
||||
"source": "${ESCAPED_SOURCE}"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "✓ Installation source saved to ~/.qwen/source.json"
|
||||
log_success "Installation source saved to ~/.qwen/source.json"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
# ============================================
|
||||
# Main function
|
||||
# ============================================
|
||||
main() {
|
||||
# Step 1: Check and install Node.js
|
||||
install_nodejs
|
||||
# Validate HOME variable
|
||||
if [[ -z "${HOME}" ]]; then
|
||||
log_warning "HOME environment variable is not set"
|
||||
local MAIN_UID
|
||||
MAIN_UID=$(id -u) || true
|
||||
if [[ "${MAIN_UID}" -eq 0 ]]; then
|
||||
export HOME="/root"
|
||||
else
|
||||
local CURRENT_USER
|
||||
CURRENT_USER=$(whoami) || true
|
||||
local user_home
|
||||
user_home=$(eval echo "~${CURRENT_USER}") || true
|
||||
export HOME="${user_home}"
|
||||
fi
|
||||
log_info "Using HOME=${HOME}"
|
||||
fi
|
||||
|
||||
# Ensure download tool is available
|
||||
ensure_download_tool
|
||||
|
||||
# Check and install Node.js
|
||||
check_and_install_nodejs
|
||||
echo ""
|
||||
|
||||
# Step 2: Check and install Qwen Code
|
||||
# Install Qwen Code
|
||||
install_qwen_code
|
||||
echo ""
|
||||
|
||||
echo "==========================================="
|
||||
echo "✓ Installation completed!"
|
||||
echo "==========================================="
|
||||
# ============================================
|
||||
# Final instructions
|
||||
# ============================================
|
||||
echo "=========================================="
|
||||
echo "✅ Installation completed!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
|
||||
# Ensure NVM and npm global bin are in PATH
|
||||
export NVM_DIR="${HOME}/.nvm"
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s "${NVM_DIR}/nvm.sh" ]] && \. "${NVM_DIR}/nvm.sh" 2>/dev/null || true
|
||||
local NPM_GLOBAL_BIN
|
||||
NPM_GLOBAL_BIN=$(npm bin -g 2>/dev/null) || true
|
||||
if [[ -n "${NPM_GLOBAL_BIN}" ]]; then
|
||||
export PATH="${NPM_GLOBAL_BIN}:${PATH}"
|
||||
fi
|
||||
|
||||
# Check if qwen is immediately available
|
||||
if command_exists qwen; then
|
||||
echo "✓ Qwen Code is ready to use!"
|
||||
log_success "Qwen Code is ready to use!"
|
||||
echo ""
|
||||
echo "You can now run: qwen"
|
||||
else
|
||||
echo "⚠ To start using Qwen Code, please run one of the following commands:"
|
||||
log_warning "To start using Qwen Code, please run:"
|
||||
echo ""
|
||||
|
||||
# Detect user's shell
|
||||
USER_SHELL=$(basename "$SHELL")
|
||||
|
||||
if [ "$USER_SHELL" = "zsh" ] && [ -f "$HOME/.zshrc" ]; then
|
||||
echo " source ~/.zshrc"
|
||||
elif [ "$USER_SHELL" = "bash" ]; then
|
||||
if [ -f "$HOME/.bash_profile" ]; then
|
||||
echo " source ~/.bash_profile"
|
||||
elif [ -f "$HOME/.bashrc" ]; then
|
||||
echo " source ~/.bashrc"
|
||||
fi
|
||||
else
|
||||
# Fallback: show all possible options
|
||||
[ -f "$HOME/.zshrc" ] && echo " source ~/.zshrc"
|
||||
[ -f "$HOME/.bashrc" ] && echo " source ~/.bashrc"
|
||||
[ -f "$HOME/.bash_profile" ] && echo " source ~/.bash_profile"
|
||||
fi
|
||||
|
||||
local PROFILE_FILE
|
||||
PROFILE_FILE=$(get_shell_profile)
|
||||
echo " source ${PROFILE_FILE}"
|
||||
echo ""
|
||||
echo "Or simply restart your terminal, then run: qwen"
|
||||
fi
|
||||
|
|
@ -456,4 +555,3 @@ main() {
|
|||
|
||||
# Run main function
|
||||
main "$@"
|
||||
main
|
||||
|
|
@ -161,6 +161,13 @@ const distPackageJson = {
|
|||
'@lydell/node-pty-linux-x64': '1.1.0',
|
||||
'@lydell/node-pty-win32-arm64': '1.1.0',
|
||||
'@lydell/node-pty-win32-x64': '1.1.0',
|
||||
'@teddyzhu/clipboard': '0.0.5',
|
||||
'@teddyzhu/clipboard-darwin-arm64': '0.0.5',
|
||||
'@teddyzhu/clipboard-darwin-x64': '0.0.5',
|
||||
'@teddyzhu/clipboard-linux-x64-gnu': '0.0.5',
|
||||
'@teddyzhu/clipboard-linux-arm64-gnu': '0.0.5',
|
||||
'@teddyzhu/clipboard-win32-x64-msvc': '0.0.5',
|
||||
'@teddyzhu/clipboard-win32-arm64-msvc': '0.0.5',
|
||||
},
|
||||
engines: rootPackageJson.engines,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,12 +70,14 @@ if (!geminiSandbox) {
|
|||
geminiSandbox = (geminiSandbox || '').toLowerCase();
|
||||
|
||||
const commandExists = (cmd) => {
|
||||
const checkCommand = os.platform() === 'win32' ? 'where' : 'command -v';
|
||||
// Use 'where.exe' (not 'where') on Windows because PowerShell aliases
|
||||
// 'where' to 'Where-Object', which breaks command detection.
|
||||
const checkCommand = os.platform() === 'win32' ? 'where.exe' : 'command -v';
|
||||
try {
|
||||
execSync(`${checkCommand} ${cmd}`, { stdio: 'ignore' });
|
||||
return true;
|
||||
} catch {
|
||||
if (os.platform() === 'win32') {
|
||||
if (os.platform() === 'win32' && !cmd.endsWith('.exe')) {
|
||||
try {
|
||||
execSync(`${checkCommand} ${cmd}.exe`, { stdio: 'ignore' });
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -18,10 +18,21 @@ const __filename = fileURLToPath(import.meta.url);
|
|||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
const projectHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(projectRoot)
|
||||
.digest('hex');
|
||||
|
||||
/**
|
||||
* Generates a unique hash for a project based on its root path.
|
||||
* On Windows, paths are case-insensitive, so we normalize to lowercase
|
||||
* to ensure the same physical path always produces the same hash.
|
||||
* This logic must match getProjectHash() in packages/core/src/utils/paths.ts
|
||||
*/
|
||||
function getProjectHash(projectRoot) {
|
||||
// On Windows, normalize path to lowercase for case-insensitive matching
|
||||
const normalizedPath =
|
||||
os.platform() === 'win32' ? projectRoot.toLowerCase() : projectRoot;
|
||||
return crypto.createHash('sha256').update(normalizedPath).digest('hex');
|
||||
}
|
||||
|
||||
const projectHash = getProjectHash(projectRoot);
|
||||
|
||||
// User-level .gemini directory in home
|
||||
const USER_GEMINI_DIR = path.join(os.homedir(), '.qwen');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue