History

Migrate legacy terminal history to terminal-scoped log path

- On open, read legacy transcript files and write them to the new terminal-scoped history path - Remove legacy files after migration and warn if cleanup fails - Update tests to assert migration, content preservation, and legacy file removal

Julius Marminge committed 9 months ago

Commit d27b08b1 parent 485747b2 files · +21 −8Browse files

Unified · Split

2 files
  1. apps/server/src/terminalManager.test.ts +5 −2
  2. apps/server/src/terminalManager.ts +16 −6
apps/server/src/terminalManager.test.ts+5−2
@@ -334,15 +334,18 @@ describe("TerminalManager", () => { · 333 unmodified lines
334 manager.dispose();334 manager.dispose();
335 });335 });
336 336
337 it("loads existing legacy transcript filenames and keeps new naming for writes", async () => {337 it("migrates legacy transcript filenames to terminal-scoped history path on open", async () => {
338 const { manager, logsDir } = makeManager();338 const { manager, logsDir } = makeManager();
339 const legacyPath = path.join(logsDir, "thread-1.log");339 const legacyPath = path.join(logsDir, "thread-1.log");
340 const nextPath = historyLogPath(logsDir);
340 fs.writeFileSync(legacyPath, "legacy-line\n", "utf8");341 fs.writeFileSync(legacyPath, "legacy-line\n", "utf8");
341 342
342 const snapshot = await manager.open(openInput());343 const snapshot = await manager.open(openInput());
343 344
344 expect(snapshot.history).toBe("legacy-line\n");345 expect(snapshot.history).toBe("legacy-line\n");
345 expect(fs.existsSync(legacyPath)).toBe(true);346 expect(fs.existsSync(nextPath)).toBe(true);
347 expect(fs.readFileSync(nextPath, "utf8")).toBe("legacy-line\n");
348 expect(fs.existsSync(legacyPath)).toBe(false);
346 349
347 manager.dispose();350 manager.dispose();
348 });351 });
apps/server/src/terminalManager.ts+16−6
@@ -582,12 +582,12 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> { · 581 unmodified lines
582 }582 }
583 583
584 private async readHistory(threadId: string, terminalId: string): Promise<string> {584 private async readHistory(threadId: string, terminalId: string): Promise<string> {
585 const historyPath = this.historyPath(threadId, terminalId);585 const nextPath = this.historyPath(threadId, terminalId);
586 try {586 try {
587 const raw = await fs.promises.readFile(historyPath, "utf8");587 const raw = await fs.promises.readFile(nextPath, "utf8");
588 const capped = capHistory(raw, this.historyLineLimit);588 const capped = capHistory(raw, this.historyLineLimit);
589 if (capped !== raw) {589 if (capped !== raw) {
590 await fs.promises.writeFile(historyPath, capped, "utf8");590 await fs.promises.writeFile(nextPath, capped, "utf8");
591 }591 }
592 return capped;592 return capped;
593 } catch (error) {593 } catch (error) {
@@ -600,12 +600,22 @@ export class TerminalManager extends EventEmitter<TerminalManagerEvents> { · 6 unmodified lines
600 return "";600 return "";
601 }601 }
602 602
603 const legacyPath = this.legacyHistoryPath(threadId);
603 try {604 try {
604 const raw = await fs.promises.readFile(this.legacyHistoryPath(threadId), "utf8");605 const raw = await fs.promises.readFile(legacyPath, "utf8");
605 const capped = capHistory(raw, this.historyLineLimit);606 const capped = capHistory(raw, this.historyLineLimit);
606 if (capped !== raw) {607
607 await fs.promises.writeFile(this.legacyHistoryPath(threadId), capped, "utf8");608 // Migrate legacy transcript filename to the terminal-scoped path.
609 await fs.promises.writeFile(nextPath, capped, "utf8");
610 try {
611 await fs.promises.rm(legacyPath, { force: true });
612 } catch (cleanupError) {
613 this.logger.warn("failed to remove legacy terminal history", {
614 threadId,
615 error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
616 });
608 }617 }
618
609 return capped;619 return capped;
610 } catch (error) {620 } catch (error) {
611 if ((error as NodeJS.ErrnoException).code === "ENOENT") {621 if ((error as NodeJS.ErrnoException).code === "ENOENT") {