feat: move workspaces into a first-party plugin

This commit is contained in:
LukeGus committed 2026-09-22 10:45:15 -05:00
1 parent 8742c5d381
commit 6f0dff58a3
19 files changed
+275 -32

No files matched your search

@@ -374,3 +374,44 @@ describe("bundled network-topology plugin", () => {
expect(source).toMatch(/export async function deactivate/);
});
});
describe("bundled workspaces plugin", () => {
it("ships a directory with a manifest and both entry points", () => {
const dir = path.join(getBundledPluginsDir(), "workspaces");
expect(fs.existsSync(path.join(dir, "manifest.json"))).toBe(true);
expect(fs.existsSync(path.join(dir, "backend", "index.mjs"))).toBe(true);
expect(fs.existsSync(path.join(dir, "frontend", "index.mjs"))).toBe(true);
});
it("has a manifest that passes the real validator", () => {
const { manifest, errors } = parseManifest(
readBundledManifest("workspaces"),
);
expect(errors).toEqual([]);
expect(manifest?.id).toBe("workspaces");
expect(manifest?.category).toBe("Productivity");
});
it("declares the transport-owner capability and qualifies for the tier", () => {
const { manifest } = parseManifest(readBundledManifest("workspaces"));
expect(manifest?.permissions).toContain(TRANSPORT_OWNER_CAPABILITY);
expect(isFirstParty("workspaces")).toBe(true);
expect(runsInProcess("workspaces", manifest!.permissions)).toBe(true);
});
it("exports activate and deactivate from its backend entry", () => {
const entry = path.join(
getBundledPluginsDir(),
"workspaces",
"backend",
"index.mjs",
);
const source = fs.readFileSync(entry, "utf8");
expect(source).toMatch(/export async function activate/);
expect(source).toMatch(/export async function deactivate/);
});
});
+20 -2
View File
@@ -39,7 +39,7 @@ function manifest(overrides: Record<string, unknown> = {}) {
}
describe("first-party allowlist", () => {
it("contains only ssh-terminal, docker, host-metrics, ai, proxmox, remote-desktop, fleets, automations, network-topology and tailscale", () => {
it("contains only ssh-terminal, docker, host-metrics, ai, proxmox, remote-desktop, fleets, automations, network-topology, tailscale and workspaces", () => {
expect([...FIRST_PARTY_PLUGIN_IDS].sort()).toEqual([
"ai",
"automations",
@@ -51,10 +51,11 @@ describe("first-party allowlist", () => {
"remote-desktop",
"ssh-terminal",
"tailscale",
"workspaces",
]);
});
it("recognises ssh-terminal, docker, host-metrics, ai, proxmox, remote-desktop, fleets, automations, network-topology and tailscale and nothing else", () => {
it("recognises ssh-terminal, docker, host-metrics, ai, proxmox, remote-desktop, fleets, automations, network-topology, tailscale and workspaces and nothing else", () => {
expect(isFirstParty("ssh-terminal")).toBe(true);
expect(isFirstParty("docker")).toBe(true);
expect(isFirstParty("host-metrics")).toBe(true);
@@ -65,6 +66,7 @@ describe("first-party allowlist", () => {
expect(isFirstParty("automations")).toBe(true);
expect(isFirstParty("network-topology")).toBe(true);
expect(isFirstParty("tailscale")).toBe(true);
expect(isFirstParty("workspaces")).toBe(true);
expect(isFirstParty("ssh-terminal-pro")).toBe(false);
expect(isFirstParty("community-plugin")).toBe(false);
expect(isFirstParty("")).toBe(false);
@@ -93,6 +95,9 @@ describe("runsInProcess", () => {
runsInProcess("network-topology", [TRANSPORT_OWNER_CAPABILITY]),
).toBe(true);
expect(runsInProcess("tailscale", [TRANSPORT_OWNER_CAPABILITY])).toBe(true);
expect(runsInProcess("workspaces", [TRANSPORT_OWNER_CAPABILITY])).toBe(
true,
);
// On the list but not asking for it: stays in a worker, so the manifest
// remains an honest description of what the plugin does.
@@ -106,6 +111,7 @@ describe("runsInProcess", () => {
expect(runsInProcess("automations", ["hosts.read"])).toBe(false);
expect(runsInProcess("network-topology", ["hosts.read"])).toBe(false);
expect(runsInProcess("tailscale", ["hosts.read"])).toBe(false);
expect(runsInProcess("workspaces", ["hosts.read"])).toBe(false);
// Asking for it but not on the list.
expect(
@@ -233,6 +239,18 @@ describe("manifest gate on the reserved capability", () => {
expect(parsed?.id).toBe("tailscale");
});
it("allows it for workspaces", () => {
const { manifest: parsed, errors } = parseManifest(
manifest({
id: "workspaces",
permissions: [TRANSPORT_OWNER_CAPABILITY],
}),
);
expect(errors).toEqual([]);
expect(parsed?.id).toBe("workspaces");
});
it("still accepts an ordinary manifest that never mentions it", () => {
const { manifest: parsed, errors } = parseManifest(
manifest({ permissions: ["hosts.read"] }),
@@ -161,8 +161,13 @@ vi.mock("../../../database/repositories/factory.js", () => ({
}),
}));
const { default: router } =
await import("../../../database/routes/workspaces.js");
vi.mock("../../../database/routes/workspace-dispatch.js", () => ({
registerWorkspacesRouter: vi.fn(),
unregisterWorkspacesRouter: vi.fn(),
}));
const { router } =
await import("../../../../../plugins/workspaces/backend/routes.js");
function findLayer(method: string, path: string) {
const stack = (router as unknown as Router).stack as Array<{