Daemon hardening epic
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import stripAnsi from "strip-ansi";
|
||||
import type { TerminalCell } from "../shared/messages.js";
|
||||
import type { TerminalSession } from "./terminal.js";
|
||||
|
||||
export interface CaptureTerminalLinesOptions {
|
||||
start?: number;
|
||||
end?: number;
|
||||
stripAnsi?: boolean;
|
||||
}
|
||||
|
||||
export interface CaptureTerminalLinesResult {
|
||||
lines: string[];
|
||||
totalLines: number;
|
||||
}
|
||||
|
||||
function cellsToPlainText(cells: TerminalCell[], options: { stripAnsi: boolean }): string {
|
||||
const text = cells
|
||||
.map((cell) => cell.char)
|
||||
.join("")
|
||||
.trimEnd();
|
||||
return options.stripAnsi ? stripAnsi(text) : text;
|
||||
}
|
||||
|
||||
function resolveCaptureLineIndex(
|
||||
lineNumber: number | undefined,
|
||||
totalLines: number,
|
||||
fallback: "start" | "end",
|
||||
): number {
|
||||
if (totalLines === 0) {
|
||||
return fallback === "start" ? 0 : -1;
|
||||
}
|
||||
|
||||
const defaultIndex = fallback === "start" ? 0 : totalLines - 1;
|
||||
if (typeof lineNumber !== "number") {
|
||||
return defaultIndex;
|
||||
}
|
||||
|
||||
const resolvedIndex = lineNumber < 0 ? totalLines + lineNumber : lineNumber;
|
||||
if (resolvedIndex < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (resolvedIndex >= totalLines) {
|
||||
return totalLines - 1;
|
||||
}
|
||||
return resolvedIndex;
|
||||
}
|
||||
|
||||
export function captureTerminalLines(
|
||||
terminal: TerminalSession,
|
||||
options: CaptureTerminalLinesOptions = {},
|
||||
): CaptureTerminalLinesResult {
|
||||
const state = terminal.getState();
|
||||
const allLines = [...state.scrollback, ...state.grid].map((cells) =>
|
||||
cellsToPlainText(cells, { stripAnsi: options.stripAnsi ?? true }),
|
||||
);
|
||||
const totalLines = allLines.length;
|
||||
const startIndex = resolveCaptureLineIndex(options.start, totalLines, "start");
|
||||
const endIndex = resolveCaptureLineIndex(options.end, totalLines, "end");
|
||||
|
||||
if (totalLines === 0 || startIndex > endIndex) {
|
||||
return {
|
||||
lines: [],
|
||||
totalLines,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
lines: allLines.slice(startIndex, endIndex + 1),
|
||||
totalLines,
|
||||
};
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { resolveTerminalBackend } from "./terminal-manager-factory.js";
|
||||
|
||||
it("uses the worker terminal backend by default", () => {
|
||||
expect(resolveTerminalBackend({})).toBe("worker");
|
||||
});
|
||||
|
||||
it("allows explicitly opting back into the in-process terminal backend", () => {
|
||||
expect(resolveTerminalBackend({ PASEO_TERMINAL_BACKEND: "in-process" })).toBe("in-process");
|
||||
});
|
||||
@@ -1,18 +1,6 @@
|
||||
import { createTerminalManager, type TerminalManager } from "./terminal-manager.js";
|
||||
import type { TerminalManager } from "./terminal-manager.js";
|
||||
import { createWorkerTerminalManager } from "./worker-terminal-manager.js";
|
||||
|
||||
export type TerminalBackend = "in-process" | "worker";
|
||||
|
||||
export function resolveTerminalBackend(env: NodeJS.ProcessEnv = process.env): TerminalBackend {
|
||||
return env.PASEO_TERMINAL_BACKEND === "in-process" ? "in-process" : "worker";
|
||||
}
|
||||
|
||||
export function createConfiguredTerminalManager(options?: {
|
||||
backend?: TerminalBackend;
|
||||
}): TerminalManager {
|
||||
const backend = options?.backend ?? resolveTerminalBackend();
|
||||
if (backend === "worker") {
|
||||
return createWorkerTerminalManager();
|
||||
}
|
||||
return createTerminalManager();
|
||||
export function createConfiguredTerminalManager(): TerminalManager {
|
||||
return createWorkerTerminalManager();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createTerminalManager } from "./terminal-manager.js";
|
||||
import { captureTerminalLines } from "./terminal.js";
|
||||
import { captureTerminalLines } from "./terminal-capture.js";
|
||||
import type { TerminalSession } from "./terminal.js";
|
||||
import type {
|
||||
TerminalWorkerRequest,
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
TerminalStateSnapshot,
|
||||
} from "./terminal.js";
|
||||
import type { TerminalState } from "../shared/messages.js";
|
||||
import type { CaptureTerminalLinesResult } from "./terminal.js";
|
||||
import type { CaptureTerminalLinesResult } from "./terminal-capture.js";
|
||||
|
||||
export interface WorkerTerminalInfo {
|
||||
id: string;
|
||||
|
||||
@@ -6,9 +6,13 @@ import { tmpdir, userInfo } from "node:os";
|
||||
import { basename, dirname, join } from "node:path";
|
||||
import { createRequire } from "node:module";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import { createExternalProcessEnv } from "../server/paseo-env.js";
|
||||
import type { TerminalCell, TerminalState } from "../shared/messages.js";
|
||||
export {
|
||||
captureTerminalLines,
|
||||
type CaptureTerminalLinesOptions,
|
||||
type CaptureTerminalLinesResult,
|
||||
} from "./terminal-capture.js";
|
||||
|
||||
const { Terminal } = xterm;
|
||||
const require = createRequire(import.meta.url);
|
||||
@@ -96,17 +100,6 @@ interface BuildTerminalEnvironmentInput {
|
||||
zshShellIntegrationDir?: string;
|
||||
}
|
||||
|
||||
export interface CaptureTerminalLinesOptions {
|
||||
start?: number;
|
||||
end?: number;
|
||||
stripAnsi?: boolean;
|
||||
}
|
||||
|
||||
export interface CaptureTerminalLinesResult {
|
||||
lines: string[];
|
||||
totalLines: number;
|
||||
}
|
||||
|
||||
interface EnsureNodePtySpawnHelperExecutableOptions {
|
||||
packageRoot?: string;
|
||||
platform?: NodeJS.Platform;
|
||||
@@ -523,63 +516,6 @@ function extractLastOutputLinesFromText(text: string, limit: number): string[] {
|
||||
return lines.slice(-limit);
|
||||
}
|
||||
|
||||
function cellsToPlainText(cells: TerminalCell[], options: { stripAnsi: boolean }): string {
|
||||
const text = cells
|
||||
.map((cell) => cell.char)
|
||||
.join("")
|
||||
.trimEnd();
|
||||
return options.stripAnsi ? stripAnsi(text) : text;
|
||||
}
|
||||
|
||||
function resolveCaptureLineIndex(
|
||||
lineNumber: number | undefined,
|
||||
totalLines: number,
|
||||
fallback: "start" | "end",
|
||||
): number {
|
||||
if (totalLines === 0) {
|
||||
return fallback === "start" ? 0 : -1;
|
||||
}
|
||||
|
||||
const defaultIndex = fallback === "start" ? 0 : totalLines - 1;
|
||||
if (typeof lineNumber !== "number") {
|
||||
return defaultIndex;
|
||||
}
|
||||
|
||||
const resolvedIndex = lineNumber < 0 ? totalLines + lineNumber : lineNumber;
|
||||
if (resolvedIndex < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (resolvedIndex >= totalLines) {
|
||||
return totalLines - 1;
|
||||
}
|
||||
return resolvedIndex;
|
||||
}
|
||||
|
||||
export function captureTerminalLines(
|
||||
terminal: TerminalSession,
|
||||
options: CaptureTerminalLinesOptions = {},
|
||||
): CaptureTerminalLinesResult {
|
||||
const state = terminal.getState();
|
||||
const allLines = [...state.scrollback, ...state.grid].map((cells) =>
|
||||
cellsToPlainText(cells, { stripAnsi: options.stripAnsi ?? true }),
|
||||
);
|
||||
const totalLines = allLines.length;
|
||||
const startIndex = resolveCaptureLineIndex(options.start, totalLines, "start");
|
||||
const endIndex = resolveCaptureLineIndex(options.end, totalLines, "end");
|
||||
|
||||
if (totalLines === 0 || startIndex > endIndex) {
|
||||
return {
|
||||
lines: [],
|
||||
totalLines,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
lines: allLines.slice(startIndex, endIndex + 1),
|
||||
totalLines,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createTerminal(options: CreateTerminalOptions): Promise<TerminalSession> {
|
||||
const {
|
||||
cwd,
|
||||
|
||||
Reference in New Issue
Block a user