fix(coding-agent,tui): stabilize windows shell/path handling
fixes #1775 fixes #1769
This commit is contained in:
@@ -5,12 +5,30 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { DefaultPackageManager, type ProgressEvent, type ResolvedResource } from "../src/core/package-manager.js";
|
||||
import { SettingsManager } from "../src/core/settings-manager.js";
|
||||
|
||||
// Helper to check if a resource is enabled
|
||||
const isEnabled = (r: ResolvedResource, pathMatch: string, matchFn: "endsWith" | "includes" = "endsWith") =>
|
||||
matchFn === "endsWith" ? r.path.endsWith(pathMatch) && r.enabled : r.path.includes(pathMatch) && r.enabled;
|
||||
function normalizeForMatch(value: string): string {
|
||||
return value.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
const isDisabled = (r: ResolvedResource, pathMatch: string, matchFn: "endsWith" | "includes" = "endsWith") =>
|
||||
matchFn === "endsWith" ? r.path.endsWith(pathMatch) && !r.enabled : r.path.includes(pathMatch) && !r.enabled;
|
||||
function pathEndsWith(actualPath: string, suffix: string): boolean {
|
||||
return normalizeForMatch(actualPath).endsWith(normalizeForMatch(suffix));
|
||||
}
|
||||
|
||||
// Helper to check if a resource is enabled
|
||||
const isEnabled = (r: ResolvedResource, pathMatch: string, matchFn: "endsWith" | "includes" = "endsWith") => {
|
||||
const normalizedPath = normalizeForMatch(r.path);
|
||||
const normalizedMatch = normalizeForMatch(pathMatch);
|
||||
return matchFn === "endsWith"
|
||||
? normalizedPath.endsWith(normalizedMatch) && r.enabled
|
||||
: normalizedPath.includes(normalizedMatch) && r.enabled;
|
||||
};
|
||||
|
||||
const isDisabled = (r: ResolvedResource, pathMatch: string, matchFn: "endsWith" | "includes" = "endsWith") => {
|
||||
const normalizedPath = normalizeForMatch(r.path);
|
||||
const normalizedMatch = normalizeForMatch(pathMatch);
|
||||
return matchFn === "endsWith"
|
||||
? normalizedPath.endsWith(normalizedMatch) && !r.enabled
|
||||
: normalizedPath.includes(normalizedMatch) && !r.enabled;
|
||||
};
|
||||
|
||||
describe("DefaultPackageManager", () => {
|
||||
let tempDir: string;
|
||||
@@ -155,7 +173,7 @@ Content`,
|
||||
);
|
||||
|
||||
// Should NOT find helper.ts (not declared in manifest)
|
||||
expect(result.extensions.some((r) => r.path.endsWith("helper.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "helper.ts"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -331,8 +349,8 @@ Content`,
|
||||
writeFileSync(join(pkgDir, "themes", "dark.json"), "{}");
|
||||
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
expect(result.extensions.some((r) => r.path.endsWith("main.ts") && r.enabled)).toBe(true);
|
||||
expect(result.themes.some((r) => r.path.endsWith("dark.json") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "main.ts") && r.enabled)).toBe(true);
|
||||
expect(result.themes.some((r) => pathEndsWith(r.path, "dark.json") && r.enabled)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -731,7 +749,7 @@ Content`,
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
expect(result.extensions.some((r) => isEnabled(r, "local.ts"))).toBe(true);
|
||||
expect(result.extensions.some((r) => isEnabled(r, "remote.ts"))).toBe(true);
|
||||
expect(result.extensions.some((r) => r.path.endsWith("skip.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "skip.ts"))).toBe(false);
|
||||
});
|
||||
|
||||
it("should support glob patterns in manifest skills", async () => {
|
||||
@@ -798,7 +816,7 @@ Content`,
|
||||
// bar.ts should be excluded (by user)
|
||||
expect(result.extensions.some((r) => isDisabled(r, "bar.ts"))).toBe(true);
|
||||
// baz.ts should be excluded (by manifest)
|
||||
expect(result.extensions.some((r) => r.path.endsWith("baz.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "baz.ts"))).toBe(false);
|
||||
});
|
||||
|
||||
it("should exclude extensions from package with ! pattern", async () => {
|
||||
@@ -1194,11 +1212,11 @@ export default function(api) { api.registerTool({ name: "test", description: "te
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
|
||||
// Should find the index.ts and standalone.ts
|
||||
expect(result.extensions.some((r) => r.path.endsWith("subagent/index.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => r.path.endsWith("standalone.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "subagent/index.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "standalone.ts") && r.enabled)).toBe(true);
|
||||
|
||||
// Should NOT find agents.ts as a standalone extension
|
||||
expect(result.extensions.some((r) => r.path.endsWith("agents.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "agents.ts"))).toBe(false);
|
||||
});
|
||||
|
||||
it("should respect package.json pi.extensions manifest in subdirectories", async () => {
|
||||
@@ -1220,10 +1238,10 @@ export default function(api) { api.registerTool({ name: "test", description: "te
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
|
||||
// Should find main.ts declared in manifest
|
||||
expect(result.extensions.some((r) => r.path.endsWith("custom/main.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "custom/main.ts") && r.enabled)).toBe(true);
|
||||
|
||||
// Should NOT find utils.ts (not declared in manifest)
|
||||
expect(result.extensions.some((r) => r.path.endsWith("utils.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "utils.ts"))).toBe(false);
|
||||
});
|
||||
|
||||
it("should handle mixed top-level files and subdirectories", async () => {
|
||||
@@ -1244,12 +1262,12 @@ export default function(api) { api.registerTool({ name: "test", description: "te
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
|
||||
// Should find simple.ts and complex/index.ts
|
||||
expect(result.extensions.some((r) => r.path.endsWith("simple.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => r.path.endsWith("complex/index.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "simple.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "complex/index.ts") && r.enabled)).toBe(true);
|
||||
|
||||
// Should NOT find helper modules
|
||||
expect(result.extensions.some((r) => r.path.endsWith("complex/a.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => r.path.endsWith("complex/b.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "complex/a.ts"))).toBe(false);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "complex/b.ts"))).toBe(false);
|
||||
|
||||
// Total should be exactly 2
|
||||
expect(result.extensions.filter((r) => r.enabled).length).toBe(2);
|
||||
@@ -1269,7 +1287,7 @@ export default function(api) { api.registerTool({ name: "test", description: "te
|
||||
const result = await packageManager.resolveExtensionSources([pkgDir]);
|
||||
|
||||
// Should only find the valid top-level extension
|
||||
expect(result.extensions.some((r) => r.path.endsWith("valid.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "valid.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.filter((r) => r.enabled).length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1299,7 +1317,7 @@ export default function(api) { api.registerTool({ name: "test", description: "te
|
||||
const refreshTemporaryGitSourceSpy = vi.spyOn(packageManager as any, "refreshTemporaryGitSource");
|
||||
|
||||
const result = await packageManager.resolveExtensionSources([gitSource], { temporary: true });
|
||||
expect(result.extensions.some((r) => r.path.endsWith("extensions/index.ts") && r.enabled)).toBe(true);
|
||||
expect(result.extensions.some((r) => pathEndsWith(r.path, "extensions/index.ts") && r.enabled)).toBe(true);
|
||||
expect(refreshTemporaryGitSourceSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user