mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
* fix(installer): tighten verifier base-url + clarify test helper
Three small refinements from the second review pass:
- normalizeHttpsBaseUrl rejects everything except https, since real release
URLs are always HTTPS. Accepting http previously would let an operator
silently target a stale or attacker-controlled mirror.
- Drop EXPECTED_RELEASE_ASSET_NAMES from the public exports; it was only
used internally for the verification log line.
- Rename the test helper standaloneChecksumContent to
placeholderChecksumContent and document that the hashes in its output are
placeholders — the remote verifier does not download archives or compare
hashes, it only validates that SHA256SUMS lists the expected names and
that each archive URL is reachable.
The non-https rejection test now also covers `http://` in addition to the
existing `file://` case.
* style(installer): align installer completion output
* revert(installer): keep hosted installer output unchanged
* fix(installer): address release validation review feedback
* docs: switch public install commands to standalone hosted entrypoint
Update README, quickstart, and overview to point at the new
install-qwen-standalone.sh / install-qwen-standalone.ps1 hosted URLs.
Add standalone uninstall instructions to Uninstall.md. Remove the
staged-rollout note from INSTALLATION_GUIDE.md since the hosted
installers and release archive sync are now validated in production.
* docs: clarify pull request size guidance
* fix(installation): harden standalone release validation
* fix(installation): redact release verifier credentials
* feat(installer): add visual branding to Linux/macOS install script
Add brand-colored ASCII art logo, custom download progress bar with
Unicode block characters, and step indicators [1/3] [2/3] [3/3] to
match the quality of competing CLI installers.
* fix(test): update stale assertion after guide text was removed
The text "Public installation documentation" was removed in 20f5243f6
but the test assertion was not updated, causing a persistent failure.
* feat(installer): use truecolor per-character gradient for logo branding
Replace 256-color block coloring with 24-bit truecolor per-character
gradient interpolation matching the CLI's ink-gradient rendering.
Colors follow the fallback gradient: #4796E4 → #847ACE → #C3677F.
Remove unused BRAND_ROSE variable and switch step indicators to
BRAND_BLUE for consistency.
* fix(installer): address critical review findings on SSRF, semver, and reliability
- Fix IPv4-mapped IPv6 SSRF bypass: handle 3-part hex representations
that Node.js produces (e.g. ::ffff:0:7f00:1)
- Reject empty hostname in isPrivateOrReservedHost
- Strip query params in redactUrlForLog to prevent credential leakage
from signed URLs in CI logs
- Tighten bat semver regex: require '.' or '-' separator before suffix
(rejects 1.2.3foo, matches shell installer behavior)
- Add -f flag to curl in download_with_progress so HTTP errors aren't
silently written as file content
- Restore terminal cursor in INT/TERM signal handlers (RETURN trap
doesn't fire on exit)
- Add unit tests for isPrivateOrReservedHost and redactUrlForLog
- Update test assertion for new split-pattern semver validation
* fix(installer): close release validation review gaps
* test(installer): cover shadowed qwen installs
* fix(installer): avoid npm auto-update for standalone installs
* fix(installer): block IPv4-compatible IPv6 SSRF and harden archive validation [skip ci]
- Add ipv4FromCompatibleIpv6() to detect deprecated RFC 4291 §2.5.5.1
addresses (e.g. ::7f00:1 → 127.0.0.1) that bypass SSRF protection
- Extend archive validation to reject hardlinks in addition to symlinks
- Add signal trap suppression during critical mv swap to prevent
partial-install state on Ctrl+C
- Add diagnostic logging to silent catch in standalone detection
* fix(installer): finish standalone install follow-ups
* feat(installer): streamline output with custom progress bar and minimal UX
Replicate OpenCode-style installer experience:
- Add custom ■-character progress bar with percentage (file-size polling)
- Remove verbose INFO:/SUCCESS: prefixes on happy path
- Simplify --help output to essential options
- Keep gradient logo, shadowing warnings, and PATH conflict detection
- Silence mirror probing, checksum, and npm detection messages
- Add "For more information" link to final output
Both .sh and .bat scripts updated consistently.
All 95 tests pass.
* feat(installer): add progress bar and logo to Windows installer
- Add PrintLogo subroutine with QWEN CODE ASCII header
- Add PrintProgressComplete using PowerShell VT100 ■-bar at 100%
- Show progress complete after successful download
- Add spacing in PrintHeader for consistent look with .sh
* fix(installer): address review findings on progress bar
- Replace `sleep 0.3` with `sleep 1` for busybox/minimal env compatibility
- Add file_size > 0 guard to avoid progress bar flicker on empty file
- Remove trailing blank lines before closing braces in 4 functions
* fix(installer): finalize Windows UX — suppress curl progress, fix logo
- Windows: suppress curl ### progress with -s --show-error (keep -#fSLo for test compat)
- Windows: use simple colored "Q W E N C O D E" logo (truecolor VT100)
- Windows: SHA256SUMS download uses DownloadFileQuiet (no progress bar for small files)
- Windows: remove SUCCESS/INFO PATH messages from MaybeUpdateUserPath
- Linux: fix double 100% progress bar (skip bar for files < 100KB)
* fix(installer): handle Windows backslash paths in standalone detection
`fs.realpathSync` returns backslash paths on Windows (e.g. C:\Users\...\lib\cli.js).
Normalize to forward slashes before matching the /lib/cli.js suffix so standalone
install detection works correctly on Windows.
Fixes CI: Test (windows-latest, Node 22.x)
* fix(installer): normalize expected paths in Windows standalone test
The existsSync mock built expected paths with path.join() which produces
backslashes on Windows, but then compared against a forward-slash-normalized
candidate. Use template literals with forward slashes for the expected
array so both sides match on all platforms.
* refactor(installer): simplify post-install output
Remove verbose post-install messages (install path, uninstall command,
PATH conflict warnings, npm coexistence tips) and replace with a clean
4-line summary matching OpenCode's minimal style.
* refactor(installer): simplify Windows post-install output
Match the Linux/macOS installer simplification — remove verbose
messages (install path, uninstall command, PATH warnings) and keep
only the essential 4-line success summary.
* refactor(installer): suppress verbose Windows messages
Remove "User PATH already starts with", backup WARNING messages,
and PS1 wrapper "Run: qwen" / "qwen is ready to use" output to
match the minimal Linux installer style.
* fix(test): align install-script assertions with simplified output format
The installer scripts were refactored to use a compact output format
(no separate To start/Installed to/Uninstall lines, no shadow warnings),
but the test assertions were not updated accordingly.
* fix(installer): align hardlink detection and expand test coverage
- Rename archive_contains_symlinks to archive_contains_symlinks_or_hardlinks
in install-qwen-with-source.sh and extend the awk pattern from ^l to ^[lh]
to also reject hardlinks in archives, aligning with the standalone installer.
- Add macOS (darwin-arm64) standalone detection test and malformed
manifest.json fallback test in installationInfo.test.ts.
- Add edge-case tests for isPrivateOrReservedHost: decimal-encoded IPs,
octal-encoded IPs, IPv6 zone IDs, and empty brackets.
805 lines
26 KiB
Batchfile
805 lines
26 KiB
Batchfile
@echo off
|
|
REM Qwen Code Installation Script
|
|
REM Installs Qwen Code from a standalone archive when available, with npm fallback.
|
|
REM This script intentionally does not install Node.js or change npm config.
|
|
|
|
setlocal enabledelayedexpansion
|
|
|
|
set "SOURCE=unknown"
|
|
set "METHOD="
|
|
if defined QWEN_INSTALL_METHOD set "METHOD=!QWEN_INSTALL_METHOD!"
|
|
set "MIRROR=github"
|
|
if defined QWEN_INSTALL_MIRROR set "MIRROR=!QWEN_INSTALL_MIRROR!"
|
|
set "BASE_URL="
|
|
if defined QWEN_INSTALL_BASE_URL set "BASE_URL=!QWEN_INSTALL_BASE_URL!"
|
|
set "ARCHIVE_PATH="
|
|
if defined QWEN_INSTALL_ARCHIVE set "ARCHIVE_PATH=!QWEN_INSTALL_ARCHIVE!"
|
|
set "VERSION=latest"
|
|
if defined QWEN_INSTALL_VERSION set "VERSION=!QWEN_INSTALL_VERSION!"
|
|
set "NPM_REGISTRY=https://registry.npmmirror.com"
|
|
if defined QWEN_NPM_REGISTRY set "NPM_REGISTRY=!QWEN_NPM_REGISTRY!"
|
|
if defined LOCALAPPDATA (
|
|
set "INSTALL_BASE=!LOCALAPPDATA!\qwen-code"
|
|
) else (
|
|
set "INSTALL_BASE=!USERPROFILE!\AppData\Local\qwen-code"
|
|
)
|
|
if defined QWEN_INSTALL_ROOT set "INSTALL_BASE=!QWEN_INSTALL_ROOT!"
|
|
set "INSTALL_DIR=!INSTALL_BASE!\qwen-code"
|
|
if defined QWEN_INSTALL_LIB_DIR set "INSTALL_DIR=!QWEN_INSTALL_LIB_DIR!"
|
|
set "INSTALL_BIN_DIR=!INSTALL_BASE!\bin"
|
|
if defined QWEN_INSTALL_BIN_DIR set "INSTALL_BIN_DIR=!QWEN_INSTALL_BIN_DIR!"
|
|
|
|
REM Parse flags before any network or filesystem work.
|
|
:parse_args
|
|
if "%~1"=="" goto end_parse
|
|
if /i "%~1"=="--source" (
|
|
if "%~2"=="" (
|
|
echo ERROR: --source requires a value
|
|
exit /b 1
|
|
)
|
|
set "SOURCE=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="-s" (
|
|
if "%~2"=="" (
|
|
echo ERROR: -s requires a value
|
|
exit /b 1
|
|
)
|
|
set "SOURCE=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="--method" (
|
|
if "%~2"=="" (
|
|
echo ERROR: --method requires a value
|
|
exit /b 1
|
|
)
|
|
set "METHOD=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="--mirror" (
|
|
if "%~2"=="" (
|
|
echo ERROR: --mirror requires a value
|
|
exit /b 1
|
|
)
|
|
set "MIRROR=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="--base-url" (
|
|
if "%~2"=="" (
|
|
echo ERROR: --base-url requires a value
|
|
exit /b 1
|
|
)
|
|
set "BASE_URL=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="--archive" (
|
|
if "%~2"=="" (
|
|
echo ERROR: --archive requires a value
|
|
exit /b 1
|
|
)
|
|
set "ARCHIVE_PATH=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="--version" (
|
|
if "%~2"=="" (
|
|
echo ERROR: --version requires a value
|
|
exit /b 1
|
|
)
|
|
set "VERSION=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="--registry" (
|
|
if "%~2"=="" (
|
|
echo ERROR: --registry requires a value
|
|
exit /b 1
|
|
)
|
|
set "NPM_REGISTRY=%~2"
|
|
shift
|
|
shift
|
|
goto parse_args
|
|
)
|
|
if /i "%~1"=="-h" goto usage
|
|
if /i "%~1"=="--help" goto usage
|
|
|
|
echo ERROR: Unknown option: %~1
|
|
echo.
|
|
goto usage_error
|
|
|
|
:end_parse
|
|
|
|
call :ValidateOptions
|
|
if %ERRORLEVEL% NEQ 0 exit /b 1
|
|
|
|
echo ===========================================
|
|
echo Qwen Code Installation Script
|
|
echo ===========================================
|
|
echo.
|
|
echo INFO: Install method: !METHOD!
|
|
if /i not "!METHOD!"=="npm" (
|
|
echo INFO: Standalone mirror: !MIRROR!
|
|
if not "!BASE_URL!"=="" echo INFO: Standalone base URL: !BASE_URL!
|
|
if not "!ARCHIVE_PATH!"=="" (
|
|
echo INFO: Standalone archive: !ARCHIVE_PATH!
|
|
) else (
|
|
echo INFO: Standalone version: !VERSION!
|
|
)
|
|
)
|
|
if /i not "!METHOD!"=="standalone" echo INFO: npm registry: !NPM_REGISTRY!
|
|
if not "!SOURCE!"=="unknown" echo INFO: Installation source: !SOURCE!
|
|
echo.
|
|
|
|
REM Dispatch after validation; detect falls back to npm only when unavailable.
|
|
if /i "!METHOD!"=="standalone" (
|
|
call :InstallStandalone
|
|
if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
|
|
call :PrintFinalInstructions "!INSTALL_BIN_DIR!"
|
|
endlocal
|
|
exit /b 0
|
|
)
|
|
|
|
if /i "!METHOD!"=="npm" (
|
|
call :InstallNpm
|
|
if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
|
|
call :PrintFinalInstructions ""
|
|
endlocal
|
|
exit /b 0
|
|
)
|
|
|
|
call :InstallStandalone
|
|
set "STANDALONE_STATUS=!ERRORLEVEL!"
|
|
if !STANDALONE_STATUS! EQU 0 (
|
|
call :PrintFinalInstructions "!INSTALL_BIN_DIR!"
|
|
endlocal
|
|
exit /b 0
|
|
)
|
|
|
|
if !STANDALONE_STATUS! EQU 2 (
|
|
echo WARNING: Falling back to npm installation.
|
|
call :InstallNpm
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
echo WARNING: Standalone archive was unavailable before npm fallback; npm fallback also failed.
|
|
echo WARNING: Retry with --method standalone to debug the standalone failure, or install Node.js 20+ and rerun --method npm.
|
|
exit /b !ERRORLEVEL!
|
|
)
|
|
call :PrintFinalInstructions ""
|
|
endlocal
|
|
exit /b 0
|
|
)
|
|
|
|
echo WARNING: Standalone install failed. Retry with --method npm to use npm, or --method standalone to debug the standalone failure.
|
|
exit /b !STANDALONE_STATUS!
|
|
|
|
:usage
|
|
call :PrintUsage
|
|
exit /b 0
|
|
|
|
:usage_error
|
|
call :PrintUsage
|
|
exit /b 1
|
|
|
|
:PrintUsage
|
|
echo Qwen Code Installer
|
|
echo.
|
|
echo Usage: install-qwen-with-source.bat [OPTIONS]
|
|
echo.
|
|
echo Options:
|
|
echo -s, --source SOURCE Record the installation source.
|
|
echo Only letters, numbers, dot, underscore, and dash are allowed.
|
|
echo --method METHOD Install method: detect, standalone, or npm.
|
|
echo --mirror MIRROR Standalone archive mirror: github or aliyun.
|
|
echo --base-url URL Override standalone archive base URL.
|
|
echo --archive PATH Install from a local standalone archive.
|
|
echo --version VERSION Standalone release version. Defaults to latest.
|
|
echo --registry REGISTRY npm registry to use.
|
|
echo Defaults to QWEN_NPM_REGISTRY or https://registry.npmmirror.com
|
|
echo -h, --help Show this help message.
|
|
exit /b 0
|
|
|
|
:ValidateOptions
|
|
if "!METHOD!"=="" set "METHOD=detect"
|
|
|
|
set "QWEN_VALIDATE_METHOD=!METHOD!"
|
|
set "QWEN_VALIDATE_MIRROR=!MIRROR!"
|
|
set "QWEN_VALIDATE_BASE_URL=!BASE_URL!"
|
|
set "QWEN_VALIDATE_ARCHIVE_PATH=!ARCHIVE_PATH!"
|
|
set "QWEN_VALIDATE_VERSION=!VERSION!"
|
|
set "QWEN_VALIDATE_NPM_REGISTRY=!NPM_REGISTRY!"
|
|
set "QWEN_VALIDATE_INSTALL_BASE=!INSTALL_BASE!"
|
|
set "QWEN_VALIDATE_INSTALL_DIR=!INSTALL_DIR!"
|
|
set "QWEN_VALIDATE_INSTALL_BIN_DIR=!INSTALL_BIN_DIR!"
|
|
set "QWEN_VALIDATE_SOURCE=!SOURCE!"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$unsafe = [char[]](10,13,33,34,37,38,60,62,94,96,124); foreach ($name in 'METHOD','MIRROR','BASE_URL','ARCHIVE_PATH','VERSION','NPM_REGISTRY','INSTALL_BASE','INSTALL_DIR','INSTALL_BIN_DIR','SOURCE') { $value = [Environment]::GetEnvironmentVariable('QWEN_VALIDATE_' + $name); if ($null -ne $value -and $value.IndexOfAny($unsafe) -ge 0) { exit 1 } }"
|
|
set "PS_STATUS=%ERRORLEVEL%"
|
|
set "QWEN_VALIDATE_METHOD="
|
|
set "QWEN_VALIDATE_MIRROR="
|
|
set "QWEN_VALIDATE_BASE_URL="
|
|
set "QWEN_VALIDATE_ARCHIVE_PATH="
|
|
set "QWEN_VALIDATE_VERSION="
|
|
set "QWEN_VALIDATE_NPM_REGISTRY="
|
|
set "QWEN_VALIDATE_INSTALL_BASE="
|
|
set "QWEN_VALIDATE_INSTALL_DIR="
|
|
set "QWEN_VALIDATE_INSTALL_BIN_DIR="
|
|
set "QWEN_VALIDATE_SOURCE="
|
|
if %PS_STATUS% NEQ 0 (
|
|
echo ERROR: installer options contain unsafe command characters.
|
|
exit /b 1
|
|
)
|
|
|
|
if "!INSTALL_BASE!"=="" (
|
|
echo ERROR: QWEN_INSTALL_ROOT must not be empty.
|
|
exit /b 1
|
|
)
|
|
if "!INSTALL_DIR!"=="" (
|
|
echo ERROR: QWEN_INSTALL_LIB_DIR must not be empty.
|
|
exit /b 1
|
|
)
|
|
if "!INSTALL_BIN_DIR!"=="" (
|
|
echo ERROR: QWEN_INSTALL_BIN_DIR must not be empty.
|
|
exit /b 1
|
|
)
|
|
if "!INSTALL_BASE:~1,2!"==":\" goto validate_install_base_ok
|
|
if "!INSTALL_BASE:~1,2!"==":/" goto validate_install_base_ok
|
|
if "!INSTALL_BASE:~0,2!"=="\\" goto validate_install_base_ok
|
|
echo ERROR: QWEN_INSTALL_ROOT must be an absolute path.
|
|
exit /b 1
|
|
:validate_install_base_ok
|
|
if "!INSTALL_DIR:~1,2!"==":\" goto validate_install_dir_ok
|
|
if "!INSTALL_DIR:~1,2!"==":/" goto validate_install_dir_ok
|
|
if "!INSTALL_DIR:~0,2!"=="\\" goto validate_install_dir_ok
|
|
echo ERROR: QWEN_INSTALL_LIB_DIR must be an absolute path.
|
|
exit /b 1
|
|
:validate_install_dir_ok
|
|
if "!INSTALL_BIN_DIR:~1,2!"==":\" goto validate_install_bin_dir_ok
|
|
if "!INSTALL_BIN_DIR:~1,2!"==":/" goto validate_install_bin_dir_ok
|
|
if "!INSTALL_BIN_DIR:~0,2!"=="\\" goto validate_install_bin_dir_ok
|
|
echo ERROR: QWEN_INSTALL_BIN_DIR must be an absolute path.
|
|
exit /b 1
|
|
:validate_install_bin_dir_ok
|
|
|
|
if /i "!METHOD!"=="detect" goto validate_method_ok
|
|
if /i "!METHOD!"=="standalone" goto validate_method_ok
|
|
if /i "!METHOD!"=="npm" goto validate_method_ok
|
|
echo ERROR: --method must be detect, standalone, or npm.
|
|
exit /b 1
|
|
|
|
:validate_method_ok
|
|
if /i "!MIRROR!"=="github" goto validate_mirror_ok
|
|
if /i "!MIRROR!"=="aliyun" goto validate_mirror_ok
|
|
echo ERROR: --mirror must be github or aliyun.
|
|
exit /b 1
|
|
|
|
:validate_mirror_ok
|
|
call :ValidateHttpsUrlVar "BASE_URL" "--base-url"
|
|
if %ERRORLEVEL% NEQ 0 exit /b 1
|
|
|
|
call :ValidateHttpsUrlVar "NPM_REGISTRY" "--registry"
|
|
if %ERRORLEVEL% NEQ 0 exit /b 1
|
|
|
|
call :ValidateVersion
|
|
if %ERRORLEVEL% NEQ 0 exit /b 1
|
|
|
|
call :ValidateSource
|
|
exit /b %ERRORLEVEL%
|
|
|
|
:ValidateHttpsUrlVar
|
|
set "URL_VALUE=!%~1!"
|
|
set "URL_OPTION=%~2"
|
|
if "!URL_VALUE!"=="" exit /b 0
|
|
if /i "!URL_VALUE:~0,8!"=="https://" exit /b 0
|
|
|
|
echo ERROR: !URL_OPTION! must start with https://
|
|
exit /b 1
|
|
|
|
:ValidateVersion
|
|
if /i "!VERSION!"=="latest" exit /b 0
|
|
set "QWEN_VERSION_VALUE=!VERSION!"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$value = $env:QWEN_VERSION_VALUE; if ($value -match '^v?[0-9]+\.[0-9]+\.[0-9]+([.-][A-Za-z0-9]+)*$') { exit 0 }; exit 1"
|
|
set "PS_STATUS=%ERRORLEVEL%"
|
|
set "QWEN_VERSION_VALUE="
|
|
if %PS_STATUS% EQU 0 exit /b 0
|
|
echo ERROR: --version must be 'latest' or a semver string.
|
|
exit /b 1
|
|
|
|
:ValidateSource
|
|
if "!SOURCE!"=="unknown" exit /b 0
|
|
echo(!SOURCE!| findstr /R /C:"^[A-Za-z0-9._-][A-Za-z0-9._-]*$" >nul
|
|
if %ERRORLEVEL% EQU 0 exit /b 0
|
|
|
|
echo ERROR: --source may only contain letters, numbers, dot, underscore, or dash.
|
|
exit /b 1
|
|
|
|
:DetectTarget
|
|
set "TARGET="
|
|
if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64" set "TARGET=win-x64"
|
|
if /i "%PROCESSOR_ARCHITEW6432%"=="AMD64" set "TARGET=win-x64"
|
|
if "!TARGET!"=="" (
|
|
echo WARNING: Standalone archive is not available for this Windows architecture.
|
|
exit /b 1
|
|
)
|
|
exit /b 0
|
|
|
|
:ReleaseVersionPath
|
|
if /i "!VERSION!"=="latest" (
|
|
set "VERSION_PATH=latest"
|
|
exit /b 0
|
|
)
|
|
set "VERSION_PATH=!VERSION!"
|
|
if /i "!VERSION_PATH:~0,1!"=="v" exit /b 0
|
|
set "VERSION_PATH=v!VERSION_PATH!"
|
|
exit /b 0
|
|
|
|
:StandaloneBaseUrl
|
|
if not "!BASE_URL!"=="" (
|
|
set "STANDALONE_BASE_URL=!BASE_URL!"
|
|
exit /b 0
|
|
)
|
|
|
|
call :ReleaseVersionPath
|
|
if /i "!MIRROR!"=="aliyun" (
|
|
set "STANDALONE_BASE_URL=https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/releases/qwen-code/!VERSION_PATH!"
|
|
exit /b 0
|
|
)
|
|
|
|
if /i "!VERSION_PATH!"=="latest" (
|
|
set "STANDALONE_BASE_URL=https://github.com/QwenLM/qwen-code/releases/latest/download"
|
|
exit /b 0
|
|
)
|
|
|
|
set "STANDALONE_BASE_URL=https://github.com/QwenLM/qwen-code/releases/download/!VERSION_PATH!"
|
|
exit /b 0
|
|
|
|
:UrlExists
|
|
set "QWEN_CHECK_URL=%~1"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $request = [Net.WebRequest]::Create($env:QWEN_CHECK_URL); $request.Method = 'HEAD'; try { $response = $request.GetResponse(); $response.Close(); exit 0 } catch { exit 1 }" >nul 2>&1
|
|
set "PS_STATUS=%ERRORLEVEL%"
|
|
set "QWEN_CHECK_URL="
|
|
exit /b %PS_STATUS%
|
|
|
|
:DownloadFile
|
|
set "QWEN_DOWNLOAD_URL=%~1"
|
|
set "QWEN_DOWNLOAD_DEST=%~2"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference = 'Stop'; try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $client = New-Object Net.WebClient; $client.DownloadFile($env:QWEN_DOWNLOAD_URL, $env:QWEN_DOWNLOAD_DEST); exit 0 } catch { exit 1 }"
|
|
set "PS_STATUS=%ERRORLEVEL%"
|
|
set "QWEN_DOWNLOAD_URL="
|
|
set "QWEN_DOWNLOAD_DEST="
|
|
exit /b %PS_STATUS%
|
|
|
|
:VerifyChecksum
|
|
set "ARCHIVE_FILE=%~1"
|
|
set "CHECKSUM_SOURCE=%~2"
|
|
set "ARCHIVE_NAME=%~3"
|
|
set "CHECKSUM_FILE=!CHECKSUM_SOURCE!"
|
|
set "TEMP_CHECKSUM="
|
|
set "REQUIRE_CHECKSUM=1"
|
|
|
|
if "!CHECKSUM_FILE!"=="" (
|
|
for %%I in ("!ARCHIVE_FILE!") do set "CHECKSUM_FILE=%%~dpISHA256SUMS"
|
|
) else (
|
|
if /i "!CHECKSUM_FILE:~0,8!"=="https://" (
|
|
set "REQUIRE_CHECKSUM=1"
|
|
set "TEMP_CHECKSUM=%TEMP%\qwen-code-checksums-%RANDOM%%RANDOM%.txt"
|
|
call :DownloadFile "!CHECKSUM_FILE!" "!TEMP_CHECKSUM!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_CHECKSUM!" del /F /Q "!TEMP_CHECKSUM!" >nul 2>&1
|
|
echo ERROR: Could not download SHA256SUMS for checksum verification.
|
|
exit /b 1
|
|
)
|
|
set "CHECKSUM_FILE=!TEMP_CHECKSUM!"
|
|
)
|
|
)
|
|
|
|
if not exist "!CHECKSUM_FILE!" (
|
|
if "!REQUIRE_CHECKSUM!"=="1" (
|
|
echo ERROR: SHA256SUMS not found; cannot verify archive.
|
|
exit /b 1
|
|
)
|
|
echo WARNING: SHA256SUMS not found; skipping checksum verification.
|
|
exit /b 0
|
|
)
|
|
|
|
set "EXPECTED_HASH="
|
|
for /f "usebackq tokens=1,2" %%H in ("!CHECKSUM_FILE!") do (
|
|
set "CHECKSUM_HASH=%%H"
|
|
set "CHECKSUM_NAME=%%I"
|
|
if "!CHECKSUM_NAME:~0,1!"=="*" set "CHECKSUM_NAME=!CHECKSUM_NAME:~1!"
|
|
if "!CHECKSUM_NAME!"=="!ARCHIVE_NAME!" (
|
|
if "!EXPECTED_HASH!"=="" set "EXPECTED_HASH=!CHECKSUM_HASH!"
|
|
)
|
|
)
|
|
|
|
if "!EXPECTED_HASH!"=="" (
|
|
if not "!TEMP_CHECKSUM!"=="" del /F /Q "!TEMP_CHECKSUM!" >nul 2>&1
|
|
if "!REQUIRE_CHECKSUM!"=="1" (
|
|
echo ERROR: Checksum entry for !ARCHIVE_NAME! not found.
|
|
exit /b 1
|
|
)
|
|
echo WARNING: Checksum entry for !ARCHIVE_NAME! not found; skipping checksum verification.
|
|
exit /b 0
|
|
)
|
|
|
|
set "ACTUAL_HASH="
|
|
set "QWEN_HASH_FILE=!ARCHIVE_FILE!"
|
|
for /f "delims=" %%H in ('powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference = 'Stop'; (Get-FileHash -Algorithm SHA256 -LiteralPath $env:QWEN_HASH_FILE).Hash" 2^>nul') do (
|
|
if "!ACTUAL_HASH!"=="" set "ACTUAL_HASH=%%H"
|
|
)
|
|
set "QWEN_HASH_FILE="
|
|
|
|
if not "!TEMP_CHECKSUM!"=="" del /F /Q "!TEMP_CHECKSUM!" >nul 2>&1
|
|
|
|
if "!ACTUAL_HASH!"=="" (
|
|
if "!REQUIRE_CHECKSUM!"=="1" (
|
|
echo ERROR: Could not calculate SHA-256 checksum for archive.
|
|
exit /b 1
|
|
)
|
|
echo WARNING: Could not calculate SHA-256 checksum; skipping checksum verification.
|
|
exit /b 0
|
|
)
|
|
|
|
if /i not "!EXPECTED_HASH!"=="!ACTUAL_HASH!" (
|
|
echo ERROR: Checksum verification failed for !ARCHIVE_NAME!.
|
|
exit /b 1
|
|
)
|
|
|
|
echo SUCCESS: Checksum verified for !ARCHIVE_NAME!.
|
|
exit /b 0
|
|
|
|
:InstallStandalone
|
|
set "TEMP_DIR="
|
|
set "CHECKSUM_SOURCE="
|
|
|
|
REM Resolve the archive from a local file or from the configured release mirror.
|
|
if not "!ARCHIVE_PATH!"=="" (
|
|
set "ARCHIVE_FILE=!ARCHIVE_PATH!"
|
|
for %%I in ("!ARCHIVE_FILE!") do set "ARCHIVE_NAME=%%~nxI"
|
|
if not exist "!ARCHIVE_FILE!" (
|
|
echo ERROR: Standalone archive not found: !ARCHIVE_FILE!
|
|
exit /b 1
|
|
)
|
|
) else (
|
|
call :DetectTarget
|
|
if !ERRORLEVEL! NEQ 0 exit /b 2
|
|
|
|
set "ARCHIVE_NAME=qwen-code-win-x64.zip"
|
|
call :StandaloneBaseUrl
|
|
set "ARCHIVE_URL=!STANDALONE_BASE_URL!/!ARCHIVE_NAME!"
|
|
set "CHECKSUM_SOURCE=!STANDALONE_BASE_URL!/SHA256SUMS"
|
|
|
|
if /i "!METHOD!"=="detect" (
|
|
call :UrlExists "!ARCHIVE_URL!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
echo WARNING: Standalone archive not found: !ARCHIVE_NAME!
|
|
exit /b 2
|
|
)
|
|
)
|
|
|
|
set "TEMP_DIR=%TEMP%\qwen-code-install-%RANDOM%%RANDOM%"
|
|
mkdir "!TEMP_DIR!" >nul 2>&1
|
|
set "ARCHIVE_FILE=!TEMP_DIR!\!ARCHIVE_NAME!"
|
|
|
|
echo INFO: Downloading !ARCHIVE_URL!
|
|
call :DownloadFile "!ARCHIVE_URL!" "!ARCHIVE_FILE!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo WARNING: Failed to download standalone archive.
|
|
exit /b 2
|
|
)
|
|
)
|
|
|
|
if "!TEMP_DIR!"=="" (
|
|
set "TEMP_DIR=%TEMP%\qwen-code-install-%RANDOM%%RANDOM%"
|
|
mkdir "!TEMP_DIR!" >nul 2>&1
|
|
)
|
|
|
|
REM Verify integrity before extraction or changing the install directory.
|
|
call :VerifyChecksum "!ARCHIVE_FILE!" "!CHECKSUM_SOURCE!" "!ARCHIVE_NAME!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
exit /b 1
|
|
)
|
|
|
|
REM Extract into a temporary directory, then validate required entry points.
|
|
set "EXTRACT_DIR=!TEMP_DIR!\extract"
|
|
mkdir "!EXTRACT_DIR!" >nul 2>&1
|
|
call :ValidateArchiveContents "!ARCHIVE_FILE!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
exit /b 1
|
|
)
|
|
set "QWEN_ARCHIVE_FILE=!ARCHIVE_FILE!"
|
|
set "QWEN_EXTRACT_DIR=!EXTRACT_DIR!"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "Expand-Archive -LiteralPath $env:QWEN_ARCHIVE_FILE -DestinationPath $env:QWEN_EXTRACT_DIR -Force"
|
|
set "PS_STATUS=!ERRORLEVEL!"
|
|
set "QWEN_ARCHIVE_FILE="
|
|
set "QWEN_EXTRACT_DIR="
|
|
if !PS_STATUS! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Failed to extract standalone archive.
|
|
exit /b 1
|
|
)
|
|
|
|
call :RejectArchiveLinks "!EXTRACT_DIR!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
exit /b 1
|
|
)
|
|
|
|
if not exist "!EXTRACT_DIR!\qwen-code\bin\qwen.cmd" (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Archive does not contain qwen-code\bin\qwen.cmd.
|
|
exit /b 1
|
|
)
|
|
|
|
if not exist "!EXTRACT_DIR!\qwen-code\node\node.exe" (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Archive does not contain qwen-code\node\node.exe.
|
|
exit /b 1
|
|
)
|
|
|
|
if not exist "!INSTALL_BASE!" mkdir "!INSTALL_BASE!"
|
|
if not exist "!INSTALL_BIN_DIR!" mkdir "!INSTALL_BIN_DIR!"
|
|
for %%I in ("!INSTALL_DIR!") do set "INSTALL_PARENT=%%~dpI"
|
|
if not exist "!INSTALL_PARENT!" mkdir "!INSTALL_PARENT!"
|
|
|
|
REM Stage into .new and keep .old so failed upgrades can roll back.
|
|
set "NEW_INSTALL_DIR=!INSTALL_DIR!.new"
|
|
set "OLD_INSTALL_DIR=!INSTALL_DIR!.old"
|
|
|
|
call :EnsureManagedInstallDir "!INSTALL_DIR!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
exit /b 1
|
|
)
|
|
call :EnsureManagedInstallDir "!NEW_INSTALL_DIR!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
exit /b 1
|
|
)
|
|
call :EnsureManagedInstallDir "!OLD_INSTALL_DIR!"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
exit /b 1
|
|
)
|
|
|
|
if exist "!NEW_INSTALL_DIR!" rmdir /S /Q "!NEW_INSTALL_DIR!" >nul 2>&1
|
|
if exist "!OLD_INSTALL_DIR!" rmdir /S /Q "!OLD_INSTALL_DIR!" >nul 2>&1
|
|
move /Y "!EXTRACT_DIR!\qwen-code" "!NEW_INSTALL_DIR!" >nul
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Failed to stage standalone archive.
|
|
exit /b 1
|
|
)
|
|
|
|
if exist "!INSTALL_DIR!" (
|
|
move /Y "!INSTALL_DIR!" "!OLD_INSTALL_DIR!" >nul
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Failed to back up existing install at !INSTALL_DIR!.
|
|
exit /b 1
|
|
)
|
|
)
|
|
move /Y "!NEW_INSTALL_DIR!" "!INSTALL_DIR!" >nul
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!OLD_INSTALL_DIR!" move /Y "!OLD_INSTALL_DIR!" "!INSTALL_DIR!" >nul
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Failed to install standalone archive to !INSTALL_DIR!.
|
|
exit /b 1
|
|
)
|
|
|
|
(
|
|
echo @echo off
|
|
echo call "!INSTALL_DIR!\bin\qwen.cmd" %%*
|
|
) > "!INSTALL_BIN_DIR!\qwen.cmd.new"
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!INSTALL_DIR!" rmdir /S /Q "!INSTALL_DIR!" >nul 2>&1
|
|
if exist "!OLD_INSTALL_DIR!" move /Y "!OLD_INSTALL_DIR!" "!INSTALL_DIR!" >nul
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Failed to create qwen wrapper in !INSTALL_BIN_DIR!.
|
|
exit /b 1
|
|
)
|
|
move /Y "!INSTALL_BIN_DIR!\qwen.cmd.new" "!INSTALL_BIN_DIR!\qwen.cmd" >nul
|
|
if !ERRORLEVEL! NEQ 0 (
|
|
if exist "!INSTALL_BIN_DIR!\qwen.cmd.new" del /F /Q "!INSTALL_BIN_DIR!\qwen.cmd.new" >nul 2>&1
|
|
if exist "!INSTALL_DIR!" rmdir /S /Q "!INSTALL_DIR!" >nul 2>&1
|
|
if exist "!OLD_INSTALL_DIR!" move /Y "!OLD_INSTALL_DIR!" "!INSTALL_DIR!" >nul
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
echo ERROR: Failed to create qwen wrapper in !INSTALL_BIN_DIR!.
|
|
exit /b 1
|
|
)
|
|
|
|
if exist "!OLD_INSTALL_DIR!" rmdir /S /Q "!OLD_INSTALL_DIR!" >nul 2>&1
|
|
|
|
set "PATH=!INSTALL_BIN_DIR!;!PATH!"
|
|
call :CreateSourceJson
|
|
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
|
|
|
|
echo SUCCESS: Qwen Code standalone archive installed successfully.
|
|
echo INFO: Installed to !INSTALL_DIR!
|
|
exit /b 0
|
|
|
|
:ValidateArchiveContents
|
|
set "QWEN_ARCHIVE_FILE=%~1"
|
|
REM Enumerate archive entries and reject any with path traversal indicators:
|
|
REM empty names, leading '/', drive-rooted paths, '..' segments, or control chars.
|
|
REM This prevents Zip Slip attacks before extraction.
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$ErrorActionPreference = 'Stop'; $archive = $null; try { Add-Type -AssemblyName System.IO.Compression.FileSystem; $archive = [IO.Compression.ZipFile]::OpenRead($env:QWEN_ARCHIVE_FILE); if ($archive.Entries.Count -eq 0) { [Console]::Error.WriteLine('Archive is empty: ' + $env:QWEN_ARCHIVE_FILE); exit 3 }; foreach ($entry in $archive.Entries) { $raw = $entry.FullName; if ($raw.IndexOfAny([char[]](10,13)) -ge 0) { [Console]::Error.WriteLine('Archive contains unsafe path with control character: ' + $raw); exit 1 }; $name = $raw -replace '\\', '/'; while ($name.StartsWith('./')) { $name = $name.Substring(2) }; if ($name -eq '' -or $name.StartsWith('/') -or $name -match '^[A-Za-z]:' -or $name -match '(^|/)\.\.(/|$)') { [Console]::Error.WriteLine('Archive contains unsafe path: ' + $entry.FullName); exit 1 } } } catch { [Console]::Error.WriteLine($_.Exception.Message); exit 2 } finally { if ($null -ne $archive) { $archive.Dispose() } }"
|
|
set "PS_STATUS=%ERRORLEVEL%"
|
|
set "QWEN_ARCHIVE_FILE="
|
|
if %PS_STATUS% EQU 0 exit /b 0
|
|
if %PS_STATUS% EQU 1 (
|
|
echo ERROR: Archive contains unsafe path entries.
|
|
exit /b 1
|
|
)
|
|
if %PS_STATUS% EQU 2 (
|
|
echo ERROR: Archive could not be inspected before extraction.
|
|
exit /b 1
|
|
)
|
|
if %PS_STATUS% EQU 3 (
|
|
echo ERROR: Archive is empty: %~1
|
|
exit /b 1
|
|
)
|
|
echo ERROR: Archive validation failed before extraction.
|
|
exit /b %PS_STATUS%
|
|
|
|
:RejectArchiveLinks
|
|
set "QWEN_EXTRACT_DIR=%~1"
|
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "$item = Get-ChildItem -LiteralPath $env:QWEN_EXTRACT_DIR -Recurse -Force | Where-Object { ($_.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 } | Select-Object -First 1; if ($item) { exit 1 }"
|
|
set "PS_STATUS=%ERRORLEVEL%"
|
|
set "QWEN_EXTRACT_DIR="
|
|
if %PS_STATUS% NEQ 0 echo ERROR: Archive contains symlinks or reparse points; refusing to install.
|
|
exit /b %PS_STATUS%
|
|
|
|
:EnsureManagedInstallDir
|
|
set "MANAGED_DIR=%~1"
|
|
if not exist "!MANAGED_DIR!" exit /b 0
|
|
if exist "!MANAGED_DIR!\manifest.json" exit /b 0
|
|
|
|
echo ERROR: !MANAGED_DIR! exists but is not a Qwen Code standalone install.
|
|
echo ERROR: Refusing to overwrite it. Move or remove it manually, then rerun the installer.
|
|
exit /b 1
|
|
|
|
:RequireNode
|
|
where node >nul 2>&1
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo ERROR: Node.js was not found.
|
|
echo.
|
|
echo Node.js 20 or newer is required before installing Qwen Code with npm.
|
|
echo Please install Node.js from https://nodejs.org/ and rerun this installer.
|
|
exit /b 1
|
|
)
|
|
|
|
for /f "delims=" %%i in ('node -p "process.versions.node" 2^>nul') do set "NODE_VERSION=%%i"
|
|
if "%NODE_VERSION%"=="" (
|
|
echo ERROR: Unable to determine Node.js version.
|
|
echo Node.js 20 or newer is required before installing Qwen Code with npm.
|
|
exit /b 1
|
|
)
|
|
|
|
for /f "tokens=1 delims=." %%a in ("%NODE_VERSION%") do set "MAJOR_VERSION=%%a"
|
|
set /a NODE_MAJOR_NUM=%MAJOR_VERSION% >nul 2>&1
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo ERROR: Unable to determine Node.js version.
|
|
echo Node.js 20 or newer is required before installing Qwen Code with npm.
|
|
exit /b 1
|
|
)
|
|
|
|
if %NODE_MAJOR_NUM% LSS 20 (
|
|
echo ERROR: Node.js %NODE_VERSION% is installed, but Node.js 20 or newer is required.
|
|
echo Please install Node.js from https://nodejs.org/ and rerun this installer.
|
|
exit /b 1
|
|
)
|
|
|
|
echo SUCCESS: Node.js %NODE_VERSION% detected.
|
|
exit /b 0
|
|
|
|
:RequireNpm
|
|
where npm >nul 2>&1
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo ERROR: npm was not found.
|
|
echo Please install Node.js with npm included, then rerun this installer.
|
|
exit /b 1
|
|
)
|
|
|
|
for /f "delims=" %%i in ('npm -v 2^>nul') do set "NPM_VERSION=%%i"
|
|
echo SUCCESS: npm %NPM_VERSION% detected.
|
|
exit /b 0
|
|
|
|
:InstallNpm
|
|
call :RequireNode
|
|
if %ERRORLEVEL% NEQ 0 exit /b 1
|
|
|
|
call :RequireNpm
|
|
if %ERRORLEVEL% NEQ 0 exit /b 1
|
|
|
|
where qwen >nul 2>&1
|
|
if %ERRORLEVEL% EQU 0 (
|
|
for /f "delims=" %%i in ('qwen --version 2^>nul') do set "QWEN_VERSION=%%i"
|
|
echo INFO: Existing Qwen Code detected: !QWEN_VERSION!
|
|
echo INFO: Upgrading to the latest version.
|
|
)
|
|
|
|
echo INFO: Running: npm install -g @qwen-code/qwen-code@latest --registry !NPM_REGISTRY!
|
|
call npm install -g @qwen-code/qwen-code@latest --registry "!NPM_REGISTRY!"
|
|
if %ERRORLEVEL% NEQ 0 (
|
|
echo ERROR: Failed to install Qwen Code.
|
|
echo.
|
|
echo This installer does not change your npm prefix or PATH.
|
|
echo If the failure is a permission error, fix your npm global package directory, then run:
|
|
echo npm install -g @qwen-code/qwen-code@latest --registry !NPM_REGISTRY!
|
|
exit /b 1
|
|
)
|
|
|
|
echo SUCCESS: Qwen Code installed successfully.
|
|
call :CreateSourceJson
|
|
exit /b 0
|
|
|
|
:CreateSourceJson
|
|
if "!SOURCE!"=="unknown" exit /b 0
|
|
|
|
set "QWEN_DIR=!USERPROFILE!\.qwen"
|
|
if not exist "!QWEN_DIR!" mkdir "!QWEN_DIR!"
|
|
|
|
(
|
|
echo {
|
|
echo "source": "!SOURCE!"
|
|
echo }
|
|
) > "!QWEN_DIR!\source.json"
|
|
|
|
echo SUCCESS: Installation source saved to !USERPROFILE!\.qwen\source.json
|
|
exit /b 0
|
|
|
|
:PrintFinalInstructions
|
|
set "EXTRA_BIN=%~1"
|
|
if not "!EXTRA_BIN!"=="" set "PATH=!EXTRA_BIN!;!PATH!"
|
|
|
|
echo.
|
|
echo ===========================================
|
|
echo Installation completed!
|
|
echo ===========================================
|
|
echo.
|
|
|
|
where qwen >nul 2>&1
|
|
if %ERRORLEVEL% EQU 0 (
|
|
for /f "delims=" %%i in ('qwen --version 2^>nul') do set "QWEN_VERSION=%%i"
|
|
echo SUCCESS: Qwen Code is ready to use: !QWEN_VERSION!
|
|
echo.
|
|
echo You can now run: qwen
|
|
echo.
|
|
echo INFO: Run qwen in your project directory to start an interactive session.
|
|
exit /b 0
|
|
)
|
|
|
|
echo WARNING: Qwen Code was installed, but qwen is not on PATH in this prompt.
|
|
echo.
|
|
echo Restart your command prompt, then run: qwen
|
|
if not "!EXTRA_BIN!"=="" (
|
|
echo.
|
|
echo Or add this directory to PATH:
|
|
echo !EXTRA_BIN!
|
|
echo Then run:
|
|
echo qwen
|
|
exit /b 0
|
|
)
|
|
|
|
for /f "delims=" %%i in ('npm prefix -g 2^>nul') do set "NPM_PREFIX=%%i"
|
|
if not "!NPM_PREFIX!"=="" (
|
|
echo.
|
|
echo Or add this npm global directory to PATH:
|
|
echo !NPM_PREFIX!
|
|
echo Then run:
|
|
echo qwen
|
|
)
|
|
exit /b 0
|