mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
* feat(web-shell): show the qwen-code version in the sidebar footer The Web Shell had no visible version. Show the running qwen-code version (from the daemon capabilities) in the sidebar footer, inline with the Settings button so it stays visible without taking its own row. Render the version consistently wherever it appears: - Prefix "v" only for a real semver release; a non-semver fallback such as "unknown" is shown as-is, so we never render a bogus "vunknown". Applied to the Web Shell badge and the TUI header. - Dev builds (scripts/dev.js) now report the real package version instead of the "dev" sentinel, matching scripts/start.js, so the UI shows the actual version (e.g. v0.19.4). DEV=true / NODE_ENV=development remain the signals that mark a dev build. * test: add readFileSync to node:fs mock in dev.test.js scripts/dev.js now reads package.json via readFileSync at module load to report the real CLI_VERSION, but the node:fs mock in dev.test.js did not export readFileSync, causing vitest to throw "No readFileSync export is defined on the node:fs mock" and failing the suite.
158 lines
4.4 KiB
JavaScript
158 lines
4.4 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Development entry point for Qwen Code CLI.
|
|
*
|
|
* Runs the CLI directly from TypeScript source files without requiring a build step.
|
|
* Changes to packages/core or packages/cli are reflected immediately.
|
|
*
|
|
* Usage: npm run dev -- [args]
|
|
* Example: npm run dev -- help
|
|
*/
|
|
|
|
import { spawn } from 'node:child_process';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
import {
|
|
writeFileSync,
|
|
mkdtempSync,
|
|
rmSync,
|
|
existsSync,
|
|
symlinkSync,
|
|
mkdirSync,
|
|
readFileSync,
|
|
} from 'node:fs';
|
|
import { tmpdir, platform } from 'node:os';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const root = join(__dirname, '..');
|
|
const cliPackageDir = join(root, 'packages', 'cli');
|
|
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));
|
|
|
|
// Ensure qc-helper bundled skill can find user docs in dev mode.
|
|
// In dev, import.meta.url resolves to the source tree, so the bundled skill
|
|
// directory is packages/core/src/skills/bundled/qc-helper/. We create a
|
|
// symlink from there to docs/users/ so the skill can read docs at runtime.
|
|
const qcHelperDocsLink = join(
|
|
root,
|
|
'packages',
|
|
'core',
|
|
'src',
|
|
'skills',
|
|
'bundled',
|
|
'qc-helper',
|
|
'docs',
|
|
);
|
|
const userDocsTarget = join(root, 'docs', 'users');
|
|
if (existsSync(userDocsTarget) && !existsSync(qcHelperDocsLink)) {
|
|
mkdirSync(dirname(qcHelperDocsLink), { recursive: true });
|
|
try {
|
|
symlinkSync(userDocsTarget, qcHelperDocsLink);
|
|
} catch {
|
|
// Symlink may fail on some systems; non-critical for dev
|
|
}
|
|
}
|
|
|
|
// Entry point for the CLI
|
|
const cliEntry = join(cliPackageDir, 'index.ts');
|
|
|
|
// Create a temporary loader file
|
|
const tmpDir = mkdtempSync(join(tmpdir(), 'qwen-dev-'));
|
|
const loaderPath = join(tmpDir, 'loader.mjs');
|
|
|
|
const coreSourcePath = join(root, 'packages', 'core', 'index.ts');
|
|
const coreSourceUrl = pathToFileURL(coreSourcePath).href;
|
|
|
|
const loaderCode = `
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
const coreSourceUrl = '${coreSourceUrl}';
|
|
|
|
export function resolve(specifier, context, nextResolve) {
|
|
if (specifier === '@qwen-code/qwen-code-core') {
|
|
return {
|
|
shortCircuit: true,
|
|
url: coreSourceUrl,
|
|
format: 'module',
|
|
};
|
|
}
|
|
return nextResolve(specifier, context);
|
|
}
|
|
`;
|
|
|
|
writeFileSync(loaderPath, loaderCode);
|
|
|
|
// Create the register script that uses the new register() API
|
|
const registerPath = join(tmpDir, 'register.mjs');
|
|
const loaderUrl = pathToFileURL(loaderPath).href;
|
|
const registerCode = `
|
|
import { register } from 'node:module';
|
|
import { pathToFileURL } from 'node:url';
|
|
|
|
register('${loaderUrl}', pathToFileURL('./'));
|
|
`;
|
|
writeFileSync(registerPath, registerCode);
|
|
|
|
// Preserve existing NODE_OPTIONS (e.g. VS Code debugger injects --inspect flags via NODE_OPTIONS)
|
|
const existingNodeOptions = process.env.NODE_OPTIONS || '';
|
|
const importFlag = `--import ${pathToFileURL(registerPath).href}`;
|
|
|
|
const env = {
|
|
...process.env,
|
|
DEV: 'true',
|
|
// Report the real package version (like scripts/start.js) so the UI shows
|
|
// e.g. "v0.19.4" instead of "dev". DEV=true / NODE_ENV=development remain the
|
|
// signals that distinguish a dev build.
|
|
CLI_VERSION: pkg.version,
|
|
NODE_ENV: 'development',
|
|
NODE_OPTIONS: `${existingNodeOptions} --expose-gc ${importFlag}`.trim(),
|
|
};
|
|
|
|
// On Windows, use tsx.cmd; on Unix, use tsx directly
|
|
const isWin = platform() === 'win32';
|
|
const tsxBinName = isWin ? 'tsx.cmd' : 'tsx';
|
|
const localTsxCli = join(root, 'node_modules', 'tsx', 'dist', 'cli.mjs');
|
|
const localTsxCmd = join(root, 'node_modules', '.bin', tsxBinName);
|
|
const hasLocalTsxCli = existsSync(localTsxCli);
|
|
const tsxCmd = hasLocalTsxCli
|
|
? process.execPath
|
|
: existsSync(localTsxCmd)
|
|
? localTsxCmd
|
|
: tsxBinName;
|
|
const tsxArgs = [
|
|
...(hasLocalTsxCli ? [localTsxCli] : []),
|
|
cliEntry,
|
|
...process.argv.slice(2),
|
|
];
|
|
const useShell = isWin && !hasLocalTsxCli;
|
|
|
|
const child = spawn(tsxCmd, tsxArgs, {
|
|
stdio: 'inherit',
|
|
env,
|
|
cwd: process.cwd(),
|
|
shell: useShell, // Needed only when falling back to tsx.cmd on Windows.
|
|
});
|
|
|
|
child.on('error', (err) => {
|
|
console.error('Failed to start dev server:', err.message);
|
|
try {
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
process.exit(1);
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
// Cleanup temp directory
|
|
try {
|
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
process.exit(code ?? 0);
|
|
});
|