From 9bc3e896782989028e62b7fbfeda48732f2cecd7 Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 29 Apr 2026 11:42:09 +1000 Subject: [PATCH 1/2] fix: resolve self-update package manager command Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/cli/src/self-update.ts | 60 +++++++++++++++++++++++++++++-- apps/cli/test/self-update.test.ts | 49 ++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/apps/cli/src/self-update.ts b/apps/cli/src/self-update.ts index 2da2bf16..4ac06fe3 100644 --- a/apps/cli/src/self-update.ts +++ b/apps/cli/src/self-update.ts @@ -16,12 +16,19 @@ * `node_modules/.bin/agentv`); update the local dep instead of the global * install. Otherwise, update globally (default). * + * Package-manager command resolution prefers runtime-adjacent executables + * (for example Node's bundled npm-cli.js or the current Bun executable) + * before falling back to PATH. This keeps self-update working in shells + * where `agentv` is reachable but `npm`/`bun` are not on PATH. + * * To add a new package manager: add a case to `detectPackageManagerFromPath()` - * and a corresponding install-args entry in `getInstallArgs()`. + * and a corresponding install-args / resolver entry below. */ import { spawn } from 'node:child_process'; +import { existsSync } from 'node:fs'; import { get } from 'node:https'; +import { basename, dirname, join, win32 } from 'node:path'; const NPM_REGISTRY_URL = 'https://registry.npmjs.org/agentv/latest'; @@ -116,6 +123,54 @@ export function getInstallArgs( return scope === 'global' ? [baseCmd, '-g', pkg] : [baseCmd, pkg]; } +function findBundledNpmCli( + execPath: string, + platform: NodeJS.Platform, + exists: (path: string) => boolean, +): string | undefined { + const pathApi = platform === 'win32' ? win32 : { dirname, join }; + const execDir = pathApi.dirname(execPath); + const candidates = + platform === 'win32' + ? [pathApi.join(execDir, 'node_modules', 'npm', 'bin', 'npm-cli.js')] + : [ + pathApi.join(execDir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'), + pathApi.join(execDir, 'node_modules', 'npm', 'bin', 'npm-cli.js'), + ]; + + return candidates.find((candidate) => exists(candidate)); +} + +export function resolvePackageManagerCommand( + pm: 'bun' | 'npm', + args: string[], + options?: { + execPath?: string; + platform?: NodeJS.Platform; + exists?: (path: string) => boolean; + }, +): { cmd: string; args: string[] } { + const execPath = options?.execPath ?? process.execPath; + const platform = options?.platform ?? process.platform; + const exists = options?.exists ?? existsSync; + const pathApi = platform === 'win32' ? win32 : { basename }; + + if (pm === 'bun') { + const runtimeName = pathApi.basename(execPath).toLowerCase(); + if ((runtimeName === 'bun' || runtimeName === 'bun.exe') && exists(execPath)) { + return { cmd: execPath, args }; + } + return { cmd: 'bun', args }; + } + + const npmCliPath = findBundledNpmCli(execPath, platform, exists); + if (npmCliPath) { + return { cmd: execPath, args: [npmCliPath, ...args] }; + } + + return { cmd: 'npm', args }; +} + /** * Run the self-update flow: install agentv using the detected (or specified) * package manager, scoped to the detected install location (global by default, @@ -146,9 +201,10 @@ export async function performSelfUpdate(options?: { const scope = options?.scope ?? detectInstallScope(); const args = getInstallArgs(pm, versionSpec, scope); + const command = resolvePackageManagerCommand(pm, args); try { - const result = await runCommand(pm, args); + const result = await runCommand(command.cmd, command.args); if (result.exitCode !== 0) { return { success: false, currentVersion, scope }; diff --git a/apps/cli/test/self-update.test.ts b/apps/cli/test/self-update.test.ts index 17620b05..9b79ad3e 100644 --- a/apps/cli/test/self-update.test.ts +++ b/apps/cli/test/self-update.test.ts @@ -3,7 +3,7 @@ import { detectInstallScopeFromPath, detectPackageManagerFromPath, } from '../src/commands/self/index.js'; -import { getInstallArgs } from '../src/self-update.js'; +import { getInstallArgs, resolvePackageManagerCommand } from '../src/self-update.js'; describe('detectPackageManagerFromPath', () => { test('detects bun when path contains .bun', () => { @@ -93,3 +93,50 @@ describe('getInstallArgs', () => { expect(getInstallArgs('npm', '>=4.1.0', 'local')).toEqual(['install', 'agentv@>=4.1.0']); }); }); + +describe('resolvePackageManagerCommand', () => { + test('resolves npm via npm-cli.js next to process.execPath on Windows', () => { + const execPath = 'C:\\Program Files\\nodejs\\node.exe'; + const npmCliPath = 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js'; + + expect( + resolvePackageManagerCommand('npm', ['install', '-g', 'agentv@latest'], { + execPath, + platform: 'win32', + exists: (candidate) => candidate === npmCliPath, + }), + ).toEqual({ + cmd: execPath, + args: [npmCliPath, 'install', '-g', 'agentv@latest'], + }); + }); + + test('resolves npm via bundled npm-cli.js near node on unix-like installs', () => { + const execPath = '/home/user/.nvm/versions/node/v20.19.0/bin/node'; + const npmCliPath = '/home/user/.nvm/versions/node/v20.19.0/lib/node_modules/npm/bin/npm-cli.js'; + + expect( + resolvePackageManagerCommand('npm', ['install', '-g', 'agentv@latest'], { + execPath, + platform: 'linux', + exists: (candidate) => candidate === npmCliPath, + }), + ).toEqual({ + cmd: execPath, + args: [npmCliPath, 'install', '-g', 'agentv@latest'], + }); + }); + + test('falls back to PATH when no runtime-adjacent npm installation is found', () => { + expect( + resolvePackageManagerCommand('npm', ['install', '-g', 'agentv@latest'], { + execPath: '/usr/bin/node', + platform: 'linux', + exists: () => false, + }), + ).toEqual({ + cmd: 'npm', + args: ['install', '-g', 'agentv@latest'], + }); + }); +}); From 7785ace7bd632c0ace3b5bc82fa85f0cfba744ff Mon Sep 17 00:00:00 2001 From: Christopher Date: Wed, 29 Apr 2026 11:44:22 +1000 Subject: [PATCH 2/2] chore: fix pre-push lint order Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/check-grader-scores.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check-grader-scores.ts b/scripts/check-grader-scores.ts index 9a86a1db..e10a5fc7 100644 --- a/scripts/check-grader-scores.ts +++ b/scripts/check-grader-scores.ts @@ -17,9 +17,9 @@ * 4. Run this script to verify. */ -import { readFileSync, existsSync } from 'node:fs'; -import path from 'node:path'; import { globSync } from 'node:fs'; +import { existsSync, readFileSync } from 'node:fs'; +import path from 'node:path'; import { parse as parseYaml } from 'yaml'; // ---------------------------------------------------------------------------