diff --git a/README.md b/README.md index 4250afa73..626c868ee 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,24 @@ Qwen Code is an open-source AI agent for the terminal, optimized for [Qwen3-Code ## Installation +### Quick Install (Recommended) + +#### Linux / macOS + +```bash +eval "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)" +``` + +#### Windows (Run as Administrator CMD) + +```cmd +curl -fsSL -o %TEMP%\install-qwen.bat https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat && %TEMP%\install-qwen.bat +``` + +> **Note**: It's recommended to restart your terminal after installation to ensure environment variables take effect. + +### Manual Installation + #### Prerequisites ```bash @@ -38,7 +56,7 @@ Qwen Code is an open-source AI agent for the terminal, optimized for [Qwen3-Code curl -qL https://www.npmjs.com/install.sh | sh ``` -#### NPM (recommended) +#### NPM ```bash npm install -g @qwen-code/qwen-code@latest diff --git a/packages/core/src/telemetry/qwen-logger/event-types.ts b/packages/core/src/telemetry/qwen-logger/event-types.ts index ed84ba785..f40caa607 100644 --- a/packages/core/src/telemetry/qwen-logger/event-types.ts +++ b/packages/core/src/telemetry/qwen-logger/event-types.ts @@ -4,6 +4,7 @@ export interface RumApp { env: string; version: string; type: 'cli' | 'extension'; + channel?: string; } export interface RumUser { diff --git a/packages/core/src/telemetry/qwen-logger/qwen-logger.test.ts b/packages/core/src/telemetry/qwen-logger/qwen-logger.test.ts index 749d6eede..6cc0f230a 100644 --- a/packages/core/src/telemetry/qwen-logger/qwen-logger.test.ts +++ b/packages/core/src/telemetry/qwen-logger/qwen-logger.test.ts @@ -147,6 +147,81 @@ describe('QwenLogger', () => { }), ); }); + + it('includes source when source.json exists with valid source', async () => { + // Note: Testing source information requires actual file system operations + // This test verifies that the payload structure is correct + const logger = QwenLogger.getInstance(mockConfig)!; + + const payload = await ( + logger as unknown as { createRumPayload(): Promise } + ).createRumPayload(); + + // Verify that payload has app.channel property + expect(payload.app).toHaveProperty('channel'); + // channel should be either undefined or a string + expect( + payload.app.channel === undefined || + typeof payload.app.channel === 'string', + ).toBe(true); + }); + + it('caches source info and does not read file on every payload creation', async () => { + const logger = QwenLogger.getInstance(mockConfig)!; + + // Get the cached sourceInfo value + const cachedSourceInfo = logger['sourceInfo']; + + // Create multiple payloads + const payload1 = await ( + logger as unknown as { createRumPayload(): Promise } + ).createRumPayload(); + const payload2 = await ( + logger as unknown as { createRumPayload(): Promise } + ).createRumPayload(); + + // Both payloads should use the same cached source info + expect(payload1.app.channel).toBe(payload2.app.channel); + // The cached value should not have changed + expect(logger['sourceInfo']).toBe(cachedSourceInfo); + }); + it('does not include source when source.json does not exist', async () => { + // Note: Testing source information requires actual file system operations + // This test verifies the payload structure is correct + const logger = QwenLogger.getInstance(mockConfig)!; + + const payload = await ( + logger as unknown as { createRumPayload(): Promise } + ).createRumPayload(); + + // Verify that channel property exists (may be undefined or have a value) + expect(payload.app).toHaveProperty('channel'); + }); + it('does not include source when source value is unknown', async () => { + // Note: Testing source information requires actual file system operations + // This test verifies the payload structure is correct + const logger = QwenLogger.getInstance(mockConfig)!; + + const payload = await ( + logger as unknown as { createRumPayload(): Promise } + ).createRumPayload(); + + // Verify that channel property exists + expect(payload.app).toHaveProperty('channel'); + }); + it('handles source.json parsing errors gracefully', async () => { + // Note: Testing source information requires actual file system operations + // This test verifies the payload structure is correct + const logger = QwenLogger.getInstance(mockConfig)!; + + const payload = await ( + logger as unknown as { createRumPayload(): Promise } + ).createRumPayload(); + + // Verify that payload is created successfully (no crash on errors) + expect(payload).toBeDefined(); + expect(payload.app).toHaveProperty('channel'); + }); }); describe('event queue management', () => { @@ -325,6 +400,28 @@ describe('QwenLogger', () => { expect(flushSpy).toHaveBeenCalled(); }); + it('should re-read source info when starting a new session', async () => { + const logger = QwenLogger.getInstance(mockConfig)!; + const readSourceInfoSpy = vi.spyOn( + logger as unknown as { readSourceInfo(): string }, + 'readSourceInfo', + ); + + const testConfig = makeFakeConfig({ + getModel: () => 'test-model', + getEmbeddingModel: () => 'test-embedding', + getSessionId: () => 'new-session-id', + }); + const event = new StartSessionEvent(testConfig); + + await logger.logStartSessionEvent(event); + + // readSourceInfo should be called when starting a new session + expect(readSourceInfoSpy).toHaveBeenCalled(); + // Session ID should be updated + expect(logger['sessionId']).toBe('new-session-id'); + }); + it('should flush end session events immediately', async () => { const logger = QwenLogger.getInstance(mockConfig)!; const flushSpy = vi.spyOn(logger, 'flushToRum').mockResolvedValue({}); diff --git a/packages/core/src/telemetry/qwen-logger/qwen-logger.ts b/packages/core/src/telemetry/qwen-logger/qwen-logger.ts index 21c09cc37..6d30e13e1 100644 --- a/packages/core/src/telemetry/qwen-logger/qwen-logger.ts +++ b/packages/core/src/telemetry/qwen-logger/qwen-logger.ts @@ -7,6 +7,8 @@ import { Buffer } from 'buffer'; import * as https from 'https'; import * as os from 'node:os'; +import fs from 'node:fs'; +import path from 'node:path'; import { HttpsProxyAgent } from 'https-proxy-agent'; import type { @@ -118,6 +120,12 @@ export class QwenLogger { private sessionId: string; + /** + * Cached source information read from source.json. + * Only read once at session start to avoid repeated file I/O. + */ + private sourceInfo: string = ''; + /** * The value is true when there is a pending flush happening. This prevents * concurrent flush operations. @@ -141,6 +149,8 @@ export class QwenLogger { this.installationManager = new InstallationManager(); this.userId = this.generateUserId(); this.sessionId = config.getSessionId(); + // Read source info once during initialization + this.sourceInfo = this.readSourceInfo(); } private generateUserId(): string { @@ -239,12 +249,14 @@ export class QwenLogger { const version = this.config?.getCliVersion() || 'unknown'; const osMetadata = this.getOsMetadata(); + // Use cached source information return { app: { id: RUN_APP_ID, env: process.env['DEBUG'] ? 'dev' : 'prod', version: version || 'unknown', type: 'cli', + channel: this.sourceInfo || undefined, }, user: { id: this.userId, @@ -282,6 +294,27 @@ export class QwenLogger { void this.flushToRum(); } + readSourceInfo(): string { + try { + const sourceJsonPath = path.join(os.homedir(), '.qwen', 'source.json'); + if (fs.existsSync(sourceJsonPath)) { + const sourceJsonContent = fs.readFileSync(sourceJsonPath, 'utf8'); + const sourceData = JSON.parse(sourceJsonContent); + if ( + sourceData && + typeof sourceData === 'object' && + sourceData.source && + sourceData.source !== 'unknown' + ) { + return sourceData.source; + } + } + } catch (_error) { + // Ignore errors when reading source.json - continue without source info + } + return ''; + } + async flushToRum(): Promise { if (this.isFlushInProgress) { this.debugLogger.debug( @@ -377,6 +410,9 @@ export class QwenLogger { // Now set the new session ID this.sessionId = event.session_id; + // Re-read source info at the start of each new session + this.sourceInfo = this.readSourceInfo(); + const applicationEvent = this.createViewEvent('session', 'session_start', { properties: { model: event.model, diff --git a/scripts/installation/INSTALLATION_GUIDE.md b/scripts/installation/INSTALLATION_GUIDE.md new file mode 100644 index 000000000..8a41bc47a --- /dev/null +++ b/scripts/installation/INSTALLATION_GUIDE.md @@ -0,0 +1,250 @@ +# Installation Guide for Qwen Code with Source Tracking + +This guide describes how to install Node.js and Qwen Code with source information tracking. + +## Overview + +The installation scripts automate the process of installing Node.js (if not present or below version 20) and Qwen Code, while capturing and storing the installation source information for analytics and tracking purposes. + +## Installation Scripts + +We provide platform-specific installation scripts: + +- **Linux/macOS**: `install-qwen-with-source.sh` +- **Windows**: `install-qwen-with-source.bat` + +## Linux/macOS Installation + +### Script: install-qwen-with-source.sh + +#### Features: + +- Checks for existing Node.js installation and version +- Installs Node.js 20+ if needed using NVM +- Installs Qwen Code globally with source information +- Stores the source information in `~/.qwen/source.json` + +#### Usage: + +```bash +# Install with a specific source +sh install-qwen-with-source.sh --source github + +# Install with internal source +sh install-qwen-with-source.sh -s internal + +# Show help +sh install-qwen-with-source.sh --help +``` + +#### Supported Source Values: + +- `github` - Installed from GitHub repository +- `npm` - Installed from npm registry +- `internal` - Internal installation +- `local-build` - Local build installation + +#### How it Works: + +1. The script accepts a `--source` parameter to specify where Qwen Code is being installed from +2. It installs Node.js if needed +3. It installs Qwen Code globally +4. It creates `~/.qwen/source.json` with the specified source information + +#### Important Notes: + +⚠️ **After installation, you need to restart your terminal or run:** + +```bash +source ~/.bashrc # For bash users +# or +source ~/.zshrc # For zsh users +``` + +This is required to load the newly installed Node.js and Qwen Code into your PATH. + +#### Prerequisites: + +- curl (for NVM installation and script download) +- bash-compatible shell + +## Windows Installation + +### Script: install-qwen-with-source.bat + +#### Features: + +- Checks for existing Node.js installation and version (requires version 18+) +- Automatically downloads and installs Node.js 24 LTS if not present or version is too low +- Installs Qwen Code globally with source information +- Stores the source information in `%USERPROFILE%\.qwen\source.json` + +#### Prerequisites: + +- **PowerShell (Administrator)**: The script must be run in PowerShell with Administrator privileges +- Internet connection for downloading Node.js and Qwen Code + +#### Usage: + +> ⚠️ **Important**: You must run PowerShell as Administrator to install Node.js and global npm packages. + +**Step 1**: Open PowerShell as Administrator + +- Right-click on PowerShell and select "Run as Administrator" +- Or press `Win + X` and select "Windows PowerShell (Admin)" + +**Step 2**: Navigate to the script directory and run: + +```powershell +# Install with a specific source using --source parameter +./install-qwen-with-source.bat --source github + +# Install with short parameter +./install-qwen-with-source.bat -s internal + +# Use default source (unknown) +./install-qwen-with-source.bat +``` + +#### Supported Source Values: + +- `github` - Installed from GitHub repository +- `npm` - Installed from npm registry +- `internal` - Internal installation +- `local-build` - Local build installation + +#### How it Works: + +1. The script accepts a `--source` or `-s` parameter to specify where Qwen Code is being installed from +2. It checks if Node.js is already installed and if the version is 18 or higher +3. If Node.js is not installed or version is too low, it automatically downloads and installs Node.js 24 LTS +4. It installs Qwen Code globally using npm +5. It creates `%USERPROFILE%\.qwen\source.json` with the specified source information + +#### Why Administrator Privileges are Required: + +- Installing Node.js requires writing to `C:\Program Files\nodejs` +- Installing global npm packages requires elevated permissions +- Modifying system PATH environment variables requires Administrator access + +## Installation Source Feature + +### Overview + +This feature implements the ability to capture and store the installation source of the Qwen Code package. The source information is used for analytics and tracking purposes. + +### Storage Location + +The installation source is stored in a separate file at: + +- **Unix/Linux/macOS**: `~/.qwen/source.json` +- **Windows**: `%USERPROFILE%\.qwen\source.json` (equivalent to `C:\Users\{username}\.qwen\source.json`) + +### File Format + +The `source.json` file contains: + +```json +{ + "source": "github" +} +``` + +### How the Source Information is Used + +1. **Telemetry Tracking**: The source information is included in RUM (Real User Monitoring) telemetry logs +2. **Analytics**: Helps understand how users are discovering and installing Qwen Code +3. **Distribution Analysis**: Tracks which distribution channels are most popular + +### Technical Implementation + +- The source information is stored as a separate JSON file +- The `QwenLogger` class reads this file during telemetry initialization +- The source is included in the `app.channel` field of the RUM payload +- The implementation gracefully handles missing files, unknown values, and parsing errors + +### Verification + +After installation and restarting your terminal (or sourcing your shell configuration), you can verify the source information: + +**Linux/macOS:** + +```bash +cat ~/.qwen/source.json +``` + +**Windows:** + +```cmd +type %USERPROFILE%\.qwen\source.json +``` + +## Manual Installation (Without Source Tracking) + +If you prefer not to use the installation scripts or don't want source tracking: + +### Prerequisites + +```bash +# Node.js 20+ +curl -qL https://www.npmjs.com/install.sh | sh +``` + +### NPM Installation + +```bash +npm install -g @qwen-code/qwen-code@latest +``` + +### Homebrew (macOS, Linux) + +```bash +brew install qwen-code +``` + +## Troubleshooting + +### Script Execution Issues + +**Linux/macOS:** + +```bash +# Run with sh +sh install-qwen-with-source.sh --source github +``` + +**Windows (PowerShell as Administrator):** + +```powershell +# Run the script with --source parameter +./install-qwen-with-source.bat --source github + +# Or with short parameter +./install-qwen-with-source.bat -s github +``` + +### Node.js Installation Issues + +**Linux/macOS:** + +- Ensure NVM is installed: `curl -o- https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh | bash` +- Restart your terminal or run: `source ~/.bashrc` + +**Windows:** + +- Install NVM for Windows from: https://github.com/coreybutler/nvm-windows/releases +- After installation, run the script again + +### Permission Issues + +You may need administrative privileges for global npm installation: + +- **Linux/macOS**: Use `sudo` with npm +- **Windows**: Run PowerShell as Administrator (required for Node.js installation and global npm packages) + +## Notes + +- The scripts require internet access to download Node.js and Qwen Code +- Administrative privileges may be required for global npm installation +- The installation source is stored locally and used for tracking purposes only +- If the source file is missing or invalid, the application continues to work normally diff --git a/scripts/installation/install-qwen-with-source.bat b/scripts/installation/install-qwen-with-source.bat new file mode 100644 index 000000000..5a919134c --- /dev/null +++ b/scripts/installation/install-qwen-with-source.bat @@ -0,0 +1,297 @@ +@echo off +REM Script to install Node.js and Qwen Code with source information +REM This script handles the installation process and sets the installation source +REM +REM Usage: install-qwen-with-source.bat --source [github|npm|internal|local-build] +REM install-qwen-with-source.bat -s [github|npm|internal|local-build] +REM + +setlocal enabledelayedexpansion + +set "SOURCE=unknown" + +REM Parse command line arguments +:parse_args +if "%~1"=="" goto end_parse +if /i "%~1"=="--source" ( + set "SOURCE=%~2" + shift + shift + goto parse_args +) +if /i "%~1"=="-s" ( + set "SOURCE=%~2" + shift + shift + goto parse_args +) +if /i "%~1"=="github" set "SOURCE=github" +if /i "%~1"=="npm" set "SOURCE=npm" +if /i "%~1"=="internal" set "SOURCE=internal" +if /i "%~1"=="local-build" set "SOURCE=local-build" +shift +goto parse_args + +:end_parse + +echo =========================================== +echo Qwen Code Installation Script with Source Tracking +echo =========================================== +echo. +echo INFO: Installation source: %SOURCE% +echo. + +REM Check if Node.js is already installed +call :CheckCommandExists node +if !ERRORLEVEL! EQU 0 ( + for /f "delims=" %%i in ('node --version') do set "NODE_VERSION=%%i" + echo INFO: Node.js is already installed: !NODE_VERSION! + + REM Extract major version number + set "MAJOR_VERSION=!NODE_VERSION:v=!" + for /f "tokens=1 delims=." %%a in ("!MAJOR_VERSION!") do ( + set "MAJOR_VERSION=%%a" + ) + + if !MAJOR_VERSION! GEQ 20 ( + echo INFO: Node.js version !NODE_VERSION! is sufficient. Skipping Node.js installation. + goto :InstallQwenCode + ) else ( + echo INFO: Node.js version !NODE_VERSION! is too low. Need version 20 or higher. + echo INFO: Installing Node.js 20+ + call :InstallNodeJSDirectly + if !ERRORLEVEL! NEQ 0 ( + echo ERROR: Failed to install Node.js. Cannot continue with Qwen Code installation. + exit /b 1 + ) + ) +) else ( + echo INFO: Node.js not found. Installing Node.js 20+ + call :InstallNodeJSDirectly + if !ERRORLEVEL! NEQ 0 ( + echo ERROR: Failed to install Node.js. Cannot continue with Qwen Code installation. + exit /b 1 + ) +) + +:InstallQwenCode + +REM Verify npm is available before installing Qwen Code +REM Always use full path to npm to avoid local node_modules conflicts +set "NODEJS_PATH=C:\Program Files\nodejs" +set "NODEJS_PATH_X86=C:\Program Files (x86)\nodejs" + +if exist "!NODEJS_PATH!\npm.cmd" ( + echo INFO: Using npm from !NODEJS_PATH! + set "NPM_CMD=!NODEJS_PATH!\npm.cmd" +) else if exist "!NODEJS_PATH_X86!\npm.cmd" ( + echo INFO: Using npm from !NODEJS_PATH_X86! + set "NPM_CMD=!NODEJS_PATH_X86!\npm.cmd" +) else ( + call :CheckCommandExists npm + if !ERRORLEVEL! NEQ 0 ( + echo ERROR: npm command not found. Node.js installation may have failed. + echo INFO: Please restart your command prompt and try again. + echo INFO: If the problem persists, manually install Node.js from: https://nodejs.org/ + exit /b 1 + ) + set "NPM_CMD=npm" +) + +REM Install Qwen Code with source information +echo INFO: Installing Qwen Code with source: %SOURCE% +echo INFO: Running: %NPM_CMD% install -g @qwen-code/qwen-code +call "%NPM_CMD%" install -g @qwen-code/qwen-code + +if %ERRORLEVEL% EQU 0 ( + echo SUCCESS: Qwen Code installed successfully! +) else ( + echo ERROR: Failed to install Qwen Code. + exit /b 1 +) + +REM After installation, create source.json in the .qwen directory +echo INFO: Creating source.json in %USERPROFILE%\.qwen... + +set "QWEN_DIR=%USERPROFILE%\.qwen" +if not exist "%QWEN_DIR%" ( + mkdir "%QWEN_DIR%" +) + +REM Create the source.json file with the installation source +echo { > "%QWEN_DIR%\source.json" +echo "source": "%SOURCE%" >> "%QWEN_DIR%\source.json" +echo } >> "%QWEN_DIR%\source.json" + +echo SUCCESS: Installation source saved to %USERPROFILE%\.qwen\source.json + +REM Verify installation +call :CheckCommandExists qwen +if %ERRORLEVEL% EQU 0 ( + echo SUCCESS: Qwen Code is available as 'qwen' command. + call qwen --version +) else ( + echo WARNING: Qwen Code may not be in PATH. Please check your npm global bin directory. +) + +echo. +echo =========================================== +echo SUCCESS: Installation completed! +echo The source information is stored in %USERPROFILE%\.qwen\source.json +echo. +echo =========================================== + +endlocal +exit /b 0 + +REM ============================================================ +REM Function: CheckCommandExists +REM Description: Check if a command exists in the system +REM ============================================================ +:CheckCommandExists +where %~1 >nul 2>&1 +exit /b %ERRORLEVEL% + +REM ============================================================ +REM Function: InstallNodeJSDirectly +REM Description: Download and install Node.js directly from official website +REM ============================================================ +:InstallNodeJSDirectly +echo INFO: Downloading Node.js LTS (20.x) from official website + +REM Create temp directory for download +set "TEMP_DIR=%TEMP%\qwen-nodejs-install" +if not exist "%TEMP_DIR%" mkdir "%TEMP_DIR%" + +REM Determine architecture +set "ARCH=x64" +if "%PROCESSOR_ARCHITECTURE%"=="x86" set "ARCH=x86" +if "%PROCESSOR_ARCHITECTURE%"=="AMD64" set "ARCH=x64" +if defined PROCESSOR_ARCHITEW6432 set "ARCH=x64" + +REM Set Node.js download URL (LTS version 20.x) +set "NODE_VERSION=20.18.1" +set "NODE_URL=https://nodejs.org/dist/v!NODE_VERSION!/node-v!NODE_VERSION!-!ARCH!.msi" +set "NODE_INSTALLER=%TEMP_DIR%\nodejs-installer.msi" + +echo INFO: Downloading from: !NODE_URL! +echo INFO: Architecture: !ARCH! + +REM Download Node.js installer using PowerShell +powershell -Command "try { Invoke-WebRequest -Uri '!NODE_URL!' -OutFile '!NODE_INSTALLER!' -UseBasicParsing; Write-Host 'Download completed successfully.' } catch { Write-Host 'Download failed:' $_.Exception.Message; exit 1 }" + +if !ERRORLEVEL! NEQ 0 ( + echo ERROR: Failed to download Node.js installer from official source. + echo INFO: Please manually download and install Node.js from: https://nodejs.org/ + echo INFO: After manual installation, restart your command prompt and run this script again. + exit /b 1 +) + +if not exist "!NODE_INSTALLER!" ( + echo ERROR: Node.js installer not found after download. + exit /b 1 +) + +echo INFO: Installing Node.js silently +REM Install Node.js silently +msiexec /i "!NODE_INSTALLER!" /quiet /norestart ADDLOCAL=ALL + +if !ERRORLEVEL! NEQ 0 ( + echo ERROR: Failed to install Node.js. + echo INFO: You may need to run this script as Administrator. + echo INFO: Or manually install Node.js from: https://nodejs.org/ + exit /b 1 +) + +echo INFO: Node.js installation completed. + +REM Clean up installer +del "!NODE_INSTALLER!" 2>nul +rmdir "!TEMP_DIR!" 2>nul + +REM Refresh environment variables +echo INFO: Refreshing environment variables +call :RefreshEnvVars + +REM Verify installation and return success +set "NODEJS_INSTALL_PATH=C:\Program Files\nodejs" +if exist "!NODEJS_INSTALL_PATH!\node.exe" ( + for /f "delims=" %%i in ('"!NODEJS_INSTALL_PATH!\node.exe" --version') do set "NODE_VERSION=%%i" + echo SUCCESS: Node.js !NODE_VERSION! installed successfully! + exit /b 0 +) + +set "NODEJS_INSTALL_PATH_X86=C:\Program Files (x86)\nodejs" +if exist "!NODEJS_INSTALL_PATH_X86!\node.exe" ( + for /f "delims=" %%i in ('"!NODEJS_INSTALL_PATH_X86!\node.exe" --version') do set "NODE_VERSION=%%i" + echo SUCCESS: Node.js !NODE_VERSION! installed successfully! + exit /b 0 +) + +call :CheckCommandExists node +if !ERRORLEVEL! EQU 0 ( + for /f "delims=" %%i in ('node --version') do set "NODE_VERSION=%%i" + echo SUCCESS: Node.js !NODE_VERSION! installed successfully! + exit /b 0 +) else ( + echo WARNING: Node.js installed but not found in PATH. + echo INFO: Trying to use Node.js from default installation path + + REM Try to use Node.js directly from installation path + set "NODE_PATH=C:\Program Files\nodejs" + if exist "%NODE_PATH%\node.exe" ( + echo INFO: Found Node.js at %NODE_PATH% + REM Update PATH for current session + set "PATH=%PATH%;%NODE_PATH%" + + REM Test if node works now + "%NODE_PATH%\node.exe" --version >nul 2>&1 + if !ERRORLEVEL! EQU 0 ( + for /f "delims=" %%i in ('"%NODE_PATH%\node.exe" --version') do set "NODE_VERSION=%%i" + echo SUCCESS: Node.js %NODE_VERSION% is working from %NODE_PATH% + exit /b 0 + ) + ) + + REM Try x86 path + set "NODE_PATH_X86=C:\Program Files (x86)\nodejs" + if exist "%NODE_PATH_X86%\node.exe" ( + echo INFO: Found Node.js at %NODE_PATH_X86% + REM Update PATH for current session + set "PATH=%PATH%;%NODE_PATH_X86%" + + REM Test if node works now + "%NODE_PATH_X86%\node.exe" --version >nul 2>&1 + if !ERRORLEVEL! EQU 0 ( + for /f "delims=" %%i in ('"%NODE_PATH_X86%\node.exe" --version') do set "NODE_VERSION=%%i" + echo SUCCESS: Node.js %NODE_VERSION% is working from %NODE_PATH_X86% + exit /b 0 + ) + ) + + echo ERROR: Node.js installation completed but cannot be executed + exit /b 1 +) + +exit /b 0 + +REM ============================================================ +REM Function: RefreshEnvVars +REM Description: Refresh environment variables without restarting +REM ============================================================ +:RefreshEnvVars +REM Add Node.js to PATH if not already there +set "NODEJS_DIR=C:\Program Files\nodejs" +if exist "!NODEJS_DIR!\node.exe" ( + echo INFO: Found Node.js at !NODEJS_DIR! + set "PATH=!PATH!;!NODEJS_DIR!" +) + +REM Try alternative path for x86 systems +set "NODEJS_DIR_X86=C:\Program Files (x86)\nodejs" +if exist "!NODEJS_DIR_X86!\node.exe" ( + echo INFO: Found Node.js at !NODEJS_DIR_X86! + set "PATH=!PATH!;!NODEJS_DIR_X86!" +) + +exit /b 0 diff --git a/scripts/installation/install-qwen-with-source.sh b/scripts/installation/install-qwen-with-source.sh new file mode 100755 index 000000000..979638ec1 --- /dev/null +++ b/scripts/installation/install-qwen-with-source.sh @@ -0,0 +1,459 @@ +#!/bin/bash + +# Script to install Node.js and Qwen Code with source information +# This script handles the installation process and sets the installation source +# +# 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 + +# 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 +} + +# 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 + fi + SOURCE="$2" + shift 2 + ;; + -h|--help) + usage + ;; + *) + usage + ;; + esac +done + +echo "===========================================" +echo "Qwen Code Installation Script with Source Tracking" +echo "===========================================" + +# Function to check if a command exists +command_exists() { + command -v "$1" >/dev/null 2>&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 + else + echo "Installing Node.js 20+..." + install_nodejs_via_nvm + fi +} + +# Function to check if NVM installation is complete +check_nvm_complete() { + export NVM_DIR="$HOME/.nvm" + + if [ ! -d "$NVM_DIR" ]; then + return 1 + 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 + + return 0 +} + +# Function to uninstall NVM +uninstall_nvm() { + echo "Uninstalling NVM..." + export 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 + + # 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 + 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 + 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 + 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..." + 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" + else + # Not root, use sudo + NPM_INSTALL_CMD="sudo npm install -g @qwen-code/qwen-code@latest" + 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!" + + # 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 + + # 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" + fi + else + echo "✗ Failed to install Qwen Code" + exit 1 + fi +} + +# Function to create source.json +create_source_json() { + QWEN_DIR="$HOME/.qwen" + + # Create .qwen directory if it doesn't exist + if [ ! -d "$QWEN_DIR" ]; then + mkdir -p "$QWEN_DIR" + fi + + # Escape special characters in SOURCE for JSON + # Replace backslashes first, then quotes + ESCAPED_SOURCE=$(printf '%s' "$SOURCE" | sed 's/\\/\\\\/g; s/"/\\"/g') + + # Create source.json file + cat > "$QWEN_DIR/source.json" <