feat(installer): add standalone archive installation

This commit is contained in:
yiliang114 2026-04-30 22:04:01 +08:00
parent 65a1503e13
commit eb2a9a8bef
14 changed files with 2387 additions and 884 deletions

View file

@ -379,6 +379,52 @@ jobs:
npm run bundle
npm run prepare:package
- name: 'Build Standalone Archives'
env:
RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}'
run: |-
set -euo pipefail
NODE_VERSION="$(node -p "process.versions.node")"
RUNTIME_DIR="${RUNNER_TEMP}/qwen-node-runtime"
NODE_DIST_URL="https://nodejs.org/dist/v${NODE_VERSION}"
mkdir -p "${RUNTIME_DIR}"
curl -fsSL "${NODE_DIST_URL}/SHASUMS256.txt" -o "${RUNTIME_DIR}/SHASUMS256.txt"
download_node() {
local qwen_target="$1"
local node_target="$2"
local extension="$3"
local archive="${RUNTIME_DIR}/node-v${NODE_VERSION}-${node_target}.${extension}"
local archive_name
archive_name="$(basename "${archive}")"
local checksum_line
local url="${NODE_DIST_URL}/${archive_name}"
echo "Downloading ${url}"
curl -fsSL "${url}" -o "${archive}"
checksum_line="$(awk -v name="${archive_name}" '$2 == name { print }' "${RUNTIME_DIR}/SHASUMS256.txt")"
if [[ -z "${checksum_line}" ]]; then
echo "::error::Node.js SHASUMS256.txt does not list ${archive_name}"
exit 1
fi
printf '%s\n' "${checksum_line}" | (cd "${RUNTIME_DIR}" && sha256sum -c -)
npm run package:standalone -- \
--target "${qwen_target}" \
--node-archive "${archive}" \
--out-dir dist/standalone \
--version "${RELEASE_VERSION}"
}
download_node darwin-arm64 darwin-arm64 tar.gz
download_node darwin-x64 darwin-x64 tar.gz
download_node linux-arm64 linux-arm64 tar.xz
download_node linux-x64 linux-x64 tar.xz
download_node win-x64 win-x64 zip
ls -la dist/standalone
cat dist/standalone/SHA256SUMS
- name: 'Publish @qwen-code/qwen-code'
working-directory: 'dist'
run: |-
@ -411,6 +457,8 @@ jobs:
gh release create "${RELEASE_TAG}" \
dist/cli.js \
dist/standalone/qwen-code-* \
dist/standalone/SHA256SUMS \
--target "${RELEASE_BRANCH}" \
--title "Release ${RELEASE_TAG}" \
--notes-start-tag "${PREVIOUS_RELEASE_TAG}" \

6
.gitignore vendored
View file

