fix(projects): scope legacy paths to hosts

This commit is contained in:
Mohamed Boudra
2026-07-29 10:20:55 +00:00
parent 1ff152edcd
commit fef479e749
3 changed files with 36 additions and 9 deletions

View File

@@ -0,0 +1,18 @@
import { describe, expect, test } from "vitest";
import { resolveProjectKey } from "./project-key";
describe("resolveProjectKey", () => {
test("keeps legacy path-shaped project IDs local to their host", () => {
const resolve = (serverId: string) =>
resolveProjectKey({ serverId, projectId: "/workspace/app" });
expect(resolve("host-a")).not.toBe(resolve("host-b"));
});
test("keeps recognized legacy remote project IDs shared across hosts", () => {
const resolve = (serverId: string) =>
resolveProjectKey({ serverId, projectId: "remote:github.com/acme/app" });
expect(resolve("host-a")).toBe(resolve("host-b"));
});
});

View File

@@ -5,9 +5,9 @@ export function resolveProjectKey(input: {
}): string { }): string {
if (input.projectKey) return input.projectKey; if (input.projectKey) return input.projectKey;
// COMPAT(projectKey): added in v0.2.4 on 2026-07-28; remove after 2027-01-28. // COMPAT(projectKey): added in v0.2.4 on 2026-07-28; remove after 2027-01-28.
// Older daemons used remote/path-shaped project IDs as their grouping key. New opaque IDs // Older daemons used recognized remote-shaped project IDs as their grouping key. Their
// must remain host-local when the new field is absent. // path-shaped and opaque IDs are host-local when the new field is absent.
return input.projectId.startsWith("prj_") ? frameHostProjectKey(input) : input.projectId; return input.projectId.startsWith("remote:") ? input.projectId : frameHostProjectKey(input);
} }
export function frameHostProjectKey(input: { serverId: string; projectId: string }): string { export function frameHostProjectKey(input: { serverId: string; projectId: string }): string {

View File

@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import type { ProjectPlacementPayload } from "@getpaseo/protocol/messages"; import type { ProjectPlacementPayload } from "@getpaseo/protocol/messages";
import { frameHostProjectKey } from "@/projects/project-key";
import type { WorkspaceDescriptor } from "@/stores/session-store"; import type { WorkspaceDescriptor } from "@/stores/session-store";
import { buildProjects } from "./projects"; import { buildProjects } from "./projects";
@@ -451,7 +452,11 @@ describe("buildProjects", () => {
const acme = result.projects.find( const acme = result.projects.find(
(project) => project.projectKey === "remote:github.com/acme/app#subdir:packages/client", (project) => project.projectKey === "remote:github.com/acme/app#subdir:packages/client",
); );
const legacy = result.projects.find((project) => project.projectKey === "legacy-project"); const legacy = result.projects.find(
(project) =>
project.projectKey ===
frameHostProjectKey({ serverId: "legacy", projectId: "legacy-project" }),
);
expect(acme?.hosts[0]?.repoRoot).toBe("/worktrees/app/main/packages/client"); expect(acme?.hosts[0]?.repoRoot).toBe("/worktrees/app/main/packages/client");
expect(legacy?.hosts[0]?.repoRoot).toBe("/repo/legacy"); expect(legacy?.hosts[0]?.repoRoot).toBe("/repo/legacy");
@@ -584,8 +589,8 @@ describe("buildProjects", () => {
}); });
expect(result.projects.map((project) => project.projectKey)).toEqual([ expect(result.projects.map((project) => project.projectKey)).toEqual([
"/repo/one", frameHostProjectKey({ serverId: "local", projectId: "/repo/one" }),
"/repo/two", frameHostProjectKey({ serverId: "local", projectId: "/repo/two" }),
]); ]);
}); });
@@ -646,7 +651,7 @@ describe("buildProjects", () => {
"remote:gitlab.com/acme/api", "remote:gitlab.com/acme/api",
"remote:bitbucket.org/acme/cli", "remote:bitbucket.org/acme/cli",
"remote:github.com/acme/web", "remote:github.com/acme/web",
"/repo/local", frameHostProjectKey({ serverId: "local", projectId: "/repo/local" }),
]); ]);
}); });
@@ -739,7 +744,9 @@ describe("buildProjects", () => {
expect(result.projects).toHaveLength(1); expect(result.projects).toHaveLength(1);
const summary = result.projects[0]; const summary = result.projects[0];
expect(summary?.projectKey).toBe("legacy-project"); expect(summary?.projectKey).toBe(
frameHostProjectKey({ serverId: "old-daemon", projectId: "legacy-project" }),
);
expect(summary?.projectName).toBe("Legacy"); expect(summary?.projectName).toBe("Legacy");
expect(summary?.githubUrl).toBeUndefined(); expect(summary?.githubUrl).toBeUndefined();
expect(summary?.hosts).toHaveLength(1); expect(summary?.hosts).toHaveLength(1);
@@ -770,7 +777,9 @@ describe("buildProjects", () => {
expect(result.projects).toHaveLength(1); expect(result.projects).toHaveLength(1);
const summary = result.projects[0]; const summary = result.projects[0];
expect(summary?.projectKey).toBe("/repo/fresh"); expect(summary?.projectKey).toBe(
frameHostProjectKey({ serverId: "local", projectId: "/repo/fresh" }),
);
expect(summary?.totalWorkspaceCount).toBe(0); expect(summary?.totalWorkspaceCount).toBe(0);
expect(summary?.hosts).toHaveLength(1); expect(summary?.hosts).toHaveLength(1);
// repoRoot must be non-empty or the project settings screen treats the host // repoRoot must be non-empty or the project settings screen treats the host