diff --git a/package.json b/package.json index bbe726e..ff2db18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@valyu/cli", - "version": "1.0.7", + "version": "1.0.8", "description": "The search CLI for knowledge workers", "license": "MIT", "repository": { diff --git a/skills/valyu-cli/SKILL.md b/skills/valyu-cli/SKILL.md index 347c6f9..312b860 100644 --- a/skills/valyu-cli/SKILL.md +++ b/skills/valyu-cli/SKILL.md @@ -12,7 +12,7 @@ description: > license: MIT metadata: author: valyu - version: "1.0.7" + version: "1.0.8" homepage: https://valyu.ai source: https://github.com/valyuAI/valyu-cli inputs: diff --git a/src/__tests__/install-source.test.ts b/src/__tests__/install-source.test.ts new file mode 100644 index 0000000..6646267 --- /dev/null +++ b/src/__tests__/install-source.test.ts @@ -0,0 +1,66 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +// The function under test depends on process.argv + process.execPath. +// We import fresh each test so any module-level caching (none today, but +// future-proof) doesn't leak between scenarios. +async function loadDetect() { + vi.resetModules(); + const mod = await import('../lib/install-source.js'); + return mod.detectInstallSource; +} + +describe('detectInstallSource', () => { + let origArgv: string[]; + let origExecPath: string; + + beforeEach(() => { + origArgv = process.argv; + origExecPath = process.execPath; + }); + + afterEach(() => { + process.argv = origArgv; + Object.defineProperty(process, 'execPath', { value: origExecPath, configurable: true }); + }); + + function setExec(path: string) { + Object.defineProperty(process, 'execPath', { value: path, configurable: true }); + } + + it('classifies a pkg-bundled Homebrew binary via process.execPath even when argv[1] is a /snapshot/ path', async () => { + setExec('/opt/homebrew/Cellar/valyu/1.0.6/bin/valyu'); + process.argv = ['/opt/homebrew/Cellar/valyu/1.0.6/bin/valyu', '/snapshot/valyu-cli/dist/cli.cjs']; + const detect = await loadDetect(); + const src = detect(); + expect(src.kind).toBe('homebrew'); + }); + + it('classifies a standalone user-local binary via process.execPath', async () => { + setExec('/Users/example/.local/bin/valyu'); + process.argv = ['/Users/example/.local/bin/valyu', '/snapshot/valyu-cli/dist/cli.cjs']; + const detect = await loadDetect(); + const src = detect(); + expect(src.kind).toBe('binary'); + if (src.kind === 'binary') expect(src.location).toBe('user-local'); + }); + + it('classifies an npm global install via argv[1] (execPath is node, not valyu)', async () => { + setExec('/usr/local/bin/node'); + process.argv = [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/@valyu/cli/dist/cli.cjs', + ]; + const detect = await loadDetect(); + const src = detect(); + expect(src.kind).toBe('npm-global'); + if (src.kind === 'npm-global') expect(src.manager).toBe('npm'); + }); + + it('falls back to "unknown" for paths that do not match any known install', async () => { + setExec('/usr/local/bin/node'); + process.argv = ['/usr/local/bin/node', '/some/random/place/cli.cjs']; + const detect = await loadDetect(); + const src = detect(); + expect(src.kind).toBe('unknown'); + }); +}); diff --git a/src/lib/install-source.ts b/src/lib/install-source.ts index 7d571c9..43273bf 100644 --- a/src/lib/install-source.ts +++ b/src/lib/install-source.ts @@ -9,9 +9,38 @@ export type InstallSource = | { kind: 'dev'; path: string } | { kind: 'unknown'; path: string }; +// For `pkg`-compiled standalone binaries (Homebrew, the curl installer, +// the Windows PowerShell installer), `process.argv[1]` resolves to a +// virtual snapshot path like `/snapshot/valyu-cli/dist/cli.cjs` that +// lives inside the bundle - useless for install-source detection. The +// real binary path is `process.execPath` in that case. +// +// For Node-script invocations (npm global install, dev via tsx/node), +// `process.execPath` is just `node` itself, so the script path in +// `process.argv[1]` is what we want. +// +// Heuristic: if `process.execPath` has a `valyu`-shaped basename, we're +// running a compiled binary and should trust it. Otherwise fall back +// to argv[1]. function executablePath(): string { + const exe = process.execPath; + const exeLower = exe.toLowerCase(); + const looksLikeCompiledBinary = + exeLower.endsWith('/valyu') || + exeLower.endsWith('\\valyu.exe') || + exeLower.endsWith('/valyu.exe') || + /\/cellar\/valyu\//.test(exeLower); + + if (looksLikeCompiledBinary) { + try { + return realpathSync(exe); + } catch { + return exe; + } + } + const script = process.argv[1]; - if (!script) return process.execPath; + if (!script) return exe; try { return realpathSync(script); } catch { diff --git a/src/lib/version.ts b/src/lib/version.ts index 3411384..b0c931f 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -1,2 +1,2 @@ -export const VERSION = '1.0.7'; +export const VERSION = '1.0.8'; export const PACKAGE_NAME = '@valyu/cli';