@ -36,6 +36,10 @@ CLAUDE.md
!.qwen/skills/**
!.qwen/agents/
!.qwen/agents/**
!.qwen/design/
!.qwen/design/**
!.qwen/e2e-tests/
!.qwen/e2e-tests/**
# OS metadata
.DS_Store
@ -89,4 +93,4 @@ storybook-static
# Dev symlink: qc-helper bundled skill docs (created by scripts/dev.js)
packages/core/src/skills/bundled/qc-helper/docs
tmp/
tmp/

View file

@ -0,0 +1,141 @@
# Standalone Installer Design
## Problem
The current one-line installer installs Qwen Code through npm. That keeps the
script small, but it still requires users to bring a working Node.js and npm
environment. This is fragile for less technical users, and it does not support
offline or controlled enterprise installs well.
Qwen Code already publishes a bundled `dist/cli.js` to GitHub Releases, but the
asset still needs a local Node.js runtime. To remove that dependency, releases
need standalone archives that bundle the Qwen CLI with a private Node.js
runtime and a small launcher.
## Goals
- Prefer standalone release archives when they are available.
- Fall back to npm when no standalone asset exists for the requested platform.
- Keep npm installation available explicitly with `--method npm`.
- Support fully offline installs with `--archive /path/to/archive`.
- Support GitHub Releases and an Aliyun OSS/CDN mirror with the same artifact
names and checksums.
- Avoid modifying npm config, shell profiles, or user PATH permanently.
- Never start `qwen` automatically from the installer.
## Non-Goals
- Build a single native executable in this change.
- Add geolocation-based mirror selection.
- Install Node.js, NVM, or system packages on behalf of the user.
- Solve code signing or notarization in the first implementation.
- Guarantee parity for optional native modules such as `node-pty` and clipboard
packages. The CLI already degrades when these optional modules are absent;
a later release job can add target-specific `node_modules` if that parity is
required.
## Artifact Format
Each release can publish these assets:
- `qwen-code-darwin-arm64.tar.gz`
- `qwen-code-darwin-x64.tar.gz`
- `qwen-code-linux-arm64.tar.gz`
- `qwen-code-linux-x64.tar.gz`
- `qwen-code-win-x64.zip`
- `SHA256SUMS`
The asset names intentionally do not include the version. This allows the
installer to use GitHub's `releases/latest/download/<asset>` URL without an API
call. Versioned installation is still supported by switching the base URL to
`releases/download/vX.Y.Z`.
Archive layout:
```text
qwen-code/
bin/qwen
bin/qwen.cmd
lib/cli.js
node/...
package.json
README.md
LICENSE
manifest.json
```
The Unix launcher executes `node/bin/node ../lib/cli.js`. The Windows launcher
executes `node/node.exe ..\lib\cli.js`. Bundling the full Node distribution is
larger than a single executable, but it is predictable and works with the
existing ESM bundle without requiring a user-managed Node.js installation.
## Installer Behavior
`--method detect` is the default:
1. If `--archive` is provided, install that local archive.
2. Detect OS and architecture.
3. Build an archive URL from the selected mirror/base URL.
4. If the archive exists, download it, verify `SHA256SUMS`, extract it into the
user install directory, and expose `qwen`.
5. If the archive does not exist, fall back to npm.
`--method standalone` follows the same standalone path, but a missing or failed
standalone asset is fatal.
`--method npm` skips standalone logic and runs npm installation after checking
that Node.js 20+ and npm are available.
## Install Locations
Unix:
- Runtime: `$HOME/.local/lib/qwen-code`
- Command shim: `$HOME/.local/bin/qwen`
Windows:
- Runtime: `%LOCALAPPDATA%\qwen-code\qwen-code`
- Command shim: `%LOCALAPPDATA%\qwen-code\bin\qwen.cmd`
The installer may add the command directory to the current process PATH for
verification, but it does not write shell profiles or persistent environment
variables. If the command directory is not on PATH, the installer prints the
exact directory to add.
## Distribution Sources
GitHub is the canonical source:
```text
https://github.com/QwenLM/qwen-code/releases/latest/download
https://github.com/QwenLM/qwen-code/releases/download/vX.Y.Z
```
Aliyun OSS/CDN is a mirror:
```text
https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/releases/qwen-code/latest
https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/releases/qwen-code/vX.Y.Z
```
All mirrors must serve byte-identical artifacts and the same `SHA256SUMS`.
## Safety
- Remote standalone installs require checksum verification.
- Local archive installs do not require network access.
- The installer only deletes temporary extraction directories and the previous
managed standalone install directory.
- npm fallback does not change npm prefix, npmrc, or PATH.
## Verification Strategy
- Static tests ensure the installer keeps the expected methods and does not
reintroduce Node/NVM installation or automatic `qwen` startup.
- Packaging tests can run against a fake target and fake Node distribution.
- Shell smoke tests run installer branches with fake `curl`, `tar`, `npm`,
`node`, and `qwen`.
- GitHub Actions should later run Linux, macOS, and Windows installer smoke
tests with locally generated archives before enabling standalone as the
public default.

View file

@ -0,0 +1,124 @@
# Standalone Installer Implementation Plan
**Goal:** Add code-server-style standalone archive distribution with npm fallback.
**Architecture:** Release builds produce per-platform archives that bundle
`dist/cli.js`, required runtime assets, and a private Node.js runtime. The
installer defaults to `detect`, installs a standalone archive when available,
and falls back to npm otherwise.
**Tech Stack:** Bash, Windows batch, Node.js release scripting, GitHub Actions,
Vitest static/smoke tests.
## Task 1: Installer Contract Tests
**Files:**
- Modify: `scripts/tests/install-script.test.js`
**Steps:**
1. Add tests asserting the Unix installer exposes `--method`, `--mirror`,
`--base-url`, `--archive`, standalone install functions, checksum
verification, and npm fallback.
2. Add tests asserting the Windows installer exposes the same options and uses
PowerShell/CertUtil for archive install and checksum verification.
3. Run `npm run test:scripts`.
4. Confirm the new tests fail before implementation.
## Task 2: Standalone Package Script
**Files:**
- Create: `scripts/create-standalone-package.js`
- Modify: `package.json`
**Steps:**
1. Add a Node.js script that accepts `--target`, `--node-archive`,
`--out-dir`, and optional `--version`.
2. Require `dist/cli.js`, `dist/vendor`, `README.md`, and `LICENSE`.
3. Extract a Node.js distribution archive into a staging directory.
4. Create `qwen-code/bin/qwen`, `qwen-code/bin/qwen.cmd`,
`qwen-code/lib/cli.js`, copied runtime assets, and `manifest.json`.
5. Emit `qwen-code-<target>.tar.gz` for Unix targets and
`qwen-code-<target>.zip` for Windows targets.
6. Write/update `SHA256SUMS`.
7. Add `npm run package:standalone`.
8. Add focused script tests where practical.
## Task 3: Unix Installer Standalone Flow
**Files:**
- Modify: `scripts/installation/install-qwen-with-source.sh`
**Steps:**
1. Add argument parsing for `--method`, `--mirror`, `--base-url`, `--archive`,
and `--version`.
2. Add target detection for supported OS/arch combinations.
3. Add URL construction for GitHub and Aliyun mirrors.
4. Add archive availability check for detect mode.
5. Add download, checksum verification, extraction, and shim creation.
6. Keep npm installation as fallback and as explicit `--method npm`.
7. Keep source tracking and final instructions.
## Task 4: Windows Installer Standalone Flow
**Files:**
- Modify: `scripts/installation/install-qwen-with-source.bat`
**Steps:**
1. Add argument parsing for `--method`, `--mirror`, `--base-url`, `--archive`,
and `--version`.
2. Add target detection for `win-x64`.
3. Add archive download with PowerShell.
4. Add checksum verification with `certutil`.
5. Add archive extraction with PowerShell `Expand-Archive`.
6. Install to `%LOCALAPPDATA%\qwen-code\qwen-code` and expose
`%LOCALAPPDATA%\qwen-code\bin\qwen.cmd`.
7. Keep npm fallback and source tracking.
## Task 5: Release Workflow
**Files:**
- Modify: `.github/workflows/release.yml`
**Steps:**
1. After `npm run prepare:package`, download supported Node.js runtime
archives.
2. Run `npm run package:standalone -- --target ...` for each supported target.
3. Upload `dist/standalone/qwen-code-*` and `dist/standalone/SHA256SUMS` to the
GitHub Release alongside `dist/cli.js`.
## Task 6: Documentation
**Files:**
- Modify: `README.md`
- Modify: `docs/users/overview.md`
- Modify: `docs/users/quickstart.md`
- Modify: `scripts/installation/INSTALLATION_GUIDE.md`
- Create: `.qwen/e2e-tests/2026-04-30-standalone-installer-test-plan.md`
**Steps:**
1. Document install methods and mirror choices.
2. Document offline archive installation.
3. Document release artifact names.
4. Document platform verification plan.
## Task 7: Verification
**Commands:**
- `npm run test:scripts`
- `npx prettier --check README.md docs/users/quickstart.md docs/users/overview.md scripts/installation/INSTALLATION_GUIDE.md .qwen/design/2026-04-30-standalone-installer-design.md .qwen/design/2026-04-30-standalone-installer-plan.md .qwen/e2e-tests/2026-04-30-standalone-installer-test-plan.md scripts/tests/install-script.test.js`
- `bash -n scripts/installation/install-qwen-with-source.sh`
- `git diff --check`
- Local fake-runtime installer smoke for npm and standalone paths.

View file

@ -0,0 +1,87 @@
# Standalone Installer Test Plan
## Scope
This plan verifies the one-line installer after standalone archive support is
added. It covers installer behavior, artifact packaging, and fallback behavior
without requiring real global npm writes.
## Local Smoke Matrix
Run from the repository root after `npm run bundle && npm run prepare:package`.
### Unix Standalone Archive
1. Build a standalone archive for the current target with a local Node.js
archive.
2. Create a temporary `HOME`.
3. Run:
```bash
HOME="$tmp_home" bash scripts/installation/install-qwen-with-source.sh \
--method standalone \
--archive dist/standalone/qwen-code-<target>.tar.gz \
--source github
```
4. Expected:
- `$tmp_home/.local/lib/qwen-code` exists.
- `$tmp_home/.local/bin/qwen` exists and is executable.
- `$tmp_home/.qwen/source.json` contains `{"source":"github"}`.
- Installer does not write `.bashrc`, `.zshrc`, `.npmrc`, or npm prefix.
### Unix npm Fallback
1. Put fake `node`, `npm`, and `qwen` commands in a temporary PATH.
2. Run detect mode with a fake base URL whose archive does not exist.
3. Expected:
- npm is invoked with `install -g @qwen-code/qwen-code@latest`.
- `qwen` is not executed interactively.
### Unix Standalone Failure
1. Run `--method standalone` with a fake base URL whose archive does not exist.
2. Expected:
- installer exits non-zero.
- npm is not invoked.
### Windows Standalone Archive
Run on `windows-latest` or a Windows VM:
```cmd
set USERPROFILE=%TEMP%\qwen-user
set LOCALAPPDATA=%TEMP%\qwen-local
scripts\installation\install-qwen-with-source.bat --method standalone --archive dist\standalone\qwen-code-win-x64.zip --source github
```
Expected:
- `%LOCALAPPDATA%\qwen-code\qwen-code` exists.
- `%LOCALAPPDATA%\qwen-code\bin\qwen.cmd` exists.
- `%USERPROFILE%\.qwen\source.json` exists.
- The script does not require Administrator.
## CI Matrix
- `ubuntu-latest`: package + install Linux x64 archive.
- `macos-latest`: package + install Darwin arm64/x64 depending on runner.
- `windows-latest`: package + install Windows x64 archive.
## Manual Release Verification
For a release candidate:
1. Download `SHA256SUMS` and all archives from GitHub Release.
2. Verify checksums locally.
3. Sync the same files to OSS/CDN.
4. Download one archive from GitHub and one from OSS/CDN.
5. Confirm byte-identical checksums.
6. Run installer with:
```bash
--mirror github --method standalone
--mirror aliyun --method standalone
--method npm
--archive /path/to/archive
```

View file

@ -43,13 +43,18 @@ Qwen Code is an open-source AI agent for the terminal, optimized for Qwen series
### Quick Install (Recommended)
The installer uses a standalone Qwen Code archive when one is available for
your platform, so the default path does not require a preinstalled Node.js
runtime. If a standalone archive is not available, it falls back to npm and then
requires Node.js 20 or later with npm on PATH.
#### Linux / macOS
```bash
bash -c "$(curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh)"
```
#### Windows (Run as Administrator)
#### Windows
Works in both Command Prompt and PowerShell:
@ -57,13 +62,17 @@ Works in both Command Prompt and PowerShell:
powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"
```
> **Note**: It's recommended to restart your terminal after installation to ensure environment variables take effect.
> **Note**: It's recommended to restart your terminal after installation if
> `qwen` is not immediately available on PATH. For offline installation, download
> a release archive such as `qwen-code-linux-x64.tar.gz` or
> `qwen-code-win-x64.zip`, then run the installer with `--archive PATH`.
### Manual Installation
#### Prerequisites
Make sure you have Node.js 20 or later installed. Download it from [nodejs.org](https://nodejs.org/en/download).
Manual npm installation requires Node.js 20 or later. Download it from
[nodejs.org](https://nodejs.org/en/download).
#### NPM

View file

@ -9,13 +9,17 @@
### Install Qwen Code:
The recommended installer uses a standalone archive when one is available for
your platform. If it falls back to npm, Node.js 20 or later with npm must be
available on PATH.
**Linux / macOS**
```sh
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash
```
**Windows (Run as Administrator)**
**Windows**
```cmd
powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"
@ -23,7 +27,11 @@ powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou
> [!note]
>
> It's recommended to restart your terminal after installation to ensure environment variables take effect. If the installation fails, please refer to [Manual Installation](./quickstart#manual-installation) in the Quickstart guide.
> It's recommended to restart your terminal after installation if `qwen` is not
> immediately available on PATH. If the installation fails, please refer to
> [Manual Installation](./quickstart#manual-installation) in the Quickstart
> guide. For offline installation, download a release archive and run the
> installer with `--archive PATH`.
### Start using Qwen Code:

View file

@ -12,6 +12,10 @@ Make sure you have:
- A code project to work with
- An API key from Alibaba Cloud Model Studio ([Beijing](https://bailian.console.aliyun.com/) / [intl](https://modelstudio.console.alibabacloud.com/)), or an Alibaba Cloud Coding Plan ([Beijing](https://bailian.console.aliyun.com/cn-beijing/?tab=coding-plan#/efm/coding-plan-index) / [intl](https://modelstudio.console.alibabacloud.com/?tab=coding-plan#/efm/coding-plan-index)) subscription
The recommended installer uses a standalone archive when one is available for
your platform. If it falls back to npm, you will need Node.js 20 or later with
npm available on PATH.
## Step 1: Install Qwen Code
To install Qwen Code, use one of the following methods:
@ -24,7 +28,7 @@ To install Qwen Code, use one of the following methods:
curl -fsSL https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.sh | bash
```
**Windows (Run as Administrator)**
**Windows**
```cmd
powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen.bat' -OutFile (Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path $env:TEMP 'install-qwen.bat')"
@ -32,13 +36,17 @@ powershell -Command "Invoke-WebRequest 'https://qwen-code-assets.oss-cn-hangzhou
> [!note]
>
> It's recommended to restart your terminal after installation to ensure environment variables take effect.
> It's recommended to restart your terminal after installation if `qwen` is not
> immediately available on PATH. For offline installation, download a release
> archive such as `qwen-code-linux-x64.tar.gz` or `qwen-code-win-x64.zip`, then
> run the installer with `--archive PATH`.
### Manual Installation
**Prerequisites**
Make sure you have Node.js 20 or later installed. Download it from [nodejs.org](https://nodejs.org/en/download).
Manual npm installation requires Node.js 20 or later. Download it from
[nodejs.org](https://nodejs.org/en/download).
**NPM**

View file

@ -64,6 +64,7 @@
"preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run build && npm run typecheck && npm run test:ci",
"prepare": "husky && npm run build && npm run bundle",
"prepare:package": "node scripts/prepare-package.js",
"package:standalone": "node scripts/create-standalone-package.js",
"release:version": "node scripts/version.js",
"telemetry": "node scripts/telemetry.js",
"check:lockfile": "node scripts/check-lockfile.js",

View file

@ -0,0 +1,376 @@
#!/usr/bin/env node
/**
* @license
* Copyright 2025 Qwen
* SPDX-License-Identifier: Apache-2.0
*/
import { execFileSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const rootDir = path.resolve(__dirname, '..');
const distDir = path.join(rootDir, 'dist');
const TARGETS = new Map([
[
'darwin-arm64',
{ outputExtension: 'tar.gz', nodeExecutable: ['bin', 'node'] },
],
[
'darwin-x64',
{ outputExtension: 'tar.gz', nodeExecutable: ['bin', 'node'] },
],
[
'linux-arm64',
{ outputExtension: 'tar.gz', nodeExecutable: ['bin', 'node'] },
],
['linux-x64', { outputExtension: 'tar.gz', nodeExecutable: ['bin', 'node'] }],
['win-x64', { outputExtension: 'zip', nodeExecutable: ['node.exe'] }],
]);
const DIST_REQUIRED_PATHS = ['cli.js', 'vendor', 'bundled/qc-helper/docs'];
const ROOT_REQUIRED_PATHS = ['README.md', 'LICENSE'];
main();
function main() {
const args = parseArgs(process.argv.slice(2));
if (args.help) {
printUsage();
return;
}
const target = args.target;
if (!target || !TARGETS.has(target)) {
fail(`--target must be one of: ${Array.from(TARGETS.keys()).join(', ')}`);
}
if (!args.nodeArchive) {
fail('--node-archive is required');
}
const nodeArchive = path.resolve(args.nodeArchive);
if (!fs.existsSync(nodeArchive)) {
fail(`Node.js archive not found: ${nodeArchive}`);
}
assertRequiredInputs();
const version = args.version || readPackageVersion();
const outDir = path.resolve(args.outDir || path.join(distDir, 'standalone'));
fs.mkdirSync(outDir, { recursive: true });
const targetConfig = TARGETS.get(target);
const outputName = `qwen-code-${target}.${targetConfig.outputExtension}`;
const outputPath = path.join(outDir, outputName);
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'qwen-standalone-'));
try {
const packageRoot = path.join(tempRoot, 'qwen-code');
const runtimeExtractDir = path.join(tempRoot, 'runtime');
fs.mkdirSync(packageRoot, { recursive: true });
fs.mkdirSync(runtimeExtractDir, { recursive: true });
copyRuntimeAssets(packageRoot);
extractNodeArchive(nodeArchive, runtimeExtractDir);
const nodeDir = path.join(packageRoot, 'node');
copyExtractedNode(runtimeExtractDir, nodeDir);
validateNodeRuntime(target, nodeDir);
writeShims(packageRoot);
writeManifest(packageRoot, {
version,
target,
nodeArchive: path.basename(nodeArchive),
});
if (fs.existsSync(outputPath)) {
fs.rmSync(outputPath, { force: true });
}
createArchive(targetConfig.outputExtension, outputPath, tempRoot);
writeSha256Sums(outDir);
console.log(`Created ${path.relative(rootDir, outputPath)}`);
console.log(
`Updated ${path.relative(rootDir, path.join(outDir, 'SHA256SUMS'))}`,
);
} finally {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
}
function parseArgs(argv) {
const args = {
help: false,
outDir: undefined,
nodeArchive: undefined,
target: undefined,
version: undefined,
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
switch (arg) {
case '--help':
case '-h':
args.help = true;
break;
case '--target':
args.target = readOptionValue(argv, index, arg);
index += 1;
break;
case '--node-archive':
args.nodeArchive = readOptionValue(argv, index, arg);
index += 1;
break;
case '--out-dir':
args.outDir = readOptionValue(argv, index, arg);
index += 1;
break;
case '--version':
args.version = readOptionValue(argv, index, arg);
index += 1;
break;
default:
fail(`Unknown option: ${arg}`);
}
}
return args;
}
function readOptionValue(argv, index, optionName) {
const value = argv[index + 1];
if (!value || value.startsWith('-')) {
fail(`${optionName} requires a value`);
}
return value;
}
function printUsage() {
console.log(`Qwen Code standalone package builder
Usage:
npm run package:standalone -- --target TARGET --node-archive PATH [OPTIONS]
Options:
--target TARGET One of: ${Array.from(TARGETS.keys()).join(', ')}
--node-archive PATH Downloaded Node.js runtime archive.
--out-dir DIR Output directory. Defaults to dist/standalone.
--version VERSION Qwen Code version. Defaults to package.json version.
-h, --help Show this help message.`);
}
function assertRequiredInputs() {
if (!fs.existsSync(distDir)) {
fail('dist/ directory not found. Run "npm run bundle" first.');
}
for (const relativePath of DIST_REQUIRED_PATHS) {
const fullPath = path.join(distDir, relativePath);
if (!fs.existsSync(fullPath)) {
fail(`Required dist asset missing: ${fullPath}`);
}
}
for (const relativePath of ROOT_REQUIRED_PATHS) {
const fullPath = path.join(rootDir, relativePath);
if (!fs.existsSync(fullPath)) {
fail(`Required repository file missing: ${fullPath}`);
}
}
}
function readPackageVersion() {
const packageJsonPath = path.join(rootDir, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return packageJson.version;
}
function copyRuntimeAssets(packageRoot) {
const libDir = path.join(packageRoot, 'lib');
fs.mkdirSync(libDir, { recursive: true });
for (const entry of fs.readdirSync(distDir)) {
if (entry === 'standalone') {
continue;
}
fs.cpSync(path.join(distDir, entry), path.join(libDir, entry), {
recursive: true,
verbatimSymlinks: true,
});
}
for (const fileName of ROOT_REQUIRED_PATHS) {
fs.copyFileSync(
path.join(rootDir, fileName),
path.join(packageRoot, fileName),
);
}
const distPackageJson = path.join(distDir, 'package.json');
if (fs.existsSync(distPackageJson)) {
fs.copyFileSync(distPackageJson, path.join(packageRoot, 'package.json'));
} else {
fs.copyFileSync(
path.join(rootDir, 'package.json'),
path.join(packageRoot, 'package.json'),
);
}
}
function extractNodeArchive(nodeArchive, extractDir) {
if (nodeArchive.endsWith('.zip')) {
run('unzip', ['-q', nodeArchive, '-d', extractDir]);
return;
}
if (
nodeArchive.endsWith('.tar.gz') ||
nodeArchive.endsWith('.tgz') ||
nodeArchive.endsWith('.tar.xz')
) {
run('tar', ['-xf', nodeArchive, '-C', extractDir]);
return;
}
fail(
`Unsupported Node.js archive format: ${nodeArchive}. Expected .zip, .tar.gz, .tgz, or .tar.xz.`,
);
}
function copyExtractedNode(extractDir, nodeDir) {
const entries = fs
.readdirSync(extractDir)
.filter((entry) => entry !== '.DS_Store');
if (entries.length === 0) {
fail('Node.js archive did not contain any files.');
}
const sourceRoot =
entries.length === 1 &&
fs.statSync(path.join(extractDir, entries[0])).isDirectory()
? path.join(extractDir, entries[0])
: extractDir;
fs.cpSync(sourceRoot, nodeDir, {
recursive: true,
verbatimSymlinks: true,
});
}
function validateNodeRuntime(target, nodeDir) {
const targetConfig = TARGETS.get(target);
const executablePath = path.join(nodeDir, ...targetConfig.nodeExecutable);
const displayPath = targetConfig.nodeExecutable.join('/');
if (!fs.existsSync(executablePath)) {
fail(`Node.js runtime for ${target} must contain ${displayPath}.`);
}
if (target !== 'win-x64') {
const mode = fs.statSync(executablePath).mode;
if ((mode & 0o111) === 0) {
fail(
`Node.js runtime for ${target} must provide executable ${displayPath}.`,
);
}
}
}
function writeShims(packageRoot) {
const binDir = path.join(packageRoot, 'bin');
fs.mkdirSync(binDir, { recursive: true });
const unixShim = `#!/usr/bin/env sh
set -e
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
exec "$ROOT/node/bin/node" "$ROOT/lib/cli.js" "$@"
`;
const unixShimPath = path.join(binDir, 'qwen');
fs.writeFileSync(unixShimPath, unixShim);
fs.chmodSync(unixShimPath, 0o755);
const windowsShim = `@echo off
setlocal
set "ROOT=%~dp0.."
"%ROOT%\\node\\node.exe" "%ROOT%\\lib\\cli.js" %*
`;
fs.writeFileSync(path.join(binDir, 'qwen.cmd'), windowsShim);
}
function writeManifest(packageRoot, manifest) {
const manifestPath = path.join(packageRoot, 'manifest.json');
fs.writeFileSync(
manifestPath,
JSON.stringify(
{
name: '@qwen-code/qwen-code',
version: manifest.version,
target: manifest.target,
nodeArchive: manifest.nodeArchive,
createdAt: new Date().toISOString(),
},
null,
2,
) + '\n',
);
}
function createArchive(outputExtension, outputPath, cwd) {
if (outputExtension === 'zip') {
run('zip', ['-qr', outputPath, 'qwen-code'], { cwd });
return;
}
run('tar', ['-czf', outputPath, '-C', cwd, 'qwen-code']);
}
function writeSha256Sums(outDir) {
const entries = fs
.readdirSync(outDir)
.filter(
(entry) =>
entry.startsWith('qwen-code-') &&
(entry.endsWith('.tar.gz') || entry.endsWith('.zip')),
)
.sort();
const lines = entries.map((entry) => {
const filePath = path.join(outDir, entry);
const hash = crypto
.createHash('sha256')
.update(fs.readFileSync(filePath))
.digest('hex');
return `${hash} ${entry}`;
});
fs.writeFileSync(path.join(outDir, 'SHA256SUMS'), `${lines.join('\n')}\n`);
}
function run(command, args, options = {}) {
try {
execFileSync(command, args, {
stdio: 'inherit',
...options,
});
} catch (error) {
const detail =
error && typeof error === 'object' && 'message' in error
? `: ${error.message}`
: '';
fail(`Command failed: ${command} ${args.join(' ')}${detail}`);
}
}
function fail(message) {
console.error(`Error: ${message}`);
process.exit(1);
}

View file

@ -1,148 +1,177 @@
# Installation Guide for Qwen Code with Source Tracking
This guide describes how to install Node.js and Qwen Code with source information tracking.
This guide describes the source-tracking installation scripts for Qwen Code.
The scripts prefer standalone release archives and can fall back to npm when a
standalone archive is not available.
## Overview
The installation scripts automate the process of installing Node.js (if not present or below version 20) and Qwen Code, while capturing and storing the installation source information for analytics and tracking purposes.
The installers are intentionally lightweight:
- They try a standalone archive first by default.
- They do not install Node.js, NVM, or any other Node version manager.
- They do not edit npm config or shell profiles.
- They do not start `qwen` automatically after installation.
- They store source information in `~/.qwen/source.json` or
`%USERPROFILE%\.qwen\source.json` when `--source` is provided.
Standalone archives include a private Node.js runtime, so users do not need a
local Node.js installation on the standalone path. Node.js 20 or newer and npm
are only required when the installer falls back to npm or when
`--method npm` is used.
## Installation Scripts
We provide platform-specific installation scripts:
- Linux/macOS: `install-qwen-with-source.sh`
- Windows: `install-qwen-with-source.bat`
- **Linux/macOS**: `install-qwen-with-source.sh`
- **Windows**: `install-qwen-with-source.bat`
## Release Artifacts
## Linux/macOS Installation
GitHub releases publish these standalone archives:
### Script: install-qwen-with-source.sh
- `qwen-code-darwin-arm64.tar.gz`
- `qwen-code-darwin-x64.tar.gz`
- `qwen-code-linux-arm64.tar.gz`
- `qwen-code-linux-x64.tar.gz`
- `qwen-code-win-x64.zip`
- `SHA256SUMS`
#### Features:
Archive layout:
- Checks for existing Node.js installation and version
- Installs Node.js 20+ if needed using NVM
- Installs Qwen Code globally with source information
- Stores the source information in `~/.qwen/source.json`
```text
qwen-code/
bin/qwen
bin/qwen.cmd
lib/cli.js
node/
package.json
README.md
LICENSE
manifest.json
```
#### Usage:
## Install Methods
The default method is `detect`:
1. Detect the current platform.
2. Try to download and install the matching standalone archive.
3. Verify the archive with `SHA256SUMS` when available.
4. Fall back to npm if the standalone archive is not available.
You can force a method:
```bash
# Install with a specific source
sh install-qwen-with-source.sh --source github
# Install with internal source
sh install-qwen-with-source.sh -s internal
# Show help
sh install-qwen-with-source.sh --help
bash install-qwen-with-source.sh --method standalone
bash install-qwen-with-source.sh --method npm
```
#### Supported Source Values:
```bat
install-qwen-with-source.bat --method standalone
install-qwen-with-source.bat --method npm
```
- `github` - Installed from GitHub repository
- `npm` - Installed from npm registry
- `internal` - Internal installation
- `local-build` - Local build installation
## Optional Native Modules
#### How it Works:
The standalone archives bundle Qwen Code and a private Node.js runtime. They do
not currently install npm optional native modules such as `node-pty` and
`@teddyzhu/clipboard`. Qwen Code is designed to degrade when these optional
modules are absent, but terminal pty behavior and clipboard image support may
not be identical to an npm installation.
1. The script accepts a `--source` parameter to specify where Qwen Code is being installed from
2. It installs Node.js if needed
3. It installs Qwen Code globally
4. It creates `~/.qwen/source.json` with the specified source information
Use `--method npm` if you specifically need npm to resolve optional native
modules for the current machine.
#### Important Notes:
⚠️ **After installation, you need to restart your terminal or run:**
## Linux/macOS Usage
```bash
source ~/.bashrc # For bash users
# or
source ~/.zshrc # For zsh users
# Default: standalone archive with npm fallback
bash install-qwen-with-source.sh
# Record a source value
bash install-qwen-with-source.sh --source github
# Use npm explicitly
bash install-qwen-with-source.sh --method npm --registry https://registry.npmjs.org
# Use the Aliyun standalone mirror
bash install-qwen-with-source.sh --mirror aliyun
# Install an offline archive
bash install-qwen-with-source.sh --archive ./qwen-code-linux-x64.tar.gz
```
This is required to load the newly installed Node.js and Qwen Code into your PATH.
Standalone installs to:
#### Prerequisites:
- Runtime: `~/.local/lib/qwen-code`
- Shim: `~/.local/bin/qwen`
- curl (for NVM installation and script download)
- bash-compatible shell
Override with `QWEN_INSTALL_ROOT`, `QWEN_INSTALL_LIB_PARENT`,
`QWEN_INSTALL_LIB_DIR`, or `QWEN_INSTALL_BIN_DIR` when needed.
## Windows Installation
## Windows Usage
### Script: install-qwen-with-source.bat
```bat
REM Default: standalone archive with npm fallback
install-qwen-with-source.bat
#### Features:
REM Record a source value
install-qwen-with-source.bat --source github
- Checks for existing Node.js installation and version (requires version 18+)
- Automatically downloads and installs Node.js 24 LTS if not present or version is too low
- Installs Qwen Code globally with source information
- Stores the source information in `%USERPROFILE%\.qwen\source.json`
REM Use npm explicitly
install-qwen-with-source.bat --method npm --registry https://registry.npmjs.org
#### Prerequisites:
REM Use the Aliyun standalone mirror
install-qwen-with-source.bat --mirror aliyun
- **PowerShell (Administrator)**: The script must be run in PowerShell with Administrator privileges
- Internet connection for downloading Node.js and Qwen Code
#### Usage:
> ⚠️ **Important**: You must run PowerShell as Administrator to install Node.js and global npm packages.
**Step 1**: Open PowerShell as Administrator
- Right-click on PowerShell and select "Run as Administrator"
- Or press `Win + X` and select "Windows PowerShell (Admin)"
**Step 2**: Navigate to the script directory and run:
```powershell
# Install with a specific source using --source parameter
./install-qwen-with-source.bat --source github
# Install with short parameter
./install-qwen-with-source.bat -s internal
# Use default source (unknown)
./install-qwen-with-source.bat
REM Install an offline archive
install-qwen-with-source.bat --archive qwen-code-win-x64.zip
```
#### Supported Source Values:
Standalone installs to:
- `github` - Installed from GitHub repository
- `npm` - Installed from npm registry
- `internal` - Internal installation
- `local-build` - Local build installation
- Runtime: `%LOCALAPPDATA%\qwen-code\qwen-code`
- Shim: `%LOCALAPPDATA%\qwen-code\bin\qwen.cmd`
#### How it Works:
Restart the terminal if `qwen` is not immediately available on PATH.
1. The script accepts a `--source` or `-s` parameter to specify where Qwen Code is being installed from
2. It checks if Node.js is already installed and if the version is 18 or higher
3. If Node.js is not installed or version is too low, it automatically downloads and installs Node.js 24 LTS
4. It installs Qwen Code globally using npm
5. It creates `%USERPROFILE%\.qwen\source.json` with the specified source information
## Mirrors and Overrides
#### Why Administrator Privileges are Required:
Options:
- Installing Node.js requires writing to `C:\Program Files\nodejs`
- Installing global npm packages requires elevated permissions
- Modifying system PATH environment variables requires Administrator access
- `--method detect|standalone|npm`
- `--mirror github|aliyun`
- `--base-url URL`
- `--archive PATH`
- `--version VERSION`
- `--registry REGISTRY`
- `--source SOURCE`
## Installation Source Feature
Environment variables:
### Overview
- `QWEN_INSTALL_METHOD`
- `QWEN_INSTALL_MIRROR`
- `QWEN_INSTALL_BASE_URL`
- `QWEN_INSTALL_ARCHIVE`
- `QWEN_INSTALL_VERSION`
- `QWEN_NPM_REGISTRY`
This feature implements the ability to capture and store the installation source of the Qwen Code package. The source information is used for analytics and tracking purposes.
Use `--base-url` for private mirrors. The URL must contain
`qwen-code-<target>` archives and `SHA256SUMS` in the same directory.
### Storage Location
## Supported Source Values
The installation source is stored in a separate file at:
The source value may only contain letters, numbers, dot, underscore, and dash.
Common values are:
- **Unix/Linux/macOS**: `~/.qwen/source.json`
- **Windows**: `%USERPROFILE%\.qwen\source.json` (equivalent to `C:\Users\{username}\.qwen\source.json`)
- `github`
- `npm`
- `internal`
- `local-build`
### File Format
## Source Tracking
The `source.json` file contains:
When `--source` or `-s` is provided, the installer writes:
```json
{
@ -150,53 +179,23 @@ The `source.json` file contains:
}
```
### How the Source Information is Used
Locations:
1. **Telemetry Tracking**: The source information is included in RUM (Real User Monitoring) telemetry logs
2. **Analytics**: Helps understand how users are discovering and installing Qwen Code
3. **Distribution Analysis**: Tracks which distribution channels are most popular
- Linux/macOS: `~/.qwen/source.json`
- Windows: `%USERPROFILE%\.qwen\source.json`
### Technical Implementation
The telemetry logger reads this file when available. Missing, invalid, or
unreadable source files are ignored.
- The source information is stored as a separate JSON file
- The `QwenLogger` class reads this file during telemetry initialization
- The source is included in the `app.channel` field of the RUM payload
- The implementation gracefully handles missing files, unknown values, and parsing errors
## Manual Installation
### Verification
After installation and restarting your terminal (or sourcing your shell configuration), you can verify the source information:
**Linux/macOS:**
```bash
cat ~/.qwen/source.json
```
**Windows:**
```cmd
type %USERPROFILE%\.qwen\source.json
```
## Manual Installation (Without Source Tracking)
If you prefer not to use the installation scripts or don't want source tracking:
### Prerequisites
```bash
# Node.js 20+
curl -qL https://www.npmjs.com/install.sh | sh
```
### NPM Installation
If source tracking is not needed and Node.js 20 or newer is already available:
```bash
npm install -g @qwen-code/qwen-code@latest
```
### Homebrew (macOS, Linux)
Homebrew users can also install Qwen Code with:
```bash
brew install qwen-code
@ -204,47 +203,47 @@ brew install qwen-code
## Troubleshooting
### Script Execution Issues
### Standalone Archive Missing
**Linux/macOS:**
In `detect` mode, the installer falls back to npm. In `standalone` mode, install
fails so that automation can detect the missing artifact.
### Node.js Missing or Too Old
This only blocks npm installation. Install or activate Node.js 20 or newer, then
rerun the installer with `--method npm` or let `detect` fall back again.
### npm Missing
Install a Node.js distribution that includes npm, then rerun the installer.
### Permission Errors During npm Install
The installers do not rewrite npm prefix settings. If global npm installation
fails with a permission error, fix the npm global install location or use a
user-owned Node.js installation, then rerun:
```bash
# Run with sh
sh install-qwen-with-source.sh --source github
npm install -g @qwen-code/qwen-code@latest --registry https://registry.npmmirror.com
```
**Windows (PowerShell as Administrator):**
### qwen Is Not on PATH After Installation
```powershell
# Run the script with --source parameter
./install-qwen-with-source.bat --source github
Restart the terminal first. For standalone installs, add the shim directory:
# Or with short parameter
./install-qwen-with-source.bat -s github
```bash
export PATH="$HOME/.local/bin:$PATH"
```
### Node.js Installation Issues
For npm installs, add npm's global binary directory. On Linux/macOS this is
usually:
**Linux/macOS:**
```bash
export PATH="$(npm prefix -g)/bin:$PATH"
```
- Ensure NVM is installed: `curl -o- https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install_nvm.sh | bash`
- Restart your terminal or run: `source ~/.bashrc`
On Windows standalone installs, add this directory to PATH:
**Windows:**
- Install NVM for Windows from: https://github.com/coreybutler/nvm-windows/releases
- After installation, run the script again
### Permission Issues
You may need administrative privileges for global npm installation:
- **Linux/macOS**: Use `sudo` with npm
- **Windows**: Run PowerShell as Administrator (required for Node.js installation and global npm packages)
## Notes
- The scripts require internet access to download Node.js and Qwen Code
- Administrative privileges may be required for global npm installation
- The installation source is stored locally and used for tracking purposes only
- If the source file is missing or invalid, the application continues to work normally
```bat
%LOCALAPPDATA%\qwen-code\bin
```

View file

@ -1,304 +1,596 @@
@echo off
REM Script to install Node.js and Qwen Code with source information
REM This script handles the installation process and sets the installation source
REM
REM Usage: install-qwen-with-source.bat --source <source>
REM install-qwen-with-source.bat -s <source>
REM
REM Qwen Code Installation Script
REM Installs Qwen Code from a standalone archive when available, with npm fallback.
REM This script intentionally does not install Node.js or change npm config.
setlocal enabledelayedexpansion
set "SOURCE=unknown"
set "METHOD=%QWEN_INSTALL_METHOD%"
set "MIRROR=github"
if not "%QWEN_INSTALL_MIRROR%"=="" set "MIRROR=%QWEN_INSTALL_MIRROR%"
set "BASE_URL=%QWEN_INSTALL_BASE_URL%"
set "ARCHIVE_PATH=%QWEN_INSTALL_ARCHIVE%"
set "VERSION=latest"
if not "%QWEN_INSTALL_VERSION%"=="" set "VERSION=%QWEN_INSTALL_VERSION%"
set "NPM_REGISTRY=https://registry.npmmirror.com"
if not "%QWEN_NPM_REGISTRY%"=="" set "NPM_REGISTRY=%QWEN_NPM_REGISTRY%"
set "INSTALL_BASE=%LOCALAPPDATA%\qwen-code"
set "INSTALL_DIR=%INSTALL_BASE%\qwen-code"
set "INSTALL_BIN_DIR=%INSTALL_BASE%\bin"
REM Parse command line arguments
:parse_args
if "%~1"=="" goto end_parse
if /i "%~1"=="--source" (
if not "%~2"=="" (
set "SOURCE=%~2"
shift
shift
goto parse_args
if "%~2"=="" (
echo ERROR: --source requires a value
exit /b 1
)
set "SOURCE=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="-s" (
if not "%~2"=="" (
set "SOURCE=%~2"
shift
shift
goto parse_args
if "%~2"=="" (
echo ERROR: -s requires a value
exit /b 1
)
set "SOURCE=%~2"
shift
shift
goto parse_args
)
shift
goto parse_args
if /i "%~1"=="--method" (
if "%~2"=="" (
echo ERROR: --method requires a value
exit /b 1
)
set "METHOD=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="--mirror" (
if "%~2"=="" (
echo ERROR: --mirror requires a value
exit /b 1
)
set "MIRROR=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="--base-url" (
if "%~2"=="" (
echo ERROR: --base-url requires a value
exit /b 1
)
set "BASE_URL=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="--archive" (
if "%~2"=="" (
echo ERROR: --archive requires a value
exit /b 1
)
set "ARCHIVE_PATH=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="--version" (
if "%~2"=="" (
echo ERROR: --version requires a value
exit /b 1
)
set "VERSION=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="--registry" (
if "%~2"=="" (
echo ERROR: --registry requires a value
exit /b 1
)
set "NPM_REGISTRY=%~2"
shift
shift
goto parse_args
)
if /i "%~1"=="-h" goto usage
if /i "%~1"=="--help" goto usage
echo ERROR: Unknown option: %~1
echo.
goto usage_error
:end_parse
echo ===========================================
echo Qwen Code Installation Script with Source Tracking
echo ===========================================
echo.
echo INFO: Installation source: %SOURCE%
echo.
call :ValidateOptions
if %ERRORLEVEL% NEQ 0 exit /b 1
REM Check if Node.js is already installed
call :CheckCommandExists node
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('node --version') do set "NODE_VERSION=%%i"
echo INFO: Node.js is already installed: !NODE_VERSION!
REM Extract major version number
set "MAJOR_VERSION=!NODE_VERSION:v=!"
for /f "tokens=1 delims=." %%a in ("!MAJOR_VERSION!") do (
set "MAJOR_VERSION=%%a"
)
if !MAJOR_VERSION! GEQ 20 (
echo INFO: Node.js version !NODE_VERSION! is sufficient. Skipping Node.js installation.
goto :InstallQwenCode
echo ===========================================
echo Qwen Code Installation Script
echo ===========================================
echo.
echo INFO: Install method: !METHOD!
if /i not "!METHOD!"=="npm" (
echo INFO: Standalone mirror: !MIRROR!
if not "!BASE_URL!"=="" echo INFO: Standalone base URL: !BASE_URL!
if not "!ARCHIVE_PATH!"=="" (
echo INFO: Standalone archive: !ARCHIVE_PATH!
) else (
echo INFO: Node.js version !NODE_VERSION! is too low. Need version 20 or higher.
echo INFO: Installing Node.js 20+
call :InstallNodeJSDirectly
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to install Node.js. Cannot continue with Qwen Code installation.
exit /b 1
)
)
) else (
echo INFO: Node.js not found. Installing Node.js 20+
call :InstallNodeJSDirectly
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to install Node.js. Cannot continue with Qwen Code installation.
exit /b 1
echo INFO: Standalone version: !VERSION!
)
)
if /i not "!METHOD!"=="standalone" echo INFO: npm registry: !NPM_REGISTRY!
if not "!SOURCE!"=="unknown" echo INFO: Installation source: !SOURCE!
echo.
:InstallQwenCode
REM Verify npm is available before installing Qwen Code
REM Always use full path to npm to avoid local node_modules conflicts
set "NODEJS_PATH=C:\Program Files\nodejs"
set "NODEJS_PATH_X86=C:\Program Files (x86)\nodejs"
if exist "!NODEJS_PATH!\npm.cmd" (
echo INFO: Using npm from !NODEJS_PATH!
set "NPM_CMD=!NODEJS_PATH!\npm.cmd"
) else if exist "!NODEJS_PATH_X86!\npm.cmd" (
echo INFO: Using npm from !NODEJS_PATH_X86!
set "NPM_CMD=!NODEJS_PATH_X86!\npm.cmd"
) else (
call :CheckCommandExists npm
if !ERRORLEVEL! NEQ 0 (
echo ERROR: npm command not found. Node.js installation may have failed.
echo INFO: Please restart your command prompt and try again.
echo INFO: If the problem persists, manually install Node.js from: https://nodejs.org/
exit /b 1
)
set "NPM_CMD=npm"
if /i "!METHOD!"=="standalone" (
call :InstallStandalone
if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
call :PrintFinalInstructions "!INSTALL_BIN_DIR!"
endlocal
exit /b 0
)
REM Install Qwen Code with source information
echo INFO: Installing Qwen Code with source: %SOURCE%
echo INFO: Running: %NPM_CMD% install -g @qwen-code/qwen-code@latest --registry https://registry.npmmirror.com
call "%NPM_CMD%" install -g @qwen-code/qwen-code@latest --registry https://registry.npmmirror.com
if %ERRORLEVEL% EQU 0 (
echo SUCCESS: Qwen Code installed successfully!
) else (
echo ERROR: Failed to install Qwen Code.
exit /b 1
if /i "!METHOD!"=="npm" (
call :InstallNpm
if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
call :PrintFinalInstructions ""
endlocal
exit /b 0
)
REM Create source.json only if --source or -s was explicitly provided
if not "!SOURCE!"=="unknown" (
echo INFO: Creating source.json in %USERPROFILE%\.qwen...
set "QWEN_DIR=%USERPROFILE%\.qwen"
if not exist "!QWEN_DIR!" (
mkdir "!QWEN_DIR!"
)
REM Create the source.json file with the installation source
(
echo {
echo "source": "!SOURCE!"
echo }
) > "!QWEN_DIR!\source.json"
echo SUCCESS: Installation source saved to %USERPROFILE%\.qwen\source.json
call :InstallStandalone
set "STANDALONE_STATUS=!ERRORLEVEL!"
if !STANDALONE_STATUS! EQU 0 (
call :PrintFinalInstructions "!INSTALL_BIN_DIR!"
endlocal
exit /b 0
)
REM Verify installation
call :CheckCommandExists qwen
if %ERRORLEVEL% EQU 0 (
echo SUCCESS: Qwen Code is available as 'qwen' command.
call qwen --version
echo.
echo INFO: Starting Qwen Code...
echo.
call qwen
) else (
echo WARNING: Qwen Code may not be in PATH. Please check your npm global bin directory.
echo.
echo ===========================================
echo SUCCESS: Installation completed!
echo The source information is stored in %USERPROFILE%\.qwen\source.json
echo.
echo ===========================================
if !STANDALONE_STATUS! EQU 2 (
echo WARNING: Falling back to npm installation.
call :InstallNpm
if !ERRORLEVEL! NEQ 0 exit /b !ERRORLEVEL!
call :PrintFinalInstructions ""
endlocal
exit /b 0
)
endlocal
exit /b !STANDALONE_STATUS!
:usage
echo Qwen Code Installer
echo.
echo Usage: install-qwen-with-source.bat [OPTIONS]
echo.
echo Options:
echo -s, --source SOURCE Record the installation source.
echo Only letters, numbers, dot, underscore, and dash are allowed.
echo --method METHOD Install method: detect, standalone, or npm.
echo --mirror MIRROR Standalone archive mirror: github or aliyun.
echo --base-url URL Override standalone archive base URL.
echo --archive PATH Install from a local standalone archive.
echo --version VERSION Standalone release version. Defaults to latest.
echo --registry REGISTRY npm registry to use.
echo Defaults to QWEN_NPM_REGISTRY or https://registry.npmmirror.com
echo -h, --help Show this help message.
exit /b 0
REM ============================================================
REM Function: CheckCommandExists
REM Description: Check if a command exists in the system
REM ============================================================
:CheckCommandExists
where %~1 >nul 2>&1
:usage_error
echo Qwen Code Installer
echo.
echo Usage: install-qwen-with-source.bat [OPTIONS]
echo.
echo Options:
echo -s, --source SOURCE Record the installation source.
echo --method METHOD Install method: detect, standalone, or npm.
echo --mirror MIRROR Standalone archive mirror: github or aliyun.
echo --base-url URL Override standalone archive base URL.
echo --archive PATH Install from a local standalone archive.
echo --version VERSION Standalone release version. Defaults to latest.
echo --registry REGISTRY npm registry to use.
echo -h, --help Show this help message.
exit /b 1
:ValidateOptions
if "!METHOD!"=="" set "METHOD=detect"
if /i "!METHOD!"=="detect" goto validate_method_ok
if /i "!METHOD!"=="standalone" goto validate_method_ok
if /i "!METHOD!"=="npm" goto validate_method_ok
echo ERROR: --method must be detect, standalone, or npm.
exit /b 1
:validate_method_ok
if /i "!MIRROR!"=="github" goto validate_mirror_ok
if /i "!MIRROR!"=="aliyun" goto validate_mirror_ok
echo ERROR: --mirror must be github or aliyun.
exit /b 1
:validate_mirror_ok
call :ValidateSource
exit /b %ERRORLEVEL%
REM ============================================================
REM Function: InstallNodeJSDirectly
REM Description: Download and install Node.js directly from official website
REM ============================================================
:InstallNodeJSDirectly
echo INFO: Downloading Node.js LTS (20.x) from official website
:ValidateSource
if "!SOURCE!"=="unknown" exit /b 0
echo(!SOURCE!| findstr /R /C:"^[A-Za-z0-9._-][A-Za-z0-9._-]*$" >nul
if %ERRORLEVEL% EQU 0 exit /b 0
REM Create temp directory for download
set "TEMP_DIR=%TEMP%\qwen-nodejs-install"
if not exist "%TEMP_DIR%" mkdir "%TEMP_DIR%"
echo ERROR: --source may only contain letters, numbers, dot, underscore, or dash.
exit /b 1
REM Determine architecture
set "ARCH=x64"
if "%PROCESSOR_ARCHITECTURE%"=="x86" set "ARCH=x86"
if "%PROCESSOR_ARCHITECTURE%"=="AMD64" set "ARCH=x64"
if defined PROCESSOR_ARCHITEW6432 set "ARCH=x64"
REM Set Node.js download URL (LTS version 20.x)
set "NODE_VERSION=20.18.1"
set "NODE_URL=https://nodejs.org/dist/v!NODE_VERSION!/node-v!NODE_VERSION!-!ARCH!.msi"
set "NODE_INSTALLER=%TEMP_DIR%\nodejs-installer.msi"
echo INFO: Downloading from: !NODE_URL!
echo INFO: Architecture: !ARCH!
REM Download Node.js installer using PowerShell
powershell -Command "try { Invoke-WebRequest -Uri '!NODE_URL!' -OutFile '!NODE_INSTALLER!' -UseBasicParsing; Write-Host 'Download completed successfully.' } catch { Write-Host 'Download failed:' $_.Exception.Message; exit 1 }"
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to download Node.js installer from official source.
echo INFO: Please manually download and install Node.js from: https://nodejs.org/
echo INFO: After manual installation, restart your command prompt and run this script again.
:DetectTarget
set "TARGET="
if /i "%PROCESSOR_ARCHITECTURE%"=="AMD64" set "TARGET=win-x64"
if /i "%PROCESSOR_ARCHITEW6432%"=="AMD64" set "TARGET=win-x64"
if "!TARGET!"=="" (
echo WARNING: Standalone archive is not available for this Windows architecture.
exit /b 1
)
exit /b 0
if not exist "!NODE_INSTALLER!" (
echo ERROR: Node.js installer not found after download.
exit /b 1
:ReleaseVersionPath
if /i "!VERSION!"=="latest" (
set "VERSION_PATH=latest"
exit /b 0
)
set "VERSION_PATH=!VERSION!"
if /i "!VERSION_PATH:~0,1!"=="v" exit /b 0
set "VERSION_PATH=v!VERSION_PATH!"
exit /b 0
echo INFO: Installing Node.js silently
REM Install Node.js silently
msiexec /i "!NODE_INSTALLER!" /quiet /norestart ADDLOCAL=ALL
if !ERRORLEVEL! NEQ 0 (
echo ERROR: Failed to install Node.js.
echo INFO: You may need to run this script as Administrator.
echo INFO: Or manually install Node.js from: https://nodejs.org/
exit /b 1
)
echo INFO: Node.js installation completed.
REM Clean up installer
del "!NODE_INSTALLER!" 2>nul
rmdir "!TEMP_DIR!" 2>nul
REM Refresh environment variables
echo INFO: Refreshing environment variables
call :RefreshEnvVars
REM Verify installation and return success
set "NODEJS_INSTALL_PATH=C:\Program Files\nodejs"
if exist "!NODEJS_INSTALL_PATH!\node.exe" (
for /f "delims=" %%i in ('"!NODEJS_INSTALL_PATH!\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js !NODE_VERSION! installed successfully!
:StandaloneBaseUrl
if not "!BASE_URL!"=="" (
set "STANDALONE_BASE_URL=!BASE_URL!"
exit /b 0
)
set "NODEJS_INSTALL_PATH_X86=C:\Program Files (x86)\nodejs"
if exist "!NODEJS_INSTALL_PATH_X86!\node.exe" (
for /f "delims=" %%i in ('"!NODEJS_INSTALL_PATH_X86!\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js !NODE_VERSION! installed successfully!
call :ReleaseVersionPath
if /i "!MIRROR!"=="aliyun" (
set "STANDALONE_BASE_URL=https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/releases/qwen-code/!VERSION_PATH!"
exit /b 0
)
call :CheckCommandExists node
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('node --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js !NODE_VERSION! installed successfully!
if /i "!VERSION_PATH!"=="latest" (
set "STANDALONE_BASE_URL=https://github.com/QwenLM/qwen-code/releases/latest/download"
exit /b 0
)
set "STANDALONE_BASE_URL=https://github.com/QwenLM/qwen-code/releases/download/!VERSION_PATH!"
exit /b 0
:UrlExists
set "CHECK_URL=%~1"
powershell -NoProfile -ExecutionPolicy Bypass -Command "$request = [Net.WebRequest]::Create('%CHECK_URL%'); $request.Method = 'HEAD'; try { $response = $request.GetResponse(); $response.Close(); exit 0 } catch { exit 1 }" >nul 2>&1
exit /b %ERRORLEVEL%
:DownloadFile
set "DOWNLOAD_URL=%~1"
set "DOWNLOAD_DEST=%~2"
powershell -NoProfile -ExecutionPolicy Bypass -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%DOWNLOAD_DEST%')"
exit /b %ERRORLEVEL%
:VerifyChecksum
set "ARCHIVE_FILE=%~1"
set "CHECKSUM_SOURCE=%~2"
set "ARCHIVE_NAME=%~3"
set "CHECKSUM_FILE=!CHECKSUM_SOURCE!"
set "TEMP_CHECKSUM="
set "REQUIRE_CHECKSUM=0"
if "!CHECKSUM_FILE!"=="" (
for %%I in ("!ARCHIVE_FILE!") do set "CHECKSUM_FILE=%%~dpISHA256SUMS"
) else (
echo WARNING: Node.js installed but not found in PATH.
echo INFO: Trying to use Node.js from default installation path
REM Try to use Node.js directly from installation path
set "NODE_PATH=C:\Program Files\nodejs"
if exist "%NODE_PATH%\node.exe" (
echo INFO: Found Node.js at %NODE_PATH%
REM Update PATH for current session
set "PATH=%PATH%;%NODE_PATH%"
REM Test if node works now
"%NODE_PATH%\node.exe" --version >nul 2>&1
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('"%NODE_PATH%\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js %NODE_VERSION% is working from %NODE_PATH%
exit /b 0
echo !CHECKSUM_FILE!| findstr /R /C:"^https*://" >nul
if !ERRORLEVEL! EQU 0 (
set "REQUIRE_CHECKSUM=1"
set "TEMP_CHECKSUM=%TEMP%\qwen-code-checksums-%RANDOM%%RANDOM%.txt"
call :DownloadFile "!CHECKSUM_FILE!" "!TEMP_CHECKSUM!"
if !ERRORLEVEL! NEQ 0 (
if exist "!TEMP_CHECKSUM!" del /F /Q "!TEMP_CHECKSUM!" >nul 2>&1
echo ERROR: Could not download SHA256SUMS for checksum verification.
exit /b 1
)
set "CHECKSUM_FILE=!TEMP_CHECKSUM!"
)
REM Try x86 path
set "NODE_PATH_X86=C:\Program Files (x86)\nodejs"
if exist "%NODE_PATH_X86%\node.exe" (
echo INFO: Found Node.js at %NODE_PATH_X86%
REM Update PATH for current session
set "PATH=%PATH%;%NODE_PATH_X86%"
REM Test if node works now
"%NODE_PATH_X86%\node.exe" --version >nul 2>&1
if !ERRORLEVEL! EQU 0 (
for /f "delims=" %%i in ('"%NODE_PATH_X86%\node.exe" --version') do set "NODE_VERSION=%%i"
echo SUCCESS: Node.js %NODE_VERSION% is working from %NODE_PATH_X86%
exit /b 0
)
)
if not exist "!CHECKSUM_FILE!" (
if "!REQUIRE_CHECKSUM!"=="1" (
echo ERROR: SHA256SUMS not found; cannot verify remote archive.
exit /b 1
)
echo ERROR: Node.js installation completed but cannot be executed
echo WARNING: SHA256SUMS not found; skipping checksum verification.
exit /b 0
)
set "EXPECTED_HASH="
for /f "tokens=1" %%H in ('findstr /C:"!ARCHIVE_NAME!" "!CHECKSUM_FILE!"') do (
if "!EXPECTED_HASH!"=="" set "EXPECTED_HASH=%%H"
)
if "!EXPECTED_HASH!"=="" (
if not "!TEMP_CHECKSUM!"=="" del /F /Q "!TEMP_CHECKSUM!" >nul 2>&1
if "!REQUIRE_CHECKSUM!"=="1" (
echo ERROR: Checksum entry for !ARCHIVE_NAME! not found.
exit /b 1
)
echo WARNING: Checksum entry for !ARCHIVE_NAME! not found; skipping checksum verification.
exit /b 0
)
set "ACTUAL_HASH="
for /f "tokens=1" %%H in ('certutil -hashfile "!ARCHIVE_FILE!" SHA256 ^| findstr /R /C:"^[0-9A-Fa-f][0-9A-Fa-f]"') do (
if "!ACTUAL_HASH!"=="" set "ACTUAL_HASH=%%H"
)
if not "!TEMP_CHECKSUM!"=="" del /F /Q "!TEMP_CHECKSUM!" >nul 2>&1
if "!ACTUAL_HASH!"=="" (
if "!REQUIRE_CHECKSUM!"=="1" (
echo ERROR: Could not calculate SHA-256 checksum for remote archive.
exit /b 1
)
echo WARNING: Could not calculate SHA-256 checksum; skipping checksum verification.
exit /b 0
)
if /i not "!EXPECTED_HASH!"=="!ACTUAL_HASH!" (
echo ERROR: Checksum verification failed for !ARCHIVE_NAME!.
exit /b 1
)
echo SUCCESS: Checksum verified for !ARCHIVE_NAME!.
exit /b 0
REM ============================================================
REM Function: RefreshEnvVars
REM Description: Refresh environment variables without restarting
REM ============================================================
:RefreshEnvVars
REM Add Node.js to PATH if not already there
set "NODEJS_DIR=C:\Program Files\nodejs"
if exist "!NODEJS_DIR!\node.exe" (
echo INFO: Found Node.js at !NODEJS_DIR!
set "PATH=!PATH!;!NODEJS_DIR!"
:InstallStandalone
set "TEMP_DIR="
set "CHECKSUM_SOURCE="
if not "!ARCHIVE_PATH!"=="" (
set "ARCHIVE_FILE=!ARCHIVE_PATH!"
for %%I in ("!ARCHIVE_FILE!") do set "ARCHIVE_NAME=%%~nxI"
if not exist "!ARCHIVE_FILE!" (
echo ERROR: Standalone archive not found: !ARCHIVE_FILE!
exit /b 1
)
) else (
call :DetectTarget
if !ERRORLEVEL! NEQ 0 exit /b 2
set "ARCHIVE_NAME=qwen-code-win-x64.zip"
call :StandaloneBaseUrl
set "ARCHIVE_URL=!STANDALONE_BASE_URL!/!ARCHIVE_NAME!"
set "CHECKSUM_SOURCE=!STANDALONE_BASE_URL!/SHA256SUMS"
if /i "!METHOD!"=="detect" (
call :UrlExists "!ARCHIVE_URL!"
if !ERRORLEVEL! NEQ 0 (
echo WARNING: Standalone archive not found: !ARCHIVE_NAME!
exit /b 2
)
)
set "TEMP_DIR=%TEMP%\qwen-code-install-%RANDOM%%RANDOM%"
mkdir "!TEMP_DIR!" >nul 2>&1
set "ARCHIVE_FILE=!TEMP_DIR!\!ARCHIVE_NAME!"
echo INFO: Downloading !ARCHIVE_URL!
call :DownloadFile "!ARCHIVE_URL!" "!ARCHIVE_FILE!"
if !ERRORLEVEL! NEQ 0 (
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
echo WARNING: Failed to download standalone archive.
exit /b 2
)
)
REM Try alternative path for x86 systems
set "NODEJS_DIR_X86=C:\Program Files (x86)\nodejs"
if exist "!NODEJS_DIR_X86!\node.exe" (
echo INFO: Found Node.js at !NODEJS_DIR_X86!
set "PATH=!PATH!;!NODEJS_DIR_X86!"
if "!TEMP_DIR!"=="" (
set "TEMP_DIR=%TEMP%\qwen-code-install-%RANDOM%%RANDOM%"
mkdir "!TEMP_DIR!" >nul 2>&1
)
call :VerifyChecksum "!ARCHIVE_FILE!" "!CHECKSUM_SOURCE!" "!ARCHIVE_NAME!"
if !ERRORLEVEL! NEQ 0 (
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
exit /b 1
)
set "EXTRACT_DIR=!TEMP_DIR!\extract"
mkdir "!EXTRACT_DIR!" >nul 2>&1
powershell -NoProfile -ExecutionPolicy Bypass -Command "Expand-Archive -LiteralPath '%ARCHIVE_FILE%' -DestinationPath '%EXTRACT_DIR%' -Force"
if !ERRORLEVEL! NEQ 0 (
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
echo ERROR: Failed to extract standalone archive.
exit /b 1
)
if not exist "!EXTRACT_DIR!\qwen-code\bin\qwen.cmd" (
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
echo ERROR: Archive does not contain qwen-code\bin\qwen.cmd.
exit /b 1
)
if not exist "!EXTRACT_DIR!\qwen-code\node\node.exe" (
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
echo ERROR: Archive does not contain qwen-code\node\node.exe.
exit /b 1
)
if not exist "!INSTALL_BASE!" mkdir "!INSTALL_BASE!"
if not exist "!INSTALL_BIN_DIR!" mkdir "!INSTALL_BIN_DIR!"
set "NEW_INSTALL_DIR=!INSTALL_DIR!.new"
set "OLD_INSTALL_DIR=!INSTALL_DIR!.old"
if exist "!NEW_INSTALL_DIR!" rmdir /S /Q "!NEW_INSTALL_DIR!" >nul 2>&1
if exist "!OLD_INSTALL_DIR!" rmdir /S /Q "!OLD_INSTALL_DIR!" >nul 2>&1
move /Y "!EXTRACT_DIR!\qwen-code" "!NEW_INSTALL_DIR!" >nul
if !ERRORLEVEL! NEQ 0 (
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
echo ERROR: Failed to stage standalone archive.
exit /b 1
)
if exist "!INSTALL_DIR!" move /Y "!INSTALL_DIR!" "!OLD_INSTALL_DIR!" >nul
move /Y "!NEW_INSTALL_DIR!" "!INSTALL_DIR!" >nul
if !ERRORLEVEL! NEQ 0 (
if exist "!OLD_INSTALL_DIR!" move /Y "!OLD_INSTALL_DIR!" "!INSTALL_DIR!" >nul
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
echo ERROR: Failed to install standalone archive to !INSTALL_DIR!.
exit /b 1
)
if exist "!OLD_INSTALL_DIR!" rmdir /S /Q "!OLD_INSTALL_DIR!" >nul 2>&1
(
echo @echo off
echo call "!INSTALL_DIR!\bin\qwen.cmd" %%*
) > "!INSTALL_BIN_DIR!\qwen.cmd"
set "PATH=!INSTALL_BIN_DIR!;!PATH!"
call :CreateSourceJson
if exist "!TEMP_DIR!" rmdir /S /Q "!TEMP_DIR!" >nul 2>&1
echo SUCCESS: Qwen Code standalone archive installed successfully.
echo INFO: Installed to !INSTALL_DIR!
exit /b 0
:RequireNode
where node >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
echo ERROR: Node.js was not found.
echo.
echo Node.js 20 or newer is required before installing Qwen Code with npm.
echo Please install Node.js from https://nodejs.org/ and rerun this installer.
exit /b 1
)
for /f "delims=" %%i in ('node -p "process.versions.node" 2^>nul') do set "NODE_VERSION=%%i"
if "%NODE_VERSION%"=="" (
echo ERROR: Unable to determine Node.js version.
echo Node.js 20 or newer is required before installing Qwen Code with npm.
exit /b 1
)
for /f "tokens=1 delims=." %%a in ("%NODE_VERSION%") do set "MAJOR_VERSION=%%a"
set /a NODE_MAJOR_NUM=%MAJOR_VERSION% >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
echo ERROR: Unable to determine Node.js version.
echo Node.js 20 or newer is required before installing Qwen Code with npm.
exit /b 1
)
if %NODE_MAJOR_NUM% LSS 20 (
echo ERROR: Node.js %NODE_VERSION% is installed, but Node.js 20 or newer is required.
echo Please install Node.js from https://nodejs.org/ and rerun this installer.
exit /b 1
)
echo SUCCESS: Node.js %NODE_VERSION% detected.
exit /b 0
:RequireNpm
where npm >nul 2>&1
if %ERRORLEVEL% NEQ 0 (
echo ERROR: npm was not found.
echo Please install Node.js with npm included, then rerun this installer.
exit /b 1
)
for /f "delims=" %%i in ('npm -v 2^>nul') do set "NPM_VERSION=%%i"
echo SUCCESS: npm %NPM_VERSION% detected.
exit /b 0
:InstallNpm
call :RequireNode
if %ERRORLEVEL% NEQ 0 exit /b 1
call :RequireNpm
if %ERRORLEVEL% NEQ 0 exit /b 1
where qwen >nul 2>&1
if %ERRORLEVEL% EQU 0 (
for /f "delims=" %%i in ('qwen --version 2^>nul') do set "QWEN_VERSION=%%i"
echo INFO: Existing Qwen Code detected: !QWEN_VERSION!
echo INFO: Upgrading to the latest version.
)
echo INFO: Running: npm install -g @qwen-code/qwen-code@latest --registry !NPM_REGISTRY!
call npm install -g @qwen-code/qwen-code@latest --registry "!NPM_REGISTRY!"
if %ERRORLEVEL% NEQ 0 (
echo ERROR: Failed to install Qwen Code.
echo.
echo This installer does not change your npm prefix or PATH.
echo If the failure is a permission error, fix your npm global package directory, then run:
echo npm install -g @qwen-code/qwen-code@latest --registry !NPM_REGISTRY!
exit /b 1
)
echo SUCCESS: Qwen Code installed successfully.
call :CreateSourceJson
exit /b 0
:CreateSourceJson
if "!SOURCE!"=="unknown" exit /b 0
set "QWEN_DIR=%USERPROFILE%\.qwen"
if not exist "%QWEN_DIR%" mkdir "%QWEN_DIR%"
(
echo {
echo "source": "!SOURCE!"
echo }
) > "%QWEN_DIR%\source.json"
echo SUCCESS: Installation source saved to %USERPROFILE%\.qwen\source.json
exit /b 0
:PrintFinalInstructions
set "EXTRA_BIN=%~1"
if not "!EXTRA_BIN!"=="" set "PATH=!EXTRA_BIN!;!PATH!"
echo.
echo ===========================================
echo Installation completed!
echo ===========================================
echo.
where qwen >nul 2>&1
if %ERRORLEVEL% EQU 0 (
for /f "delims=" %%i in ('qwen --version 2^>nul') do set "QWEN_VERSION=%%i"
echo SUCCESS: Qwen Code is ready to use: !QWEN_VERSION!
echo.
echo You can now run: qwen
echo.
echo INFO: Run qwen in your project directory to start an interactive session.
exit /b 0
)
echo WARNING: Qwen Code was installed, but qwen is not on PATH in this prompt.
echo.
echo Restart your command prompt, then run: qwen
if not "!EXTRA_BIN!"=="" (
echo.
echo Or add this directory to PATH:
echo !EXTRA_BIN!
echo Then run:
echo qwen
exit /b 0
)
for /f "delims=" %%i in ('npm prefix -g 2^>nul') do set "NPM_PREFIX=%%i"
if not "!NPM_PREFIX!"=="" (
echo.
echo Or add this npm global directory to PATH:
echo !NPM_PREFIX!
echo Then run:
echo qwen
)
exit /b 0

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,218 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it, vi } from 'vitest';
const {
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} = await vi.importActual('node:fs');
const { execFileSync } = await vi.importActual('node:child_process');
const { tmpdir } = await vi.importActual('node:os');
const path = await vi.importActual('node:path');
const readScript = (path) => readFileSync(path, 'utf8');
describe('installation scripts', () => {
it('keeps the Linux/macOS installer lightweight', () => {
const script = readScript(
'scripts/installation/install-qwen-with-source.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.toContain('brew install node@20');
expect(script).toContain('brew install node');
expect(script).toContain(
'--source may only contain letters, numbers, dot, underscore, or dash',
);
expect(script).toContain('Node.js 20 or newer is required');
expect(script).toContain(
'npm install -g @qwen-code/qwen-code@latest --registry',
);
expect(script).toContain('You can now run: qwen');
});
it('supports code-server-style standalone install on Linux/macOS', () => {
const script = readScript(
'scripts/installation/install-qwen-with-source.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 remote archive',
);
expect(script).toContain('qwen-code-${target}');
expect(script).toContain('METHOD="${METHOD:-detect}"');
expect(script).toContain('Falling back to npm installation');
expect(script).toContain('standalone_status=$?');
expect(script).toContain('[[ "${standalone_status}" -eq 2 ]]');
expect(script).not.toContain('ln -sf "${INSTALL_LIB_DIR}/bin/qwen"');
expect(script).toContain('exec "${INSTALL_LIB_DIR}/bin/qwen"');
expect(script).toContain('qwen-code/node/bin/node');
});
it('keeps the Windows installer lightweight', () => {
const script = readScript(
'scripts/installation/install-qwen-with-source.bat',
);
expect(script).not.toContain('InstallNodeJSDirectly');
expect(script).not.toContain('node-v!NODE_VERSION!');
expect(script).not.toContain('msiexec');
expect(script).not.toContain('Invoke-WebRequest');
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('findstr /R');
expect(script).toContain(
'--source may only contain letters, numbers, dot, underscore, or dash',
);
expect(script).toContain('Node.js 20 or newer is required');
expect(script).toContain('Please install Node.js');
expect(script).toContain(
'npm install -g @qwen-code/qwen-code@latest --registry',
);
expect(script).toContain('You can now run: qwen');
});
it('supports code-server-style standalone install on Windows', () => {
const script = readScript(
'scripts/installation/install-qwen-with-source.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 remote archive',
);
expect(script).toContain('qwen-code-win-x64.zip');
expect(script).toContain('Expand-Archive');
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('qwen-code\\node\\node.exe');
});
});
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(existsSync('scripts/create-standalone-package.js')).toBe(true);
const packageScript = readScript('scripts/create-standalone-package.js');
expect(packageScript).toContain("'bundled/qc-helper/docs'");
expect(packageScript).toContain("path.join(packageRoot, 'package.json')");
expect(packageScript).toContain('validateNodeRuntime');
});
it('rejects a runtime archive without a Node executable', () => {
const createdDist = ensureMinimalDist();
const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-'));
try {
const fakeRuntimeDir = path.join(tmpDir, 'not-node');
mkdirSync(fakeRuntimeDir, { recursive: true });
writeFileSync(path.join(fakeRuntimeDir, 'README.txt'), 'not node\n');
const fakeRuntimeArchive = path.join(tmpDir, 'bad-runtime.tar.gz');
execFileSync(
'tar',
['-czf', fakeRuntimeArchive, '-C', tmpDir, 'not-node'],
{
env: { ...process.env, LC_ALL: 'C' },
stdio: 'ignore',
},
);
expect(() =>
execFileSync(
'node',
[
'scripts/create-standalone-package.js',
'--target',
'linux-x64',
'--node-archive',
fakeRuntimeArchive,
'--out-dir',
path.join(tmpDir, 'out'),
'--version',
'0.0.0-test',
],
{ stdio: 'pipe' },
),
).toThrow(/Node.js runtime for linux-x64 must contain/);
} finally {
rmSync(tmpDir, { recursive: true, force: true });
if (createdDist) {
rmSync('dist', { recursive: true, force: true });
}
}
});
it('uploads standalone archives during release', () => {
const workflow = readScript('.github/workflows/release.yml');
expect(workflow).toContain('set -euo pipefail');
expect(workflow).toContain('SHASUMS256.txt');
expect(workflow).toContain('$2 == name');
expect(workflow).toContain('does not list ${archive_name}');
expect(workflow).toContain('sha256sum -c -');
expect(workflow).toContain('npm run package:standalone');
expect(workflow).toContain('dist/standalone/qwen-code-*');
expect(workflow).toContain('dist/standalone/SHA256SUMS');
});
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('node-pty');
expect(guide).toContain('clipboard');
});
});
function ensureMinimalDist() {
if (existsSync('dist')) {
return false;
}
mkdirSync('dist/vendor', { recursive: true });
mkdirSync('dist/bundled/qc-helper/docs', { recursive: true });
writeFileSync('dist/cli.js', 'console.log("qwen");\n');
writeFileSync(
'dist/package.json',
JSON.stringify({ name: '@qwen-code/qwen-code', version: '0.0.0' }),
);
return true;
}