fix: close workspace terminals before deletion

This commit is contained in:
Federico Jaramillo Martinez
2026-06-04 21:31:39 +02:00
parent 30fb9602d6
commit 9c3dafc4d4
17 changed files with 415 additions and 38 deletions
@@ -31,6 +31,14 @@ describe("terminal routes", () => {
socket.close();
});
it("closes all terminals for a cwd", async () => {
const response = await app.inject({ method: "DELETE", url: `/terminals?cwd=${encodeURIComponent("/repo/worktree")}` });
expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({ closed: true });
expect(terminals.events).toEqual(["close-cwd:/repo/worktree"]);
});
it("creates and lists terminal command runs with filters", async () => {
const createResponse = await app.inject({
method: "POST",
@@ -77,6 +85,10 @@ class FakeTerminals implements TerminalRouteService {
};
}
closeForCwd(cwd: string): void {
this.events.push(`close-cwd:${cwd}`);
}
close(id: string): void {
this.events.push(`close:${id}`);
}
+11
View File
@@ -7,6 +7,7 @@ import { parseTerminalSize } from "./terminalSize.js";
export interface TerminalRouteService {
list(cwd: string): TerminalInfo[];
create(options: { cwd: string; name?: string; cols?: number; rows?: number }): TerminalInfo;
closeForCwd(cwd: string): void;
close(id: string): void;
attach(id: string, handlers: { output: (data: string, replay: boolean) => void; exit: (exitCode: number | undefined) => void }): () => void;
write(id: string, data: string): void;
@@ -32,6 +33,16 @@ export function registerTerminalRoutes(app: FastifyInstance, terminals: Terminal
}
});
app.delete<{ Querystring: { cwd?: string } }>(`${prefix}/terminals`, (request, reply) => {
if (request.query.cwd === undefined || request.query.cwd === "") return reply.code(400).send({ error: "cwd query parameter is required" });
try {
terminals.closeForCwd(request.query.cwd);
return { closed: true };
} catch (error) {
return reply.code(400).send({ error: error instanceof Error ? error.message : String(error) });
}
});
app.post<{ Body: RunTerminalCommandOptions }>(`${prefix}/terminal-command-runs`, (request, reply) => {
try {
return terminals.runCommand(request.body);
@@ -2,6 +2,20 @@ import { describe, expect, it } from "vitest";
import { TerminalService } from "./terminalService";
describe("TerminalService command runs", () => {
it("closes all terminal records for a cwd", () => {
const service = new TerminalService();
try {
const terminal = service.create({ cwd: process.cwd() });
service.closeForCwd(process.cwd());
expect(service.get(terminal.id)).toBeUndefined();
expect(service.list(process.cwd())).toEqual([]);
} finally {
service.dispose();
}
});
it("tracks dedicated terminal command runs through completion", async () => {
const service = new TerminalService();
try {
+5
View File
@@ -48,6 +48,11 @@ export class TerminalService {
.map(toInfo);
}
closeForCwd(cwd: string): void {
if (cwd === "") throw new Error("cwd is required");
for (const terminal of [...this.terminals.values()].filter((candidate) => candidate.cwd === cwd)) this.close(terminal.id);
}
create(options: { cwd: string; name?: string; cols?: number; rows?: number }): TerminalInfo {
return this.createTerminal({ ...options, shellArgs: [] });
}