mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-08 08:15:38 +00:00
* fix(cli): avoid updating active processes * fix(cli): close update relaunch gaps * test(cli): fix standalone update source path * fix(cli): reset deferred update per relaunch
187 lines
5.7 KiB
JavaScript
Executable file
187 lines
5.7 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* @license
|
|
* Copyright 2025 Qwen Code
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Production bin entry wrapper.
|
|
*
|
|
* For most commands: launches dist/cli.js with --expose-gc so that
|
|
* global.gc() is available for the memory-pressure monitor's critical-tier
|
|
* cleanup.
|
|
*
|
|
* For bootstrap fast paths: imports cli.js directly in-process, skipping the
|
|
* spawnSync overhead. These paths do not need global.gc(); the normal
|
|
* interactive path still relaunches with --expose-gc for the memory-pressure
|
|
* monitor.
|
|
*/
|
|
|
|
const relaunchArgs = process.env['QWEN_CODE_RELAUNCH_ARGS'];
|
|
let cliArgs = process.argv.slice(2);
|
|
try {
|
|
cliArgs = relaunchArgs ? JSON.parse(relaunchArgs) : cliArgs;
|
|
} catch {
|
|
// Ignore stale or user-provided junk; normal argv is still usable.
|
|
}
|
|
delete process.env['QWEN_CODE_RELAUNCH_ARGS'];
|
|
|
|
function hasFlag(flag, alias) {
|
|
for (const arg of cliArgs) {
|
|
if (arg === '--') {
|
|
return false;
|
|
}
|
|
if (arg === flag || arg === alias) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isInProcessFastPath() {
|
|
const first = cliArgs[0];
|
|
if (first === 'serve' || first === 'mcp') {
|
|
return true;
|
|
}
|
|
if (first === undefined || first.startsWith('-')) {
|
|
return hasFlag('--help', '-h') || hasFlag('--version', '-v');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const isTopLevelVersion =
|
|
(cliArgs[0] === undefined || cliArgs[0].startsWith('-')) &&
|
|
hasFlag('--version', '-v');
|
|
|
|
if (isTopLevelVersion && process.env['CLI_VERSION']) {
|
|
process.stdout.write(`${process.env['CLI_VERSION']}\n`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const { existsSync, realpathSync } = await import('node:fs');
|
|
const { fileURLToPath, pathToFileURL } = await import('node:url');
|
|
const { delimiter, dirname, join, parse, resolve, sep } = await import(
|
|
'node:path'
|
|
);
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const cliPathCandidates = [
|
|
join(__dirname, 'cli.js'),
|
|
join(__dirname, '..', 'dist', 'cli.js'),
|
|
];
|
|
const packageJsonPathCandidates = [
|
|
join(__dirname, 'package.json'),
|
|
join(__dirname, '..', 'package.json'),
|
|
];
|
|
const cliPath =
|
|
cliPathCandidates.find((candidate) => existsSync(candidate)) ??
|
|
cliPathCandidates[0];
|
|
const packageJsonPath =
|
|
packageJsonPathCandidates.find((candidate) => existsSync(candidate)) ??
|
|
packageJsonPathCandidates[0];
|
|
|
|
if (isTopLevelVersion) {
|
|
try {
|
|
const { readFileSync } = await import('node:fs');
|
|
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
process.stdout.write(`${pkg.version || 'unknown'}\n`);
|
|
process.exit(0);
|
|
} catch {
|
|
// Fall through to cli.js, which has its own version fallback.
|
|
}
|
|
}
|
|
|
|
if (isInProcessFastPath()) {
|
|
const { default: module } = await import('node:module');
|
|
module.enableCompileCache?.();
|
|
process.argv[1] = cliPath;
|
|
await import(pathToFileURL(cliPath).href);
|
|
} else {
|
|
const { spawnSync } = await import('node:child_process');
|
|
const UPDATE_COMPLETE_EXIT_CODE = 44;
|
|
const launcherNames =
|
|
process.platform === 'win32' ? ['qwen.cmd', 'qwen.exe', 'qwen'] : ['qwen'];
|
|
const entryPath = resolve(process.argv[1]);
|
|
const entryRootLength = parse(entryPath).root.length;
|
|
const launcherFromEnv = process.env['QWEN_CODE_LAUNCHER_PATH'];
|
|
delete process.env['QWEN_CODE_LAUNCHER_PATH'];
|
|
delete process.env['QWEN_CODE_LAUNCHER_PID'];
|
|
const launcherCandidates = process.env['PATH']
|
|
?.split(delimiter)
|
|
.flatMap((dir) => launcherNames.map((name) => join(dir, name)))
|
|
.filter((candidate) => existsSync(candidate));
|
|
const launcher =
|
|
launcherFromEnv && existsSync(launcherFromEnv)
|
|
? launcherFromEnv
|
|
: launcherCandidates
|
|
?.map((candidate) => {
|
|
if (resolve(candidate) === entryPath) {
|
|
return { candidate, score: Number.MAX_SAFE_INTEGER };
|
|
}
|
|
try {
|
|
if (realpathSync(candidate) === realpathSync(entryPath)) {
|
|
return { candidate, score: Number.MAX_SAFE_INTEGER };
|
|
}
|
|
} catch {
|
|
// Fall back to matching the installation prefix.
|
|
}
|
|
let parent = resolve(dirname(candidate));
|
|
while (
|
|
parent.length > entryRootLength &&
|
|
entryPath !== parent &&
|
|
!entryPath.startsWith(`${parent}${sep}`)
|
|
) {
|
|
const next = dirname(parent);
|
|
if (next === parent) break;
|
|
parent = next;
|
|
}
|
|
return { candidate, score: parent.length };
|
|
})
|
|
.filter(({ score }) => score > entryRootLength)
|
|
.sort((a, b) => b.score - a.score)[0]?.candidate;
|
|
const env = {
|
|
...process.env,
|
|
QWEN_CODE_LAUNCHER_PID: String(process.pid),
|
|
};
|
|
const result = spawnSync(
|
|
process.execPath,
|
|
['--expose-gc', cliPath, ...cliArgs],
|
|
{ stdio: 'inherit', env },
|
|
);
|
|
|
|
if (result.signal) {
|
|
process.kill(process.pid, result.signal);
|
|
} else if (result.status !== UPDATE_COMPLETE_EXIT_CODE) {
|
|
process.exit(result.status ?? 1);
|
|
} else {
|
|
if (!launcher) {
|
|
process.stderr.write(
|
|
'Update installed. Restart Qwen Code to use the new version.\n',
|
|
);
|
|
process.exit(0);
|
|
}
|
|
const relaunchEnv = {
|
|
...process.env,
|
|
QWEN_CODE_RELAUNCH_ARGS: JSON.stringify(cliArgs),
|
|
QWEN_CODE_SKIP_UPDATE_CHECK_ONCE: 'true',
|
|
};
|
|
const relaunchResult =
|
|
process.platform === 'win32' && launcher.endsWith('.cmd')
|
|
? spawnSync(
|
|
process.env['ComSpec'] ?? 'cmd.exe',
|
|
['/d', '/s', '/c', `""${launcher}""`],
|
|
{ stdio: 'inherit', env: relaunchEnv },
|
|
)
|
|
: spawnSync(launcher, [], {
|
|
stdio: 'inherit',
|
|
env: relaunchEnv,
|
|
});
|
|
if (relaunchResult.signal) {
|
|
process.kill(process.pid, relaunchResult.signal);
|
|
} else {
|
|
process.exit(relaunchResult.status ?? 1);
|
|
}
|
|
}
|
|
}
|