diff --git a/packages/sdk-typescript/src/utils/cliPath.ts b/packages/sdk-typescript/src/utils/cliPath.ts index 99d604f1..4f031963 100644 --- a/packages/sdk-typescript/src/utils/cliPath.ts +++ b/packages/sdk-typescript/src/utils/cliPath.ts @@ -2,19 +2,10 @@ * CLI path auto-detection and subprocess spawning utilities * * Supports multiple execution modes: - * 1. Native binary: 'qwen' (production) - * 2. Node.js bundle: 'node /path/to/cli.js' (production validation) + * 1. Bundled CLI: Node.js bundle included in the SDK package (default) + * 2. Node.js bundle: 'node /path/to/cli.js' (custom path) * 3. Bun bundle: 'bun /path/to/cli.js' (alternative runtime) * 4. TypeScript source: 'tsx /path/to/index.ts' (development) - * - * Auto-detection locations for native binary: - * 1. QWEN_CODE_CLI_PATH environment variable - * 2. ~/.volta/bin/qwen - * 3. ~/.npm-global/bin/qwen - * 4. /usr/local/bin/qwen - * 5. ~/.local/bin/qwen - * 6. ~/node_modules/.bin/qwen - * 7. ~/.yarn/bin/qwen */ import * as fs from 'node:fs'; @@ -68,48 +59,11 @@ export function findNativeCliPath(): string { return bundledCli; } - const homeDir = process.env['HOME'] || process.env['USERPROFILE'] || ''; - - const candidates: Array = [ - // 1. Environment variable - process.env['QWEN_CODE_CLI_PATH'], - - // 2. Volta bin - path.join(homeDir, '.volta', 'bin', 'qwen'), - - // 3. Global npm installations - path.join(homeDir, '.npm-global', 'bin', 'qwen'), - - // 4. Common Unix binary locations - '/usr/local/bin/qwen', - - // 5. User local bin - path.join(homeDir, '.local', 'bin', 'qwen'), - - // 6. Node modules bin in home directory - path.join(homeDir, 'node_modules', '.bin', 'qwen'), - - // 7. Yarn global bin - path.join(homeDir, '.yarn', 'bin', 'qwen'), - ]; - - // Find first existing candidate - for (const candidate of candidates) { - if (candidate && fs.existsSync(candidate)) { - return path.resolve(candidate); - } - } - - // Not found - throw helpful error throw new Error( - 'qwen CLI not found. Please:\n' + - ' 1. Install qwen globally: npm install -g qwen\n' + - ' 2. Or provide explicit executable: query({ pathToQwenExecutable: "/path/to/qwen" })\n' + - ' 3. Or set environment variable: QWEN_CODE_CLI_PATH="/path/to/qwen"\n' + - '\n' + - 'For development/testing, you can also use:\n' + + 'Bundled qwen CLI not found. The CLI should be included in the SDK package.\n' + + 'If you need to use a custom CLI, provide explicit executable:\n' + + ' • query({ pathToQwenExecutable: "/path/to/cli.js" })\n' + ' • TypeScript source: query({ pathToQwenExecutable: "/path/to/index.ts" })\n' + - ' • Node.js bundle: query({ pathToQwenExecutable: "/path/to/cli.js" })\n' + ' • Force specific runtime: query({ pathToQwenExecutable: "bun:/path/to/cli.js" })', ); } diff --git a/packages/sdk-typescript/test/unit/cliPath.test.ts b/packages/sdk-typescript/test/unit/cliPath.test.ts index 3cabb9e5..c4253175 100644 --- a/packages/sdk-typescript/test/unit/cliPath.test.ts +++ b/packages/sdk-typescript/test/unit/cliPath.test.ts @@ -52,38 +52,26 @@ describe('CLI Path Utilities', () => { describe('parseExecutableSpec', () => { describe('auto-detection (no spec provided)', () => { - it('should auto-detect native CLI when no spec provided', () => { - // Mock environment variable - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - process.env['QWEN_CODE_CLI_PATH'] = '/usr/local/bin/qwen'; - // Mock existsSync to return false for bundled CLI, true for env var path + it('should auto-detect bundled CLI when no spec provided', () => { + // Mock existsSync to return true for bundled CLI mockFs.existsSync.mockImplementation((p) => { const pathStr = p.toString(); - if ( - pathStr.includes('cli/cli.js') || - pathStr.includes('cli\\cli.js') - ) { - return false; - } - return pathStr.includes('/usr/local/bin/qwen'); + return ( + pathStr.includes('cli/cli.js') || pathStr.includes('cli\\cli.js') + ); }); const result = parseExecutableSpec(); - expect(result).toEqual({ - executablePath: path.resolve('/usr/local/bin/qwen'), - isExplicitRuntime: false, - }); - - // Restore env - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(result.executablePath).toContain('cli.js'); + expect(result.isExplicitRuntime).toBe(false); }); - it('should throw when auto-detection fails', () => { + it('should throw when bundled CLI not found', () => { mockFs.existsSync.mockReturnValue(false); expect(() => parseExecutableSpec()).toThrow( - 'qwen CLI not found. Please:', + 'Bundled qwen CLI not found', ); }); }); @@ -373,83 +361,44 @@ describe('CLI Path Utilities', () => { }); describe('auto-detection fallback', () => { - it('should auto-detect when no spec provided', () => { - // Mock environment variable - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - process.env['QWEN_CODE_CLI_PATH'] = '/usr/local/bin/qwen'; - // Mock existsSync to return false for bundled CLI, true for env var path + it('should auto-detect bundled CLI when no spec provided', () => { + // Mock existsSync to return true for bundled CLI mockFs.existsSync.mockImplementation((p) => { const pathStr = p.toString(); - if ( - pathStr.includes('cli/cli.js') || - pathStr.includes('cli\\cli.js') - ) { - return false; - } - return pathStr.includes('/usr/local/bin/qwen'); + return ( + pathStr.includes('cli/cli.js') || pathStr.includes('cli\\cli.js') + ); }); const result = prepareSpawnInfo(); - expect(result).toEqual({ - command: path.resolve('/usr/local/bin/qwen'), - args: [], - type: 'native', - originalInput: '', - }); - - // Restore env - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(result.command).toBe(process.execPath); + expect(result.args[0]).toContain('cli.js'); + expect(result.type).toBe('node'); + expect(result.originalInput).toBe(''); }); }); }); describe('findNativeCliPath', () => { - it('should find CLI from environment variable', () => { - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - process.env['QWEN_CODE_CLI_PATH'] = '/custom/path/to/qwen'; - // Mock existsSync to return false for bundled CLI, true for env var path + it('should find bundled CLI', () => { + // Mock existsSync to return true for bundled CLI mockFs.existsSync.mockImplementation((p) => { const pathStr = p.toString(); - if (pathStr.includes('cli/cli.js') || pathStr.includes('cli\\cli.js')) { - return false; - } - return pathStr.includes('/custom/path/to/qwen'); + return ( + pathStr.includes('cli/cli.js') || pathStr.includes('cli\\cli.js') + ); }); const result = findNativeCliPath(); - expect(result).toBe(path.resolve('/custom/path/to/qwen')); - - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(result).toContain('cli.js'); }); - it('should search common installation locations', () => { - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - delete process.env['QWEN_CODE_CLI_PATH']; - - // Mock fs.existsSync to return true for volta bin - // Use path.join to match platform-specific path separators - const voltaBinPath = path.join('.volta', 'bin', 'qwen'); - mockFs.existsSync.mockImplementation((p) => { - return p.toString().includes(voltaBinPath); - }); - - const result = findNativeCliPath(); - - expect(result).toContain(voltaBinPath); - - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; - }); - - it('should throw descriptive error when CLI not found', () => { - const originalEnv = process.env['QWEN_CODE_CLI_PATH']; - delete process.env['QWEN_CODE_CLI_PATH']; + it('should throw descriptive error when bundled CLI not found', () => { mockFs.existsSync.mockReturnValue(false); - expect(() => findNativeCliPath()).toThrow('qwen CLI not found. Please:'); - - process.env['QWEN_CODE_CLI_PATH'] = originalEnv; + expect(() => findNativeCliPath()).toThrow('Bundled qwen CLI not found'); }); }); @@ -664,13 +613,10 @@ describe('CLI Path Utilities', () => { mockFs.existsSync.mockReturnValue(false); expect(() => parseExecutableSpec('/missing/file')).toThrow( - 'Set QWEN_CODE_CLI_PATH environment variable', + 'Executable file not found at', ); expect(() => parseExecutableSpec('/missing/file')).toThrow( - 'Install qwen globally: npm install -g qwen', - ); - expect(() => parseExecutableSpec('/missing/file')).toThrow( - 'Force specific runtime: bun:/path/to/cli.js or tsx:/path/to/index.ts', + 'Please check the file path and ensure the file exists', ); }); });