fix(coding-agent): normalize local package removal paths (fixes #1243)

This commit is contained in:
Mario Zechner 2026-02-04 12:55:49 +01:00
parent 150aeebf7d
commit b1c2c95f23
2 changed files with 65 additions and 3 deletions

View file

@ -94,7 +94,7 @@ function expandTildePath(input: string): string {
function resolveLocalSourceFromInput(source: string, cwd: string): string {
const expanded = expandTildePath(source);
return isAbsolute(expanded) ? expanded : resolve(cwd, expanded);
return isAbsolute(expanded) ? resolve(expanded) : resolve(cwd, expanded);
}
function resolveLocalSourceFromSettings(source: string, baseDir: string): string {
@ -172,18 +172,21 @@ function updatePackageSources(
cwd: string,
agentDir: string,
action: "add" | "remove",
): void {
): boolean {
const currentSettings = local ? settingsManager.getProjectSettings() : settingsManager.getGlobalSettings();
const currentPackages = currentSettings.packages ?? [];
const baseDir = local ? join(cwd, CONFIG_DIR_NAME) : agentDir;
const normalizedSource = normalizePackageSourceForSettings(source, baseDir, cwd);
let nextPackages: PackageSource[];
let changed = false;
if (action === "add") {
const exists = currentPackages.some((existing) => packageSourcesMatch(existing, source, baseDir, cwd));
nextPackages = exists ? currentPackages : [...currentPackages, normalizedSource];
changed = !exists;
} else {
nextPackages = currentPackages.filter((existing) => !packageSourcesMatch(existing, source, baseDir, cwd));
changed = nextPackages.length !== currentPackages.length;
}
if (local) {
@ -191,6 +194,8 @@ function updatePackageSources(
} else {
settingsManager.setPackages(nextPackages);
}
return changed;
}
async function handlePackageCommand(args: string[]): Promise<boolean> {
@ -230,7 +235,11 @@ async function handlePackageCommand(args: string[]): Promise<boolean> {
process.exit(1);
}
await packageManager.remove(options.source, { local: options.local });
updatePackageSources(settingsManager, options.source, options.local, cwd, agentDir, "remove");
const removed = updatePackageSources(settingsManager, options.source, options.local, cwd, agentDir, "remove");
if (!removed) {
console.error(chalk.red(`No matching package found for ${options.source}`));
process.exit(1);
}
console.log(chalk.green(`Removed ${options.source}`));
return true;
}

View file

@ -0,0 +1,53 @@
import { mkdirSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ENV_AGENT_DIR } from "../src/config.js";
import { main } from "../src/main.js";
describe("package commands", () => {
let tempDir: string;
let agentDir: string;
let projectDir: string;
let packageDir: string;
let originalCwd: string;
let originalAgentDir: string | undefined;
beforeEach(() => {
tempDir = join(tmpdir(), `pi-package-commands-${Date.now()}-${Math.random().toString(36).slice(2)}`);
agentDir = join(tempDir, "agent");
projectDir = join(tempDir, "project");
packageDir = join(tempDir, "local-package");
mkdirSync(agentDir, { recursive: true });
mkdirSync(projectDir, { recursive: true });
mkdirSync(packageDir, { recursive: true });
originalCwd = process.cwd();
originalAgentDir = process.env[ENV_AGENT_DIR];
process.env[ENV_AGENT_DIR] = agentDir;
process.chdir(projectDir);
});
afterEach(() => {
process.chdir(originalCwd);
if (originalAgentDir === undefined) {
delete process.env[ENV_AGENT_DIR];
} else {
process.env[ENV_AGENT_DIR] = originalAgentDir;
}
rmSync(tempDir, { recursive: true, force: true });
});
it("should remove local packages using a path with a trailing slash", async () => {
await main(["install", `${packageDir}/`]);
const settingsPath = join(agentDir, "settings.json");
const installedSettings = JSON.parse(readFileSync(settingsPath, "utf-8")) as { packages?: string[] };
expect(installedSettings.packages?.length).toBe(1);
await main(["remove", `${packageDir}/`]);
const removedSettings = JSON.parse(readFileSync(settingsPath, "utf-8")) as { packages?: string[] };
expect(removedSettings.packages ?? []).toHaveLength(0);
});
});