From 7cb413988ffce5bd438aec8e0bdcee1ee3a94f7a Mon Sep 17 00:00:00 2001 From: roboomp Date: Thu, 2 Jul 2026 11:48:42 +0000 Subject: [PATCH] fix(cli): routed update plugin shorthand Added the documented update -l path for marketplace plugin upgrades and covered the dispatch contract with update command tests. Fixes #4304 --- packages/coding-agent/CHANGELOG.md | 4 ++ packages/coding-agent/src/cli/update-cli.ts | 15 ++++--- packages/coding-agent/src/commands/update.ts | 10 ++++- packages/coding-agent/test/update-cli.test.ts | 43 ++++++++++++++++++- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 64c3c57ae..ea75b9dc3 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed `omp update -l` rejecting the documented plugin-update shorthand instead of upgrading installed marketplace plugins. ([#4304](https://github.com/can1357/oh-my-pi/issues/4304)) + ## [16.3.1] - 2026-07-02 ### Breaking Changes diff --git a/packages/coding-agent/src/cli/update-cli.ts b/packages/coding-agent/src/cli/update-cli.ts index 8c21d7d42..e38b26e7b 100644 --- a/packages/coding-agent/src/cli/update-cli.ts +++ b/packages/coding-agent/src/cli/update-cli.ts @@ -82,7 +82,7 @@ export interface BinaryReplacementOptions { * Parse update subcommand arguments. * Returns undefined if not an update command. */ -export function parseUpdateArgs(args: string[]): { force: boolean; check: boolean } | undefined { +export function parseUpdateArgs(args: string[]): { force: boolean; check: boolean; plugins: boolean } | undefined { if (args.length === 0 || args[0] !== "update") { return undefined; } @@ -90,6 +90,7 @@ export function parseUpdateArgs(args: string[]): { force: boolean; check: boolea return { force: args.includes("--force") || args.includes("-f"), check: args.includes("--check") || args.includes("-c"), + plugins: args.includes("--plugins") || args.includes("-l"), }; } @@ -914,12 +915,14 @@ ${chalk.bold("Usage:")} ${APP_NAME} update [options] ${chalk.bold("Options:")} - -c, --check Check for updates without installing - -f, --force Force reinstall even if up to date + -c, --check Check for updates without installing + -f, --force Force reinstall even if up to date + -l, --plugins Update installed plugins ${chalk.bold("Examples:")} - ${APP_NAME} update Update to latest version - ${APP_NAME} update --check Check if updates are available - ${APP_NAME} update --force Force reinstall + ${APP_NAME} update Update to latest version + ${APP_NAME} update --check Check if updates are available + ${APP_NAME} update --force Force reinstall + ${APP_NAME} update -l Update installed plugins `); } diff --git a/packages/coding-agent/src/commands/update.ts b/packages/coding-agent/src/commands/update.ts index b8942f696..88462f550 100644 --- a/packages/coding-agent/src/commands/update.ts +++ b/packages/coding-agent/src/commands/update.ts @@ -2,7 +2,8 @@ * Check for and install updates. */ import { Command, Flags } from "@oh-my-pi/pi-utils/cli"; -import { runUpdateCommand } from "../cli/update-cli"; +import * as pluginCli from "../cli/plugin-cli"; +import * as updateCli from "../cli/update-cli"; import { initTheme } from "../modes/theme/theme"; export default class Update extends Command { @@ -11,11 +12,16 @@ export default class Update extends Command { static flags = { force: Flags.boolean({ char: "f", description: "Force update", default: false }), check: Flags.boolean({ char: "c", description: "Check for updates without installing", default: false }), + plugins: Flags.boolean({ char: "l", description: "Update installed plugins", default: false }), }; async run(): Promise { const { flags } = await this.parse(Update); await initTheme(); - await runUpdateCommand({ force: flags.force, check: flags.check }); + if (flags.plugins) { + await pluginCli.runPluginCommand({ action: "upgrade", args: [], flags: {} }); + } else { + await updateCli.runUpdateCommand({ force: flags.force, check: flags.check }); + } } } diff --git a/packages/coding-agent/test/update-cli.test.ts b/packages/coding-agent/test/update-cli.test.ts index 39d771af3..75323040d 100644 --- a/packages/coding-agent/test/update-cli.test.ts +++ b/packages/coding-agent/test/update-cli.test.ts @@ -1,20 +1,25 @@ -import { afterEach, describe, expect, it, spyOn } from "bun:test"; +import { afterEach, describe, expect, it, spyOn, vi } from "bun:test"; import * as nodeFs from "node:fs"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import * as pluginCli from "@oh-my-pi/pi-coding-agent/cli/plugin-cli"; +import * as updateCli from "@oh-my-pi/pi-coding-agent/cli/update-cli"; import { buildBunInstallArgs, buildHomebrewUpdateArgs, buildMiseForceInstallArgs, buildMiseUpgradeArgs, + parseUpdateArgs, pruneBunInstallCache, replaceBinaryForUpdate, resolveBunGlobalNodeModulesDirFromLocations, resolveUpdateMethodForTest, sweepStaleBackups, } from "@oh-my-pi/pi-coding-agent/cli/update-cli"; +import Update from "@oh-my-pi/pi-coding-agent/commands/update"; import { removeWithRetries } from "@oh-my-pi/pi-utils"; +import type { CliConfig } from "@oh-my-pi/pi-utils/cli"; const tempDirs: string[] = []; @@ -25,8 +30,44 @@ async function makeTempDir(): Promise { } afterEach(async () => { + vi.restoreAllMocks(); await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir))); }); +const TEST_CONFIG: CliConfig = { + bin: "omp", + version: "0.0.0-test", + commands: new Map(), +}; + +describe("update command plugin dispatch", () => { + it("routes -l to plugin upgrade instead of the app updater", async () => { + const pluginSpy = spyOn(pluginCli, "runPluginCommand").mockResolvedValue(undefined); + const updateSpy = spyOn(updateCli, "runUpdateCommand").mockResolvedValue(undefined); + + const command = new Update(["-l"], TEST_CONFIG); + await command.run(); + + expect(pluginSpy).toHaveBeenCalledWith({ action: "upgrade", args: [], flags: {} }); + expect(updateSpy).not.toHaveBeenCalled(); + }); + + it("keeps normal update flags on the app updater path", async () => { + const pluginSpy = spyOn(pluginCli, "runPluginCommand").mockResolvedValue(undefined); + const updateSpy = spyOn(updateCli, "runUpdateCommand").mockResolvedValue(undefined); + + const command = new Update(["--check", "--force"], TEST_CONFIG); + await command.run(); + + expect(updateSpy).toHaveBeenCalledWith({ force: true, check: true }); + expect(pluginSpy).not.toHaveBeenCalled(); + }); +}); + +describe("parseUpdateArgs", () => { + it("preserves the legacy plugin update shorthand", () => { + expect(parseUpdateArgs(["update", "-l"])).toEqual({ force: false, check: false, plugins: true }); + }); +}); describe("update-cli install target detection", () => { it("uses bun update when prioritized omp is inside bun global bin", () => { const method = resolveUpdateMethodForTest("/Users/test/.bun/bin/omp", "/Users/test/.bun/bin");