/** * @license * Copyright 2025 Qwen Team * SPDX-License-Identifier: Apache-2.0 */ import { describe, expect, it, vi } from 'vitest'; const { appendFileSync, chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, symlinkSync, writeFileSync, } = await vi.importActual('node:fs'); const { execFileSync } = await vi.importActual('node:child_process'); const crypto = await vi.importActual('node:crypto'); const { tmpdir } = await vi.importActual('node:os'); const path = await vi.importActual('node:path'); const { pathToFileURL } = await vi.importActual('node:url'); const readScript = (path) => readFileSync(path, 'utf8'); const standaloneReleaseScriptUrl = pathToFileURL( path.resolve('scripts/build-standalone-release.js'), ).href; const hostedInstallationScriptUrl = pathToFileURL( path.resolve('scripts/build-hosted-installation-assets.js'), ).href; const installationReleaseVerificationScriptUrl = pathToFileURL( path.resolve('scripts/verify-installation-release.js'), ).href; const releaseScriptUtilsUrl = pathToFileURL( path.resolve('scripts/release-script-utils.js'), ).href; // These E2E cases execute the Unix shell installer and POSIX symlink behavior. // Windows batch behavior has separate Windows-only E2E coverage below. const itOnUnix = process.platform === 'win32' ? it.skip : it; const itOnWindows = process.platform === 'win32' ? it : it.skip; vi.setConfig({ testTimeout: 30_000 }); describe('installation scripts', () => { it('keeps the Linux/macOS installer lightweight', () => { const script = readScript( 'scripts/installation/install-qwen-standalone.sh', ); expect(script).not.toContain('install_nvm'); expect(script).not.toContain('install_nvm.sh'); expect(script).not.toContain('nvm install'); expect(script).not.toContain('NVM_NODEJS_ORG_MIRROR'); expect(script).not.toContain('npm config set prefix'); expect(script).not.toContain('clean_npmrc_conflict'); expect(script).not.toContain('.npmrc'); expect(script).not.toContain('.npm-global'); expect(script).not.toMatch(/^\s*exec\s+qwen\s*$/m); expect(script).not.toContain('--print-env'); expect(script).not.toMatch(/brew install node@\d+/); expect(script).toContain('brew install node'); expect(script).toContain( '--source may only contain letters, numbers, dot, underscore, or dash', ); expect(script).toContain('Node.js 22 or newer is required'); expect(script).toContain('npm_package_spec()'); expect(script).toContain('@qwen-code/qwen-code@latest'); expect(script).toContain('Installing Qwen Code version:'); expect(script).toContain('QWEN CODE'); expect(script).toContain( 'Qwen Code ${installed_version} installed successfully.', ); expect(script).toContain('To start:'); expect(script).toContain('Installed to:'); expect(script).toContain('Uninstall:'); expect(script).toContain('uninstall-qwen-standalone.sh'); expect(script).not.toContain('rm -rf $(shell_quote "${install_dir}")'); }); it('supports code-server-style standalone install on Linux/macOS', () => { const script = readScript( 'scripts/installation/install-qwen-standalone.sh', ); expect(script).toContain('--method METHOD'); expect(script).toContain('--mirror MIRROR'); expect(script).toContain('--base-url URL'); expect(script).toContain('--archive PATH'); expect(script).toContain('install_standalone()'); expect(script).toContain('install_npm()'); expect(script).toContain('detect_target()'); expect(script).toContain('verify_checksum()'); expect(script).toContain('SHA256SUMS not found; cannot verify archive'); expect(script).toContain('awk -v archive_name'); expect(script).not.toContain( 'grep -E "(^|[[:space:]])[*]?${archive_name}$"', ); expect(script).toContain('validate_archive_contents()'); expect(script).toContain('Archive contains unsafe path'); expect(script).toContain('qwen-code-${target}'); expect(script).toContain('*.tar.xz)'); expect(script).toContain('METHOD="${METHOD:-detect}"'); expect(script).toContain('must start with https://'); expect(script).toContain('Falling back to npm installation'); expect(script).toContain('standalone_status=$?'); expect(script).toContain('[[ "${standalone_status}" -eq 2 ]]'); expect(script).toMatch( /Aliyun standalone archive not found; retrying GitHub mirror\.[\s\S]*checksum_source="\$\{base_url\}\/SHA256SUMS"[\s\S]*MIRROR="github"/, ); expect(script).toMatch( /archive_url="\$\{github_fallback_base_url\}\/\$\{archive_name\}"[\s\S]*checksum_source="\$\{github_fallback_base_url\}\/SHA256SUMS"[\s\S]*MIRROR="github"[\s\S]*Aliyun standalone archive download failed; retrying GitHub mirror\./, ); expect(script).toContain( 'Standalone install failed. Retry with --method npm', ); expect(script).not.toContain('ln -sf "${INSTALL_LIB_DIR}/bin/qwen"'); expect(script).toContain('shell_quote()'); expect(script).toContain('exec ${quoted_qwen_bin} "\\$@"'); expect(script).toContain('validate_version()'); expect(script).toContain('validate_install_path'); expect(script).toContain('validate_https_url "${NPM_REGISTRY}"'); expect(script).toContain('qwen-code/node/bin/node'); expect(script).toContain('Archive contains symlinks; refusing to install'); expect(script).toContain('not a Qwen Code standalone install'); expect(script).toContain( 'Return 2 only when a standalone archive is unavailable', ); expect(script).toContain('npm fallback also failed'); expect(script).toContain( 'unzip -q "${archive_path}" -d "${destination}" || return 1', ); expect(script).toContain( 'tar -xzf "${archive_path}" -C "${destination}" || return 1', ); expect(script).toContain( 'curl -fL --retry 2 --connect-timeout 15 --max-time 300 --progress-bar "${url}" -o "${destination}"', ); expect(script).toContain( 'curl -fsSL --retry 2 --connect-timeout 10 --max-time 30 "${url}"', ); expect(script).toContain('wget -q "${wget_args[@]}" -O - "${url}"'); expect(script).toContain( 'wget --progress=bar:force:noscroll "${wget_args[@]}" "${url}" -O "${destination}"', ); expect(script).toContain('wget_args+=(--read-timeout=300)'); expect(script).toContain( 'curl -fsL --retry 1 --connect-timeout 10 --max-time "${timeout}"', ); expect(script).toContain('wget_args+=(--read-timeout=30)'); expect(script).toContain('echo "Downloading ${archive_name}"'); expect(script).not.toContain( 'curl -fsSL --retry 2 "${url}" -o "${destination}"', ); expect(script).not.toContain( 'wget -q --tries=3 "${url}" -O "${destination}"', ); expect(script).toContain('TEMP_DIRS+='); expect(script).toContain('validate_github_repo()'); expect(script).toContain( 'QWEN_INSTALL_GITHUB_REPO must be in owner/repo format', ); expect(script).toContain('set -gx PATH ${quoted_install_bin_dir} \\$PATH'); expect(script).toContain('export PATH=${quoted_install_bin_dir}:\\$PATH'); expect(script).toContain('Unsupported shell for automatic PATH update'); expect(script).toContain('# Qwen Code PATH block begin'); expect(script).toContain('# Qwen Code PATH block end'); expect(script).toContain('probe_url_available()'); expect(script).toContain('/latest/VERSION'); expect(script).toContain('resolve_aliyun_version_path()'); expect(script).toContain('retrying GitHub mirror'); expect(script).toContain('entry="${entry//\\\\//}"'); expect(script).toContain('restore_stale_install_backup()'); expect(script).toContain( 'restore_stale_install_backup "${old_install_dir}" "${INSTALL_LIB_DIR}"', ); expect(script).not.toContain( 'rm -rf "${new_install_dir}" "${old_install_dir}" "${wrapper_tmp}"', ); expect(script).not.toContain('-print -quit'); }); it('keeps the Windows installer lightweight', () => { const script = readScript( 'scripts/installation/install-qwen-standalone.bat', ); expect(script).not.toContain('InstallNodeJSDirectly'); expect(script).not.toContain('node-v!NODE_VERSION!'); expect(script).not.toContain('msiexec'); expect(script).toContain('Invoke-WebRequest'); expect(script).toContain( '& $curl --connect-timeout 15 --max-time 300 --retry 2 -#fSLo', ); expect(script).toContain( '& $curl --connect-timeout 15 --max-time 300 --retry 2 -fsSLo', ); expect(script).toContain('-TimeoutSec 300'); expect(script).toContain('$request.Timeout = 10000'); expect(script).toContain('$request.ReadWriteTimeout = 30000'); expect(script).not.toContain('PowerShell (Administrator)'); expect(script).not.toContain('echo INFO: Installation source: %SOURCE%'); expect(script).not.toMatch(/^\s*call\s+qwen\s*$/m); expect(script).toContain(':ValidateSource'); expect(script).toContain(':PrintUsage'); expect(script).toContain('findstr /R'); expect(script).toContain( '--source may only contain letters, numbers, dot, underscore, or dash', ); expect(script).toContain('Node.js 22 or newer is required'); expect(script).toContain('Please install Node.js'); expect(script).toContain(':NpmPackageSpec'); expect(script).toContain('@qwen-code/qwen-code@latest'); expect(script).toContain('Installing Qwen Code version:'); expect(script).toContain('QWEN CODE'); expect(script).toContain( 'Qwen Code !INSTALLED_VERSION! installed successfully.', ); expect(script).toContain('To start:'); expect(script).toContain('Installed to:'); expect(script).toContain('Uninstall:'); expect(script).toContain('uninstall-qwen-standalone.ps1'); expect(script).toContain('QWEN_VERSION_POINTER_FILE'); expect(script).toContain('QWEN_NORMALIZED_VERSION_FILE'); expect(script).toContain('NORMALIZED_VERSION_FILE'); expect(script).toContain( '[IO.File]::ReadAllText($env:QWEN_VERSION_POINTER_FILE)', ); expect(script).toContain( '[IO.File]::WriteAllText($env:QWEN_NORMALIZED_VERSION_FILE', ); expect(script).not.toContain( 'findstr /R /C:"^[0-9][0-9]*\\.[0-9][0-9]*\\.[0-9][0-9]*$"', ); expect(script).not.toContain('rmdir /S /Q "!SUMMARY_INSTALL_DIR!"'); expect(script).not.toContain('del /F /Q "!INSTALLED_BIN!"'); }); it('supports code-server-style standalone install on Windows', () => { const script = readScript( 'scripts/installation/install-qwen-standalone.bat', ); expect(script).toContain('--method METHOD'); expect(script).toContain('--mirror MIRROR'); expect(script).toContain('--base-url URL'); expect(script).toContain('--archive PATH'); expect(script).toContain(':InstallStandalone'); expect(script).toContain(':InstallNpm'); expect(script).toContain(':VerifyChecksum'); expect(script).toContain('SHA256SUMS not found; cannot verify archive'); expect(script).toContain('Get-FileHash -Algorithm SHA256'); expect(script).toContain('tokens=1,2'); expect(script).toContain('CHECKSUM_NAME'); expect(script).toContain('if "!CHECKSUM_NAME!"=="!ARCHIVE_NAME!"'); expect(script).not.toContain('findstr /C:"!ARCHIVE_NAME!"'); expect(script).not.toContain('certutil -hashfile'); expect(script).toContain('qwen-code-!TARGET!.zip'); expect(script).toContain( 'if /i "!PROCESSOR_ARCHITECTURE!"=="AMD64" set "TARGET=win-x64"', ); expect(script).not.toContain('if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64"'); expect(script).toContain('Expand-Archive'); expect(script).toContain('$env:QWEN_DOWNLOAD_URL'); expect(script).toContain('$env:QWEN_ARCHIVE_FILE'); expect(script).toContain( 'if defined QWEN_INSTALL_ROOT set "INSTALL_BASE=!QWEN_INSTALL_ROOT!"', ); expect(script).not.toContain('%QWEN_INSTALL_ROOT%'); expect(script).toContain('set "QWEN_VALIDATE_INSTALL_BASE=!INSTALL_BASE!"'); expect(script).toContain( 'installer options contain unsafe command characters', ); expect(script).not.toContain('-EncodedCommand'); expect(script).toContain('QWEN_VALIDATE_OPTIONS_SCRIPT'); expect(script).toContain('$unsafe = [char[]](10,13,33,34'); expect(script).toContain( 'powershell -NoProfile -ExecutionPolicy Bypass -File "!QWEN_VALIDATE_OPTIONS_SCRIPT!"', ); expect(script).toContain('if "!INSTALL_BASE:~1,2!"==":/"'); expect(script).toContain('if "!INSTALL_DIR:~1,2!"==":/"'); expect(script).toContain('if "!INSTALL_BIN_DIR:~1,2!"==":/"'); expect(script).toContain(':ValidateVersion'); expect(script).toContain( 'call :ValidateHttpsUrlVar "NPM_REGISTRY" "--registry"', ); expect(script).toContain('$curl = $env:QWEN_INSTALL_CURL_EXE'); expect(script).toContain('QWEN_INSTALL_CURL_EXE'); expect(script).toContain('Get-Command curl.exe -CommandType Application'); expect(script).toContain( '--connect-timeout 15 --max-time 300 --retry 2 -#fSLo', ); expect(script).toContain( '--connect-timeout 15 --max-time 300 --retry 2 -fsSLo', ); expect(script).toContain('Invoke-WebRequest'); expect(script).toContain('-TimeoutSec 300'); expect(script).toContain( '[Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13', ); expect(script).toContain( '$request = [Net.WebRequest]::Create($env:QWEN_CHECK_URL)', ); expect(script).toContain("Headers.Add('Range', 'bytes=0-0')"); expect(script).toContain('must start with https://'); expect(script).toContain('Falling back to npm installation'); expect(script).toContain('set "STANDALONE_STATUS=!ERRORLEVEL!"'); expect(script).toContain('if !STANDALONE_STATUS! EQU 2'); expect(script).toContain('set "ARG_KEY=%~1"'); expect(script).toContain('set "ARG_HAS_INLINE_VALUE=0"'); expect(script).toContain('if "!ARG_HAS_INLINE_VALUE!"=="1"'); expect(script).toContain('if /i "!ARG_KEY!"=="--version"'); expect(script).toContain('$value -match'); expect(script).toContain('QWEN_INSTALL_GITHUB_REPO'); expect(script).toContain( 'QWEN_INSTALL_GITHUB_REPO must be in owner/repo format', ); expect(script).toContain( 'Standalone install failed. Retry with --method npm', ); expect(script).toContain('qwen-code\\node\\node.exe'); expect(script).toContain('Archive contains symlinks or reparse points'); expect(script).toContain('unsafe path with control character'); expect(script).toContain('Failed to update user PATH'); expect(script).toContain('QWEN_INSTALL_ROOT'); expect(script).toContain('npm fallback also failed'); expect(script).toContain('echo Downloading !ARCHIVE_NAME!'); expect(script).toContain(':CreateTempFile'); expect(script).toContain('/latest/VERSION'); expect(script).toContain(':ResolveAliyunVersionPath'); expect(script).toContain(':UseGithubFallbackBaseUrl'); expect(script).toContain('retrying GitHub mirror'); expect(script).toContain('endlocal & set "PATH=%INSTALL_BIN_DIR%;%PATH%"'); expect(script).not.toContain( 'endlocal & set "PATH=!INSTALL_BIN_DIR!;%PATH%"', ); expect(script).toContain( 'if /i "!METHOD!"=="detect" exit /b 2\r\n exit /b 1', ); expect(script).toContain(':RestoreStaleInstallBackup'); expect(script).toContain('call :RestoreStaleInstallBackup'); expect(script).not.toContain( 'ERROR: Failed to remove stale backup directory', ); expect(script).toContain('call :ValidateRawEnvironmentOptions'); expect(script).toContain('$rawNames = @('); expect(script).toContain("'QWEN_INSTALL_VERSION'"); expect(script.indexOf('$rawNames = @(')).toBeLessThan( script.indexOf('set "QWEN_VALIDATE_VERSION=!VERSION!"'), ); expect(script).toContain('set "ARCHIVE_NAME=qwen-code-!TARGET!.zip"'); expect(script).toContain('Keep :DetectTarget in sync with RELEASE_TARGETS'); // ARM64 is intentionally not detected: RELEASE_TARGETS has no win-arm64 // entry, so we want :DetectTarget to fall through to the unsupported-arch // branch and let the caller fall back to npm. expect(script).not.toContain( 'if /i "!PROCESSOR_ARCHITECTURE!"=="ARM64" set "TARGET=win-arm64"', ); expect(script).not.toContain('%RANDOM%'); }); it('checks out the Windows standalone batch installer with CRLF line endings', () => { const attrs = execFileSync( 'git', [ 'check-attr', 'eol', '--', 'scripts/installation/install-qwen-standalone.bat', ], { encoding: 'utf8' }, ); expect(attrs).toContain( 'scripts/installation/install-qwen-standalone.bat: eol: crlf', ); const script = readScript( 'scripts/installation/install-qwen-standalone.bat', ); const bareLfLines = script .split(/(?<=\n)/) .filter((line) => line.endsWith('\n') && !line.endsWith('\r\n')); expect(bareLfLines).toHaveLength(0); }); it('prepends fake Windows tools to both PATH casings', () => { const fakeBin = 'C:\\qwen-test-bin'; const env = prependWindowsPath(fakeBin); expect(env.PATH).toMatch(/^C:\\qwen-test-bin;/); expect(env.Path).toMatch(/^C:\\qwen-test-bin;/); }); it('creates a fake Windows curl command script', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-curl-helper-')); try { const fakeCurl = createFakeWindowsCurlCommand(tmpDir); expect(fakeCurl).toBe(path.join(tmpDir, 'curl.cmd')); expect(readScript(fakeCurl)).toContain('QWEN_FAKE_CURL_LOG'); expect(readScript(fakeCurl)).toContain( '/releases/qwen-code/latest/VERSION', ); expect(readScript(fakeCurl)).toContain('set "destination=%~2"'); expect(readScript(fakeCurl)).not.toContain('set "destination=%~1"'); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); it('injects Windows processor overrides directly into cmd commands', () => { const prepared = prepareWindowsCommand( 'call "C:\\tools\\install-qwen-standalone.bat"', { Path: 'C:\\fake-bin', PROCESSOR_ARCHITECTURE: 'AMD64', PROCESSOR_ARCHITEW6432: '', }, { Path: 'C:\\Windows\\System32', processor_architecture: 'ARM64', PROCESSOR_ARCHITEW6432: 'ARM64', }, ); expect(prepared.command).toBe( 'set "PROCESSOR_ARCHITECTURE=AMD64" && set "PROCESSOR_ARCHITEW6432=" && call "C:\\tools\\install-qwen-standalone.bat"', ); expect(prepared.env).toEqual({ Path: 'C:\\fake-bin' }); }); it('creates PowerShell validation scripts with a ps1 extension', () => { const script = readScript( 'scripts/installation/install-qwen-standalone.bat', ); expect(script).toContain( 'call :CreateTempFile "qwen-validate-options" ".ps1"', ); expect(script).toContain( "($env:QWEN_TEMP_FILE_PREFIX + '-' + [IO.Path]::GetRandomFileName() + $env:QWEN_TEMP_FILE_EXTENSION)", ); }); }); describe('release-script-utils', () => { it('parses SHA256SUMS with BOM, empty lines, and CRLF', async () => { const { parseSha256Sums } = await import(releaseScriptUtilsUrl); const checksums = parseSha256Sums( `\uFEFF${'a'.repeat(64)} install-qwen-standalone.sh\n\n${'b'.repeat(64)} *install-qwen-standalone.bat\r\n${'c'.repeat(64)} install-qwen-standalone.ps1\n`, ); expect(checksums.get('install-qwen-standalone.sh')).toBe('a'.repeat(64)); expect(checksums.get('install-qwen-standalone.bat')).toBe('b'.repeat(64)); expect(checksums.get('install-qwen-standalone.ps1')).toBe('c'.repeat(64)); }); it('rejects malformed SHA256SUMS entries', async () => { const { parseSha256Sums } = await import(releaseScriptUtilsUrl); expect(() => parseSha256Sums('short-hash install-qwen-standalone.sh\n'), ).toThrow(/Malformed SHA256SUMS line 1/); }); it('rejects duplicate SHA256SUMS entries', async () => { const { parseSha256Sums } = await import(releaseScriptUtilsUrl); const first = 'a'.repeat(64); const second = 'b'.repeat(64); expect(() => parseSha256Sums( `${first} install-qwen-standalone.sh\n${second} install-qwen-standalone.sh\n`, ), ).toThrow(/Duplicate SHA256SUMS entry for: install-qwen-standalone\.sh/); }); it('supports --key=value form in parseArgs', async () => { const { parseArgs } = await import(releaseScriptUtilsUrl); const defs = { '--out-dir': { key: 'outDir', type: 'value' }, '--verbose': { key: 'verbose', type: 'flag' }, }; const args = parseArgs(['--out-dir=/tmp/build', '--verbose'], defs); expect(args.outDir).toBe('/tmp/build'); expect(args.verbose).toBe(true); expect(args.help).toBe(false); }); it('supports --key value form in parseArgs', async () => { const { parseArgs } = await import(releaseScriptUtilsUrl); const defs = { '--out-dir': { key: 'outDir', type: 'value' } }; const args = parseArgs(['--out-dir', '/tmp/build'], defs); expect(args.outDir).toBe('/tmp/build'); }); it('rejects unknown options and missing values', async () => { const { parseArgs } = await import(releaseScriptUtilsUrl); const defs = { '--out-dir': { key: 'outDir', type: 'value' } }; expect(() => parseArgs(['--unknown'], defs)).toThrow( /Unknown option: --unknown/, ); expect(() => parseArgs(['--out-dir'], defs)).toThrow( /--out-dir requires a value/, ); expect(() => parseArgs(['--out-dir='], defs)).toThrow( /--out-dir requires a value/, ); expect(() => parseArgs(['--out-dir', '--help'], defs)).toThrow( /--out-dir requires a value/, ); expect(() => parseArgs(['--out-dir=-tmp'], defs)).toThrow( /--out-dir requires a value/, ); }); it('rejects --key=value for flag-type options', async () => { const { parseArgs } = await import(releaseScriptUtilsUrl); const defs = { '--verbose': { key: 'verbose', type: 'flag' } }; expect(() => parseArgs(['--verbose=true'], defs)).toThrow( /--verbose does not accept a value/, ); }); it('recognises -h and --help without definitions', async () => { const { parseArgs } = await import(releaseScriptUtilsUrl); expect(parseArgs(['--help'], {}).help).toBe(true); expect(parseArgs(['-h'], {}).help).toBe(true); expect(() => parseArgs(['--help=anything'], {})).toThrow( /--help does not accept a value/, ); }); it('fail() wraps messages with ERROR: prefix', async () => { const { fail } = await import(releaseScriptUtilsUrl); expect(() => fail('something went wrong')).toThrow( 'ERROR: something went wrong', ); }); }); describe('standalone release packaging', () => { it('defines a standalone packaging script', () => { const packageJson = JSON.parse(readScript('package.json')); expect(packageJson.scripts['package:standalone']).toBe( 'node scripts/create-standalone-package.js', ); expect(packageJson.scripts['package:standalone:release']).toBe( 'node scripts/build-standalone-release.js', ); expect(packageJson.scripts['package:hosted-installation']).toBe( 'node scripts/build-hosted-installation-assets.js', ); expect(packageJson.scripts['verify:installation-release']).toBe( 'node scripts/verify-installation-release.js', ); expect(packageJson.scripts['package:installation-assets']).toBeUndefined(); expect(existsSync('scripts/create-standalone-package.js')).toBe(true); expect(existsSync('scripts/build-standalone-release.js')).toBe(true); expect(existsSync('scripts/build-hosted-installation-assets.js')).toBe( true, ); expect(existsSync('scripts/verify-installation-release.js')).toBe(true); expect(existsSync('scripts/build-installation-assets.js')).toBe(false); const packageScript = readScript('scripts/create-standalone-package.js'); expect(packageScript).toContain('Copyright 2025 Qwen Team'); expect(packageScript).toContain("'bundled/qc-helper/docs'"); expect(packageScript).toContain('DIST_ALLOWED_ENTRIES'); expect(packageScript).toContain('Unexpected dist asset'); expect(packageScript).toContain('topLevelDistEntryForPath(outDir)'); expect(packageScript).toContain("path.join(packageRoot, 'package.json')"); expect(packageScript).toContain('validateNodeRuntime'); expect(packageScript).toContain('copyNodeRuntimeEntry'); expect(packageScript).toContain('symlink cycle'); expect(packageScript).toContain('refusing to write empty SHA256SUMS'); expect(packageScript).toContain('--skip-checksums'); expect(packageScript).toContain('dereference: true'); expect(packageScript).toContain('fs.createReadStream'); expect(packageScript).toContain('Expand-Archive'); expect(packageScript).toContain('Compress-Archive'); const releaseScript = readScript('scripts/build-standalone-release.js'); expect(releaseScript).toContain('Copyright 2025 Qwen Team'); expect(releaseScript).toContain('https://nodejs.org/dist/v${nodeVersion}'); expect(releaseScript).toContain('SHASUMS256.txt'); expect(releaseScript).toContain('verifyNodeArchive'); expect(releaseScript).toContain( 'EXPECTED_ARCHIVE_COUNT = RELEASE_TARGETS.length', ); expect(releaseScript).toContain('nodeArchiveExtension'); expect(releaseScript).toContain('fs.createReadStream'); expect(releaseScript).toContain('expectedArchiveNames'); expect(releaseScript).toContain('qwen-code-${qwenTarget}'); expect(releaseScript).toContain('scripts/create-standalone-package.js'); expect(releaseScript).toContain('--skip-checksums'); expect(releaseScript).toContain('writeSha256Sums(outDir)'); const hostedInstallScript = readScript( 'scripts/build-hosted-installation-assets.js', ); expect(hostedInstallScript).toContain('Copyright 2025 Qwen Team'); expect(hostedInstallScript).toContain('buildHostedInstallationAssets'); expect(hostedInstallScript).toContain('HOSTED_INSTALLATION_ASSETS'); expect(hostedInstallScript).toContain( "output: 'install-qwen-standalone.sh'", ); expect(hostedInstallScript).toContain( "output: 'install-qwen-standalone.bat'", ); expect(hostedInstallScript).toContain( "output: 'install-qwen-standalone.ps1'", ); expect(hostedInstallScript).not.toContain("output: 'install'"); const releaseVerifyScript = readScript( 'scripts/verify-installation-release.js', ); expect(releaseVerifyScript).toContain('Copyright 2025 Qwen Team'); expect(releaseVerifyScript).toContain('verifyReleaseDirectory'); expect(releaseVerifyScript).toContain('verifyReleaseBaseUrl'); expect(releaseVerifyScript).toContain('EXPECTED_RELEASE_ASSET_NAMES'); expect(releaseVerifyScript).toContain('EXPECTED_STANDALONE_ARCHIVE_NAMES'); expect(releaseVerifyScript).toContain('import { RELEASE_TARGETS }'); expect(releaseVerifyScript).toContain( 'standaloneArchiveNamesFromReleaseTargets', ); expect(releaseVerifyScript).not.toContain("'qwen-code-win-x64.zip'"); expect(releaseVerifyScript).not.toContain('INSTALLATION_ASSET_NAMES'); expect(releaseVerifyScript).not.toContain('assertInstallAliasMatches'); }); it('loads the standalone release packaging helper', () => { const output = execFileSync( process.execPath, ['scripts/build-standalone-release.js', '--help'], { encoding: 'utf8' }, ); expect(output).toContain('package:standalone:release'); expect(output).toContain('--node-version VERSION'); }); it('loads the hosted installation release helpers', () => { const hostedOutput = execFileSync( process.execPath, ['scripts/build-hosted-installation-assets.js', '--help'], { encoding: 'utf8' }, ); const verifierOutput = execFileSync( process.execPath, ['scripts/verify-installation-release.js', '--help'], { encoding: 'utf8' }, ); expect(hostedOutput).toContain('package:hosted-installation'); expect(hostedOutput).toContain('--out-dir PATH'); expect(verifierOutput).toContain('verify:installation-release'); expect(verifierOutput).toContain('--dir PATH'); expect(verifierOutput).toContain('--base-url URL'); }); it('rejects invalid installation release verification CLI arguments', () => { const expectFail = (args, expectedOutput) => { let caughtError; try { execFileSync(process.execPath, args, { encoding: 'utf8', stdio: 'pipe', }); } catch (error) { caughtError = error; } expect(caughtError).toBeTruthy(); expect( [ caughtError?.message, caughtError?.stdout?.toString(), caughtError?.stderr?.toString(), ].join('\n'), ).toMatch(expectedOutput); }; expectFail( ['scripts/verify-installation-release.js', '--unknown'], /Unknown option: --unknown/, ); expectFail( ['scripts/verify-installation-release.js', '--dir'], /--dir requires a value/, ); expectFail( [ 'scripts/verify-installation-release.js', '--dir', '/tmp', '--base-url', 'https://example.com/r/', ], /Pass --dir or --base-url, not both/, ); expectFail( [ 'scripts/verify-installation-release.js', '--dir=/tmp', '--base-url=https://example.com/r/', ], /Pass --dir or --base-url, not both/, ); expectFail( ['scripts/verify-installation-release.js', '--unknown=foo'], /Unknown option: --unknown/, ); }); it('parses Node.js SHASUMS entries', async () => { const { parseChecksums } = await import(standaloneReleaseScriptUrl); const checksums = parseChecksums( [ 'a'.repeat(64) + ' node-v22.0.0-linux-x64.tar.xz', 'b'.repeat(64) + ' *node-v22.0.0-win-x64.zip', '', ].join('\n'), ); expect(checksums.get('node-v22.0.0-linux-x64.tar.xz')).toBe('a'.repeat(64)); expect(checksums.get('node-v22.0.0-win-x64.zip')).toBe('b'.repeat(64)); }); it('validates standalone release checksum output', async () => { const { assertStandaloneOutput, RELEASE_TARGETS } = await import( standaloneReleaseScriptUrl ); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-release-test-')); try { const lines = RELEASE_TARGETS.map(({ qwenTarget }) => { const extension = qwenTarget === 'win-x64' ? 'zip' : 'tar.gz'; return `${'a'.repeat(64)} qwen-code-${qwenTarget}.${extension}`; }); writeFileSync(path.join(tmpDir, 'SHA256SUMS'), `${lines.join('\n')}\n`); expect(() => assertStandaloneOutput(tmpDir)).not.toThrow(); writeFileSync( path.join(tmpDir, 'SHA256SUMS'), `${lines.join('\n')}\n${'b'.repeat(64)} qwen-code-extra.tar.gz\n`, ); expect(() => assertStandaloneOutput(tmpDir)).toThrow(/Extra/); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); it('installer scripts honor --version for hosted entrypoints', () => { const installShellSource = readScript( 'scripts/installation/install-qwen-standalone.sh', ); expect(installShellSource).toContain( 'VERSION="${QWEN_INSTALL_VERSION:-latest}"', ); expect(installShellSource).toContain('--version)'); expect(installShellSource).toContain('--version requires a value'); const installBatchSource = readScript( 'scripts/installation/install-qwen-standalone.bat', ); expect(installBatchSource).toContain('set "VERSION=latest"'); expect(installBatchSource).toContain( 'if defined QWEN_INSTALL_VERSION set "VERSION=!QWEN_INSTALL_VERSION!"', ); expect(installBatchSource).toContain('!ARG_KEY!"=="--version"'); expect(installBatchSource).toContain('--version requires a value'); const installPowerShellSource = readScript( 'scripts/installation/install-qwen-standalone.ps1', ); expect(installPowerShellSource).toContain('install-qwen-standalone.bat'); expect(installPowerShellSource).toContain('Invoke-WebRequest'); expect(installPowerShellSource).toContain('Download-File'); expect(installPowerShellSource).toContain( 'curl.exe --connect-timeout 15 --max-time 300 --retry 2 -sSfLo', ); expect(installPowerShellSource).toContain('-TimeoutSec 300'); expect(installPowerShellSource).toContain( "$global:ProgressPreference = 'SilentlyContinue'", ); expect(installPowerShellSource).toContain('QWEN_INSTALL_VERSION'); expect(installPowerShellSource).toContain('--version vX.Y.Z'); expect(installPowerShellSource).toContain('SHA256SUMS'); expect(installPowerShellSource).toContain('Get-FileHash'); expect(installPowerShellSource).toContain('Checksum mismatch'); expect(installPowerShellSource).toContain('@args'); }); it('PowerShell hosted entrypoint refreshes the current Windows shell', () => { const installPowerShellSource = readScript( 'scripts/installation/install-qwen-standalone.ps1', ); const installBatchSource = readScript( 'scripts/installation/install-qwen-standalone.bat', ); expect(installPowerShellSource).toContain('Update-CurrentSessionPath'); expect(installPowerShellSource).toContain('Install-CurrentCmdPathShim'); expect(installPowerShellSource).toContain('Save-CurrentCmdPathShim'); expect(installPowerShellSource).toContain('current-cmd-shim.txt'); expect(installPowerShellSource).toContain('Test-WritableDirectory'); expect(installPowerShellSource).toContain('Qwen Code current-session shim'); expect(installPowerShellSource).toContain( 'TEMP environment variable is not set', ); expect(installPowerShellSource).toMatch( /function Get-QwenInstallBinDir \{[\s\S]*QWEN_INSTALL_BIN_DIR[\s\S]*return Join-Path \(Get-QwenInstallBase\) 'bin'[\s\S]*\}/, ); expect(installPowerShellSource).toContain( 'Test-SystemManagedPathDirectory', ); expect(installPowerShellSource).not.toContain( "$preferredDirectories += Join-Path $env:LOCALAPPDATA 'Microsoft\\WindowsApps'", ); expect(installPowerShellSource).toContain( 'QWEN_NO_MODIFY_PATH=1; skipping current-session PATH refresh.', ); expect(installPowerShellSource).not.toContain('doskey.exe'); expect(installPowerShellSource).toContain( 'qwen is ready to use in this PowerShell session.', ); expect(installPowerShellSource).toContain( 'Added qwen.cmd to a directory already on this cmd.exe PATH:', ); expect(installPowerShellSource).toContain( 'Windows does not allow this PowerShell child process to update the parent cmd.exe PATH directly.', ); expect(installBatchSource).toContain('QWEN_INSTALLER_PARENT_POWERSHELL'); expect(installBatchSource).toContain( 'Final PATH refresh is handled by the PowerShell entrypoint.', ); }); it('stages hosted installation assets with checksums', async () => { const { HOSTED_INSTALLATION_ASSET_NAMES, HOSTED_INSTALLATION_ASSETS, assertHostedInstallationAssetChecksums, buildHostedInstallationAssets, } = await import(hostedInstallationScriptUrl); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-install-')); try { await buildHostedInstallationAssets(tmpDir); const installSh = path.join(tmpDir, 'install-qwen-standalone.sh'); const installBat = path.join(tmpDir, 'install-qwen-standalone.bat'); const installPs1 = path.join(tmpDir, 'install-qwen-standalone.ps1'); const uninstallSh = path.join(tmpDir, 'uninstall-qwen-standalone.sh'); const uninstallPs1 = path.join(tmpDir, 'uninstall-qwen-standalone.ps1'); const checksums = readScript(path.join(tmpDir, 'SHA256SUMS')); const checksumLines = checksums.trim().split('\n'); expect(HOSTED_INSTALLATION_ASSET_NAMES).toEqual([ 'install-qwen-standalone.sh', 'install-qwen-standalone.bat', 'install-qwen-standalone.ps1', 'uninstall-qwen-standalone.sh', 'uninstall-qwen-standalone.ps1', ]); expect(HOSTED_INSTALLATION_ASSETS.map(({ output }) => output)).toEqual( HOSTED_INSTALLATION_ASSET_NAMES, ); expect(readScript(installSh)).toBe( readScript('scripts/installation/install-qwen-standalone.sh'), ); expect(readScript(installBat)).toBe( readScript('scripts/installation/install-qwen-standalone.bat').replace( /\r?\n/g, '\r\n', ), ); expect(readScript(installPs1)).toBe( readScript('scripts/installation/install-qwen-standalone.ps1'), ); expect(readScript(uninstallSh)).toBe( readScript('scripts/installation/uninstall-qwen-standalone.sh'), ); expect(readScript(uninstallPs1)).toBe( readScript('scripts/installation/uninstall-qwen-standalone.ps1'), ); expect(existsSync(path.join(tmpDir, 'install'))).toBe(false); expect(checksumLines.map((line) => line.split(' ')[1])).toEqual([ 'install-qwen-standalone.bat', 'install-qwen-standalone.ps1', 'install-qwen-standalone.sh', 'uninstall-qwen-standalone.ps1', 'uninstall-qwen-standalone.sh', ]); expect(checksums).toMatch( /^[0-9a-f]{64} {2}install-qwen-standalone\.sh$/m, ); expect(checksums).toMatch( /^[0-9a-f]{64} {2}install-qwen-standalone\.bat$/m, ); expect(checksums).toMatch( /^[0-9a-f]{64} {2}install-qwen-standalone\.ps1$/m, ); expect(checksums).toMatch( /^[0-9a-f]{64} {2}uninstall-qwen-standalone\.sh$/m, ); expect(checksums).toMatch( /^[0-9a-f]{64} {2}uninstall-qwen-standalone\.ps1$/m, ); if (process.platform !== 'win32') { expect(lstatSync(installSh).mode & 0o111).not.toBe(0); expect(lstatSync(uninstallSh).mode & 0o111).not.toBe(0); } writeFileSync(installSh, 'tampered'); await expect( assertHostedInstallationAssetChecksums(tmpDir), ).rejects.toThrow(/Checksum mismatch for install-qwen-standalone\.sh/); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); it('rejects hosted installer sources without pinned hosted behavior', async () => { const { buildHostedInstallationAssets } = await import( hostedInstallationScriptUrl ); const tmpRoot = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-root-')); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-install-')); const sourceDir = path.join(tmpRoot, 'scripts', 'installation'); try { mkdirSync(sourceDir, { recursive: true }); writeFileSync( path.join(sourceDir, 'install-qwen-standalone.sh'), '#!/usr/bin/env bash\n' + 'VERSION="${QWEN_INSTALL_VERSION:-stable}"\n' + 'case "$1" in --version) shift; VERSION="$1" ;; esac\n', ); writeFileSync( path.join(sourceDir, 'install-qwen-standalone.bat'), '@echo off\r\nset "VERSION=latest"\r\n', ); writeFileSync( path.join(sourceDir, 'install-qwen-standalone.ps1'), "# --version vX.Y.Z\n$env:QWEN_INSTALL_VERSION = 'latest'\n", ); writeFileSync( path.join(sourceDir, 'uninstall-qwen-standalone.sh'), '#!/usr/bin/env bash\nis_qwen_standalone_install_dir() { return 0; }\n', ); writeFileSync( path.join(sourceDir, 'uninstall-qwen-standalone.ps1'), 'function Test-QwenStandaloneInstallDir { return $true }\n', ); await expect( buildHostedInstallationAssets(tmpDir, { root: tmpRoot }), ).rejects.toThrow( /install-qwen-standalone\.sh default install version must be 'latest'/, ); } finally { rmSync(tmpRoot, { recursive: true, force: true }); rmSync(tmpDir, { recursive: true, force: true }); } }); it('rejects hosted installer sources without real version parsing', async () => { const { buildHostedInstallationAssets } = await import( hostedInstallationScriptUrl ); const tmpRoot = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-root-')); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-install-')); const sourceDir = path.join(tmpRoot, 'scripts', 'installation'); try { mkdirSync(sourceDir, { recursive: true }); writeFileSync( path.join(sourceDir, 'install-qwen-standalone.sh'), '#!/usr/bin/env bash\n' + 'VERSION="${QWEN_INSTALL_VERSION:-latest}"\n' + 'echo "Usage: --version VERSION"\n', ); writeFileSync( path.join(sourceDir, 'install-qwen-standalone.bat'), '@echo off\r\nset "VERSION=latest"\r\n', ); writeFileSync( path.join(sourceDir, 'install-qwen-standalone.ps1'), '& $qwenInstallerPath @args\n# QWEN_INSTALL_VERSION\n', ); writeFileSync( path.join(sourceDir, 'uninstall-qwen-standalone.sh'), '#!/usr/bin/env bash\nis_qwen_standalone_install_dir() { return 0; }\n', ); writeFileSync( path.join(sourceDir, 'uninstall-qwen-standalone.ps1'), 'function Test-QwenStandaloneInstallDir { return $true }\n', ); await expect( buildHostedInstallationAssets(tmpDir, { root: tmpRoot }), ).rejects.toThrow(/install-qwen-standalone\.sh.*--version parser/); } finally { rmSync(tmpRoot, { recursive: true, force: true }); rmSync(tmpDir, { recursive: true, force: true }); } }); it('rejects stale hosted installation assets in the output directory', async () => { const { buildHostedInstallationAssets } = await import( hostedInstallationScriptUrl ); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-hosted-install-')); try { writeFileSync(path.join(tmpDir, 'install'), 'stale alias'); await expect(buildHostedInstallationAssets(tmpDir)).rejects.toThrow( /Unexpected hosted installer asset: install/, ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); it('verifies release asset directory contents and checksums', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseDirectory } = await import(installationReleaseVerificationScriptUrl); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-release-verify-')); try { writeStandaloneReleaseAssets(tmpDir, EXPECTED_STANDALONE_ARCHIVE_NAMES); await expect(verifyReleaseDirectory(tmpDir)).resolves.not.toThrow(); appendFileSync( path.join(tmpDir, EXPECTED_STANDALONE_ARCHIVE_NAMES[0]), 'tamper', ); await expect(verifyReleaseDirectory(tmpDir)).rejects.toThrow( new RegExp( `Checksum mismatch for ${escapeRegExp(EXPECTED_STANDALONE_ARCHIVE_NAMES[0])}`, ), ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); it('rejects missing release archives and unexpected checksum entries', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseDirectory } = await import(installationReleaseVerificationScriptUrl); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-release-verify-')); try { writeStandaloneReleaseAssets(tmpDir, EXPECTED_STANDALONE_ARCHIVE_NAMES); rmSync(path.join(tmpDir, EXPECTED_STANDALONE_ARCHIVE_NAMES[0])); await expect(verifyReleaseDirectory(tmpDir)).rejects.toThrow( /Missing release asset: qwen-code-/, ); writeStandaloneReleaseAssets(tmpDir, EXPECTED_STANDALONE_ARCHIVE_NAMES); writeStandaloneReleaseChecksums(tmpDir, [ ...EXPECTED_STANDALONE_ARCHIVE_NAMES, 'qwen-code-extra.tar.gz', ]); await expect(verifyReleaseDirectory(tmpDir)).rejects.toThrow( /Unexpected release asset checksum: qwen-code-extra\.tar\.gz/, ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); it('verifies release asset URLs from SHA256SUMS', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } = await import(installationReleaseVerificationScriptUrl); const checksumContent = placeholderChecksumContent( EXPECTED_STANDALONE_ARCHIVE_NAMES, ); const fetchedUrls = []; const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); try { await expect( verifyReleaseBaseUrl('https://example.com/qwen-code/v0.0.0', { fetchImpl: async (url, options = {}) => { fetchedUrls.push([url, options.method || 'GET', !!options.signal]); if (url.endsWith('/SHA256SUMS')) { return new Response(checksumContent); } const assetName = EXPECTED_STANDALONE_ARCHIVE_NAMES.find((name) => url.endsWith(`/${name}`), ); return new Response(`${assetName}\n`); }, }), ).resolves.not.toThrow(); } finally { warnSpy.mockRestore(); } expect(fetchedUrls).toContainEqual([ 'https://example.com/qwen-code/v0.0.0/SHA256SUMS', 'GET', true, ]); for (const assetName of EXPECTED_STANDALONE_ARCHIVE_NAMES) { expect(fetchedUrls).toContainEqual([ `https://example.com/qwen-code/v0.0.0/${assetName}`, 'GET', true, ]); } expect(warnSpy).not.toHaveBeenCalled(); for (const [url] of fetchedUrls) { expect(url).not.toMatch(/install-qwen\.(sh|bat|ps1)$/); expect(url).not.toMatch(/\/install$/); } }); it('rejects remote release archives whose downloaded hash differs', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } = await import(installationReleaseVerificationScriptUrl); const checksumContent = placeholderChecksumContent( EXPECTED_STANDALONE_ARCHIVE_NAMES, ); await expect( verifyReleaseBaseUrl('https://example.com/qwen-code/v0.0.0', { fetchImpl: async (url) => { if (url.endsWith('/SHA256SUMS')) { return new Response(checksumContent); } const assetName = EXPECTED_STANDALONE_ARCHIVE_NAMES.find((name) => url.endsWith(`/${name}`), ); if (assetName === EXPECTED_STANDALONE_ARCHIVE_NAMES[0]) { return new Response('tampered\n'); } return new Response(`${assetName}\n`); }, }), ).rejects.toThrow(/Checksum mismatch for qwen-code-/); }); it('rejects a release base URL that is not https', async () => { const { verifyReleaseBaseUrl } = await import( installationReleaseVerificationScriptUrl ); await expect(verifyReleaseBaseUrl('file:///tmp/release/')).rejects.toThrow( /--base-url must use https/, ); await expect( verifyReleaseBaseUrl('http://example.com/release/'), ).rejects.toThrow(/--base-url must use https/); }); it('downloads release archive bodies instead of relying on HEAD probes', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } = await import(installationReleaseVerificationScriptUrl); const checksumContent = placeholderChecksumContent( EXPECTED_STANDALONE_ARCHIVE_NAMES, ); const fetchedUrls = []; const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); try { await expect( verifyReleaseBaseUrl('https://example.com/qwen-code/v0.0.0', { fetchImpl: async (url, options = {}) => { const method = options.method || 'GET'; const range = options.headers?.Range || ''; fetchedUrls.push([url, method, range]); if (url.endsWith('/SHA256SUMS')) { return new Response(checksumContent); } if (method === 'HEAD') { return new Response(null, { status: 405 }); } const assetName = EXPECTED_STANDALONE_ARCHIVE_NAMES.find((name) => url.endsWith(`/${name}`), ); return new Response(`${assetName}\n`); }, }), ).resolves.not.toThrow(); } finally { warnSpy.mockRestore(); } for (const assetName of EXPECTED_STANDALONE_ARCHIVE_NAMES) { const assetUrl = `https://example.com/qwen-code/v0.0.0/${assetName}`; expect(fetchedUrls).toContainEqual([assetUrl, 'GET', '']); expect(fetchedUrls).not.toContainEqual([assetUrl, 'HEAD', '']); } }); it('reports each unavailable asset with its reason', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } = await import(installationReleaseVerificationScriptUrl); const checksumContent = placeholderChecksumContent( EXPECTED_STANDALONE_ARCHIVE_NAMES, ); const unavailableAsset = EXPECTED_STANDALONE_ARCHIVE_NAMES[0]; const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); try { await expect( verifyReleaseBaseUrl('https://example.com/qwen-code/v0.0.0', { fetchImpl: async (url) => { if (url.endsWith('/SHA256SUMS')) { return new Response(checksumContent); } // The first asset always fails (HEAD and Range); the rest succeed // on HEAD. Verifier should list only the failing one in the error. if (url.endsWith(`/${unavailableAsset}`)) { return new Response(null, { status: 404 }); } const assetName = EXPECTED_STANDALONE_ARCHIVE_NAMES.find((name) => url.endsWith(`/${name}`), ); return new Response(`${assetName}\n`); }, }), ).rejects.toThrow( new RegExp( `Unavailable or invalid release asset\\(s\\): ${escapeRegExp(unavailableAsset)} \\(.*\\)`, ), ); } finally { warnSpy.mockRestore(); } }); it('reports a single error when every asset URL is unavailable', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } = await import(installationReleaseVerificationScriptUrl); const checksumContent = placeholderChecksumContent( EXPECTED_STANDALONE_ARCHIVE_NAMES, ); const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); try { await expect( verifyReleaseBaseUrl('https://example.com/qwen-code/v0.0.0', { fetchImpl: async (url) => { if (url.endsWith('/SHA256SUMS')) { return new Response(checksumContent); } return new Response(null, { status: 503 }); }, }), ).rejects.toThrow( new RegExp( `All ${EXPECTED_STANDALONE_ARCHIVE_NAMES.length} release asset URLs are unavailable; check --base-url: https://example\\.com/qwen-code/v0\\.0\\.0/`, ), ); } finally { warnSpy.mockRestore(); } }); it('parses SHA256SUMS even when the file starts with a UTF-8 BOM', async () => { const { EXPECTED_STANDALONE_ARCHIVE_NAMES, verifyReleaseBaseUrl } = await import(installationReleaseVerificationScriptUrl); const checksumContent = '\uFEFF' + placeholderChecksumContent(EXPECTED_STANDALONE_ARCHIVE_NAMES); const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); try { await expect( verifyReleaseBaseUrl('https://example.com/qwen-code/v0.0.0', { fetchImpl: async (url) => { if (url.endsWith('/SHA256SUMS')) { return new Response(checksumContent); } const assetName = EXPECTED_STANDALONE_ARCHIVE_NAMES.find((name) => url.endsWith(`/${name}`), ); return new Response(`${assetName}\n`); }, }), ).resolves.not.toThrow(); } finally { warnSpy.mockRestore(); } }); it('prints explicit release asset paths for GitHub release upload', async () => { const { EXPECTED_RELEASE_ASSET_NAMES, EXPECTED_STANDALONE_ARCHIVE_NAMES } = await import(installationReleaseVerificationScriptUrl); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-release-list-')); try { writeStandaloneReleaseAssets(tmpDir, EXPECTED_STANDALONE_ARCHIVE_NAMES); const output = execFileSync( process.execPath, [ 'scripts/verify-installation-release.js', '--dir', tmpDir, '--list-release-asset-paths', ], { encoding: 'utf8' }, ); expect(output.trim().split('\n')).toEqual( EXPECTED_RELEASE_ASSET_NAMES.map((assetName) => path.join(tmpDir, assetName), ), ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); it('rejects a runtime archive without a Node executable', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); try { const target = process.platform === 'win32' ? 'win-x64' : 'linux-x64'; const fakeRuntimeArchive = process.platform === 'win32' ? createBadWindowsNodeArchive(tmpDir) : createBadUnixNodeArchive(tmpDir); expect(() => execFileSync( 'node', [ 'scripts/create-standalone-package.js', '--target', target, '--node-archive', fakeRuntimeArchive, '--out-dir', path.join(tmpDir, 'out'), '--version', '0.0.0-test', ], { stdio: 'pipe' }, ), ).toThrow(/Node\.js runtime for .* must contain/); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); it('packages a win-x64 standalone archive', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); try { const outDir = path.join(tmpDir, 'out'); execFileSync( 'node', [ 'scripts/create-standalone-package.js', '--target', 'win-x64', '--node-archive', createFakeWindowsNodeArchive(tmpDir), '--out-dir', outDir, '--version', '0.0.0-test', ], { stdio: 'pipe' }, ); const archive = path.join(outDir, 'qwen-code-win-x64.zip'); const extractDir = path.join(tmpDir, 'extract'); mkdirSync(extractDir, { recursive: true }); extractZipForTest(archive, extractDir); expect(existsSync(path.join(extractDir, 'qwen-code'))).toBe(true); expect( existsSync(path.join(extractDir, 'qwen-code', 'bin', 'qwen.cmd')), ).toBe(true); expect( existsSync(path.join(extractDir, 'qwen-code', 'node', 'node.exe')), ).toBe(true); expect(readScript(path.join(outDir, 'SHA256SUMS'))).toContain( 'qwen-code-win-x64.zip', ); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, 30_000); itOnUnix('dereferences safe Node.js runtime symlinks', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); try { const archive = packageFakeStandalone(tmpDir, { withSafeNodeSymlink: true, }); const installRoot = path.join(tmpDir, 'install'); runUnixInstaller(archive, installRoot, path.join(tmpDir, 'home')); const npmShim = path.join( installRoot, 'lib', 'qwen-code', 'node', 'bin', 'npm', ); expect(existsSync(npmShim)).toBe(true); expect(lstatSync(npmShim).isSymbolicLink()).toBe(false); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix('rejects Node.js runtime symlinks that escape the archive', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); try { expect(() => execFileSync( 'node', [ 'scripts/create-standalone-package.js', '--target', 'linux-x64', '--node-archive', createFakeNodeArchive(tmpDir, { withEscapingNodeSymlink: true, }), '--out-dir', path.join(tmpDir, 'out'), '--version', '0.0.0-test', ], { stdio: 'pipe' }, ), ).toThrow(/symlink escapes the archive/); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix('rejects Node.js runtime symlink cycles', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); try { expect(() => execFileSync( 'node', [ 'scripts/create-standalone-package.js', '--target', 'linux-x64', '--node-archive', createFakeNodeArchive(tmpDir, { withNodeSymlinkCycle: true, }), '--out-dir', path.join(tmpDir, 'out'), '--version', '0.0.0-test', ], { stdio: 'pipe' }, ), ).toThrow(/symlink cycle/); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); it('rejects unexpected dist assets', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); try { writeFileSync('dist/debug-cache.tmp', 'debug\n'); expect(() => execFileSync( 'node', [ 'scripts/create-standalone-package.js', '--target', 'win-x64', '--node-archive', createFakeWindowsNodeArchive(tmpDir), '--out-dir', path.join(tmpDir, 'out'), '--version', '0.0.0-test', ], { stdio: 'pipe' }, ), ).toThrow(/Unexpected dist asset/); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); it('syncs standalone and hosted installation assets during release', () => { const workflow = readScript('.github/workflows/release.yml'); expect(workflow).toContain('npm run package:standalone:release --'); expect(workflow).toContain( 'npm run package:hosted-installation -- --out-dir dist/installation', ); expect(workflow).not.toContain('package:installation-assets'); expect(workflow).not.toContain('verify_node_checksum()'); expect(workflow).not.toContain('download_node()'); expect(workflow).not.toContain('dist/standalone/qwen-code-*.tar.gz'); expect(workflow).not.toContain('dist/standalone/qwen-code-*.zip'); expect(workflow).toContain('--list-release-asset-paths'); expect(workflow).toContain( 'npm run verify:installation-release -- --dir dist/standalone', ); expect(workflow).toContain('secrets.ALIYUN_OSS_ACCESS_KEY_ID'); expect(workflow).toContain('secrets.ALIYUN_OSS_ACCESS_KEY_SECRET'); expect(workflow).toContain('vars.ALIYUN_OSS_BUCKET'); expect(workflow).toContain('vars.ALIYUN_OSS_ENDPOINT'); expect(workflow).toContain('vars.OSSUTIL_URL'); expect(workflow).toContain('vars.OSSUTIL_SHA256'); expect(workflow).not.toContain('sudo install'); expect(workflow).toContain('${HOME}/.local/bin/ossutil'); expect(workflow).toContain('${GITHUB_PATH}'); expect(existsSync('scripts/upload-aliyun-oss-assets.js')).toBe(true); expect(workflow).toContain('node scripts/upload-aliyun-oss-assets.js'); expect(workflow.match(/upload_asset\(\)/g) || []).toHaveLength(0); expect(workflow).toContain('releases/qwen-code/${RELEASE_TAG}'); expect(workflow).toContain('releases/qwen-code/latest'); expect(workflow).not.toContain( 'upload_release_assets "releases/qwen-code/latest"', ); const createReleaseStepIndex = workflow.indexOf( "name: 'Create GitHub Release and Tag'", ); expect(createReleaseStepIndex).toBeGreaterThanOrEqual(0); const createReleaseStep = workflow.slice(createReleaseStepIndex); expect(createReleaseStep).toContain('mapfile -t release_assets'); expect(createReleaseStep).toContain('"${release_assets[@]}"'); expect(createReleaseStep).not.toContain( 'dist/standalone/qwen-code-*.tar.gz', ); expect(createReleaseStep).not.toContain('dist/standalone/qwen-code-*.zip'); const syncStepIndex = workflow.indexOf( "name: 'Sync Release Assets to Aliyun OSS'", ); const verifyStepIndex = workflow.indexOf( "name: 'Verify Aliyun OSS Release Assets'", ); const publishLatestStepIndex = workflow.indexOf( "name: 'Publish Aliyun OSS Latest VERSION'", ); const syncHostedStepIndex = workflow.indexOf( "name: 'Sync Hosted Installation Assets to Aliyun OSS'", ); const verifyHostedStepIndex = workflow.indexOf( "name: 'Verify Aliyun OSS Hosted Installation Assets'", ); expect(syncStepIndex).toBeGreaterThanOrEqual(0); expect(verifyStepIndex).toBeGreaterThan(syncStepIndex); expect(syncHostedStepIndex).toBeGreaterThan(verifyStepIndex); expect(verifyHostedStepIndex).toBeGreaterThan(syncHostedStepIndex); // Latest VERSION pointer must flip only after every release asset and // hosted installer object is uploaded and verified. expect(publishLatestStepIndex).toBeGreaterThan(verifyHostedStepIndex); expect(workflow.slice(syncStepIndex, verifyStepIndex)).not.toContain( 'releases/qwen-code/latest/VERSION', ); expect(workflow.slice(publishLatestStepIndex)).toContain( 'releases/qwen-code/latest/VERSION', ); const syncStep = workflow.slice(syncStepIndex, verifyStepIndex); expect(syncStep).not.toContain('dist/installation/'); expect(syncStep).not.toContain('installation/install-qwen-standalone.sh'); const syncHostedStep = workflow.slice( syncHostedStepIndex, verifyHostedStepIndex, ); expect(syncHostedStep).toContain( 'dist/installation/install-qwen-standalone.sh', ); expect(syncHostedStep).toContain( 'dist/installation/install-qwen-standalone.bat', ); expect(syncHostedStep).toContain( 'dist/installation/install-qwen-standalone.ps1', ); expect(syncHostedStep).toContain( 'dist/installation/uninstall-qwen-standalone.sh', ); expect(syncHostedStep).toContain( 'dist/installation/uninstall-qwen-standalone.ps1', ); expect(syncHostedStep).toContain('--prefix "installation/${RELEASE_TAG}"'); expect(syncHostedStep).toContain('--prefix "installation"'); expect(syncHostedStep).toContain( 'dist/installation/install-qwen-standalone.sh', ); const uploadScript = readScript('scripts/upload-aliyun-oss-assets.js'); expect(uploadScript).toContain("'--acl'"); expect(uploadScript).toContain("'public-read'"); expect(workflow).toContain( 'curl -fsSL --connect-timeout 15 --max-time 300 "${OSSUTIL_URL}"', ); expect(workflow).toContain( 'npm run verify:installation-release -- --base-url "${ALIYUN_OSS_PUBLIC_BASE_URL}/releases/qwen-code/${RELEASE_TAG}"', ); expect(workflow).toContain( 'latest_version="$(curl -fsSL --connect-timeout 15 --max-time 300 "${ALIYUN_OSS_PUBLIC_BASE_URL}/releases/qwen-code/latest/VERSION" | tr -d', ); expect(workflow).not.toContain( 'npm run verify:installation-release -- --base-url "${ALIYUN_OSS_PUBLIC_BASE_URL}/releases/qwen-code/latest"', ); const verifyStep = workflow.slice(verifyStepIndex, syncHostedStepIndex); expect(verifyStep).not.toContain('hosted_tmp_dir'); const verifyHostedStep = workflow.slice(verifyHostedStepIndex); expect(workflow).toContain('hosted_tmp_dir="$(mktemp -d)"'); expect(verifyHostedStep).toContain( 'url="${ALIYUN_OSS_PUBLIC_BASE_URL}/installation/${RELEASE_TAG}/${asset}"', ); expect(verifyHostedStep).toContain( 'global_url="${ALIYUN_OSS_PUBLIC_BASE_URL}/installation/${asset}"', ); expect(verifyHostedStep).toContain( 'curl -fsSL --connect-timeout 15 --max-time 300 "${url}"', ); expect(verifyHostedStep).toContain( 'curl -fsSL --connect-timeout 15 --max-time 300 "${global_url}"', ); expect(workflow).toContain( 'cmp -s "dist/installation/SHA256SUMS" "${hosted_tmp_dir}/versioned/SHA256SUMS"', ); expect(workflow).toContain( 'cmp -s "dist/installation/SHA256SUMS" "${hosted_tmp_dir}/global/SHA256SUMS"', ); expect(workflow).toContain( '(cd "${hosted_tmp_dir}/versioned" && sha256sum -c SHA256SUMS)', ); expect(workflow).toContain( '(cd "${hosted_tmp_dir}/global" && sha256sum -c SHA256SUMS)', ); }); it('does not whitelist internal planning documents in gitignore', () => { const gitignore = readScript('.gitignore'); expect(gitignore).not.toContain('!.qwen/design/'); expect(gitignore).not.toContain('!.qwen/e2e-tests/'); }); it('documents optional native module parity for standalone installs', () => { const guide = readScript('scripts/installation/INSTALLATION_GUIDE.md'); expect(guide).toContain('Optional Native Modules'); expect(guide).toContain('package:hosted-installation'); expect(guide).toContain('installation/install-qwen-standalone.sh'); expect(guide).toContain('installation/install-qwen-standalone.bat'); expect(guide).toContain('installation/install-qwen-standalone.ps1'); expect(guide).toContain('installation/uninstall-qwen-standalone.sh'); expect(guide).toContain('installation/uninstall-qwen-standalone.ps1'); expect(guide).toContain('ALIYUN_OSS_ACCESS_KEY_ID'); expect(guide).toContain('ALIYUN_OSS_ACCESS_KEY_SECRET'); expect(guide).toContain('ALIYUN_OSS_BUCKET'); expect(guide).toContain('ALIYUN_OSS_ENDPOINT'); expect(guide).toContain('Public installation documentation'); expect(guide).toContain('node-pty'); expect(guide).toContain('clipboard'); }); it('provides standalone uninstall scripts that clean install-owned files only', () => { const uninstallShellSource = readScript( 'scripts/installation/uninstall-qwen-standalone.sh', ); const uninstallPowerShellSource = readScript( 'scripts/installation/uninstall-qwen-standalone.ps1', ); expect(uninstallShellSource).toContain('is_qwen_standalone_install_dir'); expect(uninstallShellSource).toContain('remove_shell_path_entry'); expect(uninstallShellSource).toContain('shell_quote'); expect(uninstallShellSource).toContain('quoted_qwen_bin'); expect(uninstallShellSource).toContain('QWEN_UNINSTALL_PURGE'); expect(uninstallShellSource).toContain('Preserving'); expect(uninstallShellSource).toContain('source.json'); expect(uninstallPowerShellSource).toContain( 'Test-QwenStandaloneInstallDir', ); expect(uninstallPowerShellSource).toContain('Remove-UserPathEntry'); expect(uninstallPowerShellSource).toContain('Remove-CurrentCmdPathShim'); expect(uninstallPowerShellSource).toContain( 'Remove-RecordedCurrentCmdPathShim', ); expect(uninstallPowerShellSource).toContain('current-cmd-shim.txt'); expect(uninstallPowerShellSource).toContain( 'Qwen Code current-session shim', ); expect(uninstallPowerShellSource).toContain('QWEN_UNINSTALL_PURGE'); expect(uninstallPowerShellSource).toContain('Preserving'); expect(uninstallPowerShellSource).toMatch( /if \(\$installWasManaged\) \{\n\s+Remove-CurrentCmdPathShim\n\s+Remove-Item/, ); expect(uninstallPowerShellSource).not.toMatch( /\$installWasManaged = Test-QwenStandaloneInstallDir[^\n]*\n\nRemove-CurrentCmdPathShim\n\nif \(\$installWasManaged\)/, ); }); }); describe('Linux/macOS installer end-to-end', () => { itOnUnix( 'installs a local standalone archive with checksum verification', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); const output = runUnixInstaller(archive, installRoot, home).toString(); expect(existsSync(path.join(installRoot, 'bin', 'qwen'))).toBe(true); expect( existsSync( path.join(installRoot, 'lib', 'qwen-code', 'node', 'bin', 'node'), ), ).toBe(true); expect(readScript(path.join(home, '.qwen', 'source.json'))).toContain( '"source": "smoke"', ); const version = execFileSync(path.join(installRoot, 'bin', 'qwen'), [ '--version', ]) .toString() .trim(); expect(version).toBe('0.0.0-smoke'); expect(output).toContain('Installing Qwen Code version: latest'); expect(output).toContain('QWEN CODE'); expect(output).toContain( 'Qwen Code 0.0.0-smoke installed successfully.', ); expect(output).toContain('To start:\n cd \n qwen'); expect(output).toContain( `Installed to:\n ${path.join(installRoot, 'lib', 'qwen-code')}`, ); expect(output).toContain('Uninstall:'); expect(output).toContain( 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/uninstall-qwen-standalone.sh', ); expect(output).toContain( `QWEN_INSTALL_LIB_DIR='${path.join(installRoot, 'lib', 'qwen-code')}'`, ); expect(output).toContain( `QWEN_INSTALL_BIN_DIR='${path.join(installRoot, 'bin')}'`, ); expect(output).not.toContain('rm -rf'); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, ); itOnUnix( 'resolves Aliyun latest through a single VERSION pointer before downloading archives', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const checksumFile = path.join(path.dirname(archive), 'SHA256SUMS'); const fakeBin = path.join(tmpDir, 'bin'); const curlLog = path.join(tmpDir, 'curl-urls.log'); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); mkdirSync(fakeBin, { recursive: true }); writeFileSync( path.join(fakeBin, 'uname'), [ '#!/usr/bin/env sh', 'case "$1" in', ' -s) echo Linux ;;', ' -m) echo x86_64 ;;', ' *) /usr/bin/uname "$@" ;;', 'esac', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'curl'), [ '#!/usr/bin/env sh', 'url=', 'dest=', 'while [ "$#" -gt 0 ]; do', ' case "$1" in', ' -o) shift; dest="$1" ;;', ' http*) url="$1" ;;', ' esac', ' shift', 'done', 'printf "%s\\n" "$url" >> "$QWEN_FAKE_CURL_LOG"', 'case "$url" in', ' */releases/qwen-code/latest/VERSION)', ' if [ -n "$dest" ]; then', ' printf "v0.0.0-smoke\\n" > "$dest"', ' else', ' printf "v0.0.0-smoke\\n"', ' fi ;;', ' */releases/qwen-code/v0.0.0-smoke/qwen-code-linux-x64.tar.gz)', ' cp "$QWEN_FAKE_ARCHIVE" "$dest" ;;', ' */releases/qwen-code/v0.0.0-smoke/SHA256SUMS)', ' cp "$QWEN_FAKE_SHA256SUMS" "$dest" ;;', ' *)', ' echo "unexpected url: $url" >&2', ' exit 22 ;;', 'esac', '', ].join('\n'), ); chmodSync(path.join(fakeBin, 'uname'), 0o755); chmodSync(path.join(fakeBin, 'curl'), 0o755); const output = execFileSync( 'bash', [ 'scripts/installation/install-qwen-standalone.sh', '--method', 'standalone', '--mirror', 'aliyun', '--source', 'smoke', ], { env: { ...process.env, HOME: home, PATH: `${fakeBin}:${process.env.PATH}`, QWEN_FAKE_ARCHIVE: archive, QWEN_FAKE_SHA256SUMS: checksumFile, QWEN_FAKE_CURL_LOG: curlLog, QWEN_INSTALL_ROOT: installRoot, }, stdio: 'pipe', }, ).toString(); const curlUrls = readScript(curlLog); expect(curlUrls).toContain('/releases/qwen-code/latest/VERSION'); expect(curlUrls).toContain( '/releases/qwen-code/v0.0.0-smoke/qwen-code-linux-x64.tar.gz', ); expect(curlUrls).toContain( '/releases/qwen-code/v0.0.0-smoke/SHA256SUMS', ); expect(curlUrls).not.toContain( '/releases/qwen-code/latest/qwen-code-linux-x64.tar.gz', ); expect(output).toContain('Downloading qwen-code-linux-x64.tar.gz'); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, 15000, ); itOnUnix( 'tries GitHub before npm when auto-selected Aliyun archive is unavailable', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const checksumFile = path.join(path.dirname(archive), 'SHA256SUMS'); const fakeBin = path.join(tmpDir, 'bin'); const curlLog = path.join(tmpDir, 'curl-urls.log'); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); mkdirSync(fakeBin, { recursive: true }); writeFileSync( path.join(fakeBin, 'uname'), [ '#!/usr/bin/env sh', 'case "$1" in', ' -s) echo Linux ;;', ' -m) echo x86_64 ;;', ' *) /usr/bin/uname "$@" ;;', 'esac', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'curl'), [ '#!/usr/bin/env sh', 'url=', 'dest=', 'is_head=0', 'while [ "$#" -gt 0 ]; do', ' case "$1" in', ' -o) shift; dest="$1" ;;', ' -*) case "$1" in *I*) is_head=1 ;; esac ;;', ' http*) url="$1" ;;', ' esac', ' shift', 'done', 'printf "%s\\n" "$url" >> "$QWEN_FAKE_CURL_LOG"', 'if [ "$is_head" = "1" ]; then', ' case "$url" in', ' */releases/qwen-code/latest/VERSION)', ' exit 0 ;;', ' */releases/latest/download/SHA256SUMS)', ' exit 22 ;;', ' */releases/qwen-code/v0.0.0-smoke/qwen-code-linux-x64.tar.gz)', ' exit 22 ;;', ' */releases/download/v0.0.0-smoke/qwen-code-linux-x64.tar.gz)', ' exit 0 ;;', ' *)', ' echo "unexpected HEAD url: $url" >&2', ' exit 22 ;;', ' esac', 'fi', 'case "$url" in', ' */releases/qwen-code/latest/VERSION)', ' printf "v0.0.0-smoke\\n" ;;', ' */releases/download/v0.0.0-smoke/qwen-code-linux-x64.tar.gz)', ' cp "$QWEN_FAKE_ARCHIVE" "$dest" ;;', ' */releases/download/v0.0.0-smoke/SHA256SUMS)', ' cp "$QWEN_FAKE_SHA256SUMS" "$dest" ;;', ' *)', ' echo "unexpected url: $url" >&2', ' exit 22 ;;', 'esac', '', ].join('\n'), ); chmodSync(path.join(fakeBin, 'uname'), 0o755); chmodSync(path.join(fakeBin, 'curl'), 0o755); const output = execFileSync( 'bash', [ 'scripts/installation/install-qwen-standalone.sh', '--method', 'detect', '--mirror', 'auto', '--source', 'smoke', ], { env: { ...process.env, HOME: home, PATH: `${fakeBin}:${process.env.PATH}`, QWEN_FAKE_ARCHIVE: archive, QWEN_FAKE_SHA256SUMS: checksumFile, QWEN_FAKE_CURL_LOG: curlLog, QWEN_INSTALL_ROOT: installRoot, }, stdio: 'pipe', }, ).toString(); const curlUrls = readScript(curlLog); expect(curlUrls).toContain('/releases/qwen-code/latest/VERSION'); expect(curlUrls).toContain( '/releases/qwen-code/v0.0.0-smoke/qwen-code-linux-x64.tar.gz', ); expect(curlUrls).toContain( '/releases/download/v0.0.0-smoke/qwen-code-linux-x64.tar.gz', ); expect(curlUrls).toContain( '/releases/download/v0.0.0-smoke/SHA256SUMS', ); expect(output).toContain( 'Aliyun standalone archive not found; retrying GitHub mirror.', ); expect(output).toContain('Downloading qwen-code-linux-x64.tar.gz'); expect(output).not.toContain('Falling back to npm installation'); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, 15000, ); itOnUnix('uninstalls standalone files while preserving user config', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-uninstall-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); runUnixInstaller(archive, installRoot, home); const rcFile = path.join(home, '.zshrc'); writeFileSync( rcFile, [ 'before', '# Qwen Code PATH block begin', `export PATH='${installRoot}/bin':$PATH`, '# Qwen Code PATH block end', 'after', ].join('\n') + '\n', ); const qwenDir = path.join(home, '.qwen'); const sourceJson = path.join(qwenDir, 'source.json'); const settingsJson = path.join(qwenDir, 'settings.json'); writeFileSync(settingsJson, '{"theme":"dark"}\n'); runUnixUninstaller(installRoot, home); expect(existsSync(path.join(installRoot, 'lib', 'qwen-code'))).toBe( false, ); expect(existsSync(path.join(installRoot, 'bin', 'qwen'))).toBe(false); expect(readScript(rcFile)).toBe('before\nafter\n'); expect(existsSync(sourceJson)).toBe(true); expect(existsSync(settingsJson)).toBe(true); runUnixUninstaller(installRoot, home, { QWEN_UNINSTALL_PURGE: '1' }); expect(existsSync(sourceJson)).toBe(false); expect(existsSync(settingsJson)).toBe(true); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix( 'removes only installer-owned shell rc PATH lines during uninstall', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-uninstall-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); runUnixInstaller(archive, installRoot, home); const rcFile = path.join(home, '.zshrc'); writeFileSync( rcFile, [ 'before', '# Added by qwen-code installer (multi-qwen shadow fix) ', `export PATH='${installRoot}/bin':$PATH`, 'middle', '# Added by qwen-code installer (multi-qwen shadow fix)', 'echo keep-me', 'after', ].join('\n') + '\n', ); runUnixUninstaller(installRoot, home); expect(readScript(rcFile)).toBe( ['before', 'middle', 'echo keep-me', 'after'].join('\n') + '\n', ); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, ); itOnUnix( 'removes installer-owned shell rc PATH blocks even when extra lines are inserted', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-uninstall-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); runUnixInstaller(archive, installRoot, home); const rcFile = path.join(home, '.zshrc'); writeFileSync( rcFile, [ 'before', '# Qwen Code PATH block begin', '# inserted by another tool', `export PATH='${installRoot}/bin':$PATH`, '# Qwen Code PATH block end', 'after', ].join('\n') + '\n', ); runUnixUninstaller(installRoot, home); expect(readScript(rcFile)).toBe('before\nafter\n'); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, ); itOnUnix( 'preserves malformed shell rc PATH blocks without an end marker', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-uninstall-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); runUnixInstaller(archive, installRoot, home); const rcFile = path.join(home, '.zshrc'); writeFileSync( rcFile, [ 'before', '# Qwen Code PATH block begin', `export PATH='${installRoot}/bin':$PATH`, 'user content that must stay', 'after', ].join('\n') + '\n', ); runUnixUninstaller(installRoot, home); expect(readScript(rcFile)).toBe( [ 'before', '# Qwen Code PATH block begin', `export PATH='${installRoot}/bin':$PATH`, 'user content that must stay', 'after', ].join('\n') + '\n', ); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, ); itOnUnix('shell-quotes custom install paths in the generated wrapper', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); const installLibDir = path.join( installRoot, 'lib', 'qwen-code$(touch qwen-pwned)', ); runUnixInstaller(archive, installRoot, home, 'standalone', { QWEN_INSTALL_LIB_DIR: installLibDir, }); const version = execFileSync( path.join(installRoot, 'bin', 'qwen'), ['--version'], { cwd: tmpDir, }, ) .toString() .trim(); expect(version).toBe('0.0.0-smoke'); expect(existsSync(path.join(tmpDir, 'qwen-pwned'))).toBe(false); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix( 'shell-quotes PATH updates written to shell rc files', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const fakeBin = path.join(tmpDir, 'shadow-bin'); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); const marker = path.join(tmpDir, 'qwen-pwned'); const unsafeBinDir = path.join( installRoot, 'bin path $(touch qwen-pwned)', ); mkdirSync(fakeBin, { recursive: true }); writeFileSync(path.join(fakeBin, 'qwen'), '#!/usr/bin/env sh\n'); chmodSync(path.join(fakeBin, 'qwen'), 0o755); runUnixInstaller(archive, installRoot, home, 'standalone', { PATH: `${fakeBin}:${process.env.PATH}`, SHELL: '/bin/bash', QWEN_INSTALL_BIN_DIR: unsafeBinDir, }); const bashrc = path.join(home, '.bashrc'); expect(readScript(bashrc)).toContain( `export PATH='${unsafeBinDir}':$PATH`, ); execFileSync('bash', ['-c', `source "${bashrc}"`], { cwd: tmpDir, stdio: 'pipe', }); expect(existsSync(marker)).toBe(false); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, 15000, ); itOnUnix( 'skips shell rc PATH updates for unsupported shells', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const fakeBin = path.join(tmpDir, 'shadow-bin'); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); mkdirSync(fakeBin, { recursive: true }); writeFileSync(path.join(fakeBin, 'qwen'), '#!/usr/bin/env sh\n'); chmodSync(path.join(fakeBin, 'qwen'), 0o755); const output = runUnixInstaller( archive, installRoot, home, 'standalone', { PATH: `${fakeBin}:${process.env.PATH}`, SHELL: '/bin/tcsh', }, ).toString(); expect(output).toContain('Unsupported shell for automatic PATH update'); expect(existsSync(path.join(home, '.profile'))).toBe(false); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, 15000, ); itOnUnix( 'uses ranged GET fallback when archive HEAD probes fail', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const checksumFile = path.join(path.dirname(archive), 'SHA256SUMS'); const fakeBin = path.join(tmpDir, 'bin'); const curlLog = path.join(tmpDir, 'curl-urls.log'); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); mkdirSync(fakeBin, { recursive: true }); writeFileSync( path.join(fakeBin, 'uname'), [ '#!/usr/bin/env sh', 'case "$1" in', ' -s) echo Linux ;;', ' -m) echo x86_64 ;;', ' *) /usr/bin/uname "$@" ;;', 'esac', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'curl'), [ '#!/usr/bin/env sh', 'url=', 'dest=', 'is_head=0', 'is_range=0', 'while [ "$#" -gt 0 ]; do', ' case "$1" in', ' -o) shift; dest="$1" ;;', ' --range|-r) is_range=1; shift ;;', ' -H) shift; case "$1" in Range:*) is_range=1 ;; esac ;;', ' -*) case "$1" in *I*) is_head=1 ;; esac ;;', ' http*) url="$1" ;;', ' esac', ' shift', 'done', 'printf "%s %s %s\\n" "$url" "$is_head" "$is_range" >> "$QWEN_FAKE_CURL_LOG"', 'case "$url" in', ' */qwen-code-linux-x64.tar.gz)', ' if [ "$is_head" = "1" ]; then exit 22; fi', ' if [ "$is_range" = "1" ]; then : > "${dest:-/dev/null}"; exit 0; fi', ' cp "$QWEN_FAKE_ARCHIVE" "$dest"; exit 0 ;;', ' */SHA256SUMS)', ' cp "$QWEN_FAKE_SHA256SUMS" "$dest"; exit 0 ;;', ' *)', ' echo "unexpected url: $url" >&2', ' exit 22 ;;', 'esac', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'node'), [ '#!/usr/bin/env sh', 'if [ "$1" = "-p" ]; then echo 22.0.0; exit 0; fi', 'exit 0', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'npm'), '#!/usr/bin/env sh\necho npm fallback should not run >&2\nexit 1\n', ); for (const command of ['uname', 'curl', 'node', 'npm']) { chmodSync(path.join(fakeBin, command), 0o755); } const output = execFileSync( 'bash', [ 'scripts/installation/install-qwen-standalone.sh', '--method', 'detect', '--base-url', 'https://example.com/qwen-code', '--source', 'smoke', ], { env: { ...process.env, HOME: home, PATH: `${fakeBin}:${process.env.PATH}`, QWEN_FAKE_ARCHIVE: archive, QWEN_FAKE_SHA256SUMS: checksumFile, QWEN_FAKE_CURL_LOG: curlLog, QWEN_INSTALL_ROOT: installRoot, }, stdio: 'pipe', }, ).toString(); const curlUrls = readScript(curlLog); expect(curlUrls).toContain('qwen-code-linux-x64.tar.gz 1 0'); expect(curlUrls).toContain('qwen-code-linux-x64.tar.gz 0 1'); expect(output).toContain('Downloading qwen-code-linux-x64.tar.gz'); expect(output).not.toContain('Falling back to npm installation'); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, 15000, ); itOnUnix( 'adds a new shell rc PATH entry when reinstalling with a different bin dir', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); const firstBinDir = path.join(installRoot, 'bin-one'); const secondBinDir = path.join(installRoot, 'bin-two'); runUnixInstaller(archive, installRoot, home, 'standalone', { SHELL: '/bin/bash', QWEN_INSTALL_BIN_DIR: firstBinDir, }); runUnixInstaller(archive, installRoot, home, 'standalone', { SHELL: '/bin/bash', QWEN_INSTALL_BIN_DIR: secondBinDir, }); const bashrc = readScript(path.join(home, '.bashrc')); expect(bashrc).toContain(`export PATH='${firstBinDir}':$PATH`); expect(bashrc).toContain(`export PATH='${secondBinDir}':$PATH`); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }, 15000, ); itOnUnix('rejects a tampered local archive', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); appendFileSync(archive, 'tamper'); expect(() => runUnixInstaller( archive, path.join(tmpDir, 'install'), path.join(tmpDir, 'home'), ), ).toThrow(/Checksum mismatch/); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix('rejects a local archive when SHA256SUMS is missing', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); rmSync(path.join(path.dirname(archive), 'SHA256SUMS'), { force: true }); expect(() => runUnixInstaller( archive, path.join(tmpDir, 'install'), path.join(tmpDir, 'home'), ), ).toThrow(/SHA256SUMS not found/); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix('rejects standalone archives containing symlinks', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = createSymlinkStandaloneArchive(tmpDir); expect(() => runUnixInstaller( archive, path.join(tmpDir, 'install'), path.join(tmpDir, 'home'), ), ).toThrow(/Archive contains symlinks/); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); itOnUnix( 'rejects standalone archives containing path traversal entries', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = createTraversalStandaloneArchive(tmpDir); expect(() => runUnixInstaller( archive, path.join(tmpDir, 'install'), path.join(tmpDir, 'home'), ), ).toThrow(/Archive contains unsafe path/); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }, ); itOnUnix('backs up and overwrites a non-managed install directory', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); const installRoot = path.join(tmpDir, 'install'); const installDir = path.join(installRoot, 'lib', 'qwen-code'); mkdirSync(installDir, { recursive: true }); writeFileSync(path.join(installDir, 'important.txt'), 'keep me\n'); const output = runUnixInstaller( archive, installRoot, path.join(tmpDir, 'home'), ).toString(); expect(output).toContain('not a Qwen Code standalone install'); expect(output).toContain('Backing up to'); // Original directory should be backed up, not destroyed const backups = readdirSync(path.join(installRoot, 'lib')).filter((e) => e.startsWith('qwen-code.backup.'), ); expect(backups.length).toBe(1); expect( readScript(path.join(installRoot, 'lib', backups[0], 'important.txt')), ).toBe('keep me\n'); // New install should be at the original location expect(existsSync(path.join(installDir, 'manifest.json'))).toBe(true); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix('does not fall back to npm when detect finds a bad archive', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = packageFakeStandalone(tmpDir); appendFileSync(archive, 'tamper'); let failureMessage = ''; try { runUnixInstaller( archive, path.join(tmpDir, 'install'), path.join(tmpDir, 'home'), 'detect', ); } catch (error) { failureMessage = error.message; } expect(failureMessage).toContain('Checksum mismatch'); expect(failureMessage).toContain('Standalone install failed'); expect(failureMessage).not.toContain('Falling back to npm installation'); } finally { rmSync(tmpDir, { recursive: true, force: true }); restoreMinimalDist(createdDist); } }); itOnUnix( 'falls back to npm in detect mode when archive is unavailable', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const fakeBin = path.join(tmpDir, 'bin'); const home = path.join(tmpDir, 'home'); const npmLog = path.join(tmpDir, 'npm-args.txt'); mkdirSync(fakeBin, { recursive: true }); mkdirSync(home, { recursive: true }); writeFileSync( path.join(fakeBin, 'curl'), '#!/usr/bin/env sh\nexit 22\n', ); writeFileSync( path.join(fakeBin, 'node'), [ '#!/usr/bin/env sh', 'if [ "$1" = "-p" ]; then', ' case "$2" in', ' *split*) echo 22 ;;', ' *) echo 22.0.0 ;;', ' esac', ' exit 0', 'fi', 'exit 0', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'npm'), [ '#!/usr/bin/env sh', 'case "$1" in', ' -v) echo 10.0.0 ;;', ' prefix) echo "$QWEN_FAKE_NPM_PREFIX" ;;', ' install) printf "%s\\n" "$*" > "$QWEN_FAKE_NPM_LOG" ;;', 'esac', 'exit 0', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'qwen'), '#!/usr/bin/env sh\necho 0.0.0-npm\n', ); for (const command of ['curl', 'node', 'npm', 'qwen']) { chmodSync(path.join(fakeBin, command), 0o755); } const output = execFileSync( 'bash', [ 'scripts/installation/install-qwen-standalone.sh', '--method', 'detect', '--base-url', 'https://example.invalid/qwen-code', '--source', 'smoke', ], { env: { ...process.env, HOME: home, PATH: `${fakeBin}:${process.env.PATH}`, QWEN_FAKE_NPM_LOG: npmLog, QWEN_FAKE_NPM_PREFIX: path.join(tmpDir, 'npm-prefix'), }, stdio: 'pipe', }, ).toString(); expect(output).toContain('Falling back to npm installation'); expect(readScript(npmLog)).toContain( 'install -g @qwen-code/qwen-code@latest --registry', ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }, ); itOnUnix('passes pinned versions through to npm fallback', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const fakeBin = path.join(tmpDir, 'bin'); const home = path.join(tmpDir, 'home'); const npmLog = path.join(tmpDir, 'npm-args.txt'); mkdirSync(fakeBin, { recursive: true }); mkdirSync(home, { recursive: true }); writeFileSync(path.join(fakeBin, 'curl'), '#!/usr/bin/env sh\nexit 22\n'); writeFileSync( path.join(fakeBin, 'node'), [ '#!/usr/bin/env sh', 'if [ "$1" = "-p" ]; then', ' case "$2" in', ' *split*) echo 22 ;;', ' *) echo 22.0.0 ;;', ' esac', ' exit 0', 'fi', 'exit 0', '', ].join('\n'), ); writeFileSync( path.join(fakeBin, 'npm'), [ '#!/usr/bin/env sh', 'case "$1" in', ' -v) echo 10.0.0 ;;', ' prefix) echo "$QWEN_FAKE_NPM_PREFIX" ;;', ' install) printf "%s\\n" "$*" > "$QWEN_FAKE_NPM_LOG" ;;', 'esac', 'exit 0', '', ].join('\n'), ); for (const command of ['curl', 'node', 'npm']) { chmodSync(path.join(fakeBin, command), 0o755); } execFileSync( 'bash', [ 'scripts/installation/install-qwen-standalone.sh', '--method', 'detect', '--base-url', 'https://example.invalid/qwen-code', '--version', 'v0.15.10', ], { env: { ...process.env, HOME: home, PATH: `${fakeBin}:${process.env.PATH}`, QWEN_FAKE_NPM_LOG: npmLog, QWEN_FAKE_NPM_PREFIX: path.join(tmpDir, 'npm-prefix'), }, stdio: 'pipe', }, ); expect(readScript(npmLog)).toContain( 'install -g @qwen-code/qwen-code@0.15.10 --registry', ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); itOnUnix('preserves context when npm fallback also fails', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const fakeBin = path.join(tmpDir, 'bin'); mkdirSync(fakeBin, { recursive: true }); writeFileSync(path.join(fakeBin, 'curl'), '#!/usr/bin/env sh\nexit 22\n'); chmodSync(path.join(fakeBin, 'curl'), 0o755); let failureMessage = ''; try { execFileSync( 'bash', [ 'scripts/installation/install-qwen-standalone.sh', '--method', 'detect', '--base-url', 'https://example.invalid/qwen-code', '--source', 'smoke', ], { env: { HOME: path.join(tmpDir, 'home'), PATH: `${fakeBin}:/usr/bin:/bin`, }, stdio: 'pipe', }, ); } catch (error) { failureMessage = [ error.message, error.stdout?.toString() || '', error.stderr?.toString() || '', ].join('\n'); } expect(failureMessage).toContain('Falling back to npm installation'); expect(failureMessage).toMatch( /Node\.js was not found|Unable to determine Node\.js version/, ); expect(failureMessage).toContain('npm fallback also failed'); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); }); describe('Windows installer end-to-end', () => { itOnWindows( 'installs a local standalone archive with checksum verification', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = createFakeWindowsStandaloneArchive(tmpDir); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); runWindowsInstaller(archive, installRoot, home); expect(existsSync(path.join(installRoot, 'bin', 'qwen.cmd'))).toBe( true, ); expect( existsSync(path.join(installRoot, 'qwen-code', 'node', 'node.exe')), ).toBe(true); expect(readScript(path.join(home, '.qwen', 'source.json'))).toContain( '"source": "smoke"', ); const version = runWindowsCommand( `call "${path.join(installRoot, 'bin', 'qwen.cmd')}" --version`, { USERPROFILE: home }, ) .toString() .trim(); expect(version).toBe('0.0.0-smoke'); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }, ); itOnWindows('rejects a tampered local archive', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = createFakeWindowsStandaloneArchive(tmpDir); appendFileSync(archive, 'tamper'); expect(() => runWindowsInstaller( archive, path.join(tmpDir, 'install'), path.join(tmpDir, 'home'), ), ).toThrow(/Checksum mismatch/); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); itOnWindows('rejects unsafe environment-derived install paths', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = createFakeWindowsStandaloneArchive(tmpDir); const marker = path.join(tmpDir, 'pwned.txt'); expect(() => runWindowsInstaller( archive, path.join(tmpDir, 'install'), path.join(tmpDir, 'home'), 'standalone', { QWEN_INSTALL_ROOT: `${path.join(tmpDir, 'install')}" & echo pwned > "${marker}" & "`, }, ), ).toThrow(/unsafe command characters/); expect(existsSync(marker)).toBe(false); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); itOnWindows( 'resolves Aliyun latest through a single VERSION pointer before downloading archives', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const archive = createFakeWindowsStandaloneArchive(tmpDir); const checksumFile = path.join(path.dirname(archive), 'SHA256SUMS'); const fakeBin = path.join(tmpDir, 'bin'); const curlLog = path.join(tmpDir, 'curl-urls.log'); const installRoot = path.join(tmpDir, 'install'); const home = path.join(tmpDir, 'home'); const fakeCurl = createFakeWindowsCurlCommand(fakeBin); const output = runWindowsCommand( [ `call "${path.resolve('scripts/installation/install-qwen-standalone.bat')}"`, '--method', 'standalone', '--mirror', 'aliyun', '--source', 'smoke', ].join(' '), { USERPROFILE: home, QWEN_INSTALL_ROOT: installRoot, QWEN_FAKE_ARCHIVE: archive, QWEN_FAKE_SHA256SUMS: checksumFile, QWEN_FAKE_CURL_LOG: curlLog, QWEN_INSTALL_CURL_EXE: fakeCurl, ...prependWindowsPath(fakeBin), PROCESSOR_ARCHITECTURE: 'AMD64', PROCESSOR_ARCHITEW6432: '', }, ).toString(); const curlUrls = readScript(curlLog); expect(curlUrls).toContain('/releases/qwen-code/latest/VERSION'); expect(curlUrls).toContain( '/releases/qwen-code/v0.0.0/qwen-code-win-x64.zip', ); expect(curlUrls).toContain('/releases/qwen-code/v0.0.0/SHA256SUMS'); expect(curlUrls).not.toContain( '/releases/qwen-code/latest/qwen-code-win-x64.zip', ); expect(output).toContain('Downloading qwen-code-win-x64.zip'); expect(existsSync(path.join(installRoot, 'bin', 'qwen.cmd'))).toBe( true, ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }, ); itOnWindows( 'falls back to npm in detect mode when archive is unavailable', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const fakeBin = path.join(tmpDir, 'bin'); const npmLog = path.join(tmpDir, 'npm-install.log'); createFakeWindowsNpmTools(fakeBin); const output = runWindowsCommand( [ `call "${path.resolve('scripts/installation/install-qwen-standalone.bat')}"`, '--method', 'detect', '--source', 'smoke', ].join(' '), { USERPROFILE: path.join(tmpDir, 'home'), QWEN_INSTALL_ROOT: path.join(tmpDir, 'install'), QWEN_FAKE_NPM_LOG: npmLog, QWEN_FAKE_NPM_PREFIX: path.join(tmpDir, 'npm-prefix'), ...prependWindowsPath(fakeBin), PROCESSOR_ARCHITECTURE: 'ARM64', PROCESSOR_ARCHITEW6432: '', }, ).toString(); expect(output).toContain('Falling back to npm installation'); expect(readScript(npmLog)).toContain( 'install -g @qwen-code/qwen-code@latest --registry', ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }, ); itOnWindows('passes pinned versions through to npm fallback', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const fakeBin = path.join(tmpDir, 'bin'); const npmLog = path.join(tmpDir, 'npm-install.log'); createFakeWindowsNpmTools(fakeBin); runWindowsCommand( [ `call "${path.resolve('scripts/installation/install-qwen-standalone.bat')}"`, '--method', 'detect', '--source', 'smoke', '--version', 'v0.15.10', ].join(' '), { USERPROFILE: path.join(tmpDir, 'home'), QWEN_INSTALL_ROOT: path.join(tmpDir, 'install'), QWEN_FAKE_NPM_LOG: npmLog, QWEN_FAKE_NPM_PREFIX: path.join(tmpDir, 'npm-prefix'), ...prependWindowsPath(fakeBin), PROCESSOR_ARCHITECTURE: 'ARM64', PROCESSOR_ARCHITEW6432: '', }, ); expect(readScript(npmLog)).toContain( 'install -g @qwen-code/qwen-code@0.15.10 --registry', ); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); itOnWindows('preserves context when npm fallback also fails', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-install-test-')); try { const fakeBin = path.join(tmpDir, 'bin'); mkdirSync(fakeBin, { recursive: true }); writeFileSync( path.join(fakeBin, 'node.cmd'), ['@echo off', 'exit /b 1', ''].join('\r\n'), ); let failureMessage = ''; try { runWindowsCommand( [ `call "${path.resolve('scripts/installation/install-qwen-standalone.bat')}"`, '--method', 'detect', '--source', 'smoke', ].join(' '), { USERPROFILE: path.join(tmpDir, 'home'), QWEN_INSTALL_ROOT: path.join(tmpDir, 'install'), ...prependWindowsPath(fakeBin), PROCESSOR_ARCHITECTURE: 'ARM64', PROCESSOR_ARCHITEW6432: '', }, ); } catch (error) { failureMessage = [ error.message, error.stdout?.toString() || '', error.stderr?.toString() || '', ].join('\n'); } expect(failureMessage).toContain('Falling back to npm installation'); expect(failureMessage).toContain('Unable to determine Node.js version'); expect(failureMessage).toContain('npm fallback also failed'); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); }); describe('Windows PowerShell uninstaller end-to-end', () => { itOnWindows('prints help without deleting standalone files', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-uninstall-test-')); try { const installRoot = path.join(tmpDir, 'install'); const installDir = path.join(installRoot, 'qwen-code'); const home = path.join(tmpDir, 'home'); createFakeWindowsStandaloneInstall(installRoot); const output = runWindowsPowerShellScript( 'scripts/installation/uninstall-qwen-standalone.ps1', ['-Help'], { USERPROFILE: home, QWEN_INSTALL_ROOT: installRoot, }, ).toString(); expect(output).toContain('Usage:'); expect(output).toContain('-Purge'); expect(existsSync(installDir)).toBe(true); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); itOnWindows('purges the source marker while preserving other config', () => { const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-uninstall-test-')); try { const installRoot = path.join(tmpDir, 'install'); const installDir = path.join(installRoot, 'qwen-code'); const installBinDir = path.join(installRoot, 'bin'); const home = path.join(tmpDir, 'home'); const qwenConfigDir = path.join(home, '.qwen'); const sourceMarker = path.join(qwenConfigDir, 'source.json'); const settingsFile = path.join(qwenConfigDir, 'settings.json'); createFakeWindowsStandaloneInstall(installRoot); mkdirSync(qwenConfigDir, { recursive: true }); writeFileSync(sourceMarker, '{"source":"smoke"}\n'); writeFileSync(settingsFile, '{"theme":"dark"}\n'); const output = runWindowsPowerShellScript( 'scripts/installation/uninstall-qwen-standalone.ps1', ['-Purge'], { USERPROFILE: home, QWEN_INSTALL_ROOT: installRoot, }, ).toString(); expect(output).toContain('Removed'); expect(existsSync(installDir)).toBe(false); expect(existsSync(path.join(installBinDir, 'qwen.cmd'))).toBe(false); expect(existsSync(sourceMarker)).toBe(false); expect(readScript(settingsFile)).toContain('"theme":"dark"'); } finally { rmSync(tmpDir, { recursive: true, force: true }); } }); }); function ensureMinimalDist() { const distPath = path.resolve('dist'); const backupPath = existsSync(distPath) ? path.join( path.dirname(distPath), `qwen-dist-backup-${process.pid}-${Date.now()}-${Math.random() .toString(16) .slice(2)}`, ) : null; if (backupPath) { renameSync(distPath, backupPath); } mkdirSync(path.join(distPath, 'chunks'), { recursive: true }); mkdirSync(path.join(distPath, 'vendor'), { recursive: true }); mkdirSync(path.join(distPath, 'bundled/qc-helper/docs'), { recursive: true, }); writeFileSync(path.join(distPath, 'cli.js'), 'console.log("qwen");\n'); writeFileSync(path.join(distPath, 'chunks/index.js'), 'export {};\n'); writeFileSync( path.join(distPath, 'package.json'), JSON.stringify({ name: '@qwen-code/qwen-code', version: '0.0.0' }), ); return { backupPath, distPath }; } function restoreMinimalDist(state) { rmSync(state?.distPath || path.resolve('dist'), { recursive: true, force: true, }); if (state?.backupPath) { renameSync(state.backupPath, state.distPath); } } function createFakeNodeArchive(tmpDir, options = {}) { const fakeNodeDir = path.join(tmpDir, 'node-v22.0.0-linux-x64'); mkdirSync(path.join(fakeNodeDir, 'bin'), { recursive: true }); writeFileSync( path.join(fakeNodeDir, 'bin', 'node'), '#!/usr/bin/env sh\necho 0.0.0-smoke\n', ); chmodSync(path.join(fakeNodeDir, 'bin', 'node'), 0o755); if (options.withSafeNodeSymlink) { mkdirSync(path.join(fakeNodeDir, 'lib'), { recursive: true }); writeFileSync(path.join(fakeNodeDir, 'lib', 'npm-cli.js'), 'npm cli\n'); symlinkSync('../lib/npm-cli.js', path.join(fakeNodeDir, 'bin', 'npm')); } if (options.withEscapingNodeSymlink) { const outsideTarget = path.join(tmpDir, 'outside-node-helper.js'); writeFileSync(outsideTarget, 'outside\n'); symlinkSync(outsideTarget, path.join(fakeNodeDir, 'bin', 'npm')); } if (options.withNodeSymlinkCycle) { symlinkSync('../bin', path.join(fakeNodeDir, 'bin', 'cycle')); } const archive = path.join(tmpDir, 'node-v22.0.0-linux-x64.tar.gz'); execFileSync( 'tar', ['-czf', archive, '-C', tmpDir, path.basename(fakeNodeDir)], { env: { ...process.env, LC_ALL: 'C' }, stdio: 'ignore', }, ); return archive; } function createBadUnixNodeArchive(tmpDir) { const fakeRuntimeDir = path.join(tmpDir, 'not-node'); mkdirSync(fakeRuntimeDir, { recursive: true }); writeFileSync(path.join(fakeRuntimeDir, 'README.txt'), 'not node\n'); const archive = path.join(tmpDir, 'bad-runtime.tar.gz'); execFileSync('tar', ['-czf', archive, '-C', tmpDir, 'not-node'], { env: { ...process.env, LC_ALL: 'C' }, stdio: 'ignore', }); return archive; } function createBadWindowsNodeArchive(tmpDir) { const fakeRuntimeDir = path.join(tmpDir, 'not-node'); mkdirSync(fakeRuntimeDir, { recursive: true }); writeFileSync(path.join(fakeRuntimeDir, 'README.txt'), 'not node\n'); const archive = path.join(tmpDir, 'bad-runtime.zip'); createZipForTest(archive, tmpDir, path.basename(fakeRuntimeDir)); return archive; } function createFakeWindowsNodeArchive(tmpDir) { const fakeNodeDir = path.join(tmpDir, 'node-v22.0.0-win-x64'); mkdirSync(fakeNodeDir, { recursive: true }); writeFileSync(path.join(fakeNodeDir, 'node.exe'), 'fake node.exe\n'); const archive = path.join(tmpDir, 'node-v22.0.0-win-x64.zip'); createZipForTest(archive, tmpDir, path.basename(fakeNodeDir)); return archive; } function createFakeWindowsStandaloneArchive(tmpDir) { const packageRoot = path.join(tmpDir, 'qwen-code'); const outDir = path.join(tmpDir, 'out'); mkdirSync(path.join(packageRoot, 'bin'), { recursive: true }); mkdirSync(path.join(packageRoot, 'node'), { recursive: true }); mkdirSync(outDir, { recursive: true }); writeFileSync( path.join(packageRoot, 'bin', 'qwen.cmd'), ['@echo off', 'echo 0.0.0-smoke', ''].join('\r\n'), ); writeFileSync(path.join(packageRoot, 'node', 'node.exe'), 'fake node.exe\n'); writeFileSync( path.join(packageRoot, 'manifest.json'), JSON.stringify({ name: '@qwen-code/qwen-code', target: 'win-x64' }), ); const archive = path.join(outDir, 'qwen-code-win-x64.zip'); createZipForTest(archive, tmpDir, path.basename(packageRoot)); writeChecksumFile(outDir, path.basename(archive)); return archive; } function createFakeWindowsStandaloneInstall(installRoot) { const installDir = path.join(installRoot, 'qwen-code'); const installBinDir = path.join(installRoot, 'bin'); mkdirSync(path.join(installDir, 'bin'), { recursive: true }); mkdirSync(path.join(installDir, 'node'), { recursive: true }); mkdirSync(installBinDir, { recursive: true }); writeFileSync( path.join(installDir, 'manifest.json'), JSON.stringify({ name: '@qwen-code/qwen-code', target: 'win-x64' }), ); writeFileSync( path.join(installDir, 'bin', 'qwen.cmd'), ['@echo off', 'echo 0.0.0-smoke', ''].join('\r\n'), ); writeFileSync(path.join(installDir, 'node', 'node.exe'), 'fake node.exe\n'); writeFileSync( path.join(installBinDir, 'qwen.cmd'), ['@echo off', `"${path.join(installDir, 'bin', 'qwen.cmd')}" %*`, ''].join( '\r\n', ), ); } function createFakeWindowsNpmTools(fakeBin) { mkdirSync(fakeBin, { recursive: true }); writeFileSync( path.join(fakeBin, 'node.cmd'), ['@echo off', 'if "%~1"=="-p" echo 22.0.0', 'exit /b 0', ''].join('\r\n'), ); writeFileSync( path.join(fakeBin, 'npm.cmd'), [ '@echo off', 'if "%~1"=="-v" echo 10.0.0 & exit /b 0', 'if "%~1"=="prefix" echo %QWEN_FAKE_NPM_PREFIX% & exit /b 0', 'if "%~1"=="install" echo %* > "%QWEN_FAKE_NPM_LOG%" & exit /b 0', 'exit /b 0', '', ].join('\r\n'), ); writeFileSync( path.join(fakeBin, 'qwen.cmd'), ['@echo off', 'echo 0.0.0-npm', ''].join('\r\n'), ); } function createFakeWindowsCurlCommand(fakeBin) { mkdirSync(fakeBin, { recursive: true }); const outputPath = path.join(fakeBin, 'curl.cmd'); writeFileSync( outputPath, [ '@echo off', 'setlocal EnableExtensions EnableDelayedExpansion', 'set "destination="', 'set "url="', ':parse_args', 'if "%~1"=="" goto done_parse', 'set "arg=%~1"', 'if "!arg:~0,1!"=="-" (', ' if /i "!arg!"=="-o" (', ' set "destination=%~2"', ' shift', ' shift', ' goto parse_args', ' )', ' if /i "!arg!"=="--output" (', ' set "destination=%~2"', ' shift', ' shift', ' goto parse_args', ' )', ' if not "!arg:~0,2!"=="--" if /i "!arg:~-1!"=="o" (', ' set "destination=%~2"', ' shift', ' shift', ' goto parse_args', ' )', ' shift', ' goto parse_args', ')', 'if /i "!arg:~0,4!"=="http" set "url=!arg!"', 'shift', 'goto parse_args', ':done_parse', '>>"%QWEN_FAKE_CURL_LOG%" echo(!url!', 'if "!url!"=="" echo missing url or destination 1>&2 & exit /b 2', 'if "!destination!"=="" echo missing url or destination 1>&2 & exit /b 2', 'echo(!url! | findstr /I /C:"/releases/qwen-code/latest/VERSION" >nul && (', ' > "!destination!" echo 0.0.0', ' exit /b 0', ')', 'echo(!url! | findstr /I /C:"/releases/qwen-code/v0.0.0/qwen-code-win-x64.zip" >nul && (', ' copy /Y "%QWEN_FAKE_ARCHIVE%" "!destination!" >nul', ' exit /b 0', ')', 'echo(!url! | findstr /I /C:"/releases/qwen-code/v0.0.0/SHA256SUMS" >nul && (', ' copy /Y "%QWEN_FAKE_SHA256SUMS%" "!destination!" >nul', ' exit /b 0', ')', 'echo unexpected url: !url! 1>&2', 'exit /b 22', '', ].join('\r\n'), ); return outputPath; } function prependWindowsPath(directory) { const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === 'path') || 'Path'; const value = `${directory};${process.env[pathKey] || ''}`; return { PATH: value, Path: value, [pathKey]: value, }; } function createZipForTest(archive, cwd, entry) { if (process.platform === 'win32') { execFileSync( 'powershell', [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Compress-Archive -LiteralPath $env:QWEN_TEST_ZIP_ENTRY -DestinationPath $env:QWEN_TEST_ZIP_ARCHIVE -Force', ], { env: { ...process.env, QWEN_TEST_ZIP_ENTRY: path.join(cwd, entry), QWEN_TEST_ZIP_ARCHIVE: archive, }, stdio: 'ignore', }, ); return; } execFileSync('zip', ['-qr', archive, entry], { cwd, stdio: 'ignore', }); } function extractZipForTest(archive, destination) { if (process.platform === 'win32') { execFileSync( 'powershell', [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', 'Expand-Archive -LiteralPath $env:QWEN_TEST_ZIP_ARCHIVE -DestinationPath $env:QWEN_TEST_ZIP_DESTINATION -Force', ], { env: { ...process.env, QWEN_TEST_ZIP_ARCHIVE: archive, QWEN_TEST_ZIP_DESTINATION: destination, }, stdio: 'ignore', }, ); return; } execFileSync('unzip', ['-q', archive, '-d', destination], { stdio: 'ignore', }); } function packageFakeStandalone(tmpDir, nodeArchiveOptions = {}) { const outDir = path.join(tmpDir, 'out'); mkdirSync(outDir, { recursive: true }); execFileSync( 'node', [ 'scripts/create-standalone-package.js', '--target', 'linux-x64', '--node-archive', createFakeNodeArchive(tmpDir, nodeArchiveOptions), '--out-dir', outDir, '--version', '0.0.0-smoke', ], { stdio: 'pipe' }, ); return path.join(outDir, 'qwen-code-linux-x64.tar.gz'); } function runUnixInstaller( archive, installRoot, home, method = 'standalone', extraEnv = {}, ) { mkdirSync(home, { recursive: true }); try { return execFileSync( 'bash', [ 'scripts/installation/install-qwen-standalone.sh', '--method', method, '--archive', archive, '--source', 'smoke', ], { env: { ...process.env, HOME: home, QWEN_INSTALL_ROOT: installRoot, ...extraEnv, }, stdio: 'pipe', }, ); } catch (error) { const processError = error; throw new Error( [ processError.message, processError.stdout?.toString() || '', processError.stderr?.toString() || '', ].join('\n'), ); } } function runUnixUninstaller(installRoot, home, extraEnv = {}) { mkdirSync(home, { recursive: true }); try { return execFileSync( 'bash', ['scripts/installation/uninstall-qwen-standalone.sh'], { env: { ...process.env, HOME: home, QWEN_INSTALL_ROOT: installRoot, ...extraEnv, }, stdio: 'pipe', }, ); } catch (error) { const processError = error; throw new Error( [ processError.message, processError.stdout?.toString() || '', processError.stderr?.toString() || '', ].join('\n'), ); } } function runWindowsInstaller( archive, installRoot, home, method = 'standalone', extraEnv = {}, ) { mkdirSync(home, { recursive: true }); try { return runWindowsCommand( [ `call "${path.resolve('scripts/installation/install-qwen-standalone.bat')}"`, '--method', method, '--archive', `"${archive}"`, '--source', 'smoke', ].join(' '), { USERPROFILE: home, QWEN_INSTALL_ROOT: installRoot, ...extraEnv, }, ); } catch (error) { const processError = error; throw new Error( [ processError.message, processError.stdout?.toString() || '', processError.stderr?.toString() || '', ].join('\n'), ); } } function runWindowsCommand(command, env = {}) { const prepared = prepareWindowsCommand(command, env); try { return execFileSync( process.env.ComSpec || 'cmd.exe', ['/d', '/c', prepared.command], { env: { ...prepared.env, }, stdio: 'pipe', // cmd.exe parses the command string itself; preserve quoted paths. windowsVerbatimArguments: true, }, ); } catch (error) { const processError = error; throw new Error( [ processError.message, processError.stdout?.toString() || '', processError.stderr?.toString() || '', ].join('\n'), ); } } function runWindowsPowerShellScript(scriptPath, args = [], env = {}) { try { return execFileSync( 'powershell', [ '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', path.resolve(scriptPath), ...args, ], { env: { ...process.env, ...env, }, stdio: 'pipe', }, ); } catch (error) { const processError = error; throw new Error( [ processError.message, processError.stdout?.toString() || '', processError.stderr?.toString() || '', ].join('\n'), ); } } const WINDOWS_COMMAND_ENV_OVERRIDES = [ 'PROCESSOR_ARCHITECTURE', 'PROCESSOR_ARCHITEW6432', ]; function prepareWindowsCommand(command, env = {}, baseEnv = process.env) { const commandEnv = { ...baseEnv, ...env }; const commandPrefix = []; for (const key of WINDOWS_COMMAND_ENV_OVERRIDES) { if (!Object.prototype.hasOwnProperty.call(env, key)) { continue; } for (const existingKey of Object.keys(commandEnv)) { if (existingKey.toLowerCase() === key.toLowerCase()) { delete commandEnv[existingKey]; } } commandPrefix.push(`set "${key}=${env[key] ?? ''}"`); } return { command: [...commandPrefix, command].join(' && '), env: commandEnv, }; } function createSymlinkStandaloneArchive(tmpDir) { const packageRoot = path.join(tmpDir, 'malicious', 'qwen-code'); mkdirSync(path.join(packageRoot, 'bin'), { recursive: true }); mkdirSync(path.join(packageRoot, 'node', 'bin'), { recursive: true }); symlinkSync('/usr/bin/env', path.join(packageRoot, 'bin', 'qwen')); writeFileSync( path.join(packageRoot, 'node', 'bin', 'node'), '#!/usr/bin/env sh\necho 0.0.0-smoke\n', ); chmodSync(path.join(packageRoot, 'node', 'bin', 'node'), 0o755); writeFileSync( path.join(packageRoot, 'manifest.json'), JSON.stringify({ name: '@qwen-code/qwen-code', target: 'linux-x64' }), ); const outDir = path.join(tmpDir, 'out'); mkdirSync(outDir, { recursive: true }); const archive = path.join(outDir, 'qwen-code-linux-x64.tar.gz'); execFileSync( 'tar', ['-czf', archive, '-C', path.dirname(packageRoot), 'qwen-code'], { env: { ...process.env, LC_ALL: 'C' }, stdio: 'ignore', }, ); writeChecksumFile(outDir, path.basename(archive)); return archive; } function createTraversalStandaloneArchive(tmpDir) { const maliciousRoot = path.join(tmpDir, 'malicious'); const packageRoot = path.join(maliciousRoot, 'qwen-code'); mkdirSync(path.join(packageRoot, 'bin'), { recursive: true }); mkdirSync(path.join(packageRoot, 'node', 'bin'), { recursive: true }); writeFileSync( path.join(packageRoot, 'bin', 'qwen'), '#!/usr/bin/env sh\necho 0.0.0-smoke\n', ); chmodSync(path.join(packageRoot, 'bin', 'qwen'), 0o755); writeFileSync( path.join(packageRoot, 'node', 'bin', 'node'), '#!/usr/bin/env sh\necho 0.0.0-smoke\n', ); chmodSync(path.join(packageRoot, 'node', 'bin', 'node'), 0o755); writeFileSync( path.join(packageRoot, 'manifest.json'), JSON.stringify({ name: '@qwen-code/qwen-code', target: 'linux-x64' }), ); writeFileSync(path.join(tmpDir, 'qwen-slip'), 'path traversal\n'); const outDir = path.join(tmpDir, 'out'); mkdirSync(outDir, { recursive: true }); const archive = path.join(outDir, 'qwen-code-linux-x64.zip'); execFileSync('zip', ['-qr', archive, 'qwen-code', '../qwen-slip'], { cwd: maliciousRoot, stdio: 'ignore', }); writeChecksumFile(outDir, path.basename(archive)); return archive; } function writeChecksumFile(outDir, archiveName) { const archive = path.join(outDir, archiveName); const hash = crypto .createHash('sha256') .update(readFileSync(archive)) .digest('hex'); writeFileSync(path.join(outDir, 'SHA256SUMS'), `${hash} ${archiveName}\n`); } function writeStandaloneReleaseAssets(outDir, archiveNames) { mkdirSync(outDir, { recursive: true }); for (const assetName of archiveNames) { writeFileSync(path.join(outDir, assetName), `${assetName}\n`); } writeStandaloneReleaseChecksums(outDir, archiveNames); } function writeStandaloneReleaseChecksums(outDir, archiveNames) { const lines = archiveNames.map((assetName) => { const filePath = path.join(outDir, assetName); const hash = existsSync(filePath) ? crypto.createHash('sha256').update(readFileSync(filePath)).digest('hex') : 'a'.repeat(64); return `${hash} ${assetName}`; }); writeFileSync(path.join(outDir, 'SHA256SUMS'), `${lines.join('\n')}\n`); } function placeholderChecksumContent(archiveNames) { return `${archiveNames .map( (assetName) => `${crypto .createHash('sha256') .update(`${assetName}\n`) .digest('hex')} ${assetName}`, ) .join('\n')}\n`; } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }